|
6 | 6 | ' Comprehensive audio and video stream selection utilities |
7 | 7 | ' Combines user preferences with hardware capabilities for optimal playback |
8 | 8 |
|
| 9 | +' JellyfinLanguage: ISO 639-2 three-letter language codes used by Jellyfin |
| 10 | +' Only includes languages supported by Roku OS locales |
| 11 | +enum JellyfinLanguage |
| 12 | + ENGLISH = "eng" |
| 13 | + SPANISH = "spa" |
| 14 | + PORTUGUESE = "por" |
| 15 | + FRENCH = "fra" |
| 16 | + GERMAN = "deu" |
| 17 | + ITALIAN = "ita" |
| 18 | +end enum |
| 19 | + |
9 | 20 | ' resolvePlayDefaultAudioTrack: Resolves the playDefaultAudioTrack setting value |
10 | 21 | ' |
11 | 22 | ' Checks JellyRock override setting first, then falls back to web client setting. |
|
40 | 51 | return defaultValue |
41 | 52 | end function |
42 | 53 |
|
| 54 | +' mapRokuLocaleToJellyfinLanguage: Converts Roku locale codes to Jellyfin ISO 639-2 language codes |
| 55 | +' |
| 56 | +' Maps Roku device locale (e.g., "en_US", "fr_CA") to Jellyfin's 3-letter language codes. |
| 57 | +' Extracts base language from locale and maps to standard ISO 639-2 codes. |
| 58 | +' |
| 59 | +' Supported Roku locales: |
| 60 | +' - en_US, en_GB, en_CA, en_AU → eng (English) |
| 61 | +' - es_ES, es_MX → spa (Spanish) |
| 62 | +' - pt_BR → por (Portuguese) |
| 63 | +' - fr_CA → fra (French) |
| 64 | +' - de_DE → deu (German) |
| 65 | +' - it_IT → ita (Italian) |
| 66 | +' |
| 67 | +' @param {dynamic} rokuLocale - Roku locale string (e.g., "en_US", "fr_CA") |
| 68 | +' @returns {string} - Jellyfin ISO 639-2 language code (e.g., "eng", "fra"), or empty string if not recognized |
| 69 | +function mapRokuLocaleToJellyfinLanguage(rokuLocale as dynamic) as string |
| 70 | + if not isValid(rokuLocale) or rokuLocale = "" then return "" |
| 71 | + |
| 72 | + ' Extract base language (first 2 characters before underscore) |
| 73 | + ' "en_US" → "en", "fr_CA" → "fr" |
| 74 | + baseLanguage = "" |
| 75 | + underscorePos = rokuLocale.Instr("_") |
| 76 | + if underscorePos > -1 |
| 77 | + baseLanguage = LCase(Left(rokuLocale, underscorePos)) |
| 78 | + else |
| 79 | + ' No underscore found - use entire string (shouldn't happen with valid Roku locales) |
| 80 | + baseLanguage = LCase(rokuLocale) |
| 81 | + end if |
| 82 | + |
| 83 | + ' Map base language to Jellyfin ISO 639-2 code |
| 84 | + if baseLanguage = "en" |
| 85 | + return JellyfinLanguage.ENGLISH |
| 86 | + else if baseLanguage = "es" |
| 87 | + return JellyfinLanguage.SPANISH |
| 88 | + else if baseLanguage = "pt" |
| 89 | + return JellyfinLanguage.PORTUGUESE |
| 90 | + else if baseLanguage = "fr" |
| 91 | + return JellyfinLanguage.FRENCH |
| 92 | + else if baseLanguage = "de" |
| 93 | + return JellyfinLanguage.GERMAN |
| 94 | + else if baseLanguage = "it" |
| 95 | + return JellyfinLanguage.ITALIAN |
| 96 | + end if |
| 97 | + |
| 98 | + ' Unknown language - return empty string |
| 99 | + return "" |
| 100 | +end function |
| 101 | + |
43 | 102 | ' findBestAudioStreamIndex: Primary function for selecting the best audio stream |
44 | 103 | ' |
45 | 104 | ' Selection priority when playDefault = true (Jellyfin: "Play default audio track regardless of language"): |
|
53 | 112 | ' 2. If multiple language matches, apply hardware optimization |
54 | 113 | ' 3. If no language matches, apply hardware optimization to all streams |
55 | 114 | ' |
| 115 | +' Roku OS Language Fallback: |
| 116 | +' When preferredLanguage is blank/empty, automatically uses the Roku device's |
| 117 | +' OS language (from m.global.device.locale) as fallback for better |
| 118 | +' out-of-box experience (see issue #179) |
| 119 | +' |
56 | 120 | ' Hardware optimization: |
57 | 121 | ' - Prefer streams matching device's max channel capability |
58 | 122 | ' - Among matches, prefer direct-playable codecs |
|
71 | 135 | deviceCapabilities = getDeviceAudioCapabilities() |
72 | 136 | end if |
73 | 137 |
|
| 138 | + ' Apply Roku OS language fallback when web client language preference is blank |
| 139 | + ' This provides better out-of-box experience for new users (issue #179) |
| 140 | + effectiveLanguage = preferredLanguage |
| 141 | + if not isValid(preferredLanguage) or preferredLanguage = "" |
| 142 | + ' Get Roku OS locale from global device node |
| 143 | + rokuLocale = m.global.device.locale |
| 144 | + if isValid(rokuLocale) and rokuLocale <> "" |
| 145 | + effectiveLanguage = mapRokuLocaleToJellyfinLanguage(rokuLocale) |
| 146 | + end if |
| 147 | + end if |
| 148 | + |
74 | 149 | ' Collect all audio streams with valid index fields |
75 | 150 | ' Streams without index cannot be selected, so filter them out early |
76 | 151 | audioStreams = [] |
|
111 | 186 | end if |
112 | 187 | else |
113 | 188 | ' Multiple IsDefault streams - try language preference as tiebreaker |
114 | | - if isValid(preferredLanguage) and preferredLanguage <> "" |
| 189 | + if isValid(effectiveLanguage) and effectiveLanguage <> "" |
115 | 190 | languageMatchedDefaults = [] |
116 | 191 | for i = 0 to defaultStreams.Count() - 1 |
117 | | - if isValid(defaultStreams[i].Language) and LCase(defaultStreams[i].Language) = LCase(preferredLanguage) |
| 192 | + if isValid(defaultStreams[i].Language) and LCase(defaultStreams[i].Language) = LCase(effectiveLanguage) |
118 | 193 | languageMatchedDefaults.push(defaultStreams[i]) |
119 | 194 | end if |
120 | 195 | end for |
|
141 | 216 |
|
142 | 217 | ' BRANCH 2: Either playDefault = false OR playDefault = true but no IsDefault streams found |
143 | 218 | ' Use language preference as primary filter (completely ignore IsDefault flag) |
144 | | - if isValid(preferredLanguage) and preferredLanguage <> "" |
| 219 | + if isValid(effectiveLanguage) and effectiveLanguage <> "" |
145 | 220 | languageMatchedStreams = [] |
146 | 221 | for i = 0 to audioStreams.Count() - 1 |
147 | | - if isValid(audioStreams[i].Language) and LCase(audioStreams[i].Language) = LCase(preferredLanguage) |
| 222 | + if isValid(audioStreams[i].Language) and LCase(audioStreams[i].Language) = LCase(effectiveLanguage) |
148 | 223 | languageMatchedStreams.push(audioStreams[i]) |
149 | 224 | end if |
150 | 225 | end for |
|
0 commit comments