Red Screen Video
The simplest possible example - encode 1 second of red frames:Copy
const { VideoEncoder, VideoFrame } = require('node-webcodecs');
const fs = require('fs');
async function encodeRedScreen() {
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk) => {
const buffer = Buffer.alloc(chunk.byteLength);
chunk.copyTo(buffer);
chunks.push(buffer);
console.log(`Encoded chunk ${chunks.length}: ${chunk.byteLength} bytes`);
},
error: (err) => {
console.error('Encoder error:', err);
throw err;
}
});
// Configure for H.264
encoder.configure({
codec: 'avc1.42E01E',
width: 640,
height: 480,
bitrate: 1_000_000
});
console.log('Encoding 30 frames (1 second at 30fps)...');
// Create red frame data (RGBA)
const redFrame = new Uint8Array(640 * 480 * 4);
for (let i = 0; i < redFrame.length; i += 4) {
redFrame[i] = 255; // Red
redFrame[i + 1] = 0; // Green
redFrame[i + 2] = 0; // Blue
redFrame[i + 3] = 255; // Alpha
}
// Encode 30 frames
for (let i = 0; i < 30; i++) {
const frame = new VideoFrame(redFrame, {
format: 'RGBA',
codedWidth: 640,
codedHeight: 480,
timestamp: i * 33333 // 30fps = 33.333ms per frame
});
encoder.encode(frame, { keyFrame: i === 0 });
frame.close();
}
await encoder.flush();
encoder.close();
// Save to file
const output = Buffer.concat(chunks);
fs.writeFileSync('red_screen.h264', output);
console.log(`✓ Saved ${output.length} bytes to red_screen.h264`);
console.log('Play with: ffplay red_screen.h264');
}
encodeRedScreen().catch(console.error);
Rainbow Gradient
Encode a colorful gradient animation:Copy
const { VideoEncoder, VideoFrame } = require('node-webcodecs');
const fs = require('fs');
async function encodeRainbowGradient() {
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk) => {
const buffer = Buffer.alloc(chunk.byteLength);
chunk.copyTo(buffer);
chunks.push(buffer);
},
error: (err) => { throw err; }
});
encoder.configure({
codec: 'avc1.42E01E',
width: 1280,
height: 720,
bitrate: 5_000_000,
framerate: 30
});
console.log('Encoding rainbow gradient...');
// Encode 60 frames (2 seconds)
for (let frameNum = 0; frameNum < 60; frameNum++) {
const data = new Uint8Array(1280 * 720 * 4);
for (let y = 0; y < 720; y++) {
for (let x = 0; x < 1280; x++) {
const idx = (y * 1280 + x) * 4;
// Rainbow gradient that animates
const hue = ((x / 1280) + (frameNum / 60)) % 1.0;
const rgb = hslToRgb(hue, 1.0, 0.5);
data[idx] = rgb[0];
data[idx + 1] = rgb[1];
data[idx + 2] = rgb[2];
data[idx + 3] = 255;
}
}
const frame = new VideoFrame(data, {
format: 'RGBA',
codedWidth: 1280,
codedHeight: 720,
timestamp: frameNum * 33333
});
encoder.encode(frame, { keyFrame: frameNum % 30 === 0 });
frame.close();
if ((frameNum + 1) % 10 === 0) {
console.log(` Encoded ${frameNum + 1}/60 frames`);
}
}
await encoder.flush();
encoder.close();
const output = Buffer.concat(chunks);
fs.writeFileSync('rainbow.h264', output);
console.log(`✓ Saved ${output.length} bytes to rainbow.h264`);
}
// Helper: HSL to RGB conversion
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
encodeRainbowGradient().catch(console.error);
Text Frame
Encode a frame with text (using Canvas):Copy
const { VideoEncoder, VideoFrame } = require('node-webcodecs');
const { createCanvas } = require('canvas');
const fs = require('fs');
async function encodeTextFrame() {
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk) => {
const buffer = Buffer.alloc(chunk.byteLength);
chunk.copyTo(buffer);
chunks.push(buffer);
},
error: (err) => { throw err; }
});
encoder.configure({
codec: 'avc1.42E01E',
width: 1920,
height: 1080,
bitrate: 5_000_000
});
// Create canvas
const canvas = createCanvas(1920, 1080);
const ctx = canvas.getContext('2d');
// Encode 90 frames (3 seconds)
for (let i = 0; i < 90; i++) {
// Clear canvas
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, 1920, 1080);
// Draw text
ctx.fillStyle = '#00ffc8';
ctx.font = 'bold 120px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const text = `Frame ${i + 1}`;
ctx.fillText(text, 960, 540);
// Get pixel data
const imageData = ctx.getImageData(0, 0, 1920, 1080);
const pixels = new Uint8Array(imageData.data);
// Create frame
const frame = new VideoFrame(pixels, {
format: 'RGBA',
codedWidth: 1920,
codedHeight: 1080,
timestamp: i * 33333
});
encoder.encode(frame, { keyFrame: i % 30 === 0 });
frame.close();
}
await encoder.flush();
encoder.close();
const output = Buffer.concat(chunks);
fs.writeFileSync('text_frames.h264', output);
console.log(`✓ Encoded 90 frames with text`);
}
encodeTextFrame().catch(console.error);
Multiple Colors
Encode frames with different colors:Copy
const { VideoEncoder, VideoFrame } = require('node-webcodecs');
const fs = require('fs');
async function encodeColorSequence() {
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk) => {
const buffer = Buffer.alloc(chunk.byteLength);
chunk.copyTo(buffer);
chunks.push(buffer);
},
error: (err) => { throw err; }
});
encoder.configure({
codec: 'avc1.42E01E',
width: 1280,
height: 720,
bitrate: 3_000_000
});
// Color sequence
const colors = [
{ name: 'Red', r: 255, g: 0, b: 0 },
{ name: 'Orange', r: 255, g: 165, b: 0 },
{ name: 'Yellow', r: 255, g: 255, b: 0 },
{ name: 'Green', r: 0, g: 255, b: 0 },
{ name: 'Blue', r: 0, g: 0, b: 255 },
{ name: 'Indigo', r: 75, g: 0, b: 130 },
{ name: 'Violet', r: 148, g: 0, b: 211 }
];
console.log('Encoding color sequence...');
let frameNum = 0;
// 30 frames per color (1 second each)
for (const color of colors) {
console.log(` Encoding ${color.name}...`);
const data = new Uint8Array(1280 * 720 * 4);
// Fill with solid color
for (let i = 0; i < data.length; i += 4) {
data[i] = color.r;
data[i + 1] = color.g;
data[i + 2] = color.b;
data[i + 3] = 255;
}
// Encode 30 frames of this color
for (let i = 0; i < 30; i++) {
const frame = new VideoFrame(data, {
format: 'RGBA',
codedWidth: 1280,
codedHeight: 720,
timestamp: frameNum * 33333
});
encoder.encode(frame, { keyFrame: frameNum % 30 === 0 });
frame.close();
frameNum++;
}
}
await encoder.flush();
encoder.close();
const output = Buffer.concat(chunks);
fs.writeFileSync('colors.h264', output);
console.log(`✓ Encoded ${frameNum} frames (${colors.length} colors)`);
}
encodeColorSequence().catch(console.error);
Checkerboard Pattern
Encode an animated checkerboard:Copy
async function encodeCheckerboard() {
const chunks = [];
const encoder = new VideoEncoder({
output: (chunk) => {
const buffer = Buffer.alloc(chunk.byteLength);
chunk.copyTo(buffer);
chunks.push(buffer);
},
error: (err) => { throw err; }
});
encoder.configure({
codec: 'avc1.42E01E',
width: 1920,
height: 1080,
bitrate: 5_000_000
});
console.log('Encoding checkerboard pattern...');
// Encode 60 frames
for (let frameNum = 0; frameNum < 60; frameNum++) {
const data = new Uint8Array(1920 * 1080 * 4);
const squareSize = 40;
for (let y = 0; y < 1080; y++) {
for (let x = 0; x < 1920; x++) {
const idx = (y * 1920 + x) * 4;
// Checkerboard pattern that shifts
const offsetX = frameNum * 5;
const col = Math.floor((x + offsetX) / squareSize);
const row = Math.floor(y / squareSize);
const isBlack = (col + row) % 2 === 0;
const value = isBlack ? 0 : 255;
data[idx] = value;
data[idx + 1] = value;
data[idx + 2] = value;
data[idx + 3] = 255;
}
}
const frame = new VideoFrame(data, {
format: 'RGBA',
codedWidth: 1920,
codedHeight: 1080,
timestamp: frameNum * 33333
});
encoder.encode(frame, { keyFrame: frameNum % 30 === 0 });
frame.close();
}
await encoder.flush();
encoder.close();
const output = Buffer.concat(chunks);
fs.writeFileSync('checkerboard.h264', output);
console.log('✓ Encoded checkerboard animation');
}
encodeCheckerboard().catch(console.error);
Tips for Beginners
Always close frames
Always close frames
Copy
const frame = new VideoFrame(data, options);
encoder.encode(frame);
frame.close(); // ← CRITICAL!
Use correct timestamp units
Use correct timestamp units
Copy
// ❌ Wrong (milliseconds)
timestamp: i * 33
// ✅ Correct (microseconds)
timestamp: i * 33333 // 30 fps
Await flush before closing
Await flush before closing
Copy
await encoder.flush(); // ← Wait for all frames
encoder.close(); // ← Then close
Set keyframes appropriately
Set keyframes appropriately
Copy
encoder.encode(frame, {
keyFrame: i % 30 === 0 // Keyframe every second
});