mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(collections): open a Stalker live favorite inside its portal (#1639)
Live channels in Favorites and Recently viewed now offer "Open in <playlist>" for Stalker portals, the counterpart of the VOD "View in portal" action. The chip in the programme panel and the channel's context menu jump to the channel inside its portal's Live TV, with its genre selected and the channel playing. Radio stays hidden: it is a separate legacy-paged section with no open-on-arrival contract. The handoff refuses to guess. The store records which genre the rendered ITV list belongs to, so the deferred play waits for a list that can actually serve the channel instead of inferring it from array identity. An id claimed by one channel as its `id` and another as its `stream_id` is ambiguous, so neither is played and the user still lands in the right genre. The handoff is abandoned when the user changes genre, search, portal or section first. Two pre-existing defects surfaced during review and are fixed here: - A blank provider id shadowed a valid one. `a ?? b` keeps an empty string, so a channel with a blank `stream_id` was stored with no identity at all and could not be selected, played or found again. Six writers had that shape and three private copies of the skip-the-blank rule; all now go through one `firstNonBlankStalkerId` helper. - Route-session readiness could publish before the store held the portal's row. The constructor starts one sync and the first navigation starts another, and the second skipped the bootstrap because the playlist id was claimed before the awaits that install it. Arrivals are now serialized, the id is claimed only after the store write resolves, and only the newest sync publishes readiness. Both fixes carry regression tests that fail on the old behavior.
This commit is contained in:
1 parent
6578e4073c
commit
f80a21beef
36 files changed
+2211
-121
No files matched your search
@@ -176,9 +176,63 @@ with a return handler keep Back available.
|
||||
effect re-runs as the store switches and loads;
|
||||
M3U navigates to `/workspace/playlists/:id/all` with `openM3uChannelUrl`
|
||||
(`OPEN_M3U_CHANNEL_URL_STATE_KEY`), the same key global search writes and
|
||||
the M3U player selects by URL. Stalker resolves to `null` — its ITV layout
|
||||
has no open-on-arrival contract yet, and a jump that only reached `/itv`
|
||||
would not be the affordance promised — so the action is hidden there.
|
||||
the M3U player selects by URL; Stalker navigates to `/workspace/stalker/:id/itv`
|
||||
with `openStalkerLiveItemId` + `openStalkerLivePlaylistId` (+ the row's
|
||||
genre as `openStalkerLiveCategoryId` when known) from
|
||||
`buildStalkerLiveNavigationTarget`, which `StalkerLiveAutoOpen`
|
||||
(`stalker-live-stream-layout/stalker-live-auto-open.ts`, the Stalker
|
||||
counterpart of the Xtream service + effect) consumes: it reads the state at
|
||||
construction and on every `NavigationEnd`, waits until `currentPlaylist` is
|
||||
the requested portal (channel ids are provider-local, so a colliding id in
|
||||
the previous portal's list must never match), then locates the channel in
|
||||
the full ITV channel list cache — `get_ordered_list` is server-paged, so
|
||||
the row may sit on any page of its genre — selects that genre (`'*'` for a
|
||||
channel without one), expands the rail and plays it. While the list loads it
|
||||
waits (the cache turning ready re-runs the effect); a portal that cannot
|
||||
serve a full list (`itvFullListUnsupported`, the cache's reactive
|
||||
unsupported set), a load that fails transiently (the cache only arms a
|
||||
retry cooldown and changes no signal, so the flow awaits the preload
|
||||
promise and treats "settled, neither ready nor unsupported" as the same
|
||||
outcome) or a channel missing from the list (censored genres are excluded
|
||||
from `get_all_channels`) falls back to selecting the remembered genre, so
|
||||
the user still lands in the right list, and the handoff is consumed either
|
||||
way. That remembered genre is the stored row's `tv_genre_id` (an opaque
|
||||
portal id, numeric on most panels but not all); the row's `categoryId`
|
||||
counts only when it is not a section marker, because app-written
|
||||
favorites/recent rows carry `'itv'` there. Playback is deferred whenever
|
||||
selecting the genre changes the list scope (another genre, the All Items
|
||||
grid — `null`, a different row source from the `'*'` All list — or an
|
||||
active search): `playChannel` → `navigation.prepare` captures the
|
||||
displayed rows as the remote/numeric channel order, and the store serves a
|
||||
category a tick after `setSelectedCategory` — even from the full-list
|
||||
cache — so playing right away would capture the previous scope's queue.
|
||||
The store answers "whose channels are on screen?" with
|
||||
`itvChannelsCategory` (set wherever `itvChannels` is served, cleared by
|
||||
`setItvChannels`), which is what the deferred play waits for. Array
|
||||
identity cannot answer it: filtering by `'*'` hands back the cache by
|
||||
reference, and clearing a search replaces the rendered list without the
|
||||
source moving. Clearing the search IS synchronous, so a genre already on
|
||||
screen plays at once. A pending play is dropped when a newer handoff
|
||||
arrives, when the user switches portal, genre or section or starts a
|
||||
search, and when the layout is destroyed. Two things settle before any of
|
||||
that: `StalkerWorkspaceRouteSession.isReady` (its sync resets the selected
|
||||
category and item for the arrival, and the store keeps the previous
|
||||
portal's playlist and cache across a revisit, so the playlist check alone
|
||||
passes too early and the selection would be wiped a tick later), and the
|
||||
user — the handoff is abandoned when the genre or search changes away from
|
||||
what it captured once it became actionable, which is measured after the
|
||||
session's own resets so they never read as a user action. Readiness is
|
||||
published by the NEWEST sync only, and never before the store holds that
|
||||
portal's row: the session applies arrivals one at a time and claims the
|
||||
playlist id only after `setCurrentPlaylist()` resolves. The constructor
|
||||
starts a sync before the first `NavigationEnd` starts another, so two run
|
||||
at once — the second used to find the id already claimed, skip the
|
||||
bootstrap and report ready while the first was still awaiting that write,
|
||||
which let a revisited same-id portal whose endpoint or credentials had
|
||||
changed play against the PREVIOUS row. A failed bootstrap leaves readiness
|
||||
false rather than handing the arrival a stale row. Stalker radio stations resolve to `null`: they live in the separate
|
||||
`radio` section, whose station list is legacy-paged with no
|
||||
open-on-arrival contract, so the action stays hidden for them.
|
||||
Two surfaces render the one verdict: `app-open-in-playlist-chip`
|
||||
(`libs/portal/shared/ui`), projected into the EPG timeline / list-view
|
||||
toolbar through the panels' `[epgToolbarAction]` content slot beside the
|
||||
|
||||
@@ -1278,6 +1278,12 @@ The Stalker live route and radio route intentionally share
|
||||
in the channel header. Remote up/down, numeric selection and status retain
|
||||
the captured playback order while browsing. See the
|
||||
[queue and reveal contract](./remote-control.md#live-channel-return-and-playback-order).
|
||||
- Arrival with `openStalkerLiveItemId` in history state (a collection's
|
||||
"open in playlist") is served by `StalkerLiveAutoOpen`
|
||||
(`stalker-live-auto-open.ts`): once the store serves the requested portal
|
||||
and the full ITV list (below) is ready, the channel is located there, its
|
||||
genre selected and playback started; see
|
||||
[Portal Detail Navigation](./portal-detail-navigation.md).
|
||||
|
||||
## Full ITV Channel List Cache
|
||||
|
||||
|
||||
Reference in new issue
Block a user