fix(player): 隔离播放资源所有权并修复黑屏

This commit is contained in:
Zhou Weidong committed 2026-10-26 17:03:19 +08:00
1 parent 54eb5a60a8
commit bb2c729a7e
9 files changed
+223 -25

No files matched your search

+4 -1
View File
@@ -290,6 +290,10 @@ class PlayerLogic extends ChangeNotifier {
activate: activatePlayer,
coordinator: coordinator,
waitForFrame: _waitForFrame,
owner: homeMode ? PlaybackSessionOwner.home : PlaybackSessionOwner.player,
dramaIdAt: (index) => index >= 0 && index < queue.length
? queue.entryAt(index).drama.id
: null,
);
}
@@ -1231,7 +1235,6 @@ class PlayerLogic extends ChangeNotifier {
initialIndex: index,
wallet: wallet,
downloads: downloads,
isolatePlaybackSession: true,
),
),
);
+42 -7
View File
@@ -90,7 +90,7 @@ class PlayerPage extends StatefulWidget {
this.wallet,
this.downloads,
this.handoff,
this.isolatePlaybackSession = false,
this.revealAfterFirstFrame = false,
});
final Drama drama;
@@ -118,7 +118,7 @@ class PlayerPage extends StatefulWidget {
final WalletController? wallet;
final DownloadController? downloads;
final PlayerPageHandoff? handoff;
final bool isolatePlaybackSession;
final bool revealAfterFirstFrame;
@override
State<PlayerPage> createState() => _PlayerPageState();
@@ -141,6 +141,7 @@ class _PlayerPageState extends State<PlayerPage>
DateTime? _swipeStartedAt;
bool _swipePrewarmCaptured = false;
bool _presentationActive = false;
late bool _firstFrameReady;
bool _leavingHandoff = false;
bool _preparingFullPlayer = false;
final Set<String> _precachedPosterUrls = {};
@@ -152,6 +153,7 @@ class _PlayerPageState extends State<PlayerPage>
@override
void initState() {
super.initState();
_firstFrameReady = !widget.revealAfterFirstFrame;
final handoff = widget.handoff;
logic = PlayerBinding().create(
history: context.read<WatchHistoryRepository>(),
@@ -169,12 +171,18 @@ class _PlayerPageState extends State<PlayerPage>
WidgetsBinding.instance.addObserver(this);
logic.start(
factory: context.read<PlayerFactory>(),
coordinator: handoff == null && !widget.isolatePlaybackSession
coordinator: widget.embedded
? context.read<PlaybackCoordinator>()
: PlaybackCoordinator(),
initialUserWantsPlay: handoff?.sourceUserWantsPlay ?? true,
);
_pageController = logic.pages;
if (kDebugMode && widget.revealAfterFirstFrame) {
debugPrint(
'[player-entry] phase=waiting-first-frame '
'session=${identityHashCode(logic.session)}',
);
}
}
@override
@@ -594,6 +602,7 @@ class _PlayerPageState extends State<PlayerPage>
PlayerRenderStack(
pageController: _pageController,
slots: _renderSlots(indices),
onCurrentVideoVisible: _handleCurrentVideoVisible,
),
pageLayer,
],
@@ -660,16 +669,37 @@ class _PlayerPageState extends State<PlayerPage>
),
),
);
if (widget.handoff == null) return player;
final revealContent =
!widget.revealAfterFirstFrame ||
_firstFrameReady ||
logic.session.isLocked ||
logic.ticketError != null ||
logic.blockedEpisodeId != null ||
logic.session.error != null;
final presentedPlayer = widget.revealAfterFirstFrame
? Opacity(opacity: revealContent ? 1 : 0, child: player)
: player;
if (widget.handoff == null) return presentedPlayer;
return PopScope<void>(
canPop: _leavingHandoff,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) unawaited(_leaveHandoff(context));
},
child: player,
child: presentedPlayer,
);
}
void _handleCurrentVideoVisible() {
if (!mounted || _firstFrameReady) return;
setState(() => _firstFrameReady = true);
if (kDebugMode) {
debugPrint(
'[player-entry] phase=ready '
'session=${identityHashCode(logic.session)}',
);
}
}
void _openCurrentMore() {
final entry = logic.queue.entryAt(logic.session.currentIndex);
_openMore(context, entry.episode);
@@ -1176,13 +1206,18 @@ class _PlayerPageState extends State<PlayerPage>
_logFullPlayerRoute('present');
setState(() => _presentationActive = true);
final route = Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (_) => PlayerPage(
PageRouteBuilder<void>(
opaque: false,
barrierDismissible: false,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
pageBuilder: (_, _, _) => PlayerPage(
drama: seriesDrama,
initialIndex: episodeIndex,
handoff: handoff,
wallet: logic.wallet,
downloads: logic.downloads,
revealAfterFirstFrame: true,
),
),
);
@@ -52,10 +52,12 @@ class PlayerRenderStack extends StatefulWidget {
super.key,
required this.pageController,
required this.slots,
this.onCurrentVideoVisible,
});
final PageController pageController;
final Map<PlayerRenderSlotRole, PlayerRenderSlotData> slots;
final VoidCallback? onCurrentVideoVisible;
@override
State<PlayerRenderStack> createState() => _PlayerRenderStackState();
@@ -68,7 +70,10 @@ class _PlayerRenderStackState extends State<PlayerRenderStack> {
void initState() {
super.initState();
_lastCurrentIndex = _currentSlot.index;
_log('init current=${_lastCurrentIndex ?? '-'}');
_log(
'init current=${_lastCurrentIndex ?? '-'} '
'pageController=${identityHashCode(widget.pageController)}',
);
}
@override
@@ -86,7 +91,10 @@ class _PlayerRenderStackState extends State<PlayerRenderStack> {
@override
void dispose() {
_log('dispose current=${_lastCurrentIndex ?? '-'}');
_log(
'dispose current=${_lastCurrentIndex ?? '-'} '
'pageController=${identityHashCode(widget.pageController)}',
);
super.dispose();
}
@@ -189,6 +197,9 @@ class _PlayerRenderStackState extends State<PlayerRenderStack> {
fallback: slot.fallback,
warmSlotReused: slot.warmSlotReused,
waitForPlaybackFrame: slot.waitForPlaybackFrame,
onVideoVisible: slot.role == PlayerRenderSlotRole.current
? widget.onCurrentVideoVisible
: null,
),
),
);
@@ -28,6 +28,7 @@ class PlayerVideoSurface extends StatefulWidget {
this.debugSlot = 'default',
this.debugIndex,
this.showFallbackBackground = true,
this.onVideoVisible,
});
final VideoPlayerController? controller;
@@ -57,6 +58,7 @@ class PlayerVideoSurface extends StatefulWidget {
final bool showFallbackBackground;
final String videoUrl;
final bool theater;
final VoidCallback? onVideoVisible;
@override
State<PlayerVideoSurface> createState() => _PlayerVideoSurfaceState();
@@ -73,6 +75,7 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface>
bool _confirmationScheduled = false;
bool _transitioning = false;
bool _videoVisible = false;
bool _videoVisibleNotified = false;
String? _lastLayerState;
@override
@@ -279,6 +282,7 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface>
});
}
_logLayer('video', widget.controller?.value);
_notifyVideoVisible();
return;
}
if (initial) {
@@ -303,6 +307,7 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface>
_placeholderSnapshot = null;
});
_logLayer('fade-end', widget.controller?.value);
_notifyVideoVisible();
}
void _resetForControllerChange() {
@@ -314,9 +319,20 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface>
_confirmationScheduled = false;
_transitioning = false;
_videoVisible = false;
_videoVisibleNotified = false;
_lastLayerState = null;
}
void _notifyVideoVisible() {
if (_videoVisibleNotified) return;
_videoVisibleNotified = true;
final callback = widget.onVideoVisible;
if (callback == null) return;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) callback();
});
}
void _logLayer(String phase, VideoPlayerValue? value) {
if (!kDebugMode) return;
final fallback = phase == 'video' || phase == 'fade-end'