-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
|
||
<head> | ||
<meta charset="UTF-8"> | ||
<title>WebCodecs demo: Encoding and Decoding</title> | ||
<style> | ||
canvas { | ||
padding: 10px; | ||
background: gold; | ||
margin: 5px; | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<canvas id="src" width="640" height="360"></canvas> | ||
<div id="dst"></div> | ||
|
||
<script> | ||
const foods = ['🍇','🍈','🍉','🍊','🍋','🍌','🍍','🥭','🍎','🍏','🍐','🍑','🍒','🍓','🥝','🍅','🥥','🥑','🍆','🥔','🥕','🌽','🌶️','🥒','🥬','🥦','🧄','🧅','🍄','🥜','🌰', | ||
'🍞','🍞','🥐','🥖','🥨','🥯','🥞','🧇','🧀','🍖','🍗','🥩','🥓','🍔','🍟','🍕','🌭','🥪','🌮','🌯','🥙','🧆','🥚','🍳','🥘','🍲','🥣','🥗','🍿','🧈','🧂', | ||
'🥫','🍱','🍘','🍙','🍚','🍛','🍜','🍝','🍠','🍢','🍣','🍤','🍥','🥮','🍡','🥟','🥠','🥡','🦀','🦞','🦐','🦑','🦪','🍦','🍧','🍨','🍩','🍪','🎂','🍰','🧁', | ||
'🥧','🍫','🍬','🍭','🍮','🍯','🍼','🥛','☕','🍵','🍶','🍾','🍷','🍸','🍹','🍺','🍻','🥂','🥃','🥤','🧃','🧉','🧊']; | ||
function getRandomFood() { | ||
let index = Math.floor(Math.random() * foods.length); | ||
return foods[index]; | ||
} | ||
|
||
// Draw pretty animation on the source canvas | ||
async function startDrawing() { | ||
let cnv = document.getElementById("src"); | ||
var ctx = cnv.getContext('2d'); | ||
|
||
ctx.fillStyle = "#fff5e6"; | ||
let width = cnv.width; | ||
let height = cnv.height; | ||
let cx = width / 2; | ||
let cy = height / 2; | ||
let r = Math.min(width, height) / 5; | ||
|
||
ctx.font = '30px Helvetica'; | ||
const text = getRandomFood() + "📹📷Hello WebCodecs 🎥🎞️" + getRandomFood(); | ||
const size = ctx.measureText(text).width; | ||
|
||
let drawOneFrame = function (time) { | ||
let angle = Math.PI * 2 * (time / 5000); | ||
let scale = 1 + 0.3 * Math.sin(Math.PI * 2 * (time / 7000)); | ||
ctx.save(); | ||
ctx.fillRect(0, 0, width, height); | ||
|
||
ctx.translate(cx, cy); | ||
ctx.rotate(angle); | ||
ctx.scale(scale, scale); | ||
|
||
ctx.fillStyle = "hsl(" + (angle * 40 ) + ",80%,50%)"; | ||
ctx.fillRect(-size / 2, 10, size, 25); | ||
|
||
ctx.fillStyle = 'black'; | ||
ctx.fillText(text, -size / 2, 0); | ||
|
||
ctx.restore(); | ||
window.requestAnimationFrame(drawOneFrame); | ||
} | ||
window.requestAnimationFrame(drawOneFrame); | ||
} | ||
|
||
function startWorker() { | ||
let worker = new Worker('video-worker.js', { name: "Video worker"}); | ||
worker.onmessage = function(e) { | ||
// Recreate worker in case of an error | ||
console.log('Worker error: ' + e.data); | ||
worker.terminate(); | ||
//startWorker(); | ||
}; | ||
|
||
// Capture animation track for the source canvas | ||
let src_cnv = document.getElementById("src"); | ||
const fps = 20; | ||
let stream = src_cnv.captureStream(fps); | ||
const track = stream.getVideoTracks()[0] | ||
media_processor = new MediaStreamTrackProcessor(track); | ||
const reader = media_processor.readable; | ||
|
||
// Create a new destination canvas | ||
const dst_cnv = document.createElement('canvas'); | ||
dst_cnv.width = src_cnv.width; | ||
dst_cnv.height = src_cnv.height; | ||
const dst = document.getElementById("dst"); | ||
if (dst.firstChild) | ||
dst.removeChild(dst.firstChild); | ||
dst.appendChild(dst_cnv); | ||
let offscreen = dst_cnv.transferControlToOffscreen(); | ||
worker.postMessage({ | ||
canvas : offscreen, | ||
frame_source : reader, | ||
fps : fps | ||
}, [offscreen, reader]); | ||
} | ||
|
||
function main() { | ||
if (!("VideoFrame" in window)) { | ||
document.body.innerHTML = "<h1>WebCodecs API is not supported.</h1>"; | ||
return; | ||
} | ||
|
||
startDrawing(); | ||
startWorker(); | ||
} | ||
|
||
document.body.onload = main; | ||
</script> | ||
|
||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
let codec_string = "avc1.42001F"; | ||
|
||
function reportError(e) { | ||
// Report error to the main thread | ||
console.log(e.message) | ||
postMessage(e.message); | ||
} | ||
|
||
function captureAndEncode(frame_source, cnv, fps, processChunk) { | ||
let frame_counter = 0; | ||
|
||
const init = { | ||
output: processChunk, | ||
error: reportError | ||
}; | ||
|
||
const config = { | ||
codec: codec_string, | ||
width: cnv.width, | ||
height: cnv.height, | ||
//bitrate: 1000000, | ||
//avc : { format: "annexb" }, | ||
framerate: fps, | ||
hardwareAcceleration : "prefer-hardware", | ||
}; | ||
|
||
let encoder = new VideoEncoder(init); | ||
encoder.configure(config); | ||
|
||
let reader = frame_source.getReader(); | ||
async function readFrame () { | ||
const result = await reader.read(); | ||
let frame = result.value; | ||
|
||
if (encoder.encodeQueueSize < 2) { | ||
frame_counter++; | ||
const insert_keyframe = false; // (frame_counter % 130) == 0; | ||
encoder.encode(frame, { keyFrame: insert_keyframe }); | ||
frame.close(); | ||
} else { | ||
// Too many frames in flight, encoder is overwhelmed | ||
// let's drop this frame. | ||
console.log("dropping a frame"); | ||
frame.close(); | ||
} | ||
|
||
setTimeout(readFrame, 1); | ||
}; | ||
|
||
readFrame(); | ||
} | ||
|
||
function startDecodingAndRendering(cnv) { | ||
let ctx = cnv.getContext("2d"); | ||
let ready_frames = []; | ||
let underflow = true; | ||
|
||
async function renderFrame() { | ||
if (ready_frames.length == 0) { | ||
underflow = true; | ||
return; | ||
} | ||
let frame = ready_frames.shift(); | ||
underflow = false; | ||
|
||
ctx.drawImage(frame, 0, 0); | ||
frame.close(); | ||
|
||
// Immediately schedule rendering of the next frame | ||
setTimeout(renderFrame, 0); | ||
} | ||
|
||
function handleFrame(frame) { | ||
ready_frames.push(frame); | ||
if (underflow) { | ||
underflow = false; | ||
setTimeout(renderFrame, 0); | ||
} | ||
} | ||
|
||
const init = { | ||
output: handleFrame, | ||
error: reportError | ||
}; | ||
|
||
let decoder = new VideoDecoder(init); | ||
return decoder; | ||
} | ||
|
||
var pre_config = null; | ||
function main(frame_source, canvas, fps) { | ||
let decoder = startDecodingAndRendering(canvas); | ||
function processChunk(chunk, md) { | ||
let config = md.decoderConfig; | ||
if (config) { | ||
if (pre_config == null) { | ||
config.hardwareAcceleration = 'prefer-hardware'; | ||
console.log("decoder reconfig", JSON.stringify(config, undefined, 2)); | ||
decoder.configure(config); | ||
pre_config = config; | ||
} | ||
} | ||
|
||
decoder.decode(chunk); | ||
} | ||
captureAndEncode(frame_source, canvas, fps, processChunk); | ||
} | ||
|
||
self.onmessage = async function(e) { | ||
let frame_source = e.data.frame_source; | ||
let canvas = e.data.canvas; | ||
let fps = e.data.fps; | ||
|
||
main(frame_source, canvas, fps); | ||
} |