OTT-Dienst

Liefert Streams über HTTP-basierte Protokolle — HLS (über MPEG-TS), MPEG-DASH und Low-Latency HLS (über CMAF — fragmentiertes MP4, seit Version 1.13) sowie MPEG-TS over HTTP. Unterstützt werden HTTPS (SSL) und HTTP/3 (QUIC). Die Auslieferung wird auf der Registerkarte OTT der Stream-Einstellungen aktiviert.

Die Verbindungs-URLs haben das Format:

host und port werden in den http server-Einstellungen festgelegt.

streamID des Streams. Nicht zu verwechseln mit der Reihenfolge in der Stream-Liste. Die ID wird im Kopf der Stream-Statistikseite und in der Spalte ID der Stream-Liste angezeigt; sie wird bei der Stream-Erstellung festgelegt und ändert sich nie.

Analog für HLS, DASH und Low-Latency HLS (die letzten beiden — nur im OTT/HLS/LL-HLS/LL-Dash, siehe unten):

Auf der Stream-Statistik-Seite werden die URLs der verbundenen Protokolle (als Vorlage) und ihr aktueller Status angezeigt. Nicht autorisierter Zugriff ist verboten — Clients müssen in Peers eingetragen sein.

Für HLS und DASH sind in der URL zusätzliche Parameter verfügbar (optional):

[URL]?a=1&s=40&m=40&v=5&h3=1

  • a: 1 — absoluter Pfad in der Playlist, 0 — relativer Pfad (Standard).

  • s: Länge der dynamischen Playlist (Sekunden), Standard 40 s.

  • m: Mindestlänge der dynamischen Playlist (s); Standard 40 s. Maximale Länge der dynamischen Playlist 60 s. Liegt die aktuelle Chunk-Buffer-Größe unter der im Request geforderten Mindestgröße, wird HTTP 404 zurückgegeben. So startet HLS stets mit einem gefüllten Chunk-Buffer auf dem Server.

  • v: die in der Playlist ausgegebene HLS-Protokollversion. Standardmäßig hängt der Wert vom HLS-Modus ab (siehe unten): OTT/HLS und OTT/HLS/LL-HLS/LL-Dash6, Peering/HLS3. Ein expliziter Wert in der URL überschreibt den Standard. Ein Wechsel der Version kann für die Kompatibilität mit einem bestimmten HLS-Client erforderlich sein.

  • h3: Opt-in für HTTP/3 (QUIC) für diese OTT-Sitzung. Veranlasst den Server, im Response einen Alt-Svc-Header auszugeben; ein kompatibler Player / Browser wechselt daraufhin auf QUIC. Siehe Abschnitt HTTP/3 (QUIC) weiter unten.

Für die Kompatibilität mit einigen HLS-Clients kann der Dateiname index.m3u8 an die URL angehängt werden, z. B. http://host:port/hls/stream/login/password/index.m3u8.

Der Auslieferungsmodus wird über die Stream-Einstellung OTT HLS festgelegt (Registerkarte OTT): Peering/HLS, OTT/HLS oder OTT/HLS/LL-HLS/LL-Dash.

Peering/HLS — ein Modus mit einfacher Aufteilung in Segmente (Chunks). Empfohlen für das Peering (die Distribution) von Streams. Es wird nur HLS über MPEG-TS (/hls) ausgeliefert. Standardmäßig wird die Playlist als EXT-X-VERSION:3 ausgegeben, um mit Peer-Clients kompatibel zu sein.

OTT/HLS — ein Modus mit einer Segmentaufteilung, die auf einen schnellen Player-Start beim OTT-Broadcasting optimiert ist. In diesem Modus ist die CPU-Last höher; er wird für das Broadcasting empfohlen. Es wird HLS über MPEG-TS (/hls) ausgeliefert. Standardmäßig wird die Playlist als EXT-X-VERSION:6 mit EXT-X-INDEPENDENT-SEGMENTS und dem Attribut CHARACTERISTICS in EXT-X-MEDIA TYPE=SUBTITLES ausgegeben (Apple HLS, hls.js, Safari, dash.js/Shaka). Benötigt ein bestimmter Client einen früheren Wert, setzen Sie ihn über den Query-Parameter ?v= (siehe die Parameterliste oben).

OTT/HLS/LL-HLS/LL-Dash — ein Auslieferungsmodus über CMAF (fragmentiertes MP4, fMP4; seit Version 1.13). Der Stream erzeugt fMP4-Segmente (.m4s + gemeinsame init.mp4, mimeType="video/mp4"), auf deren Basis ausgeliefert werden:

  • MPEG-DASH unter /dash — jetzt über CMAF und nicht über MPEG-TS;

  • Low-Latency HLS unter /llhls (siehe Low-Latency HLS unten);

  • bei aktivierter Stream-Einstellung Enable TS Chunk (Standard true) — zusätzlich Legacy-HLS über MPEG-TS (/hls), wie im OTT/HLS; bei false werden nur fMP4-Segmente ausgeliefert, was Festplatte und CPU schont.

Die HLS-Playlist im OTT/HLS/LL-HLS/LL-Dash ist standardmäßig ebenfalls EXT-X-VERSION:6.

Bemerkung

MPEG-DASH und Low-Latency HLS sind nur im OTT/HLS/LL-HLS/LL-Dash verfügbar. Im OTT/HLS und Peering/HLS wird ausschließlich HLS über MPEG-TS ausgeliefert.

Für den HTTP-Server kann SSL (HTTPS) aktiviert werden — in den Server-Einstellungen.

Chunk Min Interval und Chunk Max Interval

Im OTT-Modus wird der Stream auf PAT/PMT/SPS/PPS/IFrame analysiert, und die Chunks werden nach dem Kriterium des schnellen Player-Starts geschnitten. Die Analyse beginnt bei min interval, und falls die Daten aus irgendeinem Grund nicht gefunden werden, wird der Chunk bei max interval zwangsweise geschnitten.

