Player: retry audio-track restore until player acknowledges selection

Fix #2564 — audio language not applied on stream re-open (LG webOS).

On stream re-open the auto-selection effect calls video.setAudioTrack(id)
exactly once when the audioTracks list first becomes available, then sets
defaultAudioTrackSelected.current = true and never retries. On platforms
where the underlying player hasn't fully initialized at that moment (e.g.
LG webOS Smart TV), the call is silently ignored — the UI state
(selectedAudioTrackId in core) reflects the desired track, but actual
playback uses the player's default track. The user observes that the
correct language is highlighted in the menu, yet re-clicking it is what
actually switches the audio.

Patch:
- Don't mark the selection as 'done' on the first setAudioTrack call.
  Wait until video.state.selectedAudioTrackId === desired id. Once it
  matches, we know the player has actually applied the selection.
- Include video.state.selectedAudioTrackId in the effect deps so the
  effect re-runs when the player updates its state, giving us a chance
  to retry if the first call didn't take.

setAudioTrack is idempotent on the core side, so re-issuing the same id
on subsequent renders is safe.
This commit is contained in:
Omri Hefez 2026-05-18 23:28:39 +03:00
parent 3b79775245
commit d25484be46

View file

@ -463,18 +463,39 @@ const Player = ({ urlParams, queryParams }) => {
}, [player.nextVideo, video.state.time, video.state.duration]);
// Auto audio track selection
// Bug: on stream re-open, calling video.setAudioTrack() once during audioTracks
// initialization is not always honored by the underlying player — the UI state
// reflects the desired track (selectedAudioTrackId in core) but the actual
// playback uses the player's default track (the first / index 0). This is
// observable on LG webOS Smart TV (see issue #2564). Symptom: user re-selects
// the already-highlighted option and only then does the audio actually switch.
//
// Fix: don't mark the selection as "done" until the player's
// selectedAudioTrackId actually matches the desired id. Until they match,
// keep retrying on each render of audioTracks / selectedAudioTrackId / streamState.
React.useEffect(() => {
if (!defaultAudioTrackSelected.current) {
const savedTrackId = player.streamState?.audioTrack?.id;
const savedTrack = savedTrackId ? findTrackById(video.state.audioTracks, savedTrackId) : null;
const audioTrack = savedTrack ?? findTrackByLang(video.state.audioTracks, settings.audioLanguage);
if (audioTrack && audioTrack.id) {
video.setAudioTrack(audioTrack.id);
defaultAudioTrackSelected.current = true;
}
if (defaultAudioTrackSelected.current) {
return;
}
}, [video.state.audioTracks, player.streamState]);
const savedTrackId = player.streamState?.audioTrack?.id;
const savedTrack = savedTrackId ? findTrackById(video.state.audioTracks, savedTrackId) : null;
const audioTrack = savedTrack ?? findTrackByLang(video.state.audioTracks, settings.audioLanguage);
if (!audioTrack || !audioTrack.id) {
return;
}
if (video.state.selectedAudioTrackId === audioTrack.id) {
// Player has acknowledged the selection — we're done.
defaultAudioTrackSelected.current = true;
return;
}
// Either we haven't requested the change yet, or the player hasn't picked
// it up. Request again. This is safe to call repeatedly with the same id
// because setAudioTrack is idempotent on the core side.
video.setAudioTrack(audioTrack.id);
}, [video.state.audioTracks, video.state.selectedAudioTrackId, player.streamState]);
React.useEffect(() => {
defaultAudioTrackSelected.current = false;