Members
(static, constant) ESTIMATED_BUTTON_ROW_HEIGHT_PX
First-frame estimate for the button row's total rendered height. Used by anchorDateLabel() before boundingRect() has settled (it reports height=0 during initial render). Observed to land around 130 px for the typical 2-line-label icon button. A later renderTracking callback replaces this with the real measured height once layout completes.
- Default Value
- 130
- Source
(static, constant) INFO_ROW_MIN_CUT_WIDTH
Narrowest a cut info-row item may be before it is dropped instead. Measured 2026-09-22 on a Roku Ultra: the row font averages 15.6px per character, so 160px is about ten characters, the shortest cut that still reads as a word or name rather than a stub.
- Default Value
- 160
- Source
(static, constant) ITEM_DETAILS_EXTRAS_PADDING
Gap between bottom of itemInfoRows and top of extras pane when extras are open
- Default Value
- 24
- Source
(static, constant) LOGO_MAX_DISPLAY_HEIGHT
Maximum display height for non-Person primary images. Prevents portrait posters from extending above the metadata rows. Person photos are exempt because their info rows are short and don't reach the right edge where the photo sits.
- Default Value
- 336
- Source
(static, constant) LOGO_MAX_DISPLAY_WIDTH
Maximum display width for the logo image. Prevents very wide/flat logos from overlapping buttons
- Default Value
- 500
- Source
(static, constant) LOGO_MIN_DISPLAY_HEIGHT
Minimum display height for the logo image: images too small are scaled up (aspect ratio preserved).
- Default Value
- 212
- Source
(static, constant) LOGO_RIGHT_ANCHOR_X
Right edge the logo is anchored to (1920 * 0.95). Named because the button row's overflow cap is derived from it (see itemDetailsButtonCap()). If the logo moves and this stays behind, the cap silently stops describing the space it is meant to protect.
- Default Value
- 1824
- Source
(static, constant) LOGO_TEXT_CLEARANCE
The gap kept between the logo and any text beside or above it. Matches the block's own spacing between its title rows and the description.
- Default Value
- 12
- Source
Methods
(static) SetDefaultAudioTrack(mediaStreams) → {void}
SetDefaultAudioTrack: Auto-select the best audio stream based on user language prefs, storing the result on m.top.selectedAudioStreamIndex. The dropdown trigger label is refreshed separately by applyDropdown() using the same index.
| Name | Type | Description |
|---|---|---|
mediaStreams | object |
- Type:
- void
(static) activateExtras() → {void}
- Type:
- void
(static) anchorDateLabel() → {integer}
anchorDateLabel: Derive the date label's Y position from the button group's position. Returns dateLabelY so callers (for example logo positioning) can use it without duplicating the math. Safe to call multiple times (idempotent), and safe to call before the button group has finished laying out: falls back to the group's fixed translation + ESTIMATED_BUTTON_ROW_HEIGHT_PX so the label never lands off-screen. A later renderTracking callback refines the position once the real rendered height is known.
dateLabelY: the Y coordinate used for the date label
- Type:
- integer
(static) appendInfoRowItem(group, node, dividerIdPrefix) → {void}
The rows fitInfoRow() cuts are built only here, which is what lets it read a row as item, divider, item, ... by position: an item at every even index, a divider at every odd one. Append to those rows any other way and the fit misreads them.
| Name | Type | Description |
|---|---|---|
group | object | |
node | object | |
dividerIdPrefix | string |
- Type:
- void
(static) applyDropdown(dropdown, items, selectedId, applicable) → {void}
applyDropdown: Push items + selection into a single TrackDropdown. Hides the slot when the track type isn't applicable (keeps layout order stable for the other two slots).
| Name | Type | Description |
|---|---|---|
dropdown | object | |
items | object | |
selectedId | string | |
applicable | boolean |
- Type:
- void
(static) applyOverflow() → {void}
- Type:
- void
(static) applyVersionSelection(sourceId, item) → {void}
applyVersionSelection: Point the screen at a version: the field, its streams and the track dropdowns, the Video row included (populateTrackDropdowns rebuilds it with its labels).
Shared by the viewer's pick and the automatic one (syncVersionSelectionToChoice) so the two cannot drift: a version carries its own audio and subtitle streams, and leaving those behind is what hands playback a stream index belonging to a different file.
| Name | Type | Description |
|---|---|---|
sourceId | string | the version to select |
item | object | m.top.itemContent |
- Type:
- void
(static) buildAudioDropdownItems(streams) → {object}
buildAudioDropdownItems: Convert the MediaStreams array into dropdown items for audio. Item id is the stringified server-side stream index, matching the ItemDetails contract that selectedAudioStreamIndex is an integer spanning all stream types.
| Name | Type | Description |
|---|---|---|
streams | object |
- Type:
- object
(static) buildSubtitleDropdownItems(streams) → {object}
buildSubtitleDropdownItems: Subtitle rows for the track cluster.
Two shapes are returned depending on whether the item actually has subtitle streams: No subtitle streams at all: [{ id:"-1", title:"None" }]. Rendered as a static non-interactive slot so the "Subtitles" title row still shows and the layout stays consistent across items. "None" (not "Off") because the user has no choice to make: subtitles don't exist on this item. One or more subtitle streams: [{ id:"-1", title:"Off" }, ...tracks]. The "Off" entry lets the user disable subtitles when tracks ARE present. id "-1" is the SubtitleSelection.NONE sentinel used by m.top.selectedSubtitleStreamIndex.
Titles come from remoteSubtitles.subtitleDropdownEntries() rather than straight from formatSubtitleDisplayTitle(), because language + codec + flags does not separate two English SRTs. Measured on a 6-stream movie (2026-09-14), this dropdown offered "English SUBRIP" three times, so a freshly downloaded subtitle was impossible to pick out from the built-in ones. The helper marks the external ones (a download is one) and leaves the rest plain, which is the one question worth answering here.
Each item carries triggerTitle as well: the collapsed trigger is a narrow slot that already ellipsizes, and the marker is only meaningful beside the entries it distinguishes FROM, so the trigger shows the plain form.
| Name | Type | Description |
|---|---|---|
streams | object |
- Type:
- object
(static) buildVideoDropdownItems(mediaSources, inProgressSourceId) → {object}
buildVideoDropdownItems: Convert MediaSources into dropdown items. "Video" here is the MediaSource, not a stream: it's the user-facing alternate-version selector. Every version is offered, whatever its VideoType: the version selected here (findBestVideoSource, the in-progress version) can be any of them, and so can the one the player offers and names, so a narrower list would leave the trigger naming a version that is not selected and label versions differently from the player. Labels come from versionLabels; the version in progress is marked in the open menu only (triggerTitle stays plain).
| Name | Type | Description |
|---|---|---|
mediaSources | object | the item's MediaSources |
inProgressSourceId | dynamic | the version the viewer is partway through, or "" |
- Type:
- object
(static) canOfferManageSubtitles(item) → {boolean}
canOfferManageSubtitles: Everything but the plugin: a Movie or Episode, and a user the server would let search. canSearchSubtitles() carries the permission rule and the measurements behind it.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- boolean
(static) checkDeepLinkLaunch() → {void}
See the call site in onScreenShown. A deep-link route carries ?deeplink=
- Type:
- void
(static) checkDetailsLineBottoms() → {void}
Debug builds: warn when detailsLineBottoms() disagrees with where the block really laid out its lines, since the text is fitted against the computed positions.
- Type:
- void
(static) checkSubtitleProviders(itemId) → {void}
checkSubtitleProviders: Ask the server whether a subtitle provider is installed, and insert Manage Subtitles if so.
Reached only when Home's session check has not answered yet. A failed check fails OPEN (the button is offered and the answer stays unknown, so the next screen asks again). A transport error is not evidence the plugin is missing, and an empty search already explains itself.
| Name | Type | Description |
|---|---|---|
itemId | string |
- Type:
- void
(static) checkTrailerAvailability(itemId) → {void}
Fetches whether the item has local trailers, to add/remove the Trailer button (onTrailerAvailableChanged reacts to the result). Render-thread promise; replaces the former trailerResultNode + onTrailerCheckDone observer (Phase-3b: a render-thread submitApiRequest + observeField flow became fetchAsync().then(); canonical in docs/dev/promises.md).
| Name | Type | Description |
|---|---|---|
itemId | string |
- Type:
- void
(static) clearVersionResumePositions() → {void}
clearVersionResumePositions: Record that no version has a position any more. Marking any version watched marks every version played and clears every position on the server, so switching version afterwards must not bring Resume back from positions read earlier.
- Type:
- void
(static) closeMoreMenuBeforeRowChanges() → {void}
An open More menu describes a row that is about to change, so it closes with it. Cancelled HERE, at the top of the bracket, because at this instant nothing has detached yet: the More button is still in the row, so the dialog's returnFocusTo is still a live node and focus lands where the user left it. Cancelling after the re-derive would be a race against the very orphan this avoids.
No bookkeeping needed: cancelDialog resolves as externallyCancelled, and onMoreMenuResult already clears m.moreMenuDialog / m.overflowMenuIds and declines that result before it can act.
- Type:
- void
(static) closeSubtitlePanel() → {void}
closeSubtitlePanel: Lower the panel and give the buttons back the remote.
- Type:
- void
(static) createDividerNode(dividerId) → {object}
createDividerNode: Create a bullet divider node for separating info items
| Name | Type | Description |
|---|---|---|
dividerId | string | Unique ID for the divider |
Configured divider node
- Type:
- object
(static) createEndsAtNode() → {void}
createEndsAtNode: Build an "Ends at" label and store it on m.endsAtNode for dynamic clock updates. Used by types with runTimeTicks-based durations.
- Type:
- void
(static) createInfoLabel(labelId) → {object}
createInfoLabel: Create a bold label node for the info rows
| Name | Type | Description |
|---|---|---|
labelId | string | Unique ID for the label |
Configured LabelPrimaryMedium node
- Type:
- object
(static) createResumeButton(text, positionTicks, runtimeTicks) → {object}
createResumeButton: A Resume button, not yet placed in the row.
| Name | Type | Description |
|---|---|---|
text | string | the label ("Resume", or "Resume S2E3" for a Series) |
positionTicks | dynamic | where it resumes, for its progress bar |
runtimeTicks | dynamic | the runtime that progress bar is measured against |
the ResumeButton node
- Type:
- object
(static) createResumeLoadingButton(text) → {object}
createResumeLoadingButton: The loading placeholder that holds the Resume slot while its data loads, recorded in m.resumeLoadingButton. Not yet placed in the row.
| Name | Type | Description |
|---|---|---|
text | string | the label the real button is expected to carry |
the IconButton node
- Type:
- object
(static) createTimeLabel(labelId, timeText, periodText) → {object}
createTimeLabel: Creates a time label with an optional lowercase am/pm suffix. In 24h mode returns a single label; in 12h mode appends the period to the time text.
| Name | Type | Description |
|---|---|---|
labelId | string | |
timeText | string | |
periodText | string |
- Type:
- object
(static) deactivateExtras() → {void}
- Type:
- void
(static) deferDeepLinkLaunchUntilIdle() → {void}
Already-loaded view (an uncovered resume, or a same-path re-cast through onRouteUpdate): the content is there, but the hook runs MID-navigation, and the router rejects a navigateTo issued while a navigation is in progress (Router.brs _navigateTo). So wait for the navigation to settle (routerState -> NavigationEnd) before launching, otherwise startPlayFromDetail's navigateTo("/play") is swallowed (toast fires, but no player). Fresh mounts don't need this: they launch from onDetailsLoaded, which is already async / router-idle.
- Type:
- void
(static) detailsLineBottoms() → {object}
Where each line of the text block will have its bottom edge once the block lays out, in screen space, at the block's resting position. Computed, not read: before the block's first layout its rows' boundingRect() reports nothing usable (measured 2026-09-22), and this has to run before the text first paints. The block is bottom-aligned; under the rows sit the description and the track spacer when each is in the layout, with the block's itemSpacings between them, and the rows stack with no spacing. A debug build checks this against the real layout in onItemDetailsRendered().
- Type:
- object
(static) detailsLineWidth(bottomY) → {integer}
The width a line of text may use when its bottom edge sits at bottomY. Until the logo's place is known, it is taken to be the widest logo possible reaching the top of the screen, so every line stops short of the logo's leftmost POSSIBLE edge, which is safe for any logo. The left edge is read from the live translation, like the button row's geometry, so moving the block moves the budget with it.
| Name | Type | Description |
|---|---|---|
bottomY | integer |
- Type:
- integer
(static) dispatchDeepLinkAction() → {void}
Do exactly what the route's action asked, on the validated item. The action is the SENDER'S explicit intent, never inferred from the resolved item: open (and any unknown action) -> stay on the springboard (this view). Grid-container targets (library/folder/genre/studio/channel) never reach here: JRScene.onDeepLinkResolved diverts them to their grid. play -> QueueManager.launchItem (the DRY per-type play engine: video resumes its bookmark, Series smart-resumes, containers play, live watches, audio -> audio player, photo -> viewer). shuffle -> the same path the Shuffle button uses (onShuffleButtonPressed). trailer -> play this item's trailer(s). instantmix -> build an instant mix from the item (mirrors the instantMixButton).
- Type:
- void
(static) dispatchDetailButtonAction(buttonId) → {void}
Run the action for a button ID. Reached two ways (a press on a visible button, and a row picked out of the "More" menu), so the overflow menu adds no second copy of this mapping.
Detail-to-detail nav (goToSeries / goToAlbum / goToArtist / goToChannel) navigates the router; playback buttons launch through the QueueManager / the play route; the rest run their render-thread action directly.
| Name | Type | Description |
|---|---|---|
buttonId | string |
- Type:
- void
(static) displayCreditsNode(node) → {void}
displayCreditsNode: Add a people credit ("Directed by ...") to the credits row
| Name | Type | Description |
|---|---|---|
node | object |
- Type:
- void
(static) displayDetailsNode(node) → {void}
displayDetailsNode: Add a node to the second info row, prepending a bullet divider if needed
| Name | Type | Description |
|---|---|---|
node | object |
- Type:
- void
(static) displayInfoNode(node) → {void}
displayInfoNode: Add a node to the info group, prepending a bullet divider if needed
| Name | Type | Description |
|---|---|---|
node | object |
- Type:
- void
(static) displayPeopleCredits(item) → {void}
The credits row, for ANY item type: whichever credits the item's own People carry, in the order creditRowKinds() lists them.
Driven by the item's own People rather than by its type, so a type the server starts crediting later needs no change here, and a type it doesn't credit shows nothing: 10.11 sends no Creator credits at all (12.0+ adds them to Series), and a Season carries Director credits on every version. Nothing is looked up from a parent: a Season or Episode shows only its own credits.
WHICH credits appear, in WHAT order, and how they read is people.bs's to own, beside the other PersonKind tables; this owns only the nodes. Same split as castCardSpecs() and transformPersonCard(). item.people is read exactly ONCE and handed over: it is a node array field, so every read copies the whole array. The row is fitted to the logo by fitInfoRow(), which drops a later credit rather than let it reach the logo.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) displayRatingNodes(userSettings, communityRating, criticRating) → {void}
displayRatingNodes: Community + critic rating chips shared across every rateable item type's Row 1. Both are gated behind uiMoviesShowRatings and only render when the value is present (> 0), so it's safe to call for any type: types without a rating from the server show nothing. Values are passed explicitly (rather than reading item.* directly) so callers that need a parent-series fallback (Season) can resolve the value first.
| Name | Type | Description |
|---|---|---|
userSettings | object | |
communityRating | float | |
criticRating | float |
- Type:
- void
(static) endsAtDate() → {object}
endsAtDate: Local time at which playback would finish if started right now.
- Type:
- object
(static) findSelectedSource(mediaSources) → {dynamic}
findSelectedSource: The MediaSource AA getStreamsForSelectedSource reads from, or invalid. Only a source carrying MediaStreams qualifies, including the fallback. The id is matched ignoring case, as playback start matches it (mediaSourceIndexForId).
| Name | Type | Description |
|---|---|---|
mediaSources | object |
- Type:
- dynamic
(static) fitInfoRow(group) → {void}
A LayoutGroup never clips, so an over-long row would run on into the logo. The item that crosses the budget is narrowed so the Label ellipsizes it, and what follows it is removed; see fitTextRow() for the rule. The row's own boundingRect() is not used: until the group has laid out it does not include its children's positions, so the widths are summed from the children instead.
Only a Label can be narrowed. A rating chip (an icon plus a label) that crosses the budget is dropped with the rest instead.
The cut is by character, not ellipsizeOnBoundary: on a Label that does not wrap, a single word too wide for the space renders as a bare "..." (#798).
| Name | Type | Description |
|---|---|---|
group | object |
- Type:
- void
(static) fitInfoRows() → {void}
Cut the info rows to the space left of the logo. Runs after every rebuild, before the rows first paint: a Label's width is correct the moment its text is set. Row 1 has text that changes after the rebuild ("Ends at" each minute, a Playlist's item count), so those writes fit it again; a second fit of an already fitted row changes nothing.
- Type:
- void
(static) focusButtonGroupChild() → {void}
focusButtonGroupChild: Focus the button at the current buttonFocused index directly
- Type:
- void
(static) focusInteractiveDropdown(candidates) → {void}
focusInteractiveDropdown: Walks the given candidate list and focuses the first dropdown that is interactive AND visible. The candidate order encodes both direction and wrap behavior; the currently-focused dropdown is always last so the nav falls back to it if nothing else is interactive (effectively: stay put).
| Name | Type | Description |
|---|---|---|
candidates | object |
- Type:
- void
(static) formatEndsAtText(date) → {string}
formatEndsAtText: The one place the "Ends at" label text is built. Both the initial build and the per-minute refresh below go through here. They used to format independently, which meant any change to the rule had to be made twice or the label would render one way on open and another at the first clock tick.
formatDisambiguatedTime() owns the contextual am/pm rule and is shared with the playback OSD's own "Ends at", so one end time reads the same way on both screens.
| Name | Type | Description |
|---|---|---|
date | object |
- Type:
- string
(static) getButtonIndex(buttonId) → {integer}
getButtonIndex: Find the index of a button by ID in the button group
| Name | Type | Description |
|---|---|---|
buttonId | string | The id of the button to find |
The index of the button, or -1 if not found
- Type:
- integer
(static) getHistory() → {string}
getHistory: Format the air schedule/status string for a Series item Example output: "ABC" or "Fridays at 9:30 PM on NBC"
- Type:
- string
(static) getResumeButtonText(item) → {string}
getResumeButtonText: Return "Resume S{n}E{n}" when season and episode numbers are known, otherwise fall back to plain "Resume".
| Name | Type | Description |
|---|---|---|
item | object | nextUpEpisode JellyfinBaseItem node |
Localised button label
- Type:
- string
(static) getRuntime() → {integer}
- Type:
- integer
(static) getStreamsForSelectedSource(mediaSources) → {object}
getStreamsForSelectedSource: Pull the MediaStreams array from whichever media source matches m.top.selectedVideoStreamId. Falls back to mediaSources[0] when the selected id isn't found (for example, first load before selection).
| Name | Type | Description |
|---|---|---|
mediaSources | object |
- Type:
- object
(static) holdDetailsText() → {void}
Keep the text hidden until the logo's place is known, so it paints once, already fitted, instead of moving when the logo arrives. Only the text is held: the buttons, backdrop and logo paint as usual. textHoldTimer caps the wait.
- Type:
- void
(static) inProgressVersionId() → {string}
inProgressVersionId: The version to mark as in progress in the Video dropdown, or "". Only a per-version server (m.versionResume set) keeps a position per version.
- Type:
- string
(static) init() → {void}
- Source
- Type:
- void
(static) insertButtonAtFront(button) → {void}
insertButtonAtFront: Put a Resume-slot button at the front of the row. It takes focus from Play, as a Resume button leads the row; any other focused button keeps focus, with its index shifted past the insert.
Real focus moves only when the row already owns it. An async insert must not steal focus from the extras row, for example after setupButtons() cleared and rebuilt the row.
| Name | Type | Description |
|---|---|---|
button | object | createResumeButton() or createResumeLoadingButton() |
- Type:
- void
(static) insertManageSubtitlesButton() → {void}
insertManageSubtitlesButton: Add the button to a row that is already built.
In setupButtons() order: after Trailer, before Delete / Refresh. Inside the unsplit/applyOverflow bracket, which keeps focus on the button the user is on when this lands to its left (see captureRowFocus() in buttonOverflow.bs).
- Type:
- void
(static) isPlayAllType(itemType) → {boolean}
True for item types whose Play builds a multi-item queue via an API call (handled by QuickPlayTask, not the single-item path below).
| Name | Type | Description |
|---|---|---|
itemType | dynamic |
- Type:
- boolean
(static) itemDetailsButtonCap() → {integer}
The cap for THIS row: it may not reach the logo's leftmost possible edge.
- Type:
- integer
(static) itemDetailsRowGeometry() → {object}
The row's geometry, read from the live nodes rather than restated (so a change to the group's translation or spacing is picked up instead of drifting), and read in one place, so the cap and the overflow menu's anchor can never be derived from different numbers.
translation is taken as a SCREEN coordinate, which it is: buttonGrp is a direct child of this screen's root and the screen is mounted at the origin, the same thing updateItemDetailsAnimationTarget() already relies on when it compares a derived y against extrasLayout's absolute constants.
- Type:
- object
(static) itemOriginalLanguage() → {string}
The shown item's OriginalLanguage (12.0+), for the "Original language" audio preference.
- Type:
- string
(static) launchQueueItemToPlay(queueItem, routeType, routeId, versionPreferenceopt) → {void}
Seed the queue with a single item and navigate to the play route. The router is idle (the user pressed a button on this settled view), so navigateTo won't hit the in-progress guard; PlayerHostView reads this queue on mount. :type/:id give the route a deep-link identity; the queue is the source of truth for what actually plays.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
queueItem | object | |||
routeType | string | |||
routeId | string | |||
versionPreference | dynamic | <optional> | invalid | an explicit version pick for the queue (versionPick), or invalid |
- Type:
- void
(static) layoutDetailsText() → {void}
Fit the title, the description and all three info rows to the space the logo leaves. The rows must be freshly built (fitting only ever cuts); rebuildDetailsText() does both.
- Type:
- void
(static) loadFromRoute(route) → {void}
Set itemType + itemId from a route's params. Setting itemId fires onItemIdChanged (the async loader). itemType is set first since the loader reads it.
| Name | Type | Description |
|---|---|---|
route | object |
- Type:
- void
(static) logoBoxFor(bitmapWidth, bitmapHeight) → {object}
Where the logo is drawn for a bitmap of this size: right-aligned to LOGO_RIGHT_ANCHOR_X, its bottom 18px above the date label.
| Name | Type | Description |
|---|---|---|
bitmapWidth | integer | |
bitmapHeight | integer |
- Type:
- object
(static) logoBoxIntrudes(newBox, oldBox) → {boolean}
Whether a logo at newBox reaches into space text fitted to oldBox may be using.
| Name | Type | Description |
|---|---|---|
newBox | object | |
oldBox | object |
- Type:
- boolean
(static) logoLeftmostX() → {integer}
The x where the logo's LEFTMOST possible edge sits: the right anchor less the widest the logo may be drawn. Everything left of the logo stops here, for any logo, so no measurement of the loaded image (which arrives after the text paints) is needed.
- Type:
- integer
(static) manageResumeButton() → {void}
manageResumeButton: Add, update or remove the Resume button from the selected version's resume position (non-Series types). While that position is still loading, the Resume slot shows a loading button, the same placeholder Series uses for its next-up fetch.
- Type:
- void
(static) newManageSubtitlesButton() → {object}
- Type:
- object
(static) onAudioDropdownFocusExit() → {void}
- Type:
- void
(static) onAudioDropdownSelection() → {void}
Audio dropdown selection: updates selectedAudioStreamIndex and re-seeds the subtitle auto-selection (Smart mode depends on which audio is playing). If the user has already manually overridden the subtitle, we leave it alone.
- Type:
- void
(static) onButtonGrpRendered() → {void}
onButtonGrpRendered: Fires after the render thread settles the button group layout. Re-anchors the date label and repositions the logo. The track dropdown cluster lives inside itemDetails LayoutGroup and is positioned automatically by the layout engine, so it doesn't need to be re-placed here.
- Type:
- void
(static) onClockMinuteChanged() → {void}
onClockMinuteChanged: Dynamically update "Ends At" time when overhang clock minute changes.
- Type:
- void
(static) onDeepLinkRouterIdle() → {void}
- Type:
- void
(static) onDeleteButtonPressed(item) → {void}
Delete button. Confirm, then delete and leave the now-gone routed detail via the router. Standard showConfirmDialog helper (see onWatchedButtonPressed).
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) onDeleteConfirmResponse() → {void}
- Type:
- void
(static) onDestroy() → {void}
onDestroy: Full teardown releasing all resources before component removal Called automatically via JRScreen.beforeViewClose when sgRouter permanently closes this view.
- Type:
- void
(static) onDetailNavButton(msg) → {void}
Single handler for every button on this detail's button group.
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onDetailsLoaded(event) → {void}
Callback when initial item details metadata arrives from the task
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onDropdownRequestDown() → {void}
DOWN from any dropdown returns focus to the button group.
- Type:
- void
(static) onDropdownRequestUp() → {void}
Dropdown navigation: UP from any dropdown goes to description when in layout, else is consumed (no-op). parent-level UP handling reused so we don't have three identical handlers.
- Type:
- void
(static) onExtrasContentReady() → {void}
onExtrasContentReady: the extras chain has run to its end. Close the ledger's extras fill.
Reads the VALUE rather than treating the notification as the signal: the field is reset to false at the start of every chain, so half the callbacks here are the reset rather than the completion. (This is why it is not declared alwaysNotify: an alwaysNotify field would wake this on every re-set of false as well.)
- Type:
- void
(static) onExtrasItemSelected(msg) → {void}
Extras grid (cast / related / next-up) selection: navigate to its route, passing the rich node as context. Non-navigable items fall through to playback (quickPlayNode).
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onExtrasTargetTranslationYChanged() → {void}
onExtrasTargetTranslationYChanged: Animate the RowList translation to the new target. Fired early by onKeyEvent in ExtrasRowList (at key press) so the animation runs in parallel with the RowList's floatingFocus animation, and confirmed by onRowItemFocused after focus lands.
- Type:
- void
(static) onFavoriteToggleFailed(favoriteButton, attemptedState) → {void}
Reverts the favorite button to its pre-press state and surfaces an error toast.
| Name | Type | Description |
|---|---|---|
favoriteButton | object | |
attemptedState | boolean |
- Type:
- void
(static) onItemContentChanged() → {void}
- Type:
- void
(static) onItemDetailsRendered() → {void}
- Type:
- void
(static) onItemIdChanged() → {void}
Triggered when itemId is set (route load): kicks off async data load
- Type:
- void
(static) onKeyEvent(key, press) → {boolean}
| Name | Type | Description |
|---|---|---|
key | string | |
press | boolean |
- Type:
- boolean
(static) onLogoLoadStatusChanged() → {void}
onLogoLoadStatusChanged: Position logo to the right, with its bottom a little above the date label. When the date label is hidden, anchors to where the date label would be. Images smaller than LOGO_MIN_DISPLAY_HEIGHT are scaled up while preserving aspect ratio, unless the logo is very wide/flat, in which case LOGO_MAX_DISPLAY_WIDTH takes precedence.
- Type:
- void
(static) onLyricsLoaded(event) → {void}
onLyricsLoaded: Show lyrics in the description area once the async fetch completes (Audio only)
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onMoreMenuResult() → {void}
- Type:
- void
(static) onMostRecentVersionLoaded(ctx, mostRecentId) → {void}
onMostRecentVersionLoaded: Record the server's answer and re-run the choice, unless the content has been reloaded since the request started.
| Name | Type | Description |
|---|---|---|
ctx | object | |
mostRecentId | string |
- Type:
- void
(static) onNextUpEpisodeChanged() → {void}
nextUpEpisodeChanged: For Series, create/remove Resume button based on nextUpEpisode. On first load, replaces the LoadingButton placeholder from setupButtons(). On refresh, inserts/updates/removes the Resume button directly.
- Type:
- void
(static) onPersonHasMediaChanged() → {void}
onPersonHasMediaChanged: Show Shuffle as soon as the person media chain finds media.
Only a TRUE is acted on here. Every run starts by resetting the field to false, so a false on its own is not an answer: acting on it removed Shuffle on every Refresh. The run's "no media" answer is read when the run finishes (onExtrasContentReady). alwaysNotify, so the run's final true is delivered even when an early true already was.
- Type:
- void
(static) onPlaylistContentKindChanged() → {void}
onPlaylistContentKindChanged: Drives the Watched button and item-count label based on the confirmed content kind of the playlist ("video" / "audio" / "mixed" / "unknown"). Fires via alwaysNotify so it triggers even when the kind is unchanged (for example on refresh).
Watched button: only shown for "video" (has at least one video-type item). Label: "Tracks" only when positively confirmed "audio" (all items are Audio type). All other kinds, including "unknown" (load failure) and "mixed", stay "Items".
- Type:
- void
(static) onQuickPlayLaunch(msg) → {void}
Forward a non-navigable extras selection to the playback launcher.
| Name | Type | Description |
|---|---|---|
msg | dynamic |
- Type:
- void
(static) onRecordButtonPressed(item) → {void}
Record / cancel-record a Live TV program. Owns the RecordProgramTask on this render thread.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) onRecordResult() → {void}
- Type:
- void
(static) onRefreshDetailsLoaded(event) → {void}
Callback when refreshed item details metadata arrives
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onRefreshExtrasData() → {void}
onRefreshExtrasData: Reload all extras rows when the user explicitly presses Refresh.
- Type:
- void
(static) onRefreshItemDetailsData() → {void}
Triggered by refreshItemDetailsData field toggle (return from playback, refresh button). Refreshes silently in the background: no spinner, no input blocking. The UI already has data from the initial load so the user can navigate freely.
- Type:
- void
(static) onRefreshResumeData() → {void}
Triggered by refreshResumeData field toggle (watched toggle on Series). Re-fetches only the next-up/resume episode: no full rebuild, no setupButtons(). The existing onNextUpEpisodeChanged() handles adding/removing/updating the resume button.
- Type:
- void
(static) onRouteUpdate(_eventopt) → {dynamic}
The router reuses this view (no remount) when navigating to the SAME details path it's already showing, for example a cast of the item you're already on, after a player above it was popped. m.top.route is the new route by now, so re-run the deep-link check (its route.id dedup makes it a one-shot per navigation). Non-deep-link same-path updates are no-ops.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
_event | object | <optional> | {} |
- Type:
- dynamic
(static) onScreenHidden() → {void}
- Source
- Type:
- void
(static) onScreenShown() → {void}
onScreenShown: Callback when view is presented on screen
- Source
- Type:
- void
(static) onSeasonSeriesDataLoaded(event) → {void}
onSeasonSeriesDataLoaded: Update Season info rows and logo with parent series metadata
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onSeriesResumeLoaded(event) → {void}
onSeriesResumeLoaded: Set nextUpEpisode from seriesResume task result to show/hide the Resume button
| Name | Type | Description |
|---|---|---|
event | object |
- Type:
- void
(static) onServerResumePolicyLoaded(ctx, policy) → {void}
onServerResumePolicyLoaded: Store read thresholds for the session and redraw Resume.
| Name | Type | Description |
|---|---|---|
ctx | object | |
policy | dynamic |
- Type:
- void
(static) onShuffleButtonPressed(item) → {void}
Shuffle button. Routes by item type so new types can be added. PhotoAlbum launches a random slideshow; everything else resolves a randomized item set via GetShuffleItemsTask on the QueueManager.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) onSubtitleDropdownFocusExit() → {void}
- Type:
- void
(static) onSubtitleDropdownSelection() → {void}
Subtitle dropdown selection: marks user override so subsequent audio changes don't clobber the pick, then stores the new index. Action "-1" = Off.
- Type:
- void
(static) onSubtitlePanelRequestClose() → {void}
- Type:
- void
(static) onSubtitlesChanged() → {void}
onSubtitlesChanged: The server's subtitle set for this item changed.
This is not cosmetic. An added subtitle is inserted at stream index 0 and renumbers every other stream, so selectedAudioStreamIndex / selectedSubtitleStreamIndex (which are server stream indices, and which get handed to playback) are now pointing at the wrong tracks. Re-fetching the item is what makes them right again, and it also repopulates the subtitle dropdown so the newly added track is selectable the moment the panel closes.
- Type:
- void
(static) onTextHoldTimeout() → {void}
The logo's size has not arrived in time. The text is already fitted to the widest logo possible, so show it as it is; it is not refitted when the logo does arrive.
- Type:
- void
(static) onTrackDropdownFocusChanged() → {void}
onTrackDropdownFocusChanged: Observer wired to each TrackDropdown's focusedChild. Remembers the last dropdown that held focus so the UP-from-buttons handler can restore the previous slot on re-entry instead of always defaulting to video.
- Type:
- void
(static) onTrackDropdownMenuOpenChanged() → {void}
onTrackDropdownMenuOpenChanged: Observer wired to each TrackDropdown's menuOpen. Toggles the full-screen dimmer iff ANY dropdown's menu is currently open. The trackCluster (titles + triggers + the open menu itself) renders ABOVE the dimmer in XML sibling order, so only the screen content BEHIND the cluster dims: category titles and trigger labels stay full-brightness as the user's "you are here" cue while picking a track.
- Type:
- void
(static) onTrailerAvailableChanged() → {void}
- Type:
- void
(static) onVersionPositionLoaded(ctx, ticks) → {void}
onVersionPositionLoaded: Record a fetched version position and redraw Resume, unless the content has been reloaded since the fetch started.
| Name | Type | Description |
|---|---|---|
ctx | object | |
ticks | dynamic |
- Type:
- void
(static) onVideoDropdownFocusExit() → {void}
LEFT/RIGHT between dropdowns: each dropdown has its own exit handler so we know which slot we came from and can target the correct neighbor, skipping non-interactive slots.
- Type:
- void
(static) onVideoDropdownSelection() → {void}
Video dropdown selection fires when the user picks a different MediaSource (alternate version). We swap in the new source's streams, re-run audio auto-select, and rebuild the audio/subtitle dropdowns against the new source. Focus stays on the video dropdown.
- Type:
- void
(static) onWatchChannelButtonPressed(item) → {void}
Watch a Live TV channel (TvChannel detail, or the channel a Program airs on): single-item launch via the play route.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) onWatchedButtonPressed(item) → {void}
Watched button. Movie/Episode toggle immediately; a Series confirms first (marking all episodes can affect hundreds). Uses the standard showConfirmDialog helper: the result arrives on the dialog node's own result field (no shared SceneManager state). On confirm we call toggleWatched(), which re-reads the button state to pick mark vs unmark.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) onWatchedConfirmResponse() → {void}
Series mark-all-watched confirmation result.
- Type:
- void
(static) onWatchedToggleFailed(watchedButton, attemptedState) → {void}
Reverts the watched button to its pre-press state, clears the resume button's loading state, and surfaces an error toast.
| Name | Type | Description |
|---|---|---|
watchedButton | object | |
attemptedState | boolean |
- Type:
- void
(static) openMoreMenu() → {void}
Open the overflow menu. The rows carry the same label and glyph the buttons were wearing, and resolve back to a button ID so the selection runs through onDetailNavButton's dispatch rather than a second copy of it.
- Type:
- void
(static) openSubtitlePanel(item) → {void}
openSubtitlePanel: Hand the panel everything it needs and raise it.
The panel targets the SELECTED VERSION, not the item. Jellyfin builds each MediaSource from its own item (source Id = that version's item id), and the subtitle endpoints act on the item in the URL: delete resolves the stream index on it and removes that stream's file, download saves beside its video. So the id, the path and the stream list must all come from the same source: an index from one version sent to another version's id can delete that version's media file. A single-version item is unaffected: its only source's Id is the item id.
Everything comes from data we already hold, so opening costs no round-trip. canDelete is resolved HERE rather than inside the panel so the panel stays free of policy knowledge (delete is admin-only on every server version).
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) pickTrackDropdownFromButtons() → {object}
pickTrackDropdownFromButtons: Chooses which TrackDropdown to focus when UP is pressed from the button group. Ignores m.lastFocusedDropdown by design: UP-from-buttons should always land on the dropdown visually above the currently focused button, not on wherever the user was last time. Each slot spans 2 buttons: slot 1 sits above buttons [0,1], slot 2 above [2,3], slot 3 above [4,5,...]. Widens outward to the nearest interactive slot if the preferred one is static/hidden.
CRITICAL: guards on m.trackCluster.visible. When the item type has no media tracks (Series/Person/BoxSet/etc.), the cluster is hidden but the individual dropdown nodes may still carry stale visible=true / isInteractive=true from a previously-loaded item. Without this guard, focus would move to an invisible stale dropdown when the user presses UP on those item types.
- Type:
- object
(static) pickTrackDropdownFromDescription() → {object}
pickTrackDropdownFromDescription: Chooses which TrackDropdown to focus when DOWN is pressed from the description. Restores m.lastFocusedDropdown if still usable so the user returns to wherever they were before moving up. Falls back to the leftmost interactive dropdown (video, then audio, then subtitles) for the first-ever entry. Also guards on m.trackCluster.visible to match pickTrackDropdownFromButtons above.
- Type:
- object
(static) playFromDeepLink(actionopt) → {void}
Run the deep-link action on this already-loaded item in response to a runtime cast that landed while the user is already on its details (replayRoute.replayDeepLinkRuntime): no navigation, so no Home flash and no re-mount. The router is idle here, so any navigateTo the action triggers succeeds directly (no NavigationEnd defer needed). action is the new cast's intent (the cached view's m.deepLinkAction is from its original mount and may be stale).
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
action | string | <optional> | "play" |
- Type:
- void
(static) populateDescriptionGroup() → {void}
populateDescriptionGroup: Set FocusableOverview text with tagline and overview
- Type:
- void
(static) populateInfoGroup() → {void}
populateInfoGroup: Dispatch to type-specific info row builder
- Type:
- void
(static) populateInfoGroupAudio(item, userSettings) → {void}
populateInfoGroupAudio: Info rows for Audio (song) Row 1: Track N · Disc N (only when disc > 1) · Runtime · Ends At Row 2: Album name · Album artist
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupBoxSet(item, userSettings) → {void}
populateInfoGroupBoxSet: Info rows for BoxSet (movie collection) Row 1: Year · Official Rating · Community Rating · Critic Rating · N Movies Row 2: Genres · Studio
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupEpisode(item, userSettings) → {void}
populateInfoGroupEpisode: Info rows for Episode Row 1: Air Date · Official Rating · Community Rating · Critic Rating · Runtime · Ends At Row 2: "S{n}E{n}" · Series name Row 3: credits (displayPeopleCredits)
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupMovie(item, userSettings) → {void}
populateInfoGroupMovie: Info rows for Movie, Video, Recording Row 1: Year · Official Rating · Community Rating · Critic Rating · Runtime · Ends At Row 2: Genres Row 3: credits (displayPeopleCredits)
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupMusicAlbum(item, userSettings) → {void}
populateInfoGroupMusicAlbum: Info rows for MusicAlbum Row 1: Year · Runtime · Ends At Row 2: Album Artist · Genres · N Tracks
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupMusicArtist(item) → {void}
populateInfoGroupMusicArtist: Info rows for MusicArtist Row 1: N Albums · N Songs · Year Row 2: Genres
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) populateInfoGroupMusicVideo(item, userSettings) → {void}
populateInfoGroupMusicVideo: Info rows for MusicVideo Row 1: Year · Official Rating · Community Rating · Critic Rating · Runtime · Ends At Row 2: Genres · Artist(s) Row 3: credits (displayPeopleCredits)
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupPerson(item) → {void}
populateInfoGroupPerson: Info rows for Person Row 1: {birthDate} [- {deathDate}] · {n} years old Living: Jan 1, 1980 · 45 years old Deceased: Jan 1, 1920 - Dec 31, 1980 · 60 years old Row 2: {n} Movie(s) [· {n} Episode(s)], omitted entirely if both counts are 0
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) populateInfoGroupPhoto(item) → {void}
populateInfoGroupPhoto: Info rows for Photo Row 1: Resolution ("WxH") · Camera ("Make Model") · Production Year Row 2: Album name (if present)
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) populateInfoGroupPhotoAlbum(item) → {void}
populateInfoGroupPhotoAlbum: Info rows for PhotoAlbum Row 1: Photo count
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) populateInfoGroupPlaylist(item, userSettings) → {void}
populateInfoGroupPlaylist: Info rows for Playlist Row 1: N Items · Runtime · Ends At Row 2: Genres The item count label starts as "Items" and is swapped to "Tracks" by onPlaylistContentKindChanged() once the extras chain confirms the playlist is all-audio.
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupProgram(item, userSettings) → {void}
populateInfoGroupProgram: Info rows for Program (Live TV EPG entry) Row 1: Year · CH X - ChanName · OfficialRating · CommunityRating · CriticRating · S#E# · Premiere/Repeat · Runtime · BroadcastTime Row 2: Genres (or category flags)
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupRecording(item, userSettings) → {void}
populateInfoGroupRecording: Info rows for Recording (Live TV recorded content) Row 1: PremiereYear · OfficialRating · CommunityRating · CriticRating · S#E# · Runtime · Ends At · CH X - ChanName · Recorded: Date Row 2: Genres
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupSeason(item, userSettings) → {void}
populateInfoGroupSeason: Info rows for Season Row 1: Year · Official Rating (series) · Community Rating (series) · Critic Rating (series) · Avg episode runtime (series) · Ends At Row 2: Series name · Episode count · Studio
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupSeries(item, userSettings) → {void}
populateInfoGroupSeries: Info rows for Series Row 1: Year Range · Official Rating · Community Rating · Critic Rating · Runtime · Ends At (only when avg episode runtime is available) Row 2: Genres · Air schedule/Status
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateInfoGroupTvChannel(item, userSettings) → {void}
populateInfoGroupTvChannel: Info rows for TvChannel (Live TV) Displays metadata from the current program (the channel is a vessel for what's airing). Row 1: CH N · OfficialRating · CommunityRating · CriticRating · S#E# · Program Name · Runtime · BroadcastTime Row 2: Genres (or category flags)
| Name | Type | Description |
|---|---|---|
item | object | |
userSettings | object |
- Type:
- void
(static) populateTrackDropdowns(mediaSources, allStreams, itemType) → {void}
populateTrackDropdowns: Build the Video/Audio/Subtitle dropdown items from the current media source list and stream list for the given item type, then toggle the cluster + title row visibility. Seeds m.top.selectedSubtitleStreamIndex from findDefaultSubtitleStreamIndex() on first load so the initial trigger label reflects what playback will actually use.
| Name | Type | Description |
|---|---|---|
mediaSources | object | Array of MediaSource objects (may be empty) |
allStreams | object | MediaStreams from the selected source (video/audio/subs mixed) |
itemType | string | The item type string (drives which slots apply) |
- Type:
- void
(static) rebuildDetailsText() → {void}
Build the info rows again and fit them, for when what they are fitted to changes.
- Type:
- void
(static) refreshVideoDropdownLabels() → {void}
refreshVideoDropdownLabels: Rebuild the Video dropdown's labels after the in-progress version changed, so its marker does not outlive the position it stood for.
- Type:
- void
(static) removeResumeButtonWithFocus(resumeButton) → {void}
removeResumeButtonWithFocus: Remove resume button while preserving focus position
| Name | Type | Description |
|---|---|---|
resumeButton | object |
- Type:
- void
(static) removeResumeLoadingButton() → {void}
removeResumeLoadingButton: Remove the resume LoadingButton placeholder if still present. Called when async data confirms no resume episode exists (nextUpEpisode stays invalid).
- Type:
- void
(static) requestLogoSize(itemId, imageType, maxWidth, maxHeight, uri) → {void}
The size the server will send the logo at, from the original size it reports, drawn through the same logoBoxFor() the loaded bitmap uses. No answer, or a size of 0 (the server has none recorded), leaves the text waiting on the image itself.
| Name | Type | Description |
|---|---|---|
itemId | string | |
imageType | string | |
maxWidth | integer | |
maxHeight | integer | |
uri | string |
- Type:
- void
(static) requestMostRecentVersion(item, mediaSources) → {void}
requestMostRecentVersion: Ask the server which version it would resume, once per content load.
The source order answers that only from a primary; this screen can be showing an alternate (see versionResume.chooseResumeSource). Scoped to the item's parent and lean, as in LoadVideoContentTask.fetchMostRecentVersionId, so both shells ask the same question.
| Name | Type | Description |
|---|---|---|
item | object | m.top.itemContent |
mediaSources | object | its MediaSources |
- Type:
- void
(static) requestServerResumePolicy() → {boolean}
requestServerResumePolicy: Read GET /System/Configuration the first time a carry needs it.
Fails CLOSED: while the thresholds are unknown wouldMarkPlayed() answers "played", so nothing is carried: a carried position past a threshold would mark the item played and erase the place being resumed from. A failed read is remembered only for this content load, not on the server node, so the next one asks again.
failed for this content load, or its request could not be built
true while a reply is on its way; false when the read has already
- Type:
- boolean
(static) requestVersionPosition(sourceId) → {boolean}
requestVersionPosition: Fetch one version's own position, once per content load.
built, in which case the position is already recorded as unreadable
| Name | Type | Description |
|---|---|---|
sourceId | string | the version (MediaSource) id |
true while a reply is on its way; false when the request could not be
- Type:
- boolean
(static) resetInfoRows() → {void}
Empty all three info rows before a rebuild.
- Type:
- void
(static) resetLogoBox() → {void}
- Type:
- void
(static) resetVersionResume(item, mediaSources) → {void}
resetVersionResume: Start per-version resume state for newly loaded content. Leaves m.versionResume invalid ("use the item's own position", as before 12.0) unless the server keeps positions per version and the item has alternate versions.
| Name | Type | Description |
|---|---|---|
item | object | the loaded JellyfinBaseItem |
mediaSources | dynamic | its MediaSources, in server order |
- Type:
- void
(static) resolveGoToArtistId(item) → {string}
Resolve the artist id for goToArtist: prefer the primary album artist, fall back to the single artist item.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- string
(static) resolveResumeState() → {object}
resolveResumeState: Where Resume starts for the selected version, starting any fetch that answer still needs.
{ ticks, runtimeTicks, isLoading }; ticks 0 means no Resume
- Type:
- object
(static) resumeSeriesNextUp() → {void}
Series Resume plays the next-up episode (a single item) from its saved position, via the play route.
- Type:
- void
(static) revealDetailsText() → {void}
- Type:
- void
(static) round(f) → {integer}
| Name | Type | Description |
|---|---|---|
f | float |
- Type:
- integer
(static) rowHeight(node) → {integer}
A row's height before layout: its tallest child for a horizontal group, else its own.
| Name | Type | Description |
|---|---|---|
node | object |
- Type:
- integer
(static) selectedMediaSource() → {dynamic}
selectedMediaSource: The currently selected MediaSource AA, or invalid. Uses the same resolution as the track dropdowns (findSelectedSource), so the panel and the dropdowns can never disagree about which version is in play.
- Type:
- dynamic
(static) seriesResumeLoadingText() → {string}
seriesResumeLoadingText: The Series placeholder label: the episode-specific text (for example "Resume S2E3") from prior data when there is some, so the label holds steady through a reload, otherwise plain "Resume".
- Type:
- string
(static) serverResumePolicy() → {dynamic}
serverResumePolicy: The server's resume thresholds, if they have been read.
{ maxResumePct, minResumeDurationSeconds }, or invalid when unknown
- Type:
- dynamic
(static) setDateAdded(item) → {void}
setDateAdded: Set date added label text and position at bottom right corner
| Name | Type | Description |
|---|---|---|
item | object | JellyfinBaseItem node |
- Type:
- void
(static) setDescriptionInLayout(shouldShow) → {void}
setDescriptionInLayout: Add or remove itemDescription from the itemDetails LayoutGroup. Invisible nodes still take up space in RSG LayoutGroups, so reparenting is required to truly eliminate spacing when there is no description text.
| Name | Type | Description |
|---|---|---|
shouldShow | boolean |
- Type:
- void
(static) setFieldText(field, value) → {void}
| Name | Type | Description |
|---|---|---|
field | dynamic | |
value | dynamic |
- Type:
- void
(static) setItemLogo(item) → {void}
setItemLogo: Set the item logo image URL if available. Fallback chains by type. Every chain ends in a typed placeholder via getPlaceholderImagePath() so the logo slot never goes blank, matching the pattern Person and MusicArtist always used:
- Movie/Series/BoxSet: Logo, then primaryImageTag (poster, compact size), then placeholder
- Episode/Season/Recording: primaryImageTag, then parentLogoImageTag (series logo), then seriesPrimaryImageTag, then placeholder
- Video/MusicVideo: primaryImageTag, then placeholder
- MusicAlbum / Playlist / Photo / PhotoAlbum / TvChannel: primaryImageTag, then placeholder
- Program: primaryImageTag, then channel logo, then placeholder
- Audio: primaryImageTag, then album art, then placeholder
- Person / MusicArtist: primaryImageTag (portrait), then placeholder
| Name | Type | Description |
|---|---|---|
item | object | JellyfinBaseItem node |
- Type:
- void
(static) setLogoBox(newBox) → {void}
The logo's place on screen is known: newBox, or invalid for no logo at all.
| Name | Type | Description |
|---|---|---|
newBox | object |
- Type:
- void
(static) setLogoImage(itemId, imageType, params) → {void}
Point the logo at a server image, and ask the server for the image's size so the text can be fitted before the image itself arrives. A first request for a resized image can take seconds while the server makes it (measured 2026-09-22: 0.26 s median, 3 s worst, on a LAN); the size answers in tens of milliseconds.
| Name | Type | Description |
|---|---|---|
itemId | string | |
imageType | string | |
params | object |
- Type:
- void
(static) setLogoPlaceholder(itemType) → {void}
Point the logo at a packaged placeholder. It loads in moments, and its size is learned when it does (onLogoLoadStatusChanged).
| Name | Type | Description |
|---|---|---|
itemType | string |
- Type:
- void
(static) setTracksInLayout(shouldShow) → {void}
setTracksInLayout: Add or remove the 62px itemTracks spacer from the itemDetails LayoutGroup. When a track cluster is applicable to the current item, the spacer stays in the layout and pushes description bottom up to y=701 so the trackCluster sibling can sit at y=713 with 12px breathing pad above and 18px below between the description and the button group. For item types without any media tracks (Series/Person/etc.), the spacer is removed and description bottom drops back to y=775 (matching main's behavior: no cluster, no padding, no regression).
| Name | Type | Description |
|---|---|---|
shouldShow | boolean |
- Type:
- void
(static) setupButtons(item) → {void}
setupButtons: Build the complete button set for the given item type. Clears all existing buttons and adds the correct sync buttons in order. Loading placeholders are added for async buttons (Series Resume, Person Shuffle) so the button row renders at its final width immediately. The async handlers replace or remove the placeholders when data arrives. Called on every itemContentChanged; the button list is rebuilt from scratch each time.
| Name | Type | Description |
|---|---|---|
item | object |
- Type:
- void
(static) startPlayFromDetail(startingPoint) → {void}
Build the single-item play queue from this detail's own state and navigate to the play route. The router is idle (the user pressed a button on this settled view), so navigateTo won't hit the in-progress guard, and the PlayerHostView reads this queue on mount.
| Name | Type | Description |
|---|---|---|
startingPoint | longinteger |
- Type:
- void
(static) swapResumeLoadingButton(loadingButton, resumeButton) → {void}
swapResumeLoadingButton: Replace the loading placeholder with the real Resume button at the same index. The user may have moved focus while the data loaded; removing and then inserting at one index is a net-zero shift, so the focus index read first still names the same button. Real focus moves only if the placeholder itself had it.
| Name | Type | Description |
|---|---|---|
loadingButton | object | the placeholder currently in the row |
resumeButton | object | createResumeButton() |
- Type:
- void
(static) syncVersionSelectionToChoice() → {void}
syncVersionSelectionToChoice: Move the selected version to the one that should resume, once enough positions are known to say which that is.
The details screen's first guess is made before any position has been read: it can only go on the server's source order. That order names the most recently played version, which is the rule's usual answer, but not when two versions sit at about the same place: there the better version for the device wins, and deciding that needs their actual positions. So the choice is re-run as each position lands, and the SAME pure function answers it here as in LoadVideoContentTask, which is what keeps the screen from naming one version while quick play, a cast or Continue Watching plays another.
Requests ride the existing per-version fetch, so a position already read or in flight is not asked for twice, and each reply re-enters here through onVersionPositionLoaded.
- Type:
- void
(static) toggleFavorite() → {void}
Toggles the favorite state of the current item. Called from dispatchDetailButtonAction on this component's render thread, where the fetchAsync().then() promise delivery and auto-abandon work.
- Type:
- void
(static) toggleWatched() → {void}
Toggles the watched/played state of the current item. Called from onWatchedButtonPressed (Movie/Episode) or onWatchedConfirmResponse (a Series, after the "mark all episodes" confirmation), on this component's render thread like toggleFavorite().
- Type:
- void
(static) trackVersionResumeFill(isLoading) → {void}
trackVersionResumeFill: Declare the Resume slot's loading state to the readiness ledger.
A Movie's Resume button is otherwise built synchronously from the item, but on a per-version server it can wait on a version's position or the server's thresholds, and settled must not fire while it does. Declared on the first loading answer and resolved on the first final one; the flag lives on m, not on m.versionResume, so a content reload that drops that state cannot orphan the declaration. After the screen has settled (a version switch, a refresh) the ledger ignores both sides.
| Name | Type | Description |
|---|---|---|
isLoading | boolean | whether the Resume slot is still waiting |
- Type:
- void
(static) triggerTextForSelection(items, selectedId) → {string}
triggerTextForSelection: Look up the currently-selected item's display title. Prefers triggerTitle where an item carries one: the video and subtitle slots use it to keep the collapsed trigger free of what only reads correctly beside the other entries. Audio items have no triggerTitle and fall through to title unchanged.
| Name | Type | Description |
|---|---|---|
items | object | |
selectedId | string |
- Type:
- string
(static) unsplitButtons() → {void}
The two halves of the contract. Both delegate to source/utils/buttonOverflow.bs so the OSD's row runs the same implementation rather than a second copy of it; what stays here is only which nodes this screen's row is made of.
The closing half puts focus back on the button that had it when the opening half ran. A mutator that moves focus to a DIFFERENT button on purpose (a new Resume taking over from Play) must say so with claimRowFocus(m.buttonOverflow, id) between the two, or its choice is silently reverted (see ItemDetailsRowFocus.spec.bs).
- Type:
- void
(static) updateFavoriteButton() → {void}
- Type:
- void
(static) updateItemDetailsAnimationTarget() → {void}
updateItemDetailsAnimationTarget: Slide itemDetails so the bottom of itemInfoRows sits right above the extras pane. boundingRect() on a child returns LOCAL coords relative to the parent's translation point, so we use translation[1] (not boundingRect().y) as the screen origin. Called at the last moment from the DOWN key handler to guarantee the layout is fully settled (text wrapping complete) before measuring. Eager calculation via renderTracking races with async text layout, producing stale targets, especially for long descriptions.
- Type:
- void
(static) updatePersonShuffle(isFinalAnswer) → {void}
updatePersonShuffle: Bring the Person row's Shuffle slot in line with the media chain. Shows Shuffle when the chain has found media, replacing the setupButtons() placeholder, or inserting before Favorite when there is none. Removes the placeholder, or a Shuffle whose media has gone, only on the chain's FINAL answer.
personHasMedia means "no media"; false when that false may be a run's reset
| Name | Type | Description |
|---|---|---|
isFinalAnswer | boolean | true when the chain has finished, so a false |
- Type:
- void
(static) updatePlaylistItemCountLabel() → {void}
The Playlist info row's count: "Tracks" only when the playlist is positively confirmed all-audio. "unknown" (load failure) and "mixed" both fall through to "Items", the safe neutral default.
- Type:
- void
(static) updateTextGradient() → {void}
- Type:
- void
(static) updateWatchedButton() → {void}
- Type:
- void
(static) zeroVersionPositions(itemId, mediaSources) → {object}
zeroVersionPositions: A positions map recording that no version has a position.
| Name | Type | Description |
|---|---|---|
itemId | string | |
mediaSources | dynamic | the item's MediaSources |
sourceId -> 0 for the item and every source
- Type:
- object