Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-04-24 10:48:51 +08:00
parent 490a08fa79
commit 73484f1f72
4 changed files with 273 additions and 236 deletions

View File

@@ -168,21 +168,55 @@ class VideoDetailController extends GetxController
late final scrollKey = GlobalKey<ExtendedNestedScrollViewState>();
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<UgcIntroController>(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();

View File

@@ -219,10 +219,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
videoDetailController.videoHeight,
);
} else {
refreshPage();
videoDetailController.refreshPage();
}
} else {
refreshPage();
videoDetailController.refreshPage();
}
}
} catch (e) {
@@ -326,8 +326,6 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
?..removeStatusLister(playerListener)
..removePositionListener(positionListener);
videoDetailController.animController?.removeListener(animListener);
Get.delete<HorizontalMemberPageController>(
tag: videoDetailController.heroTag,
);
@@ -480,48 +478,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
: 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<VideoDetailPageV>
? 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<VideoDetailPageV>
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<VideoDetailPageV>
? maxHeight - (isPortrait ? padding.top : 0)
: videoDetailController.isExpanding ||
videoDetailController.isCollapsing
? animHeight
? videoDetailController.animHeight
: videoDetailController.videoHeight;
return [
SliverPinnedDynamicHeader(
@@ -833,10 +793,10 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
? 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<VideoDetailPageV>
);
}
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<VideoDetailPageV>
),
],
);
});
}
Widget get childWhenDisabledAlmostSquare => Obx(() {
final isFullScreen = this.isFullScreen;
@@ -1063,86 +1028,90 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
? 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) {