Service OTT

Diffuse des flux via des protocoles basés sur HTTP — HLS (sur MPEG-TS), MPEG-DASH et Low-Latency HLS (sur CMAF — MP4 fragmenté, depuis la version 1.13), ainsi que MPEG-TS over HTTP. HTTPS (SSL) et HTTP/3 (QUIC) sont pris en charge. La diffusion s’active dans l’onglet OTT des paramètres Stream.

Les URL de connexion ont le format :

host et port se définissent dans les paramètres de http server.

streamID du stream. Ne pas confondre avec le numéro d’ordre dans la liste des streams. L”ID est affiché en haut de la page de statistiques du stream et dans la colonne ID de la liste des streams ; il est défini à la création du stream et ne change jamais.

De même pour HLS, DASH et Low-Latency HLS (les deux derniers — uniquement en OTT/HLS/LL-HLS/LL-Dash, voir ci-dessous) :

Sur la page de statistiques du flux s’affichent les URL des protocoles connectés (sous forme de modèle) et leur statut. L’accès non autorisé est interdit ; les clients doivent être déclarés dans Peers.

Pour HLS et DASH, des paramètres supplémentaires sont disponibles dans l’URL (optionnels) :

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

  • a : 1 — chemin absolu dans la playlist, 0 — chemin relatif (par défaut).

  • s : durée de la playlist dynamique (s), 40 s par défaut.

  • m : durée minimale de la playlist dynamique (s), 40 s par défaut. Taille maximale de la playlist dynamique 60 s. Si la taille actuelle du buffer de chunks est inférieure à la taille minimale demandée dans la requête, une erreur 404 est retournée. Cela garantit que HLS démarre avec un buffer de chunks rempli côté serveur.

  • v : la version du protocole HLS émise dans la playlist. Par défaut, la valeur dépend du mode HLS (voir ci-dessous) : OTT/HLS et OTT/HLS/LL-HLS/LL-Dash6, Peering/HLS3. Une valeur explicite dans l’URL remplace la valeur par défaut. Changer de version peut être nécessaire pour la compatibilité avec un client HLS particulier.

  • h3 : opt-in à HTTP/3 (QUIC) pour cette session OTT. Force le serveur à émettre un en-tête Alt-Svc dans la réponse, après quoi un lecteur / navigateur compatible bascule sur QUIC. Voir la section HTTP/3 (QUIC) ci-dessous.

Pour la compatibilité avec certains clients HLS, on peut ajouter le nom de fichier index.m3u8 à l’URL, par ex. http://host:port/hls/stream/login/password/index.m3u8.

Le mode de diffusion est défini par le paramètre de flux OTT HLS (onglet OTT) : Peering/HLS, OTT/HLS ou OTT/HLS/LL-HLS/LL-Dash.

Peering/HLS — un mode avec découpage simple en segments (chunks). Recommandé pour le peering (la distribution) des flux. Seul HLS sur MPEG-TS (/hls) est diffusé. Par défaut, la playlist est servie en EXT-X-VERSION:3 pour la compatibilité avec les clients pairs.

OTT/HLS — un mode avec un découpage des segments optimisé pour un démarrage rapide des lecteurs en diffusion OTT. Dans ce mode, la charge CPU est plus élevée ; il est recommandé pour la diffusion. HLS sur MPEG-TS (/hls) est diffusé. Par défaut, la playlist est servie en EXT-X-VERSION:6 avec EXT-X-INDEPENDENT-SEGMENTS et l’attribut CHARACTERISTICS dans EXT-X-MEDIA TYPE=SUBTITLES (Apple HLS, hls.js, Safari, dash.js/Shaka). Si un client particulier a besoin d’une valeur antérieure, définissez-la via le paramètre de requête ?v= (voir la liste des paramètres ci-dessus).

OTT/HLS/LL-HLS/LL-Dash — un mode de diffusion sur CMAF (MP4 fragmenté, fMP4 ; depuis la version 1.13). Le flux produit des segments fMP4 (.m4s + un init.mp4 commun, mimeType="video/mp4"), au-dessus desquels sont diffusés :

  • MPEG-DASH sur /dash — désormais sur CMAF, et non sur MPEG-TS ;

  • Low-Latency HLS sur /llhls (voir Low-Latency HLS ci-dessous) ;

  • avec le paramètre de flux Enable TS Chunk activé (par défaut true) — en plus, du HLS legacy sur MPEG-TS (/hls), comme en OTT/HLS ; avec false, seuls les segments fMP4 sont diffusés, ce qui économise le disque et le CPU.

