297 lines
11 KiB
JavaScript
Executable File
297 lines
11 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';
|
|
|
|
import * as utils from '../utils';
|
|
export {shimGetUserMedia} from './getusermedia';
|
|
export {shimGetDisplayMedia} from './getdisplaymedia';
|
|
|
|
export function shimOnTrack(window) {
|
|
if (typeof window === 'object' && window.RTCTrackEvent &&
|
|
('receiver' in window.RTCTrackEvent.prototype) &&
|
|
!('transceiver' in window.RTCTrackEvent.prototype)) {
|
|
Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {
|
|
get() {
|
|
return {receiver: this.receiver};
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export function shimPeerConnection(window, browserDetails) {
|
|
if (typeof window !== 'object' ||
|
|
!(window.RTCPeerConnection || window.mozRTCPeerConnection)) {
|
|
return; // probably media.peerconnection.enabled=false in about:config
|
|
}
|
|
if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {
|
|
// very basic support for old versions.
|
|
window.RTCPeerConnection = window.mozRTCPeerConnection;
|
|
}
|
|
|
|
if (browserDetails.version < 53) {
|
|
// shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
|
|
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
|
|
.forEach(function(method) {
|
|
const nativeMethod = window.RTCPeerConnection.prototype[method];
|
|
const methodObj = {[method]() {
|
|
arguments[0] = new ((method === 'addIceCandidate') ?
|
|
window.RTCIceCandidate :
|
|
window.RTCSessionDescription)(arguments[0]);
|
|
return nativeMethod.apply(this, arguments);
|
|
}};
|
|
window.RTCPeerConnection.prototype[method] = methodObj[method];
|
|
});
|
|
}
|
|
|
|
const modernStatsTypes = {
|
|
inboundrtp: 'inbound-rtp',
|
|
outboundrtp: 'outbound-rtp',
|
|
candidatepair: 'candidate-pair',
|
|
localcandidate: 'local-candidate',
|
|
remotecandidate: 'remote-candidate'
|
|
};
|
|
|
|
const nativeGetStats = window.RTCPeerConnection.prototype.getStats;
|
|
window.RTCPeerConnection.prototype.getStats = function getStats() {
|
|
const [selector, onSucc, onErr] = arguments;
|
|
return nativeGetStats.apply(this, [selector || null])
|
|
.then(stats => {
|
|
if (browserDetails.version < 53 && !onSucc) {
|
|
// Shim only promise getStats with spec-hyphens in type names
|
|
// Leave callback version alone; misc old uses of forEach before Map
|
|
try {
|
|
stats.forEach(stat => {
|
|
stat.type = modernStatsTypes[stat.type] || stat.type;
|
|
});
|
|
} catch (e) {
|
|
if (e.name !== 'TypeError') {
|
|
throw e;
|
|
}
|
|
// Avoid TypeError: "type" is read-only, in old versions. 34-43ish
|
|
stats.forEach((stat, i) => {
|
|
stats.set(i, Object.assign({}, stat, {
|
|
type: modernStatsTypes[stat.type] || stat.type
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
return stats;
|
|
})
|
|
.then(onSucc, onErr);
|
|
};
|
|
}
|
|
|
|
export function shimSenderGetStats(window) {
|
|
if (!(typeof window === 'object' && window.RTCPeerConnection &&
|
|
window.RTCRtpSender)) {
|
|
return;
|
|
}
|
|
if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {
|
|
return;
|
|
}
|
|
const origGetSenders = window.RTCPeerConnection.prototype.getSenders;
|
|
if (origGetSenders) {
|
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() {
|
|
const senders = origGetSenders.apply(this, []);
|
|
senders.forEach(sender => sender._pc = this);
|
|
return senders;
|
|
};
|
|
}
|
|
|
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack;
|
|
if (origAddTrack) {
|
|
window.RTCPeerConnection.prototype.addTrack = function addTrack() {
|
|
const sender = origAddTrack.apply(this, arguments);
|
|
sender._pc = this;
|
|
return sender;
|
|
};
|
|
}
|
|
window.RTCRtpSender.prototype.getStats = function getStats() {
|
|
return this.track ? this._pc.getStats(this.track) :
|
|
Promise.resolve(new Map());
|
|
};
|
|
}
|
|
|
|
export function shimReceiverGetStats(window) {
|
|
if (!(typeof window === 'object' && window.RTCPeerConnection &&
|
|
window.RTCRtpSender)) {
|
|
return;
|
|
}
|
|
if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {
|
|
return;
|
|
}
|
|
const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;
|
|
if (origGetReceivers) {
|
|
window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {
|
|
const receivers = origGetReceivers.apply(this, []);
|
|
receivers.forEach(receiver => receiver._pc = this);
|
|
return receivers;
|
|
};
|
|
}
|
|
utils.wrapPeerConnectionEvent(window, 'track', e => {
|
|
e.receiver._pc = e.srcElement;
|
|
return e;
|
|
});
|
|
window.RTCRtpReceiver.prototype.getStats = function getStats() {
|
|
return this._pc.getStats(this.track);
|
|
};
|
|
}
|
|
|
|
export function shimRemoveStream(window) {
|
|
if (!window.RTCPeerConnection ||
|
|
'removeStream' in window.RTCPeerConnection.prototype) {
|
|
return;
|
|
}
|
|
window.RTCPeerConnection.prototype.removeStream =
|
|
function removeStream(stream) {
|
|
utils.deprecated('removeStream', 'removeTrack');
|
|
this.getSenders().forEach(sender => {
|
|
if (sender.track && stream.getTracks().includes(sender.track)) {
|
|
this.removeTrack(sender);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
export function shimRTCDataChannel(window) {
|
|
// rename DataChannel to RTCDataChannel (native fix in FF60):
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1173851
|
|
if (window.DataChannel && !window.RTCDataChannel) {
|
|
window.RTCDataChannel = window.DataChannel;
|
|
}
|
|
}
|
|
|
|
export function shimAddTransceiver(window) {
|
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
return;
|
|
}
|
|
const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;
|
|
if (origAddTransceiver) {
|
|
window.RTCPeerConnection.prototype.addTransceiver =
|
|
function addTransceiver() {
|
|
this.setParametersPromises = [];
|
|
const initParameters = arguments[1];
|
|
const shouldPerformCheck = initParameters &&
|
|
'sendEncodings' in initParameters;
|
|
if (shouldPerformCheck) {
|
|
// If sendEncodings params are provided, validate grammar
|
|
initParameters.sendEncodings.forEach((encodingParam) => {
|
|
if ('rid' in encodingParam) {
|
|
const ridRegex = /^[a-z0-9]{0,16}$/i;
|
|
if (!ridRegex.test(encodingParam.rid)) {
|
|
throw new TypeError('Invalid RID value provided.');
|
|
}
|
|
}
|
|
if ('scaleResolutionDownBy' in encodingParam) {
|
|
if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) {
|
|
throw new RangeError('scale_resolution_down_by must be >= 1.0');
|
|
}
|
|
}
|
|
if ('maxFramerate' in encodingParam) {
|
|
if (!(parseFloat(encodingParam.maxFramerate) >= 0)) {
|
|
throw new RangeError('max_framerate must be >= 0.0');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
const transceiver = origAddTransceiver.apply(this, arguments);
|
|
if (shouldPerformCheck) {
|
|
// Check if the init options were applied. If not we do this in an
|
|
// asynchronous way and save the promise reference in a global object.
|
|
// This is an ugly hack, but at the same time is way more robust than
|
|
// checking the sender parameters before and after the createOffer
|
|
// Also note that after the createoffer we are not 100% sure that
|
|
// the params were asynchronously applied so we might miss the
|
|
// opportunity to recreate offer.
|
|
const {sender} = transceiver;
|
|
const params = sender.getParameters();
|
|
if (!('encodings' in params) ||
|
|
// Avoid being fooled by patched getParameters() below.
|
|
(params.encodings.length === 1 &&
|
|
Object.keys(params.encodings[0]).length === 0)) {
|
|
params.encodings = initParameters.sendEncodings;
|
|
sender.sendEncodings = initParameters.sendEncodings;
|
|
this.setParametersPromises.push(sender.setParameters(params)
|
|
.then(() => {
|
|
delete sender.sendEncodings;
|
|
}).catch(() => {
|
|
delete sender.sendEncodings;
|
|
})
|
|
);
|
|
}
|
|
}
|
|
return transceiver;
|
|
};
|
|
}
|
|
}
|
|
|
|
export function shimGetParameters(window) {
|
|
if (!(typeof window === 'object' && window.RTCRtpSender)) {
|
|
return;
|
|
}
|
|
const origGetParameters = window.RTCRtpSender.prototype.getParameters;
|
|
if (origGetParameters) {
|
|
window.RTCRtpSender.prototype.getParameters =
|
|
function getParameters() {
|
|
const params = origGetParameters.apply(this, arguments);
|
|
if (!('encodings' in params)) {
|
|
params.encodings = [].concat(this.sendEncodings || [{}]);
|
|
}
|
|
return params;
|
|
};
|
|
}
|
|
}
|
|
|
|
export function shimCreateOffer(window) {
|
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
return;
|
|
}
|
|
const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;
|
|
window.RTCPeerConnection.prototype.createOffer = function createOffer() {
|
|
if (this.setParametersPromises && this.setParametersPromises.length) {
|
|
return Promise.all(this.setParametersPromises)
|
|
.then(() => {
|
|
return origCreateOffer.apply(this, arguments);
|
|
})
|
|
.finally(() => {
|
|
this.setParametersPromises = [];
|
|
});
|
|
}
|
|
return origCreateOffer.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
export function shimCreateAnswer(window) {
|
|
// https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647
|
|
// Firefox ignores the init sendEncodings options passed to addTransceiver
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
|
if (!(typeof window === 'object' && window.RTCPeerConnection)) {
|
|
return;
|
|
}
|
|
const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;
|
|
window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {
|
|
if (this.setParametersPromises && this.setParametersPromises.length) {
|
|
return Promise.all(this.setParametersPromises)
|
|
.then(() => {
|
|
return origCreateAnswer.apply(this, arguments);
|
|
})
|
|
.finally(() => {
|
|
this.setParametersPromises = [];
|
|
});
|
|
}
|
|
return origCreateAnswer.apply(this, arguments);
|
|
};
|
|
}
|