Files
projects/Dockers/xtream-ppv-bundler/README.md
T
Bram 91f22b54b4
Build and Push Docker Images / build-and-push (push) Successful in 36s
xtream bundler
2026-11-06 19:24:39 +01:00

124 lines
5.4 KiB
Markdown

# 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://<host>:8080/playlist.m3u`
- **EPG (XMLTV):** `http://<host>:8080/epg.xml`
## Endpoints
| Endpoint | Functie |
| --- | --- |
| `GET /playlist.m3u` | M3U met altijd `MAX_CHANNELS` vaste kanalen |
| `GET /stream/<slot>` | 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/<user>/<pass>/<stream_id>.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.