La playlist HLS en OTT/HLS/LL-HLS/LL-Dash est elle aussi EXT-X-VERSION:6 par défaut.

Note

MPEG-DASH et Low-Latency HLS ne sont disponibles qu’en OTT/HLS/LL-HLS/LL-Dash. En OTT/HLS et Peering/HLS, seul HLS sur MPEG-TS est diffusé.

SSL (HTTPS) peut être activé sur le serveur HTTP via ses paramètres.

Chunk Min Interval et Chunk Max Interval

En mode OTT, le flux est analysé pour PAT/PMT/SPS/PPS/IFrame et les chunks sont découpés selon le critère de démarrage rapide des lecteurs. L’analyse commence à min interval, et si pour une raison quelconque les données ne sont pas trouvées, le chunk est forcément découpé à max interval.

GOP-aligned segments

En OTT/HLS, le segmenteur aligne les limites des chunks sur les points d’accès aléatoire et distingue IDR d’une I-frame ordinaire. Si la source diffuse en closed-GOP (IDR présent), chaque segment TS .ts commence à coup sûr par SPS / PPS / IDR (pour HEVC — également VPS) — un véritable point d’entrée à partir duquel le lecteur ouvre le flux. Si la source est en open-GOP / sans IDR, la limite est l’I-frame la plus proche (comportement antérieur). Le segmenteur retire la « queue » du GOP précédent — les slices P / B — de la fenêtre comprise entre le PAT en tête et le premier SPS du chunk. Cela :

  • élimine la trame noire initiale au démarrage d’une session VOD (?t= / ?epg=) dans hls.js, Safari et VLC : le décodeur MSE reçoit l’ensemble d’en-tête correct d’unités NAL dès le premier segment et démarre immédiatement ;

  • aligne la sortie sur HLS RFC 8216 §3 (« Each Media Segment MUST contain a SPS and a PPS that decode its first Access Unit ») ;

  • rend correcte la déclaration EXT-X-INDEPENDENT-SEGMENTS dans une playlist EXT-X-VERSION:6.

Contrôle via le paramètre par flux gop-aligned-segment (par défaut true). Ne s’applique qu’en HLS = OTT/HLS : en Peering/HLS et pour les flux MPTS, le comportement est inchangé. Le PSI (PAT / PMT) et l’audio sont préservés en intégralité ; le seul effet secondaire est un saut d’une seule image du continuity_counter sur la PID vidéo à la frontière entre deux chunks adjacents, ce qui constitue une decode boundary légitime pour HLS / DASH MSE.

Lors de l’alignement sur les points d’entrée, la durée réelle du chunk peut être arrondie au-dessus de Chunk Min Interval. Par conséquent, dans la playlist live, la valeur EXT-X-TARGETDURATION reflète la durée de segment réelle maximale (section 4.3.3.1 de RFC 8216) plutôt que le minimum configuré. Cela maintient le manifeste conforme à la norme : hls.js et les lecteurs compatibles ne réduisent pas l’intervalle de rafraîchissement de la playlist et n’émettent pas de faux bufferStalledError.

Low-Latency HLS (/llhls)

En OTT/HLS/LL-HLS/LL-Dash, en plus de /dash, un endpoint Low-Latency HLS distinct est disponible — diffusion à latence réduite sur les mêmes segments fMP4 CMAF :

