Members

(static, readonly) JellyfinLanguage

JellyfinLanguage: ISO 639-2 three-letter language codes used by Jellyfin Only includes languages supported by Roku OS locales

Properties
NameTypeDescription
ENGLISH
SPANISH
PORTUGUESE
FRENCH
GERMAN
ITALIAN

(static, constant) ORIGINAL_LANGUAGE_PREFERENCE

The web client's "Original language" audio preference (Jellyfin 12.0+). The server stores it in AudioLanguagePreference in place of a language code; it means "the item's OriginalLanguage".

Default Value
  • originallanguage

Methods

(static) applyLiveDirectPlayFallback(video, meta) → {void}

applyLiveDirectPlayFallback: Flags that a transcode retry is available for live streams attempting direct play. Live direct play may fail on Roku if the server overestimates compatibility. VideoPlayerView observes isTranscodeAvailable to retry with transcoding on error.

Parameters:
NameTypeDescription
videoobject

Video object to update

metaobject

Video metadata containing the live stream flag

Returns:
Type: 
void

(static) findBestAudioStreamIndex(streams, playDefault, preferredLanguage, deviceCapabilitiesopt, preferOriginalopt) → {integer}

findBestAudioStreamIndex: Primary function for selecting the best audio stream

Selection priority when playDefault = true (Jellyfin: "Play default audio track regardless of language"):

  1. Filter by IsDefault = true streams (ignore language preference)
  2. If multiple IsDefault streams, use language preference as tiebreaker
  3. If still multiple or no language match, apply hardware optimization
  4. If no IsDefault streams exist, fall back to language preference, then hardware optimization

Selection priority when playDefault = false:

  1. Filter by language preference (completely ignore IsDefault flag)
  2. If multiple language matches, apply hardware optimization
  3. If no language matches, apply hardware optimization to all streams

"Original language" (preferOriginal, playDefault = false): as the server does, a stream the file flags IsOriginal wins: among the language matches when the item's original language is known, among all streams when it is not. The server ignores IsOriginal when playDefault = true.

Language matching: exact codes first, then the same language in another ISO form (matchingLanguageCodes()).

Roku OS Language Fallback: When preferredLanguage is blank/empty, automatically uses the Roku device's OS language (from m.global.device.locale) as fallback for better out-of-box experience (see issue #179). Not with preferOriginal: the viewer asked for the original, and the locale is usually the dub, so no language rule applies instead.

Hardware optimization:

  • Prefer streams matching device's max channel capability
  • Among matches, prefer direct-playable codecs
  • Fall back intelligently based on channel counts
Parameters:
NameTypeAttributesDefaultDescription
streamsdynamic

Array of media streams from Jellyfin metadata

playDefaultdynamic

Boolean, if true use IsDefault flag and only use language as tiebreaker

preferredLanguagedynamic

Language code in any ISO 639 form (for example "eng", "ko")

deviceCapabilitiesdynamic<optional>
invalid

Optional: Device audio capabilities (for testing). If invalid, will detect automatically.

preferOriginalboolean<optional>
false

The viewer wants the original audio (wantsOriginalAudio())

Returns:
  • Jellyfin index of best audio stream, or 0 if not found
Type: 
integer

(static) findBestVideoSource(mediaSources, deviceCapabilitiesopt) → {integer}

findBestVideoSource: Selects the optimal MediaSource for the device's capabilities

Scoring algorithm:

  1. Resolution fit (primary): Prefer highest resolution at or below device max. Sources exceeding device max receive a penalty (will likely transcode).
  2. HDR match (secondary): If device supports HDR, prefer HDR sources. If device does not support HDR, prefer SDR (avoids tone-mapping transcode).
  3. Direct play (tertiary): Prefer sources the server says can direct play.
  4. Bitrate (tiebreaker): Higher bitrate wins among otherwise-equal sources.
Parameters:
NameTypeAttributesDefaultDescription
mediaSourcesobject

Array of MediaSource objects from Jellyfin API

deviceCapabilitiesdynamic<optional>
invalid

