diff --git a/lib/http/video.dart b/lib/http/video.dart index 4f5ec9f9..44d2b3c1 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -582,6 +582,13 @@ class VideoHttp { SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}"); } } + if (subtitlesVtt.isNotEmpty) { + subtitlesVtt.insert(0, { + 'language': '', + 'title': '关闭字幕', + 'text': "" + }); + } return subtitlesVtt; } } diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 94f500b9..d87caa86 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -97,6 +97,7 @@ class PlPlayerController { bool _enableHeart = true; bool _isFirstTime = true; final RxList> _vttSubtitles = >[].obs; + final RxInt _vttSubtitlesIndex = 0.obs; Timer? _timer; Timer? _timerForSeek; @@ -150,7 +151,8 @@ class PlPlayerController { Stream get onMuteChanged => _mute.stream; // 视频字幕 - RxList get vttSubtitles => _vttSubtitles; + RxList> get vttSubtitles => _vttSubtitles; + RxInt get vttSubtitlesIndex => _vttSubtitlesIndex; /// [videoPlayerController] instance of Player Player? get videoPlayerController => _videoPlayerController; @@ -302,6 +304,7 @@ class PlPlayerController { for (final PlaySpeed i in PlaySpeed.values) { speedsList.add(i.value); } + speedsList.sort(); // _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) { // if (status == PlayerStatus.playing) { @@ -1106,62 +1109,18 @@ class PlPlayerController { } } - /// 选择字幕 - void showSetSubtitleSheet() async { - 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: [ - FilledButton.tonal( - onPressed: () async { - await removeSubtitle(); - Get.back(); - }, - child: const Text("关闭字幕"), - ), - for (final Map i in _vttSubtitles) ...[ - FilledButton.tonal( - onPressed: () async { - await setSubtitle(i); - Get.back(); - }, - child: Text(i["title"]!), - ), - ] - ], - ); - }), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - ], - ); - }, - ); - } - - removeSubtitle() { - _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no()); - } - // 设定字幕轨道 setSubtitle(Map s) { + if (s['text'] == null) { + _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no()); + _vttSubtitlesIndex.value = 0; + return; + } _videoPlayerController?.setSubtitleTrack(SubtitleTrack.data( s['text']!, title: s['title']!, language: s['language']!, )); + _vttSubtitlesIndex.value = _vttSubtitles.indexOf(s); } } diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 7b351ff6..86cdba0b 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -152,16 +152,35 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { : SizedBox( width: 45, height: 30, - child: IconButton( - tooltip: '字幕', - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => _.showSetSubtitleSheet(), - icon: const Icon( - Icons.closed_caption_off_outlined, - size: 19, - color: Colors.white, + child: PopupMenuButton>( + onSelected: (Map value) { + controller!.setSubtitle(value); + }, + initialValue: + _.vttSubtitles[_.vttSubtitlesIndex.value], + color: Colors.black.withOpacity(0.8), + itemBuilder: (BuildContext context) { + return _.vttSubtitles + .map((Map subtitle) { + return PopupMenuItem>( + value: subtitle, + child: Text( + "${subtitle['title']}", + style: const TextStyle(color: Colors.white), + ), + ); + }).toList(); + }, + child: Container( + width: 45, + height: 30, + alignment: Alignment.center, + child: const Icon( + Icons.closed_caption_off_outlined, + size: 19, + color: Colors.white, + semanticLabel: '字幕', + ), ), ), ), @@ -169,17 +188,32 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { SizedBox( width: 45, height: 30, - child: TextButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => showSetSpeedSheet(), - child: Obx( - () => Text( - '${_.playbackSpeed}X', - style: const TextStyle(color: Colors.white, fontSize: 13), - semanticsLabel: '${_.playbackSpeed}倍速', - ), + child: PopupMenuButton( + onSelected: (double value) { + controller!.setPlaybackSpeed(value); + }, + initialValue: _.playbackSpeed, + color: Colors.black.withOpacity(0.8), + itemBuilder: (BuildContext context) { + return _.speedsList.map((double speed) { + return PopupMenuItem( + value: speed, + child: Text( + "${speed}X", + style: const TextStyle(color: Colors.white), + semanticsLabel: "$speed倍速", + ), + ); + }).toList(); + }, + child: Container( + width: 45, + height: 30, + alignment: Alignment.center, + child: Obx(() => Text("${_.playbackSpeed}X", + style: + const TextStyle(color: Colors.white, fontSize: 13), + semanticsLabel: "${_.playbackSpeed}倍速")), ), ), ), @@ -207,64 +241,4 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget { ), ); } - - /// 选择倍速 - void showSetSpeedSheet() { - final double currentSpeed = controller!.playbackSpeed; - List speedsList = controller!.speedsList; - 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 controller!.setPlaybackSpeed(i); - Get.back(); - }, - child: Text(i.toString()), - ), - ] else ...[ - FilledButton.tonal( - onPressed: () async { - // setState(() => currentSpeed = i), - await 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 controller!.setDefaultSpeed(); - Get.back(); - }, - child: const Text('默认速度'), - ), - ], - ); - }, - ); - } }