# Xtream PPV bundler Bundelt alle live-streams van één Xtream-panel waarvan de kanaalnaam een zoekterm bevat (bijv. `Jupiler Pro League`) in maximaal 3 vaste uitgangskanalen. Toewijzing gebeurt via FIFO: zodra een wedstrijd klaar is, neemt de oudste wachtende wedstrijd het vrijgekomen slot over. De app leest periodiek de kanaalnamen van de upstream. Daaruit haalt hij wedstrijd, competitie, taal en starttijd, bijvoorbeeld: ```text View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)] ``` Alleen streams met taalcode `(nl)` of zonder taalcode worden gebruikt. Een expliciete `(fr)`- of andere taalcode wordt overgeslagen. ## Snel starten ```bash docker build -t xtream-ppv-bundler . docker run -d --name xtream-ppv-bundler -p 8080:8080 \ -e XTREAM_URL="http://panel.example.com:8080" \ -e XTREAM_USERNAME="user" \ -e XTREAM_PASSWORD="pass" \ -e SEARCH_TERM="Jupiler Pro League" \ xtream-ppv-bundler ``` Of gebruik `docker-compose.example.yml`: ```bash cp docker-compose.example.yml docker-compose.yml # zet XTREAM_URL, XTREAM_USERNAME, XTREAM_PASSWORD en SEARCH_TERM in .env docker compose up -d ``` Voeg daarna in je IPTV-client toe: - **Playlist:** `http://:8080/playlist.m3u` - **EPG (XMLTV):** `http://:8080/epg.xml` ## Endpoints | Endpoint | Functie | | --- | --- | | `GET /playlist.m3u` | M3U met altijd `MAX_CHANNELS` vaste kanalen | | `GET /stream/` | Live proxy naar de wedstrijd op dat slot | | `GET /epg.xml` of `/xmltv.xml` | XMLTV-programmagids voor de 3 kanalen | | `GET /status` | JSON met slots, FIFO-wachtrij, laatste refresh en waarschuwingen | | `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. ## Configuratie | Env | Default | Verplicht | Betekenis | | --- | --- | --- | --- | | `XTREAM_URL` | - | ja | Basis-URL van het Xtream-panel, mag een path-prefix hebben | | `XTREAM_USERNAME` | - | ja | Upstream username | | `XTREAM_PASSWORD` | - | ja | Upstream password | | `SEARCH_TERM` | - | ja | Case-insensitive substring in de kanaalnaam | | `MAX_CHANNELS` | `3` | nee | Aantal vaste uitgangsslots (1-10) | | `LANGUAGE` | `nl` | nee | Toegestane expliciete taalcode; ontbrekende code wordt toegelaten | | `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat | | `START_GRACE_MIN` | `10` | nee | Minuten voor aanvang dat een event eligibel wordt | | `REFRESH_INTERVAL_SEC` | `60` | nee | Hoe vaak kanaalnamen opnieuw worden gelezen | | `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone van de tijdstempels in kanaalnamen | | `LISTEN_PORT` | `8080` | nee | HTTP-poort | | `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken | | `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren | | `HLS_SECRET` | random | nee | Secret voor ondertekende HLS-segment-URL's | | `LOG_LEVEL` | `info` | nee | `debug`, `info`, `warn`, `error` | ## Werking 1. Elke `REFRESH_INTERVAL_SEC` haalt de app `player_api.php?action=get_live_streams` op. 2. Kanaalnamen met `SEARCH_TERM` worden geparsed naar wedstrijd, competitie, taal en starttijd. Streams met een expliciete andere taal dan `LANGUAGE` vallen af. 3. Bronnen met dezelfde wedstrijd en starttijd worden één event. Meerdere DAZN-kanalen voor dezelfde wedstrijd zijn dus fallback-bronnen, geen extra FIFO-item. 4. Een event is eligibel vanaf `START_GRACE_MIN` voor de start tot `EVENT_DURATION_MIN` na de start. 5. Vrije slots worden gevuld op starttijd en daarna op moment van binnenkomst (FIFO). Een bestaande toewijzing wordt niet gepreempt. 6. Een toegewezen event houdt zijn slot tot de berekende eindtijd, ook als de provider de kanaalnaam intussen naar de volgende wedstrijd zet. Nieuwe kijkers krijgen de actuele toewijzing; bestaande verbindingen lopen door tot de client disconnect. ## EPG `/epg.xml` genereert XMLTV voor de vaste kanalen. De gids gebruikt: - de actuele toewijzing per slot; - alle toekomstige wedstrijden uit de laatste succesvolle kanaal-refresh, geprojecteerd over de slots volgens dezelfde FIFO-regels. De EPG is dus een projectie op basis van de kanaalnamen. Nieuwe of gewijzigde uitzendingen verschijnen na de volgende refresh. Bij een mislukte refresh blijft de laatst bekende gids staan. ## Stream-URL's en HLS De app bouwt stream-URL's primair als `/live///.ts`, met `.m3u8`- en padvarianten als fallback. Werkt geen enkele kandidaat, dan wordt eenmalig de volledige `get.php?type=m3u_plus`-playlist opgehaald en gecacht om de exacte URL te vinden. MPEG-TS wordt 1-op-1 geproxied. Bij HLS worden playlist-URI's herschreven naar ondertekende URLs op deze app, zodat upstream-credentials nooit naar de client lekken. Segmenten, keys en geneste playlists worden via dezelfde proxy opgehaald. ## Beperkingen - Het kanaalnaamformaat is provider-specifiek. Onparseerbare namen worden overgeslagen en verschijnen in `/status` onder `warnings`. - Zonder upstream-EPG is de eindtijd een aanname (`EVENT_DURATION_MIN`). - De EPG is een projectie en kan afwijken van de werkelijke toewijzing als de provider kanaalnamen onverwacht wijzigt. - De output heeft geen authenticatie; bedoeld voor LAN/self-host. - Meerdere kijkers betekenen meerdere upstream-verbindingen.