mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -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
@@ -18,6 +18,7 @@ import {
|
||||
waitForXtreamWorkspaceReady,
|
||||
} from './electron-test-fixtures';
|
||||
import {
|
||||
fetchXtreamEpgFixture,
|
||||
fetchXtreamLiveFixture,
|
||||
fetchXtreamSeriesFixture,
|
||||
fetchXtreamVodFixture,
|
||||
@@ -47,6 +48,33 @@ test.describe('Dashboard Activation', () => {
|
||||
liveFixture.items,
|
||||
getXtreamTitle
|
||||
);
|
||||
// The mock's guide for the channel favourited below (its first live
|
||||
// stream). The programme on air is read off the full guide at
|
||||
// assertion time: the mock cuts its slots from the second the guide
|
||||
// was generated, so the "current" listing of a fixture fetched in
|
||||
// that same second is the slot that ended just then.
|
||||
const epgFixture = await fetchXtreamEpgFixture(
|
||||
request,
|
||||
xtreamCredentials
|
||||
);
|
||||
expect(getXtreamTitle(epgFixture.stream)).toBe(liveTitle);
|
||||
const liveNowTitle = () => {
|
||||
const nowSeconds = Math.floor(Date.now() / 1000);
|
||||
const index = epgFixture.fullEpg.findIndex(
|
||||
(listing) =>
|
||||
listing.startTimestamp <= nowSeconds &&
|
||||
nowSeconds < listing.stopTimestamp
|
||||
);
|
||||
expect(index).toBeGreaterThanOrEqual(0);
|
||||
// Tolerate a slot boundary passing between the app's answer and
|
||||
// this assertion.
|
||||
const titles = epgFixture.fullEpg
|
||||
.slice(index, index + 2)
|
||||
.map((listing) =>
|
||||
listing.title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
);
|
||||
return new RegExp(titles.join('|'));
|
||||
};
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
@@ -139,6 +167,15 @@ test.describe('Dashboard Activation', () => {
|
||||
app.mainWindow,
|
||||
'dashboard-live-favorites-rail'
|
||||
);
|
||||
// An Xtream card has no XMLTV key: its "now on air" line comes
|
||||
// from the portal, asked for lazily once the card is on screen.
|
||||
await expect(
|
||||
dashboardRailCardByTitle(
|
||||
app.mainWindow,
|
||||
'dashboard-live-favorites-rail',
|
||||
liveTitle
|
||||
).locator('.rail__channel-now')
|
||||
).toContainText(liveNowTitle(), { timeout: 30000 });
|
||||
await dashboardRailCardByTitle(
|
||||
app.mainWindow,
|
||||
'dashboard-live-favorites-rail',
|
||||
@@ -167,6 +204,14 @@ test.describe('Dashboard Activation', () => {
|
||||
app.mainWindow,
|
||||
'dashboard-recent-live-rail'
|
||||
);
|
||||
// Same channel, same key: the recent card shares the answer.
|
||||
await expect(
|
||||
dashboardRailCardByTitle(
|
||||
app.mainWindow,
|
||||
'dashboard-recent-live-rail',
|
||||
liveTitle
|
||||
).locator('.rail__channel-now')
|
||||
).toContainText(liveNowTitle(), { timeout: 30000 });
|
||||
await dashboardRailCardByTitle(
|
||||
app.mainWindow,
|
||||
'dashboard-recent-live-rail',
|
||||
|
||||
Reference in new issue
Block a user