HLS en dix minutes
Des fichiers texte et des morceaux de vidéo servis en HTTP ordinaire. Le master, la playlist de média, les segments — et les cinq tags qui décident de tout.
À la fintu lis un master et une playlist de média sans outil, et tu sais ce que le lecteur va en faire.
HLS (HTTP Live Streaming) est le format de tout ce qu'une plateforme de streaming lit — et de tout ce que tu vas lire dans ce guide. Il a gagné pour une raison simple : il ne demande rien de plus qu'un serveur de fichiers. Une vidéo HLS, c'est une playlist texte qui pointe vers d'autres playlists texte, qui pointent vers des morceaux de vidéo de quelques secondes. Un CDN sert tout ça comme il servirait des images.
Ce chapitre lit un vrai manifeste ligne par ligne. Le script manifeste.py, en bibliothèque standard, le fait pour toi ; on s'en servira à chaque nouvelle source.
Le master
La première ligne d'une playlist HLS est toujours #EXTM3U. Si elle n'y est pas, ce que tu regardes n'est pas une playlist — le plus souvent une page d'erreur HTML servie en 200. Voici le début d'un master public, celui des exemples d'Apple :
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 1",AUTOSELECT=YES,DEFAULT=YES
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="bipbop_audio",LANGUAGE="eng",NAME="BipBop Audio 2",AUTOSELECT=NO,DEFAULT=NO,URI="alternate_audio_aac/prog_index.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français",DEFAULT=NO,AUTOSELECT=YES,FORCED=NO,LANGUAGE="fr",URI="subtitles/fra/prog_index.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Français (Forced)",DEFAULT=NO,AUTOSELECT=NO,FORCED=YES,LANGUAGE="fr",URI="subtitles/fra_forced/prog_index.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=263851,CODECS="mp4a.40.2, avc1.4d400d",RESOLUTION=416x234,AUDIO="bipbop_audio",SUBTITLES="subs"
gear1/prog_index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1927833,CODECS="mp4a.40.2, avc1.4d401f",RESOLUTION=1920x1080,AUDIO="bipbop_audio",SUBTITLES="subs"
gear5/prog_index.m3u8
Deux sortes de lignes, et c'est tout ce qu'un master contient :
#EXT-X-STREAM-INF déclare une variante : une qualité. Ses attributs qui comptent sont BANDWIDTH (le débit de pointe, en bits par seconde), RESOLUTION, CODECS, et les références AUDIO et SUBTITLES vers des groupes de pistes. La ligne suivante est l'URI de sa playlist de média, relative au master.
#EXT-X-MEDIA déclare une piste : audio, sous-titres, parfois vidéo alternative. GROUP-ID la rattache aux variantes ; NAME est ce qu'on affiche ; LANGUAGE est un code de langue ; DEFAULT et AUTOSELECT disent au lecteur quoi choisir au démarrage ; FORCED=YES marque les sous-titres qu'on affiche même quand l'utilisateur n'en veut pas (les dialogues en langue étrangère). Une piste audio sans URI est celle qui est mélangée dans les segments vidéo ; une piste avec URI est servie à part.
Ce que ce master dit, si tu sais le lire : deux pistes audio anglaises dont une en fichiers séparés, des sous-titres dans quatre langues avec leurs versions forcées, et six qualités de 234p à 1080p. Le script te le résume :
$ python3 manifeste.py https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8
6 variante(s) vidéo :
audio seul 41 kb/s codecs=mp4a.40.2 audio=bipbop_audio
416x234 264 kb/s codecs=mp4a.40.2, avc1.4d400d audio=bipbop_audio
640x360 578 kb/s codecs=mp4a.40.2, avc1.4d401e audio=bipbop_audio
960x540 911 kb/s codecs=mp4a.40.2, avc1.4d401f audio=bipbop_audio
1280x720 1.0 Mb/s codecs=mp4a.40.2, avc1.4d401f audio=bipbop_audio
1920x1080 1.9 Mb/s codecs=mp4a.40.2, avc1.4d401f audio=bipbop_audio
10 piste(s) déclarée(s) par #EXT-X-MEDIA :
AUDIO groupe=bipbop_audio nom='BipBop Audio 1' langue=eng défaut=YES
AUDIO groupe=bipbop_audio nom='BipBop Audio 2' langue=eng défaut=NO
SUBTITLES groupe=subs nom='Français' langue=fr défaut=NO
…
Variante la plus lourde : 181 segments, 30.0 min, VOD (#EXT-X-ENDLIST), en clair
Beaucoup de sources que tu croiseras n'ont aucune ligne #EXT-X-MEDIA : l'audio est mélangé dans chaque variante, et il n'y a qu'une langue. D'autres déclarent deux pistes fra avec le même NAME — deux doublages, indistinguables dans le manifeste. Le chapitre 4.1 s'en occupe. Pour l'instant, retiens que les pistes réelles sont celles du manifeste, pas celles que tu supposes.
La playlist de média
Suis l'URI d'une variante et tu tombes sur la liste des segments :
#EXTM3U
#EXT-X-TARGETDURATION:11
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.9766,
#EXT-X-BYTERANGE:326744@0
main.ts
#EXTINF:9.9766,
#EXT-X-BYTERANGE:335268@326744
main.ts
…
#EXT-X-ENDLIST
#EXTINFprécède chaque segment et donne sa durée en secondes. La somme de toutes les durées est la durée du film : c'est comme ça qu'on la connaît sans rien télécharger.#EXT-X-TARGETDURATIONest la durée maximale d'un segment. Le lecteur s'en sert pour savoir combien de temps attendre avant de recharger une playlist live.#EXT-X-BYTERANGEdit que le segment est une tranche d'un fichier plus gros :326744@0, c'est 326 744 octets à partir de l'octet 0. Ici tout le film est un seulmain.ts, découpé virtuellement. C'est le mécanisme qu'on réutilisera au chapitre 9 pour lire un film hors-ligne depuis un seul fichier local.#EXT-X-ENDLISTferme la playlist : c'est une vidéo à la demande, complète. Sans cette ligne, c'est du live, et le lecteur recharge la playlist en boucle pour découvrir les nouveaux segments. C'est toute la différence entre la partie 1 et la partie 11.
Trois tags de plus, que tu verras tôt ou tard :
#EXT-X-MAP:URI="init.mp4"désigne le segment d'initialisation d'un flux en MP4 fragmenté — l'en-tête sans lequel les autres segments ne se décodent pas. Un téléchargement qui l'oublie produit un fichier illisible.#EXT-X-KEY:METHOD=AES-128,URI="…",IV=0x…annonce que les segments qui suivent sont chiffrés, et où chercher la clé. Le lecteur télécharge la clé (16 octets) et déchiffre à la volée. Le chapitre 4.6 s'en sert pour protéger tes propres flux ; certaines sources s'en servent aussi, et la clé a ses propres règles CORS — c'est une requête de plus à vérifier.#EXT-X-DISCONTINUITYprévient que le segment suivant ne suit pas le précédent (autre encodage, autre horloge). Utile pour les publicités insérées, gênant pour la lecture hors-ligne.
Les segments
Un segment est un fichier de quelques secondes de vidéo et d'audio. Deux conteneurs existent :
MPEG-TS (.ts) : l'historique. Des paquets de 188 octets qui commencent tous par l'octet 0x47. C'est ce que servent la majorité des sources. Robuste, mais les navigateurs ne le lisent pas nativement — sauf Safari.
MP4 fragmenté (.m4s, .mp4) : le moderne. Un segment d'initialisation puis des fragments. C'est ce que le navigateur sait lire via Media Source Extensions, dans tous les cas.
Les URL relatives
Presque toutes les URI d'un manifeste sont relatives : gear1/prog_index.m3u8, main.ts, url_462/…. Le lecteur les résout par rapport à l'URL de la playlist qui les contient — pas par rapport au master, pas par rapport à la page. Deux conséquences pratiques :
- Si un serveur répond avec une redirection (302) vers un autre domaine, c'est l'URL finale qui sert de base, pas celle demandée. Un lecteur ou un téléchargeur qui garde l'URL d'origine résout les segments au mauvais endroit et n'obtient que des 404.
- Si tu réécris un manifeste (tu ne devrais pas — mais si), tu dois réécrire toutes les URI, y compris celles cachées dans les attributs
URI="…"des tags#EXT-X-MEDIA,#EXT-X-KEYet#EXT-X-MAP. Un manifeste réécrit à moitié donne une vidéo qui démarre et un changement de langue qui échoue.
C'est un chapitre sur soixante-cinq. Le guide complet — douze parties, le code de référence, le carnet des intégrations, l'aide sur le frontend, les mises à jour — coûte 50 €, une fois, accès à vie.
Acheter — 50 € Retour à la présentation