From db593e1255c8375dbd5bd7312747124c16884113 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:44:51 +0200 Subject: [PATCH] fix(player): keep an explicit transcode subtitle choice through the deferred pass Persist the choice before arming the deferred selection pass. The screen callback routes to onSubtitleTrackSelectedByUser, which invalidates the pending selection, so arming first retired the very listener that applies the choice once mpv discovers the sidecar. The existing test stubbed the persist callback and so could not observe the invalidation; it now routes through the manager like production does. --- .../parts/episode_navigation.dart | 5 +- test/services/track_manager_test.dart | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/lib/screens/video_player/parts/episode_navigation.dart b/lib/screens/video_player/parts/episode_navigation.dart index abc3be90..2435b244 100644 --- a/lib/screens/video_player/parts/episode_navigation.dart +++ b/lib/screens/video_player/parts/episode_navigation.dart @@ -15,8 +15,11 @@ Future deferTranscodeSubtitleSelection({ }) async { final deferredTrack = PlaybackSubtitleResolver.subtitleTrackForSource(sourceTrack, sidecar: sourceSidecar); trackManager.preferredSubtitleTrack = deferredTrack; - trackManager.applyTrackSelectionWhenReady(); + // Persist first: the screen callback routes to onSubtitleTrackSelectedByUser, + // which invalidates the pending selection. Arming before that would retire the + // deferred pass we depend on to apply this choice once mpv discovers the sidecar. await onSubtitleTrackChanged(deferredTrack, sourceStreamId: sourceStreamId); + trackManager.applyTrackSelectionWhenReady(); return shouldContinue(); } diff --git a/test/services/track_manager_test.dart b/test/services/track_manager_test.dart index ab6a5cbd..15ee16a2 100644 --- a/test/services/track_manager_test.dart +++ b/test/services/track_manager_test.dart @@ -654,6 +654,7 @@ void main() { onSubtitleTrackChanged: (track, {sourceStreamId}) async { persistedTrack = track; persistedSourceStreamId = sourceStreamId; + await mgr.onSubtitleTrackSelectedByUser(track, sourceStreamId: sourceStreamId); }, shouldContinue: () => true, ); @@ -685,6 +686,78 @@ void main() { expect(player.selectedSubtitle.map((track) => track.id), ['native-0']); }); + test('persisting a deferred transcode choice keeps the late-track pass armed', () async { + await SettingsService.getInstance(); + final sourceTrack = MediaSubtitleTrack( + id: 32, + index: 0, + languageCode: 'spa', + title: 'Spanish', + codec: 'srt', + selected: false, + forced: false, + ); + final player = _FakePlayer( + tracks: const Tracks( + audio: [AudioTrack(id: 'audio', language: 'eng')], + ), + ); + final mgr = _make( + player: player, + mediaInfo: MediaSourceInfo( + videoUrl: 'https://example.com/transcode.m3u8', + partId: 100, + audioTracks: [MediaAudioTrack(id: 1, languageCode: 'eng', selected: true)], + subtitleTracks: [sourceTrack], + chapters: const [], + ), + ); + addTearDown(mgr.dispose); + + await deferTranscodeSubtitleSelection( + trackManager: mgr, + sourceTrack: sourceTrack, + sourceSidecar: const PlaybackSubtitleSidecar( + sourceStreamId: 32, + preload: true, + track: SubtitleTrack( + id: 'container:32', + language: 'spa', + title: 'Spanish', + codec: 'srt', + isExternal: true, + isContainer: true, + uri: 'https://example.com/video.mkv', + ), + ), + sourceStreamId: 32, + onSubtitleTrackChanged: (track, {sourceStreamId}) => + mgr.onSubtitleTrackSelectedByUser(track, sourceStreamId: sourceStreamId), + shouldContinue: () => true, + ); + expect(player.selectedSubtitle, isEmpty); + + player.emitTracks( + const Tracks( + audio: [AudioTrack(id: 'audio', language: 'eng')], + subtitle: [ + SubtitleTrack( + id: 'native-late', + language: 'spa', + title: 'Spanish', + codec: 'srt', + isExternal: true, + isContainer: true, + uri: 'https://example.com/video.mkv', + ), + ], + ), + ); + await _drainAsync(); + + expect(player.selectedSubtitle.map((track) => track.id), ['native-late']); + }); + test('five-second fallback keeps listening and applies a late advertised subtitle', () async { await SettingsService.getInstance();