mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
fix(epg): advance the Xtream channel list's current programme as time passes (#1647)
The "current programme" under each Xtream Live TV row was written only on
scroll-into-view, on a new EPG result, or on an EPG offset change. Nothing
re-evaluated it as wall-clock time passed, so once a programme ended the row
stayed on it until the category was left and re-entered. The progress bar
under the row never moved either.
The rows on screen now re-check themselves once a minute. A programme still on
air only has its progress bar advanced, at no request cost; once it ends the
row is re-picked, and only a programme on air or upcoming may replace it, so a
guide that has run out can never walk the row backwards. An exhausted cache is
dropped and refetched at most once per cache lifetime per channel, while an
empty answer from the provider is left alone. What is on screen stays there
until a replacement arrives, so a refreshing row never blanks out.
A programme occupies [start, stop) in every comparison, the visible slice is
read from the viewport rather than remembered, and the two services behind
this are root-provided because a live layout mounts the channel list more than
once over a single EPG queue: EpgRefillLimiter is the floor on refetching an
exhausted guide, keyed by playlist since stream ids are provider-local, and
EpgRefreshCoordinator owns the one timer and merges every mounted list's
request, because the queue is latest-wins and separate timers would cancel
each other on exactly the boundaries that matter.
Contract: docs/architecture/m3u-playlist-module.md ("Xtream channel-row
programme refresh").
Fixes #767. Supersedes #1610.
Co-Authored-By: Justin Willhite <5132924+thejdubb02@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
11 files changed
+1563
-155
No files matched your search
@@ -1090,6 +1090,62 @@ These URLs are playlist-scoped by default:
|
||||
|
||||
## EPG Integration
|
||||
|
||||
### Xtream channel-row programme refresh
|
||||
|
||||
The "current programme" line under each Xtream Live TV channel used to be
|
||||
written only when a row scrolled into view, when an EPG result arrived, or
|
||||
when the display offset changed. Nothing re-evaluated it as wall-clock time
|
||||
passed, so once a programme ended the row stayed on it until the category was
|
||||
left and re-entered (#767).
|
||||
|
||||
`PortalChannelsListComponent` re-checks the rows on screen once a minute. The
|
||||
rules, each of which exists to avoid a specific failure:
|
||||
|
||||
- A programme still on air only has its **progress bar** advanced. No cache
|
||||
read, no request: a row with nothing to learn must not cost traffic.
|
||||
- Once it ends the row is re-picked from the queue's cache, and only a
|
||||
programme that is **on air or upcoming** may replace it. A finished
|
||||
programme is never re-applied — it would keep presenting itself as current,
|
||||
and the earliest-item fallback that fills a blank row on first paint would
|
||||
move an advanced row *backwards*.
|
||||
- A cached guide whose programmes have **all** ended is dropped
|
||||
(`EpgQueueService.invalidate`) and refetched, because the queue skips any
|
||||
stream that still holds a cached answer. An **empty** answer means the
|
||||
provider has no guide for that channel and is left alone; re-asking would
|
||||
put one call per EPG-less visible row on the wire every minute.
|
||||
- What is on screen stays there until a replacement arrives, so a refreshing
|
||||
row never blanks out.
|
||||
|
||||
A programme occupies `[start, stop)` in every one of these comparisons, so
|
||||
"has it ended" and "what is on air" cannot disagree on the boundary instant.
|
||||
The selection rules are pure functions in
|
||||
`libs/portal/xtream/feature/src/lib/portal-channels-list/epg-preview-program.ts`
|
||||
and take an explicit `nowMs` in the PROVIDER's clock (`epgProviderClockMs`),
|
||||
never `Date.now()`.
|
||||
|
||||
Two root-provided services exist because a live layout mounts the channel list
|
||||
more than once — the sidebar and the fullscreen channel panel render side by
|
||||
side — over one shared `EpgQueueService`:
|
||||
|
||||
- `EpgRefillLimiter` is the floor on dropping an exhausted cache, the one
|
||||
place that overrides the queue's own throttling. A provider whose guide has
|
||||
genuinely run out answers the refill with the same finished programmes, so
|
||||
without a floor the row would ask again on the very next tick. Records carry
|
||||
the owning playlist, since a stream id is provider-local and the service
|
||||
outlives a playlist switch, and they expire by age rather than by viewport
|
||||
membership — a claim dropped when its row scrolled away would be handed back
|
||||
the moment the user scrolled to it again.
|
||||
- `EpgRefreshCoordinator` owns the single timer and merges what every mounted
|
||||
list needs into one queue request. `EpgQueueService.enqueue` is latest-wins:
|
||||
it bumps one generation, replaces the queue and the visible set, and drops an
|
||||
earlier caller's entries after its XMLTV await. Separate timers would cancel
|
||||
each other whenever both lists had rows to fill, which is exactly what
|
||||
happens on a programme boundary. Each list still decides for itself what is
|
||||
stale (that reads only its own state) and contributes its whole visible
|
||||
slice, since the queue drops anything outside the visible set it was last
|
||||
handed; a channel both lists show is fetched once, and playlists stay apart
|
||||
because their credentials differ.
|
||||
|
||||
### XMLTV response compression
|
||||
|
||||
The Electron EPG worker decodes HTTP `Content-Encoding` layers in reverse
|
||||
|
||||
Reference in new issue
Block a user