Skip to content

Commit e5ccdcd

Browse files
author
Dan Sanders
committed
Refactor
1 parent 471a6bc commit e5ccdcd

23 files changed

+512
-252
lines changed

samples/audio-video-player/audio_renderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MP4PullDemuxer } from "./mp4_pull_demuxer.js";
2-
import { RingBuffer } from "./third_party/ringbufjs/ringbuf.js";
2+
import { RingBuffer } from "../third_party/ringbufjs/ringbuf.js";
33

44
const DATA_BUFFER_DECODE_TARGET_DURATION = 0.3;
55
const DATA_BUFFER_DURATION = 0.6;

samples/audio-video-player/audio_video_player.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
// house and drive the demuxers and decoders.
6767
let mediaWorker = new Worker('./media_worker.js');
6868
mediaWorker.postMessage({command: 'initialize',
69-
audioFile: 'bbb_audio_aac_frag.mp4',
70-
videoFile: 'bbb_video_avc_frag.mp4',
69+
audioFile: '../data/bbb_audio_aac_frag.mp4',
70+
videoFile: '../data/bbb_video_avc_frag.mp4',
7171
canvas: offscreenCanvas},
7272
{transfer: [offscreenCanvas]});
7373

samples/audio-video-player/media_worker.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ console.info(`Worker started`);
66
// and the modules below. But presently mp4box.js does not use ES6 modules,
77
// so we import it as an old-style script and use the dynamic import() to load
88
// our modules below.
9-
importScripts('./third_party/mp4boxjs/mp4box.all.min.js');
9+
importScripts('../third_party/mp4boxjs/mp4box.all.min.js');
1010
let moduleLoadedResolver = null;
1111
let modulesReady = new Promise(resolver => (moduleLoadedResolver = resolver));
1212
let playing = false;
@@ -82,4 +82,4 @@ self.addEventListener('message', async function(e) {
8282
console.error(`Worker bad message: ${e.data}`);
8383
}
8484

85-
});
85+
});

samples/audio-video-player/web_audio_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class WebAudioController {
3737
this.audioContext.suspend();
3838

3939
// Make script modules available for execution by AudioWorklet.
40-
var workletSource = await URLFromFiles(["third_party/ringbufjs/ringbuf.js", "audiosink.js"]);
40+
var workletSource = await URLFromFiles(["../third_party/ringbufjs/ringbuf.js", "audiosink.js"]);
4141
await this.audioContext.audioWorklet.addModule(workletSource);
4242

4343
// Get an instance of the AudioSink worklet, passing it the memory for a
File renamed without changes.

samples/image-decoder/animated-gif-renderer.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<!doctype html>
22
<head>
33
<title>WebCodecs Animated GIF Renderer</title>
4-
<meta http-equiv="origin-trial" content="ArdlZia9G23wi6S2x/vVoTla5x9r1wtreSPqhUq36tpGH7HRmAkPgpBnpkfePFeClDJDzzYAhtDPoI5hJioArAYAAABjeyJvcmlnaW4iOiJodHRwczovL3czYy5naXRodWIuaW86NDQzIiwiZmVhdHVyZSI6IldlYkNvZGVjcyIsImV4cGlyeSI6MTYzODQwMzE5OSwiaXNTdWJkb21haW4iOnRydWV9"/>
54
</head>
65
<canvas width="320" height="270"></canvas>
76
<br/><br/>
@@ -93,5 +92,5 @@
9392
imageDecoder.decode({frameIndex: imageIndex}).then(renderImage);
9493
}
9594

96-
fetch('giphy.gif').then(response => decodeImage(response.body));
95+
fetch('../data/giphy.gif').then(response => decodeImage(response.body));
9796
</script>

samples/media/bbb.mp4

-30.9 MB
Binary file not shown.