Optional device capabilities (for testing)

Returns:
  • Array index of the best MediaSource (0-based)
Type: 
integer

(static) findBestVideoSourceAmongIds(mediaSources, sourceIds, deviceCapabilitiesopt) → {string}

findBestVideoSourceAmongIds: Best source for this device among a NAMED SUBSET of an item's versions.

The tie-break half of versionResume.chooseResumeSource(): that module decides which versions are at the same place and so compete on quality, but it is pure by contract and has no device knowledge, so it hands the tie back as ids. Scoring runs over only those versions, because the best of a subset is not the best overall: a 4K version nobody has started must not win a tie between two the viewer is actually partway through.

Parameters:
NameTypeAttributesDefaultDescription
mediaSourcesdynamic

the item's full MediaSource array

sourceIdsdynamic

ids to choose between (from chooseResumeSource().tiedIds)

deviceCapabilitiesdynamic<optional>
invalid

Optional device capabilities (for testing)

Returns:
  • the winning source id, or "" when none of the ids name a source
Type: 
string

(static) findDefaultSubtitleStreamIndex(mediaStreams, selectedAudioStreamIndex, originalLanguageopt) → {integer}

findDefaultSubtitleStreamIndex: Determine which subtitle stream will be auto-selected during playback Mirrors the selection logic in defaultSubtitleTrackFromVid() and sortSubtitles() (subtitles.bs) for use on the render thread without requiring playback-specific dependencies (buildURL, etc.)

Selection logic (by subtitleMode): "none": No subtitles "default": First forced or default subtitle matching preferred language "always": First non-forced subtitle matching preferred language; fallback to forced/default "onlyforced": First forced subtitle matching preferred language "smart": First non-forced subtitle when audio language differs from preference; fallback to forced/default

Priority ordering: forced > default > normal; preferred language first within each category Text subtitles are tried first; non-text only used when playbackSubsOnlyText is false

Parameters:
NameTypeAttributesDefaultDescription
mediaStreamsobject

Array of MediaStream objects from Jellyfin API (all types: video, audio, subtitle)

selectedAudioStreamIndexinteger

Server-side index of the selected audio stream (for Smart mode)

originalLanguagestring<optional>
""

The item's OriginalLanguage (JellyfinBaseItem.originalLanguage)

Returns:
  • Server-side stream index of the selected subtitle, or -1 if none
Type: 
integer

(static) findDirectPlayableStreamByChannelCount(audioStreams, targetChannels, deviceCapabilities) → {dynamic}

findDirectPlayableStreamByChannelCount: Finds a direct-playable stream with specific channel count

Parameters:
NameTypeDescription
audioStreamsobject

Array of audio streams

targetChannelsinteger

Desired channel count (2, 6, or 8)

deviceCapabilitiesobject

Device capability info

Returns:
  • Matching stream or invalid
Type: 
dynamic

(static) getDeviceAudioCapabilities() → {object}

getDeviceAudioCapabilities: Detects device audio codec and channel support

Strategy:

  • Use combined check (no PassThru) for 6-channel and below (safe, checks both)
  • Always verify 8-channel with PassThru: 1 (combined check can lie)
  • Roku max native decode is 6 channels (varies by model)
  • 8-channel requires HDMI passthrough to receiver/soundbar

maxChannels (integer: 2, 6, or 8) - Maximum supported audio channels supports8Channel (boolean) - True if HDMI passthrough supports 8-channel audio

Returns:
  • AssocArray with:
Type: 
object

(static) getDeviceVideoCapabilities(mockCapabilitiesopt) → {object}

getDeviceVideoCapabilities: Detects device video resolution and HDR support

Strategy:

  • Use cached m.global.device values for resolution (render-thread safe, no object creation)
  • Use canPlay4k() for 4K eligibility (HDCP 2.2 + HEVC decode check)
  • Use roDeviceInfo.GetDisplayProperties() for HDR format detection

