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.
This commit is contained in:
edde746
2026-07-27 17:44:51 +02:00
parent 41c6389cd4
commit db593e1255
2 changed files with 77 additions and 1 deletions
@@ -15,8 +15,11 @@ Future<bool> 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();
}
+73
View File
@@ -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();