samples/video-decode-display/demux_decode_worker.js

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
importScripts("../third_party/mp4boxjs/mp4box.all.min.js");
2+
3+
// Wraps an MP4Box File as a WritableStream underlying sink.
4+
class MP4FileSink {
5+
#setStatus = null;
6+
#file = null;
7+
#offset = 0;
8+
9+
constructor(file, setStatus) {
10+
this.#file = file;
11+
this.#setStatus = setStatus;
12+
}
13+
14+
write(chunk) {
15+
// MP4Box.js requires buffers to be ArrayBuffers, but we have a Uint8Array.
16+
const buffer = new ArrayBuffer(chunk.byteLength);
17+
new Uint8Array(buffer).set(chunk);
18+
19+
// Inform MP4Box where in the file this chunk is from.
20+
buffer.fileStart = this.#offset;
21+
this.#offset += buffer.byteLength;
22+
23+
// Append chunk.
24+
this.#setStatus("fetch", (this.#offset / (1024 ** 2)).toFixed(1) + " MiB");
25+
this.#file.appendBuffer(buffer);
26+
}
27+
28+
close() {
29+
this.#setStatus("fetch", "Done");
30+
this.#file.flush();
31+
}
32+
}
33+
34+
// Demuxes the first video track of an MP4 file using MP4Box, calling
35+
// `onConfig()` and `onChunk()` with appropriate WebCodecs objects.
36+
class MP4Demuxer {
37+
#onConfig = null;
38+
#onChunk = null;
39+
#setStatus = null;
40+
#file = null;
41+
42+
constructor(uri, {onConfig, onChunk, setStatus}) {
43+
this.#onConfig = onConfig;
44+
this.#onChunk = onChunk;
45+
this.#setStatus = setStatus;
46+
47+
// Configure an MP4Box File for demuxing.
48+
this.#file = MP4Box.createFile();
49+
this.#file.onError = error => setStatus("demux", error);
50+
this.#file.onReady = this.#onReady.bind(this);
51+
this.#file.onSamples = this.#onSamples.bind(this);
52+
53+
// Fetch the file and pipe the data through.
54+
const fileSink = new MP4FileSink(this.#file, setStatus);
55+
fetch(uri).then(response => {
56+
response.body.pipeTo(new WritableStream(fileSink, {highWaterMark: 2}));
57+
});
58+
}
59+
60+
// Get the appropriate `description` for a specific track. Assumes that the
61+
// track is H.264.
62+
#description(track) {
63+
const trak = this.#file.getTrackById(track.id);
64+
for (const entry of trak.mdia.minf.stbl.stsd.entries) {
65+
if (entry.avcC) {
66+
const stream = new DataStream(undefined, 0, DataStream.BIG_ENDIAN);
67+
entry.avcC.write(stream);
68+
return new Uint8Array(stream.buffer, 8); // Remove the box header.
69+
}
70+
}
71+
throw "avcC not found";
72+
}
73+
74+
#onReady(info) {
75+
this.#setStatus("demux", "Ready");
76+
const track = info.videoTracks[0];
77+
78+
// Generate and emit an appropriate VideoDecoderConfig.
79+
this.#onConfig({
80+
codec: track.codec,
81+
codedHeight: track.video.height,
82+
codedWidth: track.video.width,
83+
description: this.#description(track),
84+
});
85+
86+
// Start demuxing.
87+
this.#file.setExtractionOptions(track.id);
88+
this.#file.start();
89+
}
90+
91+
#onSamples(track_id, ref, samples) {
92+
// Generate and emit an EncodedVideoChunk for each demuxed sample.
93+
for (const sample of samples) {
94+
this.#onChunk(new EncodedVideoChunk({
95+
type: sample.is_sync ? "key" : "delta",
96+
timestamp: 1e6 * sample.cts / sample.timescale,
97+
duration: 1e6 * sample.duration / sample.timescale,
98+
data: sample.data
99+
}));
100+
}
101+
}
102+
}
Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,77 @@
1-
<!doctype html>
21
<!DOCTYPE html>
32
<html>
43
<head>
5-
<title>WebCodec MP4 frame extration demo</title>
6-
<meta http-equiv="origin-trial" content="ArdlZia9G23wi6S2x/vVoTla5x9r1wtreSPqhUq36tpGH7HRmAkPgpBnpkfePFeClDJDzzYAhtDPoI5hJioArAYAAABjeyJvcmlnaW4iOiJodHRwczovL3czYy5naXRodWIuaW86NDQzIiwiZmVhdHVyZSI6IldlYkNvZGVjcyIsImV4cGlyeSI6MTYzODQwMzE5OSwiaXNTdWJkb21haW4iOnRydWV9" />
4+
<title>WebCodec MP4 decode sample</title>
75
</head>
86
<body>
97
<p>
10-
This demo extracts all frames from an MP4 file and renders them to a canvas as fast as possible. It uses <a href="https://github.com/gpac/mp4box.js/">mp4box.js</a> to parse and demux the file.
8+
This demo decodes all frames from an MP4 file and renders them to a canvas as fast as possible.
9+
It uses <a href="https://github.com/gpac/mp4box.js/">mp4box.js</a> for demuxing.
1110
</p>
12-
<canvas></canvas>
13-
</body>
1411