La playlist média est découpée en segments partiels (parts, directives #EXT-X-PART) : le lecteur démarre la lecture sans attendre que le segment complet soit prêt. Sont utilisés le rechargement bloquant de la playlist (le serveur retient la requête jusqu’à ce que le part suivant soit prêt) et l’indice de préchargement #EXT-X-PRELOAD-HINT.

La durée cible d’un part est définie par le paramètre de flux Part Target Duration (ms ; par défaut 500, plage 100–5000). La valeur est appliquée à la volée, sans redémarrer le flux, et doit être inférieure à Chunk Min Interval.

HLS / DASH / LL-HLS Adaptive Multistream

HLS Adaptive Multistream est pris en charge depuis la version 1.10, DASH Adaptive Multistream depuis la version 1.12 (depuis la version 1.13 — sur CMAF), et Low-Latency HLS Adaptive depuis la version 1.13.

Pour les flux adaptatifs, une playlist distincte est configurée. Pour cela :

  • Activer la diffusion OTT sur les flux qui feront partie de la playlist adaptative : OTT/HLS — pour le HLS adaptatif sur MPEG-TS ; OTT/HLS/LL-HLS/LL-Dash — pour les DASH / Low-Latency HLS adaptatifs sur CMAF.

  • Une section des flux adaptatifs apparaît dans le menu principal. Il faut y ajouter un flux et y indiquer tous les flux qui doivent figurer dans cette playlist.

  • Les flux peuvent avoir un paramètre de débit. Par défaut, il vaut 0 — le débit est repris de la valeur mesurée ; sinon, il peut être défini explicitement.

Pour les playlists adaptatives, l’URL diffère :

Des restrictions d’accès aux flux adaptatifs peuvent être imposées aux pairs (clients), comme aux flux ordinaires. L’autorisation d’un flux adaptatif englobe l’autorisation de tous les flux qu’il contient.

HTTP/3 (QUIC)

Depuis la version 1.13.1.438, Perfect Streamer intègre un serveur HTTP/3 pour la diffusion OTT de HLS, MPEG-DASH et Low-Latency HLS sur QUIC (RFC 9000 + RFC 9114). La pile est ngtcp2 (QUIC v1) + nghttp3 (HTTP/3 frame layer) ; l’infrastructure TLS réutilise le même certificat que le listener HTTPS.

QUIC ne sert que les routes OTT :

  • / — redirection racine,

  • /hls/..., /dash/... et /llhls/... — master playlists / MPD,

  • /h<sessID>/... — URL par session (media playlists, segments, VTT),

  • /http/<stream>/... — MPEG-TS brut sur HTTP.

Low-Latency HLS et DASH sont diffusés sur QUIC de façon incrémentale (chunked) : les parts sont envoyées au client au fur et à mesure, sans attendre le segment complet.

Les chemins administratifs (/data, /config, /xmltv, /db/, /login, /logout, /restart) renvoient 404 sur QUIC — l’API d’administration reste sur HTTPS / HTTP-TCP. Il s’agit d’une restriction délibérée visant à réduire la surface d’attaque du listener QUIC.

Activation

Les paramètres QUIC se trouvent dans la section Configuration / HTTP server (nœud /config/http-server) :

  • HTTP/3 Enable (http3-enable) — drapeau global activant le listener QUIC. Valeur par défaut : off. L’activation ouvre un socket UDP sur http3-port ; la désactivation le ferme.

  • HTTP/3 Port (http3-port) — port UDP du listener QUIC. Valeur par défaut : 43984. QUIC fonctionne sur UDP et HTTPS sur TCP ; les ports peuvent coïncider ou différer, sans conflit entre eux.

  • HTTP/3 0-RTT Enable (http3-zero-rtt-enable) — autorise le handshake 0-RTT (RFC 9001 §4.6.1) lors de la reprise de connexion d’un même client. Réduit la latence de démarrage ; le risque de rejeu doit être pris en compte pour les requêtes non idempotentes (sans danger pour une charge OTT en lecture seule). Valeur par défaut : on.

Les modifications de configuration sont appliquées à chaud, sans redémarrage du service.

Le certificat et la clé sont repris du listener HTTPS — il n’existe pas de configuration de certificat distincte pour HTTP/3. Si HTTPS n’est pas configuré (ssl-enable=false), l’activation de HTTP/3 ne donne rien — le handshake n’aboutira pas.

Le paramètre ?h3 — opt-in par session

Un navigateur ne se connecte pas directement à un endpoint HTTP/3 — il passe d’abord par HTTPS/TCP, reçoit dans la réponse l’en-tête Alt-Svc: h3=":<port>"; ma=86400 (RFC 7838), et seules les requêtes suivantes vers cet origin sont basculées sur QUIC.

Dans Perfect Streamer, l’émission de Alt-Svc est un opt-in par session OTT. Le serveur n’annonce pas QUIC à tous les clients sans distinction : le client doit explicitement demander HTTP/3 via le paramètre de requête ?h3 sur l’URL master HLS / DASH.

Valeur dans l’URL

Valeur opt-in

Alt-Svc dans la réponse

paramètre absent

off (default)

non

?h3 (sans valeur)

on

oui

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

on

oui

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

explicit off

non (comme en l’absence)

Une fois que le client a obtenu l’URL de session /h<sess>/..., l’indicateur wantH3 est conservé dans la session — tous les rafraîchissements ultérieurs de media playlist, les GET de segments et de chunks VTT sous cette URL de session reçoivent également Alt-Svc, même si ?h3 est absent de la requête elle-même. Sans opt-in sur le master, aucun comportement sticky n’est instauré.

Gating Alt-Svc :

  1. Le listener QUIC est activé globalement (http3-enable=true) ; sinon l’annonce est supprimée même si ?h3 est défini.

  2. La requête n’est pas arrivée via QUIC — sur des requêtes déjà servies en H3, Alt-Svc n’a aucun sens et n’est pas émis.

  3. Par session ou par URL wantH3=true (voir le tableau ci-dessus).

  4. Les serveurs d’administration et EPG n’émettent jamais Alt-Svc — ils ne disposent d’aucun listener QUIC.

Ce comportement est conforme à la RFC 7838 §3 — le serveur décide lui-même sur quelles réponses émettre Alt-Svc.

Scénario de bascule vers QUIC dans le navigateur

Déroulement typique (Chrome / Firefox / Safari) :

  1. Le lecteur ouvre l’URL master avec un opt-in explicite :

    https://stream.example.com:41982/hls/test1/login/password/index.m3u8?h3=1
    
  2. La première requête passe par HTTPS/TCP. Dans la réponse, le serveur émet Alt-Svc: h3=":43984"; ma=86400.

  3. Le navigateur mémorise l’association stream.example.com:41982 h3=":43984". Dans Chrome elle est visible sur la page chrome://net-internals/#alt-svc ; dans Firefox — about:networking#http3.

  4. Sur les requêtes ultérieures vers le même origin (rafraîchissement de playlist, GET de segments, VTT), le navigateur ouvre une connexion QUIC sur udp/43984 et poursuit en HTTP/3. Dans DevTools → Network, la colonne Protocol affiche h3 pour ces requêtes.

Si la connexion QUIC ne peut être établie (port UDP bloqué par le pare-feu, certificat non approuvé par le système), le navigateur reste de manière transparente sur HTTPS/TCP — fonctionnellement, le flux continue à jouer sans interruption.

Comptabilisation des clients et supervision

L’adresse IP réelle d’un client QUIC dans la comptabilisation des pairs actifs (/data/http-clients, limites de connexions concurrentes, ACL par IP) correspond à l”adresse de pair effective de la connexion QUIC, et non au loopback du pont backend interne.

L’attribut ott-type dans /data/http-clients est composite, au format <PROTO>/<scheme> :

  • PROTO — protocole OTT : HLS, DASH ou HTTP.

  • scheme — le transport réseau effectif : http, https ou quic.

Exemples : HLS/quic, DASH/https, HTTP/http. L’interface d’administration affiche à la fois le protocole OTT et le transport réseau de chaque client dans une même colonne.

Le délai d’expiration d’une session OTT est de 60 secondes, quel que soit le transport. Lorsque le client change de transport, le schéma n’est mis à jour que vers le haut selon la chaîne de priorité httphttpsquic. Un repli parallèle vers une connexion moins sécurisée n’écrase pas le type courant — la comptabilisation conserve le transport le plus sécurisé observé.

Compatibilité des lecteurs

HTTP/3 pour l’OTT est pris en charge par :

  • iOS AVPlayer / Safari — nativement, via Alt-Svc ; 0-RTT est pris en charge.

  • Chrome, Firefox, Edge, Brave — via Alt-Svc ; HLS est joué via hls.js, DASH via dash.js / Shaka, et le trafic du lecteur hérite de HTTP/3 par l’API fetch du navigateur.

  • Android ExoPlayer — via le moteur QUIC Cronet (transport non utilisé par défaut, nécessite une configuration côté client).

VLC (libVLC) ne prend pas en charge HTTP/3 — sur ces clients le flux continue de fonctionner sur HTTPS/TCP, sans le bénéfice de QUIC.

Le gain réel de QUIC par rapport à HTTPS/TCP est surtout sensible sur les réseaux mobiles / Wi-Fi / les réseaux avec pertes :

  • pas de head-of-line blocking au niveau TCP — une perte dans un flux HTTP ne retarde pas les autres ;

  • handshake 0-RTT lors de la reprise de connexion d’un même client ;

  • débit ABR plus stable avec des pertes de paquets de 1 à 5 %.

Sur les réseaux gérés / les Wi-Fi d’entreprise, le gain reste habituellement modeste — 5 à 10 % sur la latence de démarrage et quasiment nul en régime établi. La charge CPU côté serveur est plus élevée pour QUIC que pour le TCP du noyau — c’est le coût d’une pile TLS + transport en espace utilisateur.

Modèle de mise en cache pour OTT HLS et DASH

Le serveur produit trois catégories de réponses qui diffèrent par leur durée de vie et leur aptitude à la mise en cache par les nœuds intermédiaires (reverse proxy, CDN, cache client).

1. Modèle de mise en cache

1.1. Ressources et en-têtes HTTP

Ressource

URL

Content-Type

Cache-Control

Segment TS (HLS)

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

video/mp2t

public, max-age=60, immutable

Segment fMP4 (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

non défini ; non mis en cache

1.2. Caractéristiques des segments

L’identifiant hexadécimal du segment dans l’URL (<keyHex> dans les chemins /h<sess>/<keyHex>.ts) est calculé comme un CRC64 sur l’instant de début du segment et l’ID du flux, et il est globalement unique. L’URL du segment adresse un contenu immuable — sur des requêtes répétées vers la même URL, un flux d’octets identique est renvoyé (tant que le segment reste dans la fenêtre glissante).

La directive immutable supprime la revalidation conditionnelle côté client (If-None-Match, If-Modified-Since). La valeur max-age=60 reste compatible avec un timeShiftBufferDepth=40s typique.

Les segments fMP4 CMAF (.m4s) et l”init.mp4 commun pour DASH / Low-Latency HLS sont adressés de manière analogue et mis en cache selon le même modèle (immutable, max-age=60). Les segments partiels (parts) de LL-HLS sont des requêtes byte-range dans le même .m4s, ils ne forment donc pas d’entrée de cache distincte.

1.3. Caractéristiques des manifestes

max-age=1 limite la limite supérieure d’obsolescence du contenu en cache à une seconde. Combiné à proxy_cache_lock on (nginx), les pics de requêtes vers le manifeste sont coalescés en une seule requête vers l’origine par seconde.

1.4. Variabilité du contenu

Avec absPath=0 (valeur par défaut, paramètre d’URL a absent), les manifestes HLS media et DASH MPD ne contiennent pas d’identifiant de session dans le corps. Le contenu du manifeste est identique entre les sessions appartenant à la même combinaison (stream, param). Cela permet au cache du reverse-proxy de réutiliser une seule entrée entre sessions lorsque la clé de cache est normalisée.

Avec absPath=1 (paramètre d’URL a=1), le corps du manifeste contient des URL absolues incluant le schéma, l’hôte et l’identifiant de session. Le contenu devient spécifique à la session ; la réutilisation du cache entre sessions n’est pas possible.

2. Comportement des clients

Client

URL de rafraîchissement du manifeste

Impact sur le nombre de sessions

VLC 3.x HLS

/h<sess>/index.m3u8

Une session par lecture

VLC 3.x DASH

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

Traité par session reuse (voir 3.3)

ffmpeg 5.x HLS

/h<sess>/index.m3u8

Une session par lecture

ffmpeg 5.x DASH

/dash/<stream>/.../index.mpd (boucle de répétition)

Traité par session reuse (voir 3.3)

dash.js, hls.js

/h<sess>/... via <Location> / URL de session

Une session par lecture

3. Mécanismes spéciaux

3.1. Redirection HTTP 302 pour DASH

Une requête de la forme /dash/<stream>/<login>/<pass>/index.mpd renvoie une réponse 302 Found avec l’en-tête Location: /h<sess>/index.mpd. Le corps de la réponse est vide. L’authentification et l’allocation de la session ont lieu pendant le traitement de la redirection.

Les clients qui prennent en charge le caching de redirection accèdent directement à l’URL de session dans les requêtes suivantes. Les clients qui ne le prennent pas en charge réémettent la requête de redirection. Le coût du retraitement de la redirection se limite à la vérification d’authentification et aux opérations de session reuse.

3.2. Session reuse pour DASH

Lors du traitement d’une requête /dash/.../index.mpd du même login vers le même flux (avec le même indicateur adaptive), le serveur trouve une session DASH déjà existante et renvoie à nouveau son identifiant. Aucune nouvelle session n’est créée ; aucun slot du quota de connexions simultanées n’est consommé.

Applicable uniquement à DASH. Pour HLS, aucun mécanisme de reuse distinct n’est nécessaire : les clients HLS rafraîchissent la media playlist via l’URL de session et ne créent pas de nouvelle session à chaque rafraîchissement.

3.3. Réutilisation des segments entre sessions

Le chemin /h<sess>/<keyHex>.ts est indépendant de <sess> lors de la résolution de <keyHex> en contenu : <keyHex> identifie de manière globalement unique un segment TS au sein d’un flux. Nginx avec une clé de cache normalisée (retirant le préfixe /h<sess>/) sert toute requête pour le même <keyHex> depuis une seule entrée de cache, quels que soient les clients qui l’ont émise.

Il en va de même pour les segments fMP4 CMAF (.m4s) et init.mp4 : leur contenu est immuable et adressé au sein du flux, de sorte que la normalisation de la cache key produit la même déduplication inter-sessions dans le cache.

4. Paramètres de requête

Paramètre

Valeur par défaut

Impact

a

0

1 — URL absolues dans les manifestes ; 0 — relatives

s

40

timeShiftBufferDepth en secondes

m

40

Longueur minimale de la fenêtre pour émettre le manifeste

v

6 pour OTT/HLS et OTT/HLS/LL-HLS/LL-Dash, 3 pour Peering/HLS

#EXT-X-VERSION en HLS (ignoré par DASH) ; une valeur explicite dans l’URL surcharge la valeur par défaut

h3

non défini (off)

opt-in à HTTP/3 (QUIC) — force le serveur à émettre Alt-Svc dans la réponse. Valeurs reconnues : presence ⇒ on, 1/on/yes/true ⇒ on, 0/off/no/false ⇒ explicit off. Sticky sur l’URL de session /h<sess>/.... Voir la section HTTP/3 (QUIC).

La modification d’un paramètre via la query string met à jour les valeurs stockées dans la session lors de sa prochaine réouverture.

5. Caractéristiques de charge

La charge sur l’origine évolue avec le nombre de streams distincts visionnés simultanément. L’augmentation du nombre de clients regardant le même stream n’augmente pas le nombre de requêtes vers l’origine en présence d’un cache reverse-proxy avec clé de cache normalisée.

Scénario

Fréquence des requêtes origin (réf.)

1 client par flux X

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

N clients sur un même flux X (cache activé)

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

N clients ffmpeg en mode replay sur un même flux

MPD : 1 req/s (avec proxy_cache_lock)

N clients sur N flux distincts

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

6. Nginx comme reverse proxy avec cache

6.1. Configuration de base

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. Rôle des directives

Directive

Objectif

proxy_cache_lock on

Sérialise l’exécution des requêtes upstream lors de cache miss simultanés sur la même clé

proxy_cache_use_stale updating

Renvoie la copie périmée aux requêtes parallèles pendant le rafraîchissement du cache

proxy_cache_revalidate on

Utilise If-Modified-Since en cas de cache miss avec une copie existante

proxy_cache_valid 404 403 0s

Interdit la mise en cache des erreurs d’autorisation et 404

keepalive 64 en upstream

Maintient un pool de connexions persistantes vers l’origin

proxy_buffering on

Pour les segments ; active la mise en buffer de la réponse dans nginx

proxy_buffering off

Pour la section / ; désactive la mise en buffer (raw streaming)

6.3. Calcul de max_size du cache de segments

Valeur indicative : bitrate × timeShiftBufferDepth × distinct_streams × 2

Exemple : 10 flux × 8 Mbps × 40 s × 2 ≈ 800 Mo. Un facteur de sécurité de 10× est recommandé pour absorber la variabilité du bitrate.

6.4. Terminaison TLS

Le serveur Perfect Streamer accepte les connexions sur les ports HTTP et HTTPS. En cas de terminaison TLS sur nginx, l’upstream utilise le port HTTP. Le transfert des en-têtes X-Forwarded-Proto et X-Forwarded-Host est obligatoire pour la formation correcte des URL absolues lorsque 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;
}

En HTTPS entre nginx et l’origin, on applique proxy_ssl_verify et proxy_ssl_trusted_certificate. Pour les connexions loopback, le chiffrement est superflu.

6.5. Multi-host

Lorsque plusieurs server_name sont servis par un même processus nginx, $host est ajouté à la cache key pour isoler les contenus :

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

La taille de keys_zone se calcule à 8000 clés/Mo. Pour des installations multi-hôtes avec des milliers de flux, il est recommandé keys_zone=...:300m ou plus.

7. Mise en cache côté client

Cache-Control: immutable est honoré par les navigateurs Chrome/Firefox/Safari. Le cache client renvoie le segment sans requête conditionnelle lors d’un nouvel accès (y compris seek arrière dans le buffer du lecteur).

Les Service Workers peuvent appliquer une stratégie cache-first basée sur le contenu de Cache-Control. Les lecteurs DASH (dash.js, Shaka) utilisent MSE via SourceBuffer ; un segment placé dans le buffer reste disponible sans nouvelle requête HTTP jusqu’à ce qu’il sorte de la fenêtre.

Pour les requêtes inter-domaines, l’en-tête Access-Control-Allow-Origin: * permet le caching dans les shared caches sans Vary: Origin. Le passage de l’ACAO à un Origin spécifique nécessite Vary: Origin, ce qui réduit l’efficacité du shared cache.

8. Distribution via CDN

Perfect Streamer est compatible avec les CDN en mode pull-from-origin (Cloudflare, Akamai, Fastly, BunnyCDN, Amazon CloudFront).

Origin shield. Il est recommandé de placer un ou plusieurs nœuds shield entre l’edge du CDN et l’origin afin de réduire le taux de requêtes vers l’origin lorsque les clients sont répartis globalement.

Purge. Les segments adressés par contenu n’ont pas besoin de purge. Lors d’un changement de métadonnées du flux (codec, résolution), les manifestes se rafraîchissent dans la fenêtre max-age=1 sans purge explicite.

Cache warming. En cas de pic de charge attendu sur un flux donné, on peut préchauffer le CDN depuis plusieurs points géographiques avant le début de la diffusion.

Géo-distribution. Les segments (max-age=60) conviennent bien au caching géographiquement distribué. Les manifestes (max-age=1) tolèrent jusqu’à une seconde de retard de livraison — acceptable pour du live non low-latency.

9. Surveillance

9.1. X-Cache-Status

Ajouter add_header X-Cache-Status $upstream_cache_status; dans chaque location avec cache. Valeurs :

Valeur

Description

HIT

Réponse depuis le cache

MISS

Absent du cache ; récupéré depuis l’origin et stocké

EXPIRED

Expiré, rafraîchi

UPDATING

Copie stale renvoyée à une requête parallèle pendant le rafraîchissement

STALE

use_stale a renvoyé la copie expirée (origin inaccessible)

REVALIDATED

L’origin a renvoyé 304 Not Modified

BYPASS

proxy_cache_bypass déclenché

9.2. Format des logs d’accès

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. Métriques

Le module nginx-vts exporte les métriques par zone au format Prometheus

GET /status/format/prometheus

Seuils recommandés pour les alertes :

Métrique

Seuil

Cause possible

Segment HIT rate

< 90 % sur 5 minutes

Normalisation de la cache key cassée ; max_size trop petit

Manifest MISS rate

> 50 % sur 1 minute

proxy_cache_lock ne sérialise pas les requêtes

Upstream response time p95

> 500 ms sur 1 minute

Surcharge de l’origin

Cache zone fill

> 90 % sur 10 minutes

Approche de max_size ; éviction LRU prévue

10. Diagnostic

Symptôme

Cause probable

Solution

Taux HIT segment faible

Vary: Origin avec une grande variabilité de l’Origin ; normalisation cassée dans map

Vérifier les en-têtes et le regex de la directive map

404 sur les segments sortis de la fenêtre

404 mis en cache pour un segment sorti de la fenêtre glissante

Ajouter proxy_cache_valid 404 0s dans la location segments

Délai de démarrage de lecture 2–5 s

proxy_cache_lock_timeout dépasse la latence cible

Réduire à 1–2 s ; activer proxy_cache_use_stale updating

Le manifeste ne se rafraîchit pas

proxy_cache_valid remplace max-age

Définir explicitement proxy_cache_valid 200 1s

Augmentation de TIME_WAIT côté upstream

keepalive manquant dans le bloc upstream

Ajouter keepalive 64, proxy_http_version 1.1, proxy_set_header Connection ""

403 sur /dash/.../<segment>.m4s depuis ffmpeg

Le client résout les URL relatives par rapport à l’URL avant redirection

Le serveur émet <BaseURL>/h<sess>/</BaseURL> (chemin absolu) ; compatible dans le build actuel

Lags, rebuffering fréquents chez les clients distants

Throughput TCP effectif faible à cause du slow start et de l’idle restart sur de longs RTT (300 ms et plus)

Réglage de la pile réseau Linux sur l’origin : voir 10.1

10.1. Réglage TCP de l’origin pour les clients high-RTT

Le problème se manifeste sur les clients ayant un grand RTT jusqu’à l’origin (par exemple 300 ms et plus), lorsque le bitrate du flux est proche de la capacité du canal. Symptômes côté lecteur (VLC, ffmpeg, dash.js) — rebuffering fréquents, warnings du type ES_OUT_SET_PCR called too late (augmentation de pts_delay), buffer deadlock prevented, ruptures de flux. Côté serveur le client paraît normal, il n’y a pas d’erreurs, et le throughput sur /data/stream/... correspond au flux d’entrée.

Cause :

  • TCP slow start. Chaque nouvelle connexion TCP démarre avec un congestion window d’environ 14 Ko et l’augmente sur plusieurs RTT. À 300 ms de RTT, atteindre la fenêtre complète prend 2 à 3 secondes. Pendant ce temps, un segment HLS/DASH d’une durée de 5 s (4-6 Mo) se télécharge sensiblement plus lentement qu’en temps réel.

  • TCP idle restart. Entre deux requêtes de segments, un client HLS en pull-model fait une pause de 4 à 5 s. Par défaut, après une telle pause, le noyau Linux remet le congestion window de la connexion à l’initial cwnd (comportement net.ipv4.tcp_slow_start_after_idle=1). En conséquence, au GET suivant, la connexion keep-alive reprend la transmission depuis le slow start — même sur une session déjà chauffée.

Une aggravation supplémentaire — le congestion control CUBIC par défaut supporte mal les longs RTT et les pertes de paquets sur des segments intermédiaires du réseau.

Solution — deux paramètres sysctl sur l’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

Pour une application persistante :

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

L’effet principal vient du premier paramètre (tcp_slow_start_after_idle=0). Il élimine directement le slow start répété entre les requêtes de segments au sein d’une même connexion keep-alive. Le second (BBR) apporte une robustesse supplémentaire et s’applique à toutes les nouvelles connexions.

Le réglage ne nécessite pas de redémarrer Perfect Streamer et s’applique à toutes les nouvelles connexions TCP immédiatement après sysctl -w. Les connexions existantes conservent le congestion control avec lequel elles ont été établies.

11. Sécurité

11.1. Session URL

Une URL au format /h<sess>/... joue le rôle de jeton de session — pas besoin de réauthentification. La durée de vie est bornée par l’idle timeout (valeur 30 s). En l’absence d’activité, la session est supprimée par la tâche cleaner.

Exigences :

  • HTTPS pour tous les chemins OTT (/hls/, /dash/, /h<sess>/) en production

  • L’identifiant de session dans l’en-tête Location du 302 n’est pas mis en cache (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;
    }
}

