diff --git a/lib/common/widgets/flutter/draggable_scrollable_sheet.dart b/lib/common/widgets/flutter/draggable_scrollable_sheet.dart index d82c21fae..9ff5370e5 100644 --- a/lib/common/widgets/flutter/draggable_scrollable_sheet.dart +++ b/lib/common/widgets/flutter/draggable_scrollable_sheet.dart @@ -984,6 +984,7 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> { required _ResetNotifier super.notifier, }); + // ignore: unused_element bool _sendReset() => notifier!.sendReset(); /// Specifies whether the [DraggableScrollableSheet] should reset to its diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 0abc9a9ef..83577969d 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -168,21 +168,55 @@ class VideoDetailController extends GetxController late final scrollKey = GlobalKey(); late final RxBool isVertical; late final RxDouble scrollRatio = 0.0.obs; + ScrollController? _scrollCtr; ScrollController get scrollCtr => _scrollCtr ??= ScrollController()..addListener(scrollListener); + late bool isExpanding = false; late bool isCollapsing = false; - AnimationController? animController; - AnimationController get animationController => - animController ??= AnimationController( - vsync: this, - duration: const Duration(milliseconds: 200), - ); late double minVideoHeight; late double maxVideoHeight; late double videoHeight; + late double animHeight; + + AnimationController? animController; + AnimationController get animationController => + animController ??= (AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + )..addListener(_animListener)); + + void refreshPage() { + if (scrollKey.currentState?.mounted ?? false) { + (scrollKey.currentState!.context as Element).markNeedsBuild(); + } + } + + void _animListener() { + if (animationController.isForwardOrCompleted) { + _calcAnimHeight(); + refreshPage(); + } + } + + void _calcAnimHeight() { + if (isExpanding) { + animHeight = clampDouble( + videoHeight * animationController.value, + kToolbarHeight, + videoHeight, + ); + } else if (isCollapsing) { + animHeight = clampDouble( + maxVideoHeight - + (maxVideoHeight - minVideoHeight) * animationController.value, + minVideoHeight, + maxVideoHeight, + ); + } + } void animToTop() { final outerController = scrollKey.currentState!.outerController; @@ -195,8 +229,18 @@ class VideoDetailController extends GetxController } } + bool _needAnimOnDimensionChanged(bool isVertical) { + if (isFullScreen) { + if (PlatformUtils.isMobile) { + plPlayerController.changeOrientation(isVertical: isVertical); + } + return false; + } + return true; + } + @pragma('vm:notify-debugger-on-exception') - void setVideoHeight() { + void _setVideoHeight() { try { var width = firstVideo.width; var height = firstVideo.height; @@ -205,10 +249,11 @@ class VideoDetailController extends GetxController final ugcIntroCtr = Get.find(tag: heroTag); final data = ugcIntroCtr.videoDetail.value; if (data.cid == cid.value) { - width = data.dimension!.width!; - height = data.dimension!.height!; + final dimension = data.dimension!; + width = dimension.width!; + height = dimension.height!; } else { - ugcIntroCtr.queryVideoIntro().whenComplete(setVideoHeight); + ugcIntroCtr.queryVideoIntro().whenComplete(_setVideoHeight); return; } } else { @@ -227,10 +272,12 @@ class VideoDetailController extends GetxController if (this.videoHeight != videoHeight) { if (videoHeight > this.videoHeight) { // current minVideoHeight - isExpanding = true; - animationController.forward( - from: (minVideoHeight - scrollCtr.offset) / maxVideoHeight, - ); + if (_needAnimOnDimensionChanged(isVertical)) { + isExpanding = true; + animationController.forward( + from: (minVideoHeight - scrollCtr.offset) / maxVideoHeight, + ); + } this.videoHeight = maxVideoHeight; } else { // current maxVideoHeight @@ -238,20 +285,28 @@ class VideoDetailController extends GetxController .toPrecision(2); double minVideoHeightPrecise = minVideoHeight.toPrecision(2); if (currentHeight == minVideoHeightPrecise) { - isExpanding = true; - this.videoHeight = minVideoHeight; + if (_needAnimOnDimensionChanged(isVertical)) { + isExpanding = true; + this.videoHeight = minVideoHeight; + } animationController.forward(from: 1); } else if (currentHeight < minVideoHeightPrecise) { // expand - isExpanding = true; - animationController.forward(from: currentHeight / minVideoHeight); + if (_needAnimOnDimensionChanged(isVertical)) { + isExpanding = true; + animationController.forward( + from: currentHeight / minVideoHeight, + ); + } this.videoHeight = minVideoHeight; } else { // collapse - isCollapsing = true; - animationController.forward( - from: scrollCtr.offset / (maxVideoHeight - minVideoHeight), - ); + if (_needAnimOnDimensionChanged(isVertical)) { + isCollapsing = true; + animationController.forward( + from: scrollCtr.offset / (maxVideoHeight - minVideoHeight), + ); + } this.videoHeight = minVideoHeight; } } @@ -313,7 +368,7 @@ class VideoDetailController extends GetxController defaultST = Duration.zero; } data = PlayUrlModel(timeLength: entry.totalTimeMilli); - setVideoHeight(); + _setVideoHeight(); } @override @@ -844,7 +899,7 @@ class VideoDetailController extends GetxController codecs: 'avc1', quality: videoQuality, ); - setVideoHeight(); + _setVideoHeight(); currentDecodeFormats = VideoDecodeFormatType.fromString('avc1'); currentVideoQa.value = videoQuality; await _initPlayerIfNeeded(autoFullScreenFlag); @@ -856,7 +911,7 @@ class VideoDetailController extends GetxController _autoPlay.value = false; videoState.value = false; if (plPlayerController.isFullScreen.value) { - plPlayerController.toggleFullScreen(false); + plPlayerController.triggerFullScreen(status: false); } isQuerying = false; return; @@ -918,7 +973,7 @@ class VideoDetailController extends GetxController (e) => currentDecodeFormats.codes.any(e.codecs!.startsWith), orElse: () => videosList.first, ); - setVideoHeight(); + _setVideoHeight(); videoUrl = VideoUtils.getCdnUrl(firstVideo.playUrls); @@ -951,8 +1006,9 @@ class VideoDetailController extends GetxController _autoPlay.value = false; videoState.value = false; if (plPlayerController.isFullScreen.value) { - plPlayerController.toggleFullScreen(false); + plPlayerController.triggerFullScreen(status: false); } + result.toast(); } isQuerying = false; } @@ -1217,7 +1273,9 @@ class VideoDetailController extends GetxController _scrollCtr ?..removeListener(scrollListener) ..dispose(); - animController?.dispose(); + animController + ?..removeListener(_animListener) + ..dispose(); subtitles.clear(); vttSubtitles.clear(); super.onClose(); diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 6f0d7ef7e..33ee64b06 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -219,10 +219,10 @@ class _VideoDetailPageVState extends State videoDetailController.videoHeight, ); } else { - refreshPage(); + videoDetailController.refreshPage(); } } else { - refreshPage(); + videoDetailController.refreshPage(); } } } catch (e) { @@ -326,8 +326,6 @@ class _VideoDetailPageVState extends State ?..removeStatusLister(playerListener) ..removePositionListener(positionListener); - videoDetailController.animController?.removeListener(animListener); - Get.delete( tag: videoDetailController.heroTag, ); @@ -480,48 +478,10 @@ class _VideoDetailPageVState extends State : Theme.of(context); } - void animListener() { - if (videoDetailController.animationController.isForwardOrCompleted) { - cal(); - refreshPage(); - } - } - - late double animHeight; - - void cal() { - if (videoDetailController.isExpanding) { - animHeight = clampDouble( - videoDetailController.videoHeight * - videoDetailController.animationController.value, - kToolbarHeight, - videoDetailController.videoHeight, - ); - } else if (videoDetailController.isCollapsing) { - animHeight = clampDouble( - videoDetailController.maxVideoHeight - - (videoDetailController.maxVideoHeight - - videoDetailController.minVideoHeight) * - videoDetailController.animationController.value, - videoDetailController.minVideoHeight, - videoDetailController.maxVideoHeight, - ); - } - } - - void refreshPage() { - if (videoDetailController.scrollKey.currentState?.mounted == true) { - videoDetailController.scrollKey.currentState?.setState(() {}); - } - } - bool get removeAppBar => videoDetailController.removeSafeArea || (isFullScreen && !isPortrait); Widget get childWhenDisabled { - videoDetailController.animationController - ..removeListener(animListener) - ..addListener(animListener); return Obx( () { final isFullScreen = this.isFullScreen; @@ -570,7 +530,7 @@ class _VideoDetailPageVState extends State ? maxHeight - (isPortrait ? padding.top : 0) : videoDetailController.isExpanding || videoDetailController.isCollapsing - ? animHeight + ? videoDetailController.animHeight : videoDetailController.isCollapsing || (plPlayerController?.playerStatus.isPlaying ?? false) ? videoDetailController.minVideoHeight @@ -580,13 +540,13 @@ class _VideoDetailPageVState extends State videoDetailController.isExpanding = false; WidgetsBinding.instance.addPostFrameCallback((_) { videoDetailController.scrollRatio.value = 0; - refreshPage(); + videoDetailController.refreshPage(); }); } else if (videoDetailController.isCollapsing && videoDetailController.animationController.value == 1) { videoDetailController.isCollapsing = false; WidgetsBinding.instance.addPostFrameCallback((_) { - refreshPage(); + videoDetailController.refreshPage(); }); } return pinnedHeight; @@ -596,7 +556,7 @@ class _VideoDetailPageVState extends State ? maxHeight - (isPortrait ? padding.top : 0) : videoDetailController.isExpanding || videoDetailController.isCollapsing - ? animHeight + ? videoDetailController.animHeight : videoDetailController.videoHeight; return [ SliverPinnedDynamicHeader( @@ -833,10 +793,10 @@ class _VideoDetailPageVState extends State ? null : AppBar(backgroundColor: Colors.black, toolbarHeight: 0), body: Padding( - padding: !isFullScreen - ? padding.copyWith(top: 0, bottom: 0) - : EdgeInsets.zero, - child: childWhenDisabledLandscapeInner(isFullScreen, padding), + padding: isFullScreen + ? EdgeInsets.zero + : padding.copyWith(top: 0, bottom: 0), + child: childWhenDisabledLandscapeInner(isFullScreen), ), ); }, @@ -893,70 +853,75 @@ class _VideoDetailPageVState extends State ); } - Widget childWhenDisabledLandscapeInner( - bool isFullScreen, - EdgeInsets padding, - ) => Obx(() { - if (videoDetailController.isVertical.value && - enableVerticalExpand && - !isPortrait) { - final double videoHeight = maxHeight - padding.vertical; - final double width = videoHeight / Style.aspectRatio16x9; - final videoWidth = isFullScreen ? maxWidth : width; - final introWidth = (maxWidth - padding.horizontal - width) / 2; - final introHeight = maxHeight - padding.top; - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Offstage( - offstage: isFullScreen, - child: SizedBox( - width: introWidth, - height: introHeight, - child: videoIntro( - width: introWidth, - height: introHeight, - ), - ), - ), - SizedBox( - width: videoWidth, - height: videoHeight, - child: videoPlayer( - width: videoWidth, - height: videoHeight, - ), - ), - Offstage( - offstage: isFullScreen, - child: SizedBox( - width: introWidth, - height: introHeight, - child: Scaffold( - key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - body: Column( - children: [ - buildTabBar(showIntro: false), - Expanded( - child: tabBarView( - controller: videoDetailController.tabCtr, - children: [ - if (videoDetailController.showReply) - videoReplyPanel(), - if (_shouldShowSeasonPanel) seasonPanel, - ], - ), - ), - ], + Widget childWhenDisabledLandscapeInner(bool isFullScreen) { + if (enableVerticalExpand) { + return Obx(() { + if (videoDetailController.isVertical.value && !isPortrait) { + final double videoHeight = maxHeight - padding.vertical; + final double width = videoHeight / Style.aspectRatio16x9; + final videoWidth = isFullScreen ? maxWidth : width; + final introWidth = (maxWidth - padding.horizontal - width) / 2; + final introHeight = maxHeight - padding.top; + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Offstage( + offstage: isFullScreen, + child: SizedBox( + width: introWidth, + height: introHeight, + child: videoIntro( + width: introWidth, + height: introHeight, + ), ), ), - ), - ), - ], - ); + SizedBox( + width: videoWidth, + height: videoHeight, + child: videoPlayer( + width: videoWidth, + height: videoHeight, + ), + ), + Offstage( + offstage: isFullScreen, + child: SizedBox( + width: introWidth, + height: introHeight, + child: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Column( + children: [ + buildTabBar(showIntro: false), + Expanded( + child: tabBarView( + controller: videoDetailController.tabCtr, + children: [ + if (videoDetailController.showReply) + videoReplyPanel(), + if (_shouldShowSeasonPanel) seasonPanel, + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + return _childWhenDisabledLandscapeInner(isFullScreen); + }); } + return _childWhenDisabledLandscapeInner(isFullScreen); + } + + Widget _childWhenDisabledLandscapeInner(bool isFullScreen) { double width = clampDouble(maxHeight / maxWidth * 1.08, 0.5, 0.7) * maxWidth; if (maxWidth >= 560) { @@ -1053,7 +1018,7 @@ class _VideoDetailPageVState extends State ), ], ); - }); + } Widget get childWhenDisabledAlmostSquare => Obx(() { final isFullScreen = this.isFullScreen; @@ -1063,86 +1028,90 @@ class _VideoDetailPageVState extends State ? null : AppBar(backgroundColor: Colors.black, toolbarHeight: 0), body: Padding( - padding: !isFullScreen - ? padding.copyWith(top: 0, bottom: 0) - : EdgeInsets.zero, - child: childWhenDisabledAlmostSquareInner(isFullScreen, padding), + padding: isFullScreen + ? EdgeInsets.zero + : padding.copyWith(top: 0, bottom: 0), + child: childWhenDisabledAlmostSquareInner(isFullScreen), ), ); }); - Widget childWhenDisabledAlmostSquareInner( - bool isFullScreen, - EdgeInsets padding, - ) => Obx( - () { - final isFullScreen = this.isFullScreen; - if (videoDetailController.isVertical.value && - enableVerticalExpand && - !isPortrait) { - return childSplit(9 / 16); - } - final shouldShowSeasonPanel = _shouldShowSeasonPanel; - final double height = maxHeight / 2.5; - final videoHeight = isFullScreen - ? maxHeight - (isPortrait ? padding.top : 0) - : height; - final bottomHeight = maxHeight - height - padding.top; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( + Widget childWhenDisabledAlmostSquareInner(bool isFullScreen) { + if (enableVerticalExpand) { + return Obx( + () { + if (videoDetailController.isVertical.value && !isPortrait) { + return childSplit(9 / 16); + } + + return _childWhenDisabledAlmostSquareInner(isFullScreen); + }, + ); + } + + return _childWhenDisabledAlmostSquareInner(isFullScreen); + } + + Widget _childWhenDisabledAlmostSquareInner(bool isFullScreen) { + final shouldShowSeasonPanel = _shouldShowSeasonPanel; + final double height = maxHeight / 2.5; + final videoHeight = isFullScreen + ? maxHeight - (isPortrait ? padding.top : 0) + : height; + final bottomHeight = maxHeight - height - padding.top; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: maxWidth, + height: videoHeight, + child: videoPlayer( width: maxWidth, height: videoHeight, - child: videoPlayer( - width: maxWidth, - height: videoHeight, - ), ), - Offstage( - offstage: isFullScreen, - child: SizedBox( - width: maxWidth - padding.horizontal, - height: bottomHeight, - child: Scaffold( - key: videoDetailController.childKey, - resizeToAvoidBottomInset: false, - backgroundColor: Colors.transparent, - body: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildTabBar(needIndicator: false), - Expanded( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: videoIntro( - width: () { - double flex = 1; - if (videoDetailController.showReply) flex++; - if (shouldShowSeasonPanel) flex++; - return maxWidth / flex; - }(), - height: bottomHeight, - ), + ), + Offstage( + offstage: isFullScreen, + child: SizedBox( + width: maxWidth - padding.horizontal, + height: bottomHeight, + child: Scaffold( + key: videoDetailController.childKey, + resizeToAvoidBottomInset: false, + backgroundColor: Colors.transparent, + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildTabBar(needIndicator: false), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: videoIntro( + width: () { + double flex = 1; + if (videoDetailController.showReply) flex++; + if (shouldShowSeasonPanel) flex++; + return maxWidth / flex; + }(), + height: bottomHeight, ), - if (videoDetailController.showReply) - Expanded(child: videoReplyPanel()), - if (shouldShowSeasonPanel) - Expanded(child: seasonPanel), - ], - ), + ), + if (videoDetailController.showReply) + Expanded(child: videoReplyPanel()), + if (shouldShowSeasonPanel) Expanded(child: seasonPanel), + ], ), - ], - ), + ), + ], ), ), ), - ], - ); - }, - ); + ), + ], + ); + } Widget get manualPlayerWidget => Obx(() { if (!videoDetailController.autoPlay) { diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index a21bc3a02..851bc5874 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -1415,7 +1415,7 @@ class PlPlayerController with BlockConfigMixin { controls = !val; } - void toggleFullScreen(bool val) { + void _setFullScreen(bool val) { isFullScreen.value = val; updateSubtitleStyle(); } @@ -1426,6 +1426,37 @@ class PlPlayerController with BlockConfigMixin { late final horizontalScreen = Pref.horizontalScreen; late final removeSafeArea = Pref.removeSafeArea; + Future? changeOrientation({ + required bool isVertical, + DeviceOrientation? orientation, + }) { + if (orientation == null && (mode == .none || mode == .gravity)) { + return null; + } + if (orientation == null && + (mode == .vertical || + (mode == .auto && isVertical) || + (mode == .ratio && (isVertical || screenRatio < kScreenRatio)))) { + return portraitUpMode(); + } else { + // https://github.com/flutter/flutter/issues/73651 + // https://github.com/flutter/flutter/issues/183708 + if (Platform.isAndroid) { + if ((orientation ?? _orientation) == .landscapeRight) { + return landscapeRightMode(); + } else { + return landscapeLeftMode(); + } + } else { + if (orientation == .landscapeLeft) { + return landscapeLeftMode(); + } else { + return landscapeRightMode(); + } + } + } + } + // 全屏 bool _fsProcessing = false; Future triggerFullScreen({ @@ -1439,38 +1470,15 @@ class PlPlayerController with BlockConfigMixin { if (_fsProcessing) return; _fsProcessing = true; - toggleFullScreen(status); this.isManualFS = isManualFS; try { if (status) { if (PlatformUtils.isMobile) { hideStatusBar(); - if (orientation == null && mode == .none) { - return; - } - if (orientation == null && - (mode == .vertical || - (mode == .auto && isVertical) || - (mode == .ratio && - (isVertical || screenRatio < kScreenRatio)))) { - await portraitUpMode(); - } else { - // https://github.com/flutter/flutter/issues/73651 - // https://github.com/flutter/flutter/issues/183708 - if (Platform.isAndroid) { - if ((orientation ?? _orientation) == .landscapeRight) { - await landscapeRightMode(); - } else { - await landscapeLeftMode(); - } - } else { - if (orientation == .landscapeLeft) { - await landscapeLeftMode(); - } else { - await landscapeRightMode(); - } - } - } + await changeOrientation( + isVertical: isVertical, + orientation: orientation, + ); } else { await enterDesktopFullScreen(inAppFullScreen: inAppFullScreen); } @@ -1502,6 +1510,7 @@ class PlPlayerController with BlockConfigMixin { } } } finally { + _setFullScreen(status); _fsProcessing = false; } }