GOP-aligned segments

Im OTT/HLS richtet der Segmenter die Chunk-Grenzen an Random-Access-Punkten aus und unterscheidet zwischen IDR und einem gewöhnlichen I-frame. Sendet die Quelle mit closed-GOP (IDR vorhanden), beginnt jedes .ts-TS-Segment garantiert mit SPS / PPS / IDR (bei HEVC — zusätzlich VPS) — einem echten Einstiegspunkt, an dem der Player den Stream öffnet. Ist die Quelle open-GOP / ohne IDR, dient der nächstgelegene I-frame als Grenze (das bisherige Verhalten). Der Segmenter schneidet den „Schwanz“ des vorherigen GOP — die P / B-Slices — aus dem Fenster zwischen dem führenden PAT und dem ersten SPS des Chunks heraus. Das:

  • beseitigt das anfängliche Schwarzbild beim Start einer VOD-Sitzung (?t= / ?epg=) in hls.js, Safari und VLC: der MSE-Decoder erhält das korrekte Header-Set der NAL-Einheiten bereits im ersten Segment und startet sofort;

  • bringt die Ausgabe in Übereinstimmung mit HLS RFC 8216 §3 („Each Media Segment MUST contain a SPS and a PPS that decode its first Access Unit“);

  • macht die EXT-X-INDEPENDENT-SEGMENTS-Erklärung in einer EXT-X-VERSION:6-Playlist korrekt.

Steuerung über die Stream-Einstellung gop-aligned-segment (Standard true). Wirkt nur bei HLS = OTT/HLS: im Peering/HLS und bei MPTS-Streams bleibt das Verhalten unverändert. PSI (PAT / PMT) und Audio bleiben vollständig erhalten; einziger Nebeneffekt ist ein einbildiger continuity_counter-Sprung auf der Video-PID an der Grenze zweier benachbarter Chunks, was für HLS / DASH MSE eine legitime Decode-Boundary ist.

Bei der Ausrichtung an Einstiegspunkten kann die tatsächliche Chunk-Dauer über Chunk Min Interval hinaus aufgerundet werden. Daher spiegelt der Wert EXT-X-TARGETDURATION in der Live-Playlist die maximale tatsächliche Segmentdauer wider (Abschnitt 4.3.3.1 von RFC 8216) statt des konfigurierten Minimums. Das hält das Manifest innerhalb des Standards: hls.js und kompatible Player verkürzen das Aktualisierungsintervall der Playlist nicht und lösen keine falschen bufferStalledError aus.

Low-Latency HLS (/llhls)

Im OTT/HLS/LL-HLS/LL-Dash steht neben /dash ein separater Low-Latency HLS-Endpoint zur Verfügung — Auslieferung mit reduzierter Latenz auf denselben CMAF-fMP4-Segmenten:

