blob: 26d8d0e096104595222c595c26017b88dbf0499c [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2014 The Cobalt Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<html>
<head>
<title>Legacy Media Source and Encrypted Media Conformance Tests (2013)</title>
<link rel="stylesheet" href="style-20150612143746.css" type="text/css"></link>
<script type="text/javascript">
function Alert(msg) {
console.log(msg);
throw msg;
}
// util-20150612143746.js begin
(function() {
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('What is trying to be bound is not a function');
}
var aArgs = Array.prototype.slice.call(arguments, 1);
var fToBind = this;
var fNOP = function() {};
var fBound = function() {
return fToBind.apply(
this instanceof fNOP && oThis ? this : oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var util = {};
util.createElement = function(tag, id, class_, innerHTML) {
var element = document.createElement(tag);
if (id != null)
element.id = id;
if (innerHTML != null)
element.innerHTML = innerHTML;
if (class_ != null)
element.classList.add(class_);
return element;
};
util.getClosestElement = function(refElement) {
if (arguments.length === 1)
return null;
var bestElement = arguments[1];
var bestDistance =
Math.abs((bestElement.offsetLeft + bestElement.offsetWidth / 2) -
(refElement.offsetLeft + refElement.offsetWidth / 2));
for (var i = 2; i < arguments.length; ++i) {
var currElement = arguments[i];
var currDistance =
Math.abs((currElement.offsetLeft + currElement.offsetWidth / 2) -
(refElement.offsetLeft + refElement.offsetWidth / 2));
if (currDistance < bestDistance) {
bestDistance = currDistance;
bestElement = currElement;
}
}
return bestElement;
};
util.fireEvent = function(obj, eventName) {
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent(eventName, true, false);
obj.dispatchEvent(event);
} else if (document.createEventObject) {
obj.fireEvent('on' + eventName);
}
};
util.getElementWidth = function(element) {
var style = window.getComputedStyle(element);
var width = 0;
if (!isNaN(parseInt(style.width))) width += parseInt(style.width);
if (!isNaN(parseInt(style.marginLeft))) width += parseInt(style.marginLeft);
if (!isNaN(parseInt(style.marginRight))) width += parseInt(style.marginRight);
return width;
};
util.isValidArgument = function(arg) {
return typeof(arg) != 'undefined' && arg != null;
};
util.MakeCapitalName = function(name) {
name = name.substr(0, 1).toUpperCase() + name.substr(1);
var offset = 0;
for (;;) {
var space = name.indexOf(' ', offset);
if (space === -1)
break;
name = name.substr(0, space + 1) +
name.substr(space + 1, 1).toUpperCase() + name.substr(space + 2);
offset = space + 1;
}
return name;
};
util.Round = function(value, digits) {
return Math.round(value * Math.pow(10, digits)) / Math.pow(10, digits);
};
util.SizeToText = function(size, unitType) {
var unit = 'B';
if (!!unitType && (unitType == 'B' || unitType == 'b')) {
unit = unitType;
}
if (size >= 1024 * 1024) {
size /= 1024 * 1024;
unit = 'M';
} else if (size >= 1024) {
size /= 1024;
unit = 'K';
}
if ((size - Math.floor(size)) * 10 <
Math.floor(size))
size = Math.floor(size);
else
size = util.Round(size, 3);
return size + unit;
};
util.formatStatus = function(status) {
if (typeof status === 'undefined')
return 'undefined';
else if (typeof status === 'string')
return '"' + status + '"';
else if (typeof status === 'number')
return status.toString();
else if (typeof status === 'boolean')
return status ? 'true' : 'false';
throw 'unknown status type';
};
util.getAttr = function(obj, attr) {
attr = attr.split('.');
if (!obj || attr.length === 0)
return undefined;
while (attr.length) {
if (!obj)
return undefined;
obj = obj[attr.shift()];
}
return obj;
};
util.resize = function(str, newLength, fillValue) {
if (typeof str != 'string')
throw 'Only string is supported';
if (str.length > newLength) {
str.substr(0, newLength);
} else {
while (str.length < newLength)
str += fillValue;
}
return str;
};
window.util = util;
})();
// util-20150612143746.js end
// streamDef-20150612143746.js begin
function getStreamDef(index) {
var d = {};
index = index || 0;
var streamDefinitions = [
{
AudioType: 'audio/mp4; codecs="mp4a.40.2"',
VideoType: 'video/mp4; codecs="avc1.640028"',
AudioTiny: ['media/car-20120827-8b.mp4', 717502, 181.62],
AudioNormal: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
200000: 12.42}],
AudioNormalAdv: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
200000: 12.42}],
AudioHuge: ['media/car-20120827-8d.mp4', 5789853, 181.58, {
'appendAudioOffset': 17.42}],
VideoTiny: ['media/car-20120827-85.mp4', 6015001, 181.44, {
'videoChangeRate': 11.47}],
VideoNormal: ['media/car-20120827-86.mp4', 15593225, 181.44, {
'mediaSourceDuration': Infinity}],
VideoHuge: ['media/car-20120827-89.mp4', 95286345, 181.44],
AudioTinyClearKey: ['media/car_cenc-20120827-8b.mp4', 783470, 181.62],
AudioNormalClearKey: ['media/car_cenc-20120827-8c.mp4', 3013084, 181.58],
AudioHugeClearKey: ['media/car_cenc-20120827-8d.mp4', 5918365, 181.58],
VideoTinyClearKey: ['media/car_cenc-20120827-85.mp4', 6217017, 181.44],
VideoNormalClearKey: ['media/car_cenc-20120827-86.mp4', 15795193, 181.44],
VideoHugeClearKey: ['media/car_cenc-20120827-89.mp4', 95488313, 181.44],
VideoStreamYTCenc: ['media/oops_cenc-20121114-145-no-clear-start.mp4', 39980507, 242.71],
VideoTinyStreamYTCenc: ['media/oops_cenc-20121114-145-143.mp4', 7229257, 30.03],
VideoSmallStreamYTCenc: ['media/oops_cenc-20121114-143-no-clear-start.mp4', 12045546, 242.71],
Audio1MB: ['media/car-audio-1MB-trunc.mp4', 1048576, 65.875],
Video1MB: ['media/test-video-1MB.mp4', 1053406, 1.04],
ProgressiveLow: ['media/car_20130125_18.mp4', 15477531, 181.55],
ProgressiveNormal: ['media/car_20130125_22.mp4', 55163609, 181.55],
ProgressiveHigh: [],
}, {
AudioType: 'audio/mp4; codecs="mp4a.40.2"',
VideoType: 'video/webm; codecs="vp9"',
AudioTiny: ['media/car-20120827-8b.mp4', 717502, 181.62],
AudioNormal: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
200000: 12.42}],
AudioNormalAdv: ['media/car-20120827-8c.mp4', 2884572, 181.58, {
200000: 12.42}],
AudioHuge: ['media/car-20120827-8d.mp4', 5789853, 181.58, {
'appendAudioOffset': 17.42}],
VideoTiny: ['media/feelings_vp9-20130806-242.webm', 4478156, 135.46, {
'videoChangeRate': 15.35}],
VideoNormal: ['media/feelings_vp9-20130806-243.webm', 7902885, 135.46, {
'mediaSourceDuration': 135.469}],
VideoHuge: ['media/feelings_vp9-20130806-247.webm', 27757852, 135.46],
AudioTinyClearKey: ['media/car_cenc-20120827-8b.mp4', 783470, 181.62],
AudioNormalClearKey: ['media/car_cenc-20120827-8c.mp4', 3013084, 181.58],
AudioHugeClearKey: ['media/car_cenc-20120827-8d.mp4', 5918365, 181.58],
VideoTinyClearKey: [],
VideoNormalClearKey: [],
VideoHugeClearKey: [],
VideoStreamYTCenc: [],
VideoTinyStreamYTCenc: [],
VideoSmallStreamYTCenc: [],
Audio1MB: ['media/car-audio-1MB-trunc.mp4', 1048576, 65.875],
Video1MB: ['media/vp9-video-1mb.webm', 1103716, 1.00],
ProgressiveLow: [],
ProgressiveNormal: [],
ProgressiveHigh: [],
}
];
d.AudioType = streamDefinitions[index]['AudioType'];
d.VideoType = streamDefinitions[index]['VideoType'];
var CreateAudioDef = function(src, size, duration, customMap) {
return {name: 'audio', type: d.AudioType, size: size, src: src,
duration: duration, bps: Math.floor(size / duration),
customMap: customMap};
};
var CreateVideoDef = function(src, size, duration, customMap) {
return {name: 'video', type: d.VideoType, size: size, src: src,
duration: duration, bps: Math.floor(size / duration),
customMap: customMap};
};
d.AudioTiny = CreateAudioDef.apply(this, streamDefinitions[index]['AudioTiny']);
d.AudioNormal = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormal']);
d.AudioNormalAdv = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormalAdv']);
d.AudioHuge = CreateAudioDef.apply(this,streamDefinitions[index]['AudioHuge']);
d.VideoTiny = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTiny']);
d.VideoNormal = CreateVideoDef.apply(this, streamDefinitions[index]['VideoNormal']);
d.VideoHuge = CreateVideoDef.apply(this, streamDefinitions[index]['VideoHuge']);
d.AudioTinyClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioTinyClearKey']);
d.AudioNormalClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioNormalClearKey']);
d.AudioHugeClearKey = CreateAudioDef.apply(this, streamDefinitions[index]['AudioHugeClearKey']);
d.VideoTinyClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTinyClearKey']);
d.VideoNormalClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoNormalClearKey']);
d.VideoHugeClearKey = CreateVideoDef.apply(this, streamDefinitions[index]['VideoHugeClearKey']);
d.VideoStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoStreamYTCenc']);
d.VideoTinyStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoTinyStreamYTCenc']);
d.VideoSmallStreamYTCenc = CreateVideoDef.apply(this, streamDefinitions[index]['VideoSmallStreamYTCenc']);
d.Audio1MB = CreateAudioDef.apply(this, streamDefinitions[index]['Audio1MB']);
d.Video1MB = CreateVideoDef.apply(this, streamDefinitions[index]['Video1MB']);
d.ProgressiveLow = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveLow']);
d.ProgressiveNormal = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveNormal']);
d.ProgressiveHigh = CreateVideoDef.apply(this, streamDefinitions[index]['ProgressiveHigh']);
d.isWebM = function() {
return index === 1;
}
return d;
}
var StreamDef = getStreamDef();
function UpdateStreamDef(index) {
StreamDef = getStreamDef(index);
}
// streamDef-20150612143746.js end
// focusManager-20150612143746.js begin
(function() {
var INFINITY = 100000;
var CLOSE = 50;
var MAX_FUDGE = INFINITY;
var DIRECTION_WEIGHT = 0.5;
var LEFT = new Pair(-1, 0);
var UP = new Pair(0, -1);
var RIGHT = new Pair(1, 0);
var DOWN = new Pair(0, 1);
function Pair(x, y) {
this.x = x;
this.y = y;
this.add = function(operand) {
return new Pair(this.x + operand.x, this.y + operand.y);
};
this.sub = function(operand) {
return new Pair(this.x - operand.x, this.y - operand.y);
};
this.dot = function(operand) {
return this.x * operand.x + this.y * operand.y;
};
this.dotRelative = function(ref, operand) {
return this.x * (operand.x - ref.x) + this.y * (operand.y - ref.y);
};
this.distTo = function(operand) {
return Math.sqrt(this.x * operand.x + this.y * operand.y);
};
this.distTo2 = function(operand) {
return this.x * operand.x + this.y * operand.y;
};
this.cross = function(operand) {
return this.x * operand.y - this.y * operand.x;
};
}
function Rect(left, top, width, height) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
this.right = this.left + this.width;
this.bottom = this.top + this.height;
var rangeDist = function(start, end, startRef, endRef) {
if (start < startRef) {
if (end < startRef)
return startRef - end;
return 0;
}
if (start <= endRef)
return 0;
return start - endRef;
};
this.valid = function() {
return this.width !== 0 && this.height !== 0;
};
this.inside = function(x, y) {
// Technically speaking, this is not correct. However, this works out for
// our usage.
return x >= this.left && x < this.left + this.width &&
y >= this.top && y < this.top + this.height;
};
this.intersect = function(that) {
return this.inside(that.left, that.top) ||
this.inside(that.right, that.top) ||
this.inside(that.left, that.bottom) ||
this.inside(that.right, that.bottom) ||
that.inside(this.left, this.top) ||
that.inside(this.right, this.top) ||
that.inside(this.left, this.bottom) ||
that.inside(this.right, this.bottom);
};
this.intersectComplete = function(that) {
var centerXThat = (that.left + that.right) * 0.5;
var centerYThat = (that.top + that.bottom) * 0.5;
var halfThatWidth = that.width * 0.5;
var halfThatHeight = that.height * 0.5;
var expandedRect = new Rect(
this.left - halfThatWidth, this.top - halfThatHeight,
this.width + that.width, this.height + that.height);
return expandedRect.inside(centerXThat, centerYThat);
};
this.distanceSquared = function(ref, dir) {
var x, y;
if (dir.x === -1) {
x = Math.max((ref.left - this.right) * DIRECTION_WEIGHT, 0);
y = rangeDist(this.top, this.bottom, ref.top, ref.bottom);
} else if (dir.x === 1) {
x = Math.max((this.left - ref.right) * DIRECTION_WEIGHT, 0);
y = rangeDist(this.top, this.bottom, ref.top, ref.bottom);
} else if (dir.y === -1) {
x = rangeDist(this.left, this.right, ref.left, ref.right);
y = Math.max((ref.top - this.bottom) * DIRECTION_WEIGHT, 0);
} else {
x = rangeDist(this.left, this.right, ref.left, ref.right);
y = Math.max((this.top - ref.bottom) * DIRECTION_WEIGHT, 0);
}
return x * x + y * y;
};
this.generateSideSliver = function(dir) {
var left, right, top, bottom;
if (dir === LEFT) {
left = this.left - CLOSE;
right = this.left - 1;
top = (this.top + this.bottom) * 0.5;
bottom = top;
} else if (dir === RIGHT) {
left = this.right + 1;
right = this.right + CLOSE;
top = (this.top + this.bottom) * 0.5;
bottom = top;
} else if (dir === UP) {
left = (this.left + this.right) * 0.5;
right = (this.left + this.right) * 0.5;
top = this.top - CLOSE;
bottom = this.top - 1;
} else {
left = (this.left + this.right) * 0.5;
right = (this.left + this.right) * 0.5;
top = this.bottom + 1;
bottom = this.bottom + CLOSE;
}
return new Rect(left, top, right - left, bottom - top);
};
// Generates a rectangle to check if there are any other rectangles strictly
// to one side (defined by dir) of 'this'.
this.generateSideRect = function(dir, fudge) {
if (!fudge) {
return this.generateSideSliver(dir);
}
var left, right, top, bottom;
if (dir === LEFT) {
left = -INFINITY;
right = this.left - 1;
top = this.top - fudge;
bottom = this.bottom + fudge;
} else if (dir === RIGHT) {
left = this.right + 1;
right = INFINITY;
top = this.top - fudge;
bottom = this.bottom + fudge;
} else if (dir === UP) {
left = this.left - fudge;
right = this.right + fudge;
top = -INFINITY;
bottom = this.top - 1;
} else {
left = this.left - fudge;
right = this.right + fudge;
top = this.bottom + 1;
bottom = INFINITY;
}
return new Rect(left, top, right - left, bottom - top);
};
this.toSideOf = function(target, dir) {
var testX = [0, this.right - this.center.x, this.left - this.center.x];
var testY = [0, this.bottom - this.center.y, this.top - this.center.y];
var testLineSegRel0 = new Pair(
testX[dir.x - (dir.x != 0)], testY[dir.y - (dir.y != 0)]);
var testLineSegRel1 = new Pair(
testY[dir.x + (dir.x != 0)], testY[dir.y + (dir.y != 0)]);
return dir.cross(testLineSegRel0) * dir.cross(testLineSegRel1) <= 0 &&
this.intersect(target);
};
this.toString = function() {
return '(' + this.left + ', ' + this.top + ', ' + this.right + ', ' +
this.bottom + ')';
};
};
function createRect(element) {
var offsetLeft = element.offsetLeft;
var offsetTop = element.offsetTop;
var e = element.offsetParent;
while (e && e !== document.body) {
offsetLeft += e.offsetLeft;
offsetTop += e.offsetTop;
e = e.offsetParent;
}
return new Rect(offsetLeft, offsetTop,
element.offsetWidth, element.offsetHeight);
};
function FocusManager() {
var elements = [];
var handlers = [];
var pickElement_ = function(currElem, dir, fudge) {
var rect = createRect(currElem);
var rectSide = rect.generateSideRect(dir, fudge);
var bestDistanceSquared = INFINITY * INFINITY;
var bestElement = null;
for (var i = 0; i < elements.length; ++i) {
if (elements[i] !== currElem) {
var r = createRect(elements[i]);
if (r.valid() && r.intersectComplete(rectSide)) {
var distanceSquared = r.distanceSquared(rect, dir);
if (!bestElement || distanceSquared < bestDistanceSquared) {
bestElement = elements[i];
bestDistanceSquared = distanceSquared;
}
}
}
}
return bestElement;
};
var pickElement = function(currElem, dir) {
return pickElement_(currElem, dir) ||
pickElement_(currElem, dir, 2) ||
pickElement_(currElem, dir, MAX_FUDGE);
};
var onkeydown = function(e) {
if (elements.indexOf(e.target) !== -1) {
var dir;
if (e.keyCode === 37) { // left
dir = LEFT;
} else if (e.keyCode === 38) { // up
dir = UP;
} else if (e.keyCode === 39) { // right
dir = RIGHT;
} else if (e.keyCode === 40) { // down
dir = DOWN;
} else {
return true;
}
var element = pickElement(e.target, dir);
if (element) {
element.focus();
e.stopPropagation();
e.preventDefault();
}
}
return true;
};
this.add = function(element) {
if (elements.indexOf(element) === -1) {
elements.push(element);
handlers.push(element.addEventListener('keydown', onkeydown));
}
};
};
window.addEventListener('load', function() {
var focusManager = new FocusManager;
var elements = document.getElementsByClassName('focusable');
for (var i = 0; i < elements.length; ++i)
focusManager.add(elements[i]);
/*var links = document.getElementsByTagName('A');
for (var i = 0; i < links.length; ++i)
focusManager.add(links[i]);*/
});
})();
// focusManager-20150612143746.js end
// logger-20150612143746.js begin
(function() {
var Logger = function(log) {
this.throwError = true;
this.log = log;
this.assert = function(cond, msg) {
if (!cond) {
this.log('Assert failed: ' + msg);
try {
var x = y.z.u.v.w;
} catch (e) {
this.log(e.stack);
}
if (this.throwError) throw 'Assert: ' + msg;
}
};
this.check = function(condition, passMsg, failMsg) {
if (condition)
this.log(passMsg);
else
this.assert(false, failMsg);
};
this.checkEq = function(x, y, name) {
var result = (x == y) ||
(typeof(x) === 'number' && typeof(y) === 'number' &&
isNaN(x) && isNaN(y));
this.check(result, 'checkEq passed: ' + name + ' is (' + x + ').',
name + ' is (' + x + ') which should be (' + y + ')');
};
this.checkNE = function(x, y, name) {
var result = (x != y) &&
!((typeof(x) === 'number' && typeof(y) === 'number' &&
isNaN(x) && isNaN(y)));
this.check(result, 'checkNE passed: ' + name + ' is (' + x + ').',
name + ' is (' + x + ') which shouldn\'t.');
};
};
window.createLogger = function(log) {
return new Logger(log || console.log.bind(console));
};
})();
// logger-20150612143746.js end
// js/harness/xhr-20150612143746.js begin
(function() {
var BYPASS_CACHE = false;
// Hook the onload event for request that is finished successfully
var Request = function(manager, logger, file, onload, postLength,
start, length) {
var self = this;
this.open = function() {
this.xhr = new XMLHttpRequest();
this.onload = onload;
this.type = util.isValidArgument(postLength) ? 'POST' : 'GET';
this.xhr.open(this.type,
file + (BYPASS_CACHE ? '?' + (new Date()).getTime() : ''));
this.xhr.responseType = 'arraybuffer';
this.startTime = new Date().getTime();
this.lastUpdate = this.startTime;
if (start != null && length != null)
this.xhr.setRequestHeader(
'Range', 'bytes=' + start + '-' + (start + length - 1));
this.xhr.addEventListener('error', function(e) {
if (self.xhr.status === 404)
Alert('Failed to find "' + file +
'" with error 404. Is it on the server?');
manager.requestFinished(self);
logger.log('XHR error with code', self.xhr.status);
self.open();
self.send();
});
this.xhr.addEventListener('timeout', function(e) {
manager.requestFinished(self);
logger.log('XHR timeout');
self.open();
self.send();
});
this.xhr.addEventListener('load', function(e) {
manager.requestFinished(self);
return self.onload(e);
});
this.xhr.addEventListener('progress', function onProgress(e) {
if (e.lengthComputable && (e.loaded === e.total)) {
self.xhr.removeEventListener('progress', onProgress);
}
self.lastUpdate = new Date().getTime();
});
};
this.getRawResponse = function() {
if (this.xhr.status === 404)
Alert('Failed to find "' + file +
'" with error 404. Is it on the server?');
logger.assert(this.xhr.status >= 200 && this.xhr.status < 300,
'XHR bad status: ' + this.xhr.status);
return this.xhr.response;
};
this.getResponseData = function() {
if (this.xhr.status === 404)
Alert('Failed to find "' + file +
'" with error 404. Is it on the server?');
logger.assert(this.xhr.status >= 200 && this.xhr.status < 300,
'XHR bad status: ' + this.xhr.status);
var result = new Uint8Array(this.xhr.response);
if (length != null) {
var rangeHeader = this.xhr.getResponseHeader('Content-Range');
var lengthHeader = this.xhr.getResponseHeader('Content-Length');
if (!rangeHeader && lengthHeader) {
logger.assert(length <= lengthHeader,
'Length of response is smaller than request');
result = result.subarray(start, start + length);
logger.checkEq(result.length, length, 'XHR length');
return result;
}
logger.checkEq(result.length, length, 'XHR length');
}
return result;
};
this.send = function(postData) {
manager.addRequest(this);
if (postData) {
logger.checkEq(this.type, 'POST', 'XHR requestType');
this.xhr.send(postData);
} else {
logger.checkEq(this.type, 'GET', 'XHR requestType');
this.xhr.send();
}
};
this.abort = function() {
this.xhr.abort();
};
this.open();
};
var XHRManager = function(logger) {
var requests = [];
this.totalRequestDuration = 0;
this.addRequest = function(request) {
logger.checkEq(requests.indexOf(request), -1, 'request index');
requests.push(request);
};
this.requestFinished = function(request) {
var currentTime = new Date().getTime();
this.totalRequestDuration += currentTime - request.startTime;
logger.checkNE(requests.indexOf(request), -1, 'request index');
requests.splice(requests.indexOf(request), 1);
};
this.abortAll = function() {
for (var i = 0; i < requests.length; ++i)
requests[i].abort();
requests = [];
};
this.createRequest = function(file, onload, start, length) {
return new Request(this, logger, file, onload, null, start, length);
};
this.createPostRequest = function(file, onload, postLength, start, length) {
return new Request(this, logger, file, onload, postLength, start, length);
};
this.hasActiveRequests = function() {
if (requests.length > 0) {
return true;
}
return false;
}
this.getLastUpdate = function() {
if (requests.length == 0) {
return null;
}
var latestUpdate = 0;
for (var i in requests) {
latestUpdate = Math.max(requests[i].lastUpdate, latestUpdate);
}
return latestUpdate;
};
};
window.createXHRManager = function(logger) {
return new XHRManager(logger);
};
})();
// js/harness/xhr-20150612143746.js end
// js/harness/timeout-20150612143746.js begin
(function() {
var TimeoutManager = function(logger) {
var timers = [];
var intervals = [];
var getUniqueItem = function(container) {
var id = 0;
while (typeof(container[id]) != 'undefined')
++id;
container[id] = {id: id};
return container[id];
};
var timeoutHandler = function(id) {
if (timers[id]) {
var func = timers[id].func;
delete timers[id];
func();
}
};
var intervalHandler = function(id) {
var func = intervals[id].func;
func();
};
this.setTimeout = function(func, timeout) {
var timer = getUniqueItem(timers);
timer.func = func;
var id = window.setTimeout(timeoutHandler.bind(this, timer.id), timeout);
};
this.setInterval = function(func, timeout) {
var interval = getUniqueItem(intervals);
interval.func = func;
interval.id = window.setInterval(intervalHandler, timeout, interval.id);
};
this.clearAll = function() {
for (var id = 0; id < timers.length; ++id)
if (typeof(timers[id]) != 'undefined')
window.clearTimeout(timers[id].id);
timers = [];
for (var id = 0; id < intervals.length; ++id)
if (typeof(intervals[id]) != 'undefined')
window.clearInterval(intervals[id].id);
intervals = [];
};
};
window.createTimeoutManager = function(logger) {
return new TimeoutManager(logger);
};
})();
// js/harness/timeout-20150612143746.js end
// js/harness/testView-20150612143746.js begin
var TestView = (function() {
var createElement = util.createElement;
var createAnchor = function(text, id) {
return util.createElement('span', id, 'rightmargin20', text);
};
var createOption = function(text, value) {
var option = document.createElement('option');
option.text = text;
option.value = value;
return option;
};
function TestView(mseSpec) {
this.mseSpec = mseSpec;
this.testList = null;
var selectors = [];
var switches = [];
var commands = [];
var testSuites = [];
var links = [];
this.addSelector = function(text, optionTexts, values, callback) {
optionTexts = optionTexts instanceof Array ? optionTexts : [optionTexts];
values = values instanceof Array ? values : [values];
if (optionTexts.length !== values.length)
throw "text length and value length don't match!";
selectors.push({
'text': text,
'optionTexts': optionTexts,
'values': values,
'cb': callback
})
};
this.addSwitch = function(text, id) {
switches.push({text: text, id: id});
};
this.addCommand = function(text, id, title, onclick) {
commands.push({text: text, id: id, title: title, onclick: onclick});
};
this.addTestSuite = function(text, href) {
testSuites.push({text: text, href: href});
};
this.addTestSuites = function(testTypes) {
var isTestTypeSupported = function(testType) {
var supported = testTypes[testType].supported;
if (typeof supported === 'string' && supported == 'all') {
return true;
} else if (typeof supported === 'object') {
for (var index in supported) {
if (supported[index] == mseSpec) {
return true;
}
}
}
return false;
};
for (var testType in testTypes) {
if (testType !== currentTestType && isTestTypeSupported(testType)) {
this.addTestSuite(testTypes[testType].name, '?test_type=' + testType);
}
}
}
this.addLink = function(text, href) {
links.push({text: text, href: href});
};
this.generate = function() {
var heading = '[' + this.mseSpec + '] ' +
testTypes[currentTestType].heading + ' (v 20150612143746-4K5xqupUzgiRyTYP)';
document.title = testTypes[currentTestType].title;
document.body.appendChild(createElement('div', 'title', null, heading));
document.body.appendChild(createElement('div', 'info'));
document.body.appendChild(createElement('div', 'usage'));
document.body.appendChild(createElement('div', 'testview'));
var div = document.getElementById(this.divId);
div.innerHTML = '';
div.appendChild(createElement('div', 'testsuites', 'container'));
div.appendChild(createElement('div', 'switches', 'container'));
div.appendChild(createElement('div', 'controls', 'container'));
var testContainer = createElement('div', null, 'container');
testContainer.appendChild(createElement('div', 'testlist', 'box-left'));
testContainer.appendChild(createElement('div', 'testarea'));
div.appendChild(testContainer);
var outputArea = createElement('div', 'outputarea');
var textArea = createElement('div', 'output');
textArea.rows = 10;
textArea.cols = 80;
outputArea.appendChild(textArea);
div.appendChild(outputArea);
var switchDiv = document.getElementById('switches');
for (var i = 0; i < switches.length; ++i) {
var id = switches[i].id;
switchDiv.appendChild(document.createTextNode(switches[i].text));
switchDiv.appendChild(createAnchor(window[id] ? 'on' : 'off', id));
switchDiv.lastChild.href = 'javascript:;';
switchDiv.lastChild.onclick = (function(id) {
return function(e) {
var wasOff = e.target.innerHTML === 'off';
e.target.innerHTML = wasOff ? 'on' : 'off';
window[id] = wasOff;
};
})(id);
}
for (var i = 0; i < selectors.length; ++i) {
switchDiv.appendChild(document.createTextNode(selectors[i].text));
var select = document.createElement('select');
for (var j = 0; j < selectors[i].optionTexts.length; ++j) {
select.appendChild(createOption(selectors[i].optionTexts[j],
selectors[i].values[j]));
}
select.onchange = selectors[i].cb;
switchDiv.appendChild(select);
}
switchDiv.appendChild(
createElement('span', 'finish-count', null, '0 tests finished'));
var controlsDiv = document.getElementById('controls');
for (var i = 0; i < commands.length; ++i) {
controlsDiv.appendChild(createAnchor(commands[i].text, commands[i].id));
controlsDiv.lastChild.href = 'javascript:;';
controlsDiv.lastChild.onclick = commands[i].onclick;
controlsDiv.lastChild.title = commands[i].title;
}
for (var i = 0; i < links.length; ++i) {
controlsDiv.appendChild(createAnchor(links[i].text));
controlsDiv.lastChild.href = links[i].href;
}
var testSuitesDiv = document.getElementById('testsuites');
for (var i = 0; i < testSuites.length; ++i) {
testSuitesDiv.appendChild(createAnchor(testSuites[i].text));
testSuitesDiv.lastChild.href = testSuites[i].href;
}
this.testList.generate(document.getElementById('testlist'));
};
this.addTest = function(desc) {
return this.testList.addTest(desc);
};
this.anySelected = function() {
return this.testList.anySelected();
};
};
return {
create: function(mseSpec) {
return new TestView(mseSpec);
}};
})();
// js/harness/testView-20150612143746.js end
// fullTestList-20150612143746.js begin
(function() {
var createElement = util.createElement;
function Test(desc, fields) {
var INDEX = 0;
var STATUS = INDEX + 1;
var DESC = STATUS + 1;
var FIELD = DESC + 1;
this.index = desc.index;
this.id = 'test-row' + this.index;
this.desc = desc;
this.steps = [];
this.createElement = function(element) {
element.id = this.id;
element.appendChild(createElement('td', null, 'index',
this.index + 1 + '.'));
element.appendChild(
createElement('td', null, 'status',
'<input type="checkbox" checked="yes"></input> >'));
element.appendChild(createElement('td', null, 'desc'));
for (var field = 0; field < fields.length; ++field)
element.appendChild(createElement('td', null, 'state', 0));
var link = createElement('span', null, null, desc.desc);
link.href = 'javascript:;';
link.onclick = desc.onclick;
link.title = desc.title;
element.childNodes[DESC].appendChild(link);
var explanationPoint = createElement('span', null, 'desc-expl-point', '?');
var explanation = createElement(
'span', null, 'desc-expl-popup', desc.title);
explanationPoint.appendChild(explanation);
element.childNodes[DESC].appendChild(explanationPoint);
};
this.addStep = function(name) {
var tr = createElement('tr');
tr.appendChild(createElement('td', null, 'small'));
tr.appendChild(createElement('td', null, 'small'));
tr.appendChild(createElement('td', null, 'small',
this.steps.length + 1 + '. ' + name));
for (var field = 0; field < fields.length; ++field)
tr.appendChild(createElement('td', null, 'small', 0));
var element = document.getElementById(this.id);
if (this.steps.length !== 0)
element = this.steps[this.steps.length - 1];
if (element.nextSibling)
element.parentNode.insertBefore(tr, element.nextSibling);
else
element.parentNode.appendChild(tr);
this.steps.push(tr);
};
this.updateStatus = function() {
var element = document.getElementById(this.id);
element.childNodes[STATUS].className =
this.desc.running ? 'status_current' : 'status';
for (var field = 0; field < fields.length; ++field)
element.childNodes[FIELD + field].innerHTML =
this.desc[fields[field].replace(' ', '_')];
};
this.selected = function() {
var element = document.getElementById(this.id);
return element.childNodes[STATUS].childNodes[0].checked;
};
this.select = function() {
var element = document.getElementById(this.id);
element.childNodes[STATUS].childNodes[0].checked = true;
};
this.deselect = function() {
var element = document.getElementById(this.id);
element.childNodes[STATUS].childNodes[0].checked = false;
};
}
function TestList(fields) {
var tableId = 'test-list-table';
var headId = tableId + '-head';
var bodyId = tableId + '-body';
var tests = [];
if (!fields || !fields.length)
throw 'No test fields';
this.addColumnHeader = function(class_, text) {
var head = document.getElementById(headId);
var th = createElement('th', null, class_, text);
th.scope = 'col';
head.appendChild(th);
};
this.addTest = function(desc) {
var test = new Test(desc, fields);
tests.push(test);
return test;
};
this.generate = function(div) {
var table = document.createElement('table');
table.id = tableId;
div.appendChild(table);
var thead = createElement('thead');
table.classList.add('test-table');
table.innerHTML = '';
var head = createElement('tr');
var body = createElement('tbody');
head.id = headId;
body.id = bodyId;
thead.appendChild(head);
table.appendChild(thead);
table.appendChild(body);
this.addColumnHeader('index');
this.addColumnHeader('status');
this.addColumnHeader('desc', 'Test');
for (var i = 0; i < fields.length; ++i)
this.addColumnHeader('state', util.MakeCapitalName(fields[i]));
for (var i = 0; i < tests.length; ++i) {
var tr = createElement('tr');
body.appendChild(tr);
tests[i].createElement(tr);
tests[i].updateStatus();
}
};
this.getTest = function(index) {
return tests[index];
};
this.anySelected = function() {
for (var i = 0; i < tests.length; ++i)
if (tests[i].selected())
return true;
return false;
};
this.selectAll = function() {
for (var i = 0; i < tests.length; ++i)
tests[i].select();
};
this.deselectAll = function() {
for (var i = 0; i < tests.length; ++i)
tests[i].deselect();
};
};
window.createFullTestList = function(fields) {
return new TestList(fields);
};
})();
// fullTestList-20150612143746.js end
// js/harness/fullTestView-20150612143746.js begin
var fullTestView = (function() {
function FullTestView(fields) {
var self = this;
this.divId = 'testview';
this.testCount = 0;
this.initialize = function() {
this.testList = createFullTestList(fields);
this.addSwitch('Loop: ', 'loop');
this.addSwitch('Stop on failure: ', 'stoponfailure');
this.addSwitch('Log: ', 'logging');
this.addSwitch('WebM/VP9 (2015/tip only): ', 'enablewebm');
this.addCommand('Select All', 'select-all', 'Select all tests.',
this.testList.selectAll.bind(this.testList));
this.addCommand('Deselect All', 'deselect-all', 'Deselect all tests.',
this.testList.deselectAll.bind(this.testList));
this.addCommand('Run Selected', 'run-selected',
'Run all selected tests in order.',
function(e) {
if (self.onrunselected)
self.onrunselected.call(self, e);
});
this.addLink('Links', 'links.html');
this.addLink('Instructions', 'instructions.html');
this.addLink('Changelog', 'main.html');
this.addLink('Download', 'download-20150612143746.tar.gz');
this.addTestSuites(testTypes);
};
this.addTest = function(desc) {
return this.testList.addTest(desc);
};
this.generate = function() {
FullTestView.prototype.generate.call(this);
// document.getElementById('run-selected').focus();
};
this.getTest = function(index) {
return this.testList.getTest(index);
};
this.finishedOneTest = function() {
++this.testCount;
document.getElementById('finish-count').innerHTML =
this.testCount === 1 ? this.testCount + ' test finished' :
this.testCount + ' tests finished';
};
this.anySelected = function() {
return this.testList.anySelected();
};
this.initialize();
};
//FullTestView.prototype = TestView.create();
//FullTestView.prototype.constructor = FullTestView;
return {
create: function(mseSpec, fields) {
FullTestView.prototype = TestView.create(mseSpec);
FullTestView.prototype.constructor = FullTestView;
return new FullTestView(fields);
}
};
})();
// js/harness/fullTestView-20150612143746.js end
// js/harness/compactTestList-20150612143746.js begin
(function() {
var ITEM_IN_COLUMN = 25; // Test item count in a column
var CATEGORY_SPACE = 1; // Row between the end of the last category and the
// beginning of the next category
var MIN_ROW_AT_THE_BOTTOM = 2; // If at the bottom of the table and the row
// count is less than this, start a new column.
var createElement = util.createElement;
function Category(categoryName) {
this.setElement = function(nameCell, statusCell) {
nameCell.className = 'cell-category';
nameCell.innerText = categoryName;
};
this.setDoubleElement = function(cellElem) {
cellElem.className = 'cell-category';
cellElem.setAttribute('colspan', 2);
cellElem.innerText = categoryName;
};
}
function Test(desc, style) {
var self = this;
this.index = desc.index;
this.nameId = 'test-item-name-' + this.index;
this.statusId = 'test-item-status-' + this.index;
this.desc = desc;
this.steps = [];
this.style = style;
this.createElement = function(name, status) {
name.id = this.nameId;
status.id = this.statusId;
var link = createElement('span', null, null,
this.index + 1 + '. ' + this.desc.desc);
link.href = 'javascript:;';
link.onclick = desc.onclick;
link.title = desc.title;
name.appendChild(link);
this.updateStatus(status);
};
this.updateStatus = function(status) {
var text = this.desc.status;
var failureStatus = '';
if (text && text.length > 5) text = '';
status = status ? status : document.getElementById(this.statusId);
if (this.style === 'extra compact') {
failureStatus = this.desc.mandatory ? 'test-status-fail' :
'test-status-optional-fail';
if (this.desc.running) {
status.className = 'test-status-running';
} else if (this.desc.failures) {
status.className = failureStatus;
} else if (this.desc.timeouts) {
status.className = failureStatus;
} else if (this.desc.passes) {
status.className = 'test-status-pass';
} else {
status.className = 'test-status-none';
}
} else {
failureStatus = this.desc.mandatory ? 'cell-status-fail' :
'cell-status-normal';
if (this.desc.running) {
status.innerHTML = '&nbsp;...&nbsp;';
status.className = 'cell-status-running';
} else if (this.desc.failures) {
status.innerHTML = text || '&nbsp;Fail&nbsp;';
status.className = failureStatus;
} else if (this.desc.timeouts) {
status.innerHTML = text || '&nbsp;Fail&nbsp;';
status.className = failureStatus;
} else if (this.desc.passes) {
status.innerHTML = text || '&nbsp;Pass&nbsp;';
status.className = 'cell-status-pass';
} else {
status.innerHTML = ' ';
status.className = 'cell-status-normal';
}
}
};
this.selected = function() {
return true;
};
this.getElement = function() {
return document.getElementById(this.nameId).childNodes[0];
};
}
function TestList(style) {
var self = this;
var tests = [];
var SINGLE_WIDTH_CELL = 1;
var DOUBLE_WIDTH_CELL = 2;
this.style = style || '';
// returns array [row, column]
var getTableDimension = function() {
var lastCategory = '';
var cells = 0;
var rowLeft;
for (var i = 0; i < tests.length; ++i) {
if (lastCategory !== tests[i].desc.category) {
rowLeft = ITEM_IN_COLUMN - cells % ITEM_IN_COLUMN;
if (rowLeft < MIN_ROW_AT_THE_BOTTOM)
cells += rowLeft;
if (cells % ITEM_IN_COLUMN !== 0)
cells += CATEGORY_SPACE;
cells++;
lastCategory = tests[i].desc.category;
} else if (cells % ITEM_IN_COLUMN === 0) {
cells++; // category (continued)
}
cells++;
}
return [Math.min(cells, ITEM_IN_COLUMN),
Math.floor((cells + ITEM_IN_COLUMN - 1) / ITEM_IN_COLUMN)];
};
var createExtraCompactTable = function(div, table) {
var lastCategory = null;
var totalCells = 0;
var totalTests = 0;
var rowsRemaining = 0;
var layoutColumnSpan = [];
var rows = [];
var j = 0;
var createEmptyCells = function(row) {
if (table.childNodes.length <= row) {
table.appendChild(createElement('tr'));
}
var tr = table.childNodes[row];
var elems = [
createElement('td', null, 'test-status-none'),
createElement('td', null, 'cell-name', '&nbsp;')
];
tr.appendChild(elems[0]);
tr.appendChild(elems[1]);
return elems;
};
var createTestCells = function(testIndex, row, test) {
var cells = createEmptyCells(row);
tests[testIndex].createElement(cells[1], cells[0]);
};
var createCategoryCell = function(row, categoryName) {
if (table.childNodes.length <= row) {
table.appendChild(createElement('tr'));
}
var tr = table.childNodes[row];
var elem = createElement('td', null, 'cell-name', '&nbsp;');
tr.appendChild(elem);
(new Category(categoryName)).setDoubleElement(elem);
};
for (var i = 0; i < tests.length; ++i) {
var currCategory = tests[i].desc.category;
if (lastCategory !== currCategory) {
rowsRemaining = ITEM_IN_COLUMN - totalCells % ITEM_IN_COLUMN;
if (rowsRemaining < MIN_ROW_AT_THE_BOTTOM) {
// Add a row for heading.
for (j = 0; j < rowsRemaining; ++j) {
createEmptyCells(totalCells % ITEM_IN_COLUMN);
totalCells += 1;
}
}
if (totalCells % ITEM_IN_COLUMN !== 0) {
// Add a row for extra space before heading, if in middle of column.
for (j = 0; j < CATEGORY_SPACE; ++j) {
createEmptyCells(totalCells % ITEM_IN_COLUMN);
totalCells += 1;
}
}
lastCategory = currCategory;
createCategoryCell(totalCells % ITEM_IN_COLUMN, lastCategory);
totalCells++;
} else if (totalCells % ITEM_IN_COLUMN === 0) {
// category (continued)
createCategoryCell(totalCells % ITEM_IN_COLUMN, lastCategory);
totalCells++;
}
createTestCells(totalTests, totalCells % ITEM_IN_COLUMN, lastCategory);
totalCells++;
totalTests++;
}
div.innerHTML = '';
div.appendChild(table);
};
this.addTest = function(desc) {
var test = new Test(desc, this.style);
tests.push(test);
return test;
};
this.generate = function(div) {
var table = createElement('div', null, 'compact-list');
var tr;
var dim = getTableDimension();
var lastCategory = '';
var row;
var column;
if (self.style === 'extra compact') {
createExtraCompactTable(div, table);
} else {
for (row = 0; row < dim[0]; ++row) {
tr = createElement('div');
table.appendChild(tr);
for (column = 0; column < dim[1]; ++column) {
tr.appendChild(createElement('span', null, 'cell-name', '&nbsp;'));
tr.appendChild(createElement('span', null, 'cell-divider'));
tr.appendChild(createElement('span', null, 'cell-status-normal'));
}
}
div.innerHTML = '';
div.appendChild(table);
row = column = 0;
for (var i = 0; i < tests.length; ++i) {
if (lastCategory !== tests[i].desc.category) {
if (ITEM_IN_COLUMN - row <= MIN_ROW_AT_THE_BOTTOM) {
row = 0;
column++;
}
if (row % ITEM_IN_COLUMN !== 0)
row += CATEGORY_SPACE;
lastCategory = tests[i].desc.category;
(new Category(lastCategory)).setElement(
table.childNodes[row].childNodes[column * 3],
table.childNodes[row].childNodes[column * 3 + 2]);
row++;
} else if (row === 0) {
(new Category(lastCategory)).setElement(
table.childNodes[row].childNodes[column * 3],
table.childNodes[row].childNodes[column * 3 + 2]);
row++;
}
tests[i].createElement(
table.childNodes[row].childNodes[column * 3],
table.childNodes[row].childNodes[column * 3 + 2]);
row++;
if (row === ITEM_IN_COLUMN) {
row = 0;
column++;
}
}
}
};
this.getTest = function(index) {
return tests[index];
};
this.anySelected = function() {
return tests.length !== 0;
};
};
window.createCompactTestList = function(style) {
return new TestList(style);
};
})();
// js/harness/compactTestList-20150612143746.js end
// js/harness/compactTestView-20150612143746.js begin
var compactTestView = (function() {
function CompactTestView(fields, style) {
var self = this;
this.divId = 'testview';
this.testCount = 0;
this.initialize = function() {
this.testList = createCompactTestList(style);
this.addSwitch('Loop: ', 'loop');
this.addSwitch('Stop on failure: ', 'stoponfailure');
this.addSwitch('Log: ', 'logging');
this.addSwitch('WebM/VP9 (2015/tip only): ', 'enablewebm');
this.addCommand('Run All', 'run-selected', 'Run all tests in order.',
function(e) {
if (self.onrunselected)
self.onrunselected.call(self, e);
});
this.addLink('Links', 'links.html');
this.addLink('Instructions', 'instructions.html');
this.addLink('Changelog', 'main.html');
this.addLink('Download', 'download-20150612143746.tar.gz');
this.addTestSuites(testTypes);
};
this.addTest = function(desc) {
return this.testList.addTest(desc);
};
this.generate = function() {
CompactTestView.prototype.generate.call(this);
// document.getElementById('run-selected').focus();
var USAGE = 'Use &uarr;&darr;&rarr;&larr; to move around, ' +
'use ENTER to select.';
document.getElementById('usage').innerHTML = USAGE;
// document.getElementById('run-selected').focus();
};
this.getTest = function(index) {
return this.testList.getTest(index);
};
this.finishedOneTest = function() {
++this.testCount;
document.getElementById('finish-count').innerHTML =
this.testCount === 1 ? this.testCount + ' test finished' :
this.testCount + ' tests finished';
};
this.anySelected = function() {
return this.testList.anySelected();
};
this.initialize();
};
CompactTestView.prototype = TestView.create();
CompactTestView.prototype.constructor = CompactTestView;
return {
create: function(mseSpec, fields, style) {
CompactTestView.prototype = TestView.create(mseSpec);
CompactTestView.prototype.constructor = CompactTestView;
return new CompactTestView(fields, style);
}
};
})();
// js/harness/compactTestView-20150612143746.js end
// js/lib/mse/2013/mediaSourcePortability-20150612143746.js begin
/*
* without Webkit prefix and MediaSource ver 0.5 with or without Webkit prefix.
*/
function setupMsePortability() {
var dlog = function() {
var forward = window.dlog || console.log.bind(console);
forward.apply(this, arguments);
};
// Check if we have MSE 0.6 WITHOUT webkit prefix
if (window.MediaSource) {
window.MediaSource.prototype.version = 'MSE-live';
return;
}
// Check if we have MSE 0.6 WITH webkit prefix
if (window.WebKitMediaSource) {
window.MediaSource = window.WebKitMediaSource;
window.MediaSource.prototype.version = 'MSE-live-webkit';
var cou = window.URL.createObjectURL;
var creatingURL = false;
window.URL.createObjectURL = function(obj) {
if (!creatingURL) {
creatingURL = true;
var url = window.URL.createObjectURL(obj);
creatingURL = false;
return url;
}
return cou.call(this, obj);
};
var ael = window.MediaSource.prototype.addEventListener;
window.MediaSource.prototype.addEventListener = function(
type, listener, useCaptures) {
var re = /^source(open|close|ended)$/;
var match = re.exec(type);
if (match) {
ael.call(this, 'webkit' + type, listener, useCaptures);
} else {
ael.call(this, type, listener, useCaptures);
}
};
return;
}
var v = document.createElement('video');
// Do we have any forms of MSE 0.5?
// NOTE: We will only support MSE 0.5 with webkit prefix.
if (!v.webkitSourceAddId)
return;
function MediaSource() {
this.sourceBuffers = [];
this.activeSourceBuffers = this.sourceBuffers;
this.readyState = 'closed';
this.msWrapperVideo = null;
this.msWrapperDuration = NaN;
this.msWrapperSourceIdCount = 1;
this.msWrapperHandlers = {};
this.msWrapperAppended = false;
this.isWrapper = true;
}
MediaSource.prototype.version = 'MSE-v0.5-wrapped-webkit';
var missingFeature = 'Missing:';
if (!v.webkitSourceSetDuration)
missingFeature += ' webkitSourceSetDuration';
if (!v.webkitSourceTimestampOffset)
missingFeature += ' webkitSourceTimestampOffset';
if (missingFeature !== 'Missing:')
MediaSource.prototype.version += '(' + missingFeature + ')';
MediaSource.prototype.msWrapperHandler = function(name, evt) {
var handlers = this.msWrapperHandlers[name] || [];
dlog(4, 'In msWrapperHandler');
if (name === 'close') {
this.readyState = 'closed';
this.msWrapperDuration = NaN;
} else {
this.readyState = name;
}
for (var i = 0; i < handlers.length; i++) {
handlers[i].call(evt, evt);
}
};
MediaSource.prototype.attachTo = function(video) {
dlog(4, 'In msWrapperAttach');
var names = ['open', 'close', 'ended'];
for (var i = 0; i < names.length; i++) {
var h = this.msWrapperHandler.bind(this, names[i]);
video.addEventListener('webkitsource' + names[i], h);
}
this.msWrapperVideo = video;
var self = this;
video.addEventListener('durationchange', function() {
LOG(video.duration);
self.msWrapperDuration = video.duration;
});
video.src = video.webkitMediaSourceURL;
};
MediaSource.prototype.addSourceBuffer = function(type) {
if (!this.msWrapperVideo) throw 'Unattached';
var id = '' + this.msWrapperSourceIdCount;
this.msWrapperSourceIdCount += 1;
this.msWrapperVideo.webkitSourceAddId(id, type);
var buf = new SourceBuffer(this.msWrapperVideo, id);
this.sourceBuffers.push(buf);
return buf;
};
MediaSource.prototype.removeSourceBuffer = function(buf) {
for (var i = 0; i < this.sourceBuffers.length; ++i) {
if (buf === this.sourceBuffers[i]) {
this.msWrapperVideo.webkitSourceRemoveId(buf.msWrapperSourceId);
delete this.sourceBuffers.splice(i, 1)[0];
break;
}
}
};
MediaSource.prototype.endOfStream = function(opt_error) {
var v = this.msWrapperVideo;
// TODO: are these prefixed in M21?
var err;
if (opt_error === 'network') {
err = v.EOS_NETWORK_ERR;
} else if (opt_error === 'decode') {
err = v.EOS_DECODE_ERR;
} else if (!opt_error) {
err = v.EOS_NO_ERROR;
} else {
throw 'Unrecognized endOfStream error type: ' + opt_error;
}
v.webkitSourceEndOfStream(err);
};
// The 'setDuration' method of the media element is an extension to the
// MSE-v0.5 spec, which will be implemented on some devices.
// Calling this method is defined to have the same semantics as setting
// the duration property of the Media Source object in the current spec.
// Getting it is undefined, although clearly here we just return the last
// value that we set.
Object.defineProperty(MediaSource.prototype, 'duration', {
get: function() {
if (this.readyState === 'closed')
return NaN;
return this.msWrapperDuration;
},
set: function(duration) {
this.msWrapperDuration = duration;
if (this.msWrapperVideo.webkitSourceSetDuration) {
this.msWrapperVideo.webkitSourceSetDuration(duration);
} else {
dlog(1, 'webkitSourceSetDuration() missing (ignored)');
}
}
});
MediaSource.prototype.addEventListener = function(name, handler) {
var re = /^source(open|close|ended)$/;
var match = re.exec(name);
if (match && match[1]) {
name = match[1];
var l = this.msWrapperHandlers[name] || [];
l.push(handler);
this.msWrapperHandlers[name] = l;
} else {
throw 'Unrecognized event name: ' + name;
}
};
function SourceBuffer(video, id) {
this.msWrapperVideo = video;
this.msWrapperSourceId = id;
this.msWrapperTimestampOffset = 0;
}
function FakeSourceBufferedRanges() {
this.length = 0;
}
SourceBuffer.prototype.msWrapperGetBuffered = function() {
dlog(4, 'In msWrapperGetBuffered');
// Chrome 22 doesn't like calling sourceBuffered() before initialization
// segment gets appended.
if (!this.msWrapperAppended) return new FakeSourceBufferedRanges();
var v = this.msWrapperVideo;
var id = this.msWrapperSourceId;
return v.webkitSourceBuffered(id);
};
SourceBuffer.prototype.append = function(bytes) {
dlog(4, 'In append');
var v = this.msWrapperVideo;
var id = this.msWrapperSourceId;
v.webkitSourceAppend(id, bytes);
this.msWrapperAppended = true;
};
SourceBuffer.prototype.abort = function() {
dlog(4, 'In abort');
var v = this.msWrapperVideo;
var id = this.msWrapperSourceId;
v.webkitSourceAbort(id);
};
// The 'setTimestampOffset' method of the media element is an extension to the
// MSE-v0.5 spec, which will be implemented on some devices.
// Calling this method is defined to have the same semantics as setting the
// timestampOffset property of the Media Source object in the current spec.
Object.defineProperty(SourceBuffer.prototype, 'timestampOffset', {
get: function() { return this.msWrapperTimestampOffset; },
set: function(o) {
this.msWrapperTimestampOffset = o;
if (this.msWrapperVideo.webkitSourceTimestampOffset) {
this.msWrapperVideo.webkitSourceTimestampOffset(
this.msWrapperSourceId, o);
} else {
dlog(1, 'webkitSourceTimestampOffset() missing (ignored)');
}
}
});
Object.defineProperty(SourceBuffer.prototype, 'buffered', {
get: SourceBuffer.prototype.msWrapperGetBuffered
});
window.MediaSource = MediaSource;
}
// js/lib/mse/2013/mediaSourcePortability-20150612143746.js end
// js/lib/eme/encryptedMediaPortability-20150612143746.js begin
/* The code tries to wrapper the EME versions with or without webkit prefix */
function prefixedAttributeName(obj, suffix, opt_preprefix) {
suffix = suffix.toLowerCase();
if (opt_preprefix === undefined) {
opt_preprefix = '';
}
for (var attr in obj) {
var lattr = attr.toLowerCase();
if (lattr.indexOf(opt_preprefix) == 0 &&
lattr.indexOf(suffix, lattr.length - suffix.length) != -1) {
return attr;
}
}
return null;
}
function normalizeAttribute(obj, suffix, opt_preprefix) {
if (opt_preprefix === undefined) {
opt_preprefix = '';
}
var attr = prefixedAttributeName(obj, suffix, opt_preprefix);
if (attr) {
obj[opt_preprefix + suffix] = obj[attr];
}
}
function stringToArray(s) {
var array = new Uint8Array(new ArrayBuffer(s.length));
for (var i = 0; i < s.length; i++) {
array[i] = s.charCodeAt(i);
}
return array;
}
function arrayToString(a) {
return String.fromCharCode.apply(String, a);
}
function parsePlayReadyKeyMessage(message) {
// message is UTF-16LE, let's throw out every second element.
var message_ascii = new Array();
for (var i = 0; i < message.length; i += 2) {
message_ascii.push(message[i]);
}
var str = String.fromCharCode.apply(String, message_ascii);
var doc = (new DOMParser()).parseFromString(str, 'text/xml');
return stringToArray(
atob(doc.childNodes[0].childNodes[0].childNodes[0].childNodes[0].data));
}
/**
* Extracts the BMFF Clear Key ID from the init data of the segment.
* @param {ArrayBuffer} initData Init data for the media segment.
* @return {ArrayBuffer} Returns the BMFF ClearKey ID if found, otherwise the
* initData itself.
*/
function extractBMFFClearKeyID(initData) {
// Accessing the Uint8Array's underlying ArrayBuffer is impossible, so we
// copy it to a new one for parsing.
var abuf = new ArrayBuffer(initData.length);
var view = new Uint8Array(abuf);
view.set(initData);
var dv = new DataView(abuf);
var pos = 0;
while (pos < abuf.byteLength) {
var box_size = dv.getUint32(pos, false);
var type = dv.getUint32(pos + 4, false);
if (type != 0x70737368)
throw 'Box type ' + type.toString(16) + ' not equal to "pssh"';
// Scan for Clear Key header
if ((dv.getUint32(pos + 12, false) == 0x58147ec8) &&
(dv.getUint32(pos + 16, false) == 0x04234659) &&
(dv.getUint32(pos + 20, false) == 0x92e6f52c) &&
(dv.getUint32(pos + 24, false) == 0x5ce8c3cc)) {
var size = dv.getUint32(pos + 28, false);
if (size != 16) throw 'Unexpected KID size ' + size;
return new Uint8Array(abuf.slice(pos + 32, pos + 32 + size));
}
// Failing that, scan for Widevine protobuf header
if ((dv.getUint32(pos + 12, false) == 0xedef8ba9) &&
(dv.getUint32(pos + 16, false) == 0x79d64ace) &&
(dv.getUint32(pos + 20, false) == 0xa3c827dc) &&
(dv.getUint32(pos + 24, false) == 0xd51d21ed)) {
return new Uint8Array(abuf.slice(pos + 36, pos + 52));
}
pos += box_size;
}
// Couldn't find it, give up hope.
return initData;
}
function base64_encode(arr) {
var b64dict = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var b64pad = "=";
var i;
var hexStr = "";
for (i = 0; i < arr.length; i++) {
var hex = arr[i].toString(16);
hexStr += hex.length == 1 ? "0" + hex : hex;
}
var dest = "";
var c;
for (i = 0; i+3 <= hexStr.length; i+=3) {
c = parseInt(hexStr.substring(i, i+3), 16);
dest += b64dict.charAt(c >> 6) + b64dict.charAt(c & 63);
}
if (i+1 == hexStr.length) {
c = parseInt(hexStr.substring(i, i+1), 16);
dest += b64dict.charAt(c << 2);
} else if (i+2 == hexStr.length) {
c = parseInt(hexStr.substring(i, i+2), 16);
dest += b64dict.charAt(c >> 2) + b64dict.charAt((c & 3) << 4);
}
while ((dest.length & 3) > 0) {
dest += b64pad;
}
return dest;
}
function b64tob64url(s) {
var b64urlStr = removePadding(s);
b64urlStr = b64urlStr.replace(/\+/g, "-");
b64urlStr = b64urlStr.replace(/\//g, "_");
return b64urlStr;
}
function removePadding(s) {
return s.replace(/\=/g, "");
}
function base64NoPadding_encode(arr) {
return removePadding(base64_encode(arr));
}
function base64url_encode(arr) {
return b64tob64url(base64_encode(arr));
}
(function() {
var dlog = function() {
var forward = window.dlog || console.log.bind(console);
forward.apply(this, arguments);
};
var proto = window.HTMLMediaElement.prototype;
if (proto.addKey || proto.setMediaKeys) {
return; // Non-prefix version, needn't wrap.
}
if (!proto.webkitAddKey) {
dlog(1, 'EME is not available');
return; // EME is not available at all.
}
proto.generateKeyRequest = function(keySystem, initData) {
return proto.webkitGenerateKeyRequest.call(this, keySystem, initData);
};
proto.addKey = function(keySystem, key, initData, sessionId) {
return proto.webkitAddKey.call(this, keySystem, key, initData, sessionId);
};
proto.cancelKeyRequest = function(keySystem, sessionId) {
return proto.webkitCancelKeyRequest.call(this, keySystem, sessionId);
};
var ael = proto.addEventListener;
var eventWrapper = function(listener, e) {
listener.call(this, e);
};
proto.addEventListener = function(type, listener, useCaptures) {
var re = /^(keyadded|keyerror|keymessage|needkey)$/;
var match = re.exec(type);
if (match) {
ael.call(this, 'webkit' + type, eventWrapper.bind(this, listener),
useCaptures);
} else {
ael.call(this, type, listener, useCaptures);
}
};
})();
// js/lib/eme/encryptedMediaPortability-20150612143746.js end
// js/harness/test-20150612143746.js begin
var BYPASS_CACHE = false;
var XHR_TIMEOUT_LIMIT = 5000;
(function() {
window.testTypes = {
'conformance-test': {
name: 'MSE Conformance Tests',
supported: 'all',
title: 'Media Source and Encrypted Media Conformance Tests',
heading: 'MSE Conformance Tests'
},
'encryptedmedia-test': {
name: 'EME Conformance Tests',
supported: ['2015', 'tip'],
title: 'Encrypted Media Extensions Conformance Tests',
heading: 'EME Conformance Tests'
},
'performance-test': {
name: 'MSE Performance Tests',
supported: 'all',
title: 'Media Source and Encrypted Media Conformance Tests',
heading: 'MSE Performance Tests'
},
'endurance-test': {
name: 'MSE Endurance Tests',
supported: 'all',
title: 'Media Source and Encrypted Media Conformance Tests',
heading: 'MSE Endurance Tests'
},
'progressive-test': {
name: 'Progressive Tests',
supported: 'all',
title: 'HTML Media Element Conformance Tests',
heading: 'HTML Media Element Conformance Tests'
}
};
window.kDefaultTestType = 'conformance-test';
window.currentTestType = null;
window.recycleVideoTag = true;
if (!window.LOG) {
window.LOG = console.log.bind(console);
}
var TestBase = {};
TestBase.onsourceopen = function() {
this.log('default onsourceopen()');
};
TestBase.start = function(runner, video) {
this.log('Test started');
};
TestBase.teardown = function(mseSpec) {
if (this.video != null) {
this.video.removeAllEventListeners();
this.video.pause();
if (mseSpec && mseSpec !== '0.5') // For backwards compatibility.
window.URL.revokeObjectURL(this.video.src);
this.video.src = '';
if (recycleVideoTag)
this.video.parentNode.removeChild(this.video);
}
this.ms = null;
this.video = null;
this.runner = null;
};
TestBase.log = function() {
var args = Array.prototype.slice.call(arguments, 0);
args.splice(0, 0, this.desc + ': ');
LOG.apply(this, args);
};
TestBase.dump = function() {
if (this.video) {
this.log('video.currentTime =', this.video.currentTime);
this.log('video.readyState =', this.video.readyState);
this.log('video.networkState =', this.video.networkState);
}
if (this.ms) {
this.log('ms.sb count =', this.ms.sourceBuffers.length);
for (var i = 0; i < this.ms.sourceBuffers.length; ++i) {
if (this.ms.sourceBuffers[i].buffered) {
var buffered = this.ms.sourceBuffers[i].buffered;
this.log('sb' + i + '.buffered.length', buffered.length);
for (var j = 0; j < buffered.length; ++j) {
this.log(' ' + j + ': (' + buffered.start(j) + ', ' +
buffered.end(j) + ')');
}
} else {
this.log('sb', i, 'invalid buffered range');
}
}
}
};
TestBase.timeout = 30000;
window.createTest = function(name) {
var t = function() {};
t.prototype.__proto__ = TestBase;
t.prototype.desc = name;
t.prototype.running = false;
t.prototype.category = '';
t.prototype.mandatory = true;
return t;
};
window.createMSTest = function(name) {
var t = createTest(name);
t.prototype.start = function(runner, video) {
this.ms = new MediaSource();
this.ms.addEventListener('sourceopen', this.onsourceopen.bind(this));
if (this.ms.isWrapper)
this.ms.attachTo(video);
else
this.video.src = window.URL.createObjectURL(this.ms);
this.log('MS test started');
};
return t;
};
var ConformanceTestRunner = function(testSuite, testsMask, mseSpec) {
this.testView = null;
this.currentTest = null;
this.currentTestIdx = 0;
this.assertThrowsError = true;
this.XHRManager = createXHRManager(createLogger(this.log.bind(this)));
this.timeouts = createTimeoutManager(createLogger(this.log.bind(this)));
this.lastResult = 'pass';
this.mseSpec = mseSpec || '0.5';
if (testsMask) {
this.testList = [];
testsMask = util.resize(testsMask, testSuite.tests.length,
testsMask.substr(-1));
for (var i = 0; i < testSuite.tests.length; ++i)
if (testsMask[i] === '1')
this.testList.push(testSuite.tests[i]);
} else {
this.testList = testSuite.tests;
}
this.fields = testSuite.fields;
this.info = testSuite.info;
this.viewType = testSuite.viewType;
};
ConformanceTestRunner.prototype.log = function() {
var args = Array.prototype.slice.call(arguments, 0);
args.splice(0, 0, 'ConformanceTestRunner: ');
LOG.apply(this, args);
};
ConformanceTestRunner.prototype.assert = function(cond, msg) {
if (!cond) {
++this.testList[this.currentTestIdx].prototype.failures;
this.updateStatus();
this.error('Assert failed: ' + msg, false);
}
};
ConformanceTestRunner.prototype.checkException = function(testFunc, exceptionCode) {
try {
testFunc();
this.fail('Expect exception ' + exceptionCode);
} catch (err) {
this.checkEq(err.code, exceptionCode, 'Exception');
}
};
ConformanceTestRunner.prototype.check = function(condition, passMsg, failMsg) {
if (condition)
this.log(passMsg);
else
this.assert(false, failMsg);
};
ConformanceTestRunner.prototype.checkType = function(x, y, name) {
var t = typeof(x);
var result = t === y;
this.check(result, 'checkType passed: type of ' + name + ' is (' + t + ').',
'Type of ' + name + ' is (' + t + ') which should be (' + y + ')');
};
ConformanceTestRunner.prototype.checkEq = function(x, y, name) {
var result = (x == y) ||
(typeof(x) === 'number' && typeof(y) === 'number' &&
isNaN(x) && isNaN(y));
this.check(result, 'checkEq passed: ' + name + ' is (' + x + ').',
name + ' is (' + x + ') which should be (' + y + ')');
};
ConformanceTestRunner.prototype.checkNE = function(x, y, name) {
var result = (x != y) &&
!(typeof(x) === 'number' && typeof(y) === 'number' &&
isNaN(x) && isNaN(y));
this.check(result, 'checkNE passed: ' + name + ' is (' + x + ').',
name + ' is (' + x + ') which shouldn\'t.');
};
ConformanceTestRunner.prototype.checkApproxEq = function(x, y, name) {
var diff = Math.abs(x - y);
var eps = 0.5;
this.check(diff < eps, 'checkApproxEq passed: ' + name + ' is (' + x + ').',
name + ' is (' + x + ') which should between [' +
(y - eps) + ', ' + (y + eps) + ']');
};
ConformanceTestRunner.prototype.checkGr = function(x, y, name) {
this.check(x > y, 'checkGr passed: ' + name + ' is (' + x + ').',
name + ' is (' + x +
') which should be greater than (' + y + ')');
};
ConformanceTestRunner.prototype.checkGE = function(x, y, name) {
this.check(x >= y, 'checkGE passed: ' + name + ' is (' + x + ').',
name + ' is (' + x +
') which should be greater than or equal to (' + y + ')');
};
ConformanceTestRunner.prototype.checkLE = function(x, y, name) {
this.check(x <= y, 'checkLE passed: ' + name + ' is (' + x + ').',
name + ' is (' + x +
') which should be less than or equal to (' + y + ')');
};
ConformanceTestRunner.prototype.getControlContainer = function() {
// Override this function to anchor one to the DOM.
return document.createElement('div');
};
ConformanceTestRunner.prototype.getNewVideoTag = function() {
// Override this function to anchor one to the DOM.
return document.createElement('video');
};
ConformanceTestRunner.prototype.getOutputArea = function() {
// Override this function to anchor one to the DOM.
return document.createElement('div');
};
ConformanceTestRunner.prototype.updateStatus = function() {
this.testView.getTest(this.currentTestIdx).updateStatus();
};
ConformanceTestRunner.prototype.initialize = function() {
var self = this;
if (this.viewType === 'extra compact')
this.testView = compactTestView.create(this.mseSpec, this.fields,
this.viewType);
else if (this.viewType === 'compact')
this.testView = compactTestView.create(this.mseSpec, this.fields);
else
this.testView = fullTestView.create(this.mseSpec, this.fields);
this.testView.onrunselected = function() {
self.startTest(0, self.testList.length);
};
for (var i = 0; i < this.testList.length; ++i) {
this.testList[i].prototype.onclick = this.startTest.bind(this, i, 1);
this.testView.addTest(this.testList[i].prototype);
}
this.testView.generate(this.mseSpec);
document.getElementById('info').innerText = this.info;
this.log('Media Source and Encrypted Media Conformance Tests ' +
'(version 20150612143746-4K5xqupUzgiRyTYP)');
this.longestTimeRatio = -1;
this.longestTest = null;
};
ConformanceTestRunner.prototype.onfinished = function() {
this.log('Finished!');
if (this.longestTest && this.longestTimeRatio > 0) {
this.log('Longest test is ' + this.longestTest + ', it takes ' +
this.longestTimeRatio + ' of its timeout.');
}
var keepRunning = (!stoponfailure || this.lastResult === 'pass') &&
loop && (this.testView.anySelected() || this.numOfTestToRun === 1);
if (keepRunning) {
this.testToRun = this.numOfTestToRun;
this.currentTestIdx = this.startIndex;
this.startNextTest();
} else {
this.lastResult = 'pass';
this.getNewVideoTag();
this.log('All tests are completed');
}
this.sendTestReport(GetTestResults());
};
ConformanceTestRunner.prototype.sendTestReport = function(results) {
if (this.clientName) {
var xhr = new XMLHttpRequest();
var resultsURL = 'http://qual-e.appspot.com/api?command=save_result';
resultsURL += '&source=mse_eme_conformance';
resultsURL += '&testid=' + this.clientName + '_' + this.runStartTime;
xhr.open('POST', resultsURL);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.send(JSON.stringify(results));
}
};
ConformanceTestRunner.prototype.startTest = function(startIndex, numOfTestToRun) {
if (!this.currentTest) {
this.startIndex = startIndex;
this.numOfTestToRun = numOfTestToRun;
this.testToRun = numOfTestToRun;
this.currentTestIdx = startIndex;
this.startNextTest();
this.runStartTime = Date.now();
}
};
ConformanceTestRunner.prototype.startNextTest = function() {
UpdateStreamDef(Number(enablewebm));
if (this.numOfTestToRun != 1) {
while (this.testToRun > 0 &&
!this.testView.getTest(this.currentTestIdx).selected()) {
this.testToRun--;
this.currentTestIdx++;
}
}
if (this.testToRun <= 0 || (stoponfailure && this.lastResult != 'pass')) {
this.onfinished();
return;
}
this.currentTest = new this.testList[this.currentTestIdx];
this.log('**** Starting test ' + (this.currentTest.index + 1) + ':' +
this.currentTest.desc + ' with timeout ' +
this.currentTest.timeout);
this.timeouts.setTimeout(this.timeout.bind(this), this.currentTest.timeout);
this.testList[this.currentTestIdx].prototype.testName = this.currentTest.desc;
this.testList[this.currentTestIdx].prototype.running = true;
this.updateStatus();
this.startTime = Date.now();
this.currentTest.runner = this;
this.currentTest.video = this.getNewVideoTag();
var addEventListener = this.currentTest.video.addEventListener;
this.currentTest.video.eventsAdded = [];
this.currentTest.video.addEventListener =
function(type, listener, useCapture) {
addEventListener.call(this, type, listener, useCapture);
this.eventsAdded.push([type, listener]);
};
this.currentTest.video.removeAllEventListeners = function() {
for (var i = 0; i < this.eventsAdded.length; ++i) {
this.removeEventListener(this.eventsAdded[i][0],
this.eventsAdded[i][1]);
}
};
this.currentTest.start(this, this.currentTest.video);
};
ConformanceTestRunner.prototype.succeed = function() {
this.lastResult = 'pass';
++this.testList[this.currentTestIdx].prototype.passes;
this.updateStatus();
this.log('**** Test ' + this.currentTest.desc + ' succeeded.');
this.teardownCurrentTest(false);
};
ConformanceTestRunner.prototype.error = function(msg, isTimeout) {
this.lastResult = isTimeout ? 'timeout' : 'failure';
var test = this.currentTest;
this.log(msg);
var stack = '';
try {
test.dump();
} catch (e) {
}
try {
var x = y.z.u.v.w;
} catch (e) {
if (e && e.stack)
{
this.log(e.stack);
stack = e.stack;
}
}
this.testList[this.currentTestIdx].prototype.lastError = {
message: msg,
callStack: stack
};
this.teardownCurrentTest(isTimeout);
if (this.assertThrowsError) throw msg;
};
ConformanceTestRunner.prototype.fail = function(msg) {
++this.testList[this.currentTestIdx].prototype.failures;
this.updateStatus();
this.error('Test ' + this.currentTest.desc + ' FAILED: ' + msg, false);
};
ConformanceTestRunner.prototype.timeout = function() {
var isTestTimedOut = false;
var currentTime = new Date().getTime();
var testTime = currentTime - this.startTime;
// Wait longer if we have still running xhr requests.
// Don't consider data transfer time as part of the timeout.
if (this.XHRManager) {
if (this.XHRManager.hasActiveRequests()) {
var timeSinceLastUpdate = currentTime - this.XHRManager.getLastUpdate();
if (timeSinceLastUpdate < XHR_TIMEOUT_LIMIT) {
this.timeouts.setTimeout(this.timeout.bind(this), 1000);
} else {
isTestTimedOut = true;
}
} else if (this.XHRManager.totalRequestDuration > 0) {
var testTimeLimit = this.currentTest.timeout + this.XHRManager.totalRequestDuration;
if (testTime < testTimeLimit) {
this.timeouts.setTimeout(this.timeout.bind(this),
this.XHRManager.totalRequestDuration);
} else {
isTestTimedOut = true;
}
}
} else {
isTestTimedOut = true;
}
if (isTestTimedOut) {
++this.testList[this.currentTestIdx].prototype.timeouts;
this.updateStatus();
this.error('Test ' + this.currentTest.desc + ' TIMED OUT!', true);
}
};
ConformanceTestRunner.prototype.teardownCurrentTest = function(isTimeout) {
if (!isTimeout) {
var time = Date.now() - this.startTime;
var ratio = time / this.currentTest.timeout;
if (ratio >= this.longestTimeRatio) {
this.longestTimeRatio = ratio;
this.longestTest = this.currentTest.desc;
this.log('New longest test ' + this.currentTest.desc +
' with timeout ' + this.currentTest.timeout + ' takes ' + time);
}
}
this.testList[this.currentTestIdx].prototype.running = false;
this.updateStatus();
this.timeouts.clearAll();
this.XHRManager.abortAll();
this.testView.finishedOneTest();
this.currentTest.teardown(this.mseSpec);
if (this.currentTest.ms &&
!this.currentTest.ms.isWrapper &&
this.currentTest &&
this.currentTest.video &&
this.currentTest.video.src) {
if (this.mseSpec !== '0.5')
window.URL.revokeObjectURL(this.currentTest.video.src);
this.currentTest.video.src = '';
}
this.currentTest = null;
this.testToRun--;
this.currentTestIdx++;
window.setTimeout(this.startNextTest.bind(this), 1);
};
window.TestBase = TestBase;
window.ConformanceTestRunner = ConformanceTestRunner;
window.GetTestResults = function(testStartId, testEndId) {
testStartId = testStartId || 0;
testEndId = testEndId || window.globalRunner.testList.length;
var results = [];
for (var i = testStartId; i < testEndId; ++i) {
if (window.globalRunner.testList[i]) {
var newResult = {
id: i,
name: window.globalRunner.testList[i].prototype.testName,
passed: false,
passes: 0,
error: ''
};
if (window.globalRunner.testList[i].prototype.passes > 0) {
newResult.passed = true;
newResult.passes = window.globalRunner.testList[i].prototype.passes;
}
else {
newResult.passed = false;
newResult.error = window.globalRunner.testList[i].prototype.lastError;
}
results.push(newResult);
}
}
return results;
};
window.createSimpleTest = function() {
window.ms = new MediaSource;
ms.addEventListener('sourceopen', function() {
window.vsrc = ms.addSourceBuffer(StreamDef.VideoType);
window.asrc = ms.addSourceBuffer(StreamDef.AudioType);
console.log('Objects has been created:\n' +
'They are video, ms, logger, XHMManager, timeouts, ' +
'vchain, vsrc, achain, asrc');
});
window.video = document.createElement('video');
window.logger = createLogger();
window.XHRManager = createXHRManager(logger);
window.timeouts = createTimeoutManager(logger);
video.src = window.URL.createObjectURL(ms);
window.vchain = new ResetInit(new FileSource(
'media/car-20120827-85.mp4', XHRManager, timeouts));
window.achain = new ResetInit(new FileSource(
'media/car-20120827-8b.mp4', XHRManager, timeouts));
};
})();
// js/harness/test-20150612143746.js end
// js/lib/mse/2013/msutil-20150612143746.js begin
(function() {
var DLOG_LEVEL = 3;
// Log a debug message. Only logs if the given level is less than the current
// value of the global variable DLOG_LEVEL.
window.dlog = function(level) {
if (typeof(level) !== 'number')
throw 'level has to be an non-negative integer!';
// Comment this to prevent debug output
if (arguments.length > 1 && level <= DLOG_LEVEL) {
var args = [];
for (var i = 1; i < arguments.length; ++i)
args.push(arguments[i]);
if (window.LOG)
window.LOG.apply(null, args);
else
console.log(args);
}
};
var ensureUID = (function() {
var uid = 0;
return function(sb) {
if (!sb.uid) sb.uid = uid++;
};
})();
var elementInBody = function(element) {
while (element && element !== document.body)
element = element.parentNode;
return Boolean(element);
};
// A version of 'SourceBuffer.append()' that automatically handles EOS
// (indicated by the 'null' values. Returns true if append succeeded,
// false if EOS.
window.safeAppend = function(sb, buf) {
ensureUID(sb);
if (!buf)
dlog(2, 'EOS appended to ' + sb.uid);
else
sb.append(buf);
return Boolean(buf);
};
// Convert a 4-byte array into a signed 32-bit int.
function btoi(data, offset) {
offset = offset || 0;
var result = data[offset] >>> 0;
result = (result << 8) + (data[offset + 1] >>> 0);
result = (result << 8) + (data[offset + 2] >>> 0);
result = (result << 8) + (data[offset + 3] >>> 0);
return result;
}
// Convert a 4-byte array into a fourcc.
function btofourcc(data, offset) {
offset = offset || 0;
return String.fromCharCode(data[offset], data[offset + 1],
data[offset + 2], data[offset + 3]);
}
// Convert a signed 32-bit int into a 4-byte array.
function itob(value) {
return [value >>> 24, (value >>> 16) & 0xff, (value >>> 8) & 0xff,
value & 0xff];
}
// Return the offset of sidx box
function getSIDXOffset(data) {
var length = data.length;
var pos = 0;
while (pos + 8 <= length) {
var size = [];
for (var i = 0; i < 4; ++i)
size.push(data[pos + i]);
size = btoi(size);
if (size < 8) throw 'Unexpectedly small size';
if (pos + size >= data.length) break;
if (btofourcc(data, pos + 4) === 'sidx')
return pos;
pos += size;
}
throw 'Cannot find sidx box in first ' + data.length + ' bytes of file';
}
// Given a buffer contains the first 32k of a file, return a list of tables
// containing 'time', 'duration', 'offset', and 'size' properties for each
// subsegment.
function parseSIDX(data) {
var sidxStartBytes = getSIDXOffset(data);
var currPos = sidxStartBytes;
function read(bytes) {
if (currPos + bytes > data.length) throw 'sidx box is incomplete.';
var result = [];
for (var i = 0; i < bytes; ++i) result.push(data[currPos + i]);
currPos += bytes;
return result;
}
var size = btoi(read(4));
var sidxEnd = sidxStartBytes + size;
var boxType = read(4);
boxType = btofourcc(boxType);
if (boxType !== 'sidx') throw 'Unrecognized box type ' + boxType;
var verFlags = btoi(read(4));
var refId = read(4);
var timescale = btoi(read(4));
var earliestPts, offset;
if (verFlags === 0) {
earliestPts = btoi(read(4));
offset = btoi(read(4));
} else {
dlog(2, 'Warning: may be truncating sidx values');
read(4);
earliestPts = btoi(read(4));
read(4);
offset = btoi(read(4));
}
offset = offset + sidxEnd;
var count = btoi(read(4));
var time = earliestPts;
var res = [];
for (var i = 0; i < count; ++i) {
var size = btoi(read(4));
var duration = btoi(read(4));
var sapStuff = read(4);
res.push({
time: time / timescale,
duration: duration / timescale,
offset: offset,
size: size
});
time = time + duration;
offset = offset + size;
}
if (currPos !== sidxEnd) throw 'Bad end point' + currPos + sidxEnd;
return res;
}
// Given a BufferedRange object, find the one that contains the given time
// 't'. Returns the end time of the buffered range. If a suitable buffered
// range is not found, returns 'null'.
function findBufferedRangeEndForTime(sb, t) {
var buf = sb.buffered;
ensureUID(sb);
for (var i = 0; i < buf.length; ++i) {
var s = buf.start(i), e = buf.end(i);
dlog(4, 'findBuf: uid=' + sb.uid + ' index=' + i + ' time=' + t +
' start=' + s + ' end=' + e);
if (t >= s && t <= e)
return e;
}
return null;
}
// This part defines the... source, for the, erm... media. But it's not the
// Media Source. No. No way.
//
// Let's call it "source chain" instead.
//
// At the end of a source chain is a file source. File sources implement the
// following methods:
//
// init(t, cb): Gets the (cached) initialization segment buffer for t.
// Current position is not affected. If cb is null, it will return the init
// segment, otherwise it will call cb with the asynchronously received init
// segment. If will throw is init segment is not ready and cb is null.
//
// seek(t): Sets the maximum time of the next segment to be appended. Will
// likely round down to the nearest segment start time. (To reset a source
// after EOF, seek to 0.)
//
// pull(cb): Call the cb with the next media segment.
// return value of EOS('null') indicates that the chain has been exhausted.
//
// Most source chain elements will return entire media segments, and many will
// expect incoming data to begin on a media segment boundary. Those elements
// that either do not require this property, or return output that doesn't
// follow it, will be noted.
//
// All source chain elements will forward messages that are not handled to the
// upstream element until they reach the file source.
// Produces a FileSource table.
window.FileSource = function(path, xhrManager, timeoutManager,
startIndex, endIndex) {
this.path = path;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.segs = null;
this.segIdx = 0;
this.initBuf = null;
this.init = function(t, cb) {
if (!cb) {
if (!this.initBuf)
throw 'Calling init synchronusly when the init seg is not ready';
return this.initBuf;
}
self = this;
if (this.initBuf) {
timeoutManager.setTimeout(cb.bind(this, this.initBuf), 1);
} else {
var self = this;
var xhr = xhrManager.createRequest(this.path, function(e) {
self.segs = parseSIDX(this.getResponseData());
self.startIndex = self.startIndex || 0;
self.endIndex = self.endIndex || self.segs.length - 1;
self.endIndex = Math.min(self.endIndex, self.segs.length - 1);
self.startIndex = Math.min(self.startIndex, self.endIndex);
self.segIdx = self.startIndex;
xhr = xhrManager.createRequest(self.path, function(e) {
self.initBuf = this.getResponseData();
cb.call(self, self.initBuf);
}, 0, self.segs[0].offset);
xhr.send();
}, 0, 32 * 1024);
xhr.send();
}
};
this.seek = function(t, sb) {
if (!this.initBuf)
throw 'Seek must be called after init';
if (sb)
sb.abort();
else if (t !== 0)
throw 'You can only seek to the beginning without providing a sb';
t += this.segs[this.startIndex].time;
var i = this.startIndex;
while (i <= this.endIndex && this.segs[i].time <= t)
++i;
this.segIdx = i - 1;
dlog(2, 'Seeking to segment index=' + this.segIdx + ' time=' + t +
' start=' + this.segs[this.segIdx].time +
' length=' + this.segs[this.segIdx].duration);
};
this.pull = function(cb) {
if (this.segIdx > this.endIndex) {
timeoutManager.setTimeout(cb.bind(this, null), 1);
return;
}
var seg = this.segs[this.segIdx];
++this.segIdx;
var self = this;
var xhr = xhrManager.createRequest(this.path, function(e) {
cb.call(self, this.getResponseData());
}, seg.offset, seg.size);
xhr.send();
};
this.duration = function() {
var last = this.segs[this.segs.length - 1];
return last.time + last.duration;
};
this.currSegDuration = function() {
if (!this.segs || !this.segs[this.segIdx])
return 0;
return this.segs[this.segIdx].duration;
};
};
function attachChain(downstream, upstream) {
downstream.upstream = upstream;
downstream.init = function(t, cb) {
return upstream.init(t, cb);
};
downstream.seek = function(t, sb) {
return upstream.seek(t, sb);
};
downstream.pull = function(cb) {
return upstream.pull(cb);
};
downstream.duration = function() {
return upstream.duration();
};
downstream.currSegDuration = function() {
return upstream.currSegDuration();
};
}
window.ResetInit = function(upstream) {
this.initSent = false;
attachChain(this, upstream);
this.init = function(t, cb) {
this.initSent = true;
return this.upstream.init(t, cb);
};
this.seek = function(t, sb) {
this.initSent = false;
return this.upstream.seek(t, sb);
};
this.pull = function(cb) {
if (!this.initSent) {
this.initSent = true;
this.upstream.init(0, function(initSeg) {
cb(initSeg);
});
return;
}
var self = this;
this.upstream.pull(function(rsp) {
if (!rsp)
self.initSent = false;
cb(rsp);
});
};
};
// This function _blindly_ parses the mdhd header in the segment to find the
// timescale. It doesn't take any box hierarchy into account.
function parseTimeScale(data) {
for (var i = 0; i < data.length - 3; ++i) {
if (btofourcc(data, i) !== 'mdhd')
continue;
var off = i + 16;
if (data[i + 4] != 0)
off = i + 28;
return btoi(data, off);
}
throw 'Failed to find mdhd box in the segment provided';
}
function replaceTFDT(data, tfdt) {
for (var i = 0; i < data.length - 3; ++i) {
if (btofourcc(data, i) !== 'tfdt')
continue;
tfdt = itob(tfdt); // convert it into array
var off = i + 8;
if (data[i + 4] === 0) {
data[off] = tfdt[0];
data[off + 1] = tfdt[1];
data[off + 2] = tfdt[2];
data[off + 3] = tfdt[3];
} else {
data[off] = 0;
data[off + 1] = 0;
data[off + 2] = 0;
data[off + 3] = 0;
data[off + 4] = tfdt[0];
data[off + 5] = tfdt[1];
data[off + 6] = tfdt[2];
data[off + 7] = tfdt[3];
}
return true;
}
// the init segment doesn't have tfdt box.
return false;
}
// It will repeat a normal stream to turn it into an infinite stream.
// This type of stream cannot be seeked.
window.InfiniteStream = function(upstream) {
this.upstream = upstream;
this.timescale = null;
this.elapsed = 0;
attachChain(this, upstream);
this.seek = function(t, sb) {
throw 'InfiniteStream cannot be seeked';
};
this.pull = function(cb) {
var self = this;
var currSegDuration = self.upstream.currSegDuration();
function onPull(buf) {
if (!buf) {
self.upstream.seek(0, null);
self.upstream.pull(onPull);
return;
}
if (!self.timescale) {
var initBuf = self.upstream.init(0);
self.timescale = parseTimeScale(initBuf);
}
var tfdt = Math.floor(self.timescale * self.elapsed);
if (tfdt === 1) tfdt = 0;
dlog(3, 'TFDT: time=' + self.elapsed + ' timescale=' + self.timescale +
' tfdt=' + tfdt);
if (replaceTFDT(buf, tfdt))
self.elapsed = self.elapsed + currSegDuration;
cb(buf);
}
this.upstream.pull(onPull);
};
return this;
};
// Pull 'len' bytes from upstream chain element 'elem'. 'cache'
// is a temporary buffer of bytes left over from the last pull.
//
// This function will send exactly 0 or 1 pull messages upstream. If 'len' is
// greater than the number of bytes in the combined values of 'cache' and the
// pulled buffer, it will be capped to the available bytes. This avoids a
// number of nasty edge cases.
//
// Returns 'rsp, newCache'. 'newCache' should be passed as 'cache' to the
// next invocation.
function pullBytes(elem, len, cache, cb) {
if (!cache) {
// Always return EOS if cache is EOS, the caller should call seek before
// reusing the source chain.
cb(cache, null);
return;
}
if (len <= cache.length) {
var buf = cache.subarray(0, len);
cache = cache.subarray(len);
cb(buf, cache);
return;
}
elem.pull(function(buf) {
if (!buf) { // EOS
cb(cache, buf);
return;
}
var newCache = new Uint8Array(new ArrayBuffer(cache.length + buf.length));
newCache.set(cache);
newCache.set(buf, cache.length);
cache = newCache;
if (cache.length <= len) {
cb(cache, new Uint8Array(new ArrayBuffer(0)));
} else {
buf = cache.subarray(0, len);
cache = cache.subarray(len);
cb(buf, cache);
}
});
}
window.FixedAppendSize = function(upstream, size) {
this.cache = new Uint8Array(new ArrayBuffer(0));
attachChain(this, upstream);
this.appendSize = function() {
return size || 512 * 1024;
};
this.seek = function(t, sb) {
this.cache = new Uint8Array(new ArrayBuffer(0));
return this.upstream.seek(t, sb);
};
this.pull = function(cb) {
var len = this.appendSize();
var self = this;
pullBytes(this.upstream, len, this.cache, function(buf, cache) {
self.cache = cache;
cb(buf);
});
};
};
window.RandomAppendSize = function(upstream, min, max) {
FixedAppendSize.apply(this, arguments);
this.appendSize = function() {
min = min || 100;
max = max || 10000;
return Math.floor(Math.random() * (max - min + 1) + min);
};
};
window.RandomAppendSize.prototype = new window.FixedAppendSize;
window.RandomAppendSize.prototype.constructor = window.RandomAppendSize;
// This function appends the init segment to media source
window.appendInit = function(mp, sb, chain, t, cb) {
chain.init(t, function(initSeg) {
sb.append(initSeg);
cb();
});
};
// This is a simple append loop. It pulls data from 'chain' and appends it to
// 'sb' until the end of the buffered range contains time 't'.
// It starts from the current playback location.
window.appendUntil = function(timeoutManager, mp, sb, chain, t, cb) {
if (!elementInBody(mp)) {
cb();
return;
}
var started = sb.buffered.length !== 0;
var current = mp.currentTime;
var bufferedEnd = findBufferedRangeEndForTime(sb, current);
if (bufferedEnd) {
bufferedEnd = bufferedEnd + 0.1;
} else {
bufferedEnd = 0;
if (started) {
chain.seek(0, sb);
}
}
(function loop(buffer) {
if (!elementInBody(mp)) {
cb();
return;
}
if (buffer) {
if (!safeAppend(sb, buffer)) {
cb();
return;
}
bufferedEnd = findBufferedRangeEndForTime(sb, bufferedEnd);
if (bufferedEnd) {
bufferedEnd = bufferedEnd + 0.1;
} else {
bufferedEnd = 0;
}
timeoutManager.setTimeout(loop, 0);
} else {
if (t >= bufferedEnd && !mp.error)
chain.pull(loop);
else
cb();
}
})();
};
// This is a simple append loop. It pulls data from 'chain' and appends it to
// 'sb' until the end of the buffered range that contains time 't' is at
// least 'gap' seconds beyond 't'. If 't' is not currently in a buffered
// range, it first seeks to a time before 't' and appends until 't' is
// covered.
window.appendAt = function(timeoutManager, mp, sb, chain, t, gap, cb) {
if (!elementInBody(mp)) {
cb();
return;
}
gap = gap || 3;
var bufferedEnd = findBufferedRangeEndForTime(sb, t);
(function loop(buffer) {
if (!elementInBody(mp)) {
cb();
return;
}
if (buffer) {
if (!safeAppend(sb, buffer))
return;
bufferedEnd = findBufferedRangeEndForTime(sb, t);
timeoutManager.setTimeout(loop, 0);
} else {
if (t + gap >= (bufferedEnd || 0) && !mp.error) {
chain.pull(loop);
} else {
cb();
}
}
})();
};
// Append data from chains 'f1' and 'f2' to source buffers 's1' and 's2',
// maintaining 'lead' seconds of time between current playback time and end of
// current buffered range. Continue to do this until the current playback time
// reaches 'endTime'.
// It supports play one stream, where 's2' and 'f2' are null.
//
// 'lead' may be small or negative, which usually triggers some interesting
// fireworks with regard to the network buffer level state machine.
//
// TODO: catch transition to HAVE_CURRENT_DATA or lower and append enough to
// resume in that case
window.playThrough = function(timeoutManager, mp, lead, endTime, s1, f1, s2,
f2, cb) {
var yieldTime = 0.03;
function loop() {
if (!elementInBody(mp))
return;
if (mp.currentTime <= endTime && !mp.error)
timeoutManager.setTimeout(playThrough.bind(
null, timeoutManager, mp, lead, endTime, s1, f1, s2, f2, cb),
yieldTime * 1000);
else
cb();
};
appendAt(timeoutManager, mp, s1, f1, mp.currentTime, yieldTime + lead,
function() {
if (s2)
appendAt(timeoutManager, mp, s2, f2, mp.currentTime,
yieldTime + lead, loop);
else
loop();
});
};
window.waitUntil = function(timeouts, media, target, cb) {
var initTime = media.currentTime;
var lastTime = lastTime;
var check = function() {
if (media.currentTime === initTime) {
timeouts.setTimeout(check, 500);
} else if (media.currentTime === lastTime || media.currentTime > target) {
cb();
} else {
lastTime = media.currentTime;
timeouts.setTimeout(check, 500);
}
};
timeouts.setTimeout(check, 500);
};
window.callAfterLoadedMetaData = function(media, testFunc) {
var onLoadedMetadata = function() {
LOG('onLoadedMetadata called');
media.removeEventListener('loadedmetadata', onLoadedMetadata);
testFunc();
};
if (media.readyState >= media.HAVE_METADATA) {
LOG('onLoadedMetadata bypassed');
testFunc();
} else {
media.addEventListener('loadedmetadata', onLoadedMetadata);
}
};
})();
// js/lib/mse/2013/msutil-20150612143746.js end
// js/tests/2013/conformanceTest-20150612143746.js begin
var ConformanceTest = function() {
var tests = [];
var info = 'No MSE Support!';
if (window.MediaSource)
info = 'MSE Version: ' + MediaSource.prototype.version;
info += ' / Default Timeout: ' + TestBase.timeout + 'ms';
var fields = ['passes', 'failures', 'timeouts'];
var createConformanceTest = function(name) {
var t = createMSTest(name);
t.prototype.index = tests.length;
t.prototype.passes = 0;
t.prototype.failures = 0;
t.prototype.timeouts = 0;
tests.push(t);
return t;
};
var testPresence = createConformanceTest('Presence');
testPresence.prototype.title = 'Test if MediaSource object is present.';
testPresence.prototype.start = function(runner, video) {
if (!window.MediaSource)
return runner.fail('No MediaSource object available.');
var ms = new MediaSource();
if (!ms)
return runner.fail('Found MediaSource but could not create one');
if (ms.version)
this.log('Media source version reported as ' + ms.version);
else
this.log('No media source version reported');
runner.succeed();
};
testPresence.prototype.teardown = function() {};
var testAttach = createConformanceTest('Attach');
testAttach.prototype.timeout = 2000;
testAttach.prototype.title =
'Test if MediaSource object can be attached to video.';
testAttach.prototype.start = function(runner, video) {
this.ms = new MediaSource();
this.ms.addEventListener('sourceopen', function() {
runner.succeed();
});
if (this.ms.isWrapper)
this.ms.attachTo(video);
else
video.src = window.URL.createObjectURL(this.ms);
video.load();
};
testAttach.prototype.teardown = function() {};
var testAddSourceBuffer = createConformanceTest('addSourceBuffer');
testAddSourceBuffer.prototype.title =
'Test if we can add source buffer';
testAddSourceBuffer.prototype.onsourceopen = function() {
this.runner.checkEq(this.ms.sourceBuffers.length, 0, 'Source buffer number');
this.ms.addSourceBuffer(StreamDef.AudioType);
this.runner.checkEq(this.ms.sourceBuffers.length, 1, 'Source buffer number');
this.ms.addSourceBuffer(StreamDef.VideoType);
this.runner.checkEq(this.ms.sourceBuffers.length, 2, 'Source buffer number');
this.runner.succeed();
};
var testSupportedFormats = createConformanceTest('SupportedFormats');
testSupportedFormats.prototype.title =
'Test if we support mp4 video (video/mp4; codecs="avc1.640008") and ' +
'audio (audio/mp4; codecs="mp4a.40.5") formats, or webm video' +
'(video/webm; codecs="vorbis,vp9") and audio (audio/webm; codecs="vorbis").';
testSupportedFormats.prototype.onsourceopen = function() {
try {
this.log('Trying format ' + StreamDef.AudioType);
var src = this.ms.addSourceBuffer(StreamDef.AudioType);
this.log('Trying format ' + StreamDef.VideoType);
var src = this.ms.addSourceBuffer(StreamDef.VideoType);
} catch (e) {
return this.runner.fail(e);
}
this.runner.succeed();
};
var testAddSourceBufferException = createConformanceTest('AddSBException');
testAddSourceBufferException.prototype.title =
'Test if add incorrect source buffer type will fire the correct ' +
'exceptions.';
testAddSourceBufferException.prototype.onsourceopen = function() {
var runner = this.runner;
var self = this;
runner.checkException(function() {
self.ms.addSourceBuffer('^^^');
}, DOMException.NOT_SUPPORTED_ERR);
if (this.ms.isWrapper) {
runner.checkException(function() {
var video = document.createElement('video');
video.webkitSourceAddId('id', StreamDef.AudioType);
}, DOMException.INVALID_STATE_ERR);
} else {
runner.checkException(function() {
var ms = new MediaSource;
ms.addSourceBuffer(StreamDef.AudioType);
}, DOMException.INVALID_STATE_ERR);
}
runner.succeed();
};
var createInitialMediaStateTest = function(state, value, check) {
var test = createConformanceTest('InitialMedia' +
util.MakeCapitalName(state));
check = typeof(check) === 'undefined' ? 'checkEq' : check;
test.prototype.title = 'Test if the state ' + state +
' is correct when onsourceopen is called';
test.prototype.onsourceopen = function() {
this.runner[check](this.video[state], value, state);
this.runner.succeed();
};
};
createInitialMediaStateTest('duration', NaN);
createInitialMediaStateTest('videoWidth', 0);
createInitialMediaStateTest('videoHeight', 0);
createInitialMediaStateTest('readyState', HTMLMediaElement.HAVE_NOTHING);
createInitialMediaStateTest('src', '', 'checkNE');
createInitialMediaStateTest('currentSrc', '', 'checkNE');
var createInitialMSStateTest = function(state, value, check) {
var test = createConformanceTest('InitialMS' + util.MakeCapitalName(state));
check = typeof(check) === 'undefined' ? 'checkEq' : check;
test.prototype.title = 'Test if the state ' + state +
' is correct when onsourceopen is called';
test.prototype.onsourceopen = function() {
this.runner[check](this.ms[state], value, state);
this.runner.succeed();
};
};
createInitialMSStateTest('duration', NaN);
createInitialMSStateTest('readyState', 'open');
var createAppendTest = function(stream) {
var test = createConformanceTest('Append' +
util.MakeCapitalName(stream.name));
test.prototype.title = 'Test if we can append a whole ' + stream.name +
' file whose size is 1MB.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
sb.append(xhr.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
runner.checkApproxEq(sb.buffered.end(0), stream.duration, 'Range end');
runner.succeed();
});
xhr.send();
};
};
createAppendTest(StreamDef.Audio1MB);
createAppendTest(StreamDef.Video1MB);
var createAbortTest = function(stream) {
var test = createConformanceTest('Abort' + util.MakeCapitalName(stream.name));
test.prototype.title = 'Test if we can abort the current segment.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
sb.append(xhr.getResponseData());
sb.abort();
sb.append(xhr.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
runner.checkGr(sb.buffered.end(0), 0, 'Range end');
runner.succeed();
}, 0, 200000);
xhr.send();
};
};
createAbortTest(StreamDef.Audio1MB);
createAbortTest(StreamDef.Video1MB);
var createTimestampOffsetTest = function(stream) {
var test = createConformanceTest('TimestampOffset' +
util.MakeCapitalName(stream.name));
test.prototype.title = 'Test if we can set timestamp offset.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
sb.timestampOffset = 5;
sb.append(xhr.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 5, 'Range start');
runner.checkApproxEq(sb.buffered.end(0), stream.duration + 5,
'Range end');
runner.succeed();
});
xhr.send();
};
};
createTimestampOffsetTest(StreamDef.Audio1MB);
createTimestampOffsetTest(StreamDef.Video1MB);
var testDuration = createConformanceTest('Duration');
testDuration.prototype.title =
'Test if we can set duration.';
testDuration.prototype.onsourceopen = function() {
this.ms.duration = 10;
this.runner.checkEq(this.ms.duration, 10, 'ms.duration');
this.runner.succeed();
};
var testSourceRemove = createConformanceTest('SourceRemove');
testSourceRemove.prototype.title =
'Test if we can add/remove source buffer and do it for more than once';
testSourceRemove.prototype.onsourceopen = function() {
var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
this.ms.removeSourceBuffer(sb);
this.runner.checkEq(this.ms.sourceBuffers.length, 0, 'Source buffer number');
this.ms.addSourceBuffer(StreamDef.AudioType);
this.runner.checkEq(this.ms.sourceBuffers.length, 1, 'Source buffer number');
for (var i = 0; i < 10; ++i) {
try {
sb = this.ms.addSourceBuffer(StreamDef.VideoType);
this.runner.checkEq(this.ms.sourceBuffers.length, 2,
'Source buffer number');
this.ms.removeSourceBuffer(sb);
this.runner.checkEq(this.ms.sourceBuffers.length, 1,
'Source buffer number');
} catch (e) {
return this.runner.fail(e);
}
}
this.runner.succeed();
};
var createDurationAfterAppendTest = function(type, stream) {
var test = createConformanceTest('DurationAfterAppend' +
util.MakeCapitalName(type));
test.prototype.title = 'Test if the duration expands after appending data.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var ms = this.ms;
var sb = ms.addSourceBuffer(stream.type);
var self = this;
var ondurationchange = function() {
self.log('ondurationchange called');
media.removeEventListener('durationchange', ondurationchange);
runner.checkApproxEq(ms.duration, sb.buffered.end(0), 'ms.duration');
runner.succeed();
};
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
var data = xhr.getResponseData();
sb.append(data);
sb.abort();
ms.duration = sb.buffered.end(0) / 2;
media.addEventListener('durationchange', ondurationchange);
sb.append(data);
});
xhr.send();
};
};
createDurationAfterAppendTest('audio', StreamDef.Audio1MB);
createDurationAfterAppendTest('video', StreamDef.Video1MB);
var createPausedTest = function(type, stream) {
var test = createConformanceTest('PausedStateWith' +
util.MakeCapitalName(type));
test.prototype.title = 'Test if the paused state is correct before or ' +
' after appending data.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var ms = this.ms;
var sb = ms.addSourceBuffer(stream.type);
runner.checkEq(media.paused, true, 'media.paused');
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
runner.checkEq(media.paused, true, 'media.paused');
sb.append(xhr.getResponseData());
runner.checkEq(media.paused, true, 'media.paused');
runner.succeed();
});
xhr.send();
};
};
createPausedTest('audio', StreamDef.Audio1MB);
createPausedTest('video', StreamDef.Video1MB);
var createMediaElementEventsTest = function() {
var test = createConformanceTest('MediaElementEvents');
test.prototype.title = 'Test if the events on MediaSource are correct.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var ms = this.ms;
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var lastState = 'open';
var self = this;
var videoXhr = runner.XHRManager.createRequest(StreamDef.Video1MB.src,
function(e) {
self.log('onload called');
videoSb.append(videoXhr.getResponseData());
videoSb.abort();
ms.duration = 1;
ms.endOfStream();
media.play();
});
var audioXhr = runner.XHRManager.createRequest(StreamDef.Audio1MB.src,
function(e) {
self.log('onload called');
audioSb.append(audioXhr.getResponseData());
audioSb.abort();
videoXhr.send();
});
media.addEventListener('ended', function() {
self.log('onended called');
runner.succeed();
});
audioXhr.send();
};
};
createMediaElementEventsTest();
var createMediaSourceEventsTest = function() {
var test = createConformanceTest('MediaSourceEvents');
test.prototype.title = 'Test if the events on MediaSource are correct.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var ms = this.ms;
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var lastState = 'open';
var self = this;
var videoXhr = runner.XHRManager.createRequest(StreamDef.Video1MB.src,
function(e) {
self.log('onload called');
videoSb.append(videoXhr.getResponseData());
videoSb.abort();
ms.endOfStream();
});
var audioXhr = runner.XHRManager.createRequest(StreamDef.Audio1MB.src,
function(e) {
self.log('onload called');
audioSb.append(audioXhr.getResponseData());
audioSb.abort();
videoXhr.send();
});
ms.addEventListener('sourceclose', function() {
self.log('onsourceclose called');
runner.checkEq(lastState, 'ended', 'The previous state');
runner.succeed();
});
ms.addEventListener('sourceended', function() {
self.log('onsourceended called');
runner.checkEq(lastState, 'open', 'The previous state');
lastState = 'ended';
media.removeAttribute('src');
media.load();
});
audioXhr.send();
};
};
createMediaSourceEventsTest();
var testBufferSize = createConformanceTest('VideoBufferSize');
testBufferSize.prototype.title = 'Determines video buffer sizes by ' +
'appending incrementally until discard occurs, and tests that it meets ' +
'the minimum requirements for streaming.';
testBufferSize.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(StreamDef.VideoType);
var self = this;
var xhr = runner.XHRManager.createRequest('media/test-video-1MB.mp4',
function(e) {
// The test clip has a bitrate which is nearly exactly 1MB/sec, and
// lasts 1s. We start appending it repeatedly until we get eviction.
var expectedTime = 0;
while (true) {
sb.append(xhr.getResponseData());
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
if (expectedTime > sb.buffered.end(0) + 0.1) break;
expectedTime++;
sb.timestampOffset = expectedTime;
}
var MIN_SIZE = 12;
runner.checkGE(expectedTime, MIN_SIZE, 'Estimated source buffer size');
runner.succeed();
});
xhr.send();
};
var testSourceChain = createConformanceTest('SourceChain');
testSourceChain.prototype.title =
'Test if Source Chain works properly. Source Chain is a stack of ' +
'classes that help with common tasks like appending init segment or ' +
'append data in random size.';
testSourceChain.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new RandomAppendSize(new ResetInit(
new FileSource('media/car-20120827-85.mp4', runner.XHRManager,
runner.timeouts)));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new FixedAppendSize(new ResetInit(
new FileSource('media/car-20120827-8b.mp4', runner.XHRManager,
runner.timeouts)));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
media.play();
playThrough(
runner.timeouts, media, 1, 2,
videoSb, videoChain, audioSb, audioChain,
function() {
runner.checkGE(media.currentTime, 2, 'currentTime');
runner.succeed();
});
});
});
};
var testVideoDimension = createConformanceTest('VideoDimension');
testVideoDimension.prototype.title =
'Test if the readyState transition is correct.';
testVideoDimension.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(new FixedAppendSize(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts), 65536));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var self = this;
runner.checkEq(media.videoWidth, 0, 'video width');
runner.checkEq(media.videoHeight, 0, 'video height');
media.addEventListener('loadedmetadata', function(e) {
self.log('loadedmetadata called');
runner.checkEq(media.videoWidth, 640, 'video width');
runner.checkEq(media.videoHeight, 360, 'video height');
runner.succeed();
});
runner.checkEq(media.readyState, media.HAVE_NOTHING, 'readyState');
appendInit(media, videoSb, videoChain, 0, function() {});
};
var testPlaybackState = createConformanceTest('PlaybackState');
testPlaybackState.prototype.title =
'Test if the playback state transition is correct.';
testPlaybackState.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(new FixedAppendSize(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts), 65536));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(new FixedAppendSize(
new FileSource('media/car-20120827-8b.mp4', runner.XHRManager,
runner.timeouts), 65536));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var self = this;
media.play();
runner.checkEq(media.currentTime, 0, 'media.currentTime');
media.pause();
runner.checkEq(media.currentTime, 0, 'media.currentTime');
appendInit(media, audioSb, audioChain, 0, function() {});
appendInit(media, videoSb, videoChain, 0, function() {});
callAfterLoadedMetaData(media, function() {
media.play();
runner.checkEq(media.currentTime, 0, 'media.currentTime');
media.pause();
runner.checkEq(media.currentTime, 0, 'media.currentTime');
media.play();
appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
playThrough(runner.timeouts, media, 1, 2, audioSb, audioChain,
videoSb, videoChain, function() {
var time = media.currentTime;
media.pause();
runner.checkApproxEq(media.currentTime, time, 'media.currentTime');
runner.succeed();
});
});
});
});
};
var testStartPlayWithoutData = createConformanceTest('StartPlayWithoutData');
testStartPlayWithoutData.prototype.title =
'Test if we can start play before feeding any data. The play should ' +
'start automatically after data is appended';
testStartPlayWithoutData.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8d.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
media.play();
appendUntil(runner.timeouts, media, videoSb, videoChain, 1, function() {
appendUntil(runner.timeouts, media, audioSb, audioChain, 1, function() {
playThrough(
runner.timeouts, media, 1, 2,
videoSb, videoChain, audioSb, audioChain,
function() {
runner.checkGE(media.currentTime, 2, 'currentTime');
runner.succeed();
});
});
});
};
var testPlayPartialSegment = createConformanceTest('PlayPartialSegment');
testPlayPartialSegment.prototype.title =
'Test if we can play a partially appended video segment.';
testPlayPartialSegment.prototype.onsourceopen = function() {
var runner = this.runner;
var video = this.video;
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var videoXhr = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
function(e) {
videoSb.append(this.getResponseData());
video.addEventListener('timeupdate', function(e) {
if (!video.paused && video.currentTime >= 2) {
runner.succeed();
}
});
video.play();
}, 0, 1500000);
var audioXhr = runner.XHRManager.createRequest('media/car-20120827-8b.mp4',
function(e) {
audioSb.append(this.getResponseData());
videoXhr.send();
}, 0, 500000);
audioXhr.send();
};
var testIncrementalAudio = createConformanceTest('IncrementalAudio');
testIncrementalAudio.prototype.title =
'Test if we can append audio not in the unit of segment.';
testIncrementalAudio.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
var xhr = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
function(e) {
sb.append(xhr.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
runner.checkApproxEq(sb.buffered.end(0), 12.42, 'Range end');
runner.succeed();
}, 0, 200000);
xhr.send();
};
var testAppendAudioOffset = createConformanceTest('AppendAudioOffset');
testAppendAudioOffset.prototype.title =
'Test if we can append audio data with an explicit offset.';
testAppendAudioOffset.prototype.onsourceopen = function() {
var runner = this.runner;
var video = this.video;
var sb = this.ms.addSourceBuffer(StreamDef.AudioType);
var xhr = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
function(e) {
sb.timestampOffset = 5;
sb.append(this.getResponseData());
xhr2.send();
}, 0, 200000);
var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8d.mp4',
function(e) {
sb.abort();
sb.timestampOffset = 0;
sb.append(this.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
runner.checkApproxEq(sb.buffered.end(0), 17.42, 'Range end');
runner.succeed();
}, 0, 200000);
xhr.send();
};
var testVideoChangeRate = createConformanceTest('VideoChangeRate');
testVideoChangeRate.prototype.title =
'Test if we can change the format of video on the fly.';
testVideoChangeRate.prototype.onsourceopen = function() {
var self = this;
var runner = this.runner;
var video = this.video;
var sb = this.ms.addSourceBuffer(StreamDef.VideoType);
var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
function(e) {
sb.timestampOffset = 5;
sb.append(this.getResponseData());
xhr2.send();
}, 0, 200000);
var xhr2 = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
function(e) {
sb.abort();
sb.timestampOffset = 0;
sb.append(this.getResponseData());
runner.checkEq(sb.buffered.length, 1, 'Source buffer number');
runner.checkEq(sb.buffered.start(0), 0, 'Range start');
runner.checkApproxEq(sb.buffered.end(0), 11.47, 'Range end');
callAfterLoadedMetaData(video, function() {
video.currentTime = 3;
video.addEventListener('seeked', function(e) {
self.log('seeked called');
video.addEventListener('timeupdate', function(e) {
self.log('timeupdate called with ' + video.currentTime);
if (!video.paused && video.currentTime >= 2) {
runner.succeed();
}
});
});
});
video.play();
}, 0, 400000);
this.ms.duration = 100000000; // Ensure that we can seek to any position.
xhr.send();
};
var createAppendMultipleInitTest = function(type, stream) {
var test = createConformanceTest('AppendMultipleInit' +
util.MakeCapitalName(type));
test.prototype.title = 'Test if we can append multiple init segments.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var chain = new FileSource(stream.src, runner.XHRManager, runner.timeouts);
var src = this.ms.addSourceBuffer(stream.type);
var init;
chain.init(0, function(buf) {
init = buf;
chain.pull(function(buf) {
for (var i = 0; i < 10; ++i)
src.append(init);
src.append(buf);
src.abort();
var end = src.buffered.end(0);
for (var i = 0; i < 10; ++i)
src.append(init);
runner.checkEq(src.buffered.end(0), end, 'Range end');
runner.succeed();
});
});
};
};
createAppendMultipleInitTest('audio', StreamDef.Audio1MB);
createAppendMultipleInitTest('video', StreamDef.Video1MB);
var testAppendOutOfOrder = createConformanceTest('AppendOutOfOrder');
testAppendOutOfOrder.prototype.title =
'Test if we can append segments out of order. This is valid according' +
' to MSE v0.6 section 2.3.';
testAppendOutOfOrder.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audioChain = new FileSource('media/car-20120827-8c.mp4',
runner.XHRManager,
runner.timeouts);
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var bufs = [];
audioChain.init(0, function(buf) {
bufs.push(buf);
audioChain.pull(function(buf) {
bufs.push(buf);
audioChain.pull(function(buf) {
bufs.push(buf);
audioChain.pull(function(buf) {
bufs.push(buf);
audioChain.pull(function(buf) {
bufs.push(buf);
audioSb.append(bufs[0]);
runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
audioSb.append(bufs[2]);
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkGr(audioSb.buffered.start(0), 0, 'Range start');
audioSb.append(bufs[1]);
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
audioSb.append(bufs[4]);
runner.checkEq(audioSb.buffered.length, 2, 'Source buffer number');
runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
audioSb.append(bufs[3]);
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkEq(audioSb.buffered.start(0), 0, 'Range start');
runner.succeed();
});
});
});
});
});
};
var testBufferedRange = createConformanceTest('BufferedRange');
testBufferedRange.prototype.title =
'Test if SourceBuffer.buffered get updated correctly after feeding data.';
testBufferedRange.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
runner.checkEq(videoSb.buffered.length, 0, 'Source buffer number');
runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
appendInit(media, videoSb, videoChain, 0, function() {
appendInit(media, audioSb, audioChain, 0, function() {
runner.checkEq(videoSb.buffered.length, 0, 'Source buffer number');
runner.checkEq(audioSb.buffered.length, 0, 'Source buffer number');
appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
runner.checkEq(videoSb.buffered.length, 1, 'Source buffer number');
runner.checkEq(videoSb.buffered.start(0), 0, 'Source buffer number');
runner.checkGE(videoSb.buffered.end(0), 5, 'Range end');
appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkEq(audioSb.buffered.start(0), 0, 'Source buffer number');
runner.checkGE(audioSb.buffered.end(0), 5, 'Range end');
runner.succeed();
});
});
});
});
};
var testMediaSourceDuration = createConformanceTest('MediaSourceDuration');
testMediaSourceDuration.prototype.title =
'Test if the duration on MediaSource can be set and got sucessfully.';
testMediaSourceDuration.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var ms = this.ms;
var videoChain = new ResetInit(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var self = this;
var onsourceclose = function() {
self.log('onsourceclose called');
runner.assert(isNaN(ms.duration));
runner.succeed();
};
runner.assert(isNaN(media.duration), 'Initial media duration not NaN');
media.play();
appendInit(media, videoSb, videoChain, 0, function() {
appendUntil(runner.timeouts, media, videoSb, videoChain, 10, function() {
runner.checkEq(ms.duration, Infinity, 'ms.duration');
ms.duration = 5;
runner.checkEq(ms.duration, 5, 'ms.duration');
runner.checkEq(media.duration, 5, 'media.duration');
runner.checkLE(videoSb.buffered.end(0), 5.1, 'Range end');
videoSb.abort();
videoChain.seek(0);
appendInit(media, videoSb, videoChain, 0, function() {
appendUntil(runner.timeouts, media,
videoSb, videoChain, 10, function() {
runner.checkApproxEq(ms.duration, 10, 'ms.duration');
ms.duration = 5;
var duration = videoSb.buffered.end(0);
ms.endOfStream();
runner.checkEq(ms.duration, duration, 'ms.duration');
media.play();
ms.addEventListener('sourceended', function() {
runner.checkEq(ms.duration, duration, 'ms.duration');
runner.checkEq(media.duration, duration, 'media.duration');
ms.addEventListener('sourceclose', onsourceclose);
media.removeAttribute('src');
media.load();
});
});
});
});
});
};
var testAudioWithOverlap = createConformanceTest('AudioWithOverlap');
testAudioWithOverlap.prototype.title =
'Test if audio data with overlap will be merged into one range.';
testAudioWithOverlap.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var GAP = 0.1;
appendInit(media, audioSb, audioChain, 0, function() {
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
var segmentDuration = audioSb.buffered.end(0);
audioSb.timestampOffset = segmentDuration - GAP;
audioChain.seek(0);
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkApproxEq(audioSb.buffered.end(0),
segmentDuration * 2 - GAP, 'Range end');
runner.succeed();
});
});
});
});
};
var testAudioWithSmallGap = createConformanceTest('AudioWithSmallGap');
testAudioWithSmallGap.prototype.title =
'Test if audio data with a gap smaller than an audio frame size ' +
'will be merged into one buffered range.';
testAudioWithSmallGap.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var GAP = 0.01; // The audio frame size of this file is 0.0232
appendInit(media, audioSb, audioChain, 0, function() {
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
var segmentDuration = audioSb.buffered.end(0);
audioSb.timestampOffset = segmentDuration + GAP;
audioChain.seek(0);
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf, 'safeAppend failed'));
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
runner.checkApproxEq(audioSb.buffered.end(0),
segmentDuration * 2 + GAP, 'Range end');
runner.succeed();
});
});
});
});
};
var testAudioWithLargeGap = createConformanceTest('AudioWithLargeGap');
testAudioWithLargeGap.prototype.title =
'Test if audio data with a gap larger than an audio frame size ' +
'will not be merged into one buffered range.';
testAudioWithLargeGap.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var GAP = 0.03; // The audio frame size of this file is 0.0232
appendInit(media, audioSb, audioChain, 0, function() {
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 1, 'Source buffer number');
var segmentDuration = audioSb.buffered.end(0);
audioSb.timestampOffset = segmentDuration + GAP;
audioChain.seek(0);
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
audioChain.pull(function(buf) {
runner.assert(safeAppend(audioSb, buf), 'safeAppend failed');
runner.checkEq(audioSb.buffered.length, 2, 'Source buffer number');
runner.succeed();
});
});
});
});
};
var testCanPlayClearKey = createConformanceTest('CanPlayClearKey');
testCanPlayClearKey.prototype.title =
'Test if canPlay return is correct for clear key.';
testCanPlayClearKey.prototype.onsourceopen = function() {
var video = this.video;
this.runner.assert(
video.canPlayType(
StreamDef.VideoType, 'org.w3.clearkey') === 'probably' ||
video.canPlayType(
StreamDef.VideoType, 'webkit-org.w3.clearkey') === 'probably',
"canPlay doesn't support video and clearkey properly");
this.runner.assert(
video.canPlayType(
StreamDef.AudioType, 'org.w3.clearkey') === 'probably' ||
video.canPlayType(
StreamDef.AudioType, 'webkit-org.w3.clearkey') === 'probably',
"canPlay doesn't support audio and clearkey properly");
this.runner.succeed();
};
var testCanPlayPlayReady = createConformanceTest('CanPlayPlayReady');
testCanPlayPlayReady.prototype.title =
'Test if canPlay return is correct for PlayReady.';
testCanPlayPlayReady.prototype.onsourceopen = function() {
var video = this.video;
this.runner.checkEq(
video.canPlayType(StreamDef.VideoType, 'com.youtube.playready'),
'probably', 'canPlayType result');
this.runner.checkEq(
video.canPlayType(StreamDef.AudioType, 'com.youtube.playready'),
'probably', 'canPlayType result');
this.runner.succeed();
};
var testCannotPlayWidevine = createConformanceTest('CannotPlayWidevine');
testCannotPlayWidevine.prototype.title =
'Test if canPlay return is correct for Widevine.';
testCannotPlayWidevine.prototype.onsourceopen = function() {
var video = this.video;
this.runner.checkEq(
video.canPlayType(StreamDef.VideoType, 'com.widevine.alpha'), '',
'canPlayType result');
this.runner.checkEq(
video.canPlayType(StreamDef.AudioType, 'com.widevine.alpha'), '',
'canPlayType result');
this.runner.succeed();
};
var testWebM = createConformanceTest('WebMHandling');
testWebM.prototype.title = 'Ensure that WebM is either supported or ' +
'that attempting to add a WebM SourceBuffer results in an error.';
testWebM.prototype.onsourceopen = function() {
var mime = 'video/webm; codecs="vorbis,vp8"';
var runner = this.runner;
try {
this.log('Add sourceBuffer typed webm');
var webmSb = this.ms.addSourceBuffer(mime);
} catch (e) {
runner.checkEq(e.code, DOMException.NOT_SUPPORTED_ERR,
'exception code');
this.log('Add sourceBuffer typed webm to closed MediaSource');
try {
(new MediaSource).addSourceBuffer(mime);
} catch (e) {
LOG("WebM with mime '" + mime + "' not supported. (This is okay.)");
runner.succeed();
return;
}
runner.fail('Add sourceBuffer typed webm to closed MediaSource hasn\'t' +
' thrown any exception.');
return;
}
var xhr = runner.XHRManager.createRequest('media/test.webm',
function(e) {
try {
webmSb.append(xhr.getResponseData());
} catch (e) {
LOG('WebM support claimed but error on appending data!');
runner.fail();
return;
}
runner.checkEq(webmSb.buffered.length, 1, 'buffered.length');
runner.checkApproxEq(webmSb.buffered.end(0), 6.04, 'buffered.end(0)');
runner.succeed();
});
xhr.send();
};
var testClearKeyAudio = createConformanceTest('ClearKeyAudio');
testClearKeyAudio.prototype.title =
'Test if we can play audio encrypted with ClearKey encryption.';
testClearKeyAudio.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(
new FileSource('media/car-20120827-86.mp4', runner.XHRManager,
runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(
new FileSource('media/car_cenc-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
media.addEventListener('needkey', function(e) {
e.target.generateKeyRequest('org.w3.clearkey', e.initData);
});
media.addEventListener('keymessage', function(e) {
var key = new Uint8Array([
0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
var keyId = new Uint8Array([
0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
e.target.addKey('org.w3.clearkey', key, keyId, e.sessionId);
});
appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
media.play();
playThrough(
runner.timeouts, media, 10, 5,
videoSb, videoChain, audioSb, audioChain, function() {
runner.checkGE(media.currentTime, 5, 'currentTime');
runner.succeed();
});
});
});
};
var testClearKeyVideo = createConformanceTest('ClearKeyVideo');
testClearKeyVideo.prototype.title =
'Test if we can play video encrypted with ClearKey encryption.';
testClearKeyVideo.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(
new FileSource('media/car_cenc-20120827-86.mp4', runner.XHRManager,
runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
media.addEventListener('needkey', function(e) {
e.target.generateKeyRequest('org.w3.clearkey', e.initData);
});
media.addEventListener('keymessage', function(e) {
var key = new Uint8Array([
0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
var keyId = new Uint8Array([
0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
e.target.addKey('org.w3.clearkey', key, keyId, e.sessionId);
});
appendUntil(runner.timeouts, media, videoSb, videoChain, 5, function() {
appendUntil(runner.timeouts, media, audioSb, audioChain, 5, function() {
media.play();
playThrough(
runner.timeouts, media, 10, 5,
videoSb, videoChain, audioSb, audioChain, function() {
runner.checkGE(media.currentTime, 5, 'currentTime');
runner.succeed();
});
});
});
};
var testSeekTimeUpdate = createConformanceTest('SeekTimeUpdate');
testSeekTimeUpdate.prototype.title =
'Timeupdate event fired with correct currentTime after seeking.';
testSeekTimeUpdate.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var lastTime = 0;
var updateCount = 0;
var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
function() {
videoSb.append(xhr.getResponseData());
var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
function() {
audioSb.append(xhr2.getResponseData());
callAfterLoadedMetaData(media, function() {
media.addEventListener('timeupdate', function(e) {
if (!media.paused) {
++updateCount;
runner.checkGE(media.currentTime, lastTime,
'media.currentTime');
if (updateCount > 3) {
updateCount = 0;
lastTime += 10;
if (lastTime >= 35)
runner.succeed();
else
media.currentTime = lastTime + 6;
}
}
});
media.play();
});
}, 0, 1000000);
xhr2.send();
}, 0, 5000000);
this.ms.duration = 100000000; // Ensure that we can seek to any position.
xhr.send();
};
var testSourceSeek = createConformanceTest('Seek');
testSourceSeek.prototype.title = 'Test if we can seek during playing. It' +
' also tests if the implementation properly supports seek operation' +
' fired immediately after another seek that hasn\'t been completed.';
testSourceSeek.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new ResetInit(new FileSource(
'media/car-20120827-86.mp4', runner.XHRManager, runner.timeouts));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new ResetInit(new FileSource(
'media/car-20120827-8c.mp4', runner.XHRManager, runner.timeouts));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var self = this;
this.ms.duration = 100000000; // Ensure that we can seek to any position.
appendUntil(runner.timeouts, media, videoSb, videoChain, 20, function() {
appendUntil(runner.timeouts, media, audioSb, audioChain, 20, function() {
self.log('Seek to 17s');
callAfterLoadedMetaData(media, function() {
media.currentTime = 17;
media.play();
playThrough(
runner.timeouts, media, 10, 19,
videoSb, videoChain, audioSb, audioChain, function() {
runner.checkGE(media.currentTime, 19, 'currentTime');
self.log('Seek to 28s');
media.currentTime = 53;
media.currentTime = 58;
playThrough(
runner.timeouts, media, 10, 60,
videoSb, videoChain, audioSb, audioChain, function() {
runner.checkGE(media.currentTime, 60, 'currentTime');
self.log('Seek to 7s');
media.currentTime = 0;
media.currentTime = 7;
videoChain.seek(7, videoSb);
audioChain.seek(7, audioSb);
playThrough(runner.timeouts, media, 10, 9, videoSb, videoChain,
audioSb, audioChain, function() {
runner.checkGE(media.currentTime, 9, 'currentTime');
runner.succeed();
});
});
});
});
});
});
};
var testBufUnbufSeek = createConformanceTest('BufUnbufSeek');
testBufUnbufSeek.prototype.title = 'Seek into and out of a buffered region.';
testBufUnbufSeek.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var xhr = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
function() {
videoSb.append(xhr.getResponseData());
var xhr2 = runner.XHRManager.createRequest('media/car-20120827-8c.mp4',
function() {
audioSb.append(xhr2.getResponseData());
callAfterLoadedMetaData(media, function() {
var N = 30;
function loop(i) {
if (i > N) {
media.currentTime = 1.005;
media.addEventListener('timeupdate', function(e) {
if (!media.paused && media.currentTime > 3)
runner.succeed();
});
return;
}
// bored of shitty test scripts now => test scripts get shittier
media.currentTime = (i++ % 2) * 1.0e6 + 1;
runner.timeouts.setTimeout(loop.bind(null, i), 50);
}
media.play();
media.addEventListener('play', loop.bind(null, 0));
});
}, 0, 100000);
xhr2.send();
}, 0, 1000000);
this.ms.duration = 100000000; // Ensure that we can seek to any position.
xhr.send();
};
var createDelayedTest = function(delayed, nonDelayed) {
var test = createConformanceTest('Delayed' +
util.MakeCapitalName(delayed.name));
test.prototype.title = 'Test if we can play properly when there' +
' is not enough ' + delayed.name + ' data. The play should resume once ' +
delayed.name + ' data is appended.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var chain = new FixedAppendSize(new ResetInit(
new FileSource(nonDelayed.src, runner.XHRManager, runner.timeouts)),
65536);
var src = this.ms.addSourceBuffer(nonDelayed.type);
var delayedChain = new FixedAppendSize(new ResetInit(
new FileSource(delayed.src, runner.XHRManager, runner.timeouts)),
65536);
var delayedSrc = this.ms.addSourceBuffer(delayed.type);
var self = this;
var ontimeupdate = function(e) {
if (!media.paused) {
var end = delayedSrc.buffered.end(0);
runner.checkLE(media.currentTime, end + 1.0, 'media.currentTime');
}
};
appendUntil(runner.timeouts, media, src, chain, 15, function() {
appendUntil(runner.timeouts, media, delayedSrc, delayedChain, 8,
function() {
var end = delayedSrc.buffered.end(0);
self.log('Start play when there is only ' + end + ' seconds of ' +
delayed.name + ' data.');
media.play();
media.addEventListener('timeupdate', ontimeupdate);
waitUntil(runner.timeouts, media, delayedSrc.buffered.end(0) + 3,
function() {
runner.checkLE(media.currentTime, end + 1.0, 'media.currentTime');
runner.checkGr(media.currentTime, end - 1.0, 'media.currentTime');
runner.succeed();
});
});
});
};
};
createDelayedTest(StreamDef.AudioNormal, StreamDef.VideoNormal);
createDelayedTest(StreamDef.VideoNormal, StreamDef.AudioNormal);
var testXHRUint8Array = createConformanceTest('XHRUint8Array');
testXHRUint8Array.prototype.title = 'Ensure that XHR can send an Uint8Array';
testXHRUint8Array.prototype.timeout = 10000;
testXHRUint8Array.prototype.start = function(runner, video) {
var s = 'XHR DATA';
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i < s.length; i++) {
view[i] = s.charCodeAt(i);
}
var xhr = runner.XHRManager.createPostRequest(
'https://drmproxy.appspot.com/echo',
function(e) {
runner.checkEq(String.fromCharCode.apply(null, xhr.getResponseData()),
s, 'XHR response');
runner.succeed();
},
view.length);
xhr.send(view);
};
var testXHRAbort = createConformanceTest('XHRAbort');
testXHRAbort.prototype.title = 'Ensure that XHR aborts actually abort by ' +
'issuing an absurd number of them and then aborting all but one.';
testXHRAbort.prototype.start = function(runner, video) {
var N = 100;
var startTime = Date.now();
var lastAbortTime;
function startXHR(i) {
var xhr = runner.XHRManager.createRequest(
'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
function() {
if (i >= N) {
xhr.getResponseData(); // This will verify status internally.
runner.succeed();
}
});
if (i < N) {
runner.timeouts.setTimeout(xhr.abort.bind(xhr), 10);
runner.timeouts.setTimeout(startXHR.bind(null, i + 1), 1);
lastAbortTime = Date.now();
}
xhr.send();
};
startXHR(0);
};
var testXHROpenState = createConformanceTest('XHROpenState');
testXHROpenState.prototype.title = 'Ensure XMLHttpRequest.open does not ' +
'reset XMLHttpRequest.responseType';
testXHROpenState.prototype.start = function(runner, video) {
var xhr = new XMLHttpRequest;
// It should not be an error to set responseType before calling open
xhr.responseType = 'arraybuffer';
xhr.open('GET', 'http://google.com', true);
runner.checkEq(xhr.responseType, 'arraybuffer', 'XHR responseType');
runner.succeed();
};
var testFrameGaps = createConformanceTest('FrameGaps');
testFrameGaps.prototype.title = 'Test media with frame durations of 24FPS ' +
'but segment timing corresponding to 23.976FPS';
testFrameGaps.prototype.filename = 'media/nq-frames24-tfdt23.mp4';
testFrameGaps.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var videoChain = new FixedAppendSize(new ResetInit(
new FileSource(this.filename, runner.XHRManager,
runner.timeouts)));
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioChain = new FixedAppendSize(new ResetInit(
new FileSource('media/car-20120827-8c.mp4', runner.XHRManager,
runner.timeouts)));
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
media.play();
playThrough(runner.timeouts, media, 5, 18, videoSb, videoChain,
audioSb, audioChain, runner.succeed.bind(runner));
};
var testFrameOverlaps = createConformanceTest('FrameOverlaps');
testFrameOverlaps.prototype.title = 'Test media with frame durations of ' +
'23.976FPS but segment timing corresponding to 24FPS';
testFrameOverlaps.prototype.filename = 'media/nq-frames23-tfdt24.mp4';
testFrameOverlaps.prototype.onsourceopen = testFrameGaps.prototype.onsourceopen;
var testAAC51 = createConformanceTest('AAC51');
testAAC51.prototype.title = 'Test 5.1-channel AAC';
testAAC51.prototype.audioFilename = 'media/sintel-trunc.mp4';
testAAC51.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var xhr = runner.XHRManager.createRequest(this.audioFilename,
function(e) {
audioSb.append(xhr.getResponseData());
var xhr2 = runner.XHRManager.createRequest('media/car-20120827-86.mp4',
function(e) {
videoSb.append(xhr2.getResponseData());
media.play();
media.addEventListener('timeupdate', function(e) {
if (!media.paused && media.currentTime > 2)
runner.succeed();
});
}, 0, 3000000);
xhr2.send();
});
xhr.send();
};
var testEventTimestamp = createConformanceTest('EventTimestamp');
testEventTimestamp.prototype.title = 'Test Event Timestamp is relative to ' +
'the epoch';
testEventTimestamp.prototype.onsourceopen = function() {
var runner = this.runner;
var video = this.video;
var videoSb = this.ms.addSourceBuffer(StreamDef.VideoType);
var audioSb = this.ms.addSourceBuffer(StreamDef.AudioType);
var last = Date.now();
runner.checkGr(last, 1360000000000, 'Date.now()');
var audioXhr = runner.XHRManager.createRequest('media/car-20120827-8b.mp4',
function(e) {
audioSb.append(this.getResponseData());
video.addEventListener('timeupdate', function(e) {
runner.checkGE(e.timeStamp, last, 'event.timeStamp');
last = e.timeStamp;
if (!video.paused && video.currentTime >= 2) {
runner.succeed();
}
});
video.play();
}, 0, 500000);
var videoXhr = runner.XHRManager.createRequest('media/car-20120827-85.mp4',
function(e) {
videoSb.append(this.getResponseData());
audioXhr.send();
}, 0, 1500000);
videoXhr.send();
};
var testDualKey = createConformanceTest('[OPTIONAL/NEW]DualKey');
testDualKey.prototype.title = 'Tests multiple video keys';
testDualKey.prototype.start = function(runner, video) {
var ms = new MediaSource();
var testEmeHandler = new EMEHandler();
var firstLicense = null;
var licenseTestPass = false;
testEmeHandler['_onLoad'] = testEmeHandler['onLoad'];
testEmeHandler['onLoad'] = function(initData, session, e) {
try {
testEmeHandler._onLoad(initData, session, e);
} catch (exp) {
if (firstLicense)
runner.fail('Adding second key failed. Perhaps the system does not ' +
'support more than one video key?');
else
runner.fail('Failed to add first key.');
}
var licenseString = arrayToString(
new Uint8Array(e.target.response)).split('\r\n').pop();
if (!firstLicense)
firstLicense = licenseString;
else if (firstLicense !== licenseString)
licenseTestPass = true;
else
runner.fail('Somehow, the same key was used. This is a failure of the ' +
'test video selection.');
};
testEmeHandler.init(video);
var kFlavorMap = {
playready: 'http://dash-mse-test.appspot.com/api/drm/playready?' +
'drm_system=playready&source=YOUTUBE&' +
'video_id=03681262dc412c06&ip=0.0.0.0&ipbits=0&' +
'expire=19000000000&' +
'sparams=ip,ipbits,expire,drm_system,source,video_id&' +
'signature=3BB038322E72D0B027F7233A733CD67D518AF675.' +
'2B7C39053DA46498D23F3BCB87596EF8FD8B1669&key=test_key1',
clearkey: 'http://dash-mse-test.appspot.com/api/drm/clearkey?' +
'drm_system=clearkey&source=YOUTUBE&video_id=03681262dc412c06&' +
'ip=0.0.0.0&ipbits=0&expire=19000000000&' +
'sparams=ip,ipbits,expire,drm_system,source,video_id&' +
'signature=065297462DF2ACB0EFC28506C5BA5E2E509864D3.' +
'1FEC674BBB2420DE6B0C7FE3ECD8740C58A43420&key=test_key1'
};
var kFlavorFiles = {
playready: [
'media/oops_cenc-20121114-145-no-clear-start.mp4',
'media/oops_cenc-20121114-145-143.mp4'],
clearkey: [
'media/oops_cenc-20121114-145-no-clear-start.mp4',
'media/oops_cenc-20121114-143-no-clear-start.mp4']
};
var keySystem = 'clearkey';
var keySystemQuery = /keysystem=([^&]*)/.exec(document.location.search);
if (keySystemQuery && kFlavorMap[keySystemQuery[1]]) {
keySystem = keySystemQuery[1];
}
try {
testEmeHandler.setFlavor(kFlavorMap, keySystem);
} catch (e) {
runner.fail('Browser does not support the requested key system: ' +
keySystem);
return;
}
function onError(e) {
runner.fail('Error reported in TestClearKeyNeedKey');
}
// Open two sources. When the second source finishes, it should also call
// onLoad above. onLoad will then check if the two keys are dissimilar.
function onSourceOpen(e) {
var sb = ms.addSourceBuffer('video/mp4; codecs="avc1.640028"');
var firstFile = new ResetInit(new FileSource(
kFlavorFiles[keySystem][0],
runner.XHRManager, runner.timeouts));
appendUntil(runner.timeouts, video, sb, firstFile, 5, function() {
sb.abort();
var secondFile = new ResetInit(new FileSource(
kFlavorFiles[keySystem][1],
runner.XHRManager, runner.timeouts));
appendInit(video, sb, secondFile, 0, function() {
sb.timestampOffset = video.buffered.end(0);
appendAt(runner.timeouts, video, sb, secondFile, 5, 5, function() {
video.play();
});
});
});
video.addEventListener('timeupdate', function onTimeUpdate() {
if (video.currentTime >= 10 - 1) {
video.removeEventListener('timeupdate', onTimeUpdate);
runner.succeed();
}
});
}
ms.addEventListener('sourceopen', onSourceOpen);
ms.addEventListener('webkitsourceopen', onSourceOpen);
video.addEventListener('error', onError);
video.src = window.URL.createObjectURL(ms);
video.load();
};
testDualKey.prototype.teardown = function() {};
return {tests: tests, info: info, fields: fields, viewType: 'compact'};
};
// js/tests/2013/conformanceTest-20150612143746.js end
// js/tests/2013/enduranceTest-20150612143746.js begin
var EnduranceTest = function() {
var tests = [];
var info = 'Please use these tests to check for resource leaks or ' +
'accumulating issues.';
var fields = ['elapsed'];
var createEnduranceTest = function(name) {
var t = createMSTest(name);
t.prototype.index = tests.length;
t.prototype.elapsed = 0;
t.prototype.timeout = 2147483647;
tests.push(t);
return t;
};
var enableProgressUpdate = function(test, runner, media) {
test.prototype.elapsed = 0;
runner.updateStatus();
runner.timeouts.setInterval(function() {
test.prototype.elapsed = util.Round(media.currentTime, 3);
runner.updateStatus();
}, 1000);
};
var createOneShotTest = function(stream) {
var test = createEnduranceTest(util.MakeCapitalName(stream.name) + 'OneShot');
test.prototype.title = 'XHR and Play media once.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var sb = this.ms.addSourceBuffer(stream.type);
enableProgressUpdate(test, runner, media);
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
sb.append(xhr.getResponseData());
var end = util.Round(sb.buffered.end(0), 2);
media.addEventListener('timeupdate', function(e) {
if (!media.paused && media.currentTime > end - 1) {
media.pause();
runner.succeed();
}
});
media.play();
});
xhr.send();
};
};
createOneShotTest(StreamDef.AudioNormal);
createOneShotTest(StreamDef.VideoNormal);
var createInfiniteLoopTest = function(stream) {
var test = createEnduranceTest('Infinite' +
util.MakeCapitalName(stream.name) + 'Loop');
test.prototype.title = 'Play in an infinite loop, good way to see if ' +
'there is any resource leak.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var chain = new InfiniteStream(new ResetInit(
new FileSource(stream.src, runner.XHRManager, runner.timeouts)));
var src = this.ms.addSourceBuffer(stream.type);
enableProgressUpdate(test, runner, media);
appendUntil(runner.timeouts, media, src, chain, 1, function() {
media.play();
playThrough(
runner.timeouts, media, 20, Infinity, src, chain, null, null,
function() {}
);
});
};
};
createInfiniteLoopTest(StreamDef.AudioNormal);
createInfiniteLoopTest(StreamDef.VideoNormal);
var createInfiniteAVLoopTest = function(audio, video, desc) {
var test = createEnduranceTest('InfiniteAVLoop' + desc);
test.prototype.times = 'n/a';
test.prototype.length = 'n/a';
test.prototype.title =
'Play in an infinite loop, good way to see if there is any resource leak.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var timeouts = runner.timeouts;
var media = this.video;
var video_chain = new InfiniteStream(new ResetInit(
new FileSource(video.src, runner.XHRManager, runner.timeouts)));
var video_src = this.ms.addSourceBuffer(StreamDef.VideoType);
var audio_chain = new InfiniteStream(new ResetInit(
new FileSource(audio.src, runner.XHRManager, runner.timeouts)));
var audio_src = this.ms.addSourceBuffer(StreamDef.AudioType);
enableProgressUpdate(test, runner, media);
media.addEventListener('needkey', function(e) {
e.target.generateKeyRequest('org.w3.clearkey', e.initData);
});
media.addEventListener('keymessage', function(e) {
var key = new Uint8Array([
0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2,
0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82]);
var key_id = new Uint8Array([
0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87,
0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e]);
e.target.addKey('org.w3.clearkey', key, key_id, e.sessionId);
});
appendUntil(timeouts, media, video_src, video_chain, 1, function() {
appendUntil(timeouts, media, audio_src, audio_chain, 1, function() {
media.play();
playThrough(
timeouts, media, 5, Infinity, video_src, video_chain,
audio_src, audio_chain, function() {}
);
});
});
};
};
createInfiniteAVLoopTest(StreamDef.AudioTiny, StreamDef.VideoTiny, 'Tiny');
createInfiniteAVLoopTest(StreamDef.AudioNormal, StreamDef.VideoNormal,
'Normal');
createInfiniteAVLoopTest(StreamDef.AudioHuge, StreamDef.VideoHuge, 'Huge');
createInfiniteAVLoopTest(StreamDef.AudioTinyClearKey,
StreamDef.VideoTinyClearKey, 'TinyWithClearKey');
createInfiniteAVLoopTest(StreamDef.AudioNormalClearKey,
StreamDef.VideoNormalClearKey, 'NormalWithClearKey');
createInfiniteAVLoopTest(StreamDef.AudioHugeClearKey,
StreamDef.VideoHugeClearKey, 'HugeWithClearKey');
var createSourceAbortTest = function(stream) {
var test = createEnduranceTest('Source Abort Test');
test.prototype.title = 'Source Abort Test.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var chain = new ResetInit(new FileSource(stream.src, runner.XHRManager,
runner.timeouts));
var src = this.ms.addSourceBuffer(stream.type);
test.prototype.times = 0;
test.prototype.min = 0;
test.prototype.max = 0;
test.prototype.average = 0;
runner.updateStatus();
var segs = [];
var i = 0;
var j = 0;
var k = 0;
function doTest() {
src.append(segs[0]);
if (i < segs[1].length) {
if (j < segs[2].length) {
if (k < segs[3].length) {
src.append(segs[1].subarray(0, i));
src.abort();
src.append(segs[2].subarray(0, j));
src.abort();
src.append(segs[3].subarray(0, k));
src.abort();
test.prototype.elapsed++;
runner.updateStatus();
k++;
if (k == segs[3].length) {
k = 0;
j++;
if (j == segs[2].length) {
j = 0;
i++;
if (i == segs[1].length) {
runner.succeed();
return;
}
}
}
runner.timeouts.setTimeout(doTest, 0);
}
}
}
}
chain.pull(function(data) {
segs.push(data);
chain.pull(function(data) {
segs.push(data);
chain.pull(function(data) {
segs.push(data);
chain.pull(function(data) {
segs.push(data);
doTest();
});
});
});
});
};
};
createSourceAbortTest(StreamDef.VideoHuge);
/*
var createInfiniteLoopYTCencTest = function(stream, keysystem, desc) {
var test = createEnduranceTest(
'Infinite' + util.MakeCapitalName(stream.name) + 'LoopWith' + desc);
test.prototype.times = '∞';
test.prototype.length = '∞';
test.prototype.title =
'Play in an infinite loop, good way to see if there is any resource leak.';
var extractBMFFClearKeyID = function(initData) {
// Accessing the Uint8Array's underlying ArrayBuffer is impossible, so we
// copy it to a new one for parsing.
var abuf = new ArrayBuffer(initData.length);
var view = new Uint8Array(abuf);
view.set(initData);
var dv = new DataView(abuf);
var pos = 0;
while (pos < abuf.byteLength) {
var box_size = dv.getUint32(pos, false);
var type = dv.getUint32(pos + 4, false);
if (type !== 0x70737368)
throw 'Box type ' + type.toString(16) + ' not equal to "pssh"';
if ((dv.getUint32(pos + 12, false) === 0x58147ec8) &&
(dv.getUint32(pos + 16, false) === 0x04234659) &&
(dv.getUint32(pos + 20, false) === 0x92e6f52c) &&
(dv.getUint32(pos + 24, false) === 0x5ce8c3cc)) {
var size = dv.getUint32(pos + 28, false);
if (size !== 16) throw 'Unexpected KID size ' + size;
return new Uint8Array(abuf.slice(pos + 32, pos + 32 + size));
}
pos += box_size;
}
// Couldn't find it, give up hope.
return initData;
};
test.prototype.onsourceopen = function() {
var runner = this.runner;
var timeouts = runner.timeouts;
var media = this.video;
var chain = new InfiniteStream(new ResetInit(
new FileSource(stream.src, runner.XHRManager, runner.timeouts)));
var src = this.ms.addSourceBuffer(stream.type);
var self = this;
media.addEventListener('needkey', function(e) {
if (keysystem.indexOf('clearkey') !== -1) {
self.initData = extractBMFFClearKeyID(e.initData);
console.log(e.initData);
console.log(self.initData);
} else {
self.initData = e.initData;
}
e.target.generateKeyRequest(keysystem, self.initData);
});
media.addEventListener('keymessage', function(e) {
var xhr = runner.XHRManager.createPostRequest(
// TODO: make this universal
'http://dash-mse-test.appspot.com/api/drm/clearkey?' +
'source=YOUTUBE&video_id=03681262dc412c06',
function() {
e.target.addKey('org.w3.clearkey', xhr.getResponseData(),
self.initData, e.sessionId);
}, e.message.length);
xhr.send(e.message);
});
appendUntil(timeouts, media, src, chain, 1, function() {
media.play();
playThrough(
timeouts, media, 5, Infinity, src, chain, null, null, function() {}
);
});
};
};
createInfiniteLoopYTCencTest(StreamDef.VideoNormalYTCenc,
'webkit-org.w3.clearkey', 'ClearKey');
createInfiniteLoopYTCencTest(StreamDef.VideoNormalYTCenc,
'com.youtube.playready', 'PlayReady');
*/
return {tests: tests, info: info, fields: fields, viewType: 'full'};
};
// js/tests/2013/enduranceTest-20150612143746.js end
// js/tests/2013/performanceTest-20150612143746.js begin
var PerformanceTest = function() {
var tests = [];
var info = 'These tests can evaluate the quality of the implementation.';
var fields = ['times', 'min', 'max', 'average', 'baseline PC',
'baseline device'];
function Profiler() {
var start = Date.now();
var last = Date.now();
var times = 0;
this.min = Infinity;
this.max = -Infinity;
this.average = 0;
this.tick = function() {
var curr = Date.now();
var elapsed = (curr - last) / 1000.;
last = curr;
++times;
if (elapsed > this.max) this.max = elapsed;
if (elapsed < this.min) this.min = elapsed;
this.average = (curr - start) / times / 1000.;
};
};
var createPerformanceTest = function(name) {
var t = createMSTest(name);
t.prototype.index = tests.length;
t.prototype.times = 0;
t.prototype.min = 0;
t.prototype.max = 0;
t.prototype.average = 0;
t.prototype.baseline_PC = 'N/A';
t.prototype.baseline_device = 'N/A';
t.prototype.timeout = 2147483647;
tests.push(t);
return t;
};
var createCreateUint8ArrayTest = function(size, times, refPC, refDevice) {
var test = createPerformanceTest(
'create Uint8Array in ' + util.SizeToText(size));
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Measure Uint8Array creation performance.';
test.prototype.start = function(runner, video) {
var profiler = new Profiler;
test.prototype.times = 0;
var array;
for (var i = 0; i < times; ++i) {
array = new Uint8Array(new ArrayBuffer(size));
array = new Uint8Array(array);
profiler.tick();
++test.prototype.times;
test.prototype.min = profiler.min;
test.prototype.max = profiler.max;
test.prototype.average = util.Round(profiler.average, 3);
runner.updateStatus();
}
runner.succeed();
};
};
createCreateUint8ArrayTest(1024 * 1024, 1, 0.001, 0.002);
var createXHRRequestTest = function(size, times) {
var test = createPerformanceTest('XHR Request in ' + util.SizeToText(size));
test.prototype.title = 'Measure XHR request performance.';
test.prototype.start = function(runner, video) {
var startTime = Date.now();
var profiler = new Profiler;
test.prototype.times = 0;
function startXHR(i) {
var xhr = runner.XHRManager.createRequest(
'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
function() {
xhr.getResponseData();
profiler.tick();
++test.prototype.times;
test.prototype.min = profiler.min;
test.prototype.max = profiler.max;
test.prototype.average = util.Round(profiler.average, 3);
runner.updateStatus();
if (i < times)
runner.timeouts.setTimeout(startXHR.bind(null, i + 1), 10);
else
runner.succeed();
}, 0, size);
xhr.send();
};
startXHR(1);
};
};
createXHRRequestTest(4096, 32);
createXHRRequestTest(1024 * 1024, 16);
createXHRRequestTest(4 * 1024 * 1024, 16);
var createXHRAbortTest = function(size, times, refPC, refDevice) {
var test = createPerformanceTest('Abort XHR Request in ' +
util.SizeToText(size));
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Measure how fast to abort XHR request.';
test.prototype.start = function(runner, video) {
var startTime = Date.now();
var profiler = new Profiler;
test.prototype.times = 0;
function startXHR(i) {
var xhr = runner.XHRManager.createRequest(
'media/car-20120827-85.mp4?x=' + Date.now() + '.' + i,
function() {});
xhr.send();
runner.timeouts.setTimeout(function() {
xhr.abort();
profiler.tick();
++test.prototype.times;
test.prototype.min = profiler.min;
test.prototype.max = profiler.max;
test.prototype.average = util.Round(profiler.average, 3);
runner.updateStatus();
if (i < times)
startXHR(i + 1);
else
runner.succeed();
}, 0, size);
};
startXHR(1);
};
};
createXHRAbortTest(4096, 64, 0.098, 0.125);
createXHRAbortTest(1024 * 1024, 64, 0.116, 0.14);
createXHRAbortTest(4 * 1024 * 1024, 64, 0.126, 0.15);
var createAppendTest = function(stream, size, times, refPC, refDevice) {
var test = createPerformanceTest('Append ' + util.SizeToText(size) +
' to ' + stream.name + ' source buffer');
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Measure source buffer append performance.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
var profiler = new Profiler;
var responseData = xhr.getResponseData();
test.prototype.times = 0;
for (var i = 0; i < times; ++i) {
sb.append(responseData);
sb.abort();
sb.timestampOffset = sb.buffered.end(sb.buffered.length - 1);
profiler.tick();
++test.prototype.times;
test.prototype.min = profiler.min;
test.prototype.max = profiler.max;
test.prototype.average = util.Round(profiler.average, 3);
runner.updateStatus();
}
runner.succeed();
}, 0, size);
xhr.send();
};
};
createAppendTest(StreamDef.AudioNormal, 16384, 1024, 0.002, 0.12);
createAppendTest(StreamDef.AudioNormal, 2 * 1024 * 1024, 128, 0.098, 0.19);
createAppendTest(StreamDef.VideoNormal, 16384, 1024, 0.002, 0.1);
createAppendTest(StreamDef.VideoNormal, 4 * 1024 * 1024, 64, 0.015, 0.15);
var createSeekAccuracyTest = function(stream, size, times, step) {
var test = createPerformanceTest('Video Seek Accuracy Test');
test.prototype.baseline_PC = 0;
test.prototype.baseline_device = 0;
test.prototype.title = 'Measure video seeking accuracy.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var sb = this.ms.addSourceBuffer(stream.type);
var seekTime = 0;
var minimumTimeAfterSeek = Infinity;
var totalDiff = 0;
var xhr = runner.XHRManager.createRequest(stream.src,
function(e) {
test.prototype.times = 0;
test.prototype.min = Infinity;
test.prototype.max = 0;
sb.append(xhr.getResponseData());
sb.abort();
media.addEventListener('timeupdate', function(e) {
if (media.currentTime < minimumTimeAfterSeek)
minimumTimeAfterSeek = media.currentTime;
});
media.addEventListener('seeked', function(e) {
if (media.currentTime < minimumTimeAfterSeek)
minimumTimeAfterSeek = media.currentTime;
var diff = minimumTimeAfterSeek - seekTime;
totalDiff += diff;
++test.prototype.times;
if (diff < test.prototype.min) test.prototype.min = diff;
if (diff > test.prototype.max) test.prototype.max = diff;
test.prototype.average =
util.Round(totalDiff / test.prototype.times, 3);
seekTime += step;
minimumTimeAfterSeek = Infinity;
runner.updateStatus();
if (seekTime < times)
media.currentTime = seekTime;
else
runner.succeed();
});
callAfterLoadedMetaData(media, function() {
media.play();
media.currentTime = seekTime;
});
}, 0, size);
xhr.send();
};
};
createSeekAccuracyTest(StreamDef.VideoNormal, 12 * 1024 * 1024, 100, 1);
var createSeekBackwardsTest = function(audio, video) {
var test = createPerformanceTest('Seek Backwards Test');
test.prototype.baseline_PC = 0;
test.prototype.baseline_device = 0;
test.prototype.title = 'Measure seeking accuracy while seeking backwards.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var audio_chain = new ResetInit(
new FileSource(audio.src, runner.XHRManager, runner.timeouts));
var video_chain = new ResetInit(
new FileSource(video.src, runner.XHRManager, runner.timeouts));
var audio_src = this.ms.addSourceBuffer(audio.type);
var video_src = this.ms.addSourceBuffer(video.type);
var seekTime = video.duration - 5;
var minimumTimeAfterSeek = Infinity;
var totalDiff = 0;
var doingSeek = false;
test.prototype.times = 0;
test.prototype.min = 0;
test.prototype.max = 0;
runner.updateStatus();
var ontimeupdate = function() {
media.removeEventListener('timeupdate', ontimeupdate);
if (seekTime > 5) {
seekTime -= 1;
doSeek();
} else {
runner.succeed();
}
};
var onseeked = function() {
media.removeEventListener('seeked', onseeked);
media.addEventListener('timeupdate', ontimeupdate);
};
var doSeek = function() {
if (doingSeek) {
runner.timeouts.setTimeout(doSeek, 100);
return;
}
doingSeek = true;
media.addEventListener('seeked', onseeked);
audio_chain.seek(Math.max(seekTime, 0), audio_src);
video_chain.seek(seekTime, video_src);
media.currentTime = seekTime;
audio_chain.pull(function(data) {
audio_src.append(data);
audio_chain.pull(function(data) {
audio_src.append(data);
video_chain.pull(function(data) {
video_src.append(data);
video_chain.pull(function(data) {
video_src.append(data);
video_chain.pull(function(data) {
video_src.append(data);
doingSeek = false;
});
});
});
});
});
};
this.ms.duration = 100000000; // Ensure that we can seek to any position.
audio_chain.init(0, function(data) {
audio_src.append(data);
video_chain.init(0, function(data) {
video_src.append(data);
media.play();
callAfterLoadedMetaData(media, doSeek);
});
});
};
};
createSeekBackwardsTest(StreamDef.AudioNormal, StreamDef.VideoNormal);
var createBufferSizeTest = function(stream, refPC, refDevice) {
var test = createPerformanceTest(
'Buffer Size for ' + stream.name + ' in ' +
util.SizeToText(stream.bps) + ' bps');
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Determines buffer sizes for different stream ' +
'types and qualites.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
function startXHR() {
var size = Math.min(stream.size, 1024 * 1024);
var xhr = runner.XHRManager.createRequest(
stream.src,
function() {
var buf = xhr.getResponseData();
while (true) {
var old_end = sb.buffered.length ? sb.buffered.end(0) : 0;
sb.timestampOffset = old_end;
sb.append(buf);
sb.abort();
var new_end = sb.buffered.length ? sb.buffered.end(0) : 0;
test.prototype.min = Math.floor(new_end);
test.prototype.max = Math.floor(new_end);
test.prototype.average = Math.floor(new_end);
runner.updateStatus();
if (new_end <= old_end && new_end !== 0)
break;
}
runner.succeed();
}, 0, size);
xhr.send();
};
startXHR();
};
};
createBufferSizeTest(StreamDef.AudioTiny, 3147, 512);
createBufferSizeTest(StreamDef.AudioNormal, 786, 128);
createBufferSizeTest(StreamDef.AudioHuge, 393, 64);
createBufferSizeTest(StreamDef.VideoTiny, 4610, 784);
createBufferSizeTest(StreamDef.VideoNormal, 1062, 182);
createBufferSizeTest(StreamDef.VideoHuge, 281, 47);
var createPrerollSizeTest = function(stream, refPC, refDevice) {
var test = createPerformanceTest(
'Preroll Size for ' + stream.name + ' in ' +
util.SizeToText(stream.bps) + ' bps');
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Determines preroll sizes for different stream ' +
'types and qualites.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var sb = this.ms.addSourceBuffer(stream.type);
var end = 0;
test.prototype.times = 0;
test.prototype.min = 0;
test.prototype.max = 0;
test.prototype.average = 0;
runner.updateStatus();
function timeupdate(e) {
if (this.currentTime) {
runner.succeed();
}
};
function append(buf) {
var size = buf.length;
while (buf.length) {
var appendSize = Math.min(1, buf.length);
sb.append(buf.subarray(0, appendSize));
buf = buf.subarray(appendSize);
++test.prototype.times;
if (sb.buffered.length && sb.buffered.end(0) - end > 0.1) {
end = sb.buffered.end(0);
break;
}
}
test.prototype.min = util.Round(end, 3);
test.prototype.max = util.Round(end, 3);
test.prototype.average = util.Round(end, 3);
runner.updateStatus();
runner.timeouts.setTimeout(append.bind(null, buf), 500);
};
function startXHR() {
var size = Math.min(stream.size, 5 * 1024 * 1024);
var xhr = runner.XHRManager.createRequest(
stream.src,
function() {
var buf = new Uint8Array(new ArrayBuffer(size));
buf.set(xhr.getResponseData());
append(buf);
}, 0, size);
xhr.send();
};
this.video.addEventListener('timeupdate', timeupdate);
this.video.play();
startXHR();
};
};
createPrerollSizeTest(StreamDef.AudioTiny, 1.486, 0.557);
createPrerollSizeTest(StreamDef.AudioNormal, 0.418, 0.209);
createPrerollSizeTest(StreamDef.AudioHuge, 0.418, 0.209);
createPrerollSizeTest(StreamDef.VideoTiny, 0.25, 0.751);
createPrerollSizeTest(StreamDef.VideoNormal, 0.25, 0.667);
createPrerollSizeTest(StreamDef.VideoHuge, 0.25, 0.584);
var createSizeToPauseTest = function(stream, refPC, refDevice) {
var test = createPerformanceTest(
'Buffer Size Before Pausing ' + stream.name + ' in ' +
util.SizeToText(stream.bps) + ' bps');
test.prototype.baseline_PC = refPC;
test.prototype.baseline_device = refDevice;
test.prototype.title = 'Determines preroll sizes for different stream ' +
'types and qualites.';
test.prototype.onsourceopen = function() {
var runner = this.runner;
var media = this.video;
var chain = new ResetInit(new FileSource(stream.src, runner.XHRManager,
runner.timeouts));
var src = this.ms.addSourceBuffer(stream.type);
test.prototype.times = 0;
test.prototype.min = 0;
test.prototype.max = 0;
test.prototype.average = 0;
runner.updateStatus();
appendUntil(runner.timeouts, media, src, chain, 10, function() {
function timeupdate(e) {
if (this.currentTime) {
runner.timeouts.setTimeout(function() {
var gap = src.buffered.end(0) - media.currentTime;
gap = util.Round(gap, 3);
test.prototype.times = 1;
test.prototype.min = gap;
test.prototype.max = gap;
test.prototype.average = gap;
runner.updateStatus();
runner.succeed();
}, (src.buffered.end(0) + 3) * 1000);
}
};
media.addEventListener('timeupdate', timeupdate);
media.play();
});
};
};
createSizeToPauseTest(StreamDef.AudioTiny, 0, 0.094);
createSizeToPauseTest(StreamDef.AudioNormal, 0, 0.047);
createSizeToPauseTest(StreamDef.AudioHuge, 0, 0.047);
createSizeToPauseTest(StreamDef.VideoTiny, 0.083, 0.043);
createSizeToPauseTest(StreamDef.VideoNormal, 0.125, 0.084);
createSizeToPauseTest(StreamDef.VideoHuge, 0.083, 0.043);
return {tests: tests, info: info, fields: fields, viewType: 'full'};
};
// js/tests/2013/performanceTest-20150612143746.js end
// js/tests/progressiveTest-20150612143746.js begin
var ProgressiveTest = function() {
var tests = [];
var info = 'Default Timeout: ' + TestBase.timeout + 'ms';
var fields = ['passes', 'failures', 'timeouts'];
var createProgressiveTest = function(category, name, mandatory) {
var t = createTest(name);
t.prototype.category = category;
t.prototype.index = tests.length;
t.prototype.passes = 0;
t.prototype.failures = 0;
t.prototype.timeouts = 0;
t.prototype.mandatory = true;
if (typeof mandatory == 'boolean' && !mandatory)
t.prototype.mandatory = false;
tests.push(t);
return t;
};
var createInitialMediaStateTest = function(state, value, check) {
var test = createProgressiveTest('state before initial', state);
check = typeof(check) === 'undefined' ? 'checkEq' : check;
test.prototype.title = 'Test if the state ' + state +
' is correct when media element is just created';
test.prototype.start = function(runner, video) {
test.prototype.status = util.formatStatus(util.getAttr(video, state));
runner[check](util.getAttr(video, state), value, state);
runner.succeed();
};
};
createInitialMediaStateTest('src', ''); // can actually be undefined
createInitialMediaStateTest('currentSrc', '');
createInitialMediaStateTest('defaultPlaybackRate', 1);
createInitialMediaStateTest('playbackRate', 1);
createInitialMediaStateTest('duration', NaN);
createInitialMediaStateTest('paused', true);
createInitialMediaStateTest('seeking', false);
createInitialMediaStateTest('ended', false);
createInitialMediaStateTest('videoWidth', 0);
createInitialMediaStateTest('videoHeight', 0);
createInitialMediaStateTest('buffered.length', 0);
createInitialMediaStateTest('played.length', 0);
createInitialMediaStateTest('seekable.length', 0);
createInitialMediaStateTest('networkState', HTMLMediaElement.NETWORK_EMPTY);
createInitialMediaStateTest('readyState', HTMLMediaElement.HAVE_NOTHING);
var createMediaStateAfterSrcAssignedTest = function(state, value, check) {
var test = createProgressiveTest('state after src assigned', state);
check = typeof(check) === 'undefined' ? 'checkEq' : check;
test.prototype.title = 'Test if the state ' + state +
' is correct when media element is a src has been assigned';
test.prototype.start = function(runner, video) {
video.src = StreamDef.ProgressiveLow.src;
test.prototype.status = util.formatStatus(util.getAttr(video, state));
runner[check](util.getAttr(video, state), value, state);
runner.succeed();
};
};
createMediaStateAfterSrcAssignedTest('networkState',
HTMLMediaElement.NETWORK_NO_SOURCE);
createMediaStateAfterSrcAssignedTest('readyState',
HTMLMediaElement.HAVE_NOTHING);
createMediaStateAfterSrcAssignedTest('src', '', 'checkNE');
var createMediaStateInLoadStart = function(state, value, check) {
var test = createProgressiveTest('state in loadstart', state);
check = typeof(check) === 'undefined' ? 'checkEq' : check;
test.prototype.title = 'Test if the state ' + state +
' is correct when media element is a src has been assigned';
test.prototype.start = function(runner, video) {
video.addEventListener('loadstart', function() {
test.prototype.status = util.formatStatus(util.getAttr(video, state));
runner[check](util.getAttr(video, state), value, state);
runner.succeed();
});
video.src = StreamDef.ProgressiveLow.src;
};
};
createMediaStateInLoadStart('networkState', HTMLMediaElement.NETWORK_LOADING);
createMediaStateInLoadStart('readyState', HTMLMediaElement.HAVE_NOTHING);
createMediaStateInLoadStart('currentSrc', '', 'checkNE');
var createProgressTest = function() {
var test = createProgressiveTest('event', 'onprogress');
test.prototype.title = 'Test if there is progress event.';
test.prototype.start = function(runner, video) {
var self = this;
video.src = StreamDef.ProgressiveLow.src + '?' + Date.now();
video.addEventListener('progress', function() {
self.log('onprogress called');
runner.succeed();
});
};
};
createProgressTest();
var createTimeUpdateTest = function() {
var test = createProgressiveTest('event', 'ontimeupdate');
test.prototype.title = 'Test if there is timeupdate event.';
test.prototype.start = function(runner, video) {
var self = this;
video.src = StreamDef.ProgressiveLow.src;
video.addEventListener('timeupdate', function() {
self.log('ontimeupdate called');
runner.succeed();
});
video.play();
};
};
createTimeUpdateTest();
var createCanPlayTest = function() {
var test = createProgressiveTest('event', 'canplay');
test.prototype.title = 'Test if there is canplay event.';
test.prototype.start = function(runner, video) {
var self = this;
video.src = StreamDef.ProgressiveLow.src;
video.addEventListener('canplay', function() {
self.log('canplay called');
runner.succeed();
});
};
};
createCanPlayTest();
var createAutoPlayTest = function() {
var test = createProgressiveTest('control', 'autoplay');
test.prototype.title = 'Test if autoplay works';
test.prototype.start = function(runner, video) {
var self = this;
video.autoplay = true;
video.src = StreamDef.ProgressiveLow.src;
video.addEventListener('timeupdate', function() {
self.log('ontimeupdate called');
runner.succeed();
});
};
};
createAutoPlayTest();
var createNetworkStateTest = function() {
var test = createProgressiveTest('state', 'networkState', false);
test.prototype.title = 'Test if the network state is correct';
test.prototype.start = function(runner, video) {
var self = this;
video.src = StreamDef.ProgressiveLow.src;
video.addEventListener('suspend', function() {
self.log('onsuspend called');
runner.checkEq(video.networkState, HTMLMediaElement.NETWORK_IDLE,
'networkState');
runner.succeed();
});
};
};
createNetworkStateTest();
var createOnLoadedMetadataTest = function() {
var test = createProgressiveTest('event', 'onloadedmetadata');
test.prototype.title = 'Test if the onloadedmetadata is called correctly';
test.prototype.start = function(runner, video) {
video.addEventListener('loadedmetadata', function() {
runner.succeed();
});
video.src = 'getvideo.py';
};
};
// getvideo.py is not supported by AppEngine.
// createOnLoadedMetadataTest();
var createPlayingWithoutDataPaused = function() {
var test = createProgressiveTest('play without data', 'paused',
false);
test.prototype.title = 'Test if we can play without any data';
test.prototype.start = function(runner, video) {
video.src = 'hang.py';
video.play();
test.prototype.status = util.formatStatus(video.paused);
runner.checkEq(video.paused, false, 'video.paused');
runner.succeed();
};
};
createPlayingWithoutDataPaused();
var createPlayingWithoutDataWaiting = function() {
var test = createProgressiveTest('play without data', 'onwaiting',
false);
test.prototype.title = 'Test if we can play without any data';
test.prototype.start = function(runner, video) {
video.addEventListener('waiting', function() {
runner.checkEq(video.currentTime, 0, 'video.currentTime');
runner.succeed();
});
video.src = 'hang.py';
video.play();
};
};
createPlayingWithoutDataWaiting();
var createTimeUpdateMaxGranularity = function(suffix, playbackRatio) {
var test = createProgressiveTest(
'timeupdate', 'max granularity' + suffix, false);
test.prototype.title = 'Test the time update granularity.';
test.prototype.start = function(runner, video) {
var maxGranularity = 0;
var times = 0;
var last = 0;
video.addEventListener('suspend', function() {
video.playbackRate = playbackRatio;
video.play();
video.addEventListener('timeupdate', function() {
if (times !== 0) {
var interval = Date.now() - last;
if (interval > maxGranularity)
maxGranularity = interval;
}
if (times === 50) {
maxGranularity = maxGranularity / 1000.0;
test.prototype.status = util.Round(maxGranularity, 2);
runner.checkLE(maxGranularity, 0.26, 'maxGranularity');
runner.succeed();
}
last = Date.now();
++times;
});
});
video.src = StreamDef.ProgressiveLow.src;
};
};
createTimeUpdateMaxGranularity('', 1.0);
createTimeUpdateMaxGranularity(' slow motion', 0.2);
createTimeUpdateMaxGranularity(' fast motion', 2.0);
var createTimeUpdateMinGranularity = function(suffix, playbackRatio) {
var test = createProgressiveTest(
'timeupdate', 'min granularity' + suffix, false);
test.prototype.title = 'Test the time update granularity.';
test.prototype.start = function(runner, video) {
var minGranularity = Infinity;
var times = 0;
var last = 0;
video.addEventListener('suspend', function() {
video.playbackRate = playbackRatio;
video.play();
video.addEventListener('timeupdate', function() {
if (times !== 0) {
var interval = Date.now() - last;
if (interval > 1 && interval < minGranularity)
minGranularity = interval;
}
if (times === 50) {
minGranularity = minGranularity / 1000.0;
test.prototype.status = util.Round(minGranularity, 2);
runner.checkGE(minGranularity, 0.015, 'minGranularity');
runner.succeed();
}
last = Date.now();
++times;
});
});
video.src = StreamDef.ProgressiveLow.src;
};
};
createTimeUpdateMinGranularity('', 1.0);
createTimeUpdateMinGranularity(' slow motion', 0.2);
createTimeUpdateMinGranularity(' fast motion', 2.0);
var createTimeUpdateAccuracy = function() {
var test = createProgressiveTest('timeupdate', 'accuracy', false);
test.prototype.title = 'Test the time update granularity.';
test.prototype.start = function(runner, video) {
var maxTimeDiff = 0;
var baseTimeDiff = 0;
var times = 0;
video.addEventListener('suspend', function() {
video.play();
video.addEventListener('timeupdate', function() {
if (times === 0) {
baseTimeDiff = Date.now() / 1000.0 - video.currentTime;
} else {
var timeDiff = Date.now() / 1000.0 - video.currentTime;
maxTimeDiff = Math.max(Math.abs(timeDiff - baseTimeDiff),
maxTimeDiff);
}
if (times > 500 || video.currentTime > 10) {
test.prototype.status = util.Round(maxTimeDiff, 2);
runner.checkLE(maxTimeDiff, 0.5, 'maxTimeDiff');
runner.succeed();
}
++times;
});
});
video.src = StreamDef.ProgressiveLow.src;
};
};
createTimeUpdateAccuracy();
var createTimeUpdateProgressing = function() {
var test = createProgressiveTest('timeupdate', 'progressing', false);
test.prototype.title = 'Test if the time updates progress.';
test.prototype.start = function(runner, video) {
var last = 0;
var times = 0;
video.addEventListener('timeupdate', function() {
if (times === 0) {
last = video.currentTime;
} else {
runner.checkGE(video.currentTime, last, 'video.currentTime');
last = video.currentTime;
}
if (video.currentTime > 10) {
test.prototype.status = util.Round(video.currentTime, 2);
runner.succeed();
}
++times;
});
video.src = StreamDef.ProgressiveLow.src;
video.play();
};
};
createTimeUpdateProgressing();
var createTimeUpdateProgressingWithInitialSeek = function() {
var test = createProgressiveTest(
'timeupdate', 'progressing after seek', false);
test.prototype.title = 'Test if the time updates progress.';
test.prototype.start = function(runner, video) {
var last = 0;
var times = 0;
video.addEventListener('canplay', function() {
if (times == 0) {
video.currentTime = 0.001;
video.play();
video.addEventListener('timeupdate', function() {
if (times === 0) {
last = video.currentTime;
} else {
runner.checkGE(video.currentTime, last, 'video.currentTime');
last = video.currentTime;
}
if (video.currentTime > 10) {
test.prototype.status = util.Round(video.currentTime, 2);
runner.succeed();
}
++times;
});
}
});
video.src = StreamDef.ProgressiveLow.src;
};
};
createTimeUpdateProgressingWithInitialSeek();
var createTimeUpdateProgressingWithDurationCheck = function() {
var test = createProgressiveTest(
'timeupdate', 'duration on timeupdate', true);
test.prototype.title = 'Test if the duration is non-negative when time ' +
'updates.';
test.prototype.start = function(runner, video) {
video.addEventListener('timeupdate', function() {
runner.checkGE(video.duration, 0, 'video.duration');
if (video.currentTime > 1) {
runner.succeed();
}
});
video.src = StreamDef.ProgressiveLow.src;
video.play();
};
};
createTimeUpdateProgressingWithDurationCheck();
return {tests: tests, info: info, fields: fields, viewType: 'compact'};
};
// js/tests/progressiveTest-20150612143746.js end
// js/harness/main-20150612143746.js begin
(function() {
var timestamp;
var command;
var viewType;
var timeout;
var testsMask;
var loadTests = function(testType) {
currentTestType = testType;
// We have to make it compatible to the legacy url format.
var testName = testType.substr(0, testType.indexOf('-'));
testName = util.MakeCapitalName(testName) + 'Test';
console.log(currentTestType);
console.log(testName);
return window[testName]();
};
var parseParam = function(param, defaultValue) {
var regex = new RegExp('(\\?|\\&)' + param + '=([-,\\w]+)', 'g');
var value = regex.exec(document.URL);
return value ? value[2] : defaultValue;
};
var parseParams = function() {
var testType = parseParam('test_type', kDefaultTestType);
if (!testTypes[testType]) {
Alert('Cannot find test type ' + testType);
throw 'Cannot find test type ' + testType;
}
timestamp = parseParam('timestamp');
// if (!timestamp) return;
command = parseParam('command');
viewType = parseParam('view_type');
TestBase.timeout = parseParam('timeout', TestBase.timeout);
var disableLog = parseParam('disable_log', 'false');
window.logging = disableLog !== 'true';
var loop = parseParam('loop', 'false');
window.loop = loop === 'true';
var stoponfailure = parseParam('stoponfailure', 'false');
window.stoponfailure = stoponfailure === 'true';
var enablewebm = parseParam('enablewebm', 'false');
window.enablewebm = enablewebm === 'true';
var tests = parseParam('tests');
var exclude = parseParam('exclude');
if (tests) {
testsMask = '';
tests = tests.split(',').map(function(x) {return parseInt(x);}).
sort(function(a, b) {return a - b;});
for (var i = 0; i < tests.length; ++i) {
var index = tests[i] * 1 - 1;
if (index < 0)
continue;
testsMask = util.resize(testsMask, index, '0');
testsMask += '1';
}
testsMask += '0';
} else if (exclude) {
exclude = exclude.split(',').map(function(x) {return parseInt(x);}).
sort(function(a, b) {return a - b;});
testsMask = '';
for (var i = 0; i < exclude.length; ++i) {
var index = exclude[i] * 1 - 1;
if (index < 0)
continue;
testsMask = util.resize(testsMask, index, '1');
testsMask += '0';
}
testsMask += '1';
} else {
testsMask = parseParam('tests_mask');
if (!testsMask)
testsMask = '1';
}
var testSuite = loadTests(testType);
if (viewType)
testSuite.viewType = viewType;
return testSuite;
};
window.globalRunner = null;
var startRunner = function(testSuite, mseSpec) {
var id = 0;
var runner = new ConformanceTestRunner(testSuite, testsMask, mseSpec);
// Expose the runner so outside/injected scripts can read it.
window.globalRunner = runner;
runner.getNewVideoTag = function() {
var testarea = document.getElementById('testarea');
var vid = 'v' + id;
if (recycleVideoTag)
++id;
if (!document.getElementById(vid)) {
testarea.innerHTML = '';
testarea.appendChild(util.createElement('video', vid, 'box-right'));
document.getElementById(vid).controls = true;
}
return document.getElementById(vid);
};
runner.getControlContainer = function() {
return document.getElementById('control');
};
window.LOG = function() {
if (!window.logging)
return;
var output = document.getElementById('output');
var text = '';
for (var i = 0; i < arguments.length; ++i)
text += arguments[i].toString() + ' ';
console.log(text);
output.value = text + '\n' + output.value;
};
runner.initialize();
if (command === 'run')
runner.startTest(0, runner.testList.length);
};
window.startMseTest = function(mseSpec) {
setupMsePortability(mseSpec);
var testSuite = parseParams();
if (!timestamp) {
/* if (!/\?/.test(document.URL))
window.location = document.URL + '?timestamp=' + (new Date()).getTime();
else
window.location = document.URL + '&timestamp=' + (new Date()).getTime();
return;*/
}
startRunner(testSuite, mseSpec);
};
})();
// js/harness/main-20150612143746.js end
</script>
</head>
<body>
<script type="text/javascript">
window.setTimeout(function() { startMseTest(); }, 1);
</script>
</body>
</html>