
Lire du HEVC dans un navigateur, sans plugin ni extension
Vous encodez votre catalogue en HEVC pour économiser 30 à 50 % de bande passante, et vous découvrez que Chrome sous Linux refuse de le lire. Firefox Windows réclame une extension Microsoft payante. Firefox Linux ne l'aura probablement jamais. Résultat : vous maintenez deux pipelines d'encodage, ou vous perdez un segment de votre audience.
J'ai construit hevc.js, un décodeur HEVC en WebAssembly qui transcode le flux vers H.264 côté client, à la volée, à travers WebCodecs. Plugin drop-in pour dash.js, 236 KB de WASM, zéro dépendance, aucune modification serveur. 1080p à 60 fps. Construit en 8 jours, avec une IA comme copilote.
Le problème : HEVC est partout, sauf dans le navigateur
HEVC (H.265) est devenu le codec de référence de l'industrie. Netflix, Apple, les broadcasters, toute la chaîne broadcast 4K et HDR s'appuient dessus. Le gain est mesurable : 30 à 50 % de bitrate en moins à qualité perceptuelle équivalente par rapport à H.264. Pour un service streaming à l'échelle, ce sont des millions d'euros de CDN économisés chaque année.
Le navigateur, lui, reste un champ de mines. Et surtout, chaque navigateur a son propre chemin de décodage, avec ses propres dépendances.
- macOS (Safari 13+, Chrome, Edge, Firefox) : HEVC natif via VideoToolbox. Rien à installer, rien à acheter. Apple est dans le pool de licence MPEG LA et distribue le décodeur avec l'OS.
- Chrome 107+ sur Windows : utilise
D3D11VideoDecoderqui appelle directement D3D11VA (DXVA). Aucune extension Microsoft requise. En revanche, il faut un GPU avec décodeur HEVC matériel — en gros, Intel Skylake (2015), NVIDIA Maxwell 2ᵉ gen (GTX 960, 2015) ou AMD Fiji (R9 Fury, 2015) et plus récent. Sans GPU compatible, Chrome ne décode pas : il n'a pas de fallback software HEVC. Et jusqu'à Chrome 130, la résolution est plafonnée à 1920×1088 @ 30 fps. - Edge sur Windows : utilise
VDAVideoDecoderqui passe par Media Foundation (MFT). Requiert la « HEVC Video Extension » de Microsoft, vendue 0,99 € sur le Microsoft Store. Sans elle, pas de HEVC — peu importe le GPU. Certains constructeurs préinstallent une variante OEM gratuite, mais elle reste minoritaire. - Firefox 133+ sur Windows : passe aussi par MFT, même dépendance à l'extension Microsoft.
- Chrome et Edge < 107 sur Windows : aucun support HEVC.
- Chrome Linux : HEVC matériel via VAAPI, mais dépend du driver et du GPU. Souvent absent.
- Firefox Linux : pas de support natif prévu.
La raison profonde est juridique. La licence MPEG LA et le pool Access Advance imposent des redevances par unité distribuée plus des fees sur le contenu. Microsoft a choisi de faire payer cette licence à l'utilisateur final via l'extension Store pour Edge et pour tout ce qui transite par Media Foundation. Google a négocié sa propre voie via D3D11VA direct pour Chrome. Mozilla n'a ni licence, ni décodeur interne, et dépend donc de l'extension Microsoft sur Windows. D'où la fragmentation.
Le résultat côté plateforme : soit vous encodez tout en double (H.264 + HEVC) pour couvrir Chrome/Edge sans GPU et Firefox Linux, soit vous excluez ces utilisateurs. Les deux options sont mauvaises.
La solution : décoder H.265 côté client en WebAssembly
L'idée est simple : puisque le navigateur refuse le HEVC, on le lui cache. Le flux HEVC est décodé en JavaScript, réencodé en H.264 dans le même tour d'horloge, puis livré à Media Source Extensions comme un flux H.264 standard. Le player ne voit rien.
Le pipeline complet tourne dans un Web Worker :
fMP4 HEVC → mp4box.js (demux) → NAL units
→ décodeur WASM H.265 → frames YUV
→ WebCodecs VideoEncoder → H.264
→ muxer fMP4 custom → MSE → <video>
L'intégration dans dash.js se fait par une interception de MediaSource.addSourceBuffer(). Un proxy accepte la MIME type HEVC, mais nourrit le SourceBuffer réel avec du H.264. Le proxy reporte updating, émet updatestart et updateend, renvoie les bons buffered ranges. Le buffer management, l'ABR et le seek du player fonctionnent sans une ligne de modification.
Pourquoi c'est faisable maintenant, et pas il y a trois ans : WebCodecs ship dans Chrome depuis la v94, WebAssembly est mature, et mp4box.js gère la démux fMP4 côté client de manière fiable.
Détection d'abord, transcoding en dernier recours. MediaSource.isTypeSupported() n'est pas fiable : Firefox Windows reporte HEVC comme supporté même sans la Video Extension installée. hevc.js probe en créant réellement un SourceBuffer HEVC. Si ça échoue, et seulement alors, le transcoding s'active. Quand le natif fonctionne, l'overhead est nul.
Tableau de compatibilité navigateur
| Navigateur + OS + condition | HEVC natif | hevc.js s'active ? | Transcoding fonctionne ? | Raison |
|---|---|---|---|---|
| Safari 13+ (macOS/iOS) | Oui (VideoToolbox) | Non — natif | — | Décodage matériel macOS/iOS |
| Chrome/Edge/Firefox (Mac) | Oui (VideoToolbox) | Non — natif | — | Décodage natif via macOS |
| Chrome 107+ (Win, GPU HEVC compatible) | Oui (D3D11VA) | Non — natif | — | Décodage GPU direct, sans extension |
| Chrome 107+ (Win, GPU incompatible) | Non | Oui | Oui | Chrome n'a pas de fallback software HEVC |
| Edge (Win, avec HEVC Video Extension) | Oui (MFT) | Non — natif | — | Requiert extension Microsoft (0,99 € Store) |
| Edge (Win, sans extension) | Non | Oui | Oui | MFT sans extension : pas de décodeur |
| Firefox 133+ (Win, avec extension) | Oui (MFT) | Non — natif | — | Requiert extension Microsoft (0,99 € Store) |
| Firefox 133+ (Win, sans extension) | Reporté mais faux | Oui | Oui | Probe SourceBuffer détecte le faux positif |
| Chrome/Edge 94–106 | Non | Oui | Oui | HEVC pas encore intégré, WebCodecs présent |
| Chrome/Edge < 94 | Non | Non | Non | Pas de WebCodecs H.264 encoder |
| Chrome (Linux, avec VAAPI actif) | Variable | Parfois | Oui | Dépend du driver et du GPU |
| Chrome (Linux, sans VAAPI) | Non | Oui | Oui | Encodage H.264 software via WebCodecs |
| Firefox (Linux) | Non | Oui | Dépend | Requiert encoder H.264 via WebCodecs |
Pré-requis côté navigateur : WebAssembly, Web Workers, Secure Context (HTTPS ou localhost — WebCodecs n'est pas exposé en HTTP clair), et un VideoEncoder WebCodecs supportant H.264. Pas besoin de headers COOP/COEP : le décodeur est single-thread, sans SharedArrayBuffer.
Construit en 8 jours avec une IA — ce que ça change
La spec ITU-T H.265 v8 d'août 2021 fait 716 pages. CABAC au §9.3, 35 modes d'intra prediction au §8.4, merge / AMVP / TMVP pour l'inter au §8.5, interpolation 8-tap luma et 4-tap chroma au §8.5.3, weighted prediction, transformées inverses DCT 4–32 plus DST 4, scaling lists, deblocking, SAO edge et band offset, 10-bit Main 10, multi-slice, Wavefront Parallel Processing. Ce n'est pas un week-end de code.
C'est là que la méthode change. J'ai transcrit la spec section par section, en C++17, assisté par une IA qui a produit le squelette de chaque algorithme à partir du texte normatif. Je pilote, je choisis l'architecture, je valide. L'IA accélère la partie mécanique — traduire des pseudocodes ITU en C++ compilable, dérouler des tables de 64 entrées sans se tromper, implémenter les 35 modes intra un par un.
La validation reste humaine, et binaire : conformité bit à bit contre ffmpeg, le décodeur de référence du domaine, sur 128 bitstreams de test officiels. 128 sur 128 passent pixel-perfect. Quand un mode échoue, l'IA n'invente rien : je relis la section correspondante de la spec et je corrige.
Ce n'est pas la première fois que j'applique cette méthode. J'ai documenté un projet équivalent — un émulateur d'arcade construit en 13 jours avec une IA — qui suivait exactement le même protocole : spec de référence, implémentation assistée, validation contre une implémentation de référence. La méthode est reproductible.
Soyons honnête sur ce que l'IA ne fait pas. Elle ne comprend pas seule qu'un décodeur HEVC doit être single-thread pour tenir dans une seule instance WASM légère. Elle ne choisit pas entre Emscripten et wasm-bindgen. Elle ne décide pas d'abandonner WPP multi-thread au profit d'un pipeline WebCodecs asynchrone. Ces décisions d'architecture restent à ma charge.
Les résultats mesurés
Sur Apple Silicon (M-series), en single-thread :
| Natif C++ | WASM (Chrome) | |
|---|---|---|
| Decode 1080p | 76 fps | 61 fps |
| Decode 4K | 28 fps | 21 fps |
| Transcode 1080p | — | ~2.5x temps réel (segment 6s en 2.4s) |
Le WASM atteint 80 % de la perf du même code compilé en natif, ce qui est cohérent avec ce qu'on observe sur d'autres décodeurs vidéo WASM. Comparé à libde265, décodeur open-source mûr de dix ans, compilé en WASM dans les mêmes conditions, hevc.js atteint 83 % de sa vitesse.
Conformité : 128/128 bitstreams pixel-perfect contre ffmpeg. Aucune dérive.
Taille : 236 KB de WebAssembly gzippé, zéro dépendance runtime, aucun header serveur requis. Intégrable dans un bundle dash.js standard sans toucher au pipeline CI/CD.
Quand l'utiliser, et quand s'abstenir
À utiliser :
- Plateforme streaming avec catalogue HEVC existant, segment d'audience sur Chrome/Edge sans GPU compatible ou Firefox Linux.
- Simplification d'infrastructure : un seul pipeline d'encodage HEVC au lieu de deux (HEVC + H.264 fallback).
- VOD ou live à latence modérée où 2-3 secondes de startup supplémentaires sont acceptables.
- Environnements contrôlés (IPTV, B2B, player embarqué) où vous connaissez la machine cliente.
À ne pas utiliser :
- Mobile bas de gamme. Le transcoding software coûte cher en CPU et en batterie.
- 4K sur machines anémiques. 21 fps en décodage 4K, c'est en dessous du temps réel.
- Contextes à latence ultra-faible (live sport sub-second). Le buffering initial est rédhibitoire.
Limitations à assumer :
- 2 à 3 secondes de latence au premier segment. Après buffering, lecture fluide. Mais le time-to-first-frame n'est pas celui du natif.
- Patents MPEG LA et Access Advance. Le décodage côté client n'exonère pas automatiquement des redevances selon la juridiction. À évaluer avec un juriste avant déploiement commercial.
- Dépendance à WebCodecs H.264 encoder. C'est le facteur limitant. Sur certains Firefox Linux sans encodeur H.264 exposé, le fallback ne fonctionne pas.
Essayer maintenant
Démo live et code : hevcjs.dev.
Installation :
npm install @hevcjs/dashjs-plugin
Intégration dans un player dash.js existant, trois lignes :
import dashjs from 'dashjs';
import { attachHevcSupport } from '@hevcjs/dashjs-plugin';
const player = dashjs.MediaPlayer().create();
attachHevcSupport(player, { workerUrl: './transcode-worker.js' });
player.initialize(videoElement, 'https://example.com/manifest.mpd', true);
Le plugin détecte le support HEVC natif du navigateur et ne s'active que si nécessaire. Sur Safari ou Chrome avec GPU compatible, overhead zéro. Sur Firefox Linux, le transcoding WASM prend le relais sans que le code applicatif ne change d'une ligne.
Ce projet illustre ce que devient le développement quand on traite l'IA comme un copilote technique rigoureux plutôt que comme un générateur de snippets. Une spec de 716 pages, un décodeur conforme bit à bit, un plugin drop-in, livré en 8 jours. La méthode est documentée et reproductible. Si vous avez un projet technique ambitieux bloqué par le coût humain de son implémentation, parlons-en.