Service OTT

Émet des flux via des protocoles HTTP — HLS, MPEG-DASH (depuis la version 1.12) et MPEG-TS over HTTP. HTTPS (SSL) est 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 manière similaire pour HLS et DASH :

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, des paramètres supplémentaires sont disponibles dans l’URL (optionnels) :

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

  • a : 1 — chemins absolus dans la playlist (par défaut), 0 — chemins relatifs.

  • 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 au minimum demandé, l’erreur 404 est renvoyée. Cela garantit que le HLS démarre depuis un buffer de chunks rempli sur le serveur.

  • v : version du protocole HLS annoncée dans la playlist. 5 par défaut ; un changement peut être nécessaire pour certains clients HLS.

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 serveur HLS dispose de deux modes — Peer mode et OTT mode.

Peer mode — mode avec découpage simple des segments (chunks). Recommandé pour le peering / la distribution.

OTT mode — mode avec un découpage des segments optimisé pour la diffusion OTT et un démarrage rapide des lecteurs. La charge CPU est plus élevée ; recommandé pour la diffusion.

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é sur 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 avec min interval ; si pour une raison quelconque les données ne sont pas trouvées, le chunk est forcément découpé selon max interval.

HLS Adaptive Multistream

Depuis la version 1.10, prise en charge de HLS Adaptive Multistream et, depuis 1.12, de DASH Adaptive Multistream

Pour les flux adaptatifs, on configure une playlist HLS dédiée. Pour ce faire :

  • Pour les flux à inclure dans la playlist adaptative, activez HLS avec OTT Mode.

  • Dans le menu principal apparaît la section des flux adaptatifs. Y ajouter un flux et y inscrire tous les flux à inclure dans la playlist.

  • On peut affecter aux flux un paramètre de bitrate. Par défaut 0 signifie que le bitrate mesuré est utilisé ; sinon, on peut le fixer 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.

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

/h<sess>/<keyID>.ts, /h<sess>/<subID>/<keyID>.ts

video/mp2t

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>/<subID>/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 TS

L’identifiant keyID est formé comme CRC64(startTime || streamID) et est globalement unique. L’URL d’un segment adresse un contenu immuable — sur des requêtes répétées de 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.

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 (par défaut, sans paramètre d’URL « a »), les manifestes HLS media et DASH MPD ne contiennent pas l’identifiant de session dans leur corps. Le contenu du manifeste est identique entre sessions appartenant à une même combinaison (stream, param). Cela permet au cache reverse-proxy de réutiliser une entrée entre sessions avec normalisation de la clé de cache.

Avec absPath=1 (paramètre d’URL « a=1 »), le corps du manifeste contient des URL absolues incluant schéma, host et identifiant de session. Le contenu devient spécifique à la session — la réutilisation cross-session du cache n’est plus 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 de la requête /dash/.../index.mpd exécutée sous login-id L pour stream-id S et le drapeau adaptive=A, si _ottClientList contient déjà une session DASH avec les mêmes (L, S, A), son sessID est renvoyé. Aucune nouvelle session n’est créée, aucun slot maxConn n’est consommé.

Applicable uniquement à DASH. HLS n’a pas besoin d’un mécanisme de reuse distinct : les clients HLS rafraîchissent la media playlist via l’URL de session et ne déclenchent pas applyNewOTTSess à chaque rafraîchissement.

3.3. Réutilisation des segments entre sessions

Le chemin /h<sess>/<keyID>.ts est indépendant de sess lors de la résolution de keyID en contenu : keyID identifie de manière unique le segment dans la ChunkList enregistrée (voir _ottStreamList). Nginx avec une clé de cache normalisée (en supprimant le préfixe /h<sess>/) sert toutes les requêtes du même keyID depuis une unique entrée de 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

3

#EXT-X-VERSION en HLS (ignoré par DASH)

Modifier le paramètre via la query string met à jour les valeurs stockées dans la session lors du prochain appel à applyNewOTTSess.

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$" {
        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;
        # + директивы кеширования из 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>.ts 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

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;

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

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 un même 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 déconnecte tous les abonnés actifs. Pour un remplacement progressif, deux prefix-logins sont temporairement nécessaires.

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 Peer mode, 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 l’hex 16 caractères de CRC64(startTime, streamID, pid). <seq> est le numéro décimal du chunk dans le subtitle storage (compteur distinct du video storage).