From 1fe856afaa77f21e16962ee103f6af52c7ec805e Mon Sep 17 00:00:00 2001 From: Situla <196762523+Aquarius-Situla@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:20:29 +0800 Subject: [PATCH 1/2] fix: silence original audio unavailable toast on single-language streams --- apps/web/src/components/player-defaults.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/player-defaults.tsx b/apps/web/src/components/player-defaults.tsx index 6a6b4ee5..297e1116 100644 --- a/apps/web/src/components/player-defaults.tsx +++ b/apps/web/src/components/player-defaults.tsx @@ -114,11 +114,19 @@ export function PlayerDefaults({ : (audioOptions.find((option) => option.track.id === preferredDefaultAudioTrackId) ?? audioOptions.find((option) => option.track.id === originalAudioTrackId)); + const hasMultipleLanguages = + new Set( + audioOptions + .map((option) => normalizeLanguageTag(option.track.language)) + .filter(Boolean), + ).size > 1; + const missingOriginalByContract = forceOriginal && originalAudioTrackId === null; const missingOriginalByHeuristic = - forceOriginal && originalAudioTrackId === undefined && !match; + forceOriginal && originalAudioTrackId === undefined && hasMultipleLanguages && !match; if ( (missingOriginalByContract || missingOriginalByHeuristic) && + hasMultipleLanguages && !originalMissingNotified.current ) { originalMissingNotified.current = true; From b2d06525eab391103ba6c09d16511a2c41ef7f28 Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sat, 26 Sep 2026 18:00:37 +0000 Subject: [PATCH 2/2] test: cover single-language audio defaults --- apps/web/src/components/player-defaults.tsx | 13 ++++++------- apps/web/src/components/player-language.ts | 12 ++++++++++++ apps/web/tests/player-language.test.ts | 21 +++++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 apps/web/tests/player-language.test.ts diff --git a/apps/web/src/components/player-defaults.tsx b/apps/web/src/components/player-defaults.tsx index 297e1116..aa386b5a 100644 --- a/apps/web/src/components/player-defaults.tsx +++ b/apps/web/src/components/player-defaults.tsx @@ -7,7 +7,11 @@ import { useMediaState, useVideoQualityOptions, } from "../lib/vidstack"; -import { includesOriginal, normalizeLanguageTag } from "./player-language"; +import { + hasMultipleLanguageTracks, + includesOriginal, + normalizeLanguageTag, +} from "./player-language"; const QUALITY_OPTIONS = { sort: "descending" } as const; @@ -114,12 +118,7 @@ export function PlayerDefaults({ : (audioOptions.find((option) => option.track.id === preferredDefaultAudioTrackId) ?? audioOptions.find((option) => option.track.id === originalAudioTrackId)); - const hasMultipleLanguages = - new Set( - audioOptions - .map((option) => normalizeLanguageTag(option.track.language)) - .filter(Boolean), - ).size > 1; + const hasMultipleLanguages = hasMultipleLanguageTracks(audioOptions); const missingOriginalByContract = forceOriginal && originalAudioTrackId === null; const missingOriginalByHeuristic = diff --git a/apps/web/src/components/player-language.ts b/apps/web/src/components/player-language.ts index 08fc13af..c2608844 100644 --- a/apps/web/src/components/player-language.ts +++ b/apps/web/src/components/player-language.ts @@ -8,3 +8,15 @@ export function includesOriginal(value: string | undefined): boolean { if (!value) return false; return value.toLowerCase().includes("original"); } + +export function hasMultipleLanguageTracks( + options: readonly { track: { language?: string | null } }[], +): boolean { + const languages = new Set(); + for (const option of options) { + const language = normalizeLanguageTag(option.track.language); + if (language) languages.add(language); + if (languages.size > 1) return true; + } + return false; +} diff --git a/apps/web/tests/player-language.test.ts b/apps/web/tests/player-language.test.ts new file mode 100644 index 00000000..4473c184 --- /dev/null +++ b/apps/web/tests/player-language.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from "bun:test"; +import { hasMultipleLanguageTracks } from "../src/components/player-language"; + +test("distinguishes multiple bitrate tracks from multiple languages", () => { + expect( + hasMultipleLanguageTracks([{ track: { language: "fr-FR" } }, { track: { language: "fr-CA" } }]), + ).toBe(false); + expect( + hasMultipleLanguageTracks([{ track: { language: "fr-FR" } }, { track: { language: "en-US" } }]), + ).toBe(true); +}); + +test("ignores missing language tags", () => { + expect( + hasMultipleLanguageTracks([ + { track: { language: "fr-FR" } }, + { track: { language: null } }, + { track: {} }, + ]), + ).toBe(false); +});