diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 33d4fa73e..475329319 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -764,6 +764,14 @@ List get videoSettings => [ } }, ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: 'CDN 测速', + leading: const Icon(Icons.speed), + subtitle: '测速通过加载视频实现,注意流量消耗', + setKey: SettingBoxKey.cdnSpeedTest, + defaultVal: true, + ), SettingsModel( settingsType: SettingsType.sw1tch, title: '音频不跟随 CDN 设置', diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index dd5a3b310..acb2314e4 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -1,4 +1,12 @@ +import 'package:PiliPalaX/http/video.dart'; +import 'package:PiliPalaX/models/video/play/CDN.dart'; +import 'package:PiliPalaX/models/video/play/url.dart'; +import 'package:PiliPalaX/utils/extension.dart'; +import 'package:PiliPalaX/utils/storage.dart'; +import 'package:PiliPalaX/utils/video_utils.dart'; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; +import 'package:get/get_utils/get_utils.dart'; class SelectDialog extends StatefulWidget { final T value; @@ -17,11 +25,75 @@ class SelectDialog extends StatefulWidget { class _SelectDialogState extends State> { late T _tempValue; + late List _cdnResList; + late final cdnSpeedTest = GStorage.cdnSpeedTest; @override void initState() { super.initState(); _tempValue = widget.value; + if (widget.title == 'CDN 设置' && cdnSpeedTest) { + _cdnResList = List.generate(widget.values.length, (_) => null); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + try { + dynamic result = await VideoHttp.videoUrl( + cid: 196018899, + bvid: 'BV1fK4y1t7hj', + ); + if (result['status']) { + VideoItem videoItem = result['data'].dash.video.first; + + for (CDNService item in CDNService.values) { + if (mounted.not) { + break; + } + String videoUrl = VideoUtils.getCdnUrl(videoItem, item.code); + Dio dio = Dio() + ..options.headers['referer'] = 'https://www.bilibili.com/'; + int maxSize = 8 * 1024 * 1024; + int downloaded = 0; + Stopwatch stopwatch = Stopwatch()..start(); + try { + await dio.get( + videoUrl, + onReceiveProgress: (int count, int total) { + downloaded += count; + if (stopwatch.elapsedMilliseconds > 15 * 1000) { + stopwatch.stop(); + dio.close(force: true); + } + if (downloaded >= maxSize) { + stopwatch.stop(); + dio.close(force: true); + _cdnResList[item.index] = + (maxSize / stopwatch.elapsedMilliseconds / 1000) + .toPrecision(2); + if (mounted) { + setState(() {}); + } + } + }, + ); + } catch (e) { + stopwatch.stop(); + if (_cdnResList[item.index] == null) { + _cdnResList[item.index] = '测速失败: $e'; + if (mounted) { + setState(() {}); + } + } + } + if (stopwatch.isRunning) { + stopwatch.stop(); + } + } + } + } catch (e) { + debugPrint('failed to check: $e'); + } + }); + } } @override @@ -39,22 +111,31 @@ class _SelectDialogState extends State> { return SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - children: [ - for (var i in widget.values) ...[ - RadioListTile( - dense: true, - value: i['value'], - title: Text(i['title'], style: titleStyle), - groupValue: _tempValue, - onChanged: (value) { - setState(() { - _tempValue = value as T; - }); - Navigator.pop(context, _tempValue); - }, - ), - ] - ], + children: List.generate( + widget.values.length, + (index) => RadioListTile( + dense: true, + value: widget.values[index]['value'], + title: Text(widget.values[index]['title'], style: titleStyle), + subtitle: widget.title == 'CDN 设置' && cdnSpeedTest + ? Text( + _cdnResList[index] is double + ? '${_cdnResList[index]} MB/s' + : _cdnResList[index] is String + ? _cdnResList[index] + : '---', + style: TextStyle(fontSize: 13), + ) + : null, + groupValue: _tempValue, + onChanged: (value) { + setState(() { + _tempValue = value as T; + }); + Navigator.pop(context, _tempValue); + }, + ), + ), ), ); }), diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 5e674b0d7..78aade7a1 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -287,6 +287,9 @@ class GStorage { static bool get continuePlayingPart => setting.get(SettingBoxKey.continuePlayingPart, defaultValue: true); + static bool get cdnSpeedTest => + setting.get(SettingBoxKey.cdnSpeedTest, defaultValue: true); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -495,6 +498,7 @@ class SettingBoxKey { subtitleBgOpaticy = 'subtitleBgOpaticy', badCertificateCallback = 'badCertificateCallback', continuePlayingPart = 'continuePlayingPart', + cdnSpeedTest = 'cdnSpeedTest', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock', diff --git a/lib/utils/video_utils.dart b/lib/utils/video_utils.dart index c76eeffb4..37d8ebccb 100644 --- a/lib/utils/video_utils.dart +++ b/lib/utils/video_utils.dart @@ -12,10 +12,10 @@ class VideoUtils { RegExp(r'^https?://\d{1,3}\.\d{1,3}').hasMatch(url); } - static String getCdnUrl(dynamic item) { + static String getCdnUrl(dynamic item, [defaultCDNService]) { String? backupUrl; String? videoUrl; - String defaultCDNService = GStorage.setting + defaultCDNService ??= GStorage.setting .get(SettingBoxKey.CDNService, defaultValue: CDNService.backupUrl.code); if (item is AudioItem) { if (GStorage.setting