LG Webos - wrong audio track

Follow-up: a second, distinct webOS audio-selection bug — merged multi-version items

This is a different bug with a different trigger that lives in the same function area (HTMLPlayer._selectAudioTrack). I instrumented it end-to-end the same way — Chrome DevTools on the live app.plex.tv/tv-v5-webos build plus the Plex DB — and traced it to the exact call. The prior bug was about language tags; this one is about merged Versions, and it can short-circuit the language logic entirely.

Setup: LG 49SM9000PLA, webOS 4.10.0; PMS 1.43.2.10687; LG Plex app cdp-30 5.2.1. Repro content: Spider-Noir S01 as a merged multi-version item — each episode has two release files combined into one entry: a FLUX version (1 audio track, English) and a B&W version (2 audio tracks, Italian + English).

In plain English (for anyone hitting this)

If you have two copies of the same movie or episode in Plex and Plex has merged them into a single entry with multiple “Versions,” the audio picker can stop working on one of those copies. You open the audio menu, choose Italian, the menu shows Italian selected — but the sound stays English. It never actually changes.

Why it happens: when you play a merged item, the app needs a list of “which audio tracks this file has.” Because of a bug, it reads that list from the wrong copy — the first/default Version — instead of the copy you’re actually watching. The two copies have a different number of audio tracks (here 1 vs 2), the lists don’t line up, and the app gives up and just plays the default (English) track no matter what you pick.

It’s not your TV’s fault, and it’s not a sound-file problem. Single-copy items switch audio fine in both directions. It only happens on merged items.

Workaround

  1. Remove the extra version from the library so the item has only one Version.
  2. Disable “Allow Direct Play” (or lower max quality) so the server transcodes to a single audio track — the app can’t pick the wrong index. Same universal workaround as before.
For Plex devs — full technical detail

Trigger: a merged multi-version metadata_item whose played version has a different audio-stream count than version index 0. Test item: FLUX = 1 audio (en/eac3); B&W = 2 audio (it+en, native element order [en, it]).

Symptom: Direct-Playing the B&W (2-track) version, the picker is ignored — audio stays pinned to English:

Unexpected number of audio tracks available on media element. Found: 2, expected: 1

Proof the native API is fine: in DevTools, audioTracks[1].enabled = true on the <video> element switched the decoder to Italian instantly — no reload, no transcode. The native list [en, it] is correct; only the index the app computes is wrong.

Root cause. _selectAudioTrack (HTMLPlayer.tsx, module 29074) builds its server-stream list from the default version, not the playing one:

_selectAudioTrack() {
  const tracks = this.mediaElement?.audioTracks;          // native AudioTrackList — correct: [en, it]
  const playable = this._playable;
  if (!tracks?.length || !playable?.isDirectPlay
      || playable.playableType !== PlayableType.Video) return;

  // BUG: no media-version index passed → reads version 0's streams (the FLUX copy)
  const serverStreams = getStreamsOfType(playable.metadataItem, StreamType.Audio);   // = (0,f.bp)(...)
  if (!serverStreams.length) return;

  const elementTracks = [...tracks];
  if (elementTracks.length === 1) { elementTracks[0].enabled = true; return; }

  const idx = this._getSelectedAudioStreamIndex
    ? this._getSelectedAudioStreamIndex(serverStreams, elementTracks)
    : serverStreams.findIndex(s => s.selected);
  elementTracks.forEach((t, i) => { t.enabled = (i === idx); });
}

grep: var i=(0,f.bp)(n.metadataItem,m.jx.Audio);

f.bp (f = n(90606)) is the shared helper D:

// f.bp — "get streams of a type from an item"
function getStreamsOfType(item, streamType, mediaIndex = 0) {   // ← 3rd arg, defaults to 0
  if (!item.mediaItems) return [];
  const version = item.mediaItems[mediaIndex];                  // version 0 unless told otherwise
  if (!version?.parts) return [];
  return (version.parts[0].streams || []).filter(s => s.streamType === streamType);
}

verbatim: function D(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;if(!e.mediaItems)return[];var i=e.mediaItems[n];return null!=i&&i.parts?(i.parts[0].streams||[]).filter(function(e){return e.streamType===t}):[]}

With no index it returns mediaItems[0] = FLUX (1 audio) while the element plays mediaItems[1] = B&W (2 audio) — hence expected: 1 against a 2-track file. (metadataItem is the full multi-version array, the same one appInitializer indexes via mediaItems[t.mediaIndex].)

Downstream (stock code). Lengths differ → getSelectedAudioStreamIndex (getAppConfig.ts, the #939218 function) short-circuits:

function getSelectedAudioStreamIndex(serverStreams, elementTracks) {
  const r = serverStreams.findIndex(s => s.selected);

  if (serverStreams.length !== elementTracks.length) {
    log.warn(`Unexpected number of audio tracks... Found: ${elementTracks.length}, expected: ${serverStreams.length}`);
    return r;                       // ← BUG PATH (1 !== 2): skips the language match + mirror below
  }

  const aligned = serverStreams.every((s, i) => {              // normal path — only when lists line up
    const elemLang = elementTracks[i].language;
    if (!(s.languageCode || (elemLang && elemLang !== "und"))) return true;
    return s.languageCode === elemLang
        || (!!s.languageCode && (s.languageTag || isoMap[s.languageCode]) === elemLang);
  });
  return aligned ? r : elementTracks.length - r - 1;   // ← the #939218 mirror lives here
}

So audioTracks[r].enabled → ~track 0 = English, regardless of the pick. The language/mirror code never runs — the defect is the wrong input handed upstream.

Proposed fix — source from the playing version (playable.decision.choice.{mediaIndex,part}, already read in PlaybackSessionController/appInitializer):

const part = playable.decision?.choice?.part;
const serverStreams = part?.streams
  ? part.streams.filter(s => s.id != null && s.streamType === StreamType.Audio)  // same predicate as PSC._getStreamsOfType
  : getStreamsOfType(playable.metadataItem, StreamType.Audio);                    // fallback

Minified drop-in:

var p=n.decision&&n.decision.choice&&n.decision.choice.part;
var i=p&&p.streams?p.streams.filter(function(e){return null!=e.id&&e.streamType===m.jx.Audio}):(0,f.bp)(n.metadataItem,m.jx.Audio);

(Or minimal — pass the index: (0,f.bp)(n.metadataItem,m.jx.Audio,(n.decision&&n.decision.choice?n.decision.choice.mediaIndex:0)||0).) After it, lists match and the existing language path runs — also re-enabling the en-US/#939218 handling the mismatch was short-circuiting.

TL;DR: _selectAudioTrack reads the audio list from version 0 instead of the playing version; source it from playable.decision.choice.part.streams.