feat: cellular video/audio qa

Closes #52

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-26 13:35:35 +08:00
parent ed3036cc43
commit 5d1c1494dd
8 changed files with 185 additions and 99 deletions

View File

@@ -1,6 +1,6 @@
// ignore_for_file: avoid_print
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -77,29 +77,8 @@ class ApiInterceptor extends Interceptor {
case DioExceptionType.sendTimeout:
return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown:
final String res = await checkConnect();
final String res = (await Utils.checkConnectivity()).title;
return '$res网络异常 ${error.error}';
}
}
static Future<String> checkConnect() async {
final List<ConnectivityResult> connectivityResult =
await Connectivity().checkConnectivity();
switch (connectivityResult.first) {
case ConnectivityResult.mobile:
return '流量';
case ConnectivityResult.wifi:
return 'Wi-Fi';
case ConnectivityResult.ethernet:
return '局域';
case ConnectivityResult.vpn:
return '代理';
case ConnectivityResult.other:
return '其他';
case ConnectivityResult.none:
return '';
default:
return '';
}
}
}

View File

@@ -68,12 +68,13 @@ class _MainAppState extends State<MainApp>
}
void _checkDefaultSearch([bool shouldCheck = false]) {
if (shouldCheck &&
_mainController.pages[_mainController.pageController.page?.round() ?? 0]
is! HomePage) {
return;
}
if (_homeController.enableSearchWord) {
if (shouldCheck &&
_mainController
.pages[_mainController.pageController.page?.round() ?? 0]
is! HomePage) {
return;
}
int now = DateTime.now().millisecondsSinceEpoch;
if (now - _homeController.lateCheckAt >= 5 * 60 * 1000) {
_homeController.lateCheckAt = now;

View File

@@ -148,11 +148,10 @@ class _ExtraSettingState extends State<ExtraSetting> {
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.shield),
const Icon(Icons.shield_outlined),
Icon(
Icons.play_arrow_rounded,
size: 18,
color: Theme.of(context).colorScheme.surface,
size: 15,
),
],
),

View File

@@ -20,7 +20,9 @@ class VideoSetting extends StatefulWidget {
class _VideoSettingState extends State<VideoSetting> {
Box setting = GStorage.setting;
late dynamic defaultVideoQa;
late dynamic defaultVideoQaCellular;
late dynamic defaultAudioQa;
late dynamic defaultAudioQaCellular;
late dynamic defaultDecode;
late dynamic secondDecode;
late dynamic hardwareDecoding;
@@ -30,20 +32,42 @@ class _VideoSettingState extends State<VideoSetting> {
@override
void initState() {
super.initState();
defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
defaultValue: VideoQuality.values.last.code);
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.values.last.code);
defaultDecode = setting.get(SettingBoxKey.defaultDecode,
defaultValue: VideoDecodeFormats.values.last.code);
secondDecode = setting.get(SettingBoxKey.secondDecode,
defaultValue: VideoDecodeFormats.values[1].code);
hardwareDecoding = setting.get(SettingBoxKey.hardwareDecoding,
defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto');
videoSync =
setting.get(SettingBoxKey.videoSync, defaultValue: 'display-resample');
defaultCDNService = setting.get(SettingBoxKey.CDNService,
defaultValue: CDNService.backupUrl.code);
defaultVideoQa = setting.get(
SettingBoxKey.defaultVideoQa,
defaultValue: VideoQuality.values.last.code,
);
defaultVideoQaCellular = setting.get(
SettingBoxKey.defaultVideoQaCellular,
defaultValue: VideoQuality.high1080.code,
);
defaultAudioQa = setting.get(
SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.values.last.code,
);
defaultAudioQaCellular = setting.get(
SettingBoxKey.defaultAudioQaCellular,
defaultValue: AudioQuality.k192.code,
);
defaultDecode = setting.get(
SettingBoxKey.defaultDecode,
defaultValue: VideoDecodeFormats.values.last.code,
);
secondDecode = setting.get(
SettingBoxKey.secondDecode,
defaultValue: VideoDecodeFormats.values[1].code,
);
hardwareDecoding = setting.get(
SettingBoxKey.hardwareDecoding,
defaultValue: Platform.isAndroid ? 'auto-safe' : 'auto',
);
videoSync = setting.get(
SettingBoxKey.videoSync,
defaultValue: 'display-resample',
);
defaultCDNService = setting.get(
SettingBoxKey.CDNService,
defaultValue: CDNService.backupUrl.code,
);
}
@override
@@ -79,26 +103,25 @@ class _VideoSettingState extends State<VideoSetting> {
defaultVal: true,
),
ListTile(
enabled: false,
onTap: null,
title: Text("b站定向流量支持", style: titleStyle),
title: Text("B站定向流量支持", style: titleStyle),
subtitle:
Text("若套餐含b站定向流量,则会自动使用。可查阅运营商的流量记录确认。", style: subTitleStyle),
Text("若套餐含B站定向流量,则会自动使用。可查阅运营商的流量记录确认。", style: subTitleStyle),
leading: const Icon(Icons.perm_data_setting_outlined),
trailing: Transform.scale(
alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
scale: 0.8,
child: Switch(
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.isNotEmpty &&
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
}),
value: true,
onChanged: null),
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.isNotEmpty &&
states.first == WidgetState.selected) {
return const Icon(Icons.lock_outline_rounded);
}
return null; // All other states will use the default thumbIcon.
}),
value: true,
onChanged: (_) {},
),
),
),
ListTile(
@@ -147,11 +170,12 @@ class _VideoSettingState extends State<VideoSetting> {
context: context,
builder: (context) {
return SelectDialog<int>(
title: '默认画质',
value: defaultVideoQa,
values: VideoQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList());
title: '默认画质',
value: defaultVideoQa,
values: VideoQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList(),
);
},
);
if (result != null) {
@@ -161,6 +185,34 @@ class _VideoSettingState extends State<VideoSetting> {
}
},
),
ListTile(
dense: false,
title: Text('蜂窝网络画质', style: titleStyle),
leading: const Icon(Icons.video_settings_outlined),
subtitle: Text(
'当前画质:${VideoQualityCode.fromCode(defaultVideoQaCellular)!.description!}',
style: subTitleStyle,
),
onTap: () async {
int? result = await showDialog(
context: context,
builder: (context) {
return SelectDialog<int>(
title: '蜂窝网络画质',
value: defaultVideoQaCellular,
values: VideoQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList(),
);
},
);
if (result != null) {
defaultVideoQaCellular = result;
setting.put(SettingBoxKey.defaultVideoQaCellular, result);
setState(() {});
}
},
),
ListTile(
dense: false,
title: Text('默认音质', style: titleStyle),
@@ -174,11 +226,12 @@ class _VideoSettingState extends State<VideoSetting> {
context: context,
builder: (context) {
return SelectDialog<int>(
title: '默认音质',
value: defaultAudioQa,
values: AudioQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList());
title: '默认音质',
value: defaultAudioQa,
values: AudioQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList(),
);
},
);
if (result != null) {
@@ -188,6 +241,34 @@ class _VideoSettingState extends State<VideoSetting> {
}
},
),
ListTile(
dense: false,
title: Text('蜂窝网络音质', style: titleStyle),
leading: const Icon(Icons.music_video_outlined),
subtitle: Text(
'当前音质:${AudioQualityCode.fromCode(defaultAudioQaCellular)!.description!}',
style: subTitleStyle,
),
onTap: () async {
int? result = await showDialog(
context: context,
builder: (context) {
return SelectDialog<int>(
title: '蜂窝网络音质',
value: defaultAudioQaCellular,
values: AudioQuality.values.reversed.map((e) {
return {'title': e.description, 'value': e.code};
}).toList(),
);
},
);
if (result != null) {
defaultAudioQaCellular = result;
setting.put(SettingBoxKey.defaultAudioQaCellular, result);
setState(() {});
}
},
),
ListTile(
dense: false,
title: Text('首选解码格式', style: titleStyle),

View File

@@ -18,6 +18,7 @@ import 'package:PiliPalaX/pages/video/detail/related/controller.dart';
import 'package:PiliPalaX/pages/video/detail/reply/controller.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:canvas_danmaku/models/danmaku_content_item.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart';
@@ -206,7 +207,7 @@ class VideoDetailController extends GetxController
late PreferredSizeWidget headerControl;
// late bool enableCDN;
late int? cacheVideoQa;
int? cacheVideoQa;
late String cacheDecode;
late String cacheSecondDecode;
late int cacheAudioQa;
@@ -303,16 +304,11 @@ class VideoDetailController extends GetxController
// CDN优化
// enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);
// 预设的画质
cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
defaultValue: VideoQuality.values.last.code);
// 预设的解码格式
cacheDecode = setting.get(SettingBoxKey.defaultDecode,
defaultValue: VideoDecodeFormats.values.last.code);
cacheSecondDecode = setting.get(SettingBoxKey.secondDecode,
defaultValue: VideoDecodeFormats.values[1].code);
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code);
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
enableSponsorBlock =
setting.get(SettingBoxKey.enableSponsorBlock, defaultValue: false);
@@ -1013,6 +1009,21 @@ class VideoDetailController extends GetxController
// 视频链接
Future queryVideoUrl() async {
if (cacheVideoQa == null) {
await Utils.checkConnectivity().then((res) {
cacheVideoQa = res == ConnectivityResult.mobile
? setting.get(SettingBoxKey.defaultVideoQaCellular,
defaultValue: VideoQuality.high1080.code)
: setting.get(SettingBoxKey.defaultVideoQa,
defaultValue: VideoQuality.values.last.code);
cacheAudioQa = res == ConnectivityResult.mobile
? setting.get(SettingBoxKey.defaultAudioQaCellular,
defaultValue: AudioQuality.k192.code)
: setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code);
});
}
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
if (result['status']) {
data = result['data'];

View File

@@ -808,21 +808,21 @@ class _HeaderControlState extends State<HeaderControl> {
videoFormat[i].quality) {
return;
}
Get.back();
final int quality = videoFormat[i].quality!;
widget.videoDetailCtr!.currentVideoQa =
VideoQualityCode.fromCode(quality)!;
String oldQualityDesc =
VideoQualityCode.fromCode(setting.get(
SettingBoxKey.defaultVideoQa,
defaultValue:
VideoQuality.values.last.code))!
.description;
setting.put(
SettingBoxKey.defaultVideoQa, quality);
SmartDialog.showToast(
"默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}");
widget.videoDetailCtr!.updatePlayer();
Get.back();
// String oldQualityDesc =
// VideoQualityCode.fromCode(setting.get(
// SettingBoxKey.defaultVideoQa,
// defaultValue:
// VideoQuality.values.last.code))!
// .description;
// setting.put(
// SettingBoxKey.defaultVideoQa, quality);
// SmartDialog.showToast(
// "默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}");
},
// 可能包含会员解锁画质
enabled: i >= totalQaSam - userfulQaSam,
@@ -897,20 +897,20 @@ class _HeaderControlState extends State<HeaderControl> {
if (currentAudioQa.code == i.id) {
return;
}
Get.back();
final int quality = i.id!;
widget.videoDetailCtr!.currentAudioQa =
AudioQualityCode.fromCode(quality)!;
String oldQualityDesc = AudioQualityCode.fromCode(
setting.get(SettingBoxKey.defaultAudioQa,
defaultValue:
AudioQuality.values.last.code))!
.description;
setting.put(
SettingBoxKey.defaultAudioQa, quality);
SmartDialog.showToast(
"默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}");
widget.videoDetailCtr!.updatePlayer();
Get.back();
// String oldQualityDesc = AudioQualityCode.fromCode(
// setting.get(SettingBoxKey.defaultAudioQa,
// defaultValue:
// AudioQuality.values.last.code))!
// .description;
// setting.put(
// SettingBoxKey.defaultAudioQa, quality);
// SmartDialog.showToast(
// "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}");
},
contentPadding:
const EdgeInsets.only(left: 20, right: 20),
@@ -1763,11 +1763,15 @@ class _HeaderControlState extends State<HeaderControl> {
icon: Stack(
alignment: Alignment.center,
children: [
Icon(Icons.shield, size: 18),
Icon(
Icons.shield_outlined,
size: 19,
color: Colors.white,
),
Icon(
Icons.play_arrow_rounded,
size: 17,
color: Colors.black87,
size: 13,
color: Colors.white,
),
],
),

View File

@@ -257,7 +257,9 @@ class SettingBoxKey {
autoUpgradeEnable = 'autoUpgradeEnable',
feedBackEnable = 'feedBackEnable',
defaultVideoQa = 'defaultVideoQa',
defaultVideoQaCellular = 'defaultVideoQaCellular',
defaultAudioQa = 'defaultAudioQa',
defaultAudioQaCellular = 'defaultAudioQaCellular',
autoPlayEnable = 'autoPlayEnable',
fullScreenMode = 'fullScreenMode',
defaultDecode = 'defaultDecode',

View File

@@ -17,6 +17,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/widgets/group_panel.da
import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/login.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:crypto/crypto.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
@@ -30,9 +31,17 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;
extension ConnectivityResultExt on ConnectivityResult {
String get title => ['蓝牙', 'Wi-Fi', '局域', '流量', '', '代理', '其他'][index];
}
class Utils {
static final Random random = Random();
static Future<ConnectivityResult> checkConnectivity() async {
return (await Connectivity().checkConnectivity()).first;
}
static Future<dynamic> getWwebid(mid) async {
try {
dynamic response =