Real-time datamosh effects in the browser using WebCodecs. Feed it a camera or video, trigger drops and corruptions, watch frames smear.
Requires Chrome / Edge 94+ over HTTPS or localhost.
Credits: Over-delta technique by Amagi. Inspired by Hydra Datamosh (Emptyflash) and Hydra Synth. API design and H.264 implementation by geikha. General implementation and documentation done using Claude Code.
npm install datamoshlive
npm run build # → dist/datamoshlive.js + dist/datamoshlive.esm.jsOr use the CDN:
<script src="https://unpkg.com/datamoshlive@latest/dist/datamoshlive.js"></script>const dm = new DatamoshLive({ width: 640, height: 480 });
dm.mount(); // append to document.body (or pass a custom parent element)
await dm.initCam();
// Click to smear
dm.canvas.addEventListener('click', () => dm.drop());That's it. dm.canvas is a plain <canvas> — customize size and positioning as needed.
Drop on click:
dm.canvas.addEventListener('click', () => dm.drop());Continuous probabilistic dropping:
dm.dropRate = 0.15; // 15% chance per frame; 0 = offFreeze and smear on hold:
dm.canvas.addEventListener('mousedown', () => dm.hold = true);
dm.canvas.addEventListener('mouseup', () => dm.hold = false);Persistent datamosh (no auto-recovery):
dm.recover = false;
dm.drop(); // stays glitched until dm.sync()Parameters as live functions (called every frame):
let mouseX = 0;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX / window.innerWidth;
});
dm.speed = () => Math.floor(mouseX * 4) + 1; // 1–5 based on mouse XCapture and replay a moment:
dm.sample(30); // capture the next 30 frames
setTimeout(() => dm.inject(), 500); // replay them half a second later
// dm.sampleLoop = true; // keep looping until stopInject()const dm = new DatamoshLive(opts)| Option | Default | Description |
|---|---|---|
width |
640 |
Encoder/decoder resolution |
height |
480 |
Encoder/decoder resolution |
canvasWidth |
width |
Output canvas size (can differ from render resolution) |
canvasHeight |
height |
Output canvas size |
canvas |
auto | Provide an existing <canvas> element |
params |
{} |
Initial parameter overrides |
All source methods are async. The loop starts automatically unless you pass autoStart: false.
Open a webcam. selector is a camera index (number, default 0) or a label string.
Load a video file. source is a URL string or an existing <video> element. Loops automatically.
Use any <canvas> as a live source (e.g. a generative sketch).
Start or restart the capture/encode/decode loop. Safe to call while already running.
Pause the loop.
Drop the next encoded frame. The decoder keeps its old reference and decodes subsequent deltas against stale content — producing motion smear. Auto-recovers after recoverAfter frames if recover is enabled.
Zero out a random region of the next encoded frame's bitstream before decode. Produces block corruption and color drift rather than smear. Also auto-recovers.
Force a clean keyframe and deliver it to the decoder immediately. Cancels any pending drop or recovery. Use this to snap back to a clean image at any time.
Set directly as properties or via setParam(name, value) / setParams({...}).
All parameters accept live functions — set a function and it will be called each frame, with automatic type coercion and bounds clamping:
dm.speed = 4; // static
dm.speed = () => Math.sin(Date.now() / 500) * 3 + 4; // live| Parameter | Type | Default | Description |
|---|---|---|---|
dm.speed |
integer ≥1 | 1 |
Times each delta frame is decoded. Higher = stronger smear accumulation |
dm.enabled |
boolean | true |
false bypasses the codec entirely and draws source frames directly |
dm.hold |
boolean | false |
Freeze the canvas — drops all incoming frames. On release, smears briefly before recovering |
| Parameter | Type | Default | Description |
|---|---|---|---|
dm.dropRate |
0–1 | 0 |
Per-frame drop probability. 0 = off |
| Parameter | Type | Default | Description |
|---|---|---|---|
dm.corruptRate |
0–1 | 0 |
Per-frame corruption probability. 0 = off |
dm.corruptAmount |
0–1 | 0.3 |
Fraction of frame bytes zeroed per corruption event |
| Parameter | Type | Default | Description |
|---|---|---|---|
dm.recover |
boolean | true |
Auto-recover after drops and corruptions |
dm.recoverAfter |
integer | fps || 30 |
Frames to wait before forcing a clean recovery keyframe |
| Parameter | Type | Default | Description |
|---|---|---|---|
dm.bitrate |
number | 1000000 |
Encoder bitrate in bits/s. Lower = more compression artifacts |
dm.codec |
string | 'vp8' |
'vp8', 'vp9', 'h264', or 'av1'. H.264 and AV1 availability is browser/platform-dependent |
dm.fit |
string | 'fill' |
Canvas scaling: 'fill' (crop to fill), 'fit' (letterbox), 'stretch' (distort) |
dm.fps |
number | 30 |
Frame rate cap. 0 = unlimited |
Codec strings are automatically resolved to the appropriate level for your render resolution. You can also pass a raw WebCodecs codec string directly.
Changing bitrate or codec resets the encoder/decoder pair.
Capture a buffer of encoded frames and replay it.
Capture the next n frames into the buffer (default: sampleFrames). Captures post-effect output — artifacts are baked in.
Replay the buffer instead of encoding the live feed. Plays once then stops unless sampleLoop is true. Also triggers recovery when recover is enabled.
Stop injection and resume live encoding.
| Parameter | Type | Default | Description |
|---|---|---|---|
dm.sampleFrames |
integer | 1 |
Default frame count for sample() |
dm.sampleLoop |
boolean | false |
true = loop buffer indefinitely, false = play once then stop |
Change the encoder/decoder resolution. Resets the codec pipeline.
Also: dm.width = 800; dm.height = 600;
Resize the output canvas. Does not reset the codec.
Remove dm.canvas from the DOM.
Shortcuts: show() calls mount(), hide() calls unmount().
Stop the loop, remove the canvas, close the encoder/decoder.
Append dm.canvas to a DOM element (defaults to document.body). Positions canvas absolutely at top-left. Throws an error if parent is not an HTMLElement.
VP8/VP9/H.264/AV1 delta frames are encoded relative to a reference (the last keyframe the decoder received). Drop a frame before the decoder sees it and its reference stays stale — every subsequent delta is decoded against the wrong content, producing the characteristic datamosh smear.
drop() triggers this deliberately. recover schedules a clean keyframe after N frames to restore normal output.
corrupt() works differently: it zeroes out a region of a frame's encoded bitstream before decode. The decoder conceals the loss using surrounding block data, producing block corruption and color drift.
hold freezes the canvas by dropping all incoming chunks. On release, the pipeline skips keyframes and waits for the first delta to composite over the frozen image — a brief smear-on-freeze effect — before recovering.
