129 lines
5.6 KiB
Markdown
129 lines
5.6 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)]
|
|
```
|
|
|
|
De tijdstempels in de kanaalnamen worden standaard als **UTC** gelezen. De app toont ze
|
|
vervolgens in `TIMEZONE` (standaard `Europe/Brussels`), dus `19:25:00` UTC wordt in de
|
|
playlist en EPG als `20:25` getoond.
|
|
|
|
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 |
|
|
| `SOURCE_TIMEZONE` | `UTC` | nee | Tijdzone van de tijdstempels in de kanaalnamen |
|
|
| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone voor weergave in playlist en EPG |
|
|
| `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.
|