From 0b311d37c88fe35e265875b667abce4479f6bf0b Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 22 Dec 2024 10:50:00 +0800 Subject: [PATCH] opt: video page Signed-off-by: bggRGjQaUbCoE --- lib/pages/live_room/view.dart | 2 +- lib/pages/setting/recommend_setting.dart | 1 + lib/pages/video/detail/view.dart | 103 ++- .../video/detail/widgets/header_control.dart | 741 +++++++++--------- lib/plugin/pl_player/view.dart | 292 +++---- lib/plugin/pl_player/widgets/common_btn.dart | 2 +- 6 files changed, 612 insertions(+), 529 deletions(-) diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index e40f78d03..afde29d74 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -129,7 +129,7 @@ class _LiveRoomPageState extends State builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData && snapshot.data['status']) { return PLVideoPlayer( - controller: plPlayerController, + plPlayerController: plPlayerController, bottomControl: BottomControl( controller: plPlayerController, liveRoomCtr: _liveRoomController, diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index 95956f721..fdd413d74 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -165,6 +165,7 @@ class _RecommendSettingState extends State { children: [ const Text('使用|隔开,如:尝试|测试'), TextField( + autofocus: true, controller: textController, textInputAction: TextInputAction.newline, minLines: 1, diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d97dce82b..7bba841b4 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -892,7 +892,7 @@ class _VideoDetailPageState extends State () => !videoDetailController.autoPlay.value ? const SizedBox() : PLVideoPlayer( - controller: plPlayerController!, + plPlayerController: plPlayerController!, videoIntroController: videoDetailController.videoType == SearchType.video ? videoIntroController @@ -936,38 +936,73 @@ class _VideoDetailPageState extends State primary: false, foregroundColor: Colors.white, backgroundColor: Colors.transparent, - actions: videoDetailController.userInfo == null - ? null - : [ - PopupMenuButton( - onSelected: (String type) async { - switch (type) { - case 'later': - var res = await UserHttp.toViewLater( - bvid: videoDetailController.bvid); - SmartDialog.showToast(res['msg']); - break; - case 'report': - Get.toNamed('/webviewnew', parameters: { - 'url': - 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(videoDetailController.bvid)}&bvid=${videoDetailController.bvid}' - }); - break; - } - }, - itemBuilder: (BuildContext context) => - >[ - const PopupMenuItem( - value: 'later', - child: Text('稍后再看'), - ), - const PopupMenuItem( - value: 'report', - child: Text('举报'), - ), - ], - ), - ], + // automaticallyImplyLeading: false, + // title: Row( + // children: [ + // SizedBox( + // width: 42, + // height: 34, + // child: IconButton( + // tooltip: '返回', + // icon: const Icon( + // FontAwesomeIcons.arrowLeft, + // size: 15, + // color: Colors.white, + // ), + // onPressed: Get.back, + // ), + // ), + // SizedBox( + // width: 42, + // height: 34, + // child: IconButton( + // tooltip: '返回主页', + // icon: const Icon( + // FontAwesomeIcons.house, + // size: 15, + // color: Colors.white, + // ), + // onPressed: () { + // Get.until((route) => route.isFirst); + // }, + // ), + // ), + // ], + // ), + actions: [ + PopupMenuButton( + onSelected: (String type) async { + switch (type) { + case 'later': + var res = await UserHttp.toViewLater( + bvid: videoDetailController.bvid); + SmartDialog.showToast(res['msg']); + break; + case 'report': + if (videoDetailController.userInfo == null) { + SmartDialog.showToast('账号未登录'); + } else { + Get.toNamed('/webviewnew', parameters: { + 'url': + 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(videoDetailController.bvid)}&bvid=${videoDetailController.bvid}' + }); + } + break; + } + }, + itemBuilder: (BuildContext context) => + >[ + const PopupMenuItem( + value: 'later', + child: Text('稍后再看'), + ), + const PopupMenuItem( + value: 'report', + child: Text('举报'), + ), + ], + ), + ], ), ), Positioned( @@ -998,7 +1033,7 @@ class _VideoDetailPageState extends State plPlayerController!.videoController == null ? nil : PLVideoPlayer( - controller: plPlayerController!, + plPlayerController: plPlayerController!, videoIntroController: videoDetailController.videoType == SearchType.video ? videoIntroController diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index a42fa22a6..e43472794 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -132,7 +132,12 @@ class _HeaderControlState extends State { color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.all(Radius.circular(12)), ), - margin: const EdgeInsets.all(12), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), child: Column( children: [ SizedBox( @@ -152,331 +157,367 @@ class _HeaderControlState extends State { ), ), Expanded( - child: Material( - child: ListView( - children: [ - // ListTile( - // onTap: () {}, - // dense: true, - // enabled: false, - // leading: - // const Icon(Icons.network_cell_outlined, size: 20), - // title: Text('省流模式', style: titleStyle), - // subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle), - // trailing: Transform.scale( - // scale: 0.75, - // child: Switch( - // thumbIcon: WidgetStateProperty.resolveWith( - // (Set states) { - // if (states.isNotEmpty && - // states.first == WidgetState.selected) { - // return const Icon(Icons.done); - // } - // return null; // All other states will use the default thumbIcon. - // }), - // value: false, - // onChanged: (value) => {}, - // ), - // ), - // ), - if (widget.videoDetailCtr?.userInfo != null) - ListTile( - onTap: () { - Get.back(); - Get.toNamed('/webviewnew', parameters: { - 'url': - 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr!.bvid)}&bvid=${widget.videoDetailCtr!.bvid}' - }); - }, - dense: true, - leading: const Icon(Icons.error_outline, size: 20), - title: const Text('举报', style: titleStyle), - ), - ListTile( - onTap: () async { - Get.back(); - final res = await UserHttp.toViewLater( - bvid: widget.videoDetailCtr!.bvid); - SmartDialog.showToast(res['msg']); - }, - dense: true, - leading: const Icon(Icons.watch_later_outlined, size: 20), - title: const Text('添加至「稍后再看」', style: titleStyle), - ), - ListTile( - onTap: () => {Get.back(), scheduleExit()}, - dense: true, - leading: - const Icon(Icons.hourglass_top_outlined, size: 20), - title: const Text('定时关闭', style: titleStyle), - ), - ListTile( - onTap: () => - {Get.back(), widget.videoDetailCtr!.queryVideoUrl()}, - dense: true, - leading: const Icon(Icons.refresh_outlined, size: 20), - title: const Text('重载视频', style: titleStyle), - ), - ListTile( - title: const Text('CDN 设置', style: titleStyle), - leading: Icon(MdiIcons.cloudPlusOutline, size: 20), - subtitle: Text( - '当前:${CDNServiceCode.fromCode(defaultCDNService)!.description},无法播放请切换', - style: subTitleStyle, - ), - onTap: () async { - Get.back(); - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: 'CDN 设置', - value: defaultCDNService, - values: CDNService.values.map((e) { - return { - 'title': e.description, - 'value': e.code - }; - }).toList()); + child: Material( + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + // ListTile( + // onTap: () {}, + // dense: true, + // enabled: false, + // leading: + // const Icon(Icons.network_cell_outlined, size: 20), + // title: Text('省流模式', style: titleStyle), + // subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle), + // trailing: Transform.scale( + // scale: 0.75, + // child: Switch( + // thumbIcon: WidgetStateProperty.resolveWith( + // (Set states) { + // if (states.isNotEmpty && + // states.first == WidgetState.selected) { + // return const Icon(Icons.done); + // } + // return null; // All other states will use the default thumbIcon. + // }), + // value: false, + // onChanged: (value) => {}, + // ), + // ), + // ), + // if (widget.videoDetailCtr?.userInfo != null) + ListTile( + dense: true, + onTap: () { + if (widget.videoDetailCtr?.userInfo == null) { + SmartDialog.showToast('账号未登录'); + return; + } + Get.back(); + Get.toNamed('/webviewnew', parameters: { + 'url': + 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr!.bvid)}&bvid=${widget.videoDetailCtr!.bvid}' + }); }, - ); - if (result != null) { - defaultCDNService = result; - setting.put(SettingBoxKey.CDNService, result); - SmartDialog.showToast( - '已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频'); - setState(() {}); - widget.videoDetailCtr!.queryVideoUrl(); - } - }, - ), - ListTile( - onTap: () { - Get.back(); - Player? player = - widget.controller?.videoPlayerController; - if (player == null) { - SmartDialog.showToast('播放器未初始化'); - return; - } - var pp = player.platform as NativePlayer; - pp.setProperty("video", "no"); - }, - dense: true, - leading: const Icon(Icons.headphones_outlined, size: 20), - title: const Text('听视频(需返回首页才能终止该状态)', style: titleStyle), - ), - ListTile( - onTap: () => {Get.back(), showSetVideoQa()}, - dense: true, - leading: const Icon(Icons.play_circle_outline, size: 20), - title: const Text('选择画质', style: titleStyle), - subtitle: Text( - '当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}', - style: subTitleStyle), - ), - if (widget.videoDetailCtr!.currentAudioQa != null) - ListTile( - onTap: () => {Get.back(), showSetAudioQa()}, - dense: true, - leading: const Icon(Icons.album_outlined, size: 20), - title: const Text('选择音质', style: titleStyle), - subtitle: Text( - '当前音质 ${widget.videoDetailCtr!.currentAudioQa!.description}', - style: subTitleStyle), - ), - ListTile( - onTap: () => {Get.back(), showSetDecodeFormats()}, - dense: true, - leading: const Icon(Icons.av_timer_outlined, size: 20), - title: const Text('解码格式', style: titleStyle), - subtitle: Text( - '当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}', - style: subTitleStyle), - ), - ListTile( - onTap: () => {Get.back(), showSetRepeat()}, - dense: true, - leading: const Icon(Icons.repeat, size: 20), - title: const Text('播放顺序', style: titleStyle), - subtitle: Text(widget.controller!.playRepeat.description, - style: subTitleStyle), - ), - ListTile( - onTap: () => {Get.back(), showSetDanmaku()}, - dense: true, - leading: const Icon(Icons.subtitles_outlined, size: 20), - title: const Text('弹幕设置', style: titleStyle), - ), - ListTile( - title: const Text('播放信息', style: titleStyle), - leading: const Icon(Icons.info_outline, size: 20), - onTap: () { - Player? player = - widget.controller?.videoPlayerController; - if (player == null) { - SmartDialog.showToast('播放器未初始化'); - return; - } - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('播放信息'), - content: SizedBox( - width: double.maxFinite, - child: ListView( - children: [ - ListTile( - title: const Text("Resolution"), - subtitle: Text( - '${player.state.width}x${player.state.height}'), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "Resolution\n${player.state.width}x${player.state.height}", - ), - ); - }, - ), - ListTile( - title: const Text("VideoParams"), - subtitle: Text(player.state.videoParams - .toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "VideoParams\n${player.state.videoParams}", - ), - ); - }, - ), - ListTile( - title: const Text("AudioParams"), - subtitle: Text(player.state.audioParams - .toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "AudioParams\n${player.state.audioParams}", - ), - ); - }, - ), - ListTile( - title: const Text("Media"), - subtitle: Text( - player.state.playlist.toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "Media\n${player.state.playlist}", - ), - ); - }, - ), - ListTile( - title: const Text("AudioTrack"), - subtitle: Text(player.state.track.audio - .toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "AudioTrack\n${player.state.track.audio}", - ), - ); - }, - ), - ListTile( - title: const Text("VideoTrack"), - subtitle: Text(player.state.track.video - .toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "VideoTrack\n${player.state.track.audio}", - ), - ); - }, - ), - ListTile( - title: const Text("pitch"), + leading: const Icon(Icons.error_outline, size: 20), + title: const Text('举报', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () async { + Get.back(); + final res = await UserHttp.toViewLater( + bvid: widget.videoDetailCtr!.bvid); + SmartDialog.showToast(res['msg']); + }, + leading: + const Icon(Icons.watch_later_outlined, size: 20), + title: const Text('添加至「稍后再看」', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), scheduleExit()}, + leading: const Icon(Icons.hourglass_top_outlined, + size: 20), + title: const Text('定时关闭', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () => { + Get.back(), + widget.videoDetailCtr!.queryVideoUrl() + }, + leading: const Icon(Icons.refresh_outlined, size: 20), + title: const Text('重载视频', style: titleStyle), + ), + ListTile( + dense: true, + title: const Text('CDN 设置', style: titleStyle), + leading: Icon(MdiIcons.cloudPlusOutline, size: 20), + subtitle: Text( + '当前:${CDNServiceCode.fromCode(defaultCDNService)!.description},无法播放请切换', + style: subTitleStyle, + ), + onTap: () async { + Get.back(); + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: 'CDN 设置', + value: defaultCDNService, + values: CDNService.values.map((e) { + return { + 'title': e.description, + 'value': e.code + }; + }).toList()); + }, + ); + if (result != null) { + defaultCDNService = result; + setting.put(SettingBoxKey.CDNService, result); + SmartDialog.showToast( + '已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频'); + setState(() {}); + widget.videoDetailCtr!.queryVideoUrl(); + } + }, + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + Player? player = + widget.controller?.videoPlayerController; + if (player == null) { + SmartDialog.showToast('播放器未初始化'); + return; + } + var pp = player.platform as NativePlayer; + pp.setProperty("video", "no"); + }, + leading: + const Icon(Icons.headphones_outlined, size: 20), + title: const Text('听视频(需返回首页才能终止该状态)', + style: titleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetVideoQa()}, + leading: + const Icon(Icons.play_circle_outline, size: 20), + title: const Text('选择画质', style: titleStyle), + subtitle: Text( + '当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}', + style: subTitleStyle), + ), + if (widget.videoDetailCtr!.currentAudioQa != null) + ListTile( + dense: true, + onTap: () => {Get.back(), showSetAudioQa()}, + leading: const Icon(Icons.album_outlined, size: 20), + title: const Text('选择音质', style: titleStyle), + subtitle: Text( + '当前音质 ${widget.videoDetailCtr!.currentAudioQa!.description}', + style: subTitleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetDecodeFormats()}, + leading: + const Icon(Icons.av_timer_outlined, size: 20), + title: const Text('解码格式', style: titleStyle), + subtitle: Text( + '当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}', + style: subTitleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetRepeat()}, + leading: const Icon(Icons.repeat, size: 20), + title: const Text('播放顺序', style: titleStyle), + subtitle: Text( + widget.controller!.playRepeat.description, + style: subTitleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetDanmaku()}, + leading: + const Icon(Icons.subtitles_outlined, size: 20), + title: const Text('弹幕设置', style: titleStyle), + ), + ListTile( + dense: true, + title: const Text('播放信息', style: titleStyle), + leading: const Icon(Icons.info_outline, size: 20), + onTap: () { + Player? player = + widget.controller?.videoPlayerController; + if (player == null) { + SmartDialog.showToast('播放器未初始化'); + return; + } + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('播放信息'), + content: SizedBox( + width: double.maxFinite, + child: ListView( + children: [ + ListTile( + dense: true, + title: const Text("Resolution"), subtitle: Text( - player.state.pitch.toString()), + '${player.state.width}x${player.state.height}'), onTap: () { Clipboard.setData( ClipboardData( text: - "pitch\n${player.state.pitch}", + "Resolution\n${player.state.width}x${player.state.height}", ), ); - }), - ListTile( - title: const Text("rate"), - subtitle: Text( - player.state.rate.toString()), + }, + ), + ListTile( + dense: true, + title: const Text("VideoParams"), + subtitle: Text(player + .state.videoParams + .toString()), onTap: () { Clipboard.setData( ClipboardData( text: - "rate\n${player.state.rate}", + "VideoParams\n${player.state.videoParams}", ), ); - }), - ListTile( - title: const Text("AudioBitrate"), - subtitle: Text(player.state.audioBitrate - .toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "AudioBitrate\n${player.state.audioBitrate}", - ), - ); - }, - ), - ListTile( - title: const Text("Volume"), - subtitle: Text( - player.state.volume.toString()), - onTap: () { - Clipboard.setData( - ClipboardData( - text: - "Volume\n${player.state.volume}", - ), - ); - }, - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '确定', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), + }, + ), + ListTile( + dense: true, + title: const Text("AudioParams"), + subtitle: Text(player + .state.audioParams + .toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "AudioParams\n${player.state.audioParams}", + ), + ); + }, + ), + ListTile( + dense: true, + title: const Text("Media"), + subtitle: Text( + player.state.playlist.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "Media\n${player.state.playlist}", + ), + ); + }, + ), + ListTile( + dense: true, + title: const Text("AudioTrack"), + subtitle: Text(player + .state.track.audio + .toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "AudioTrack\n${player.state.track.audio}", + ), + ); + }, + ), + ListTile( + dense: true, + title: const Text("VideoTrack"), + subtitle: Text(player + .state.track.video + .toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "VideoTrack\n${player.state.track.audio}", + ), + ); + }, + ), + ListTile( + dense: true, + title: const Text("pitch"), + subtitle: Text( + player.state.pitch.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "pitch\n${player.state.pitch}", + ), + ); + }), + ListTile( + dense: true, + title: const Text("rate"), + subtitle: Text( + player.state.rate.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "rate\n${player.state.rate}", + ), + ); + }), + ListTile( + dense: true, + title: const Text("AudioBitrate"), + subtitle: Text(player + .state.audioBitrate + .toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "AudioBitrate\n${player.state.audioBitrate}", + ), + ); + }, + ), + ListTile( + dense: true, + title: const Text("Volume"), + subtitle: Text( + player.state.volume.toString()), + onTap: () { + Clipboard.setData( + ClipboardData( + text: + "Volume\n${player.state.volume}", + ), + ); + }, + ), + ], ), ), - ], - ); - }, - ); - }) - ], + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + '确定', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), + ], + ); + }, + ); + }, + ), + ], + ), + ), ), - )) + ), ], ), ); @@ -524,6 +565,7 @@ class _HeaderControlState extends State { const SizedBox(height: 10), for (final int choice in scheduleTimeChoices) ...[ ListTile( + dense: true, onTap: () { shutdownTimerService.scheduledExitInMinutes = choice; @@ -531,7 +573,6 @@ class _HeaderControlState extends State { Get.back(); }, contentPadding: const EdgeInsets.only(), - dense: true, title: Text(choice == -1 ? "禁用" : "$choice分钟后"), trailing: shutdownTimerService .scheduledExitInMinutes == @@ -551,12 +592,12 @@ class _HeaderControlState extends State { )), const SizedBox(height: 10), ListTile( + dense: true, onTap: () { shutdownTimerService.waitForPlayingCompleted = !shutdownTimerService.waitForPlayingCompleted; setState(() {}); }, - dense: true, contentPadding: const EdgeInsets.only(), title: const Text("额外等待视频播放完毕", style: titleStyle), trailing: Switch( @@ -681,6 +722,7 @@ class _HeaderControlState extends State { children: [ for (int i = 0; i < totalQaSam; i++) ...[ ListTile( + dense: true, onTap: () { if (currentVideoQa.code == videoFormat[i].quality) { @@ -701,7 +743,6 @@ class _HeaderControlState extends State { widget.videoDetailCtr!.updatePlayer(); Get.back(); }, - dense: true, // 可能包含会员解锁画质 enabled: i >= totalQaSam - userfulQaSam, contentPadding: @@ -761,6 +802,7 @@ class _HeaderControlState extends State { children: [ for (final AudioItem i in audio) ...[ ListTile( + dense: true, onTap: () { if (currentAudioQa.code == i.id) { return; @@ -779,7 +821,6 @@ class _HeaderControlState extends State { widget.videoDetailCtr!.updatePlayer(); Get.back(); }, - dense: true, contentPadding: const EdgeInsets.only(left: 20, right: 20), title: Text(i.quality!), @@ -847,6 +888,7 @@ class _HeaderControlState extends State { children: [ for (var i in list) ...[ ListTile( + dense: true, onTap: () { if (i.startsWith(currentDecodeFormats.code)) return; widget.videoDetailCtr!.currentDecodeFormats = @@ -854,7 +896,6 @@ class _HeaderControlState extends State { widget.videoDetailCtr!.updatePlayer(); Get.back(); }, - dense: true, contentPadding: const EdgeInsets.only(left: 20, right: 20), title: Text(VideoDecodeFormatsCode.fromString(i)! @@ -940,7 +981,9 @@ class _HeaderControlState extends State { left: 12, top: 12, right: 12, - bottom: widget.controller?.isFullScreen.value == true ? 70 : 12, + bottom: + (widget.controller?.isFullScreen.value == true ? 70 : 12) + + MediaQuery.paddingOf(context).bottom, ), padding: const EdgeInsets.only(left: 14, right: 14), child: SingleChildScrollView( @@ -1427,11 +1470,11 @@ class _HeaderControlState extends State { children: [ for (final PlayRepeat i in PlayRepeat.values) ...[ ListTile( + dense: true, onTap: () { widget.controller!.setPlayRepeat(i); Get.back(); }, - dense: true, contentPadding: const EdgeInsets.only(left: 20, right: 20), title: Text(i.description), @@ -1481,45 +1524,45 @@ class _HeaderControlState extends State { title: Row( children: [ SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回', + icon: const Icon( + FontAwesomeIcons.arrowLeft, + size: 15, + color: Colors.white, + ), + onPressed: () { + if (isFullScreen) { + widget.controller!.triggerFullScreen(status: false); + } else if (MediaQuery.of(context).orientation == + Orientation.landscape && + !horizontalScreen) { + verticalScreenForTwoSeconds(); + } else { + Get.back(); + } + }, + ), + ), + if (!isFullScreen || + MediaQuery.of(context).orientation != Orientation.portrait) + SizedBox( width: 42, height: 34, child: IconButton( - tooltip: '上一页', + tooltip: '返回主页', icon: const Icon( - FontAwesomeIcons.arrowLeft, + FontAwesomeIcons.house, size: 15, color: Colors.white, ), onPressed: () { - if (isFullScreen) { - widget.controller!.triggerFullScreen(status: false); - } else if (MediaQuery.of(context).orientation == - Orientation.landscape && - !horizontalScreen) { - verticalScreenForTwoSeconds(); - } else { - Get.back(); - } + Get.until((route) => route.isFirst); }, - )), - if (!isFullScreen || - MediaQuery.of(context).orientation != Orientation.portrait) - SizedBox( - width: 42, - height: 34, - child: IconButton( - tooltip: '返回主页', - icon: const Icon( - FontAwesomeIcons.house, - size: 15, - color: Colors.white, - ), - onPressed: () async { - // 销毁播放器实例 - // await widget.controller!.dispose(type: 'all'); - Get.until((route) => route.isFirst); - }, - )), + ), + ), if ((videoIntroController.videoDetail.value.title != null) && (isFullScreen || equivalentFullScreen)) Column( @@ -1579,9 +1622,7 @@ class _HeaderControlState extends State { ), ), ), - const SizedBox( - width: 15, - ), + const SizedBox(width: 15), ], // ComBtn( // icon: const Icon( @@ -1780,7 +1821,7 @@ class _HeaderControlState extends State { style: ButtonStyle( padding: WidgetStateProperty.all(EdgeInsets.zero), ), - onPressed: () => showSettingSheet(), + onPressed: showSettingSheet, icon: const Icon( Icons.more_vert_outlined, size: 19, diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index ffb442d1f..1f4701b35 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -38,7 +38,7 @@ import 'widgets/play_pause_btn.dart'; class PLVideoPlayer extends StatefulWidget { const PLVideoPlayer({ - required this.controller, + required this.plPlayerController, this.videoIntroController, this.bangumiIntroController, this.headerControl, @@ -52,7 +52,7 @@ class PLVideoPlayer extends StatefulWidget { super.key, }); - final PlPlayerController controller; + final PlPlayerController plPlayerController; final VideoIntroController? videoIntroController; final BangumiIntroController? bangumiIntroController; final PreferredSizeWidget? headerControl; @@ -123,7 +123,6 @@ class _PLVideoPlayerState extends State // 双击播放、暂停 void onDoubleTapCenter() { - final PlPlayerController plPlayerController = widget.controller; plPlayerController.videoPlayerController!.playOrPause(); } @@ -152,12 +151,12 @@ class _PLVideoPlayerState extends State super.initState(); animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 100)); - videoController = widget.controller.videoController!; + videoController = plPlayerController.videoController!; videoIntroController = widget.videoIntroController; bangumiIntroController = widget.bangumiIntroController; - widget.controller.headerControl = widget.headerControl; - widget.controller.bottomControl = widget.bottomControl; - widget.controller.danmuWidget = widget.danmuWidget; + plPlayerController.headerControl = widget.headerControl; + plPlayerController.bottomControl = widget.bottomControl; + plPlayerController.danmuWidget = widget.danmuWidget; defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior, defaultValue: BtmProgressBehavior.values.first.code); enableQuickDouble = @@ -218,7 +217,7 @@ class _PLVideoPlayerState extends State _brightnessIndicator.value = false; } }); - widget.controller.brightness.value = value; + plPlayerController.brightness.value = value; } @override @@ -230,7 +229,6 @@ class _PLVideoPlayerState extends State // 动态构建底部控制条 List buildBottomControl() { - final PlPlayerController plPlayerController = widget.controller; bool isSeason = videoIntroController?.videoDetail.value.ugcSeason != null; bool isPage = videoIntroController?.videoDetail.value.pages != null && videoIntroController!.videoDetail.value.pages!.length > 1; @@ -239,7 +237,7 @@ class _PLVideoPlayerState extends State Map videoProgressWidgets = { /// 上一集 BottomControlType.pre: Container( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, alignment: Alignment.center, child: ComBtn( @@ -271,7 +269,7 @@ class _PLVideoPlayerState extends State /// 下一集 BottomControlType.next: Container( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, alignment: Alignment.center, child: ComBtn( @@ -338,7 +336,7 @@ class _PLVideoPlayerState extends State () => plPlayerController.viewPointList.isEmpty ? const SizedBox.shrink() : Container( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, alignment: Alignment.center, child: ComBtn( @@ -358,7 +356,7 @@ class _PLVideoPlayerState extends State /// 选集 BottomControlType.episode: Container( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, alignment: Alignment.center, child: ComBtn( @@ -370,8 +368,8 @@ class _PLVideoPlayerState extends State ), fuc: () { int? index; - int currentCid = widget.controller.cid; - String bvid = widget.controller.bvid; + int currentCid = plPlayerController.cid; + String bvid = plPlayerController.bvid; List episodes = []; // late Function changeFucCall; if (isPage) { @@ -385,7 +383,7 @@ class _PLVideoPlayerState extends State for (int i = 0; i < sections.length; i++) { final List episodesList = sections[i].episodes!; for (int j = 0; j < episodesList.length; j++) { - if (episodesList[j].cid == widget.controller.cid) { + if (episodesList[j].cid == plPlayerController.cid) { index = i; episodes = episodesList; break; @@ -413,7 +411,7 @@ class _PLVideoPlayerState extends State /// 画面比例 BottomControlType.fit: SizedBox( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, child: TextButton( onPressed: () => plPlayerController.toggleVideoFit(), @@ -434,7 +432,7 @@ class _PLVideoPlayerState extends State () => plPlayerController.vttSubtitles.isEmpty ? const SizedBox.shrink() : SizedBox( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, child: PopupMenuButton( onSelected: (int value) { @@ -475,9 +473,10 @@ class _PLVideoPlayerState extends State ), /// 播放速度 - BottomControlType.speed: SizedBox( - width: 35, + BottomControlType.speed: Container( height: 30, + margin: const EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.center, child: PopupMenuButton( onSelected: (double value) { plPlayerController.setPlaybackSpeed(value); @@ -498,33 +497,25 @@ class _PLVideoPlayerState extends State ); }).toList(); }, - child: Container( - width: 35, - height: 30, - alignment: Alignment.center, - child: Obx(() => Text("${plPlayerController.playbackSpeed}X", - style: const TextStyle(color: Colors.white, fontSize: 13), - semanticsLabel: "${plPlayerController.playbackSpeed}倍速")), - ), + child: Text("${plPlayerController.playbackSpeed}X", + style: const TextStyle(color: Colors.white, fontSize: 13), + semanticsLabel: "${plPlayerController.playbackSpeed}倍速"), ), ), /// 全屏 BottomControlType.fullscreen: SizedBox( - width: 35, + width: isFullScreen ? 42 : 35, height: 30, child: Obx(() => ComBtn( icon: Icon( - plPlayerController.isFullScreen.value - ? Icons.fullscreen_exit - : Icons.fullscreen, - semanticLabel: - plPlayerController.isFullScreen.value ? '退出全屏' : '全屏', + isFullScreen ? Icons.fullscreen_exit : Icons.fullscreen, + semanticLabel: isFullScreen ? '退出全屏' : '全屏', size: 24, color: Colors.white, ), - fuc: () => plPlayerController.triggerFullScreen( - status: !plPlayerController.isFullScreen.value), + fuc: () => + plPlayerController.triggerFullScreen(status: !isFullScreen), )), ), }; @@ -538,7 +529,7 @@ class _PLVideoPlayerState extends State BottomControlType.space, BottomControlType.viewPoints, if (anySeason) BottomControlType.episode, - if (plPlayerController.isFullScreen.value) BottomControlType.fit, + if (isFullScreen) BottomControlType.fit, BottomControlType.subtitle, BottomControlType.speed, BottomControlType.fullscreen, @@ -558,12 +549,14 @@ class _PLVideoPlayerState extends State return list; } - PlPlayerController get plPlayerController => widget.controller; + PlPlayerController get plPlayerController => widget.plPlayerController; + + bool get isFullScreen => plPlayerController.isFullScreen.value; TextStyle get subTitleStyle => TextStyle( height: 1.5, fontSize: 16 * - (plPlayerController.isFullScreen.value + (isFullScreen ? plPlayerController.subtitleFontScaleFS.value : plPlayerController.subtitleFontScale.value), letterSpacing: 0.1, @@ -585,7 +578,7 @@ class _PLVideoPlayerState extends State @override Widget build(BuildContext context) { - if (plPlayerController.isFullScreen.value) { + if (isFullScreen) { plPlayerController.subtitleFontScaleFS.listen((value) { _updateSubtitle(value); }); @@ -701,15 +694,13 @@ class _PLVideoPlayerState extends State if (cumulativeDy > threshold) { _gestureType = 'center_down'; - if (plPlayerController.isFullScreen.value ^ - fullScreenGestureReverse) { + if (isFullScreen ^ fullScreenGestureReverse) { fullScreenTrigger(fullScreenGestureReverse); } // debugPrint('center_down:$cumulativeDy'); } else if (cumulativeDy < -threshold) { _gestureType = 'center_up'; - if (!plPlayerController.isFullScreen.value ^ - fullScreenGestureReverse) { + if (!isFullScreen ^ fullScreenGestureReverse) { fullScreenTrigger(!fullScreenGestureReverse); } // debugPrint('center_up:$cumulativeDy'); @@ -765,20 +756,23 @@ class _PLVideoPlayerState extends State opacity: plPlayerController.doubleSpeedStatus.value ? 1.0 : 0.0, duration: const Duration(milliseconds: 150), child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: const Color(0x88000000), - borderRadius: BorderRadius.circular(16.0), + alignment: Alignment.center, + decoration: BoxDecoration( + color: const Color(0x88000000), + borderRadius: BorderRadius.circular(16.0), + ), + height: 32.0, + width: 70.0, + child: Center( + child: Obx( + () => Text( + '${plPlayerController.enableAutoLongPressSpeed ? plPlayerController.playbackSpeed * 2 : plPlayerController.longPressSpeed}倍速中', + style: + const TextStyle(color: Colors.white, fontSize: 13), + ), ), - height: 32.0, - width: 70.0, - child: Center( - child: Obx(() => Text( - '${plPlayerController.enableAutoLongPressSpeed ? plPlayerController.playbackSpeed * 2 : plPlayerController.longPressSpeed}倍速中', - style: const TextStyle( - color: Colors.white, fontSize: 13), - )), - )), + ), + ), ), ), ), @@ -1022,7 +1016,7 @@ class _PLVideoPlayerState extends State position: 'bottom', child: widget.bottomControl ?? BottomControl( - controller: widget.controller, + controller: plPlayerController, buildBottomControl: buildBottomControl(), ), ), @@ -1047,11 +1041,11 @@ class _PLVideoPlayerState extends State } if (defaultBtmProgressBehavior == BtmProgressBehavior.onlyShowFullScreen.code && - !plPlayerController.isFullScreen.value) { + !isFullScreen) { return const SizedBox(); } else if (defaultBtmProgressBehavior == BtmProgressBehavior.onlyHideFullScreen.code && - plPlayerController.isFullScreen.value) { + isFullScreen) { return const SizedBox(); } @@ -1149,28 +1143,33 @@ class _PLVideoPlayerState extends State // 锁 Obx( () => Visibility( - visible: plPlayerController.videoType.value != 'live' && - plPlayerController.isFullScreen.value, + visible: + plPlayerController.videoType.value != 'live' && isFullScreen, child: Align( alignment: Alignment.centerLeft, child: FractionalTranslation( translation: const Offset(1, -0.4), child: Visibility( visible: plPlayerController.showControls.value && - (plPlayerController.isFullScreen.value || - plPlayerController.controlsLock.value), - child: ComBtn( - icon: Icon( - plPlayerController.controlsLock.value - ? FontAwesomeIcons.lock - : FontAwesomeIcons.lockOpen, - semanticLabel: - plPlayerController.controlsLock.value ? '解锁' : '锁定', - size: 15, - color: Colors.white, + (isFullScreen || plPlayerController.controlsLock.value), + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color(0x45000000), + borderRadius: BorderRadius.circular(8), + ), + child: ComBtn( + icon: Icon( + plPlayerController.controlsLock.value + ? FontAwesomeIcons.lock + : FontAwesomeIcons.lockOpen, + semanticLabel: + plPlayerController.controlsLock.value ? '解锁' : '锁定', + size: 15, + color: Colors.white, + ), + fuc: () => plPlayerController.onLockControl( + !plPlayerController.controlsLock.value), ), - fuc: () => plPlayerController - .onLockControl(!plPlayerController.controlsLock.value), ), ), ), @@ -1185,68 +1184,75 @@ class _PLVideoPlayerState extends State child: FractionalTranslation( translation: const Offset(-1, -0.4), child: Visibility( - visible: plPlayerController.showControls.value && - plPlayerController.isFullScreen.value, - child: ComBtn( - icon: const Icon( - Icons.photo_camera, - semanticLabel: '截图', - size: 20, - color: Colors.white, + visible: plPlayerController.showControls.value && isFullScreen, + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color(0x45000000), + borderRadius: BorderRadius.circular(8), ), - fuc: () { - SmartDialog.showToast('截图中'); - plPlayerController.videoPlayerController - ?.screenshot(format: 'image/png') - .then((value) { - if (value != null && context.mounted) { - SmartDialog.showToast('点击弹窗保存截图'); - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - // title: const Text('点击保存'), - titlePadding: EdgeInsets.zero, - contentPadding: const EdgeInsets.all(8), - insetPadding: - EdgeInsets.only(left: context.width / 2), - //移除圆角 - shape: const RoundedRectangleBorder(), - content: GestureDetector( - onTap: () async { - String name = DateTime.now().toString(); - final SaveResult result = - await SaverGallery.saveImage( - value, - fileName: name, - androidRelativePath: "Pictures/Screenshots", - skipIfExists: false, - ); + child: ComBtn( + icon: const Icon( + Icons.photo_camera, + semanticLabel: '截图', + size: 20, + color: Colors.white, + ), + fuc: () { + SmartDialog.showToast('截图中'); + plPlayerController.videoPlayerController + ?.screenshot(format: 'image/png') + .then((value) { + if (value != null && context.mounted) { + SmartDialog.showToast('点击弹窗保存截图'); + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + // title: const Text('点击保存'), + titlePadding: EdgeInsets.zero, + contentPadding: const EdgeInsets.all(8), + insetPadding: + EdgeInsets.only(left: context.width / 2), + //移除圆角 + shape: const RoundedRectangleBorder(), + content: GestureDetector( + onTap: () async { + String name = DateTime.now().toString(); + final SaveResult result = + await SaverGallery.saveImage( + value, + fileName: name, + androidRelativePath: + "Pictures/Screenshots", + skipIfExists: false, + ); - if (result.isSuccess) { - Get.back(); - SmartDialog.showToast('$name.png已保存到相册/截图'); - } else { - await SmartDialog.showToast( - '保存失败,${result.errorMessage}'); - } - }, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: context.width / 3, - maxHeight: context.height / 3, + if (result.isSuccess) { + Get.back(); + SmartDialog.showToast( + '$name.png已保存到相册/截图'); + } else { + await SmartDialog.showToast( + '保存失败,${result.errorMessage}'); + } + }, + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: context.width / 3, + maxHeight: context.height / 3, + ), + child: Image.memory(value), ), - child: Image.memory(value), ), - ), - ); - }, - ); - } else { - SmartDialog.showToast('截图失败'); - } - }); - }, + ); + }, + ); + } else { + SmartDialog.showToast('截图失败'); + } + }); + }, + ), ), ), ), @@ -1331,16 +1337,16 @@ class _PLVideoPlayerState extends State onSubmitted: (Duration value) { _hideSeekBackwardButton.value = true; _mountSeekBackwardButton.value = false; - final Player player = - widget.controller.videoPlayerController!; + final Player player = widget + .plPlayerController.videoPlayerController!; Duration result = player.state.position - value; result = result.clamp( Duration.zero, player.state.duration, ); - widget.controller - .seekTo(result, type: 'slider'); - widget.controller.play(); + plPlayerController.seekTo(result, + type: 'slider'); + plPlayerController.play(); }, ), ) @@ -1377,16 +1383,16 @@ class _PLVideoPlayerState extends State onSubmitted: (Duration value) { _hideSeekForwardButton.value = true; _mountSeekForwardButton.value = false; - final Player player = - widget.controller.videoPlayerController!; + final Player player = widget + .plPlayerController.videoPlayerController!; Duration result = player.state.position + value; result = result.clamp( Duration.zero, player.state.duration, ); - widget.controller - .seekTo(result, type: 'slider'); - widget.controller.play(); + plPlayerController.seekTo(result, + type: 'slider'); + plPlayerController.play(); }, ), ) diff --git a/lib/plugin/pl_player/widgets/common_btn.dart b/lib/plugin/pl_player/widgets/common_btn.dart index 8ea36ac81..e9a3a0b87 100644 --- a/lib/plugin/pl_player/widgets/common_btn.dart +++ b/lib/plugin/pl_player/widgets/common_btn.dart @@ -15,7 +15,7 @@ class ComBtn extends StatelessWidget { return SizedBox( width: 34, height: 34, - child: InkWell( + child: GestureDetector( onTap: fuc, child: icon!, ),