Skip to content

Instantly share code, notes, and snippets.

@ftence
Created November 5, 2019 15:39
Show Gist options
  • Save ftence/8d2c0cd48bb8278e6b6eb77fb7ac05ea to your computer and use it in GitHub Desktop.
Save ftence/8d2c0cd48bb8278e6b6eb77fb7ac05ea to your computer and use it in GitHub Desktop.
Serve a simple web page with a player
<'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>WarpTV #1: What is Warp10™?</title>
</head>
<body>
<video controls></video>
<script>
var video = document.querySelector('video'); // The HTML-5 video element
var sourceBuffer; // The SourceBuffer used in the MediaSource
var data_url = window.location.href + '/data'; // URL of the data API
var CHUNK_DURATION = 2; // 2-second chunks
var buffers_to_be_appended = []; // Fetched data to be appended, waiting for source buffer to by ready
var last_seeked_chunk = -1; // Last chunk requested by a seek
var fetched_chunks = []; // Already fetched chunk, not to be fetched again
// Codec definition, use mp4info and look for `Codecs String`.
var mimeCodec = 'video/mp4; codecs="avc1.64001F, mp4a.40.2"';
// Check browser-compatibility and initialize the MediaSource and add the event listeners
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
var mediaSource = new MediaSource;
video.src = URL.createObjectURL(mediaSource);
video.addEventListener('timeupdate', timeupdate);
video.addEventListener('seeking', seeking);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
// Called at the MediaSource initialization. Load the init segmenent and the first chunk of video.
function sourceOpen(_) {
var mediaSource = this;
sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
sourceBuffer.addEventListener('updateend', onupdateend);
getData(data_url, function (buf) {
sourceBuffer.appendBuffer(buf);
addChunk(0);
});
// Check if a seek has been done every 500ms. Avoid calling too much times addChunk.
var t = setInterval(checkSeek, 500);
};
function timeupdate(_) {
var next_chunk = Math.round(video.currentTime / CHUNK_DURATION + 1);
addChunk(next_chunk);
}
function seeking() {
last_seeked_chunk = Math.round(video.currentTime / CHUNK_DURATION);
}
function checkSeek() {
if (last_seeked_chunk >= 0) {
addChunk(last_seeked_chunk);
addChunk(last_seeked_chunk - 1); // Also load the previous chunk because it could span after the current time.
last_seeked_chunk = -1;
}
}
// Fetch the requested chunk if needed and add it the SourceBuffer.
function addChunk(chunk) {
if (!fetched_chunks.includes(chunk)) {
fetched_chunks.push(chunk);
var end = (chunk + 1) * CHUNK_DURATION * 1000000 - 1; // Timestamp in us, end excluded
end = Math.round(end);
var start = chunk * CHUNK_DURATION * 1000000; // Timestamp in us, start included
start = Math.round(start);
var query = data_url + '?start=' + start + '&end=' + end;
getData(query, function (buf) {
// Add to the SourceBuffer or the the waiting list if not available for add.
if (sourceBuffer.updating) {
buffers_to_be_appended.push(buf);
} else {
sourceBuffer.appendBuffer(buf);
}
});
}
}
// Load the next chunk on the waiting list if possible.
function onupdateend() {
if (!sourceBuffer.updating && buffers_to_be_appended.length > 0) {
sourceBuffer.appendBuffer(buffers_to_be_appended.shift());
}
}
// Get the data with XMLHttpRequest
function getData(url, cb) {
// console.log(url);
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
cb(xhr.response);
};
xhr.send();
};
</script>
</body>
</html>
'>
'page' STORE
{
'path' '/warpflix'
'prefix' false // Only respond to http(s)://endpoint/warpflix
'macro'
<%
{
'status' 200
'headers'
{ 'Content-Type' 'text/html' }
'body' !$page
}
%>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment