A Media Source Extensions (MSE) Video Player
Overview
-
This is a very basic approach to using the Media Source API (aka Media Source Extension) to load tracks in a <video> tag instead of using a
srcattribute that points to a single file (or HLS playlist). -
This is primarily a prototype I'm working on for my Multi-Player project. Chrome slows way down as the number of videos on the page increases. Hopefully, this approach can keep things snappy.
-
You should probably use VideoJS instead of this in most cases.
-
This does not currently do an explicit check to see if the mime type is supported in a given browser. I'm using .webm in the examples. Things are lined up already with the videos (I'm using
video/webm; codecs="vp9, vorbis"). See the doc examples for details on how to check in situations where you can't control the input files. -
My original version of this didn't use global variables. I ran into an issue where missing audio was breaking chrome and added the feature to track videos by UUID in the global
videosvariable during the bug hunt. While I like the idea of avoiding global variable I'm fine with them here. -
This version is also prep work for making a version that works with encrypted video files (e.g. by Password Protecting Static Site Content with WASM). You can check out the code for that on An Encrypted Media Source Extensions (MSE) Video Player.
The Video
Here's the output demo video to get started. It's from pexels which is a nice place to get free videos.
The JavaScript
The main JavaScript consists of a variable and two functions in this module:
const videos = ;
-
The
loadVideofunction takes a selector that points to an existingvideoelement and a config. -
The format of the config is in the HTML below.
-
The config includes a fallback in addition to the split up files the MediaSource uses.
-
A check is made to verify the MediaSource available/loaded. If not, the fallback URL is used.
-
I'm using a direct link to a
.webmfile for the fallback. In practice, I'd probably use an HLS playlist. -
The module hangs a canplaythrough event listener on the video element. It's fired when the browser estimates it has enough data to play the video without having to stop for buffering.
-
I left the
console.log('processing segment...'line in to show how thecanplaythroughevent generally fires while the individual segment files are still being loaded.
The HTML
Here's the HTML I'm using on the page:
-
The video tag has an
idattribute that's used in the selector to grab the element. -
In this case, the config is set up for a webm format and supporting codec.
-
The video is split into 11 parts in the
urlsarray. These get processed in order by the module. -
The last thing in the config is the fallback to use if the MediaSource doesn't work or isn't available.
The Prep Script
The sample video I used didn't have an audio track with it. It worked fine in Safari and Firefox. It broke in chrome. It took a while to figure out that the missing audio was the problem.
This is the ffmpeg command I used to fix
the issue. It makes a new .webm file
with a silent audio track.
#!/bin/bash
NOTE: I updated ffmpeg an hour after writing
this post and libvorbis didn't get installed
with the new version. I don't want to compile
ffmpeg myself so I'll see about using
libopus or something else instead
next time I have to mess with stuff.
The Split Script
Spitting the source .webm file into the parts
is done with this script that runs in bash
on mac/linux (I don't have a Windows machine
handy to determine the command on that OS).
#!/bin/bash
for; do ; Line 4 is optional. It adds file extensions. Some servers require one to server the proper mine time. It's not necessary on mine. I just like having explicit extensions.
Error Messages
Here's some of the Chrome console log error messages I got before figuring out that the audio track was the problem. Putting them here in hopes that folks searching for them will see these instead of being sent down dead ends by AI that doesn't take into account that the video could be the problem.
MediaSource readyState is: closed
MediaSource or SourceBuffer invalid
InvalidStateError: Failed to execute
'appendBuffer' on 'SourceBuffer':
This SourceBuffer has been removed
from the parent media source.
InvalidStateError: Failed to execute
'endOfStream' on 'MediaSource':
The MediaSource's readyState is
not 'open'.
--- From ffmpeg during processing
Error parsing Opus packet header.Wrap Up
This is the first time I've really looked at web video in the modern age. As with so much these days, it's mind-blowing what you can do with it without having to bend over backwards. It took me way longer to figure out that the audio track was causing issues than to write up the player script.
Very cool.
-a
References
- /multi-player/
- /rust/wasm/static-site-file-decryption/
- /rust/wasm/static-site-file-encryption/
- https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
- https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API
- https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer
- https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer#examples
- https://en.wikipedia.org/wiki/HTTP_Live_Streaming
- https://ffmpeg.org/ffmpeg-filters.html#anullsrc
- https://ffmpeg.org/ffmpeg-formats.html#hls-1
- https://man7.org/linux/man-pages/man1/split.1.html
- https://videojs.org/
- https://www.rfc-editor.org/rfc/rfc8216
- https://www.webmproject.org/
- https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/video