diff --git a/.kilo/plans/1793988235237-xtream-ppv-bundler.md b/.kilo/plans/1793988235237-xtream-ppv-bundler.md index 3a90162..8d3eae1 100644 --- a/.kilo/plans/1793988235237-xtream-ppv-bundler.md +++ b/.kilo/plans/1793988235237-xtream-ppv-bundler.md @@ -25,7 +25,8 @@ View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (202 vrijgekomen slot wordt gevuld met het oudste wachtende event (sortering op starttijd, daarna volgorde van binnenkomst). Geen preemptie van een al toegewezen event. - **Altijd 3 slots**: ook bij 0, 1 of 2 wedstrijden blijven `Jupiler 1` t/m `Jupiler 3` - in de playlist staan. Een leeg slot heet `Geen wedstrijd` en `/stream/` geeft 503. + in de playlist staan met volledig vaste id's, namen en `tvg-chno`. De actuele wedstrijd + staat in de EPG en `/status`, niet in de kanaalnaam. Een leeg slot geeft 503. - **Live-bepaling**: starttijd uit de kanaalnaam + instelbare duur. Een event is eligibel vanaf `startAt - START_GRACE_MIN` tot `startAt + EVENT_DURATION_MIN`. - **Einde event**: op basis van de berekende eindtijd, niet op basis van het verdwijnen @@ -159,9 +160,9 @@ blijven de overige slots leeg (naam `Geen wedstrijd`). ## Endpoints - `GET /` - eenvoudige HTML met playlist-link en kanaalstatus. -- `GET /playlist.m3u` - altijd `MAX_CHANNELS` entries: - `#EXTINF:-1 tvg-id="jupiler-1" tvg-name="Jupiler 1",Jupiler 1 | ()` - en daaronder `http:///stream/1`. +- `GET /playlist.m3u` - altijd `MAX_CHANNELS` entries met vaste metadata: + `#EXTINF:-1 tvg-id="jupiler-1" tvg-name="Jupiler 1" tvg-chno="1",Jupiler 1` + en daaronder `http:///stream/1`. Wedstrijdinformatie staat in de EPG. - `GET /epg.xml` of `/xmltv.xml` - XMLTV-gids voor de vaste kanalen. De gids toont de actuele toewijzing plus een FIFO-projectie van toekomstige wedstrijden uit de laatste kanaalnamen-refresh. diff --git a/Dockers/xtream-ppv-bundler/README.md b/Dockers/xtream-ppv-bundler/README.md index c10cd23..f6d0ca6 100644 --- a/Dockers/xtream-ppv-bundler/README.md +++ b/Dockers/xtream-ppv-bundler/README.md @@ -55,9 +55,10 @@ Voeg daarna in je IPTV-client toe: | `GET /health` | Healthcheck voor Docker | | `GET /` | Eenvoudige statuspagina | -De playlist bevat altijd `Jupiler 1` t/m `Jupiler 3`. Is een slot leeg, dan heet het -`Jupiler N | Geen wedstrijd` en geeft `/stream/N` een 503 tot er een wedstrijd op het slot -staat. +De playlist bevat altijd exact dezelfde drie kanalen: `Jupiler 1`, `Jupiler 2` en +`Jupiler 3`, met vaste `tvg-id`, `tvg-name` en `tvg-chno`. De kanaalnaam verandert nooit; +de actuele wedstrijd staat in de EPG en op `/status`. Is een slot leeg, dan geeft +`/stream/N` een 503 tot er een wedstrijd op het slot staat. ## Configuratie diff --git a/Dockers/xtream-ppv-bundler/src/playlist.js b/Dockers/xtream-ppv-bundler/src/playlist.js index d3bd0a3..ae3baf2 100644 --- a/Dockers/xtream-ppv-bundler/src/playlist.js +++ b/Dockers/xtream-ppv-bundler/src/playlist.js @@ -9,12 +9,9 @@ function buildPlaylist(req, scheduler) { const count = scheduler.getSlotCount(); const lines = ['#EXTM3U']; for (let i = 0; i < count; i += 1) { - const event = scheduler.getSlotEvent(i); - const label = event - ? `${event.title} (${formatLocalTime(event.startAt, settings.timezone)})` - : 'Geen wedstrijd'; + const name = `Jupiler ${i + 1}`; lines.push( - `#EXTINF:-1 tvg-id="jupiler-${i + 1}" tvg-name="Jupiler ${i + 1}",Jupiler ${i + 1} | ${label}` + `#EXTINF:-1 tvg-id="jupiler-${i + 1}" tvg-name="${name}" tvg-chno="${i + 1}",${name}` ); lines.push(`${origin}/stream/${i + 1}`); } diff --git a/Dockers/xtream-ppv-bundler/test/playlist.test.js b/Dockers/xtream-ppv-bundler/test/playlist.test.js index 0d5583d..5134d59 100644 --- a/Dockers/xtream-ppv-bundler/test/playlist.test.js +++ b/Dockers/xtream-ppv-bundler/test/playlist.test.js @@ -26,10 +26,34 @@ test('playlist always contains three fixed channels', () => { assert.equal(lines[0], '#EXTM3U'); assert.equal( lines[1], - '#EXTINF:-1 tvg-id="jupiler-1" tvg-name="Jupiler 1",Jupiler 1 | Standard de Liège - SK Beveren (19:25)' + '#EXTINF:-1 tvg-id="jupiler-1" tvg-name="Jupiler 1" tvg-chno="1",Jupiler 1' ); assert.equal(lines[2], 'http://example.test:8080/stream/1'); - assert.ok(body.includes('Jupiler 2 | Geen wedstrijd')); - assert.ok(body.includes('Jupiler 3 | Geen wedstrijd')); + assert.ok(body.includes('#EXTINF:-1 tvg-id="jupiler-2" tvg-name="Jupiler 2" tvg-chno="2",Jupiler 2')); + assert.ok(body.includes('#EXTINF:-1 tvg-id="jupiler-3" tvg-name="Jupiler 3" tvg-chno="3",Jupiler 3')); assert.ok(body.includes('http://example.test:8080/stream/3')); }); + +test('channel metadata stays identical when the assigned event changes', () => { + const first = { + getSlotCount: () => 3, + getSlotEvent: (index) => (index === 0 ? event : null), + }; + const second = { + getSlotCount: () => 3, + getSlotEvent: (index) => + index === 0 + ? { + key: 'b', + title: 'Anderlecht - Genk', + startAt: new Date('2026-11-06T21:00:00Z'), + endAt: new Date('2026-11-06T23:30:00Z'), + } + : null, + }; + const channelLines = (body) => body.split('\n').filter((line) => line.startsWith('#EXTINF')); + assert.deepEqual( + channelLines(buildPlaylist(fakeRequest(), first)), + channelLines(buildPlaylist(fakeRequest(), second)) + ); +});