Les URL de session (/h<sess>/) ne nécessitent pas de rate limiting — le traitement est peu coûteux, les réponses sont mises en cache.

11.3. Mise en cache des réponses d’erreur

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

Interdit la mise en cache des redirections (sess unique dans Location) et des réponses d’erreur d’autorisation ou de ressource absente.

11.4. Restriction de l’accès réseau à l’origin

Le port 41972 (41982 pour HTTPS) doit être fermé au trafic externe. Configurations acceptables :

  1. Lier Perfect Streamer à 127.0.0.1 (si nginx est colocalisé)

  2. Règle de pare-feu :

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

12. Intégration middleware

12.1. Modèle prefix-login

Perfect Streamer peut déléguer l’identification utilisateur à un middleware/système de facturation via le mécanisme prefix-login. Un connecteur externe vers le système de facturation n’est pas inclus dans la version actuelle.

Configuration de l’utilisateur embedded :

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

Avec is-prefix: true, le serveur accepte les URL dont le login suit <prefix><billing_user_id>

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

12.2. Format des statistiques

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

Le champ login-id contient le hash du login URL. login est la valeur configurée. match-login est le login URL utilisé par le client.

12.3. Limitations du prefix-login

  • Mot de passe partagé. Tous les abonnés du pool prefix utilisent une même valeur de mot de passe. Sa compromission donne accès à tout <prefix><string>.

  • Granularité ACL. accept-stream s’applique à tout le pool prefix ; pas d’ACL par abonné sans facturation externe.

  • Rotation du mot de passe. Le changement du mot de passe déconnecte tous les abonnés actifs. Un remplacement progressif nécessite l’utilisation temporaire de deux prefix-logins.

