@hevcjs/dashjs-plugin
v1.1.0
Published
dash.js plugin for HEVC/H.265 playback — transparently intercepts MSE to decode HEVC streams via @hevcjs/core
Maintainers
Readme
@hevcjs/dashjs-plugin
HEVC/H.265 playback plugin for dash.js. Transparently transcodes HEVC segments to H.264 via WebAssembly when native HEVC is unavailable. When native HEVC is available, the plugin detects it and does nothing.
Install
npm install @hevcjs/dashjs-plugin dashjsUsage — bundled (Vite, Webpack, etc.)
Copy the static assets from @hevcjs/core to your public directory:
cp node_modules/@hevcjs/core/dist/transcode-worker.js public/
cp node_modules/@hevcjs/core/dist/wasm/hevc-decode.js public/
cp node_modules/@hevcjs/core/dist/wasm/hevc-decode.wasm public/Then:
import dashjs from 'dashjs';
import { attachHevcSupport } from '@hevcjs/dashjs-plugin';
const video = document.querySelector('video');
const player = dashjs.MediaPlayer().create();
await attachHevcSupport(player, {
workerUrl: '/transcode-worker.js',
wasmUrl: '/hevc-decode.js',
});
player.initialize(video, 'https://example.com/stream/manifest.mpd', true);Usage — from a CDN (zero build)
Load everything from a CDN, no install or build step required. Useful for prototyping, samples, codepens, and integrations where you don't control the build pipeline.
<script type="module">
import { attachHevcSupport } from 'https://esm.sh/@hevcjs/dashjs-plugin@1';
const video = document.querySelector('video');
const player = dashjs.MediaPlayer().create();
await attachHevcSupport(player, {
workerUrl: 'https://unpkg.com/@hevcjs/core@1/dist/transcode-worker.js',
wasmUrl: 'https://unpkg.com/@hevcjs/core@1/dist/wasm/hevc-decode.js',
wasmBinaryUrl: 'https://unpkg.com/@hevcjs/core@1/dist/wasm/hevc-decode.wasm',
});
player.initialize(video, 'https://example.com/stream/manifest.mpd', true);
</script>wasmBinaryUrl is required when assets live on a different origin than the page — Emscripten otherwise resolves the .wasm relative to the worker's blob: URL and fails. The plugin transparently fetches the cross-origin worker source and wraps it in a same-origin blob: URL (the Worker constructor refuses cross-origin scripts even with CORS headers).
How It Works
When attachHevcSupport(player) is called:
- Probes native HEVC support — creates a real SourceBuffer (not just
isTypeSupported, which can lie on Firefox) - If native HEVC works — does nothing, zero overhead
- If not — patches
MediaSource.addSourceBuffer()to intercept HEVC and return an H.264 proxy - The proxy SourceBuffer intercepts
appendBuffer():- Init segments: extracts VPS/SPS/PPS from hvcC
- Media segments: demux (mp4box.js) → decode HEVC (WASM) → encode H.264 (WebCodecs) → mux fMP4 → append to real H.264 SourceBuffer
- Proper
updatingstate management — the proxy reportsupdating = trueduring transcoding, so dash.js waits between segments
Audio and subtitle tracks pass through untouched.
API
attachHevcSupport(player, config?)
const cleanup = await attachHevcSupport(player, {
workerUrl: '/transcode-worker.js', // URL to the Web Worker script
wasmUrl: '/hevc-decode.js', // URL to the Emscripten loader
wasmBinaryUrl: '/hevc-decode.wasm', // URL to the .wasm binary — required for cross-origin loading
fps: 25, // Target framerate (optional, default: 25)
bitrate: 4_000_000, // H.264 encode bitrate (optional)
forceTranscode: false, // Bypass native HEVC detection (optional)
adaptiveCompute: true, // Compute-aware ABR (default true; pass false to opt out)
});
// Remove patches when done (also detaches the compute-aware listener)
cleanup();Compute-aware ABR
The plugin watches per-segment transcode speedX (segDurMs / wallClockMs). When the device can't keep up, it asks dash.js to narrow its variant ceiling via player.updateSettings({ streaming: { abr: { maxBitrate: { video } } } }) — dash.js's own bandwidth-based ABR keeps picking freely from what's left. On by default.
// Tune (defaults: measureWindow 2, lowerAfter 1, raiseAfter 6, targetSpeedX 1.3)
await attachHevcSupport(player, {
adaptiveCompute: { targetSpeedX: 1.5, lowerAfter: 2 },
});
// Telemetry hook — fires per segment, not just on cap changes
await attachHevcSupport(player, {
adaptiveCompute: {
onObservation: (stat, avgSpeedX, capIndex, reason) => {
console.log(`speedX=${stat.speedX.toFixed(2)} cap=${capIndex} (${reason})`);
},
},
});
// Opt out
await attachHevcSupport(player, { adaptiveCompute: false });subscribeSegmentStat and SegmentPerfStat are also re-exported from @hevcjs/dashjs-plugin for custom telemetry on the raw perf bus.
Requirements
- Chrome 94+, Edge 94+, or Firefox with WebCodecs H.264 encoding support
- Secure Context (HTTPS or localhost)
- dash.js >= 4.0.0
License
MIT