maxHeight (integer) - Maximum display height in pixels maxWidth (integer) - Maximum display width in pixels canDecode4k (boolean) - True if device can play 4K content supportsHdr10 (boolean) - True if display supports HDR10 supportsDolbyVision (boolean) - True if display supports Dolby Vision supportsHlg (boolean) - True if display supports HLG

Parameters:
NameTypeAttributesDefaultDescription
mockCapabilitiesdynamic<optional>
invalid

Optional mock for unit testing (must have isMock=true)

Returns:
  • AssocArray with:
Type: 
object

(static) getTranscodeReasons(url) → {object}

getTranscodeReasons: Extracts the transcode reason codes from a Jellyfin transcoding URL. The server embeds reasons as a comma-separated TranscodeReasons query parameter. Used to detect VideoLevelNotSupported so a direct-play fallback can be attempted.

Parameters:
NameTypeDescription
urlstring

The transcoding URL containing TranscodeReasons

Returns:

Array of reason strings, or empty array if param not present

Type: 
object

(static) isCommentaryAudioStream(stream) → {boolean}

isCommentaryAudioStream: Determine whether a stream is a commentary/secondary track.

Detection: case-insensitive substring match for "commentary" in the stream Title. Used by findBestAudioStreamIndex() to deprioritize commentary tracks during auto-selection so a director's-commentary stereo track doesn't outrank the main multichannel track on a stereo device (issue #500).

Parameters:
NameTypeDescription
streamobject

MediaStream object from Jellyfin metadata

Returns:
  • true if the stream looks like a commentary track
Type: 
boolean

(static) isLanguageMatch(matches, code) → {boolean}

isLanguageMatch: Whether a stream language is in a matchingLanguageCodes() set.

Parameters:
NameTypeDescription
matchesobject

Set from matchingLanguageCodes()

codedynamic

Stream language code

Returns:
Type: 
boolean

(static) isSelectedAudioStreamDirectPlayable(jellyfinAudioIndex, audioStreams) → {boolean}

isSelectedAudioStreamDirectPlayable: Whether the audio stream at jellyfinAudioIndex inside audioStreams is decodable by the current device.

Used by mid-playback audio switching to decide whether Roku's native track-switch is viable, or whether a server reload is required (for example switching back to an 8ch track on a stereo device: Roku can't decode it natively, so a transcode reload is needed).

Parameters:
NameTypeDescription
jellyfinAudioIndexinteger

Jellyfin index of the chosen audio stream

audioStreamsdynamic

Array of audio MediaStream objects (for example fullAudioData)

Returns:
  • true if the chosen stream can be direct-played on this device
Type: 
boolean

(static) isStreamDirectPlayable(stream, deviceCapabilities) → {boolean}

isStreamDirectPlayable: Checks if an audio stream can be directly played by the device

Answers the question the device profile answers (getActualCodecSupport, plus the rules around it in getCodecProfiles), so a mid-play switch goes native exactly where the server would have direct-played the track, and auto-pick favors those tracks. Asking Roku its own way here is what let a switch go native onto a path the server never chooses: DTS 7.1, which failed to start, and TrueHD, which buffers forever.

Parameters:
NameTypeDescription
streamobject

Audio stream object with codec and channels fields

deviceCapabilitiesobject

Device capability info

Returns:
  • True if stream can be direct played
Type: 
boolean

(static) mapRokuLocaleToJellyfinLanguage(rokuLocale) → {string}

mapRokuLocaleToJellyfinLanguage: Converts Roku locale codes to Jellyfin ISO 639-2 language codes

Maps Roku device locale (for example "en_US", "fr_CA") to Jellyfin's 3-letter language codes. Extracts base language from locale and maps to standard ISO 639-2 codes.

Supported Roku locales:

  • en_US, en_GB, en_CA, en_AU: eng (English)
  • es_ES, es_MX: spa (Spanish)
  • pt_BR: por (Portuguese)
  • fr_CA: fra (French)
  • de_DE: deu (German)
  • it_IT: ita (Italian)
Parameters:
NameTypeDescription
rokuLocaledynamic

Roku locale string (for example "en_US", "fr_CA")

