feat(player): 接入双端画中画播放
This commit is contained in:
1 parent
5346be40a9
commit
43e60e29ec
10 files changed
+1015
-29
No files matched your search
@@ -200,6 +200,8 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
);
|
||||
}
|
||||
_pageController = logic.pages;
|
||||
// 自动小窗:独立播放页进入即挂;沉浸流随所在 tab 的激活状态挂/摘。
|
||||
unawaited(logic.syncPipAutoArm(widget.embedded ? widget.active : true));
|
||||
if (!widget.embedded &&
|
||||
!logic.isEpisodeLocked(logic.session.currentEpisode)) {
|
||||
_loadingVisible = true;
|
||||
@@ -217,6 +219,10 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
void didUpdateWidget(covariant PlayerPage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
logic.attach(widget, isMounted: () => mounted);
|
||||
// 沉浸流所在 tab 切换时同步自动小窗(激活挂、切走摘)。
|
||||
if (widget.embedded && oldWidget.active != widget.active) {
|
||||
unawaited(logic.syncPipAutoArm(widget.active));
|
||||
}
|
||||
if (oldWidget.scrubCancelRequest != widget.scrubCancelRequest) {
|
||||
_cancelFeedScrub();
|
||||
}
|
||||
@@ -703,6 +709,13 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
onRefresh: _refreshHomeFeed,
|
||||
child: pages,
|
||||
);
|
||||
// 进画中画时预加载当前集海报:还原过渡封面立即可显示,避免再黑一次。
|
||||
if (logic.pip) {
|
||||
final poster = logic.trackedEpisode?.posterUrl?.trim();
|
||||
if (poster != null && poster.isNotEmpty) {
|
||||
unawaited(precacheImage(NetworkImage(poster), context));
|
||||
}
|
||||
}
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
clipBehavior: Clip.none,
|
||||
@@ -711,7 +724,13 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
pageController: _pageController,
|
||||
slots: _renderSlots(indices),
|
||||
onCurrentVideoVisible: _handleCurrentVideoVisible,
|
||||
resumeSignal: logic.pipResumeToken,
|
||||
),
|
||||
if (logic.pipRecovering)
|
||||
Positioned.fill(
|
||||
key: const Key('player-pip-recover-cover'),
|
||||
child: _PipRecoverCover(episode: logic.trackedEpisode),
|
||||
),
|
||||
if (!widget.embedded && !_loadingRemoved)
|
||||
Center(
|
||||
child: IgnorePointer(
|
||||
@@ -804,6 +823,9 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
|
||||
void _handleCurrentVideoVisible(int? index) {
|
||||
if (!mounted || _loadingRemoved) return;
|
||||
// 收起还原封面要放在 early-return 之前:恢复时集未变,
|
||||
// _videoReadyIndex 已相等,只有这里能接到淡入完成的回调。
|
||||
logic.endPipRecover();
|
||||
final currentIndex = logic.session.currentIndex;
|
||||
if (index == null ||
|
||||
index != currentIndex ||
|
||||
@@ -1447,3 +1469,43 @@ class _PlayerPageState extends State<PlayerPage>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 画中画还原过渡封面:盖住回前台瞬间的黑纹理帧,视频出帧后由
|
||||
/// [PlayerLogic.endPipRecover] 收起。背景用主题渐变,海报预加载命中前也有过渡观感。
|
||||
class _PipRecoverCover extends StatelessWidget {
|
||||
const _PipRecoverCover({required this.episode});
|
||||
|
||||
final Episode? episode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final poster = episode?.posterUrl?.trim();
|
||||
final thumbnail = episode?.thumbnailsUrl?.trim();
|
||||
final url = poster != null && poster.isNotEmpty
|
||||
? poster
|
||||
: (thumbnail != null && thumbnail.isNotEmpty ? thumbnail : null);
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
AppColors.followingGlow,
|
||||
AppColors.followingGlowMid,
|
||||
Color(0xFF101012),
|
||||
],
|
||||
stops: [0, 0.22, 0.62],
|
||||
),
|
||||
),
|
||||
child: url == null
|
||||
? const SizedBox.shrink()
|
||||
: Image.network(
|
||||
url,
|
||||
key: const Key('player-pip-recover-cover-image'),
|
||||
fit: BoxFit.cover,
|
||||
gaplessPlayback: true,
|
||||
errorBuilder: (_, _, _) => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user