fix(player): 预解码观看全集首帧

This commit is contained in:
Zhou Weidong committed 2026-10-26 17:27:43 +08:00
1 parent 1fc8c4924e
commit c8ec481fc9
5 files changed
+145 -26

No files matched your search

+40 -17
View File
@@ -93,6 +93,7 @@ class PlayerPage extends StatefulWidget {
this.handoff,
this.revealAfterFirstFrame = false,
this.initialFrameBytes,
this.initialFrameProvider,
});
final Drama drama;
@@ -122,6 +123,7 @@ class PlayerPage extends StatefulWidget {
final PlayerPageHandoff? handoff;
final bool revealAfterFirstFrame;
final Uint8List? initialFrameBytes;
final ImageProvider<Object>? initialFrameProvider;
@override
State<PlayerPage> createState() => _PlayerPageState();
@@ -147,6 +149,7 @@ class _PlayerPageState extends State<PlayerPage>
late bool _firstFrameReady;
bool _leavingHandoff = false;
bool _preparingFullPlayer = false;
bool _loggedInitialFrameBuild = false;
final Set<String> _precachedPosterUrls = {};
/// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。
@@ -157,14 +160,6 @@ class _PlayerPageState extends State<PlayerPage>
void initState() {
super.initState();
_firstFrameReady = !widget.revealAfterFirstFrame;
if (kDebugMode &&
widget.revealAfterFirstFrame &&
widget.initialFrameBytes != null) {
debugPrint(
'[player-entry] phase=initial-frame-shown '
'bytes=${widget.initialFrameBytes!.length}',
);
}
final handoff = widget.handoff;
logic = PlayerBinding().create(
history: context.read<WatchHistoryRepository>(),
@@ -690,6 +685,18 @@ class _PlayerPageState extends State<PlayerPage>
logic.ticketError != null ||
logic.blockedEpisodeId != null ||
logic.session.error != null;
final hasInitialFrame =
widget.initialFrameBytes != null || widget.initialFrameProvider != null;
if (kDebugMode &&
widget.revealAfterFirstFrame &&
hasInitialFrame &&
!_loggedInitialFrameBuild) {
_loggedInitialFrameBuild = true;
debugPrint(
'[player-entry] phase=first-build-with-initial-frame '
'kind=${widget.initialFrameBytes != null ? 'capture' : 'poster'}',
);
}
final presentedPlayer = widget.revealAfterFirstFrame
? Stack(
fit: StackFit.expand,
@@ -698,7 +705,15 @@ class _PlayerPageState extends State<PlayerPage>
child: revealContent
? const SizedBox.expand()
: widget.initialFrameBytes == null
? _buildInitialFrameFallback()
? widget.initialFrameProvider == null
? _buildInitialFrameFallback()
: Image(
image: widget.initialFrameProvider!,
fit: BoxFit.cover,
gaplessPlayback: true,
errorBuilder: (_, _, _) =>
_buildInitialFrameFallback(),
)
: Image.memory(
widget.initialFrameBytes!,
key: const Key('player-entry-frame'),
@@ -726,7 +741,7 @@ class _PlayerPageState extends State<PlayerPage>
setState(() => _firstFrameReady = true);
if (kDebugMode) {
debugPrint(
'[player-entry] phase=ready '
'[player-entry] phase=video-first-frame '
'session=${identityHashCode(logic.session)}',
);
}
@@ -1245,10 +1260,15 @@ class _PlayerPageState extends State<PlayerPage>
seriesEpisodes.insert(0, sourceEpisode);
episodeIndex = 0;
}
final initialFrameBytes = await PlayerFrameCapture.capture(
final fallbackPosterUrl =
episodeIndex >= 0 && episodeIndex < seriesEpisodes.length
? seriesEpisodes[episodeIndex].posterUrl
: sourceEpisode.posterUrl;
final preparedFrame = await PlayerFrameCapture.captureAndPrecache(
context: context,
boundaryKey: logic.renderBoundaryKey,
source: 'full-player',
fallbackPosterUrl: fallbackPosterUrl,
);
if (!mounted || !context.mounted) return;
final sourceUserWantsPlay = logic.session.userWantsPlay;
@@ -1265,21 +1285,24 @@ class _PlayerPageState extends State<PlayerPage>
PageRouteBuilder<void>(
opaque: true,
barrierDismissible: false,
transitionDuration: const Duration(milliseconds: 180),
reverseTransitionDuration: const Duration(milliseconds: 180),
transitionsBuilder: (_, animation, _, child) =>
FadeTransition(opacity: animation, child: child),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
pageBuilder: (_, _, _) => PlayerPage(
drama: seriesDrama,
initialIndex: episodeIndex,
handoff: handoff,
wallet: logic.wallet,
downloads: logic.downloads,
revealAfterFirstFrame: initialFrameBytes != null,
initialFrameBytes: initialFrameBytes,
revealAfterFirstFrame: preparedFrame.isReady,
initialFrameBytes: preparedFrame.bytes,
initialFrameProvider: preparedFrame.provider,
),
),
);
if (kDebugMode) {
debugPrint('[player-entry] phase=route-pushed source=full-player ms=0');
}
unawaited(logic.saveCurrentProgress());
await route;
if (!mounted) return;
logic.attach(widget, isMounted: () => mounted);