| <!DOCTYPE html> |
| <!-- |
| Copyright 2014 Google Inc. 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 = ' ... '; |
| status.className = 'cell-status-running'; |
| } else if (this.desc.failures) { |
| status.innerHTML = text || ' Fail '; |
| status.className = failureStatus; |
| } else if (this.desc.timeouts) { |
| status.innerHTML = text || ' Fail '; |
| status.className = failureStatus; |
| } else if (this.desc.passes) { |
| status.innerHTML = text || ' Pass '; |
| 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', ' ') |
| ]; |
| 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', ' '); |
| 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', ' ')); |
| 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 ↑↓→← 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 + '×tamp=' + (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> |