13. Sous-titres WebVTT

La source des sous-titres est DVB Teletext / DVB Subtitling depuis le MPEG-TS d’entrée. Les pistes de sous-titres Teletext doivent être présentes dans les sections Media Information ou Original Media Information. La section Analyzer permet également de vérifier que les paquets des PID correspondants sont actifs.

Pour OTT HLS/DASH, le mode OTT doit être activé (en Peering/HLS, les sous-titres WebVTT ne sont pas disponibles). Dans la section Output # OTT, le compteur de chunks OTT WebVTT buffer chunk count doit devenir non nul.

Pour diagnostiquer les sous-titres, activer Analyze et Trace sur le stream. Au démarrage du flux, le journal du stream doit afficher :

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

Le journal contient ensuite le texte décodé des sous-titres.

13.1. URL des segments VTT

Schéma

URL

Contenu

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 avec #EXTINF

Segment VTT HLS

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

VTT avec X-TIMESTAMP-MAP de type HLS

DASH MPD AdaptationSet

dans index.mpd

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

Segment VTT DASH

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

VTT avec X-TIMESTAMP-MAP de type DASH

<keyHex> est un CRC64 hex de 16 caractères calculé sur l’instant de début du segment, l’ID du flux et le PID de la piste de sous-titres. <seq> est le numéro d’ordre décimal d’un chunk du flux de sous-titres (la numérotation des sous-titres est indépendante de celle des chunks TS).