Methods

(static) LoadItems_AddVideoContent(video, mediaSourceId, audioStreamIdxopt, forceTranscodingopt, bypassDoviPreservationopt) → {void}

Parameters:
NameTypeAttributesDefaultDescription
videoobject
mediaSourceIddynamic
audioStreamIdxinteger<optional>
1
forceTranscodingboolean<optional>
false
bypassDoviPreservationboolean<optional>
false
Returns:
Type: 
void

(static) LoadItems_VideoPlayer(id, mediaSourceId, audioStreamIdxopt, forceTranscodingopt, bypassDoviPreservationopt) → {dynamic}

Parameters:
NameTypeAttributesDefaultDescription
idstring
mediaSourceIddynamic
audioStreamIdxinteger<optional>
1
forceTranscodingboolean<optional>
false
bypassDoviPreservationboolean<optional>
false
Returns:
Type: 
dynamic

(static) addAudioStreamsToVideo(video, meta, mediaSourceId) → {void}

addAudioStreamsToVideo: Populate video.fullAudioData with the item's audio tracks.

Sources from the original item metadata (meta.mediaSourcesData), not from the PlaybackInfo response. The /PlaybackInfo response can return a transcode-shaped MediaSource carrying only the chosen audio stream, which would shrink the OSD picker to a single entry (issue #500). The OSD picker must always list every original track so the user can switch.

Parameters:
NameTypeDescription
videoobject

video AA receiving the fullAudioData field

metadynamic

item metadata (from ItemMetaData) holding mediaSourcesData

mediaSourceIddynamic

chosen MediaSource ID (matches one entry under meta.mediaSourcesData.mediaSources)

Returns:
Type: 
void

(static) addNextEpisodesToQueue(showID) → {void}

Add next episodes to the playback queue

Parameters:
NameTypeDescription
showIDdynamic
Returns:
Type: 
void

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

Parameters:
NameTypeDescription
videodynamic
metadynamic
Returns:
Type: 
void

(static) fetchMostRecentVersionId(itemId, meta, mediaSources) → {string}

fetchMostRecentVersionId: Ask the server which of an item's versions it would resume.

The resume query returns the version that owns the resume point, which is the answer the source order gives only from a primary (see versionResume.chooseResumeSource). Scoped to the item's parent (its season, or the folder holding a movie), so the reply is that parent's in-progress items rather than the whole of Continue Watching, and lean, because only ids are read.

answer, so the caller falls back to the order instead of asking again

Parameters:
NameTypeDescription
itemIdstring

the item being played

metaobject

ItemMetaData() for it

mediaSourcesobject

its MediaSources

Returns:

the version's source id, or "" when the server cannot say, itself an

Type: 
string

(static) fetchVersionPositions(positions, sourceIds) → {void}

fetchVersionPositions: Read each named version's own position into positions.

A version that does not answer is recorded as POSITION_UNREADABLE rather than left absent, so the caller's next pass cannot ask for it again and loop, and so an unread position is never mistaken for "this version has no progress", which would silently restart the content.

Parameters:
NameTypeDescription
positionsobject

sourceId -> ticks, filled in place

sourceIdsobject

the versions to read

Returns:
Type: 
void

(static) init() → {void}

Returns:
Type: 
void

(static) loadItems() → {void}

Returns:
Type: 
void

(static) reloadProbedStreams(meta, mediaSourceId) → {void}

reloadProbedStreams: Replace the item's media sources with the ones the server saved when the first PlaybackInfo probed them. Leaves meta as it was when the read fails, so the caller's picks stand and nothing else changes.

Parameters:
NameTypeDescription
metaobject

item metadata (from ItemMetaData), updated in place

mediaSourceIddynamic

the MediaSource being played

Returns:
Type: 
void

(static) resolveResumeVersion(itemId, meta, mediaSources) → {object}

resolveResumeVersion: Which of an item's versions resumes, and from where.

The imperative shell around versionResume.chooseResumeSource(): that function owns every rule and says what it still needs; this fetches it and asks again. Same division as ItemDetails, so both entry points reach an identical answer. The split between them is what made the app name one version on the details screen and play another.

Reads ride apiPipeline rather than a fetch loop: the pipeline keeps as many requests on the pool as it has slots, so N versions cost roughly one round trip instead of N. Each reply was ~150 bytes against the item's ~17 KB (measured 2026-09-19), and nothing is fetched at all unless something is already in progress, so an item nobody has started costs no requests on the playback path. When the source order cannot say which version was played last, one more request asks the server (fetchMostRecentVersionId).

Parameters:
NameTypeDescription
itemIdstring

the item being played

metaobject

ItemMetaData() for it (its own UserData position comes from here)

mediaSourcesobject

its MediaSources, in server order

Returns:

{ sourceId, ticks }: sourceId "" when no version is in progress

Type: 
object

(static) setupVideoContentWithAuth(video, mediaSourceId, audioStreamIdx) → {void}

setupVideoContentWithAuth: Configures video content URL and applies authentication

Determines the appropriate URL based on protocol and stream location, then applies Jellyfin authentication headers for internal streams. External streams (non-localhost) receive the raw URL without authentication to prevent credential leakage.

Protocol handling:

  • "file": Direct stream from Jellyfin server (gets auth)
  • Non-file with localhost domain: Proxied through Jellyfin (gets auth)
  • Non-file with external domain: Direct external URL (NO auth)
Parameters:
NameTypeDescription
videoobject

Video object containing content node to configure

mediaSourceIddynamic

Media source ID or empty string for live streams

audioStreamIdxinteger

Selected audio stream index

Returns:
Type: 
void

(static) upstreamManifestAnswers(url, format) → {boolean}

upstreamManifestAnswers: Whether THIS device can load a source's upstream manifest URL.

The device check before playing a source from its own URL (getUpstreamManifestBlocker). The server reaching a URL says nothing about the Roku reaching it, and a Roku handed one it cannot reach does not error: an unroutable host or an unresolvable name leaves it buffering, so the direct-play fallback does not run. A GET within timeouts.UPSTREAM_PREFLIGHT_MS that returns a manifest of the declared format is the answer (isUpstreamManifestResponse); anything else uses the server's stream. Raw roUrlTransfer on this Task thread, no Jellyfin auth (the URL is not Jellyfin's) and the URL is never logged, because upstream URLs carry session tokens.

Parameters:
NameTypeDescription
urldynamic

the source's upstream manifest URL (MediaSource.Path)

formatstring

"hls" or "dash" (upstreamManifestFormat)

Returns:
Type: 
boolean