Die Medien-Playlist wird in partielle Segmente (parts, #EXT-X-PART-Direktiven) aufgeteilt: Der Player beginnt die Wiedergabe, ohne auf das fertige vollständige Segment zu warten. Verwendet werden ein blockierendes Neuladen der Playlist (der Server hält die Anfrage zurück, bis der nächste part bereit ist) und der Preload-Hinweis #EXT-X-PRELOAD-HINT.

Die Ziel-Dauer eines part wird über die Stream-Einstellung Part Target Duration festgelegt (ms; Standard 500, Bereich 100–5000). Der Wert wird im laufenden Betrieb übernommen, ohne den Stream neu zu starten, und muss kleiner als Chunk Min Interval sein.

HLS / DASH / LL-HLS Adaptive Multistream

HLS Adaptive Multistream wird seit Version 1.10 unterstützt, DASH Adaptive Multistream seit Version 1.12 (seit Version 1.13 — über CMAF), Low-Latency HLS Adaptive seit Version 1.13.

Für adaptive Streams wird eine separate Playlist konfiguriert. Dazu ist Folgendes nötig:

  • OTT-Auslieferung bei den Streams aktivieren, die in die adaptive Playlist aufgenommen werden: OTT/HLS — für adaptives HLS über MPEG-TS; OTT/HLS/LL-HLS/LL-Dash — für adaptives DASH / Low-Latency HLS über CMAF.

  • Im Hauptmenü erscheint ein Bereich für adaptive Streams. Dort muss ein Stream hinzugefügt und alle Streams angegeben werden, die in diese Playlist aufgenommen werden sollen.

  • Für Streams kann ein Bitrate-Parameter gesetzt werden. Standardmäßig ist er 0 — die Bitrate wird aus dem gemessenen Wert übernommen; andernfalls kann sie explizit gesetzt werden.

Für adaptive Playlists gilt eine andere URL:

Peers (Clients) können — wie bei normalen Streams — Zugriffsbeschränkungen auf adaptive Streams haben. Eine Berechtigung für einen adaptiven Stream schließt die Berechtigung für alle enthaltenen Substreams ein.

HTTP/3 (QUIC)

Seit Version 1.13.1.438 enthält Perfect Streamer einen integrierten HTTP/3-Server für die OTT-Auslieferung von HLS, MPEG-DASH und Low-Latency HLS über QUIC (RFC 9000 + RFC 9114). Der Stack ist ngtcp2 (QUIC v1) + nghttp3 (HTTP/3 frame layer); die TLS-Infrastruktur verwendet dasselbe Zertifikat wie der HTTPS-Listener.

QUIC bedient ausschließlich OTT-Routen:

  • / — Root-Redirect,

  • /hls/..., /dash/... und /llhls/... — Master-Playlists / MPD,

  • /h<sessID>/... — Per-Session-URL (Media-Playlists, Segmente, VTT),

  • /http/<stream>/... — Raw-MPEG-TS über HTTP.

Low-Latency HLS und DASH werden über QUIC inkrementell (chunked) ausgeliefert: parts gehen an den Client, sobald sie bereit sind, ohne auf das vollständige Segment zu warten.

Administrative Pfade (/data, /config, /xmltv, /db/, /login, /logout, /restart) liefern über QUIC 404 — die Admin-API verbleibt auf HTTPS / HTTP-TCP. Dies ist eine bewusste Einschränkung zur Reduzierung der Angriffsfläche des QUIC-Listeners.

Aktivierung

Die QUIC-Einstellungen befinden sich im Abschnitt Configuration / HTTP server (Knoten /config/http-server):

  • HTTP/3 Enable (http3-enable) — globales Flag zur Aktivierung des QUIC-Listeners. Standardwert: off. Die Aktivierung öffnet einen UDP-Socket an http3-port; die Deaktivierung schließt ihn.

  • HTTP/3 Port (http3-port) — UDP-Port des QUIC-Listeners. Standardwert: 43984. QUIC läuft auf UDP, HTTPS auf TCP; die Ports können übereinstimmen oder abweichen — ein Konflikt zwischen ihnen besteht nicht.

  • HTTP/3 0-RTT Enable (http3-zero-rtt-enable) — erlaubt den 0-RTT-Handshake (RFC 9001 §4.6.1) bei wiederaufgenommenen Verbindungen desselben Clients. Reduziert die Start-Latenz; das Replay-Risiko ist bei nicht idempotenten Anfragen zu berücksichtigen (für rein lesende OTT-Last unbedenklich). Standardwert: on.

Konfigurationsänderungen werden im laufenden Betrieb angewendet, ohne Neustart des Dienstes.

Zertifikat und Schlüssel werden vom HTTPS-Listener übernommen — es gibt keine separate Zertifikatskonfiguration für HTTP/3. Ist HTTPS nicht konfiguriert (ssl-enable=false), liefert die Aktivierung von HTTP/3 nichts — der Handshake scheitert.

Der Parameter ?h3 — Opt-in pro Sitzung

Ein Browser verbindet sich nicht direkt mit einem HTTP/3-Endpunkt — er geht zunächst über HTTPS/TCP, empfängt im Response den Header Alt-Svc: h3=":<port>"; ma=86400 (RFC 7838) und schaltet erst nachfolgende Anfragen an diesen Origin auf QUIC um.

In Perfect Streamer ist die Ausgabe von Alt-Svc ein Opt-in pro OTT-Sitzung. Der Server kündigt QUIC nicht jedem Client unterschiedslos an: Der Client muss HTTP/3 explizit über den Query-Parameter ?h3 auf der HLS-/DASH-Master-URL anfordern.

Wert in der URL

Opt-in-Wert

Alt-Svc im Response

Parameter fehlt

off (default)

nein

?h3 (ohne Wert)

on

ja

?h3=1, ?h3=on, ?h3=yes, ?h3=true

on

ja

?h3=0, ?h3=off, ?h3=no, ?h3=false

explicit off

nein (wie bei fehlend)

Sobald der Client die Session-URL /h<sess>/... erhalten hat, wird das wantH3-Flag in der Sitzung gespeichert — alle nachfolgenden Media-Playlist-Refreshs, Segment-GETs und VTT-Chunk-GETs unter dieser Session-URL erhalten ebenfalls Alt-Svc, auch wenn ?h3 in der jeweiligen Anfrage nicht vorhanden ist. Ohne Opt-in auf dem Master entsteht kein Sticky-Verhalten.

Alt-Svc-Gating:

  1. Der QUIC-Listener ist global aktiviert (http3-enable=true); andernfalls wird die Ankündigung selbst bei gesetztem ?h3 unterdrückt.

  2. Die Anfrage kam nicht über QUIC — bei Anfragen, die bereits über H3 laufen, ist Alt-Svc sinnlos und wird nicht ausgegeben.

  3. Per-Session- oder Per-URL-wantH3=true (siehe Tabelle oben).

  4. Der Admin- und der EPG-Server geben niemals Alt-Svc aus — sie verfügen über keinen QUIC-Listener.

Dieses Verhalten ist mit RFC 7838 §3 konform — der Server entscheidet selbst, bei welchen Antworten Alt-Svc ausgegeben wird.

Szenario der QUIC-Umschaltung im Browser

Typischer Ablauf (Chrome / Firefox / Safari):

  1. Der Player öffnet die Master-URL mit explizitem Opt-in:

    https://stream.example.com:41982/hls/test1/login/password/index.m3u8?h3=1
    
  2. Die erste Anfrage erfolgt über HTTPS/TCP. Im Response gibt der Server Alt-Svc: h3=":43984"; ma=86400 aus.

  3. Der Browser merkt sich die Zuordnung stream.example.com:41982 h3=":43984". In Chrome lässt sie sich auf der Seite chrome://net-internals/#alt-svc einsehen; in Firefox — about:networking#http3.

  4. Bei nachfolgenden Anfragen an denselben Origin (Playlist-Refresh, Segment-GETs, VTT) öffnet der Browser eine QUIC-Verbindung auf udp/43984 und kommuniziert weiter über HTTP/3. In den DevTools → Network wird die Spalte Protocol für diese Anfragen h3.

Lässt sich die QUIC-Verbindung nicht aufbauen (UDP-Port am Firewall blockiert, Zertifikat im System nicht vertrauenswürdig), bleibt der Browser transparent auf HTTPS/TCP — der Stream läuft funktional ohne Unterbrechung weiter.

Client-Accounting und Monitoring

Die reale IP-Adresse eines QUIC-Clients im Accounting aktiver Peers (/data/http-clients, Limits konkurrierender Verbindungen, IP-basierte ACLs) ist die tatsächliche Peer-Adresse der QUIC-Verbindung und nicht der Loopback der internen Backend-Brücke.

Das Attribut ott-type in /data/http-clients ist zusammengesetzt, im Format <PROTO>/<scheme>:

  • PROTO — OTT-Protokoll: HLS, DASH oder HTTP.

  • scheme — der tatsächliche Netzwerktransport: http, https oder quic.

Beispiele: HLS/quic, DASH/https, HTTP/http. Das Admin-UI zeigt sowohl das OTT-Protokoll als auch den Netzwerktransport jedes Clients in einer einzigen Spalte.

Das OTT-Sitzungs-Timeout beträgt 60 Sekunden, unabhängig vom Transport. Wechselt ein Client zwischen Transporten, wird das Schema ausschließlich entlang der Prioritätskette httphttpsquic aufgewertet. Ein paralleler Rückfall auf eine weniger gesicherte Verbindung überschreibt den aktuellen Typ nicht — im Accounting verbleibt der sicherste beobachtete Transport.

Player-Kompatibilität

HTTP/3 für OTT unterstützen:

  • iOS AVPlayer / Safari — nativ, über Alt-Svc; 0-RTT wird unterstützt.

  • Chrome, Firefox, Edge, Brave — über Alt-Svc; HLS wird mit hls.js, DASH mit dash.js / Shaka wiedergegeben; der Player-Traffic erbt HTTP/3 vom Browser-fetch-API.

  • Android ExoPlayer — über die Cronet-QUIC-Engine (nicht der Standardtransport; erfordert clientseitige Konfiguration).

VLC (libVLC) unterstützt HTTP/3 nicht — auf diesen Clients funktioniert der Stream weiter über HTTPS/TCP, ohne den QUIC-Vorteil.

Der praktische Vorteil von QUIC gegenüber HTTPS/TCP zeigt sich am deutlichsten in Mobilfunknetzen / Wi-Fi / verlustbehafteten Netzen:

  • kein Head-of-Line-Blocking auf TCP-Ebene — ein Verlust in einem HTTP-Stream blockiert die übrigen nicht;

  • 0-RTT-Handshake bei wiederaufgenommenen Verbindungen desselben Clients;

  • stabilere ABR-Bitrate bei Paketverlusten von 1–5 %.

In verwalteten Netzen / Unternehmens-Wi-Fi fällt der Gewinn meist bescheiden aus — 5–10 % bei der Startup-Latenz und nahezu null im Steady-State. Die CPU-Last auf dem Server ist bei QUIC höher als bei Kernel-TCP — der Preis für TLS und Transport im User Space.

Caching-Modell für OTT HLS und DASH

Der Server liefert Antworten in drei Kategorien, die sich in Inhaltslebensdauer und Cache-Eignung in Zwischenknoten (Reverse Proxy, CDN, Client-Cache) unterscheiden.

1. Caching-Modell

1.1. Ressourcen und HTTP-Header

Ressource

URL

Content-Type

Cache-Control

TS-Segment (HLS)

/h<sess>/<keyHex>.ts, /h<sess>/sub/<pid>/<keyHex>.vtt

video/mp2t

public, max-age=60, immutable

fMP4-Segment (DASH / LL-HLS)

/h<sess>/init.mp4, /h<sess>/<key|N>.m4s

video/mp4

public, max-age=60, immutable

DASH MPD

/h<sess>/index.mpd

application/dash+xml; charset=utf-8

public, max-age=1

HLS master

/hls/<stream>/<login>/<pass>/index.m3u8

application/vnd.apple.mpegurl

public, max-age=1

HLS media

/h<sess>/index.m3u8, /h<sess>/sub/<pid>/index.m3u8

application/vnd.apple.mpegurl

public, max-age=1

302 Redirect

/dash/<stream>/<login>/<pass>/index.mpd

no-cache, no-store

Raw TS

/http/<stream>/<login>/<pass>

video/mp2t

nicht gesetzt; nicht gecacht

1.2. Segmenteigenschaften

Der hexadezimale Segment-Identifier in der URL (<keyHex> in den Pfaden /h<sess>/<keyHex>.ts) wird als CRC64 über die Segment-Startzeit und die Stream-ID gebildet und ist global eindeutig. Die Segment-URL adressiert unveränderliche Inhalte — bei wiederholten Anfragen an dieselbe URL wird ein identischer Bytestrom zurückgegeben (solange das Segment innerhalb des Schiebefensters bleibt).

Die Direktive immutable unterdrückt die bedingte Revalidierung durch den Client (If-None-Match, If-Modified-Since). max-age=60 ist mit dem typischen timeShiftBufferDepth=40s kompatibel.

CMAF-fMP4-Segmente (.m4s) und die gemeinsame init.mp4 für DASH / Low-Latency HLS werden analog adressiert und nach demselben Modell zwischengespeichert (immutable, max-age=60). Partielle Segmente (parts) von LL-HLS sind Byte-Range-Anfragen in dieselbe .m4s, sodass sie keinen separaten Cache-Eintrag bilden.

1.3. Manifest-Eigenschaften

max-age=1 begrenzt die obere Schranke der Inhalts-Veralterung im Cache auf eine Sekunde. Zusammen mit proxy_cache_lock on (nginx) werden Anfragenspitzen am Manifest zu einer einzigen Origin-Anfrage pro Sekunde zusammengefasst.

1.4. Inhaltsvariabilität

Bei absPath=0 (Standard; ohne URL-Parameter a) enthalten HLS-media- und DASH-MPD-Manifeste keinen Sitzungs-Identifier im Body. Der Manifest-Inhalt ist zwischen Sitzungen identisch, die zur selben (stream, param)-Kombination gehören. Dadurch kann ein Reverse-Proxy-Cache bei normalisiertem Cache-Key einen einzigen Eintrag sitzungsübergreifend wiederverwenden.

Bei absPath=1 (URL-Parameter a=1) enthält der Manifest-Body absolute URLs einschließlich Schema, Host und Sitzungs-Identifier. Der Inhalt wird sitzungsspezifisch; eine sitzungsübergreifende Cache-Wiederverwendung ist nicht möglich.

2. Client-Verhalten

Klient

Manifest-Refresh-URL

Auswirkung auf die Sitzungszahl

VLC 3.x HLS

/h<sess>/index.m3u8

Eine Sitzung pro Wiedergabe

VLC 3.x DASH

/dash/<stream>/.../index.mpd

Wird per Session-Reuse behandelt (siehe 3.3)

ffmpeg 5.x HLS

/h<sess>/index.m3u8

Eine Sitzung pro Wiedergabe

ffmpeg 5.x DASH

/dash/<stream>/.../index.mpd (Wiederholungsschleife)

Wird per Session-Reuse behandelt (siehe 3.3)

dash.js, hls.js

/h<sess>/... über <Location> / Session-URL

Eine Sitzung pro Wiedergabe

3. Spezielle Mechanismen

3.1. HTTP 302 Redirect für DASH

Eine Anfrage der Form /dash/<stream>/<login>/<pass>/index.mpd liefert die Antwort 302 Found mit dem Header Location: /h<sess>/index.mpd. Der Antwort-Body ist leer. Authentifizierung und Sitzungs-Allokation finden in der Phase der Redirect-Verarbeitung statt.

Clients, die Redirect-Caching unterstützen, greifen in nachfolgenden Anfragen direkt auf die Sitzungs-URL zu. Clients ohne Unterstützung wiederholen die Redirect-Anfrage. Die Kosten der Redirect-Wiederverarbeitung beschränken sich auf Authentifizierungsprüfung und Session-Reuse-Operationen.

3.2. Session-Reuse für DASH

Bei der Verarbeitung einer /dash/.../index.mpd-Anfrage desselben Logins an denselben Stream (mit demselben adaptive-Merkmal) findet der Server eine bereits bestehende DASH-Sitzung und gibt deren Kennung erneut zurück. Es wird keine neue Sitzung erstellt; ein Platz im Limit gleichzeitiger Verbindungen wird nicht verbraucht.

Gilt nur für DASH. Für HLS ist kein eigener Reuse-Mechanismus erforderlich: HLS-Clients aktualisieren die Media-Playlist über die Session-URL und erzeugen bei jedem Refresh keine neue Sitzung.

3.3. Wiederverwendung von Segmenten zwischen Sitzungen

Der Pfad /h<sess>/<keyHex>.ts ist unabhängig von <sess>, wenn <keyHex> auf Inhalt aufgelöst wird: <keyHex> identifiziert ein TS-Segment innerhalb eines Streams global eindeutig. Nginx mit einem normalisierten Cache-Key (Entfernen des Präfixes /h<sess>/) bedient jede Anfrage nach demselben <keyHex> aus einem einzigen Cache-Eintrag, unabhängig davon, welche Clients sie gestellt haben.

Dasselbe gilt für CMAF-fMP4-Segmente (.m4s) und init.mp4: Ihr Inhalt ist unveränderlich und wird innerhalb des Streams adressiert, sodass die Normalisierung des Cache-Keys dieselbe sitzungsübergreifende Deduplizierung im Cache ergibt.

4. Anfrageparameter

Parameter

Standardwert

Auswirkung

a

0

1 — absolute URLs in den Manifesten; 0 — relative

s

40

timeShiftBufferDepth in Sekunden

m

40

Mindestfensterlänge für die Manifest-Ausgabe

v

6 für OTT/HLS und OTT/HLS/LL-HLS/LL-Dash, 3 für Peering/HLS

#EXT-X-VERSION in HLS (von DASH ignoriert); ein expliziter Wert in der URL überschreibt den Default

h3

fehlt (off)

Opt-in für HTTP/3 (QUIC) — veranlasst den Server, Alt-Svc im Response auszugeben. Erkannte Werte: presence ⇒ on, 1/on/yes/true ⇒ on, 0/off/no/false ⇒ explicit off. Sticky auf der Session-URL /h<sess>/.... Siehe Abschnitt HTTP/3 (QUIC).

Das Ändern eines Parameters per Query-String aktualisiert die in der Sitzung gespeicherten Werte beim nächsten erneuten Öffnen der Sitzung.

5. Lastcharakteristiken

Die Origin-Last skaliert mit der Anzahl gleichzeitig beobachteter unterschiedlicher Streams. Die Erhöhung der Anzahl gleichzeitig denselben Stream beobachtender Clients erhöht die Anzahl der Origin-Anfragen nicht, sofern ein Reverse-Proxy-Cache mit normalisiertem Cache-Key vorhanden ist.

Szenario

Origin-Request-Rate (Ref.)

1 Client pro Stream X

MPD: 0.4 req/s, segment: 0.2 req/s

N Clients auf einem Stream X (Cache aktiv)

MPD: 1 req/s, segment: 0.2 req/s

N ffmpeg-Clients im Replay-Modus auf einem Stream

MPD: 1 req/s (mit proxy_cache_lock)

N Clients auf N verschiedene Streams

MPD: 0.4·N req/s, segment: 0.2·N req/s

6. Nginx als cachender Reverse-Proxy

6.1. Basiskonfiguration

proxy_cache_path /var/cache/nginx/pss_segments
    levels=1:2 keys_zone=pss_segments:100m
    max_size=20g inactive=30m use_temp_path=off;

proxy_cache_path /var/cache/nginx/pss_manifests
    levels=1:2 keys_zone=pss_manifests:10m
    max_size=256m inactive=5m use_temp_path=off;

upstream pss_backend {
    server 127.0.0.1:41972;
    keepalive 64;
}

map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "stream:$tail";
    default                                     $uri;
}

