Methods
(static) buildReplayRoutes(stashed) → {object}
Pure: turn a guard-stashed path into the ordered route chain to replay. Home is always first (back-stack root); a /play path additionally inserts its Details screen between Home and the player (the details path is the play path minus the trailing "/play").
| Name | Type | Description |
|---|---|---|
stashed | dynamic |
- Source
- Type:
- object
(static) deepLinkDetailsRoute(deepLink) → {string}
Build the /details route for a deep link. The :type is just a display hint (ItemDetails resolves the REAL type from the fetched item). The ?deeplink=
| Name | Type | Description |
|---|---|---|
deepLink | object |
- Source
- Type:
- string
(static) findSavedServerByGuid(guid) → {object}
Look up a saved server entry by its Jellyfin server id (GUID). Returns the entry ({ name, id, baseUrl, originalUrl, ... }) or invalid. The pure match logic lives in config.bs (findServerInList) next to the saved_servers shape it matches.
| Name | Type | Description |
|---|---|---|
guid | string |
- Source
- Type:
- object
(static) isDeepLinkPlayerView(subtype) → {boolean}
Active routed views that ARE a media player (a deep link arriving over one must tear it down before launching the next, not stack on top).
| Name | Type | Description |
|---|---|---|
subtype | string |
- Source
- Type:
- boolean
(static) isPlausibleDeepLinkItemId(id) → {boolean}
A deep-link itemId is concatenated raw into the details route AND the metadata-fetch URL ("/Items/
| Name | Type | Description |
|---|---|---|
id | string |
- Source
- Type:
- boolean
(static) isPlausibleDeepLinkToken(value) → {boolean}
Safe-token check for deep-link fields that ride UNENCODED into the details route — mediaType in the PATH ("/details/
| Name | Type | Description |
|---|---|---|
value | string |
- Source
- Type:
- boolean
(static) notifyDeepLinkDeferred() → {void}
Toast shown when a deep link arrives but the user must authenticate first — it explains why nothing happened yet and that the content will open after sign-in. No-op if nothing stashed.
- Source
- Type:
- void
(static) onRuntimeDeepLink() → {void}
RUNTIME (app already signed in): resolve the stashed deep link's server and act.
- no/absent server, or it's the active one -> replay now.
- a different SAVED server -> prompt to switch.
- an unknown server -> can't connect; toast + discard. When NOT signed in (user at pre-login), do nothing: the stash rides until login and validates on whatever server they sign into.
- Source
- Type:
- void
(static) onServerProbeDone(reachable) → {void}
Continuation of performServerSwitch once the off-thread reachability probe returns. On an unreachable target, surface the error and discard the deep link. On success, reuse the Change-Server machinery (resetRouter -> point at the new server -> SignOut -> reenterLogin); the stashed deep link survives the teardown and replays once the new server's login completes.
| Name | Type | Description |
|---|---|---|
reachable | boolean |
- Source
- Type:
- void
(static) parseDeepLinkContentId(raw) → {object}
Parse a Roku deep-link contentId into { itemId, serverGuid, action }. contentId is the only deep-link field that survives BOTH Roku ingress paths intact (cold-start /launch standardizes only contentId+mediaType; /input forwards arbitrary params but /launch does not), and Roku forbids "&" inside it — so the whole contract rides inside contentId, pipe-delimited (Roku's own documented convention, e.g. series=x|Season=1). We mint it as "id=
| Name | Type | Description |
|---|---|---|
raw | string |
- Source
- Type:
- object
(static) performServerSwitch(entry) → {void}
Perform the confirmed switch. Pre-flight the target FIRST (non-destructive) so an offline server never strands the user. The probe runs on a Task thread (ServerReachableTask) so the up-to-8s reachability check can't freeze remote input; the switch itself continues in onServerProbeDone once the verdict lands back on the main loop.
| Name | Type | Description |
|---|---|---|
entry | object |
- Source
- Type:
- void
(static) promptServerSwitch(entry) → {void}
Ask before switching servers (it signs the user out of the current one). The choice comes back through SceneManager.returnData -> main.bs's isDataReturned handler (the same bridge the exit dialog uses), which calls performServerSwitch on confirm.
| Name | Type | Description |
|---|---|---|
entry | object |
- Source
- Type:
- void
(static) replayAfterLogin() → {void}
Read + clear whatever is stashed and replay it after a successful login (or immediately when already authed). Deep link wins over a guard path. Always navigates Home first so it is the back-stack root.
- Source
- Type:
- void
(static) replayDeepLinkRuntime() → {void}
Runtime replay (app already signed in, deep link for the active server). Unlike the post-login path above, this does NOT prepend Home — an in-app cast shouldn't flash through the Home screen.
- already on the target item's LOADED details -> launch the action in place (no fetch, no re-mount); for "open" we're already showing it, so nothing to do. The itemContent guard matters: a lingering empty details would otherwise swallow the cast.
- otherwise -> hand the intent to JRScene.resolveDeepLink, which VALIDATES the id (a fetch) BEFORE navigating. An invalid id never disturbs the active session — it just toasts. A valid id then navigates (replacing an active player for a playback cast, else a plain push).
- Source
- Type:
- void
(static) stashDeepLink(rawContentId, mediaType, itemNameopt) → {void}
Stash a deep link (cold-start or runtime) as a structured intent for replayAfterLogin. Parses the contentId; records itemId + mediaType (+ serverGuid for the server resolver). Replaces the old queue-seed approach — ItemDetails now builds the queue from the validated item (real type + bookmark), so no queue work happens here. itemName is an OPTIONAL display title the sender (a casting client) may include so the server-switch prompt can name what's being cast — we can't fetch it ourselves before the switch (it lives on a server we're not yet authed to). Empty for bare/unknown senders.
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
rawContentId | string | |||
mediaType | string | |||
itemName | string | <optional> | "" |
- Source
- Type:
- void
(static) steerColdStartDeepLinkServer() → {void}
COLD start: if the stashed deep link names a saved server, point the login at it BEFORE beginLogin runs (no prompt — there is no active session to disrupt). An unknown/absent GUID is left alone: login proceeds normally and the deep link validates on whatever server we land on (graceful error -> Home if the item isn't there).
- Source
- Type:
- void
(static) wouldStackOverActivePlayer(action, activeSubtype) → {boolean}
True when a non-playback ("open") deep link would stack a details screen on TOP of an active media player, and so must be dropped rather than enacted. jellyfin-web's display-mirroring fires a DisplayContent ("open") on EVERY item-detail browse while JellyRock is the cast target, and the Roku OS can hand us an "open" deep link mid-playback — neither is an intent to interrupt playback, so the controller's browsing must never yank the cast target off the video. A PLAYBACK action (play/shuffle/trailer/instantmix) is exempt: it legitimately REPLACES the player (replacePlayer below). activeSubtype is the active routed view's subtype ("" / invalid when there's no active view). Pure so the rule is unit-testable without a live node.
| Name | Type | Description |
|---|---|---|
action | string | |
activeSubtype | dynamic |
- Source
- Type:
- boolean