264 lines
7.8 KiB
JavaScript
Executable File
264 lines
7.8 KiB
JavaScript
Executable File
/*
|
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree.
|
|
*/
|
|
/* eslint-env node */
|
|
'use strict';
|
|
|
|
let logDisabled_ = true;
|
|
let deprecationWarnings_ = true;
|
|
|
|
/**
|
|
* Extract browser version out of the provided user agent string.
|
|
*
|
|
* @param {!string} uastring userAgent string.
|
|
* @param {!string} expr Regular expression used as match criteria.
|
|
* @param {!number} pos position in the version string to be returned.
|
|
* @return {!number} browser version.
|
|
*/
|
|
export function extractVersion(uastring, expr, pos) {
|
|
const match = uastring.match(expr);
|
|
return match && match.length >= pos && parseInt(match[pos], 10);
|
|
}
|
|
|
|
// Wraps the peerconnection event eventNameToWrap in a function
|
|
// which returns the modified event object (or false to prevent
|
|
// the event).
|
|
export function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
|
|
if (!window.RTCPeerConnection) {
|
|
return;
|
|
}
|
|
const proto = window.RTCPeerConnection.prototype;
|
|
const nativeAddEventListener = proto.addEventListener;
|
|
proto.addEventListener = function(nativeEventName, cb) {
|
|
if (nativeEventName !== eventNameToWrap) {
|
|
return nativeAddEventListener.apply(this, arguments);
|
|
}
|
|
const wrappedCallback = (e) => {
|
|
const modifiedEvent = wrapper(e);
|
|
if (modifiedEvent) {
|
|
if (cb.handleEvent) {
|
|
cb.handleEvent(modifiedEvent);
|
|
} else {
|
|
cb(modifiedEvent);
|
|
}
|
|
}
|
|
};
|
|
this._eventMap = this._eventMap || {};
|
|
if (!this._eventMap[eventNameToWrap]) {
|
|
this._eventMap[eventNameToWrap] = new Map();
|
|
}
|
|
this._eventMap[eventNameToWrap].set(cb, wrappedCallback);
|
|
return nativeAddEventListener.apply(this, [nativeEventName,
|
|
wrappedCallback]);
|
|
};
|
|
|
|
const nativeRemoveEventListener = proto.removeEventListener;
|
|
proto.removeEventListener = function(nativeEventName, cb) {
|
|
if (nativeEventName !== eventNameToWrap || !this._eventMap
|
|
|| !this._eventMap[eventNameToWrap]) {
|
|
return nativeRemoveEventListener.apply(this, arguments);
|
|
}
|
|
if (!this._eventMap[eventNameToWrap].has(cb)) {
|
|
return nativeRemoveEventListener.apply(this, arguments);
|
|
}
|
|
const unwrappedCb = this._eventMap[eventNameToWrap].get(cb);
|
|
this._eventMap[eventNameToWrap].delete(cb);
|
|
if (this._eventMap[eventNameToWrap].size === 0) {
|
|
delete this._eventMap[eventNameToWrap];
|
|
}
|
|
if (Object.keys(this._eventMap).length === 0) {
|
|
delete this._eventMap;
|
|
}
|
|
return nativeRemoveEventListener.apply(this, [nativeEventName,
|
|
unwrappedCb]);
|
|
};
|
|
|
|
Object.defineProperty(proto, 'on' + eventNameToWrap, {
|
|
get() {
|
|
return this['_on' + eventNameToWrap];
|
|
},
|
|
set(cb) {
|
|
if (this['_on' + eventNameToWrap]) {
|
|
this.removeEventListener(eventNameToWrap,
|
|
this['_on' + eventNameToWrap]);
|
|
delete this['_on' + eventNameToWrap];
|
|
}
|
|
if (cb) {
|
|
this.addEventListener(eventNameToWrap,
|
|
this['_on' + eventNameToWrap] = cb);
|
|
}
|
|
},
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
export function disableLog(bool) {
|
|
if (typeof bool !== 'boolean') {
|
|
return new Error('Argument type: ' + typeof bool +
|
|
'. Please use a boolean.');
|
|
}
|
|
logDisabled_ = bool;
|
|
return (bool) ? 'adapter.js logging disabled' :
|
|
'adapter.js logging enabled';
|
|
}
|
|
|
|
/**
|
|
* Disable or enable deprecation warnings
|
|
* @param {!boolean} bool set to true to disable warnings.
|
|
*/
|
|
export function disableWarnings(bool) {
|
|
if (typeof bool !== 'boolean') {
|
|
return new Error('Argument type: ' + typeof bool +
|
|
'. Please use a boolean.');
|
|
}
|
|
deprecationWarnings_ = !bool;
|
|
return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
|
|
}
|
|
|
|
export function log() {
|
|
if (typeof window === 'object') {
|
|
if (logDisabled_) {
|
|
return;
|
|
}
|
|
if (typeof console !== 'undefined' && typeof console.log === 'function') {
|
|
console.log.apply(console, arguments);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows a deprecation warning suggesting the modern and spec-compatible API.
|
|
*/
|
|
export function deprecated(oldMethod, newMethod) {
|
|
if (!deprecationWarnings_) {
|
|
return;
|
|
}
|
|
console.warn(oldMethod + ' is deprecated, please use ' + newMethod +
|
|
' instead.');
|
|
}
|
|
|
|
/**
|
|
* Browser detector.
|
|
*
|
|
* @return {object} result containing browser and version
|
|
* properties.
|
|
*/
|
|
export function detectBrowser(window) {
|
|
// Returned result object.
|
|
const result = {browser: null, version: null};
|
|
|
|
// Fail early if it's not a browser
|
|
if (typeof window === 'undefined' || !window.navigator) {
|
|
result.browser = 'Not a browser.';
|
|
return result;
|
|
}
|
|
|
|
const {navigator} = window;
|
|
|
|
if (navigator.mozGetUserMedia) { // Firefox.
|
|
result.browser = 'firefox';
|
|
result.version = extractVersion(navigator.userAgent,
|
|
/Firefox\/(\d+)\./, 1);
|
|
} else if (navigator.webkitGetUserMedia ||
|
|
(window.isSecureContext === false && window.webkitRTCPeerConnection &&
|
|
!window.RTCIceGatherer)) {
|
|
// Chrome, Chromium, Webview, Opera.
|
|
// Version matches Chrome/WebRTC version.
|
|
// Chrome 74 removed webkitGetUserMedia on http as well so we need the
|
|
// more complicated fallback to webkitRTCPeerConnection.
|
|
result.browser = 'chrome';
|
|
result.version = extractVersion(navigator.userAgent,
|
|
/Chrom(e|ium)\/(\d+)\./, 2);
|
|
} else if (window.RTCPeerConnection &&
|
|
navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari.
|
|
result.browser = 'safari';
|
|
result.version = extractVersion(navigator.userAgent,
|
|
/AppleWebKit\/(\d+)\./, 1);
|
|
result.supportsUnifiedPlan = window.RTCRtpTransceiver &&
|
|
'currentDirection' in window.RTCRtpTransceiver.prototype;
|
|
} else { // Default fallthrough: not supported.
|
|
result.browser = 'Not a supported browser.';
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks if something is an object.
|
|
*
|
|
* @param {*} val The something you want to check.
|
|
* @return true if val is an object, false otherwise.
|
|
*/
|
|
function isObject(val) {
|
|
return Object.prototype.toString.call(val) === '[object Object]';
|
|
}
|
|
|
|
/**
|
|
* Remove all empty objects and undefined values
|
|
* from a nested object -- an enhanced and vanilla version
|
|
* of Lodash's `compact`.
|
|
*/
|
|
export function compactObject(data) {
|
|
if (!isObject(data)) {
|
|
return data;
|
|
}
|
|
|
|
return Object.keys(data).reduce(function(accumulator, key) {
|
|
const isObj = isObject(data[key]);
|
|
const value = isObj ? compactObject(data[key]) : data[key];
|
|
const isEmptyObject = isObj && !Object.keys(value).length;
|
|
if (value === undefined || isEmptyObject) {
|
|
return accumulator;
|
|
}
|
|
return Object.assign(accumulator, {[key]: value});
|
|
}, {});
|
|
}
|
|
|
|
/* iterates the stats graph recursively. */
|
|
export function walkStats(stats, base, resultSet) {
|
|
if (!base || resultSet.has(base.id)) {
|
|
return;
|
|
}
|
|
resultSet.set(base.id, base);
|
|
Object.keys(base).forEach(name => {
|
|
if (name.endsWith('Id')) {
|
|
walkStats(stats, stats.get(base[name]), resultSet);
|
|
} else if (name.endsWith('Ids')) {
|
|
base[name].forEach(id => {
|
|
walkStats(stats, stats.get(id), resultSet);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/* filter getStats for a sender/receiver track. */
|
|
export function filterStats(result, track, outbound) {
|
|
const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';
|
|
const filteredResult = new Map();
|
|
if (track === null) {
|
|
return filteredResult;
|
|
}
|
|
const trackStats = [];
|
|
result.forEach(value => {
|
|
if (value.type === 'track' &&
|
|
value.trackIdentifier === track.id) {
|
|
trackStats.push(value);
|
|
}
|
|
});
|
|
trackStats.forEach(trackStat => {
|
|
result.forEach(stats => {
|
|
if (stats.type === streamStatsType && stats.trackId === trackStat.id) {
|
|
walkStats(result, stats, filteredResult);
|
|
}
|
|
});
|
|
});
|
|
return filteredResult;
|
|
}
|
|
|