server {
    listen 80;
    server_name stream.example.com;

    location ~* "^/h[0-9a-f]{16}(/[0-9]+)?/([0-9a-f]+\.(ts|m4s)|init\.mp4)$" {
        proxy_cache               pss_segments;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 60s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_use_stale     updating error timeout;
        proxy_cache_revalidate    on;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_buffering           on;
    }

    location ~* "(^/h[0-9a-f]{16}(/[0-9]+)?/index\.(m3u8|mpd)$|^/(hls|dash)/.*\.(m3u8|mpd)$)" {
        proxy_cache               pss_manifests;
        proxy_cache_key           $pss_cache_key;
        proxy_cache_valid         200 1s;
        proxy_cache_valid         404 403 0s;
        proxy_cache_lock          on;
        proxy_cache_lock_timeout  2s;
        proxy_cache_use_stale     updating;
        add_header                X-Cache-Status $upstream_cache_status;

        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
    }

    location / {
        proxy_pass                http://pss_backend;
        proxy_http_version        1.1;
        proxy_set_header          Connection "";
        proxy_set_header          X-Forwarded-Proto $scheme;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_buffering           off;
        proxy_read_timeout        3600s;
    }
}

6.2. Zweck der Direktiven

Direktive

Zweck

proxy_cache_lock on

