diff --git a/assets/images/video/dlna.png b/assets/images/video/dlna.png new file mode 100755 index 00000000..a5d65872 Binary files /dev/null and b/assets/images/video/dlna.png differ diff --git a/assets/images/video/fullscreen.png b/assets/images/video/fullscreen.png new file mode 100755 index 00000000..449f1425 Binary files /dev/null and b/assets/images/video/fullscreen.png differ diff --git a/assets/images/video/fullscreen_exit.png b/assets/images/video/fullscreen_exit.png new file mode 100755 index 00000000..9881ed1d Binary files /dev/null and b/assets/images/video/fullscreen_exit.png differ diff --git a/assets/images/video/pip.png b/assets/images/video/pip.png new file mode 100755 index 00000000..1b742125 Binary files /dev/null and b/assets/images/video/pip.png differ diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index b6e7f150..78028904 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -116,6 +116,7 @@ class VideoDetailController extends GetxController BottomControlType.time, BottomControlType.space, BottomControlType.fit, + BottomControlType.speed, BottomControlType.fullscreen, ].obs; RxDouble sheetHeight = 0.0.obs; diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 0fbdc493..5412d326 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -57,7 +57,7 @@ class _HeaderControlState extends State { final Box localCache = GStorage.localCache; final Box videoStorage = GStorage.video; late List speedsList; - double buttonSpace = 8; + double buttonSpace = 4; RxBool isFullScreen = false.obs; late String heroTag; late VideoIntroController videoIntroController; @@ -472,65 +472,6 @@ class _HeaderControlState extends State { }); } - /// 选择倍速 - void showSetSpeedSheet() { - final double currentSpeed = widget.controller!.playbackSpeed; - showDialog( - context: Get.context!, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('播放速度'), - content: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Wrap( - spacing: 8, - runSpacing: 2, - children: [ - for (final double i in speedsList) ...[ - if (i == currentSpeed) ...[ - FilledButton( - onPressed: () async { - // setState(() => currentSpeed = i), - await widget.controller!.setPlaybackSpeed(i); - Get.back(); - }, - child: Text(i.toString()), - ), - ] else ...[ - FilledButton.tonal( - onPressed: () async { - // setState(() => currentSpeed = i), - await widget.controller!.setPlaybackSpeed(i); - Get.back(); - }, - child: Text(i.toString()), - ), - ] - ] - ], - ); - }), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - await widget.controller!.setDefaultSpeed(); - Get.back(); - }, - child: const Text('默认速度'), - ), - ], - ); - }, - ); - } - /// 选择画质 void showSetVideoQa() { final List videoFormat = videoInfo.supportFormats!; @@ -1208,7 +1149,7 @@ class _HeaderControlState extends State { fuc: () async { // 销毁播放器实例 await widget.controller!.dispose(type: 'all'); - if (mounted) { + if (context.mounted) { Navigator.popUntil( context, (Route route) => route.isFirst); } @@ -1216,21 +1157,9 @@ class _HeaderControlState extends State { ), ], const Spacer(), - // ComBtn( - // icon: const Icon( - // FontAwesomeIcons.cropSimple, - // size: 15, - // color: Colors.white, - // ), - // fuc: () => _.screenshot(), - // ), if (GlobalDataCache.enableDlna) ...[ ComBtn( - icon: const Icon( - Icons.cast, - size: 19, - color: Colors.white, - ), + icon: Image.asset('assets/images/video/dlna.png', width: 19), fuc: () async { showDialog( context: context, @@ -1241,7 +1170,10 @@ class _HeaderControlState extends State { ); }, ), + SizedBox(width: buttonSpace), ], + + /// 弹幕开关(全屏时) if (isFullScreen.value) ...[ SizedBox( width: 56, @@ -1257,6 +1189,7 @@ class _HeaderControlState extends State { ), ), ), + SizedBox(width: buttonSpace), SizedBox( width: 34, height: 34, @@ -1278,8 +1211,10 @@ class _HeaderControlState extends State { ), ), ), + SizedBox(width: buttonSpace), ], - SizedBox(width: buttonSpace), + + /// pip if (Platform.isAndroid) ...[ SizedBox( width: 34, @@ -1304,9 +1239,9 @@ class _HeaderControlState extends State { await widget.floating!.enable(aspectRatio: aspectRatio); } else {} }, - icon: const Icon( - Icons.picture_in_picture_outlined, - size: 19, + icon: Image.asset( + 'assets/images/video/pip.png', + width: 19, color: Colors.white, ), ), @@ -1315,37 +1250,21 @@ class _HeaderControlState extends State { ], /// 字幕 - if (widget.showSubtitleBtn) + if (widget.showSubtitleBtn) ...[ ComBtn( - icon: const Icon( - Icons.closed_caption_off, - size: 22, - color: Colors.white, + icon: Icon( + FontAwesomeIcons.closedCaptioning, + size: 16, + color: Colors.white.withOpacity(0.9), ), fuc: () => showSubtitleDialog(), ), - SizedBox(width: buttonSpace), - Obx( - () => SizedBox( - width: 45, - height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => showSetSpeedSheet(), - child: Text( - '${_.playbackSpeed}X', - style: textStyle, - ), - ), - ), - ), - SizedBox(width: buttonSpace), + SizedBox(width: buttonSpace), + ], ComBtn( icon: const Icon( Icons.more_vert_outlined, - size: 18, + size: 19, color: Colors.white, ), fuc: () => showSettingSheet(), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 075a2e91..c2c3027a 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:easy_debounce/easy_throttle.dart'; 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'; import 'package:hive/hive.dart'; @@ -113,12 +114,13 @@ class PlPlayerController { // final Durations durations; List> videoFitType = [ - {'attr': BoxFit.contain, 'desc': '包含'}, - {'attr': BoxFit.cover, 'desc': '覆盖'}, - {'attr': BoxFit.fill, 'desc': '填充'}, - {'attr': BoxFit.fitHeight, 'desc': '高度适应'}, - {'attr': BoxFit.fitWidth, 'desc': '宽度适应'}, - {'attr': BoxFit.scaleDown, 'desc': '缩小适应'}, + {'attr': BoxFit.contain, 'desc': '自动'}, + {'attr': BoxFit.cover, 'desc': '铺满'}, + {'attr': BoxFit.fill, 'desc': '填满'}, + {'attr': BoxFit.fitHeight, 'desc': '等高'}, + {'attr': BoxFit.fitWidth, 'desc': '等宽'}, + {'attr': BoxFit.scaleDown, 'desc': '缩放'}, + {'attr': BoxFit.none, 'desc': '原始'}, ]; PreferredSizeWidget? headerControl; @@ -828,47 +830,53 @@ class PlPlayerController { } /// Toggle Change the videofit accordingly - void toggleVideoFit() { - showDialog( - context: Get.context!, - builder: (context) { - return AlertDialog( - title: const Text('画面比例'), - content: StatefulBuilder(builder: (context, StateSetter setState) { - return Wrap( - alignment: WrapAlignment.start, + void toggleVideoFit(String toggleType) { + if (toggleType == 'press') { + final String videoFitDEsc = _videoFitDesc.value; + final int index = videoFitType.indexWhere( + (element) => element['desc'] == videoFitDEsc, + ); + final int newIndex = index + 1 >= videoFitType.length ? 0 : index + 1; + _videoFit.value = videoFitType[newIndex]['attr']; + _videoFitDesc.value = videoFitType[newIndex]['desc']; + setVideoFit(); + SmartDialog.showToast('画面比例:${videoFitType[newIndex]['desc']}'); + } else { + void onPressed(item) { + _videoFit.value = item['attr']; + _videoFitDesc.value = item['desc']; + setVideoFit(); + Navigator.of(Get.context!).pop(); + } + + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('画面比例'), + content: Wrap( spacing: 8, runSpacing: 2, children: [ for (var i in videoFitType) ...[ if (_videoFit.value == i['attr']) ...[ FilledButton( - onPressed: () async { - _videoFit.value = i['attr']; - _videoFitDesc.value = i['desc']; - setVideoFit(); - Get.back(); - }, + onPressed: () => onPressed(i), child: Text(i['desc']), ), ] else ...[ FilledButton.tonal( - onPressed: () async { - _videoFit.value = i['attr']; - _videoFitDesc.value = i['desc']; - setVideoFit(); - Get.back(); - }, + onPressed: () => onPressed(i), child: Text(i['desc']), ), ] ] ], - ); - }), - ); - }, - ); + ), + ); + }, + ); + } } /// 缓存fit diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 4c405cc0..c379303e 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -301,16 +301,22 @@ class _PLVideoPlayerState extends State /// 画面比例 BottomControlType.fit: SizedBox( + width: 45, height: 30, child: TextButton( - onPressed: () => _.toggleVideoFit(), + onPressed: () => _.toggleVideoFit('press'), + onLongPress: () => _.toggleVideoFit('longPress'), style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero), ), child: Obx( () => Text( _.videoFitDEsc.value, - style: const TextStyle(color: Colors.white, fontSize: 13), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), ), ), @@ -320,29 +326,49 @@ class _PLVideoPlayerState extends State BottomControlType.speed: SizedBox( width: 45, height: 34, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () {}, - child: Obx( - () => Text( - '${_.playbackSpeed.toString()}X', - style: textStyle, + child: PopupMenuButton( + tooltip: '更改播放速度', + onSelected: (double value) { + _.setPlaybackSpeed(value); + }, + initialValue: _.playbackSpeed, + color: Colors.black.withOpacity(0.8), + itemBuilder: (BuildContext context) { + return _.speedsList.map((double speed) { + return PopupMenuItem( + height: 40, + padding: const EdgeInsets.only(left: 20), + value: speed, + child: Text( + '${speed}x', + style: textStyle.copyWith(fontWeight: FontWeight.bold), + ), + ); + }).toList(); + }, + child: Container( + width: 45, + height: 34, + alignment: Alignment.center, + margin: const EdgeInsets.only(right: 4), + child: Obx( + () => Text( + '${_.playbackSpeed.toString()}x', + style: textStyle.copyWith(fontWeight: FontWeight.bold), + ), ), ), ), ), - /// 字幕 /// 全屏 BottomControlType.fullscreen: ComBtn( icon: Obx( - () => Icon( + () => Image.asset( _.isFullScreen.value - ? FontAwesomeIcons.compress - : FontAwesomeIcons.expand, - size: 15, + ? 'assets/images/video/fullscreen_exit.png' + : 'assets/images/video/fullscreen.png', + width: 19, color: Colors.white, ), ), @@ -359,6 +385,7 @@ class _PLVideoPlayerState extends State BottomControlType.time, BottomControlType.space, BottomControlType.fit, + BottomControlType.speed, BottomControlType.fullscreen, ]; for (var i = 0; i < userSpecifyItem.length; i++) {