ovenplayer
Version:
OvenPlayer is Open-Source HTML5 Player. OvenPlayer supports WebRTC Signaling from OvenMediaEngine for Sub-Second Latency Streaming.
634 lines (578 loc) • 26.7 kB
JavaScript
/*
* Copyright 2018 Joshua Bell
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* */
(function(global) {
'use strict';
if (!('window' in global && 'document' in global))
return;
//----------------------------------------------------------------------
//
// DOM
// https://dom.spec.whatwg.org/
//
//----------------------------------------------------------------------
// Document.querySelectorAll method
// http://ajaxian.com/archives/creating-a-queryselector-for-ie-that-runs-at-native-speed
// Needed for: IE7-
if (!document.querySelectorAll) {
document.querySelectorAll = function(selectors) {
var style = document.createElement('style'), elements = [], element;
document.documentElement.firstChild.appendChild(style);
document._qsa = [];
style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
window.scrollBy(0, 0);
style.parentNode.removeChild(style);
while (document._qsa.length) {
element = document._qsa.shift();
element.style.removeAttribute('x-qsa');
elements.push(element);
}
document._qsa = null;
return elements;
};
}
// Document.querySelector method
// Needed for: IE7-
if (!document.querySelector) {
document.querySelector = function(selectors) {
var elements = document.querySelectorAll(selectors);
return (elements.length) ? elements[0] : null;
};
}
// Document.getElementsByClassName method
// Needed for: IE8-
if (!document.getElementsByClassName) {
document.getElementsByClassName = function(classNames) {
classNames = String(classNames).replace(/^|\s+/g, '.');
return document.querySelectorAll(classNames);
};
}
// Node interface constants
// Needed for: IE8-
global.Node = global.Node || function() { throw TypeError("Illegal constructor"); };
[
['ELEMENT_NODE', 1],
['ATTRIBUTE_NODE', 2],
['TEXT_NODE', 3],
['CDATA_SECTION_NODE', 4],
['ENTITY_REFERENCE_NODE', 5],
['ENTITY_NODE', 6],
['PROCESSING_INSTRUCTION_NODE', 7],
['COMMENT_NODE', 8],
['DOCUMENT_NODE', 9],
['DOCUMENT_TYPE_NODE', 10],
['DOCUMENT_FRAGMENT_NODE', 11],
['NOTATION_NODE', 12]
].forEach(function(p) { if (!(p[0] in global.Node)) global.Node[p[0]] = p[1]; });
// DOMException constants
// Needed for: IE8-
global.DOMException = global.DOMException || function() { throw TypeError("Illegal constructor"); };
[
['INDEX_SIZE_ERR', 1],
['DOMSTRING_SIZE_ERR', 2],
['HIERARCHY_REQUEST_ERR', 3],
['WRONG_DOCUMENT_ERR', 4],
['INVALID_CHARACTER_ERR', 5],
['NO_DATA_ALLOWED_ERR', 6],
['NO_MODIFICATION_ALLOWED_ERR', 7],
['NOT_FOUND_ERR', 8],
['NOT_SUPPORTED_ERR', 9],
['INUSE_ATTRIBUTE_ERR', 10],
['INVALID_STATE_ERR', 11],
['SYNTAX_ERR', 12],
['INVALID_MODIFICATION_ERR', 13],
['NAMESPACE_ERR', 14],
['INVALID_ACCESS_ERR', 15]
].forEach(function(p) { if (!(p[0] in global.DOMException)) global.DOMException[p[0]] = p[1]; });
// Event and EventTargets interfaces
// Needed for: IE8
(function(){
if (!('Element' in global) || Element.prototype.addEventListener || !Object.defineProperty)
return;
// interface Event
// PhaseType (const unsigned short)
Event.CAPTURING_PHASE = 1;
Event.AT_TARGET = 2;
Event.BUBBLING_PHASE = 3;
Object.defineProperties(Event.prototype, {
CAPTURING_PHASE: { get: function() { return 1; } },
AT_TARGET: { get: function() { return 2; } },
BUBBLING_PHASE: { get: function() { return 3; } },
target: {
get: function() {
return this.srcElement;
}},
currentTarget: {
get: function() {
return this._currentTarget;
}},
eventPhase: {
get: function() {
return (this.srcElement === this.currentTarget) ? Event.AT_TARGET : Event.BUBBLING_PHASE;
}},
bubbles: {
get: function() {
switch (this.type) {
// Mouse
case 'click':
case 'dblclick':
case 'mousedown':
case 'mouseup':
case 'mouseover':
case 'mousemove':
case 'mouseout':
case 'mousewheel':
// Keyboard
case 'keydown':
case 'keypress':
case 'keyup':
// Frame/Object
case 'resize':
case 'scroll':
// Form
case 'select':
case 'change':
case 'submit':
case 'reset':
return true;
}
return false;
}},
cancelable: {
get: function() {
switch (this.type) {
// Mouse
case 'click':
case 'dblclick':
case 'mousedown':
case 'mouseup':
case 'mouseover':
case 'mouseout':
case 'mousewheel':
// Keyboard
case 'keydown':
case 'keypress':
case 'keyup':
// Form
case 'submit':
return true;
}
return false;
}},
timeStamp: {
get: function() {
return this._timeStamp;
}},
stopPropagation: {
value: function() {
this.cancelBubble = true;
}},
preventDefault: {
value: function() {
this.returnValue = false;
}},
defaultPrevented: {
get: function() {
return this.returnValue === false;
}}
});
// interface EventTarget
function addEventListener(type, listener, useCapture) {
if (typeof listener !== 'function') return;
if (type === 'DOMContentLoaded') type = 'load';
var target = this;
var f = function(e) {
e._timeStamp = Date.now();
e._currentTarget = target;
listener.call(this, e);
e._currentTarget = null;
};
this['_' + type + listener] = f;
this.attachEvent('on' + type, f);
}
function removeEventListener(type, listener, useCapture) {
if (typeof listener !== 'function') return;
if (type === 'DOMContentLoaded') type = 'load';
var f = this['_' + type + listener];
if (f) {
this.detachEvent('on' + type, f);
this['_' + type + listener] = null;
}
}
[Window, HTMLDocument, Element].forEach(function(o) {
o.prototype.addEventListener = addEventListener;
o.prototype.removeEventListener = removeEventListener;
});
}());
// CustomEvent
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
// Needed for: IE
(function () {
if ('CustomEvent' in global && typeof global.CustomEvent === "function")
return;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = global.Event.prototype;
global.CustomEvent = CustomEvent;
})();
// Shim for DOM Events for IE7-
// http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
// Use addEvent(object, event, handler) instead of object.addEventListener(event, handler)
window.addEvent = function(obj, type, fn) {
if (obj.addEventListener) {
obj.addEventListener(type, fn, false);
} else if (obj.attachEvent) {
obj["e" + type + fn] = fn;
obj[type + fn] = function() {
var e = window.event;
e.currentTarget = obj;
e.preventDefault = function() { e.returnValue = false; };
e.stopPropagation = function() { e.cancelBubble = true; };
e.target = e.srcElement;
e.timeStamp = Date.now();
obj["e" + type + fn].call(this, e);
};
obj.attachEvent("on" + type, obj[type + fn]);
}
};
window.removeEvent = function(obj, type, fn) {
if (obj.removeEventListener) {
obj.removeEventListener(type, fn, false);
} else if (obj.detachEvent) {
obj.detachEvent("on" + type, obj[type + fn]);
obj[type + fn] = null;
obj["e" + type + fn] = null;
}
};
// DOMTokenList interface and Element.classList / Element.relList
// Needed for: IE9-
// Use getClassList(elem) instead of elem.classList() if IE7- support is needed
// Use getRelList(elem) instead of elem.relList() if IE7- support is needed
(function() {
function DOMTokenListShim(o, p) {
function split(s) { return s.length ? s.split(/\s+/g) : []; }
// NOTE: This does not exactly match the spec.
function removeTokenFromString(token, string) {
var tokens = split(string),
index = tokens.indexOf(token);
if (index !== -1) {
tokens.splice(index, 1);
}
return tokens.join(' ');
}
Object.defineProperties(
this,
{
length: {
get: function() { return split(o[p]).length; }
},
item: {
value: function(idx) {
var tokens = split(o[p]);
return 0 <= idx && idx < tokens.length ? tokens[idx] : null;
}
},
contains: {
value: function(token) {
token = String(token);
if (token.length === 0) { throw SyntaxError(); }
if (/\s/.test(token)) { throw Error("InvalidCharacterError"); }
var tokens = split(o[p]);
return tokens.indexOf(token) !== -1;
}
},
add: {
value: function(/*tokens...*/) {
var tokens = Array.prototype.slice.call(arguments).map(String);
if (tokens.some(function(token) { return token.length === 0; })) {
throw SyntaxError();
}
if (tokens.some(function(token) { return (/\s/).test(token); })) {
throw Error("InvalidCharacterError");
}
try {
var underlying_string = o[p];
var token_list = split(underlying_string);
tokens = tokens.filter(function(token) { return token_list.indexOf(token) === -1; });
if (tokens.length === 0) {
return;
}
if (underlying_string.length !== 0 && !(/\s$/).test(underlying_string)) {
underlying_string += ' ';
}
underlying_string += tokens.join(' ');
o[p] = underlying_string;
} finally {
var length = split(o[p]).length;
if (this.length !== length) { this.length = length; }
}
}
},
remove: {
value: function(/*tokens...*/) {
var tokens = Array.prototype.slice.call(arguments).map(String);
if (tokens.some(function(token) { return token.length === 0; })) {
throw SyntaxError();
}
if (tokens.some(function(token) { return (/\s/).test(token); })) {
throw Error("InvalidCharacterError");
}
try {
var underlying_string = o[p];
tokens.forEach(function(token) {
underlying_string = removeTokenFromString(token, underlying_string);
});
o[p] = underlying_string;
} finally {
var length = split(o[p]).length;
if (this.length !== length) { this.length = length; }
}
}
},
toggle: {
value: function(token/*, force*/) {
var force = arguments[1];
try {
token = String(token);
if (token.length === 0) { throw SyntaxError(); }
if (/\s/.test(token)) { throw Error("InvalidCharacterError"); }
var tokens = split(o[p]),
index = tokens.indexOf(token);
if (index !== -1 && (!force || force === (void 0))) {
o[p] = removeTokenFromString(token, o[p]);
return false;
}
if (index !== -1 && force) {
return true;
}
var underlying_string = o[p];
if (underlying_string.length !== 0 && !/\s$/.test(underlying_string)) {
underlying_string += ' ';
}
underlying_string += token;
o[p] = underlying_string;
return true;
} finally {
var length = split(o[p]).length;
if (this.length !== length) { this.length = length; }
}
}
},
toString: {
value: function() {
return o[p];
}
}
});
if (!('length' in this)) {
// In case getters are not supported
this.length = split(o[p]).length;
} else {
// If they are, shim in index getters (up to 100)
for (var i = 0; i < 100; ++i) {
Object.defineProperty(this, String(i), {
get: (function(n) { return function() { return this.item(n); }; }(i))
});
}
}
}
function addToElementPrototype(p, f) {
if ('Element' in global && Element.prototype && Object.defineProperty) {
Object.defineProperty(Element.prototype, p, { get: f });
}
}
// HTML - https://html.spec.whatwg.org
// Element.classList
if ('classList' in document.createElement('span')) {
window.getClassList = function(elem) { return elem.classList; };
} else {
window.getClassList = function(elem) { return new DOMTokenListShim(elem, 'className'); };
addToElementPrototype('classList', function() { return new DOMTokenListShim(this, 'className'); } );
}
// HTML - https://html.spec.whatwg.org
// HTMLAnchorElement.relList
// HTMLLinkElement.relList
if ('relList' in document.createElement('link')) {
window.getRelList = function(elem) { return elem.relList; };
} else {
window.getRelList = function(elem) { return new DOMTokenListShim(elem, 'rel'); };
addToElementPrototype('relList', function() { return new DOMTokenListShim(this, 'rel'); } );
}
// Add second argument to native DOMTokenList.toggle() if necessary
(function() {
if (!('DOMTokenList' in global)) return;
var e = document.createElement('span');
if (!('classList' in e)) return;
e.classList.toggle('x', false);
if (!e.classList.contains('x')) return;
global.DOMTokenList.prototype.toggle = function toggle(token/*, force*/) {
var force = arguments[1];
if (force === undefined) {
var add = !this.contains(token);
this[add ? 'add' : 'remove'](token);
return add;
}
force = !!force;
this[force ? 'add' : 'remove'](token);
return force;
};
}());
// DOM - Interface NonDocumentTypeChildNode
// Interface NonDocumentTypeChildNode
// previousElementSibling / nextElementSibling - for IE8
if (!('previousElementSibling' in document.documentElement)) {
addToElementPrototype('previousElementSibling', function() {
var n = this.previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE)
n = n.previousSibling;
return n;
});
}
if (!('nextElementSibling' in document.documentElement)) {
addToElementPrototype('nextElementSibling', function() {
var n = this.nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE)
n = n.nextSibling;
return n;
});
}
}());
// Element.matches
// https://developer.mozilla.org/en/docs/Web/API/Element/matches
// Needed for: IE, Firefox 3.6, early Webkit and Opera 15.0
// Use msMatchesSelector(selector) for IE
// Use oMatchesSelector(selector) for Opera 15.0
// Use mozMatchesSelector(selector) for Firefox 3.6
// Use webkitMatchesSelector(selector) for early Webkit
// Use polyfill if no matches() support, but querySelectorAll() support
if ('Element' in global && !Element.prototype.matches) {
if (Element.prototype.msMatchesSelector) {
Element.prototype.matches = Element.prototype.msMatchesSelector;
} else if (Element.prototype.oMatchesSelector) {
Element.prototype.matches = Element.prototype.oMatchesSelector;
} else if (Element.prototype.mozMatchesSelector) {
Element.prototype.matches = Element.prototype.mozMatchesSelector;
} else if (Element.prototype.webkitMatchesSelector) {
Element.prototype.matches = Element.prototype.webkitMatchesSelector;
} else if (document.querySelectorAll) {
Element.prototype.matches = function matches(selector) {
var matches = (this.document || this.ownerDocument).querySelectorAll(selector),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
}
// Element.closest
// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
if (window.Element && !Element.prototype.closest) {
Element.prototype.closest = function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i,
el = this;
do {
i = matches.length;
while (--i >= 0 && matches.item(i) !== el) {};
} while ((i < 0) && (el = el.parentElement));
return el;
};
}
function mixin(o, ps) {
if (!o) return;
Object.keys(ps).forEach(function(p) {
if ((p in o) || (p in o.prototype)) return;
try {
Object.defineProperty(
o.prototype,
p,
Object.getOwnPropertyDescriptor(ps, p)
);
} catch (ex) {
// Throws in IE8; just copy it
o[p] = ps[p];
}
});
}
// Mixin ParentNode
// https://dom.spec.whatwg.org/#interface-parentnode
function convertNodesIntoANode(nodes) {
var node = null;
nodes = nodes.map(function(node) {
return !(node instanceof Node) ? document.createTextNode(node) : node;
});
if (nodes.length === 1) {
node = nodes[0];
} else {
node = document.createDocumentFragment();
nodes.forEach(function(n) { node.appendChild(n); });
}
return node;
}
var ParentNode = {
prepend: function(/*...nodes*/) {
var nodes = [].slice.call(arguments);
nodes = convertNodesIntoANode(nodes);
this.insertBefore(nodes, this.firstChild);
},
append: function(/*...nodes*/) {
var nodes = [].slice.call(arguments);
nodes = convertNodesIntoANode(nodes);
this.appendChild(nodes);
}
};
mixin(global.Document || global.HTMLDocument, ParentNode); // HTMLDocument for IE8
mixin(global.DocumentFragment, ParentNode);
mixin(global.Element, ParentNode);
// Mixin ChildNode
// https://dom.spec.whatwg.org/#interface-childnode
var ChildNode = {
before: function(/*...nodes*/) {
var nodes = [].slice.call(arguments);
var parent = this.parentNode;
if (!parent) return;
var viablePreviousSibling = this.previousSibling;
while (nodes.indexOf(viablePreviousSibling) !== -1)
viablePreviousSibling = viablePreviousSibling.previousSibling;
var node = convertNodesIntoANode(nodes);
parent.insertBefore(node, viablePreviousSibling ?
viablePreviousSibling.nextSibling : parent.firstChild);
},
after: function(/*...nodes*/) {
var nodes = [].slice.call(arguments);
var parent = this.parentNode;
if (!parent) return;
var viableNextSibling = this.nextSibling;
while (nodes.indexOf(viableNextSibling) !== -1)
viableNextSibling = viableNextSibling.nextSibling;
var node = convertNodesIntoANode(nodes);
parent.insertBefore(node, viableNextSibling);
},
replaceWith: function(/*...nodes*/) {
var nodes = [].slice.call(arguments);
var parent = this.parentNode;
if (!parent) return;
var viableNextSibling = this.nextSibling;
while (nodes.indexOf(viableNextSibling) !== -1)
viableNextSibling = viableNextSibling.nextSibling;
var node = convertNodesIntoANode(nodes);
if (this.parentNode === parent)
parent.replaceChild(node, this);
else
parent.insertBefore(node, viableNextSibling);
},
remove: function() {
if (!this.parentNode) return;
this.parentNode.removeChild(this);
}
};
mixin(global.DocumentType, ChildNode);
mixin(global.Element, ChildNode);
mixin(global.CharacterData, ChildNode);
}(self));