diff --git a/lib/screens/video_player/parts/episode_navigation.dart b/lib/screens/video_player/parts/episode_navigation.dart index 9e51b99d..1c95aa9f 100644 --- a/lib/screens/video_player/parts/episode_navigation.dart +++ b/lib/screens/video_player/parts/episode_navigation.dart @@ -150,10 +150,11 @@ extension _VideoPlayerEpisodeNavigationMethods on VideoPlayerScreenState { // Read the client before any await — context across an async gap. A // missing client leaves this null and the guard below reports it. final serverId = _currentMetadata.serverId; - PlexClient? subtitleClient; - if (isSubtitleChange && serverId != null) { + final isPlexBacked = _currentMetadata.backend == MediaBackend.plex; + PlexClient? streamSelectClient; + if ((isSubtitleChange || (isAudioChange && isPlexBacked)) && serverId != null) { try { - subtitleClient = context.getPlexClientForServer(ServerId(serverId)); + streamSelectClient = context.getPlexClientForServer(ServerId(serverId)); } catch (_) {} } @@ -162,18 +163,19 @@ extension _VideoPlayerEpisodeNavigationMethods on VideoPlayerScreenState { await saveMediaVersionIndexFor(_currentMetadata, effectiveMediaIndex); } - if (isSubtitleChange) { + if (isSubtitleChange || (isAudioChange && isPlexBacked)) { final partId = _currentMediaInfo?.partId; - if (subtitleClient == null || partId == null || effectiveSubtitleStreamId == null) { - throw StateError('No Plex part available for subtitle stream selection'); + if (streamSelectClient == null || partId == null) { + throw StateError('No Plex part available for stream selection'); } - final saved = await subtitleClient.selectStreams( + final saved = await streamSelectClient.selectStreams( partId, - subtitleStreamID: effectiveSubtitleStreamId, + audioStreamID: isAudioChange ? effectiveAudioStreamId : null, + subtitleStreamID: isSubtitleChange ? effectiveSubtitleStreamId : null, allParts: true, ); if (!saved) { - throw StateError('Failed to select subtitle stream'); + throw StateError('Failed to select streams'); } } diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 794396a9..37b40ef5 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -1237,6 +1237,7 @@ class PlexClient if (queryParams.isEmpty) { return true; } + queryParams['allParts'] = 1; // Use PUT request on /library/parts/{partId} return _wrapBoolApiCall( diff --git a/test/services/plex_playback_data_request_test.dart b/test/services/plex_playback_data_request_test.dart index 49f33b0c..c9c7e4af 100644 --- a/test/services/plex_playback_data_request_test.dart +++ b/test/services/plex_playback_data_request_test.dart @@ -56,6 +56,24 @@ void main() { return client.buildTranscodeSidecarSubtitlesForTesting(mediaInfoWithSubtitles(subtitleTracks)); } + test('selectStreams sends audio stream selection with allParts', () async { + final requests = []; + final client = makeClient((request) async { + requests.add(request); + return http.Response('', 200); + }); + addTearDown(client.close); + + final saved = await client.selectStreams(99, audioStreamID: 301, allParts: true); + + expect(saved, isTrue); + expect(requests, hasLength(1)); + expect(requests.single.method, 'PUT'); + expect(requests.single.url.path, '/library/parts/99'); + expect(requests.single.url.queryParameters['audioStreamID'], '301'); + expect(requests.single.url.queryParameters['allParts'], '1'); + }); + test('playback metadata request includes streams for transcode sidecar subtitles', () async { final requests = []; final client = makeClient((request) async {