Files
projects/Dockers/xtream-ppv-bundler
Bram 774200fb39
Build and Push Docker Images / build-and-push (push) Successful in 22s
always same channels
2026-11-07 16:33:13 +01:00
..
2026-11-07 16:33:13 +01:00
2026-11-07 16:33:13 +01:00
2026-11-06 19:24:39 +01:00
2026-11-06 19:24:39 +01:00
2026-11-07 16:30:47 +01:00
2026-11-07 16:30:47 +01:00
2026-11-06 19:24:39 +01:00
2026-11-07 16:33:13 +01:00
2026-11-07 16:30:47 +01:00
2026-11-06 19:24:39 +01:00

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:

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

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:

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 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

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.