diff --git a/src/renderer/components/PlayerSettings/PlayerSettings.vue b/src/renderer/components/PlayerSettings/PlayerSettings.vue index d7d10d51b1251..5a3dae3b23a87 100644 --- a/src/renderer/components/PlayerSettings/PlayerSettings.vue +++ b/src/renderer/components/PlayerSettings/PlayerSettings.vue @@ -102,6 +102,14 @@ :icon="['fas', 'photo-film']" @change="updateDefaultQuality" /> + { + if (index === 0) { + return t('Settings.Player Settings.Default Language.System Language') + } + + return new Intl.DisplayNames('en', { type: 'language', languageDisplay: 'standard' }).of(language) ?? language +}) + +/** @type {import('vue').ComputedRef} */ +const defaultAudioLanguage = computed(() => store.getters.getDefaultAudioLanguage) + +/** + * @param {string} value + */ +function updateDefaultAudioLanguage(value) { + store.dispatch('updateDefaultAudioLanguage', value) +} + const PLAYBACK_RATE_INTERVAL_VALUES = ['0.1', '0.25', '0.5', '1'] /** @type {import('vue').ComputedRef} */ diff --git a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js index acf452532b528..6e15dadb78b68 100644 --- a/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js +++ b/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js @@ -290,6 +290,33 @@ export default defineComponent({ return parseInt(value) }) + /** @type {import('vue').ComputedRef} */ + const defaultAudioLanguage = computed(() => store.getters.getDefaultAudioLanguage) + + /** @type {import('vue').ComputedRef} */ + const preferredAudioLanguage = computed(() => { + return defaultAudioLanguage.value === 'system' ? locale.value : defaultAudioLanguage.value + }) + + /** + * Filters the given variants to the user's preferred audio language, falling back to the main audio track. + * @param {shaka.extern.Variant[]} variants + * @returns {shaka.extern.Variant[]} + */ + function filterVariantsByPreferredLanguage(variants) { + const languageVariants = variants.filter((variant) => { + return shaka.util.LanguageUtils.areLanguageCompatible(preferredAudioLanguage.value, variant.language) + }) + + if (languageVariants.length > 0) { + const mainVariants = languageVariants.filter(variant => variant.audioRoles.includes('main')) + return mainVariants.length > 0 ? mainVariants : languageVariants + } + + const mainVariants = variants.filter(variant => variant.audioRoles.includes('main')) + return mainVariants.length > 0 ? mainVariants : variants + } + /** @type {import('vue').ComputedRef} */ const enterFullscreenOnDisplayRotate = computed(() => { return store.getters.getEnterFullscreenOnDisplayRotate @@ -634,7 +661,11 @@ export default defineComponent({ // Electron doesn't like YouTube's vp9 VR video streams and throws: // "CHUNK_DEMUXER_ERROR_APPEND_FAILED: Projection element is incomplete; ProjectionPoseYaw required." // So use the AV1 and h264 codecs instead which it doesn't reject - preferredVideoCodecs: typeof props.vrProjection === 'string' ? ['av01', 'avc1'] : [] + preferredVideoCodecs: typeof props.vrProjection === 'string' ? ['av01', 'avc1'] : [], + + // The default language to prefer for the audio track. + // 'system' uses FreeTube's active locale, which shaka-player matches by prefix. + preferredAudioLanguage: preferredAudioLanguage.value } } @@ -1462,12 +1493,7 @@ export default defineComponent({ if (label) { variants = variants.filter(variant => variant.label === label) } else if (hasMultipleAudioTracks.value) { - // default audio track - const filteredVariants = variants.filter(variant => variant.audioRoles.includes('main')) - // Sometimes there is nothing marked as main, don't filter in this case - if (filteredVariants.length > 0) { - variants = filteredVariants - } + variants = filterVariantsByPreferredLanguage(variants) } const isPortrait = variants[0].height > variants[0].width @@ -2906,8 +2932,7 @@ export default defineComponent({ let variants = player.getVariantTracks() if (hasMultipleAudioTracks.value) { - // default audio track - variants = variants.filter(variant => variant.audioRoles.includes('main')) + variants = filterVariantsByPreferredLanguage(variants) } const highestBandwidth = Math.max(...variants.map(variant => variant.audioBandwidth)) @@ -3164,12 +3189,7 @@ export default defineComponent({ if (label) { variants = variants.filter(variant => variant.label === label) } else if (variants.length > 1) { - // default audio track - const filteredVariants = variants.filter(variant => variant.audioRoles.includes('main')) - // Sometimes there is nothing marked as main, don't filter in this case - if (filteredVariants.length > 0) { - variants = filteredVariants - } + variants = filterVariantsByPreferredLanguage(variants) } let chosenVariant diff --git a/src/renderer/store/modules/settings.js b/src/renderer/store/modules/settings.js index 694be8ff0be32..996c65932d19a 100644 --- a/src/renderer/store/modules/settings.js +++ b/src/renderer/store/modules/settings.js @@ -168,6 +168,7 @@ const state = { mainColor: 'Red', secColor: 'Blue', defaultAutoplayInterruptionIntervalHours: 3, + defaultAudioLanguage: 'system', defaultCaptionSettings: '{}', defaultInterval: 5, defaultPlayback: 1, diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml index 6dafdfcf6bbb3..93cb7e497dbf3 100644 --- a/static/locales/en-US.yaml +++ b/static/locales/en-US.yaml @@ -479,6 +479,9 @@ Settings: 1440p: 1440p 4k: 4k 8k: 8k + Default Language: + Default Language: Default Audio Language + System Language: Use System Locale Screenshot: Enable: Enable Screenshot Mode: Screenshot Mode