JSConf India 2023
1const canvas = document.querySelector('canvas');2const record = (duration) => {3const stream = canvas.captureStream(30);4const recorder = new MediaRecorder(stream);5const chunks = [];67recorder.ondataavailable = (event) => chunks.push(event.data);8recorder.onstop = () => {9const videoBlob = new Blob(chunks, { type: 'video/webm' });10download(videoBlob);11};1213recorder.start();14setTimeout(() => recorder.stop(), duration);15}
canvas.captureStream(framerate)
framerate
– maximum framerate in which the video is supposed to be captured.1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = []
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = []
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = [
]
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = [
]
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = [
]
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = [
...
]
1const images = [];23while(frame < totalFrames) {4seekCanvas(frame);5const image = canvas.toDataURL('image/webp');6images.push(image);7++frame;8}910const video = encode(images);11download(video);
images = [
...
]
function encode(images) {return window.Whammy.fromImageArray(images, 30);}
VideoEncoder
1const encoder = new VideoEncoder({2output: (chunk, metadata) => {3// Do something with encoded chunk4},5error: (err) => {6// Handle error7}8});
VideoEncoder
1const chunks = [];2const encoder = new VideoEncoder({3output: (chunk, metadata) => {4chunks.push(chunk);5},6error: (err) => {7console.log(err);8}9});
VideoEncoder
1const encoder = new VideoEncoder(...);23encoder.configure({4codec: 'vp8',5hardwareAcceleration: 'prefer-hardware',6framerate: 30,7latencyMode: 'quality',8width: 1920,9height: 1080,10bitrate: 2_000_000,11...12});
codec
?codec
again?container | codec | example |
---|---|---|
WebM | VP8, AV1 | vp8 |
MP4 | AVC, AV1 | avc1.420034, av01.0.12M.10 |
1const chunks = [];2const encoder = new VideoEncoder(...);3const FPS = 30;45encoder.configure({6codec: 'avc1.420034',7avc: { format: 'annexb' },8hardwareAcceleration: 'prefer-hardware',9framerate: FPS,10latencyMode: 'quality',11width: 1920,12height: 1080,13bitrate: 2_000_000,14...15});
1const chunks = [];2const encoder = new VideoEncoder(...);3const FPS = 30;45const config = {6codec: 'avc1.420034',7...8};910const support = await VideoEncoder.isConfigSupported(config);11if (!support.supported) {12console.log("Oh noes, config not supported! 😢");13return;14}1516encoder.configure(config);
1const chunks = [];2const encoder = new VideoEncoder(...);3const FPS = 30;4encoder.configure({ ... });56while(frame < totalFrames) {7seekCanvas(frame);89const videoFrame = new VideoFrame(canvas, {10timestamp: frame / FPS * 1_000_000,11});12encoder.encode(videoFrame);13videoFrame.close();1415++frame;16}
1const chunks = [];2const encoder = new VideoEncoder(...);3const FPS = 30;4encoder.configure({ ... });56while(frame < totalFrames) {7...8}910await encoder.flush();11encoder.close();1213const videoBuffer = concatBuffers(chunks);
videoBuffer
?1...2await encoder.flush();3encoder.close();45const videoBuffer = concatBuffers(chunks);67const ffmpeg = createFFmpeg();8await ffmpeg.load();
1...2await encoder.flush();3encoder.close();45const videoBuffer = concatBuffers(chunks);67const ffmpeg = createFFmpeg();8await ffmpeg.load();910ffmpeg.FS('writeFile', 'raw.h264', videoBuffer);11ffmpeg.run('-i', 'raw.h264', '-map', '0:v:0', '-c:v', 'copy', 'output.mp4');
1...2await encoder.flush();3encoder.close();45const videoBuffer = concatBuffers(chunks);67const ffmpeg = createFFmpeg();8await ffmpeg.load();910ffmpeg.FS('writeFile', 'raw.h264', videoBuffer);11ffmpeg.run('-i', 'raw.h264', '-map', '0:v:0', '-c:v', 'copy', 'output.mp4');1213const output = ffmpeg.FS('readFile', 'output.mp4');14ffmpeg.FS('unlink', 'raw.h264');15ffmpeg.FS('unlink', 'output.mp4');
1...2await encoder.flush();3encoder.close();45const videoBuffer = concatBuffers(chunks);67const ffmpeg = createFFmpeg();8await ffmpeg.load();910ffmpeg.FS('writeFile', 'raw.h264', videoBuffer);11ffmpeg.run('-i', 'raw.h264', '-map', '0:v:0', '-c:v', 'copy', 'output.mp4');1213const output = ffmpeg.FS('readFile', 'output.mp4');14ffmpeg.FS('unlink', 'raw.h264');15ffmpeg.FS('unlink', 'output.mp4');1617const video = new Blob([output.buffer]);18download(video);