pub async fn player_switch_audio_track(
player: State<'_, PlayerStateWrapper>,
repository_manager: State<'_, RepositoryManagerWrapper>,
repository_handle: String,
stream_index: i32,
array_index: i32,
use_html5: bool,
current_position: Option<f64>,
media_source_id: Option<String>,
) -> Result<AudioTrackSwitchResponse, String>Expand description
Switch audio track. Note: Frontend should handle saving series preferences after this command succeeds
What decides the route is whether the stream in front of the engine
carries the requested track at all — see
determine_audio_track_switch_strategy:
- An HTML5
<video>element has no track-selection API, so the stream is always re-opened at the chosenAudioStreamIndexand the frontend seeks the reloaded element back toposition. - A native backend playing a direct play holds the source file with every track in it, so ExoPlayer selects in place by track-group index.
- A native backend playing a transcode does not. Jellyfin builds a
transcode around one
AudioStreamIndex, so the alternate tracks are not in the stream; the switch has to re-open it, which this command does itself and resumes atcurrent_position.
That last case is a bug fix, and it was the common case on Android: any
source whose default audio codec the device cannot decode is transcoded, so
ExoPlayer saw Audio tracks: 1 while the menu listed every track in the
file. The old code called setAudioTrack(n) regardless, which indexes
ExoPlayer’s audio track groups, found nothing at n, warned Invalid audio track index and dropped the request — the default track just kept
playing, with nothing in the UI saying so.
libmpv implements neither selection nor reload here — it is the audio-only
backend and leaves PlayerBackend::set_audio_track at its
not_implemented() default, which is why IR-019 is met by these paths
rather than by MPV.
TRACES: UR-021, UR-005 | IR-019, DR-024, DR-258