diff --git a/lib/common/widgets/loading_widget.dart b/lib/common/widgets/loading_widget.dart new file mode 100644 index 000000000..b2751017f --- /dev/null +++ b/lib/common/widgets/loading_widget.dart @@ -0,0 +1,54 @@ +import 'dart:math'; + +import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class LoadingWidget extends StatelessWidget { + const LoadingWidget({ + super.key, + this.msg = 'loading...', + required this.progress, + }); + + ///loading msg + final String msg; + final RxDouble progress; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final onSurfaceVariant = theme.colorScheme.onSurfaceVariant; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), + decoration: BoxDecoration( + color: theme.dialogTheme.backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + //loading animation + RepaintBoundary.wrap( + Obx( + () => CustomPaint( + size: const Size.square(40), + painter: ArcPainter( + color: onSurfaceVariant, + strokeWidth: 3, + sweepAngle: progress.value * 2 * pi, + ), + ), + ), + 0, + ), + //msg + Padding( + padding: const EdgeInsets.only(top: 20), + child: Text(msg, style: TextStyle(color: onSurfaceVariant)), + ), + ], + ), + ); + } +} diff --git a/lib/models/common/audio_normalization.dart b/lib/models/common/audio_normalization.dart new file mode 100644 index 000000000..43534f33a --- /dev/null +++ b/lib/models/common/audio_normalization.dart @@ -0,0 +1,10 @@ +enum AudioNormalization { + disable('禁用'), + // ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7 + dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'), + custom('自定义参数'); + + final String title; + final String param; + const AudioNormalization(this.title, [this.param = '']); +} diff --git a/lib/models/common/video/video_decode_type.dart b/lib/models/common/video/video_decode_type.dart index 904b0928d..4825c90cf 100644 --- a/lib/models/common/video/video_decode_type.dart +++ b/lib/models/common/video/video_decode_type.dart @@ -1,34 +1,19 @@ // ignore_for_file: constant_identifier_names enum VideoDecodeFormatType { - DVH1, - AV1, - HEVC, - AVC, -} - -extension VideoDecodeFormatTypeExt on VideoDecodeFormatType { - String get description => const ['DVH1', 'AV1', 'HEVC', 'AVC'][index]; - - static const List _codeList = ['dvh1', 'av01', 'hev1', 'avc1']; - String get code => _codeList[index]; - - static VideoDecodeFormatType? fromCode(String code) { - final index = _codeList.indexOf(code); - if (index != -1) { - return VideoDecodeFormatType.values[index]; - } - return null; - } - - static VideoDecodeFormatType? fromString(String val) { - var result = VideoDecodeFormatType.values.first; - for (var i in _codeList) { - if (val.startsWith(i)) { - result = VideoDecodeFormatType.values[_codeList.indexOf(i)]; - break; - } - } - return result; - } + DVH1('dvh1'), + AV1('av01'), + HEVC('hev1'), + AVC('avc1'); + + String get description => name; + final String code; + + const VideoDecodeFormatType(this.code); + + static VideoDecodeFormatType fromCode(String code) => + values.firstWhere((i) => i.code == code); + + static VideoDecodeFormatType fromString(String val) => + values.firstWhere((i) => val.startsWith(i.code)); } diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart index 159632872..8cac419ff 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/fav.dart'; +import 'package:PiliPlus/models/common/audio_normalization.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/common/member/tab_type.dart'; import 'package:PiliPlus/models/common/reply/reply_sort_type.dart'; @@ -438,6 +439,94 @@ List get extraSettings => [ } catch (_) {} }, ), + SettingsModel( + settingsType: SettingsType.normal, + title: '音量均衡', + setKey: SettingBoxKey.audioNormalization, + leading: const Icon(Icons.multitrack_audio), + getSubtitle: () { + String audioNormalization = Pref.audioNormalization; + // TODO: remove next version + if (audioNormalization == '2') { + GStorage.setting.put(SettingBoxKey.audioNormalization, '1'); + audioNormalization = '1'; + } + audioNormalization = switch (audioNormalization) { + '0' => AudioNormalization.disable.title, + '1' => AudioNormalization.dynaudnorm.title, + _ => audioNormalization, + }; + return '当前:「$audioNormalization」'; + }, + onTap: (setState) async { + String? result = await showDialog( + context: Get.context!, + builder: (context) { + String audioNormalization = Pref.audioNormalization; + final values = {'0', '1', audioNormalization, '2'}; + return SelectDialog( + title: '音量均衡', + value: audioNormalization, + values: values + .map( + (e) => ( + e, + switch (e) { + '0' => AudioNormalization.disable.title, + '1' => AudioNormalization.dynaudnorm.title, + '2' => AudioNormalization.custom.title, + _ => e, + }, + ), + ) + .toList(), + ); + }, + ); + if (result != null) { + if (result == '2') { + String param = ''; + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('自定义参数'), + content: TextField( + autofocus: true, + onChanged: (value) => param = value, + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () async { + Get.back(); + await GStorage.setting.put( + SettingBoxKey.audioNormalization, + param, + ); + setState(); + }, + child: const Text('确定'), + ), + ], + ); + }, + ); + } else { + await GStorage.setting.put(SettingBoxKey.audioNormalization, result); + setState(); + } + } + }, + ), SettingsModel( settingsType: SettingsType.normal, title: '超分辨率', diff --git a/lib/pages/setting/models/video_settings.dart b/lib/pages/setting/models/video_settings.dart index 2dd73cf5c..d52f1382d 100644 --- a/lib/pages/setting/models/video_settings.dart +++ b/lib/pages/setting/models/video_settings.dart @@ -242,7 +242,7 @@ List get videoSettings => [ title: '首选解码格式', leading: const Icon(Icons.movie_creation_outlined), getSubtitle: () => - '首选解码格式:${VideoDecodeFormatTypeExt.fromCode(Pref.defaultDecode)!.description},请根据设备支持情况与需求调整', + '首选解码格式:${VideoDecodeFormatType.fromCode(Pref.defaultDecode).description},请根据设备支持情况与需求调整', onTap: (setState) async { String? result = await showDialog( context: Get.context!, @@ -266,7 +266,7 @@ List get videoSettings => [ settingsType: SettingsType.normal, title: '次选解码格式', getSubtitle: () => - '非杜比视频次选:${VideoDecodeFormatTypeExt.fromCode(Pref.secondDecode)!.description},仍无则选择首个提供的解码格式', + '非杜比视频次选:${VideoDecodeFormatType.fromCode(Pref.secondDecode).description},仍无则选择首个提供的解码格式', leading: const Icon(Icons.swap_horizontal_circle_outlined), onTap: (setState) async { String? result = await showDialog( diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index b9d9204e0..cca8e9542 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -56,12 +56,12 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:get/get.dart' hide ContextExtensionss; import 'package:get/get_navigation/src/dialog/dialog_route.dart'; import 'package:hive/hive.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:media_kit/media_kit.dart'; +import 'package:volume_controller/volume_controller.dart'; class VideoDetailController extends GetxController with GetTickerProviderStateMixin { @@ -993,6 +993,33 @@ class VideoDetailController extends GetxController } } + VideoItem findVideoByQa(int qa) { + /// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl + final videoList = data.dash!.video!.where((i) => i.id == qa).toList(); + + final currentDecodeFormats = this.currentDecodeFormats.code; + final defaultDecodeFormats = VideoDecodeFormatType.fromString( + cacheDecode, + ).code; + final secondDecodeFormats = VideoDecodeFormatType.fromString( + cacheSecondDecode, + ).code; + + VideoItem? video; + for (var i in videoList) { + final codec = i.codecs!; + if (codec.startsWith(currentDecodeFormats)) { + video = i; + break; + } else if (codec.startsWith(defaultDecodeFormats)) { + video = i; + } else if (video == null && codec.startsWith(secondDecodeFormats)) { + video = i; + } + } + return video ?? videoList.first; + } + /// 更新画质、音质 void updatePlayer() { autoPlay.value = true; @@ -1001,76 +1028,17 @@ class VideoDetailController extends GetxController plPlayerController.isBuffering.value = false; plPlayerController.buffered.value = Duration.zero; - /// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl - List videoList = data.dash!.video! - .where((i) => i.id == currentVideoQa.value.code) - .toList(); - - final List supportDecodeFormats = videoList - .map((e) => e.codecs!) - .toList(); - VideoDecodeFormatType defaultDecodeFormats = - VideoDecodeFormatTypeExt.fromString(cacheDecode)!; - VideoDecodeFormatType secondDecodeFormats = - VideoDecodeFormatTypeExt.fromString(cacheSecondDecode)!; - try { - // 当前视频没有对应格式返回第一个 - int flag = 0; - for (var i in supportDecodeFormats) { - if (i.startsWith(currentDecodeFormats.code)) { - flag = 1; - break; - } else if (i.startsWith(defaultDecodeFormats.code)) { - flag = 2; - } else if (i.startsWith(secondDecodeFormats.code)) { - if (flag == 0) { - flag = 4; - } - } - } - if (flag == 1) { - //currentDecodeFormats - firstVideo = videoList.firstWhere( - (i) => i.codecs!.startsWith(currentDecodeFormats.code), - orElse: () => videoList.first, - ); - } else { - if (currentVideoQa.value == VideoQuality.dolbyVision) { - currentDecodeFormats = VideoDecodeFormatTypeExt.fromString( - videoList.first.codecs!, - )!; - firstVideo = videoList.first; - } else if (flag == 2) { - //defaultDecodeFormats - currentDecodeFormats = defaultDecodeFormats; - firstVideo = videoList.firstWhere( - (i) => i.codecs!.startsWith(defaultDecodeFormats.code), - orElse: () => videoList.first, - ); - } else if (flag == 4) { - //secondDecodeFormats - currentDecodeFormats = secondDecodeFormats; - firstVideo = videoList.firstWhere( - (i) => i.codecs!.startsWith(secondDecodeFormats.code), - orElse: () => videoList.first, - ); - } else if (flag == 0) { - currentDecodeFormats = VideoDecodeFormatTypeExt.fromString( - supportDecodeFormats.first, - )!; - firstVideo = videoList.first; - } - } - } catch (err) { - SmartDialog.showToast('DecodeFormats error: $err'); + final video = findVideoByQa(currentVideoQa.value.code); + if (firstVideo.codecs != video.codecs) { + currentDecodeFormats = VideoDecodeFormatType.fromString(video.codecs!); } - - videoUrl = firstVideo.baseUrl!; + firstVideo = video; + videoUrl = video.baseUrl!; /// 根据currentAudioQa 重新设置audioUrl if (currentAudioQa != null) { - final AudioItem firstAudio = data.dash!.audio!.firstWhere( - (AudioItem i) => i.id == currentAudioQa!.code, + final firstAudio = data.dash!.audio!.firstWhere( + (i) => i.id == currentAudioQa!.code, orElse: () => data.dash!.audio!.first, ); audioUrl = firstAudio.baseUrl ?? ''; @@ -1204,7 +1172,7 @@ class VideoDetailController extends GetxController quality: VideoQuality.fromCode(data.quality!), ); setVideoHeight(); - currentDecodeFormats = VideoDecodeFormatTypeExt.fromString('avc1')!; + currentDecodeFormats = VideoDecodeFormatType.fromString('avc1'); currentVideoQa = Rx(VideoQuality.fromCode(data.quality!)); if (autoPlay.value || plPlayerController.preInitPlayer) { await playerInit(); @@ -1256,9 +1224,9 @@ class VideoDetailController extends GetxController ) .codecs!; // 默认从设置中取AV1 - currentDecodeFormats = VideoDecodeFormatTypeExt.fromString(cacheDecode)!; + currentDecodeFormats = VideoDecodeFormatType.fromString(cacheDecode); VideoDecodeFormatType secondDecodeFormats = - VideoDecodeFormatTypeExt.fromString(cacheSecondDecode)!; + VideoDecodeFormatType.fromString(cacheSecondDecode); // 当前视频没有对应格式返回第一个 int flag = 0; for (var i in supportDecodeFormats) { @@ -1272,9 +1240,9 @@ class VideoDetailController extends GetxController if (flag == 2) { currentDecodeFormats = secondDecodeFormats; } else if (flag == 0) { - currentDecodeFormats = VideoDecodeFormatTypeExt.fromString( + currentDecodeFormats = VideoDecodeFormatType.fromString( supportDecodeFormats.first, - )!; + ); } /// 取出符合当前解码格式的videoItem @@ -1539,7 +1507,7 @@ class VideoDetailController extends GetxController if (idx == 0) { if (preference == SubtitlePrefType.on || (preference == SubtitlePrefType.auto && - (await FlutterVolumeController.getVolume() ?? 0) <= 0)) { + await VolumeController.instance.getVolume() <= 0)) { idx = 1; } } diff --git a/lib/pages/video/introduction/ugc/widgets/action_item.dart b/lib/pages/video/introduction/ugc/widgets/action_item.dart index bc7cfe8c5..d84059799 100644 --- a/lib/pages/video/introduction/ugc/widgets/action_item.dart +++ b/lib/pages/video/introduction/ugc/widgets/action_item.dart @@ -54,7 +54,7 @@ class ActionItem extends StatelessWidget { animation: animation!, builder: (context, child) => CustomPaint( size: const Size.square(28), - painter: _ArcPainter( + painter: ArcPainter( color: primary, sweepAngle: animation!.value, ), @@ -110,13 +110,15 @@ class ActionItem extends StatelessWidget { } } -class _ArcPainter extends CustomPainter { - const _ArcPainter({ +class ArcPainter extends CustomPainter { + const ArcPainter({ required this.color, required this.sweepAngle, + this.strokeWidth = 2, }); final Color color; final double sweepAngle; + final double strokeWidth; @override void paint(Canvas canvas, Size size) { @@ -126,7 +128,7 @@ class _ArcPainter extends CustomPainter { final paint = Paint() ..color = color - ..strokeWidth = 2 + ..strokeWidth = strokeWidth ..style = PaintingStyle.stroke; final rect = Rect.fromCircle( @@ -140,7 +142,7 @@ class _ArcPainter extends CustomPainter { } @override - bool shouldRepaint(covariant _ArcPainter oldDelegate) { + bool shouldRepaint(covariant ArcPainter oldDelegate) { return sweepAngle != oldDelegate.sweepAngle || color != oldDelegate.color; } } diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 6d8be8f84..67685b595 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -37,6 +37,154 @@ class PostPanel extends CommonCollapseSlidePage { @override State createState() => _PostPanelState(); + + static void updateSegment({ + required bool isFirst, + required PostSegmentModel item, + required double value, + }) { + if (isFirst) { + item.segment.first = value; + } else { + item.segment.second = value; + } + if (item.category == SegmentType.poi_highlight || + item.actionType == ActionType.full) { + item.segment.second = value; + } + } + + static Widget segmentWidget( + ThemeData theme, { + required PostSegmentModel item, + required double currentPos, + required double videoDuration, + }) { + List segment(BuildContext context, bool isFirst) { + String value = DurationUtil.formatDuration( + isFirst ? item.segment.first : item.segment.second, + ); + return [ + Text( + '${isFirst ? '开始' : '结束'}: $value', + ), + iconButton( + context: context, + size: 26, + tooltip: '设为当前', + icon: Icons.my_location, + onPressed: () { + updateSegment( + isFirst: isFirst, + item: item, + value: currentPos, + ); + (context as Element).markNeedsBuild(); + }, + ), + iconButton( + context: context, + size: 26, + tooltip: isFirst ? '视频开头' : '视频结尾', + icon: isFirst ? Icons.first_page : Icons.last_page, + onPressed: () { + updateSegment( + isFirst: isFirst, + item: item, + value: isFirst ? 0 : videoDuration, + ); + (context as Element).markNeedsBuild(); + }, + ), + iconButton( + context: context, + size: 26, + tooltip: '编辑', + icon: Icons.edit, + onPressed: () { + showDialog( + context: context, + builder: (context) { + String initV = value; + return AlertDialog( + content: TextFormField( + initialValue: value, + autofocus: true, + onChanged: (value) => initV = value, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[\d:.]+')), + ], + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle(color: theme.colorScheme.outline), + ), + ), + TextButton( + onPressed: () => Get.back(result: initV), + child: const Text('确定'), + ), + ], + ); + }, + ).then((res) { + if (res != null) { + try { + List split = res + .split(':') + .reversed + .map(num.parse) + .toList(); + double duration = 0; + for (int i = 0; i < split.length; i++) { + duration += split[i] * pow(60, i); + } + if (duration <= videoDuration) { + updateSegment( + isFirst: isFirst, + item: item, + value: duration, + ); + (context as Element).markNeedsBuild(); + } + } catch (e) { + if (kDebugMode) debugPrint(e.toString()); + } + } + }); + }, + ), + ]; + } + + final child = Builder( + builder: (context) => Row( + spacing: 5, + mainAxisSize: MainAxisSize.min, + children: segment(context, true), + ), + ); + if (item.category != SegmentType.poi_highlight) { + return Wrap( + runSpacing: 8, + spacing: 16, + children: [ + child, + Builder( + builder: (context) => Row( + spacing: 5, + mainAxisSize: MainAxisSize.min, + children: segment(context, false), + ), + ), + ], + ); + } + return child; + } } class _PostPanelState extends CommonCollapseSlidePageState { @@ -162,130 +310,6 @@ class _PostPanelState extends CommonCollapseSlidePageState { ); } - void updateSegment({ - required bool isFirst, - required PostSegmentModel item, - required double value, - }) { - if (isFirst) { - item.segment.first = value; - } else { - item.segment.second = value; - } - if (item.category == SegmentType.poi_highlight || - item.actionType == ActionType.full) { - item.segment.second = value; - } - } - - List segmentWidget( - BuildContext context, - ThemeData theme, { - required PostSegmentModel item, - required bool isFirst, - }) { - String value = DurationUtil.formatDuration( - isFirst ? item.segment.first : item.segment.second, - ); - return [ - Text( - '${isFirst ? '开始' : '结束'}: $value', - ), - const SizedBox(width: 5), - iconButton( - context: context, - size: 26, - tooltip: '设为当前', - icon: Icons.my_location, - onPressed: () { - updateSegment( - isFirst: isFirst, - item: item, - value: currentPos, - ); - (context as Element).markNeedsBuild(); - }, - ), - const SizedBox(width: 5), - iconButton( - context: context, - size: 26, - tooltip: isFirst ? '视频开头' : '视频结尾', - icon: isFirst ? Icons.first_page : Icons.last_page, - onPressed: () { - updateSegment( - isFirst: isFirst, - item: item, - value: isFirst ? 0 : videoDuration, - ); - (context as Element).markNeedsBuild(); - }, - ), - const SizedBox(width: 5), - iconButton( - context: context, - size: 26, - tooltip: '编辑', - icon: Icons.edit, - onPressed: () { - showDialog( - context: context, - builder: (context) { - String initV = value; - return AlertDialog( - content: TextFormField( - initialValue: value, - autofocus: true, - onChanged: (value) => initV = value, - inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp(r'[\d:.]+')), - ], - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle(color: theme.colorScheme.outline), - ), - ), - TextButton( - onPressed: () => Get.back(result: initV), - child: const Text('确定'), - ), - ], - ); - }, - ).then((res) { - if (res != null) { - try { - List split = res - .split(':') - .reversed - .map(num.parse) - .toList(); - double duration = 0; - for (int i = 0; i < split.length; i++) { - duration += split[i] * pow(60, i); - } - if (duration <= videoDuration) { - updateSegment( - isFirst: isFirst, - item: item, - value: duration, - ); - (context as Element).markNeedsBuild(); - } - } catch (e) { - if (kDebugMode) debugPrint(e.toString()); - } - } - }); - }, - ), - ]; - } - void _onPost() { Request() .post( @@ -376,42 +400,13 @@ class _PostPanelState extends CommonCollapseSlidePageState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (item.actionType != ActionType.full) ...[ - Wrap( - runSpacing: 8, - spacing: 16, - children: [ - Builder( - builder: (context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: segmentWidget( - context, - theme, - isFirst: true, - item: item, - ), - ); - }, - ), - if (item.category != SegmentType.poi_highlight) - Builder( - builder: (context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: segmentWidget( - context, - theme, - isFirst: false, - item: item, - ), - ); - }, - ), - ], + if (item.actionType != ActionType.full) + PostPanel.segmentWidget( + theme, + item: item, + currentPos: currentPos, + videoDuration: videoDuration, ), - const SizedBox(height: 8), - ], Wrap( runSpacing: 8, spacing: 16, @@ -430,14 +425,14 @@ class _PostPanelState extends CommonCollapseSlidePageState { } switch (e) { case SegmentType.poi_highlight: - updateSegment( + PostPanel.updateSegment( isFirst: false, item: item, value: item.segment.first, ); break; case SegmentType.exclusive_access: - updateSegment( + PostPanel.updateSegment( isFirst: true, item: item, value: 0, @@ -491,7 +486,7 @@ class _PostPanelState extends CommonCollapseSlidePageState { onSelected: (e) { item.actionType = e; if (e == ActionType.full) { - updateSegment( + PostPanel.updateSegment( isFirst: true, item: item, value: 0, diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index c1d5affd7..f199b88f8 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -837,7 +837,7 @@ class HeaderControlState extends TripleState { } videoDetailCtr ..currentDecodeFormats = - VideoDecodeFormatTypeExt.fromString(i)! + VideoDecodeFormatType.fromString(i) ..updatePlayer(); Get.back(); }, @@ -846,7 +846,7 @@ class HeaderControlState extends TripleState { right: 20, ), title: Text( - VideoDecodeFormatTypeExt.fromString(i)!.description, + VideoDecodeFormatType.fromString(i).description, ), subtitle: Text( i, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 331b1e25d..72dbfab4f 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -11,6 +11,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/ua_type.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/account_type.dart'; +import 'package:PiliPlus/models/common/audio_normalization.dart'; import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; import 'package:PiliPlus/models/common/super_resolution_type.dart'; import 'package:PiliPlus/models/common/video/video_type.dart'; @@ -43,7 +44,6 @@ import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:media_kit/media_kit.dart'; @@ -52,6 +52,7 @@ import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:screen_brightness/screen_brightness.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'package:volume_controller/volume_controller.dart'; class PlPlayerController { Player? _videoPlayerController; @@ -717,7 +718,16 @@ class PlPlayerController { if (isAnim) { setShader(superResolutionType.value, pp); } - await pp.setProperty("af", "scaletempo2=max-speed=8"); + String audioNormalization = Pref.audioNormalization; + audioNormalization = switch (audioNormalization) { + '0' => '', + '1' => ',${AudioNormalization.dynaudnorm.param}', + _ => ',$audioNormalization', + }; + await pp.setProperty( + "af", + "scaletempo2=max-speed=8$audioNormalization", + ); if (Platform.isAndroid) { await pp.setProperty("volume-max", "100"); String ao = Pref.useOpenSLES @@ -943,7 +953,12 @@ class PlPlayerController { isLive, ); }), + if (kDebugMode) + videoPlayerController!.stream.log.listen(((PlayerLog log) { + debugPrint(log.toString()); + })), videoPlayerController!.stream.error.listen((String event) { + debugPrint('MPV Exception: $event'); if (isLive) { if (event.startsWith('tcp: ffurl_read returned ') || event.startsWith("Failed to open https://") || @@ -982,16 +997,13 @@ class PlPlayerController { ); } else if (event.startsWith('Could not open codec')) { SmartDialog.showToast('无法加载解码器, $event,可能会切换至软解'); - } else { - if (!onlyPlayAudio.value) { - if (event.startsWith("Failed to open .") || - event.startsWith("Cannot open") || - event.startsWith("Can not open")) { - return; - } - SmartDialog.showToast('视频加载错误, $event'); - if (kDebugMode) debugPrint('视频加载错误, $event'); + } else if (!onlyPlayAudio.value) { + if (event.startsWith("Failed to open .") || + event.startsWith("Cannot open") || + event.startsWith("Can not open")) { + return; } + SmartDialog.showToast('视频加载错误, $event'); } }), // videoPlayerController!.stream.volume.listen((event) { @@ -1200,7 +1212,7 @@ class PlPlayerController { Future getCurrentVolume() async { // mac try...catch try { - _currentVolume.value = (await FlutterVolumeController.getVolume())!; + _currentVolume.value = await VolumeController.instance.getVolume(); } catch (_) {} } @@ -1219,8 +1231,9 @@ class PlPlayerController { volume.value = volumeNew; try { - FlutterVolumeController.updateShowSystemUI(false); - await FlutterVolumeController.setVolume(volumeNew); + await (VolumeController.instance..showSystemUI = false).setVolume( + volumeNew, + ); } catch (err) { if (kDebugMode) debugPrint(err.toString()); } diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index a63a1a7eb..f4cff921a 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -4,10 +4,15 @@ import 'dart:math'; import 'dart:ui' as ui; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/init.dart'; +import 'package:PiliPlus/models/common/sponsor_block/action_type.dart'; +import 'package:PiliPlus/models/common/sponsor_block/post_segment_model.dart'; +import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; import 'package:PiliPlus/models/common/super_resolution_type.dart'; import 'package:PiliPlus/models/common/video/video_quality.dart'; import 'package:PiliPlus/models/video/play/url.dart'; @@ -18,6 +23,7 @@ import 'package:PiliPlus/models_new/video/video_shot/data.dart'; import 'package:PiliPlus/pages/common/common_intro_controller.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart'; +import 'package:PiliPlus/pages/video/post_panel/view.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart'; @@ -31,11 +37,14 @@ import 'package:PiliPlus/plugin/pl_player/widgets/backward_seek.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/bottom_control.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/forward_seek.dart'; +import 'package:PiliPlus/plugin/pl_player/widgets/mpv_convert_webp.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart'; import 'package:PiliPlus/utils/duration_util.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; +import 'package:PiliPlus/utils/utils.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:easy_debounce/easy_throttle.dart'; @@ -44,7 +53,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart' hide ContextExtensionss; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -52,6 +60,7 @@ import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:saver_gallery/saver_gallery.dart'; import 'package:screen_brightness/screen_brightness.dart'; +import 'package:volume_controller/volume_controller.dart'; class PLVideoPlayer extends StatefulWidget { const PLVideoPlayer({ @@ -203,12 +212,12 @@ class _PLVideoPlayerState extends State videoController = plPlayerController.videoController!; Future.microtask(() async { try { - FlutterVolumeController.updateShowSystemUI(true); - _volumeValue.value = (await FlutterVolumeController.getVolume())!; - FlutterVolumeController.addListener((double value) { + final volumeCtr = VolumeController.instance..showSystemUI = true; + _volumeValue.value = await volumeCtr.getVolume(); + volumeCtr.addListener((double value) { if (mounted && !_volumeInterceptEventStream.value) { _volumeValue.value = value; - if (Platform.isIOS && !FlutterVolumeController.showSystemUI) { + if (Platform.isIOS && !volumeCtr.showSystemUI) { _volumeIndicator.value = true; _volumeTimer?.cancel(); _volumeTimer = Timer(const Duration(milliseconds: 800), () { @@ -239,9 +248,9 @@ class _PLVideoPlayerState extends State Future setVolume(double value) async { try { - FlutterVolumeController.updateShowSystemUI(false); - await FlutterVolumeController.setVolume(value); + await (VolumeController.instance..showSystemUI = false).setVolume(value); } catch (_) {} + _volumeValue.value = value; _volumeIndicator.value = true; _volumeInterceptEventStream.value = true; @@ -273,7 +282,7 @@ class _PLVideoPlayerState extends State _listener?.cancel(); _controlsListener?.cancel(); animationController.dispose(); - FlutterVolumeController.removeListener(); + VolumeController.instance.removeListener(); transformationController.dispose(); super.dispose(); } @@ -403,6 +412,7 @@ class _PLVideoPlayerState extends State /// 超分辨率 BottomControlType.superResolution => Obx( () => PopupMenuButton( + tooltip: '超分辨率', requestFocus: false, initialValue: plPlayerController.superResolutionType.value, color: Colors.black.withValues(alpha: 0.8), @@ -511,6 +521,7 @@ class _PLVideoPlayerState extends State /// 画面比例 BottomControlType.fit => Obx( () => PopupMenuButton( + tooltip: '画面比例', requestFocus: false, initialValue: plPlayerController.videoFit.value, color: Colors.black.withValues(alpha: 0.8), @@ -545,6 +556,7 @@ class _PLVideoPlayerState extends State () => widget.videoDetailController?.subtitles.isEmpty == true ? const SizedBox.shrink() : PopupMenuButton( + tooltip: '选择字幕', requestFocus: false, initialValue: widget .videoDetailController! @@ -597,6 +609,7 @@ class _PLVideoPlayerState extends State /// 播放速度 BottomControlType.speed => Obx( () => PopupMenuButton( + tooltip: '倍速', requestFocus: false, initialValue: plPlayerController.playbackSpeed, color: Colors.black.withValues(alpha: 0.8), @@ -650,6 +663,7 @@ class _PLVideoPlayerState extends State } } return PopupMenuButton( + tooltip: '画质', requestFocus: false, initialValue: currentVideoQa.code, color: Colors.black.withValues(alpha: 0.8), @@ -1655,6 +1669,10 @@ class _PLVideoPlayerState extends State size: 20, color: Colors.white, ), + onLongPress: + (Platform.isAndroid || kDebugMode) && !isLive + ? screenshotWebp + : null, onTap: () { SmartDialog.showToast('截图中'); plPlayerController.videoPlayerController @@ -1857,6 +1875,155 @@ class _PLVideoPlayerState extends State ], ); } + + Future screenshotWebp() async { + final videoCtr = widget.videoDetailController!; + final videoInfo = widget.videoDetailController!.data; + final ids = videoInfo.dash!.video!.map((i) => i.id!).toSet(); + final video = videoCtr.findVideoByQa(ids.reduce((p, n) => p < n ? p : n)); + + VideoQuality qa = video.quality; + String? url = video.baseUrl; + if (url == null) return; + + final ctr = plPlayerController; + final theme = Theme.of(context); + final currentPos = ctr.position.value.inMilliseconds / 1000.0; + final duration = ctr.durationSeconds.value.inMilliseconds / 1000.0; + final segment = Pair(first: currentPos, second: currentPos + 10.0); + final model = PostSegmentModel( + segment: segment, + category: SegmentType.sponsor, + actionType: ActionType.skip, + ); + final isPlay = ctr.playerStatus.playing; + if (isPlay) ctr.pause(); + + WebpPreset preset = WebpPreset.def; + + final success = + await Get.dialog( + AlertDialog( + title: const Text('动态截图'), + content: Column( + spacing: 12, + mainAxisSize: MainAxisSize.min, + children: [ + PostPanel.segmentWidget( + theme, + item: model, + currentPos: currentPos, + videoDuration: duration, + ), + Builder( + builder: (context) => PopupMenuButton( + initialValue: qa.code, + onSelected: (value) { + if (value == qa.code) return; + final video = videoCtr.findVideoByQa(value); + url = video.baseUrl; + qa = video.quality; + (context as Element).markNeedsBuild(); + }, + itemBuilder: (_) => videoInfo.supportFormats! + .map( + (i) => PopupMenuItem( + enabled: ids.contains(i.quality), + value: i.quality, + child: Text(i.newDesc ?? ''), + ), + ) + .toList(), + child: Text('转码画质:${qa.shortDesc}'), + ), + ), + Builder( + builder: (context) => PopupMenuButton( + initialValue: preset, + onSelected: (value) { + if (preset == value) return; + preset = value; + (context as Element).markNeedsBuild(); + }, + itemBuilder: (_) => WebpPreset.values + .map( + (i) => PopupMenuItem(value: i, child: Text(i.name)), + ) + .toList(), + child: Text('webp预设:${preset.name}(${preset.desc})'), + ), + ), + Text( + '*转码使用软解,速度可能慢于播放,请不要选择过长的时间段或过高画质', + style: theme.textTheme.bodySmall, + ), + ], + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: theme.colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + if (segment.first < segment.second) { + Get.back(result: true); + } + }, + child: const Text('确定'), + ), + ], + ), + ) ?? + false; + if (!success) return; + + final progress = 0.0.obs; + final name = + '${ctr.cid}-${segment.first.toStringAsFixed(3)}_${segment.second.toStringAsFixed(3)}.webp'; + final file = '${await Utils.temporaryDirectory}/$name'; + + final mpv = MpvConvertWebp( + url!, + file, + segment.first, + segment.second, + progress: progress, + preset: preset, + ); + final future = mpv.convert().whenComplete( + () => SmartDialog.dismiss(status: SmartStatus.loading), + ); + + SmartDialog.showLoading( + backType: SmartBackType.normal, + builder: (_) => LoadingWidget(progress: progress, msg: '正在保存,可能需要较长时间'), + onDismiss: () async { + if (progress.value < 1.0) { + mpv.dispose(); + } + if (await future) { + await SaverGallery.saveFile( + filePath: file, + fileName: name, + androidRelativePath: 'Pictures/Screenshots', + skipIfExists: false, + ); + SmartDialog.showToast('$name已保存到相册/截图'); + } else { + SmartDialog.showToast('转码出现错误或已取消'); + } + progress.close(); + File(file).delSync(); + if (isPlay) ctr.play(); + }, + ); + } } Widget buildDmChart( diff --git a/lib/plugin/pl_player/widgets/mpv_convert_webp.dart b/lib/plugin/pl_player/widgets/mpv_convert_webp.dart new file mode 100644 index 000000000..410d2f76a --- /dev/null +++ b/lib/plugin/pl_player/widgets/mpv_convert_webp.dart @@ -0,0 +1,186 @@ +import 'dart:async'; +import 'dart:ffi'; + +import 'package:PiliPlus/http/constants.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; +import 'package:flutter/material.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:media_kit/ffi/src/allocation.dart'; +import 'package:media_kit/ffi/src/utf8.dart'; +import 'package:media_kit/generated/libmpv/bindings.dart' as generated; +import 'package:media_kit/src/player/native/core/initializer.dart'; +import 'package:media_kit/src/player/native/core/native_library.dart'; + +class MpvConvertWebp { + final _mpv = generated.MPV(DynamicLibrary.open(NativeLibrary.path)); + late final Pointer _ctx; + final _completer = Completer(); + + bool _success = false; + + final String url; + final String outFile; + final double start; + final double duration; + final RxDouble? progress; + final WebpPreset preset; + + MpvConvertWebp( + this.url, + this.outFile, + this.start, + double end, { + this.progress, + this.preset = WebpPreset.def, + }) : duration = end - start; + + Future _init() async { + _ctx = await Initializer.create( + NativeLibrary.path, + _onEvent, + options: { + 'o': outFile, + 'start': start.toStringAsFixed(3), + 'end': (start + duration).toStringAsFixed(3), + 'of': 'webp', + 'ovc': 'libwebp_anim', + 'ofopts': 'loop=0', + 'ovcopts': 'preset=${preset.flag}', + }, + ); + _setHeader(); + if (progress != null) { + _observeProperty('time-pos'); + } + final level = (kDebugMode ? 'info' : 'error').toNativeUtf8(); + _mpv.mpv_request_log_messages(_ctx, level.cast()); + calloc.free(level); + } + + void dispose() { + Initializer.dispose(_ctx); + _mpv.mpv_terminate_destroy(_ctx); + if (!_completer.isCompleted) _completer.complete(false); + } + + Future convert() async { + await _init(); + _command(['loadfile', url]); + return _completer.future; + } + + Future _onEvent(Pointer event) async { + switch (event.ref.event_id) { + case generated.mpv_event_id.MPV_EVENT_PROPERTY_CHANGE: + final prop = event.ref.data.cast().ref; + if (prop.name.cast().toDartString() == 'time-pos' && + prop.format == generated.mpv_format.MPV_FORMAT_DOUBLE) { + progress!.value = (prop.data.cast().value - start) / duration; + } + break; + case generated.mpv_event_id.MPV_EVENT_FILE_LOADED: + _success = true; + break; + case generated.mpv_event_id.MPV_EVENT_LOG_MESSAGE: + final log = event.ref.data.cast().ref; + final prefix = log.prefix.cast().toDartString().trim(); + final level = log.level.cast().toDartString().trim(); + final text = log.text.cast().toDartString().trim(); + debugPrint('WebpConvert: $level $prefix : $text'); + if (kDebugMode) { + _success = level != 'error' && level != 'fatal'; + } else { + _success = false; + } + break; + case generated.mpv_event_id.MPV_EVENT_END_FILE || + generated.mpv_event_id.MPV_EVENT_SHUTDOWN: + progress?.value = 1; + _completer.complete(_success); + dispose(); + break; + } + } + + void _command(List args) { + final pointers = args.map((e) => e.toNativeUtf8()).toList(); + final arr = calloc>(128); + for (int i = 0; i < args.length; i++) { + arr[i] = pointers[i]; + } + + _mpv.mpv_command(_ctx, arr.cast()); + + calloc.free(arr); + pointers.forEach(calloc.free); + } + + void _observeProperty(String property) { + final name = property.toNativeUtf8(); + _mpv.mpv_observe_property( + _ctx, + property.hashCode, + name.cast(), + generated.mpv_format.MPV_FORMAT_DOUBLE, + ); + + calloc.free(name); + } + + void _setHeader() { + final property = 'http-header-fields'.toNativeUtf8(); + // Allocate & fill the [mpv_node] with the headers. + final value = calloc(); + final valRef = value.ref + ..format = generated.mpv_format.MPV_FORMAT_NODE_ARRAY; + valRef.u.list = calloc(); + final valList = valRef.u.list.ref + ..num = 2 + ..values = calloc(2); + + const entries = [ + ( + 'user-agent', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', + ), + ('referer', HttpString.baseUrl), + ]; + for (int i = 0; i < 2; i++) { + final (k, v) = entries[i]; + valList.values[i] + ..format = generated.mpv_format.MPV_FORMAT_STRING + ..u.string = '$k: $v'.toNativeUtf8().cast(); + } + _mpv.mpv_set_property( + _ctx, + property.cast(), + generated.mpv_format.MPV_FORMAT_NODE, + value.cast(), + ); + // Free the allocated memory. + calloc.free(property); + for (int i = 0; i < valList.num; i++) { + calloc.free(valList.values[i].u.string); + } + calloc + ..free(valList.values) + ..free(valRef.u.list) + ..free(value); + } +} + +enum WebpPreset { + none('none', '无', '不使用预设'), + def('default', '默认', '默认预设'), + picture('picture', '图片', '数码照片,如人像、室内拍摄'), + photo('photo', '照片', '户外摄影,自然光环境'), + drawing('drawing', '绘图', '手绘或线稿,高对比度细节'), + icon('icon', '图标', '小型彩色图像'), + text('text', '文本', '文字类'); + + final String flag; + final String name; + final String desc; + + const WebpPreset(this.flag, this.name, this.desc); +} diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index 5ae3b69b7..19bbbb3a8 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -91,6 +91,7 @@ class SettingBoxKey { refreshDragPercentage = 'refreshDragPercentage', refreshDisplacement = 'refreshDisplacement', showHotRcmd = 'showHotRcmd', + audioNormalization = 'audioNormalization', superResolutionType = 'superResolutionType', preInitPlayer = 'preInitPlayer', mainTabBarView = 'mainTabBarView', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 04127905e..120b03929 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -405,6 +405,9 @@ class Pref { static bool get showHotRcmd => _setting.get(SettingBoxKey.showHotRcmd, defaultValue: false); + static String get audioNormalization => + _setting.get(SettingBoxKey.audioNormalization, defaultValue: '0'); + static SuperResolutionType get superResolutionType { SuperResolutionType? superResolutionType; final index = _setting.get(SettingBoxKey.superResolutionType); diff --git a/pubspec.lock b/pubspec.lock index a0bb9eff7..7e347b369 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -517,14 +517,6 @@ packages: url: "https://github.com/bggRGjQaUbCoE/extended_nested_scroll_view.git" source: git version: "6.2.1" - fading_edge_scrollview: - dependency: "direct overridden" - description: - name: fading_edge_scrollview - sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" - url: "https://pub.dev" - source: hosted - version: "4.1.1" fake_async: dependency: transitive description: @@ -782,14 +774,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_volume_controller: - dependency: "direct main" - description: - name: flutter_volume_controller - sha256: "15f2c25bc4632ac5e8d42a208fe07c3224a4ee66b155d1ac86945b3db2bb58d9" - url: "https://pub.dev" - source: hosted - version: "1.3.3" flutter_web_plugins: dependency: transitive description: flutter @@ -1157,12 +1141,13 @@ packages: source: hosted version: "1.1.11" media_kit_libs_android_video: - dependency: transitive + dependency: "direct overridden" description: - name: media_kit_libs_android_video - sha256: adff9b571b8ead0867f9f91070f8df39562078c0eb3371d88b9029a2d547d7b7 - url: "https://pub.dev" - source: hosted + path: "libs/android/media_kit_libs_android_video" + ref: "version_1.2.5" + resolved-ref: f89452bc27af26324a83961c8286d4f41432a5f9 + url: "https://github.com/My-Responsitories/media-kit.git" + source: git version: "1.3.7" media_kit_libs_ios_video: dependency: transitive @@ -1205,7 +1190,7 @@ packages: source: hosted version: "1.0.11" media_kit_native_event_loop: - dependency: transitive + dependency: "direct overridden" description: name: media_kit_native_event_loop sha256: "7d82e3b3e9ded5c35c3146c5ba1da3118d1dd8ac3435bac7f29f458181471b40" @@ -1217,8 +1202,8 @@ packages: description: path: media_kit_video ref: "version_1.2.5" - resolved-ref: "4785f3223ff04d86b1644ab114e3960723ddf7cd" - url: "https://github.com/bggRGjQaUbCoE/media-kit.git" + resolved-ref: f89452bc27af26324a83961c8286d4f41432a5f9 + url: "https://github.com/My-Responsitories/media-kit.git" source: git version: "1.2.5" meta: @@ -1971,13 +1956,13 @@ packages: source: hosted version: "15.0.2" volume_controller: - dependency: transitive + dependency: "direct main" description: name: volume_controller - sha256: c71d4c62631305df63b72da79089e078af2659649301807fa746088f365cb48e + sha256: d75039e69c0d90e7810bfd47e3eedf29ff8543ea7a10392792e81f9bded7edf5 url: "https://pub.dev" source: hosted - version: "2.0.8" + version: "3.4.0" wakelock_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 3d9828c03..761ab0e6d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,7 +104,7 @@ dependencies: # media_kit_video: ^1.2.5 # For video rendering. media_kit_video: git: - url: https://github.com/bggRGjQaUbCoE/media-kit.git + url: https://github.com/My-Responsitories/media-kit.git path: media_kit_video ref: version_1.2.5 media_kit_libs_video: 1.0.5 @@ -114,7 +114,7 @@ dependencies: audio_session: ^0.2.2 # 音量、亮度、屏幕控制 - flutter_volume_controller: ^1.3.3 + volume_controller: ^2.0.7 wakelock_plus: ^1.2.8 # universal_platform: ^1.1.0 auto_orientation: @@ -214,17 +214,23 @@ dependencies: collection: any material_color_utilities: any flutter_cache_manager: any - http2: any + dependency_overrides: - screen_brightness: ^2.0.1 + screen_brightness: ^2.1.7 path: ^1.9.1 mime: ^2.0.0 - fading_edge_scrollview: ^4.1.1 rxdart: ^0.28.0 media_kit: 1.1.11 media_kit_libs_video: 1.0.5 + media_kit_native_event_loop: ^1.0.9 font_awesome_flutter: 10.9.0 + media_kit_libs_android_video: + git: + url: https://github.com/My-Responsitories/media-kit.git + path: libs/android/media_kit_libs_android_video + ref: version_1.2.5 + volume_controller: ^3.4.0 dev_dependencies: flutter_test: