Members

(static, constant) LIVE_EDGE_SEEK

Sentinel seek position used to jump to the live edge of an HLS stream. Roku clips this to the manifest's available range, landing at the live edge.

Default Value
  • 999999

(static, constant) LIVE_EDGE_THRESHOLD_SECONDS

Maximum behind-live offset (seconds) at which we still consider the user to be at the live edge. Absorbs 1-tick rounding flicker between the integer wall-clock (roDateTime.AsSeconds) and int(m.top.position) — both tick discretely but can be out of phase by up to 1 second depending on calibration alignment, which would otherwise flicker the UI between LIVE and -0:01 every second during normal play.

Default Value
  • 2

Methods

(static) applyScrubToAccumulatedBehind(accumulated, delta) → {integer}

Apply a scrub-during-pause delta to the accumulated behind-live counter. Forward scrub (positive delta) reduces behind; backward scrub (negative) grows it. Clamped to >= 0 so a forward scrub past the live edge resolves to 0 (live edge).

Parameters:
NameTypeDescription
accumulatedinteger
deltainteger
Returns:
Type: 
integer

(static) buildChannelQueueList(channels, currentChannelId) → {object}

Build a queue array from a Jellyfin /livetv/channels response and locate the currently-playing channel within it.

Parameters:
NameTypeDescription
channelsobject

array of channel AAs as returned by /livetv/channels

currentChannelIdstring

id of the currently-playing channel

Returns:

AA with shape: items=[queueItem...], currentIndex=integer (-1 if currentChannelId not found)

Type: 
object

(static) computeLiveTvBehindLive(epochAtPosition0, calibrated, position, now, pausedAtEpoch, accumulated) → {integer}

Compute seconds the user is behind the live edge using a hybrid play/pause model.

Roku's m.top.position for live HLS DVR streams keeps advancing during pause — it tracks the live edge, not the user's playhead. So position-vs-wallclock math cancels out to zero during pause and we have to switch strategies based on state:

During pause: behindLive = accumulated + (now - pausedAtEpoch) Wall-clock-only — independent of position so Roku's auto-advance during pause is invisible to us.

During play: behindLive = (now - epochAtPosition0) - position Position-math — self-correcting on scrubs while playing, where position genuinely tracks the user's playhead.

On the pause→play transition the caller re-anchors epochAtPosition0 so play-math continues from the same offset where wall-clock math left off.

scrub adjustments made during the current pause)

Parameters:
NameTypeDescription
epochAtPosition0integer

epoch corresponding to video position=0 in the current stream

calibratedboolean

false until first play tick anchors the reference

positioninteger

current video position (seconds), used only during play

nowinteger

current wall-clock epoch

pausedAtEpochinteger

epoch when the current pause started, or 0 if not paused

accumulatedinteger

seconds-behind-live snapshotted at pause start (plus any

Returns:
Type: 
integer

(static) parseProgramEndTimeEpoch(meta) → {integer}

Parameters:
NameTypeDescription
metaobject
Returns:
Type: 
integer

(static) parseProgramStartTimeEpoch(meta) → {integer}

Parameters:
NameTypeDescription
metaobject
Returns:
Type: 
integer

(static) repartitionPauseStateOnScrub(accumulated, pauseDurationSoFar, scrubDelta) → {integer}

Compute the new accumulated baseline after a scrub during pause. Folds the elapsed pause duration into accumulated first so the wall-clock pause counter can be reset from the scrub moment — otherwise a forward scrub to live edge would still leave the wall-clock counter adding to total behindLive.

Caller is expected to also reset pausedAtEpoch = now after this returns.

Parameters:
NameTypeDescription
accumulatedinteger
pauseDurationSoFarinteger
scrubDeltainteger
Returns:
Type: 
integer

(static) shouldTreatAsEpisode(videotype, meta) → {boolean}

Decide whether a video should be tagged as ContentNode contenttype="episode". Tagging "episode" enables the Next-Episode flow in VideoPlayerView.

Episodes and series items are always episodes. Recordings are episodes only when their metadata identifies them as part of a series (seriesId set) — a standalone recording of a movie or sports event should NOT trigger the Next-Episode prompt.

Recordings without a season/episode number but WITH a seriesId still count as series content (e.g. news bulletins or shows whose EPG omits S/E numbering).

Parameters:
NameTypeDescription
videotypestring

lowercased meta.type

metaobject

JellyfinBaseItem or AA with seriesId field

Returns:
Type: 
boolean