Serialisiert Upstream-Anfragen bei parallelen Cache-Misses auf denselben Schlüssel

proxy_cache_use_stale updating

Liefert die veraltete Kopie an parallele Anfragen während der Cache-Aktualisierung

proxy_cache_revalidate on

Nutzt If-Modified-Since bei Cache Miss mit vorhandener Kopie

proxy_cache_valid 404 403 0s

Verbietet das Caching von Authorization-Fehlern und 404

keepalive 64 im Upstream

Verwaltet einen Pool persistenter Verbindungen zum Origin

proxy_buffering on

Für Segmente; aktiviert die Antwortpufferung in nginx

proxy_buffering off

Für den Bereich /; deaktiviert Buffering (Raw Streaming)

6.3. Berechnung der max_size des Segmentcaches

Richtwert: bitrate × timeShiftBufferDepth × distinct_streams × 2

Beispiel: 10 Streams × 8 Mbps × 40 s × 2 ≈ 800 MB. Es wird empfohlen, einen 10-fachen Sicherheitsfaktor für Bitratenschwankungen einzuplanen.

6.4. TLS-Terminierung

Der Perfect-Streamer-Server akzeptiert Verbindungen auf HTTP- und HTTPS-Ports. Bei TLS-Terminierung am nginx verwendet das Upstream den HTTP-Port. Die Weiterleitung der Header X-Forwarded-Proto und X-Forwarded-Host ist erforderlich für die korrekte Bildung absoluter URLs bei absPath=1.

