From 4d07f1508aea4a2d2619f904cb866868baa48f39 Mon Sep 17 00:00:00 2001 From: orz12 Date: Mon, 18 Dec 2023 21:25:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=85=A8=E5=B1=8F=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=85=A8=E5=B1=8F=E5=BC=B9?= =?UTF-8?q?=E5=B9=95=E4=B8=8E=E6=A8=AA=E5=B1=8F=E9=94=99=E4=BD=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 不再使用showDialog覆盖并传递对象的方式实现全屏,改用原控件调整高度(用Obx包裹SliverAppBar)、safeArea切换上下边距、构建detail页时根据屏幕方向切换状态栏可见性的方式实现全屏。 以上方式既能兼容屏幕旋转,也能绕过弹幕不加载的问题,还可以保留播放器上的弹幕避免旋屏时清空。 另外添加了两处针对全屏或旋屏状态的返回处理。 --- lib/pages/danmaku/view.dart | 6 +- lib/pages/video/detail/controller.dart | 4 +- lib/pages/video/detail/view.dart | 529 +++++++++--------- .../video/detail/widgets/header_control.dart | 13 +- lib/plugin/pl_player/controller.dart | 66 +-- 5 files changed, 330 insertions(+), 288 deletions(-) diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index 4f5527ff..dd7558d5 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -95,9 +95,9 @@ class _PlDanmakuState extends State { // 根据position判断是否有已缓存弹幕。没有则请求对应段 int segIndex = (currentPosition / (6 * 60 * 1000)).ceil(); segIndex = segIndex < 1 ? 1 : segIndex; - print('🌹🌹: ${segIndex}'); - print('🌹🌹: ${ctr.dmSegList.length}'); - print('🌹🌹: ${ctr.hasrequestSeg.contains(segIndex - 1)}'); + // print('🌹🌹: ${segIndex}'); + // print('🌹🌹: ${ctr.dmSegList.length}'); + // print('🌹🌹: ${ctr.hasrequestSeg.contains(segIndex - 1)}'); if (segIndex - 1 >= ctr.dmSegList.length || (ctr.dmSegList[segIndex - 1].elems.isEmpty && !ctr.hasrequestSeg.contains(segIndex - 1))) { diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 6fa0df6f..c3bb7b4b 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -138,8 +138,8 @@ class VideoDetailController extends GetxController } showReplyReplyPanel() { - PersistentBottomSheetController? ctr = - scaffoldKey.currentState?.showBottomSheet((BuildContext context) { + PersistentBottomSheetController? ctr = + scaffoldKey.currentState?.showBottomSheet((BuildContext context) { return VideoReplyReplyPanel( oid: oid, rpid: fRpid, diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index d67f9b66..e73e8eab 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -21,6 +21,7 @@ import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/utils/storage.dart'; +import 'package:pilipala/plugin/pl_player/utils/fullscreen.dart'; import 'widgets/header_control.dart'; class VideoDetailPage extends StatefulWidget { @@ -233,268 +234,298 @@ class _VideoDetailPageState extends State @override Widget build(BuildContext context) { final videoHeight = MediaQuery.of(context).size.width * 9 / 16; - final double pinnedHeaderHeight = - statusBarHeight + kToolbarHeight + videoHeight; - Widget childWhenDisabled = SafeArea( - top: false, - bottom: false, - child: Stack( - children: [ - Scaffold( - resizeToAvoidBottomInset: false, - key: videoDetailController.scaffoldKey, - backgroundColor: Colors.black, - body: ExtendedNestedScrollView( - controller: _extendNestCtr, - headerSliverBuilder: - (BuildContext context, bool innerBoxIsScrolled) { - return [ - SliverAppBar( - automaticallyImplyLeading: false, - pinned: false, - elevation: 0, - scrolledUnderElevation: 0, - forceElevated: innerBoxIsScrolled, - expandedHeight: videoHeight, - backgroundColor: Colors.black, - flexibleSpace: FlexibleSpaceBar( - background: Padding( - padding: EdgeInsets.only(top: statusBarHeight), - child: LayoutBuilder( - builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return Stack( - children: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: ((context, snapshot) { - if (snapshot.hasData && - snapshot.data['status']) { - return Obx( - () => !videoDetailController - .autoPlay.value - ? const SizedBox() - : PLVideoPlayer( - controller: plPlayerController!, - headerControl: - videoDetailController - .headerControl, - danmuWidget: Obx( - () => PlDanmaku( - key: Key( - videoDetailController - .danmakuCid.value - .toString()), - cid: videoDetailController - .danmakuCid.value, - playerController: - plPlayerController!, - ), - ), - ), - ); - } else { - return const SizedBox(); - } - }), - ), - - Obx( - () => Visibility( - visible: - videoDetailController.isShowCover.value, - child: Positioned( - top: 0, - left: 0, - right: 0, - child: NetworkImgLayer( - type: 'emote', - src: videoDetailController - .videoItem['pic'], - width: maxWidth, - height: maxHeight, + // final double pinnedHeaderHeight = + // statusBarHeight + kToolbarHeight + videoHeight; + if (MediaQuery.of(context).orientation == Orientation.landscape) { + enterFullScreen(); + } else { + exitFullScreen(); + } + Widget childWhenDisabled = PopScope( + canPop: !plPlayerController!.isFullScreen.value, + onPopInvoked: (bool didPop) { + if (plPlayerController!.isFullScreen.value) { + plPlayerController!.triggerFullScreen(status: false); + } + if (MediaQuery.of(context).orientation == Orientation.landscape) { + verticalScreen(); + } + }, + child: SafeArea( + top: MediaQuery.of(context).orientation == Orientation.portrait, + bottom: MediaQuery.of(context).orientation == Orientation.portrait, + left: !plPlayerController!.isFullScreen.value, + right: !plPlayerController!.isFullScreen.value, + child: Stack( + children: [ + Scaffold( + resizeToAvoidBottomInset: false, + key: videoDetailController.scaffoldKey, + backgroundColor: Colors.black, + body: ExtendedNestedScrollView( + controller: _extendNestCtr, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + Obx( + () => SliverAppBar( + automaticallyImplyLeading: false, + pinned: false, + elevation: 0, + scrolledUnderElevation: 0, + forceElevated: innerBoxIsScrolled, + expandedHeight: + plPlayerController!.isFullScreen.value || + MediaQuery.of(context).orientation == + Orientation.landscape + ? MediaQuery.of(context).size.height + : videoHeight, + backgroundColor: Colors.black, + flexibleSpace: FlexibleSpaceBar( + background: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + FutureBuilder( + future: _futureBuilderFuture, + builder: ((context, snapshot) { + if (snapshot.hasData && + snapshot.data['status']) { + return Obx( + () => !videoDetailController + .autoPlay.value + ? const SizedBox() + : PLVideoPlayer( + controller: + plPlayerController!, + headerControl: + videoDetailController + .headerControl, + danmuWidget: Obx( + () => PlDanmaku( + key: Key( + videoDetailController + .danmakuCid + .value + .toString()), + cid: + videoDetailController + .danmakuCid + .value, + playerController: + plPlayerController!, + ), + ), + ), + ); + } else { + return const SizedBox(); + } + }), ), - ), - ), - ), - /// 关闭自动播放时 手动播放 - Obx( - () => Visibility( - visible: videoDetailController - .isShowCover.value && - videoDetailController - .isEffective.value && - !videoDetailController.autoPlay.value, - child: Stack( - children: [ - Positioned( + Obx( + () => Visibility( + visible: videoDetailController + .isShowCover.value, + child: Positioned( top: 0, left: 0, right: 0, - child: AppBar( - primary: false, - foregroundColor: Colors.white, - backgroundColor: - Colors.transparent, - actions: [ - IconButton( - tooltip: '稍后再看', - onPressed: () async { - var res = await UserHttp - .toViewLater( - bvid: - videoDetailController - .bvid); - SmartDialog.showToast( - res['msg']); - }, - icon: const Icon( - Icons.history_outlined), + child: NetworkImgLayer( + type: 'emote', + src: videoDetailController + .videoItem['pic'], + width: maxWidth, + height: maxHeight, + ), + ), + ), + ), + + /// 关闭自动播放时 手动播放 + Obx( + () => Visibility( + visible: videoDetailController + .isShowCover.value && + videoDetailController + .isEffective.value && + !videoDetailController + .autoPlay.value, + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + child: AppBar( + primary: false, + foregroundColor: + Colors.white, + backgroundColor: + Colors.transparent, + actions: [ + IconButton( + tooltip: '稍后再看', + onPressed: () async { + var res = await UserHttp + .toViewLater( + bvid: + videoDetailController + .bvid); + SmartDialog.showToast( + res['msg']); + }, + icon: const Icon(Icons + .history_outlined), + ), + const SizedBox(width: 14) + ], + ), + ), + Positioned( + right: 12, + bottom: 10, + child: TextButton.icon( + style: ButtonStyle( + backgroundColor: + MaterialStateProperty + .resolveWith( + (states) { + return Theme.of(context) + .colorScheme + .primaryContainer; + }), + ), + onPressed: () => + handlePlay(), + icon: const Icon( + Icons.play_circle_outline, + size: 20, + ), + label: const Text('Play'), + ), ), - const SizedBox(width: 14) ], - ), - ), - Positioned( - right: 12, - bottom: 10, - child: TextButton.icon( - style: ButtonStyle( - backgroundColor: - MaterialStateProperty - .resolveWith((states) { - return Theme.of(context) - .colorScheme - .primaryContainer; - }), - ), - onPressed: () => handlePlay(), - icon: const Icon( - Icons.play_circle_outline, - size: 20, - ), - label: const Text('Play'), - ), - ), - ], - )), - ), - ], - ); - }, - ), + )), + ), + ], + ); + }, + ), + )), ), + ]; + }, + // pinnedHeaderSliverHeightBuilder: () { + // return playerStatus != PlayerStatus.playing + // ? statusBarHeight + kToolbarHeight + // : pinnedHeaderHeight; + // }, + /// 不收回 + // pinnedHeaderSliverHeightBuilder: () { + // return pinnedHeaderHeight; + // }, + onlyOneScrollInBody: true, + body: Container( + key: Key(heroTag), + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + Opacity( + opacity: 0, + child: SizedBox( + width: double.infinity, + height: 0, + child: Obx( + () => TabBar( + controller: videoDetailController.tabCtr, + dividerColor: Colors.transparent, + indicatorColor: + Theme.of(context).colorScheme.background, + tabs: videoDetailController.tabs + .map((String name) => Tab(text: name)) + .toList(), + ), + ), + ), + ), + Expanded( + child: TabBarView( + controller: videoDetailController.tabCtr, + children: [ + Builder( + builder: (context) { + return CustomScrollView( + key: const PageStorageKey('简介'), + slivers: [ + if (videoDetailController.videoType == + SearchType.video) ...[ + const VideoIntroPanel(), + ] else if (videoDetailController + .videoType == + SearchType.media_bangumi) ...[ + Obx(() => BangumiIntroPanel( + cid: videoDetailController + .cid.value)), + ], + // if (videoDetailController.videoType == + // SearchType.video) ...[ + // SliverPersistentHeader( + // floating: true, + // pinned: true, + // delegate: SliverHeaderDelegate( + // height: 50, + // child: + // const MenuRow(loadingStatus: false), + // ), + // ), + // ], + SliverToBoxAdapter( + child: Divider( + indent: 12, + endIndent: 12, + color: Theme.of(context) + .dividerColor + .withOpacity(0.06), + ), + ), + const RelatedVideoPanel(), + ], + ); + }, + ), + VideoReplyPanel( + bvid: videoDetailController.bvid, + ) + ], + ), + ), + ], ), ), - ]; - }, - // pinnedHeaderSliverHeightBuilder: () { - // return playerStatus != PlayerStatus.playing - // ? statusBarHeight + kToolbarHeight - // : pinnedHeaderHeight; - // }, - /// 不收回 - pinnedHeaderSliverHeightBuilder: () { - return pinnedHeaderHeight; - }, - onlyOneScrollInBody: true, - body: Container( - key: Key(heroTag), - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - Opacity( - opacity: 0, - child: SizedBox( - width: double.infinity, - height: 0, - child: Obx( - () => TabBar( - controller: videoDetailController.tabCtr, - dividerColor: Colors.transparent, - indicatorColor: - Theme.of(context).colorScheme.background, - tabs: videoDetailController.tabs - .map((String name) => Tab(text: name)) - .toList(), - ), - ), - ), - ), - Expanded( - child: TabBarView( - controller: videoDetailController.tabCtr, - children: [ - Builder( - builder: (context) { - return CustomScrollView( - key: const PageStorageKey('简介'), - slivers: [ - if (videoDetailController.videoType == - SearchType.video) ...[ - const VideoIntroPanel(), - ] else if (videoDetailController.videoType == - SearchType.media_bangumi) ...[ - Obx(() => BangumiIntroPanel( - cid: videoDetailController.cid.value)), - ], - // if (videoDetailController.videoType == - // SearchType.video) ...[ - // SliverPersistentHeader( - // floating: true, - // pinned: true, - // delegate: SliverHeaderDelegate( - // height: 50, - // child: - // const MenuRow(loadingStatus: false), - // ), - // ), - // ], - SliverToBoxAdapter( - child: Divider( - indent: 12, - endIndent: 12, - color: Theme.of(context) - .dividerColor - .withOpacity(0.06), - ), - ), - const RelatedVideoPanel(), - ], - ); - }, - ), - VideoReplyPanel( - bvid: videoDetailController.bvid, - ) - ], - ), - ), - ], ), ), - ), - ), - /// 重新进入会刷新 - // 播放完成/暂停播放 - // StreamBuilder( - // stream: appbarStream.stream, - // initialData: 0, - // builder: ((context, snapshot) { - // return ScrollAppBar( - // snapshot.data!.toDouble(), - // () => continuePlay(), - // playerStatus, - // null, - // ); - // }), - // ) - ], - ), - ); + /// 重新进入会刷新 + // 播放完成/暂停播放 + // StreamBuilder( + // stream: appbarStream.stream, + // initialData: 0, + // builder: ((context, snapshot) { + // return ScrollAppBar( + // snapshot.data!.toDouble(), + // () => continuePlay(), + // playerStatus, + // null, + // ); + // }), + // ) + ], + ), + )); Widget childWhenEnabled = FutureBuilder( key: Key(heroTag), future: _futureBuilderFuture, diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 10383900..5c2491a0 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -880,7 +880,18 @@ class _HeaderControlState extends State { size: 15, color: Colors.white, ), - fuc: () => Get.back(), + fuc: () => { + if (widget.controller!.isFullScreen.value){ + widget.controller!.triggerFullScreen(status: false) + } else { + if (MediaQuery.of(context).orientation == Orientation.landscape){ + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]) + }, + Get.back() + } + }, ), SizedBox(width: buttonSpace), ComBtn( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 80fd6671..706209e1 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -899,6 +899,7 @@ class PlPlayerController { await StatusBarControl.setHidden(true, animation: StatusBarAnimation.FADE); if (!isFullScreen.value && status) { /// 按照视频宽高比决定全屏方向 + toggleFullScreen(true); switch (mode) { case FullScreenMode.auto: if (direction.value == 'horizontal') { @@ -927,41 +928,40 @@ class PlPlayerController { break; } - toggleFullScreen(true); - bool isValid = - direction.value == 'vertical' || mode == FullScreenMode.vertical - ? true - : false; - var result = await showDialog( - context: Get.context!, - useSafeArea: false, - builder: (context) => Dialog.fullscreen( - backgroundColor: Colors.black, - child: SafeArea( - // 忽略手机安全区域 - top: isValid, - left: false, - right: false, - bottom: isValid, - child: PLVideoPlayer( - controller: this, - headerControl: headerControl, - bottomControl: bottomControl, - danmuWidget: danmuWidget, - ), - ), - ), - ); - if (result == null) { - // 退出全屏 - StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); - exitFullScreen(); - await verticalScreen(); - toggleFullScreen(false); - } + // bool isValid = + // direction.value == 'vertical' || mode == FullScreenMode.vertical + // ? true + // : false; + // var result = await showDialog( + // context: Get.context!, + // useSafeArea: false, + // builder: (context) => Dialog.fullscreen( + // backgroundColor: Colors.black, + // child: SafeArea( + // // 忽略手机安全区域 + // top: isValid, + // left: false, + // right: false, + // bottom: isValid, + // child: PLVideoPlayer( + // controller: this, + // headerControl: headerControl, + // bottomControl: bottomControl, + // danmuWidget: danmuWidget, + // ), + // ), + // ), + // ); + // if (result == null) { + // // 退出全屏 + // StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); + // exitFullScreen(); + // await verticalScreen(); + // toggleFullScreen(false); + // } } else if (isFullScreen.value) { StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); - Get.back(); + // Get.back(); exitFullScreen(); await verticalScreen(); toggleFullScreen(false);