---
title: Service OTT
url: https://doc.pstreamer.tv/fr/manual/ott.html
lang: fr
product: Perfect Streamer
version: 1.13.3.445
---

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

- [http://host:port/http/stream/login/password](http://host:port/http/stream/login/password) — autorisation par login et mot de passe
- [http://host:port/http/stream/login](http://host:port/http/stream/login) — autorisation par login (token)
- [http://host:port/http/stream/](http://host:port/http/stream/) — autorisation par IP

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

**stream** — **ID** 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) :

- [http://host:port/hls/stream/login/password](http://host:port/hls/stream/login/password)
- [http://host:port/hls/stream/login](http://host:port/hls/stream/login)
- [http://host:port/hls/stream/](http://host:port/hls/stream/)
- [http://host:port/dash/stream/login/password](http://host:port/dash/stream/login/password)
- [http://host:port/dash/stream/login](http://host:port/dash/stream/login)
- [http://host:port/dash/stream/](http://host:port/dash/stream/)
- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

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-Dash* — *6*, *Peering/HLS* — *3*. 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](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 :

- [http://host:port/llhls/stream/login/password](http://host:port/llhls/stream/login/password)
- [http://host:port/llhls/stream/login](http://host:port/llhls/stream/login)
- [http://host:port/llhls/stream/](http://host:port/llhls/stream/)

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 :

- [http://host:port/hls/adaptive/stream/login/password](http://host:port/hls/adaptive/stream/login/password)
- [http://host:port/hls/adaptive/stream/login](http://host:port/hls/adaptive/stream/login)
- [http://host:port/hls/adaptive/stream/](http://host:port/hls/adaptive/stream/)
- [http://host:port/dash/adaptive/stream/login/password](http://host:port/dash/adaptive/stream/login/password)
- [http://host:port/dash/adaptive/stream/login](http://host:port/dash/adaptive/stream/login)
- [http://host:port/dash/adaptive/stream/](http://host:port/dash/adaptive/stream/)
- [http://host:port/llhls/adaptive/stream/login/password](http://host:port/llhls/adaptive/stream/login/password)
- [http://host:port/llhls/adaptive/stream/login](http://host:port/llhls/adaptive/stream/login)
- [http://host:port/llhls/adaptive/stream/](http://host:port/llhls/adaptive/stream/)

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é `http` → `https` → `quic`. 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

```nginx
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`.

```nginx
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 :

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

```nginx
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 :

```bash
# 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 :

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

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

```nginx
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).
