Transmuxing in a Web Worker
Overview
When playing HLS streams with MPEG-TS segments, Shaka Player must transmux
(convert) each segment from MPEG-TS to fMP4 before feeding it to
MediaSource. By default this work happens synchronously on the main thread,
which can cause frame drops or audio glitches on slower devices.
Shaka Player can offload this work to a dedicated Web Worker, freeing the main thread for rendering and UI. The worker is shared across all active streams (audio and video), so only one worker thread is ever created per page.
Quick Summary
To enable worker-based transmuxing you must do two things:
- Make the compiled worker script reachable over HTTP from your page.
- Tell Shaka where to find it via
mediaSource.transmuxWorkerUrl.
Shaka does not auto-detect the worker URL. The library cannot reliably know where its assets live at runtime (script tags, bundler output, CDNs, ES modules, hashed filenames all differ). The integrating application owns how Shaka is loaded, so the application also owns the worker URL.
If transmuxWorkerUrl is not set, transmux falls back to the main thread.
No error is thrown — the feature simply stays dormant.
Configuration Key
player.configure({
mediaSource: {
// URL of the worker script. Empty by default. Required for the worker
// to run; empty string keeps transmuxing on the main thread.
transmuxWorkerUrl: '',
},
});
When transmuxWorkerUrl is a non-empty string, Shaka creates the worker on
the first transmux call. Empty value (or device-level opt-out — see below)
falls back to main-thread transmuxing.
The Worker File
Compiled builds emit the worker as a standalone bundle next to the main library bundle:
| Build type | Worker filename |
|---|---|
| Release | shaka-player.transmuxer-worker.js |
| Debug | shaka-player.transmuxer-worker.debug.js |
Both files live in dist/ after running python3 build/all.py, and ship
inside the npm package under node_modules/shaka-player/dist/.
You must serve the worker file from a URL the browser can reach. The exact path depends on how you deploy Shaka — see the patterns below.
Deployment Patterns
Plain <script> tag
If you ship shaka-player.compiled.js from /static/, copy the worker
alongside it and point Shaka at the same directory:
<script src="/static/shaka-player.compiled.js"></script>
<script>
const player = new shaka.Player();
player.configure(
'mediaSource.transmuxWorkerUrl',
'/static/shaka-player.transmuxer-worker.js');
</script>
Webpack 5 / Vite / Rollup (modern bundlers)
Use new URL(..., import.meta.url). The bundler resolves the npm path,
copies the worker into the build output, and rewrites the URL at build
time:
const workerUrl = new URL(
'shaka-player/dist/shaka-player.transmuxer-worker.js',
import.meta.url,
).toString();
player.configure('mediaSource.transmuxWorkerUrl', workerUrl);
Webpack 4
Webpack 4 has no import.meta.url support. Use file-loader or
asset/resource:
import workerUrl from
'shaka-player/dist/shaka-player.transmuxer-worker.js?url';
player.configure('mediaSource.transmuxWorkerUrl', workerUrl);
Create React App / static public/ folder
Copy node_modules/shaka-player/dist/shaka-player.transmuxer-worker.js
into public/ (or your equivalent static asset folder) and reference it
by absolute path:
player.configure(
'mediaSource.transmuxWorkerUrl',
'/shaka-player.transmuxer-worker.js');
A small build script that copies the file on postinstall keeps the
worker version in sync with the installed package.
Same-Origin and CORS Requirements
new Worker(url) requires the worker script to be either:
- same origin as the host page, or
- served with CORS headers that allow the host origin
(
Access-Control-Allow-Origin, plusCross-Origin-Resource-Policywhen the page itself runs cross-origin).
Self-hosted same-origin deployments need no extra headers. Cross-origin
deployments must serve the worker with proper CORS configuration, or the
browser will reject the new Worker(url) call and Shaka will fall back to
main-thread transmuxing.
Uncompiled / Development Mode
In uncompiled mode (running Shaka directly from source for development)
the worker is bootstrapped from transmuxer_worker.uncompiled.js at the
repository root. Set the URL the same way:
player.configure(
'mediaSource.transmuxWorkerUrl',
'/path/to/shaka-player/transmuxer_worker.uncompiled.js');
Run python3 build/gendeps.py first so the bootstrap script can locate
the Closure dependency graph.
Disabling the Worker
To force main-thread transmuxing — for debugging or for environments
where Web Workers are unreliable — leave transmuxWorkerUrl empty, or
clear it at runtime:
player.configure('mediaSource.transmuxWorkerUrl', '');
Certain TV platforms (Tizen, WebOS, Hisense) also opt out of worker transmuxing internally via the device layer regardless of this setting, because Worker support is often limited or unstable on those devices.
Fallback Chain
Shaka silently falls back to main-thread transmuxing in any of these cases:
transmuxWorkerUrlis empty.- The device platform reports no Worker support (e.g. older Tizen/WebOS).
new Worker(url)throws — CSP block, network error, MIME mismatch.- The first
postMessageto the worker fails. - The worker does not respond within 30 seconds for a given segment.
The fallback is transparent: playback continues without error. A warning is logged to the console when a fallback is taken.
Troubleshooting
Worker URL returns 404. Open DevTools → Network, filter by worker,
and compare the requested URL against your deployed asset paths. Most
often the worker file was not copied alongside the main bundle.
CSP blocks the worker. Add the worker's origin to the relevant Content-Security-Policy directives:
worker-src 'self';
script-src 'self';
If the worker is served from a different origin, list that origin in
both directives (and connect-src if your app also fetches it
directly).
Cross-origin Worker rejected. Ensure the worker response includes
Access-Control-Allow-Origin: <your page origin> and, when the page is
itself cross-origin-isolated, Cross-Origin-Resource-Policy: cross-origin.
Need to isolate a transmux bug. Disable the worker as shown above so that any transmux failure surfaces on the main thread with a full stack trace.
For general configuration see Configuration, and for all
mediaSource options see shaka.extern.MediaSourceConfiguration.