server {
    listen 443 ssl http2;
    server_name stream.example.com;

    ssl_certificate           /etc/letsencrypt/live/stream.example.com/fullchain.pem;
    ssl_certificate_key       /etc/letsencrypt/live/stream.example.com/privkey.pem;
    ssl_protocols             TLSv1.2 TLSv1.3;
    ssl_session_cache         shared:SSL:10m;
    ssl_session_timeout       1d;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location ... {
        proxy_pass                http://pss_backend;
        proxy_set_header          X-Forwarded-Proto https;
        proxy_set_header          X-Forwarded-Host  $host;
        proxy_set_header          Host              $host;
        # + caching directives from 6.1
    }
}

server {
    listen 80;
    server_name stream.example.com;
    return 301 https://$host$request_uri;
}

Bei HTTPS zwischen nginx und Origin gelten proxy_ssl_verify und proxy_ssl_trusted_certificate. Bei Loopback-Verbindungen ist Verschlüsselung überflüssig.

6.5. Multi-host

Wenn ein nginx-Prozess mehrere server_name bedient, wird $host zum Cache-Schlüssel hinzugefügt, um Inhalte zu isolieren:

map $uri $pss_cache_key {
    ~^/h[0-9a-f]{16}(?<tail>/.+\.(ts|m3u8))$  "$host:stream:$tail";
    default                                     "$host:$uri";
}

Die keys_zone-Größe wird mit 8000 Keys/MB berechnet. Für Multi-Host-Installationen mit Tausenden von Streams werden keys_zone=...:300m oder mehr empfohlen.

7. Client-seitiges Caching

Cache-Control: immutable wird von den Browsern Chrome/Firefox/Safari berücksichtigt. Der Client-Cache liefert das Segment bei wiederholtem Zugriff ohne bedingte Anfrage zurück (auch bei Rückwärts-Seek innerhalb des Player-Buffers).

Service Workers können basierend auf dem Cache-Control-Inhalt eine cache-first-Strategie anwenden. DASH-Player (dash.js, Shaka) nutzen MSE über SourceBuffer; ein in den Buffer eingelegtes Segment bleibt ohne erneute HTTP-Anfrage verfügbar, bis es das Schiebe-Fenster verlässt.

Für Cross-Domain-Anfragen erlaubt der Header Access-Control-Allow-Origin: * Caching in shared caches ohne Vary: Origin. Beim Wechsel des ACAO-Werts auf einen konkreten Origin wird Vary: Origin erforderlich, was die Effizienz des shared cache reduziert.

8. Verteilung über CDN

Perfect Streamer ist mit Pull-from-Origin-CDNs kompatibel (Cloudflare, Akamai, Fastly, BunnyCDN, Amazon CloudFront).

Origin shield. Empfohlen werden ein oder mehrere Shield-Knoten zwischen CDN-Edge und Origin, um die Origin-Anfragerate bei global verteilten Clients zu senken.

Purge. Content-addressed Segmente erfordern keinen Purge. Bei Stream-Metadaten-Änderungen (Codec, Auflösung) aktualisieren sich Manifeste innerhalb von max-age=1 ohne expliziten Purge.

Cache Warming. Bei erwartetem Lastanstieg auf einen Stream darf das CDN von mehreren Standorten aus vor Sendebeginn vorgeheizt werden.

Geo-Verteilung. Segmente (max-age=60) eignen sich gut für geografisch verteiltes Caching. Manifeste (max-age=1) tolerieren bis zu eine Sekunde Lieferverzögerung — akzeptabel für non-low-latency live.

9. Überwachung

9.1. X-Cache-Status

