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();