I instrumented this end‑to‑end and found the actual root cause — in the webOS app’s own track‑selection code, down to the exact function. Sharing the full chain because it explains every symptom in this thread, including why it looks random across files.
Setup: rooted LG (2019, webOS 4.5). I attached Chrome DevTools to the live Plex app (it’s the app.plex.tv/tv-v5-webos web build running in webOS Chromium) and simultaneously tailed LG’s media‑pipeline log (starfish-media-pipeline), so I can see the exact track each layer selects.
Repro file: 2 audio tracks — #1 Russian AC3 5.1, #2 English E‑AC3 5.1 (Atmos) (an Amazon WEB‑DL). Direct Play. Selecting English plays Russian and vice‑versa.
Everything below the app is correct
LG’s pipeline maps track index → audio faithfully (Atmos flag as ground truth, since only the English track is Atmos):
SELECT_TRACK audio index 0 → audio path: NONE (Russian)
SELECT_TRACK audio index 1 → audio path: ATMOS (English)
The HTML audioTracks list is also correctly ordered: idx0 {language:"ru"}, idx1 {language:"en"}.
But with English selected in Plex’s menu, the app enables audioTracks[0] (Russian). Force‑enabling audioTracks[1] from the DevTools console makes the pipeline switch instantly (SELECT_TRACK index 1) and the correct language plays. So the player is innocent — the app hands it the wrong index.
The bug in the app code
HTMLPlayer._selectAudioTrack (in packages_app_components_player_HTMLPlayer_tsx.js) delegates to a webOS‑specific helper injected from getAppConfig_ts.js. Deobfuscated:
function getSelectedAudioStreamIndex(serverStreams, elementTracks) {
// (filters truehd/dca/pcm when list lengths differ — not relevant here)
const r = serverStreams.findIndex(s => s.selected);
// sanity check: per-position language match, EXACT string equality
const ok = serverStreams.every((s, i) => {
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 ok ? r : elementTracks.length - r - 1; // ← check fails ⇒ MIRROR the index
}
If any position fails the language comparison, the app assumes the element’s track list is reversed (presumably a workaround for some old webOS firmware) and enables the mirrored entry.
My file’s English track carries the Matroska LanguageIETF tag en-US (typical of Amazon WEB‑DLs), so PMS reports languageTag: "en-US" — but the webOS media element normalizes audioTracks[i].language to "en". The exact‑equality check fails on that position, the helper takes the “reversed” branch, and with English selected (r = 1) it returns 2 − 1 − 1 = 0 → Russian. Selecting Russian returns 1 → English. A perfect swap, both ways.
I replayed this exact logic in the live page against the real server metadata: langCheckPassed: false, indexAppWouldEnable: 0. Matches the observed behavior bit‑for‑bit. (The helper even logs "Unexpected number of audio tracks available on media element" in a related path — searchable in the source.)
The real trigger (verified)
It is not about codecs. I tested three multi‑track files, all Direct Play:
| File |
Audio |
Language tags (PMS languageTag vs element) |
Result |
| AC3 rus + E‑AC3 eng (Atmos) |
mixed |
ru/ru ✓, en-US/en ✗ |
swapped |
| AC3 rus + E‑AC3 eng (Atmos), different release |
mixed |
ru/ru ✓, en/en ✓ |
correct |
| AC3 rus + AC3 rus + E‑AC3 eng |
mixed |
all ✓ |
correct |
Same codec mix, opposite outcomes — the only difference is the container’s IETF language tag. The trigger is: any Direct‑Played multi‑track file where one stream’s BCP‑47 tag doesn’t exactly equal the element’s normalized language (en-US vs en, regional variants, missing/und mismatches). That’s why this bug looks file‑dependent and random across release groups, and why it survives reinstalls and different PMS versions.
Why the known workarounds work
- Disable “Allow Direct Play” / disable E‑AC3 (earlier in this thread): forces a transcode to a single audio stream — with one element track the helper is skipped entirely (
tracks[0].enabled = true unconditionally). Same reason the desktop web client is unaffected.
- Fixing the file’s tag also fixes playback:
mkvpropedit file.mkv --edit track:a2 --set language-ietf=en (seconds, no remux) makes the check pass and the correct track play.
The ask
The fix is one line in the webOS client: compare primary subtags (languageTag.split("-")[0]) instead of exact strings — or better, drop the guess‑reversal heuristic and match element tracks by language directly. The audioStreamID sent to the server is already correct; only the local audioTracks.enabled mapping is mirrored. Relevant because the webOS app has been frozen for many of us, and en-US‑tagged WEB‑DLs are extremely common.
Happy to share the DevTools dumps, pipeline SELECT_TRACK logs, and PMS stream JSON if it helps repro.