15-
<script type="module">
16-
var demuxDecodeWorker;
12+
<p>
13+
Renderer:
14+
<label for="renderer_2d">
15+
<input id="renderer_2d" type="radio" name="renderer" value="2d"> 2D
16+
</label>
17+
<label for="renderer_webgl">
18+
<input id="renderer_webgl" type="radio" name="renderer" value="webgl" checked> WebGL
19+
</label>
20+
<label for="renderer_webgl2">
21+
<input id="renderer_webgl2" type="radio" name="renderer" value="webgl2"> WebGL 2
22+
</label>
23+
<label for="renderer_webgpu">
24+
<input id="renderer_webgpu" type="radio" name="renderer" value="webgpu"> WebGPU
25+
</label>
26+
</p>
1727

18-
var canvas = document.querySelector("canvas");
19-
var offscreen = canvas.transferControlToOffscreen();
20-
document.body.appendChild(canvas);
28+
<p>
29+
<button id="start">Start</button>
30+
</p>
2131

22-
var frameCount = 0;
23-
var startTime;
32+
<table cellspacing="8" id="status">
33+
<tr><th align="right">Fetch</th><td id="fetch">Not started</td></tr>
34+
<tr><th align="right">Demux</th><td id="demux">Not started</td></tr>
35+
<tr><th align="right">Decode</th><td id="decode">Not started</td></tr>
36+
<tr><th align="right">Render</th><td id="render">Not started</td></tr>
37+
</table>
2438

25-
demuxDecodeWorker = new Worker("./demux_decode_worker.js");
26-
demuxDecodeWorker.postMessage({ canvas: offscreen}, [offscreen]);
27-
</script>
39+
<canvas></canvas>
40+
41+
<script type="module">
42+
// Play button.
43+
const startButton = document.querySelector("#start");
44+
startButton.addEventListener("click", () => {
45+
startButton.disabled = true;
46+
start();
47+
}, {once: true});
48+
49+
// Status UI.
50+
const status = {
51+
fetch: document.querySelector("#fetch"),
52+
demux: document.querySelector("#demux"),
53+
decode: document.querySelector("#decode"),
54+
render: document.querySelector("#render"),
55+
};
2856

57+
function setStatus(message) {
58+
for (const key in message.data) {
59+
if (status.hasOwnProperty(key)) {
60+
status[key].innerText = message.data[key];
61+
}
62+
}
63+
}
64+
65+
// Worker setup.
66+
function start() {
67+
const dataUri = "../data/bbb_video_avc_frag.mp4";
68+
const rendererName = document.querySelector("input[name=\"renderer\"]:checked").value;
69+
const canvas = document.querySelector("canvas").transferControlToOffscreen();
70+
const worker = new Worker("./worker.js");
71+
worker.addEventListener("message", setStatus);
72+
worker.postMessage({dataUri, rendererName, canvas}, [canvas]);
73+
}
74+
</script>
75+
</body>
2976
</html>
3077

0 commit comments

Comments
 (0)