Returns:
  • Jellyfin ISO 639-2 language code (for example "eng", "fra"), or empty string if not recognized
Type: 
string

(static) matchingLanguageCodes(codes, preference) → {object}

matchingLanguageCodes: The stream language codes that count as a match for a preference.

Exact codes win: when any stream carries the preferred code itself (case-insensitively), only that code matches, so a "pob" preference keeps its Brazilian track over a "por" one. Only when none does are codes for the same language in another form accepted (languagesMatch()). That is how a "fre" preference finds a "fra" track, and an OriginalLanguage of "ko" a "kor" one.

Compute it once over ALL the candidate streams, not per filtered subset, so a subset without the exact code cannot fall back to a same-language code the full list would have ruled out.

Parameters:
NameTypeDescription
codesobject

Array of stream language codes (entries may be invalid or empty)

preferencestring

Preferred language code, or "" for none

Returns:
  • AssocArray set of lowercased matching codes; empty when preference is ""
Type: 
object

(static) mediaSourceForId(mediaSources, mediaSourceId) → {dynamic}

mediaSourceForId: The MediaSource a playback request is for.

mediaSources[0] is only the server's default. A version picked in the Video dropdown sits elsewhere in the list, and from Jellyfin 12.0 the server moves the in-progress version to the front, so anything about the file being PLAYED (its runtime, bitrate, container or streams) has to be read from the source the request names (mediaSourceIndexForId).

are no sources

Parameters:
NameTypeDescription
mediaSourcesdynamic

MediaSourceInfo array

mediaSourceIddynamic

the id the request names ("" or invalid = the default)

Returns:

the matching source; mediaSources[0] when none matches; invalid when there

Type: 
dynamic

(static) mediaSourceIndexForId(mediaSources, mediaSourceId) → {integer}

mediaSourceIndexForId: Index of the MediaSource with this id.

Case-insensitive, matching the server: it keys its own per-version lookup with StringComparer.OrdinalIgnoreCase and compares source ids with StringComparison .OrdinalIgnoreCase (MediaSourceManager), so it does not assume one spelling of a GUID and neither should any caller that matches one.

Parameters:
NameTypeDescription
mediaSourcesdynamic

MediaSourceInfo array

mediaSourceIddynamic
Returns:

the index, or -1 when absent

Type: 
integer

(static) needsSeriesOriginalLanguage(item, userSettings, userConfig) → {boolean}

needsSeriesOriginalLanguage: True when an episode's track selection needs its series' OriginalLanguage: the viewer wants the original audio and the episode has none of its own. The server falls back to the series itself (Episode.GetInheritedOriginalLanguage), but sends only the item's own value, and not every metadata provider sets one on episodes. ItemMetaData() fetches it; render-thread callers, which cannot, leave the track to LoadVideoContentTask.

Parameters:
NameTypeDescription
itemobject

JellyfinBaseItem

userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

Returns:
Type: 
boolean

(static) originalAudioStreams(audioStreams) → {object}

originalAudioStreams: The streams the file flags as the original audio (MediaStream.IsOriginal).

Parameters:
NameTypeDescription
audioStreamsobject

Array of audio MediaStream AAs

Returns:
  • Array of the flagged streams, possibly empty
Type: 
object

(static) resolveAudioLanguagePreference(userSettings, userConfig, originalLanguageopt) → {string}

resolveAudioLanguagePreference: Resolves the audio language preference setting value

Checks JellyRock override setting first, then falls back to web client setting. Returns a 3-letter ISO 639-2 language code (for example "eng", "jpn") or empty string.

The web client's "Original language" choice (12.0+) resolves to the item's OriginalLanguage, which is ISO 639-1 ("ko"), or to "" when the item has none. Pair the result with wantsOriginalAudio(): "" then means "no language rule", not "use the Roku locale".

Parameters:
NameTypeAttributesDefaultDescription
userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

originalLanguagestring<optional>
""

The item's OriginalLanguage (JellyfinBaseItem.originalLanguage)

Returns:
  • Resolved audio language preference (language code or empty string)
Type: 
string

