Mattias Buelens
THEO Technologies
Why?
Hello world
<!DOCTYPE html>�<html>� <body>� <baby-video></baby-video>� <script src="./baby-video.js"></script>� </body>�</html>
class BabyVideoElement extends HTMLElement {� #canvas;� #canvasContext;�� constructor() {� super();� const shadowRoot = this.attachShadow({ mode: "open" });� this.#canvas = document.createElement("canvas");� this.#canvas.width = 300;� this.#canvas.height = 150;� shadowRoot.appendChild(this.#canvas);�� this.#canvasContext = this.#canvas.getContext("2d");� this.#canvasContext.fillStyle = "black";� this.#canvasContext.fillRect(� 0, 0, this.#canvas.width, this.#canvas.height);� }�}��customElements.define("baby-video", BabyVideoElement);
Add some buttons
Could build our own UI from scratch with HTML/CSS/JS
…but if our <baby-video> looks like a <video>
…and it quacks like a <video>
…then we can use <media-chrome>(https://github.com/muxinc/media-chrome)
Add some buttons
<!DOCTYPE html>�<html>� <body>�� <media-controller>� <baby-video slot="media"></baby-video>� <media-control-bar>� <media-play-button></media-play-button>� <media-time-display show-duration></media-time-display>� <media-time-range></media-time-range>� <media-fullscreen-button></media-fullscreen-button>� </media-control-bar>� </media-controller>�� <script type="module"� src="https://unpkg.com/media-chrome@0.12.0"></script>� <script src="./baby-video.js"></script>� </body>�</html>
Buffering with Media Source Extensions
const mediaSource = new BabyMediaSource();�video.srcObject = mediaSource;�await waitForEvent(mediaSource, "sourceopen");�mediaSource.duration = 30;�const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.640028"');��const segmentURLs = ["video_init.mp4", "video_1.mp4", "video_2.mp4"];�for (const segmentURL of segmentURLs) {
� const segmentData = await (await fetch(segmentURL)).arrayBuffer();
� sourceBuffer.appendBuffer(segmentData);� await waitForEvent(sourceBuffer, "updateend");
�}
3. Append to SourceBuffer
2. Download fMP4 files
1. Create MediaSource
Implementing MSE
SourceBuffer.appendBuffer(data)
Playback
class BabyVideoElement extends HTMLElement {� #videoDecoder;� constructor() {� this.#videoDecoder = new VideoDecoder({� output: (frame) => this.#onVideoFrameDecoded(frame),� error: (error) => console.error("Uhoh"),� });� }
#onAnimationFrame() {� const videoTrackBuffer = getActiveVideoTrackBuffer(this.#mediaSource);� if (this.#videoDecoder.state === "unconfigured") {� this.#videoDecoder.configure(videoTrackBuffer.codecConfig);� }
const frame = videoTrackBuffer.findFrameForTime(this.currentTime);� if (frame) {� this.#videoDecoder.decode(frame);� }� }� #onVideoFrameDecoded(frame) {� this.#canvasContext.drawImage(frame, 0, 0, frame.displayWidth, frame.displayHeight);� frame.close();� }�}
3. Draw frame
2. Find and decode frame
1. Configure decoder
Playback (attempt #2)
Problem: display frame rate (e.g. 60 Hz) > video frame rate (e.g. 30 fps)
Solution: on every animation frame:
Seeking
…should just work, right? 🤷♂️
Seeking (attempt #2)
Problem:
Solution:
Also handles case where display frame rate < video frame rate
⚠ Fewer keyframes = more frame dependencies = slower seeking
👉 Use keyframe interval of ~2 seconds
Managing buffer size
P
I
P
P
P
P
I
P
P
P
P
I
P
P
P
Buffer before:
P
I
P
P
I
P
P
P
Buffer after:
remove range
Managing buffer size
Quality switching
👉 Player should avoid switching qualities too close to current time
👉 Even better: align segment boundaries across qualities
Buffer before:
#1
#2
#1
480p quality:
720p quality:
#2
#3
#3
#1
#2
#4
Buffer after:
#1
#2
#3
#4
Conclusion
Links