fix(player): resume mpv startup frame after subtitles
This commit is contained in:
@@ -439,14 +439,20 @@ extension _VideoPlayerEpisodeNavigationMethods on VideoPlayerScreenState {
|
||||
_trackManager = trackManager;
|
||||
trackManager.cacheExternalSubtitles(result.externalSubtitles);
|
||||
|
||||
final resumeForStartupFrame =
|
||||
frameRatePlan.needsStartupRefresh && externalSubtitlePlan.requiresPostOpenAdd && !wtOwnsStart;
|
||||
await _applyTracksAfterOpen(
|
||||
trackManager: trackManager,
|
||||
externalSubtitlePlan: externalSubtitlePlan,
|
||||
// Same guard as the start path: don't resume a player a newer flow
|
||||
// owns, and let a pending startup gate (or Watch Together's group
|
||||
// start) own the resume instead.
|
||||
// start) own the resume instead. Android mpv external-subtitle opens
|
||||
// resume once here so the startup refresh gate can observe a frame.
|
||||
shouldResumeAfterSubtitleLoad: () =>
|
||||
!frameRatePlan.holdPlaybackStart && !wtOwnsStart && mounted && player == currentPlayer,
|
||||
(!frameRatePlan.holdPlaybackStart || resumeForStartupFrame) &&
|
||||
!wtOwnsStart &&
|
||||
mounted &&
|
||||
player == currentPlayer,
|
||||
applySelectionWhenResumeSkipped: wtOwnsStart && !frameRatePlan.holdPlaybackStart,
|
||||
);
|
||||
if (!isCurrentReload()) return true;
|
||||
@@ -461,6 +467,7 @@ extension _VideoPlayerEpisodeNavigationMethods on VideoPlayerScreenState {
|
||||
reason: reason,
|
||||
wtOwnsStart: wtOwnsStart,
|
||||
),
|
||||
playbackResumedForStartupFrame: resumeForStartupFrame,
|
||||
);
|
||||
if (!isCurrentReload()) return true;
|
||||
|
||||
|
||||
@@ -215,7 +215,17 @@ extension _VideoPlayerOpenMethods on VideoPlayerScreenState {
|
||||
required SettingsService settingsService,
|
||||
required _FrameRateStartupPlan plan,
|
||||
required Future<void> Function(String reason) resumeAfterStartupGate,
|
||||
bool playbackResumedForStartupFrame = false,
|
||||
}) async {
|
||||
Future<void> resumeAfterRefresh(String reason) async {
|
||||
if (playbackResumedForStartupFrame) {
|
||||
appLogger.d('Frame rate matching: continuing already-resumed playback after $reason');
|
||||
await _playWithPlaybackIntent(currentPlayer);
|
||||
} else {
|
||||
await resumeAfterStartupGate(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback refresh-rate path. The player was opened paused;
|
||||
// setVideoFrameRate awaits the real display-change event (+ settle +
|
||||
// user delay) before returning, then we start playback.
|
||||
@@ -241,7 +251,7 @@ extension _VideoPlayerOpenMethods on VideoPlayerScreenState {
|
||||
// Always resume — either the switch completed and we want to play,
|
||||
// or no switch was needed and we need to start playback now that the
|
||||
// preparation gate has been cleared.
|
||||
await resumeAfterStartupGate('post-open frame rate switch');
|
||||
await resumeAfterRefresh('post-open frame rate switch');
|
||||
|
||||
unawaited(
|
||||
Sentry.addBreadcrumb(
|
||||
@@ -256,10 +266,10 @@ extension _VideoPlayerOpenMethods on VideoPlayerScreenState {
|
||||
if (startupReady) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
await _refreshAndroidMpvDecoderAfterFrameRateSwitch(reason: 'pre-load frame rate startup');
|
||||
await resumeAfterStartupGate('startup decoder refresh');
|
||||
await resumeAfterRefresh('startup decoder refresh');
|
||||
} else {
|
||||
appLogger.w('Frame rate matching: skipping Android MPV decoder refresh because startup frame timed out');
|
||||
await resumeAfterStartupGate('startup frame timeout');
|
||||
await resumeAfterRefresh('startup frame timeout');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -320,14 +320,19 @@ extension _VideoPlayerPlaybackStartMethods on VideoPlayerScreenState {
|
||||
// Store external subtitles for re-use after backend fallback
|
||||
_trackManager!.cacheExternalSubtitles(result.externalSubtitles);
|
||||
|
||||
final resumeForStartupFrame =
|
||||
frameRatePlan.needsStartupRefresh && externalSubtitlePlan.requiresPostOpenAdd && !wtOwnsStart;
|
||||
await _applyTracksAfterOpen(
|
||||
trackManager: _trackManager!,
|
||||
externalSubtitlePlan: externalSubtitlePlan,
|
||||
// When a startup gate below owns the resume, skip this one to
|
||||
// avoid a double-play. Watch Together stays paused for the group
|
||||
// start, so selection is armed through the resume-skipped branch.
|
||||
// avoid a double-play. Android mpv external-subtitle opens are the
|
||||
// exception: after sub-add we must resume once so mpv can produce the
|
||||
// startup frame that the decoder-refresh gate is waiting for.
|
||||
// Watch Together stays paused for the group start, so selection is
|
||||
// armed through the resume-skipped branch.
|
||||
shouldResumeAfterSubtitleLoad: () =>
|
||||
!shouldHoldPlaybackStart && !wtOwnsStart && mounted && player == currentPlayer,
|
||||
(!shouldHoldPlaybackStart || resumeForStartupFrame) && !wtOwnsStart && mounted && player == currentPlayer,
|
||||
applySelectionWhenResumeSkipped: wtOwnsStart && !shouldHoldPlaybackStart,
|
||||
);
|
||||
|
||||
@@ -342,6 +347,7 @@ extension _VideoPlayerPlaybackStartMethods on VideoPlayerScreenState {
|
||||
wtOwnsStart: wtOwnsStart,
|
||||
wtStartupHold: wtStartupHold,
|
||||
),
|
||||
playbackResumedForStartupFrame: resumeForStartupFrame,
|
||||
);
|
||||
// Backstop: if the gate never ran its resume path (unmounted race),
|
||||
// don't leave Watch Together readiness held forever.
|
||||
|
||||
Reference in New Issue
Block a user