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
- Source
(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
- Source
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).
| Name | Type | Description |
|---|---|---|
accumulated | integer | |
delta | integer |
- Source
- 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.
| Name | Type | Description |
|---|---|---|
channels | object | array of channel AAs as returned by /livetv/channels |
currentChannelId | string | id of the currently-playing channel |
- Source
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)
| Name | Type | Description |
|---|---|---|
epochAtPosition0 | integer | epoch corresponding to video position=0 in the current stream |
calibrated | boolean | false until first play tick anchors the reference |
position | integer | current video position (seconds), used only during play |
now | integer | current wall-clock epoch |
pausedAtEpoch | integer | epoch when the current pause started, or 0 if not paused |
accumulated | integer | seconds-behind-live snapshotted at pause start (plus any |
- Source
- Type:
- integer
(static) parseProgramEndTimeEpoch(meta) → {integer}
| Name | Type | Description |
|---|---|---|
meta | object |
- Source
- Type:
- integer
(static) parseProgramStartTimeEpoch(meta) → {integer}
| Name | Type | Description |
|---|---|---|
meta | object |
- Source
- 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.
| Name | Type | Description |
|---|---|---|
accumulated | integer | |
pauseDurationSoFar | integer | |
scrubDelta | integer |
- Source
- 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).
| Name | Type | Description |
|---|---|---|
videotype | string | lowercased meta.type |
meta | object | JellyfinBaseItem or AA with seriesId field |
- Source
- Type:
- boolean