(static) resolvePlayDefaultAudioTrack(userSettings, userConfig) → {boolean}

resolvePlayDefaultAudioTrack: Resolves the playDefaultAudioTrack setting value

Checks JellyRock override setting first, then falls back to web client setting. Ensures a valid boolean is always returned.

Parameters:
NameTypeDescription
userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

Returns:
  • Resolved playDefaultAudioTrack value (guaranteed boolean)
Type: 
boolean

(static) resolveSubtitleLanguagePreference(userSettings, userConfig, originalLanguageopt) → {string}

resolveSubtitleLanguagePreference: Resolves the subtitle language preference setting value

Checks JellyRock override setting first, then falls back to web client setting. The "audio" option chains to resolveAudioLanguagePreference() so subtitle language follows the user's audio language override, including "Original language", which follows the item's OriginalLanguage, or applies no language rule when the item has none.

Parameters:
NameTypeAttributesDefaultDescription
userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

originalLanguagestring<optional>
""

The item's OriginalLanguage (JellyfinBaseItem.originalLanguage)

Returns:
  • Resolved subtitle language preference (language code or empty string)
Type: 
string

(static) selectAudioStreamIndex(streams, userSettings, userConfig, originalLanguageopt) → {integer}

selectAudioStreamIndex: The audio stream to auto-select for the viewer: their "play default track" and language preferences resolved, then findBestAudioStreamIndex().

Parameters:
NameTypeAttributesDefaultDescription
streamsobject

Array of MediaStream AAs (all types, or audio only)

userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

originalLanguagestring<optional>
""

The item's OriginalLanguage (JellyfinBaseItem.originalLanguage)

Returns:
  • Jellyfin index of the audio stream, or 0 if none
Type: 
integer

(static) selectBestStreamByHardware(audioStreams, deviceCapabilities) → {dynamic}

selectBestStreamByHardware: Selects the best audio stream based on device capabilities

Priority:

  1. Prefer streams matching device's max channel capability (for quality)
  2. Among matching streams, prefer direct-playable codecs
  3. Fall back intelligently based on channel counts
Parameters:
NameTypeDescription
audioStreamsobject

Array of audio stream objects

deviceCapabilitiesobject

Device capability info from getDeviceAudioCapabilities()

Returns:
  • Best matching audio stream object, or invalid
Type: 
dynamic

(static) selectSubtitleByMode(sortedEntries, subtitleMode, prefLang, selectedAudioLanguage, requireText, languageMatches) → {integer}

selectSubtitleByMode: Apply subtitle mode selection logic to sorted subtitle entries Mirrors defaultSubtitleTrack() in subtitles.bs

Parameters:
NameTypeDescription
sortedEntriesobject

Array of sorted subtitle entries from findDefaultSubtitleStreamIndex

subtitleModestring

Lowercase subtitle mode ("default", "always", "onlyforced", "smart")

prefLangstring

Preferred subtitle language (ISO 639-2 code, for example "eng")

selectedAudioLanguagestring

Language of the selected audio stream

requireTextboolean

If true, only consider text subtitle entries

languageMatchesobject

matchingLanguageCodes() for prefLang over ALL the item's subtitles

Returns:
  • Server-side stream index of the selected subtitle, or -1 if none
Type: 
integer

(static) streamLanguageCodes(streams, streamType) → {object}

streamLanguageCodes: The Language of each stream of one type, for matchingLanguageCodes().

Parameters:
NameTypeDescription
streamsobject

Array of MediaStream AAs

streamTypestring

"audio" or "subtitle" (case-insensitive)

Returns:
  • Array of language codes (entries may be invalid)
Type: 
object

(static) wantsOriginalAudio(userSettings, userConfig) → {boolean}

wantsOriginalAudio: True when the viewer's audio preference is "Original language": the web client's choice, not overridden by a JellyRock Roku or custom language.

Parameters:
NameTypeDescription
userSettingsobject

JellyfinUserSettings node (JellyRock settings)

userConfigobject

JellyfinUserConfiguration node (web client settings)

Returns:
Type: 
boolean