Skip to content

Commit

Permalink
Managed Media Source playback sample adopted from WWDC23 demo (tests/…
Browse files Browse the repository at this point in the history
…ManagedMediaSource/bipbop.html in github.com/jyavenard/htmltests) with AirPlay blocked
  • Loading branch information
vitaly-castLabs committed Sep 20, 2023
1 parent e39991f commit fd64215
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
*.DS_Store
*.Makefile
*.host.mk
*.ncb
*.ninja
*.props
*.pyc
*.rules
*.scons
*.sdf
*.sln
*.suo
*.target.mk
*.targets
*.user
*.vcproj
*.vcxproj
*.vcxproj.filters
*.vpj
*.vpw
*.vpwhistu
*.vs
*.vtg
*.xcodeproj
*.xcworkspace

20 changes: 20 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
<title>Managed MediaSource API Demo</title>
</head>

<body onload="startUp()">

<h3>Appending .mp4 video chunks using the Managed Media Source API</h3>

<section>
</section>

<script src="./mediasource.js"></script>
<script src="./main.js"></script>

</body>
</html>
54 changes: 54 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use strict'

function runWithManagedMSE(testFunction, id = 'log') {
var el = document.createElement('video');
el.disableRemotePlayback = true;
el.controls = true;
document.body.appendChild(el);

var log = document.createElement('pre');
log.setAttribute('id', id);
document.body.appendChild(log);

var logger = new Logger(id);

if (!!!window.ManagedMediaSource) {
info('Managed MediaSource API is not available');
return;
}
var ms = new ManagedMediaSource();

el.src = URL.createObjectURL(ms);
el.preload = 'auto';

testFunction(ms, el);
}

function logEvents(events, target) {
// Log events for debugging.
function logEvent(e) {
var v = e.target;
info('got ' + e.type + ' event');
}
events.forEach(function(e) {
target.addEventListener(e, logEvent);
});
}

function startUp() {
runWithManagedMSE(async (ms, el) => {
// Log events for debugging.
logEvents(['suspend', 'play', 'canplay', 'canplaythrough', 'loadstart', 'loadedmetadata',
'loadeddata', 'playing', 'ended', 'error', 'stalled', 'emptied', 'abort',
'waiting', 'pause', 'durationchange', 'seeking', 'seeked'], el);
logEvents(['sourceopen', 'ended', 'startstreaming', 'endstreaming'], ms);

await once(ms, 'sourceopen');
ok(true, 'Receive a sourceopen event');
var sb = ms.addSourceBuffer('video/mp4; codecs="mp4a.40.2,avc1.4d4015"');
await fetchAndLoad(sb, './media/bipbopinit', [''], '.mp4');
await fetchAndLoad(sb, './media/bipbop', range(1, 13), '.m4s');
ms.endOfStream();
});
}
window.startUp = startUp
Binary file added media/bipbop1.m4s
Binary file not shown.
Binary file added media/bipbop10.m4s
Binary file not shown.
Binary file added media/bipbop11.m4s
Binary file not shown.
Binary file added media/bipbop12.m4s
Binary file not shown.
Binary file added media/bipbop13.m4s
Binary file not shown.
Binary file added media/bipbop2.m4s
Binary file not shown.
Binary file added media/bipbop3.m4s
Binary file not shown.
Binary file added media/bipbop4.m4s
Binary file not shown.
Binary file added media/bipbop5.m4s
Binary file not shown.
Binary file added media/bipbop6.m4s
Binary file not shown.
Binary file added media/bipbop7.m4s
Binary file not shown.
Binary file added media/bipbop8.m4s
Binary file not shown.
Binary file added media/bipbop9.m4s
Binary file not shown.
Binary file added media/bipbopinit.mp4
Binary file not shown.
138 changes: 138 additions & 0 deletions mediasource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Helpers for Media Source Extensions tests
var _logger;

function Logger(id) {
this.el = document.getElementById(id);
_logger = this;
}
var log_start = Date.now();
Logger.prototype.log = function(msg) {
var fragment = document.createDocumentFragment();
fragment.appendChild(document.createTextNode((Date.now() - log_start) + ': ' + msg));
fragment.appendChild(document.createElement('br'));
this.el.appendChild(fragment);
};

Logger.prototype.clear = function() {
this.el.textContent = '';
};

function info(message) {
_logger.log(message);
}

function ok(value, message) {
_logger.log("ok" + (value ? "(true) " : "(false) ") + message);
}

function is(value1, value2, message) {
var result = value1 == value2;
_logger.log("is(" + value1 + " == " + value2 + "): " + result + " (" + message + ")");
}

function isfuzzy(value1, value2, fuzz, message) {
var result = (value1 >= value2 - fuzz && value1 <= value2 + fuzz);
_logger.log("" + value1 + " == " + value2 + " ~" + fuzz + ": " + result + " (" + message + ")");
}

function runWithMSE(testFunction, id = 'log') {
window.onload = function() {
var ms = new MediaSource();

var el = document.createElement("video");
el.src = URL.createObjectURL(ms);
el.preload = "auto";

document.body.appendChild(el);

var log = document.createElement("pre");
log.setAttribute("id", id);
document.body.appendChild(log);

var logger = new Logger(id);

testFunction(ms, el);
};
}

function fetchWithXHR(uri, onLoadFunction) {
var p = new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open("GET", uri, true);
xhr.responseType = "arraybuffer";
xhr.addEventListener("load", function () {
is(xhr.status, 200, "fetchWithXHR load uri='" + uri + "' status=" + xhr.status);
resolve(xhr.response);
});
xhr.send();
});

if (onLoadFunction) {
p.then(onLoadFunction);
}

return p;
};

function range(start, end) {
var rv = [];
for (var i = start; i < end; ++i) {
rv.push(i);
}
return rv;
}

function once(target, name, cb) {
var p = new Promise(function(resolve, reject) {
target.addEventListener(name, function() {
target.removeEventListener(name, arguments.callee);
resolve();
});
});
if (cb) {
p.then(cb);
}
return p;
}

function timeRangeToString(r) {
var str = "TimeRanges: ";
for (var i = 0; i < r.length; i++) {
str += "[" + r.start(i) + ", " + r.end(i) + ")";
}
return str;
}

function loadSegment(sb, typedArrayOrArrayBuffer) {
var typedArray = (typedArrayOrArrayBuffer instanceof ArrayBuffer) ? new Uint8Array(typedArrayOrArrayBuffer)
: typedArrayOrArrayBuffer;
info(`Loading buffer: [${typedArray.byteOffset}, ${typedArray.byteOffset + typedArray.byteLength})`);
var beforeBuffered = timeRangeToString(sb.buffered);
return new Promise(function(resolve, reject) {
once(sb, 'update').then(function() {
var afterBuffered = timeRangeToString(sb.buffered);
info(`SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}`);
resolve();
});
sb.appendBuffer(typedArray);
});
}

function fetchAndLoad(sb, prefix, chunks, suffix) {

// Fetch the buffers in parallel.
var buffers = {};
var fetches = [];
for (var chunk of chunks) {
fetches.push(fetchWithXHR(prefix + chunk + suffix).then(((c, x) => buffers[c] = x).bind(null, chunk)));
}

// Load them in series, as required per spec.
return Promise.all(fetches).then(function() {
var rv = Promise.resolve();
for (var chunk of chunks) {
rv = rv.then(loadSegment.bind(null, sb, buffers[chunk]));
}
return rv;
});
}

0 comments on commit fd64215

Please sign in to comment.