| <!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 |