add_header X-Cache-Status $upstream_cache_status; in jeder gecachten Location ergänzen. Werte:

Wert

Beschreibung

HIT

Antwort aus Cache

MISS

War nicht im Cache; vom Origin geholt und gespeichert

EXPIRED

Abgelaufen, erneuert

UPDATING

Stale-Kopie an parallele Anfrage während der Aktualisierung ausgeliefert

STALE

use_stale lieferte die abgelaufene Kopie (Origin nicht erreichbar)

REVALIDATED

Origin lieferte 304 Not Modified

BYPASS

proxy_cache_bypass wurde ausgelöst

9.2. Access-Log-Format

log_format pss_cache '$remote_addr $status $request_method "$request" '
                     '$body_bytes_sent rt=$request_time ut=$upstream_response_time '
                     'cache=$upstream_cache_status key=$pss_cache_key';

server {
    access_log /var/log/nginx/pss.log pss_cache;
}

9.3. Metriken

Das Modul nginx-vts exportiert Per-Zone-Metriken im Prometheus-Format:

GET /status/format/prometheus

Empfohlene Schwellwerte für Alerts:

Metrik

Schwelle

Mögliche Ursache

Segment HIT rate

< 90 % über 5 Minuten

Cache-Key-Normalisierung defekt; max_size zu klein

Manifest MISS rate

> 50 % über 1 Minute

proxy_cache_lock serialisiert die Anfragen nicht

Upstream response time p95

> 500 ms über 1 Minute

Origin-Überlastung

Cache zone fill

> 90 % über 10 Minuten

Annäherung an max_size; LRU-Eviction wird geplant

10. Diagnose

Symptom

Wahrscheinliche Ursache

Lösung

Niedrige Segment-HIT-Rate

Vary: Origin mit hoher Origin-Variabilität; defekte Normalisierung in map

Header und Regex in der map-Direktive prüfen

404 bei Segmenten nach Verlassen des Fensters

Gecachter 404 bei Segment, das aus dem Sliding Window gefallen ist

proxy_cache_valid 404 0s in der Segments-Location ergänzen

Playback-Start-Verzögerung 2–5 s

proxy_cache_lock_timeout überschreitet die Ziel-Latenz

Auf 1–2 s senken; proxy_cache_use_stale updating aktivieren

Manifest aktualisiert nicht

proxy_cache_valid überschreibt max-age

proxy_cache_valid 200 1s explizit setzen

Wachsendes TIME_WAIT am Upstream

keepalive fehlt im Upstream-Block

keepalive 64, proxy_http_version 1.1 und proxy_set_header Connection "" hinzufügen

403 bei /dash/.../<segment>.m4s von ffmpeg

Der Client löst relative URLs gegen die Pre-Redirect-URL auf

Der Server gibt <BaseURL>/h<sess>/</BaseURL> aus (absolute Pfade); im aktuellen Build kompatibel

Lags, häufiges Rebuffering bei entfernten Clients

Niedriger effektiver TCP-Durchsatz aufgrund von Slow Start und Idle Restart bei großen RTTs (300 ms und mehr)

Tuning des Linux-Netzwerk-Stacks auf dem Origin: siehe 10.1

10.1. TCP-Tuning des Origin für High-RTT-Clients

Das Problem tritt bei Clients mit großem RTT zum Origin (z. B. 300 ms und mehr) auf, wenn die Stream-Bitrate nahe an der Kanalkapazität liegt. Symptome im Player (VLC, ffmpeg, dash.js) — häufiges Rebuffering, Warnungen wie ES_OUT_SET_PCR called too late (mit Anstieg von pts_delay), buffer deadlock prevented, Stream-Abbrüche. Auf dem Server wirkt der Client dabei normal, Fehler gibt es keine, und der Durchsatz auf /data/stream/... entspricht dem Eingangsstrom.

Ursache:

  • TCP slow start. Jede neue TCP-Verbindung beginnt mit einem Congestion Window von etwa 14 KB und vergrößert es über mehrere RTTs. Bei einem RTT von 300 ms dauert das Erreichen des vollen Fensters 2-3 Sekunden. In dieser Zeit wird ein HLS/DASH-Segment von 5 s Dauer (4-6 MB) merklich langsamer als in Echtzeit heruntergeladen.

  • TCP idle restart. Zwischen Segmentanfragen pausiert ein HLS-Pull-Modell-Client 4-5 s. Standardmäßig setzt der Linux-Kernel nach einer solchen Pause das Congestion Window der Verbindung auf das Initial cwnd zurück (Verhalten net.ipv4.tcp_slow_start_after_idle=1). Folglich beginnt die Keep-Alive-Verbindung beim nächsten GET die Übertragung erneut aus dem Slow Start — auch bei einer bereits aufgewärmten Sitzung.

Eine zusätzliche Verschärfung — das standardmäßige CUBIC-Congestion-Control kommt mit langen RTTs und Paketverlusten auf zwischengeschalteten Netzabschnitten schlecht zurecht.

Lösung — zwei sysctl-Parameter auf dem Origin:

# Keep congestion window across idle pauses inside keep-alive sessions.
sysctl -w net.ipv4.tcp_slow_start_after_idle=0

# Use BBR instead of CUBIC: better behaviour on long-RTT paths
# with mild packet loss; paces sending instead of bursting.
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr

Für eine dauerhafte Anwendung:

cat > /etc/sysctl.d/99-pss-net.conf <<EOF
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_congestion_control    = bbr
EOF
sysctl --system

Den Haupteffekt liefert der erste Parameter (tcp_slow_start_after_idle=0). Er beseitigt direkt den erneuten Slow Start zwischen Segment-Anfragen innerhalb einer Keep-Alive-Verbindung. Der zweite (BBR) bietet zusätzliche Robustheit und gilt für alle neuen Verbindungen.

Das Tuning erfordert keinen Neustart von Perfect Streamer und gilt unmittelbar nach sysctl -w für alle neuen TCP-Verbindungen. Bestehende Verbindungen behalten das Congestion Control, mit dem sie aufgebaut wurden.

