mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(dashboard): portal EPG on the live rails, loaded lazily per visible card (#1638)
Xtream and Stalker live cards on the dashboard carried no XMLTV key, so the rails never asked anything for them and showed only the LIVE chip. Their programme now comes from the portal, one card at a time and only once the card is on screen. - `lib-dashboard-rail` reports the cards inside its viewport through an IntersectionObserver rooted at the track; `DashboardLiveEpgPresenter` unions them with the pinned hero row and hands the set to `DashboardPortalLiveEpgPresenter`. - `DashboardPortalLiveEpgService` runs the bounded queue — two requests in flight, 200 ms apart, one card each — through `StreamResolverService.loadEpgForItems`, publishing every answer the moment it lands, so the page never waits and a slow portal delays no other card. A card scrolled past before its turn is never requested. - A programme is trusted for 60 s, an empty answer for 30 s (the resolver reports a dead portal and a guide-less channel identically), and a completion captures both the display offset and the EPG source revision, requeueing itself when either moved. - The presenter hands its wanted set back on destroy. Desktop only: the shared collection resolver is gated on the local XMLTV bridge. - A shimmer placeholder shows only before a card's first portal answer; M3U cards keep the batched XMLTV lookup.
This commit is contained in:
1 parent
4e575a810e
commit
6578e4073c
19 files changed
+1937
-46
No files matched your search
@@ -141,7 +141,69 @@ Render rules:
|
||||
`dashboard-recent-live-rail`); there is no fallback from one to the
|
||||
other. M3U cards carry an `epg_lookup_key` using the app-wide XMLTV
|
||||
fallback order (`tvg-id` -> `tvg-name` -> channel name); EPG enrichment
|
||||
must use that key before falling back to the card title.
|
||||
must use that key before falling back to the card title. Both rails are
|
||||
enriched by `DashboardLiveEpgPresenter`, the one component-provided
|
||||
facade for live EPG: it owns the XMLTV lookup described under "Scoped
|
||||
lookups" in `m3u-playlist-module.md`, forwards everything portal-shaped
|
||||
to `DashboardPortalLiveEpgPresenter`, and `enrich()` returns the cards
|
||||
with their "now on air" row filled in.
|
||||
Xtream and Stalker cards have no XMLTV key of their own; their "now on
|
||||
air" line comes from the portal, **lazily and per card**:
|
||||
- `buildDashboardPortalLiveEpgEntry` (dashboard data-access) turns a
|
||||
live `PortalActivityItem` into the `UnifiedCollectionItem` the
|
||||
collection pages hand `StreamResolverService.loadEpgForItems`, keyed
|
||||
by the collection uid — favourites and recent rows of one channel
|
||||
share the answer. Radio rows and rows without a usable provider id
|
||||
get no entry. Cards carry that key as `liveEpgSourceKey`.
|
||||
- `lib-dashboard-rail` reports the cards inside its track viewport
|
||||
(plus ~one card of `rootMargin`) through `visibleCardsChanged`, from
|
||||
an `IntersectionObserver` rooted at the track; without the API every
|
||||
card counts as visible. Cards that leave the list are reported gone
|
||||
at once.
|
||||
- `DashboardPortalLiveEpgPresenter` (component-provided) unions the
|
||||
visible keys of both rails with the pinned hero key and calls
|
||||
`DashboardPortalLiveEpgService.sync()` with exactly those entries —
|
||||
on every change, on the 30 s tick, and on a display-offset change.
|
||||
It is reached through `DashboardLiveEpgPresenter`, which derives the
|
||||
portal rows itself from the enabled rails and pins the hero, so the
|
||||
page component only forwards what a rail can see. The queue lives in
|
||||
the root service, so leaving the dashboard hands the wanted set back
|
||||
(`sync([])` on destroy); otherwise the queue would keep asking for
|
||||
cards on a page that is gone.
|
||||
- `DashboardPortalLiveEpgService` (root) owns the queue: at most two
|
||||
requests in flight, 200 ms between starts (the numbers
|
||||
`EpgQueueService` proved against real panels), one card per request,
|
||||
each answer published the moment it lands in `programs`, so the page
|
||||
never waits and a slow portal delays no other card. Only wanted keys
|
||||
are dequeued, so a card scrolled past before its turn is never
|
||||
requested. A programme lives 60 s; a programme that ended is asked
|
||||
again, but not within 30 s of the last answer (a portal may keep
|
||||
returning the stale row). An answer with **no** programme lives only
|
||||
30 s, because the resolver reports a failed portal and a guide-less
|
||||
channel identically (it files per-channel failures as `null`), so
|
||||
there is no failure cooldown to keep and the short TTL is what lets
|
||||
an outage recover on the next tick.
|
||||
- Every answer is "at the provider clock" and against one XMLTV source
|
||||
set. A request captures both the display offset and
|
||||
`EpgSourceSettingsService.revision()` — the same fence
|
||||
`EpgService.guard()` uses — and a completion whose either fact moved
|
||||
is discarded and requeued instead of published. That requeue has to
|
||||
happen in the completion: while the key is in flight the retire pass
|
||||
cannot queue a replacement, and without it the pre-change answer
|
||||
would be trusted for a full TTL (the repo's late-result
|
||||
invalidation contract).
|
||||
- Desktop only in practice: the shared collection resolver is gated on
|
||||
the local XMLTV bridge (`supportsProgramLookup`) and answers nothing
|
||||
without it, so `sync()` returns immediately in the PWA rather than
|
||||
filing an empty answer for every card. Lifting that gate for portal
|
||||
lookups would change the collection pages too and is deliberately
|
||||
out of scope here.
|
||||
- `DashboardLiveEpgPresenter.enrich()` prefers the portal answer, falls
|
||||
back to the XMLTV title match when the portal said "nothing on air",
|
||||
and marks a card
|
||||
`nowPlayingState: 'pending'` only before its FIRST answer — the
|
||||
channel layout then shows a shimmer placeholder in the programme
|
||||
slot; a refresh keeps the previous answer on screen.
|
||||
4. `xtreamRecentlyAddedCards` — maps `xtreamRecentlyAddedItems()` to rail
|
||||
cards. Aggregates newly added VOD and series across *all* Xtream
|
||||
playlists via `DashboardDataService.reloadXtreamRecentlyAddedItems()`,
|
||||
|
||||
Reference in new issue
Block a user