11. Sicherheit

11.1. Session URL

Eine URL der Form /h<sess>/... erfüllt die Funktion eines Sitzungs-Tokens — eine erneute Authentifizierung ist nicht nötig. Die Lebensdauer ist durch das idle timeout begrenzt (Wert 30 s). Bei Inaktivität wird die Sitzung von der cleaner-Aufgabe entfernt.

Anforderungen:

  • HTTPS für alle OTT-Pfade (/hls/, /dash/, /h<sess>/) in Produktion

  • Die Session-ID im Location-Header der 302 wird nicht gecacht (no-cache, no-store)

11.2. Rate limiting

limit_req_zone $binary_remote_addr zone=dash_top:10m  rate=5r/s;
limit_req_zone $binary_remote_addr zone=hls_top:10m   rate=5r/s;
limit_req_zone $binary_remote_addr zone=llhls_top:10m rate=5r/s;

server {
    location /dash/ {
        limit_req zone=dash_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /hls/ {
        limit_req zone=hls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
    location /llhls/ {
        limit_req zone=llhls_top burst=20 nodelay;
        proxy_pass http://pss_backend;
    }
}

Session-URLs (/h<sess>/) benötigen kein Rate Limiting — die Verarbeitung ist günstig, Antworten werden gecacht.

11.3. Caching von Fehlerantworten

proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;

Verbietet das Caching von Redirects (eindeutiges sess in Location) und von Antworten mit Auth- oder Not-Found-Fehlern.

11.4. Einschränkung des Netzwerkzugriffs auf den Origin

Port 41972 (41982 für HTTPS) muss für externen Verkehr geschlossen sein. Zulässige Konfigurationen:

  1. Perfect Streamer an 127.0.0.1 binden (bei lokalem nginx)

  2. Firewall-Regel:

iptables -A INPUT -p tcp --dport 41972 ! -s 10.0.0.0/8 -j DROP

12. Middleware-Integration

12.1. Prefix-Login-Modell

Perfect Streamer kann die Nutzeridentifikation über Prefix-Login an Middleware/Billing-Systeme delegieren. Ein externer Connector zum Billing-System ist im aktuellen Release nicht enthalten.

Konfiguration des Embedded-Benutzers:

{
  "id": 9,
  "login": "sub",
  "password": "xxx",
  "is-prefix": true,
  "max-conn-http-hls": 1,
  "accept-stream": [ ... ]
}

Mit is-prefix: true akzeptiert der Server URLs mit Logins der Form <prefix><billing_user_id>:

/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8

12.2. Statistikformat

<clients>
  <client login-id="-1974387287" login="sub" match-login="sub42"
          sess-id="11331..." ott-type="dash" stream-id="10000" .../>
  <client login-id="-2147031294" login="sub" match-login="sub43"
          sess-id="11132..." ott-type="dash" stream-id="10000" .../>
</clients>

Das Feld login-id enthält den Hash des URL-Logins. login ist der konfigurierte Wert. match-login ist der vom Client genutzte URL-Login.

12.3. Einschränkungen des Prefix-Logins

  • Gemeinsames Passwort. Alle Teilnehmer des Prefix-Pools nutzen einen einzigen Passwortwert. Eine Kompromittierung des Passworts gewährt Zugriff auf jedes <prefix><string>.

  • ACL-Granularität. accept-stream gilt für den gesamten Prefix-Pool; eine subscriber-spezifische ACL gibt es ohne externes Billing nicht.

  • Passwort-Rotation. Eine Passwortänderung trennt alle aktiven Teilnehmer. Für eine schrittweise Umstellung sind vorübergehend zwei Prefix-Logins erforderlich.

13. WebVTT-Untertitel

Die Untertitelquelle ist DVB Teletext / DVB Subtitling aus dem Eingangs-MPEG-TS. In den Abschnitten Media Information oder Original Media Information müssen Teletext-Untertitel-Spuren vorhanden sein. Im Abschnitt Analyzer kann zusätzlich überprüft werden, dass Pakete der entsprechenden PIDs aktiv sind.

Für OTT HLS/DASH muss der OTT-Modus aktiviert sein (im Peering/HLS sind WebVTT-Untertitel nicht verfügbar). Im Abschnitt Output # OTT muss der Chunk-Zähler OTT WebVTT buffer chunk count einen Wert ungleich null aufweisen.

Zur Diagnose der Untertitel Analyze und Trace am Stream aktivieren. Beim Stream-Start sollte im Stream-Log Folgendes erscheinen:

Start Teletext subtitle decoder
[ttxsubdec] ttx: pid=331 magazine=8 page=0x88 lang=***

Im Folgenden wird der dekodierte Untertiteltext in das Log geschrieben.

13.1. URLs der VTT-Segmente

Schema

URL

Inhalt

HLS master

/hls/.../index.m3u8

#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",...,URI="/h<sess>/sub/<pid>/index.m3u8"

HLS subtitle playlist

/h<sess>/sub/<pid>/index.m3u8

Liste <keyHex>.vtt mit #EXTINF

HLS-VTT-Segment

/h<sess>/sub/<pid>/<keyHex>.vtt

VTT mit HLS-spezifischer X-TIMESTAMP-MAP

DASH MPD AdaptationSet

in index.mpd

contentType="text" mimeType="text/vtt" + <SegmentTemplate media="$Number$.vtt">

DASH-VTT-Segment

/h<sess>/sub/<pid>/<seq>.vtt

VTT mit DASH-spezifischer X-TIMESTAMP-MAP

<keyHex> ist ein 16-stelliger Hex-CRC64 aus Segment-Startzeit, Stream-ID und Untertitelspur-PID. <seq> ist die dezimale laufende Nummer eines Untertitel-Stream-Chunks (die Untertitel-Nummerierung ist von der TS-Chunk-Nummerierung unabhängig).