703 lines
25 KiB
JavaScript
703 lines
25 KiB
JavaScript
|
/*
|
||
|
* 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.js';
|
||
|
|
||
|
export {shimGetUserMedia} from './getusermedia';
|
||
|
export {shimGetDisplayMedia} from './getdisplaymedia';
|
||
|
|
||
|
export function shimMediaStream(window) {
|
||
|
window.MediaStream = window.MediaStream || window.webkitMediaStream;
|
||
|
}
|
||
|
|
||
|
export function shimOnTrack(window) {
|
||
|
if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
|
||
|
window.RTCPeerConnection.prototype)) {
|
||
|
Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
|
||
|
get() {
|
||
|
return this._ontrack;
|
||
|
},
|
||
|
set(f) {
|
||
|
if (this._ontrack) {
|
||
|
this.removeEventListener('track', this._ontrack);
|
||
|
}
|
||
|
this.addEventListener('track', this._ontrack = f);
|
||
|
},
|
||
|
enumerable: true,
|
||
|
configurable: true
|
||
|
});
|
||
|
const origSetRemoteDescription =
|
||
|
window.RTCPeerConnection.prototype.setRemoteDescription;
|
||
|
window.RTCPeerConnection.prototype.setRemoteDescription =
|
||
|
function setRemoteDescription() {
|
||
|
if (!this._ontrackpoly) {
|
||
|
this._ontrackpoly = (e) => {
|
||
|
// onaddstream does not fire when a track is added to an existing
|
||
|
// stream. But stream.onaddtrack is implemented so we use that.
|
||
|
e.stream.addEventListener('addtrack', te => {
|
||
|
let receiver;
|
||
|
if (window.RTCPeerConnection.prototype.getReceivers) {
|
||
|
receiver = this.getReceivers()
|
||
|
.find(r => r.track && r.track.id === te.track.id);
|
||
|
} else {
|
||
|
receiver = {track: te.track};
|
||
|
}
|
||
|
|
||
|
const event = new Event('track');
|
||
|
event.track = te.track;
|
||
|
event.receiver = receiver;
|
||
|
event.transceiver = {receiver};
|
||
|
event.streams = [e.stream];
|
||
|
this.dispatchEvent(event);
|
||
|
});
|
||
|
e.stream.getTracks().forEach(track => {
|
||
|
let receiver;
|
||
|
if (window.RTCPeerConnection.prototype.getReceivers) {
|
||
|
receiver = this.getReceivers()
|
||
|
.find(r => r.track && r.track.id === track.id);
|
||
|
} else {
|
||
|
receiver = {track};
|
||
|
}
|
||
|
const event = new Event('track');
|
||
|
event.track = track;
|
||
|
event.receiver = receiver;
|
||
|
event.transceiver = {receiver};
|
||
|
event.streams = [e.stream];
|
||
|
this.dispatchEvent(event);
|
||
|
});
|
||
|
};
|
||
|
this.addEventListener('addstream', this._ontrackpoly);
|
||
|
}
|
||
|
return origSetRemoteDescription.apply(this, arguments);
|
||
|
};
|
||
|
} else {
|
||
|
// even if RTCRtpTransceiver is in window, it is only used and
|
||
|
// emitted in unified-plan. Unfortunately this means we need
|
||
|
// to unconditionally wrap the event.
|
||
|
utils.wrapPeerConnectionEvent(window, 'track', e => {
|
||
|
if (!e.transceiver) {
|
||
|
Object.defineProperty(e, 'transceiver',
|
||
|
{value: {receiver: e.receiver}});
|
||
|
}
|
||
|
return e;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export function shimGetSendersWithDtmf(window) {
|
||
|
// Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack.
|
||
|
if (typeof window === 'object' && window.RTCPeerConnection &&
|
||
|
!('getSenders' in window.RTCPeerConnection.prototype) &&
|
||
|
'createDTMFSender' in window.RTCPeerConnection.prototype) {
|
||
|
const shimSenderWithDtmf = function(pc, track) {
|
||
|
return {
|
||
|
track,
|
||
|
get dtmf() {
|
||
|
if (this._dtmf === undefined) {
|
||
|
if (track.kind === 'audio') {
|
||
|
this._dtmf = pc.createDTMFSender(track);
|
||
|
} else {
|
||
|
this._dtmf = null;
|
||
|
}
|
||
|
}
|
||
|
return this._dtmf;
|
||
|
},
|
||
|
_pc: pc
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// augment addTrack when getSenders is not available.
|
||
|
if (!window.RTCPeerConnection.prototype.getSenders) {
|
||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() {
|
||
|
this._senders = this._senders || [];
|
||
|
return this._senders.slice(); // return a copy of the internal state.
|
||
|
};
|
||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack;
|
||
|
window.RTCPeerConnection.prototype.addTrack =
|
||
|
function addTrack(track, stream) {
|
||
|
let sender = origAddTrack.apply(this, arguments);
|
||
|
if (!sender) {
|
||
|
sender = shimSenderWithDtmf(this, track);
|
||
|
this._senders.push(sender);
|
||
|
}
|
||
|
return sender;
|
||
|
};
|
||
|
|
||
|
const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
|
||
|
window.RTCPeerConnection.prototype.removeTrack =
|
||
|
function removeTrack(sender) {
|
||
|
origRemoveTrack.apply(this, arguments);
|
||
|
const idx = this._senders.indexOf(sender);
|
||
|
if (idx !== -1) {
|
||
|
this._senders.splice(idx, 1);
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream;
|
||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
|
||
|
this._senders = this._senders || [];
|
||
|
origAddStream.apply(this, [stream]);
|
||
|
stream.getTracks().forEach(track => {
|
||
|
this._senders.push(shimSenderWithDtmf(this, track));
|
||
|
});
|
||
|
};
|
||
|
|
||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
|
||
|
window.RTCPeerConnection.prototype.removeStream =
|
||
|
function removeStream(stream) {
|
||
|
this._senders = this._senders || [];
|
||
|
origRemoveStream.apply(this, [stream]);
|
||
|
|
||
|
stream.getTracks().forEach(track => {
|
||
|
const sender = this._senders.find(s => s.track === track);
|
||
|
if (sender) { // remove sender
|
||
|
this._senders.splice(this._senders.indexOf(sender), 1);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
} else if (typeof window === 'object' && window.RTCPeerConnection &&
|
||
|
'getSenders' in window.RTCPeerConnection.prototype &&
|
||
|
'createDTMFSender' in window.RTCPeerConnection.prototype &&
|
||
|
window.RTCRtpSender &&
|
||
|
!('dtmf' in window.RTCRtpSender.prototype)) {
|
||
|
const origGetSenders = window.RTCPeerConnection.prototype.getSenders;
|
||
|
window.RTCPeerConnection.prototype.getSenders = function getSenders() {
|
||
|
const senders = origGetSenders.apply(this, []);
|
||
|
senders.forEach(sender => sender._pc = this);
|
||
|
return senders;
|
||
|
};
|
||
|
|
||
|
Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {
|
||
|
get() {
|
||
|
if (this._dtmf === undefined) {
|
||
|
if (this.track.kind === 'audio') {
|
||
|
this._dtmf = this._pc.createDTMFSender(this.track);
|
||
|
} else {
|
||
|
this._dtmf = null;
|
||
|
}
|
||
|
}
|
||
|
return this._dtmf;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export function shimGetStats(window) {
|
||
|
if (!window.RTCPeerConnection) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const origGetStats = window.RTCPeerConnection.prototype.getStats;
|
||
|
window.RTCPeerConnection.prototype.getStats = function getStats() {
|
||
|
const [selector, onSucc, onErr] = arguments;
|
||
|
|
||
|
// If selector is a function then we are in the old style stats so just
|
||
|
// pass back the original getStats format to avoid breaking old users.
|
||
|
if (arguments.length > 0 && typeof selector === 'function') {
|
||
|
return origGetStats.apply(this, arguments);
|
||
|
}
|
||
|
|
||
|
// When spec-style getStats is supported, return those when called with
|
||
|
// either no arguments or the selector argument is null.
|
||
|
if (origGetStats.length === 0 && (arguments.length === 0 ||
|
||
|
typeof selector !== 'function')) {
|
||
|
return origGetStats.apply(this, []);
|
||
|
}
|
||
|
|
||
|
const fixChromeStats_ = function(response) {
|
||
|
const standardReport = {};
|
||
|
const reports = response.result();
|
||
|
reports.forEach(report => {
|
||
|
const standardStats = {
|
||
|
id: report.id,
|
||
|
timestamp: report.timestamp,
|
||
|
type: {
|
||
|
localcandidate: 'local-candidate',
|
||
|
remotecandidate: 'remote-candidate'
|
||
|
}[report.type] || report.type
|
||
|
};
|
||
|
report.names().forEach(name => {
|
||
|
standardStats[name] = report.stat(name);
|
||
|
});
|
||
|
standardReport[standardStats.id] = standardStats;
|
||
|
});
|
||
|
|
||
|
return standardReport;
|
||
|
};
|
||
|
|
||
|
// shim getStats with maplike support
|
||
|
const makeMapStats = function(stats) {
|
||
|
return new Map(Object.keys(stats).map(key => [key, stats[key]]));
|
||
|
};
|
||
|
|
||
|
if (arguments.length >= 2) {
|
||
|
const successCallbackWrapper_ = function(response) {
|
||
|
onSucc(makeMapStats(fixChromeStats_(response)));
|
||
|
};
|
||
|
|
||
|
return origGetStats.apply(this, [successCallbackWrapper_,
|
||
|
selector]);
|
||
|
}
|
||
|
|
||
|
// promise-support
|
||
|
return new Promise((resolve, reject) => {
|
||
|
origGetStats.apply(this, [
|
||
|
function(response) {
|
||
|
resolve(makeMapStats(fixChromeStats_(response)));
|
||
|
}, reject]);
|
||
|
}).then(onSucc, onErr);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function shimSenderReceiverGetStats(window) {
|
||
|
if (!(typeof window === 'object' && window.RTCPeerConnection &&
|
||
|
window.RTCRtpSender && window.RTCRtpReceiver)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// shim sender stats.
|
||
|
if (!('getStats' in window.RTCRtpSender.prototype)) {
|
||
|
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() {
|
||
|
const sender = this;
|
||
|
return this._pc.getStats().then(result =>
|
||
|
/* Note: this will include stats of all senders that
|
||
|
* send a track with the same id as sender.track as
|
||
|
* it is not possible to identify the RTCRtpSender.
|
||
|
*/
|
||
|
utils.filterStats(result, sender.track, true));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// shim receiver stats.
|
||
|
if (!('getStats' in window.RTCRtpReceiver.prototype)) {
|
||
|
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() {
|
||
|
const receiver = this;
|
||
|
return this._pc.getStats().then(result =>
|
||
|
utils.filterStats(result, receiver.track, false));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (!('getStats' in window.RTCRtpSender.prototype &&
|
||
|
'getStats' in window.RTCRtpReceiver.prototype)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// shim RTCPeerConnection.getStats(track).
|
||
|
const origGetStats = window.RTCPeerConnection.prototype.getStats;
|
||
|
window.RTCPeerConnection.prototype.getStats = function getStats() {
|
||
|
if (arguments.length > 0 &&
|
||
|
arguments[0] instanceof window.MediaStreamTrack) {
|
||
|
const track = arguments[0];
|
||
|
let sender;
|
||
|
let receiver;
|
||
|
let err;
|
||
|
this.getSenders().forEach(s => {
|
||
|
if (s.track === track) {
|
||
|
if (sender) {
|
||
|
err = true;
|
||
|
} else {
|
||
|
sender = s;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
this.getReceivers().forEach(r => {
|
||
|
if (r.track === track) {
|
||
|
if (receiver) {
|
||
|
err = true;
|
||
|
} else {
|
||
|
receiver = r;
|
||
|
}
|
||
|
}
|
||
|
return r.track === track;
|
||
|
});
|
||
|
if (err || (sender && receiver)) {
|
||
|
return Promise.reject(new DOMException(
|
||
|
'There are more than one sender or receiver for the track.',
|
||
|
'InvalidAccessError'));
|
||
|
} else if (sender) {
|
||
|
return sender.getStats();
|
||
|
} else if (receiver) {
|
||
|
return receiver.getStats();
|
||
|
}
|
||
|
return Promise.reject(new DOMException(
|
||
|
'There is no sender or receiver for the track.',
|
||
|
'InvalidAccessError'));
|
||
|
}
|
||
|
return origGetStats.apply(this, arguments);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function shimAddTrackRemoveTrackWithNative(window) {
|
||
|
// shim addTrack/removeTrack with native variants in order to make
|
||
|
// the interactions with legacy getLocalStreams behave as in other browsers.
|
||
|
// Keeps a mapping stream.id => [stream, rtpsenders...]
|
||
|
window.RTCPeerConnection.prototype.getLocalStreams =
|
||
|
function getLocalStreams() {
|
||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {};
|
||
|
return Object.keys(this._shimmedLocalStreams)
|
||
|
.map(streamId => this._shimmedLocalStreams[streamId][0]);
|
||
|
};
|
||
|
|
||
|
const origAddTrack = window.RTCPeerConnection.prototype.addTrack;
|
||
|
window.RTCPeerConnection.prototype.addTrack =
|
||
|
function addTrack(track, stream) {
|
||
|
if (!stream) {
|
||
|
return origAddTrack.apply(this, arguments);
|
||
|
}
|
||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {};
|
||
|
|
||
|
const sender = origAddTrack.apply(this, arguments);
|
||
|
if (!this._shimmedLocalStreams[stream.id]) {
|
||
|
this._shimmedLocalStreams[stream.id] = [stream, sender];
|
||
|
} else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {
|
||
|
this._shimmedLocalStreams[stream.id].push(sender);
|
||
|
}
|
||
|
return sender;
|
||
|
};
|
||
|
|
||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream;
|
||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
|
||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {};
|
||
|
|
||
|
stream.getTracks().forEach(track => {
|
||
|
const alreadyExists = this.getSenders().find(s => s.track === track);
|
||
|
if (alreadyExists) {
|
||
|
throw new DOMException('Track already exists.',
|
||
|
'InvalidAccessError');
|
||
|
}
|
||
|
});
|
||
|
const existingSenders = this.getSenders();
|
||
|
origAddStream.apply(this, arguments);
|
||
|
const newSenders = this.getSenders()
|
||
|
.filter(newSender => existingSenders.indexOf(newSender) === -1);
|
||
|
this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);
|
||
|
};
|
||
|
|
||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
|
||
|
window.RTCPeerConnection.prototype.removeStream =
|
||
|
function removeStream(stream) {
|
||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {};
|
||
|
delete this._shimmedLocalStreams[stream.id];
|
||
|
return origRemoveStream.apply(this, arguments);
|
||
|
};
|
||
|
|
||
|
const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;
|
||
|
window.RTCPeerConnection.prototype.removeTrack =
|
||
|
function removeTrack(sender) {
|
||
|
this._shimmedLocalStreams = this._shimmedLocalStreams || {};
|
||
|
if (sender) {
|
||
|
Object.keys(this._shimmedLocalStreams).forEach(streamId => {
|
||
|
const idx = this._shimmedLocalStreams[streamId].indexOf(sender);
|
||
|
if (idx !== -1) {
|
||
|
this._shimmedLocalStreams[streamId].splice(idx, 1);
|
||
|
}
|
||
|
if (this._shimmedLocalStreams[streamId].length === 1) {
|
||
|
delete this._shimmedLocalStreams[streamId];
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return origRemoveTrack.apply(this, arguments);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function shimAddTrackRemoveTrack(window, browserDetails) {
|
||
|
if (!window.RTCPeerConnection) {
|
||
|
return;
|
||
|
}
|
||
|
// shim addTrack and removeTrack.
|
||
|
if (window.RTCPeerConnection.prototype.addTrack &&
|
||
|
browserDetails.version >= 65) {
|
||
|
return shimAddTrackRemoveTrackWithNative(window);
|
||
|
}
|
||
|
|
||
|
// also shim pc.getLocalStreams when addTrack is shimmed
|
||
|
// to return the original streams.
|
||
|
const origGetLocalStreams = window.RTCPeerConnection.prototype
|
||
|
.getLocalStreams;
|
||
|
window.RTCPeerConnection.prototype.getLocalStreams =
|
||
|
function getLocalStreams() {
|
||
|
const nativeStreams = origGetLocalStreams.apply(this);
|
||
|
this._reverseStreams = this._reverseStreams || {};
|
||
|
return nativeStreams.map(stream => this._reverseStreams[stream.id]);
|
||
|
};
|
||
|
|
||
|
const origAddStream = window.RTCPeerConnection.prototype.addStream;
|
||
|
window.RTCPeerConnection.prototype.addStream = function addStream(stream) {
|
||
|
this._streams = this._streams || {};
|
||
|
this._reverseStreams = this._reverseStreams || {};
|
||
|
|
||
|
stream.getTracks().forEach(track => {
|
||
|
const alreadyExists = this.getSenders().find(s => s.track === track);
|
||
|
if (alreadyExists) {
|
||
|
throw new DOMException('Track already exists.',
|
||
|
'InvalidAccessError');
|
||
|
}
|
||
|
});
|
||
|
// Add identity mapping for consistency with addTrack.
|
||
|
// Unless this is being used with a stream from addTrack.
|
||
|
if (!this._reverseStreams[stream.id]) {
|
||
|
const newStream = new window.MediaStream(stream.getTracks());
|
||
|
this._streams[stream.id] = newStream;
|
||
|
this._reverseStreams[newStream.id] = stream;
|
||
|
stream = newStream;
|
||
|
}
|
||
|
origAddStream.apply(this, [stream]);
|
||
|
};
|
||
|
|
||
|
const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;
|
||
|
window.RTCPeerConnection.prototype.removeStream =
|
||
|
function removeStream(stream) {
|
||
|
this._streams = this._streams || {};
|
||
|
this._reverseStreams = this._reverseStreams || {};
|
||
|
|
||
|
origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]);
|
||
|
delete this._reverseStreams[(this._streams[stream.id] ?
|
||
|
this._streams[stream.id].id : stream.id)];
|
||
|
delete this._streams[stream.id];
|
||
|
};
|
||
|
|
||
|
window.RTCPeerConnection.prototype.addTrack =
|
||
|
function addTrack(track, stream) {
|
||
|
if (this.signalingState === 'closed') {
|
||
|
throw new DOMException(
|
||
|
'The RTCPeerConnection\'s signalingState is \'closed\'.',
|
||
|
'InvalidStateError');
|
||
|
}
|
||
|
const streams = [].slice.call(arguments, 1);
|
||
|
if (streams.length !== 1 ||
|
||
|
!streams[0].getTracks().find(t => t === track)) {
|
||
|
// this is not fully correct but all we can manage without
|
||
|
// [[associated MediaStreams]] internal slot.
|
||
|
throw new DOMException(
|
||
|
'The adapter.js addTrack polyfill only supports a single ' +
|
||
|
' stream which is associated with the specified track.',
|
||
|
'NotSupportedError');
|
||
|
}
|
||
|
|
||
|
const alreadyExists = this.getSenders().find(s => s.track === track);
|
||
|
if (alreadyExists) {
|
||
|
throw new DOMException('Track already exists.',
|
||
|
'InvalidAccessError');
|
||
|
}
|
||
|
|
||
|
this._streams = this._streams || {};
|
||
|
this._reverseStreams = this._reverseStreams || {};
|
||
|
const oldStream = this._streams[stream.id];
|
||
|
if (oldStream) {
|
||
|
// this is using odd Chrome behaviour, use with caution:
|
||
|
// https://bugs.chromium.org/p/webrtc/issues/detail?id=7815
|
||
|
// Note: we rely on the high-level addTrack/dtmf shim to
|
||
|
// create the sender with a dtmf sender.
|
||
|
oldStream.addTrack(track);
|
||
|
|
||
|
// Trigger ONN async.
|
||
|
Promise.resolve().then(() => {
|
||
|
this.dispatchEvent(new Event('negotiationneeded'));
|
||
|
});
|
||
|
} else {
|
||
|
const newStream = new window.MediaStream([track]);
|
||
|
this._streams[stream.id] = newStream;
|
||
|
this._reverseStreams[newStream.id] = stream;
|
||
|
this.addStream(newStream);
|
||
|
}
|
||
|
return this.getSenders().find(s => s.track === track);
|
||
|
};
|
||
|
|
||
|
// replace the internal stream id with the external one and
|
||
|
// vice versa.
|
||
|
function replaceInternalStreamId(pc, description) {
|
||
|
let sdp = description.sdp;
|
||
|
Object.keys(pc._reverseStreams || []).forEach(internalId => {
|
||
|
const externalStream = pc._reverseStreams[internalId];
|
||
|
const internalStream = pc._streams[externalStream.id];
|
||
|
sdp = sdp.replace(new RegExp(internalStream.id, 'g'),
|
||
|
externalStream.id);
|
||
|
});
|
||
|
return new RTCSessionDescription({
|
||
|
type: description.type,
|
||
|
sdp
|
||
|
});
|
||
|
}
|
||
|
function replaceExternalStreamId(pc, description) {
|
||
|
let sdp = description.sdp;
|
||
|
Object.keys(pc._reverseStreams || []).forEach(internalId => {
|
||
|
const externalStream = pc._reverseStreams[internalId];
|
||
|
const internalStream = pc._streams[externalStream.id];
|
||
|
sdp = sdp.replace(new RegExp(externalStream.id, 'g'),
|
||
|
internalStream.id);
|
||
|
});
|
||
|
return new RTCSessionDescription({
|
||
|
type: description.type,
|
||
|
sdp
|
||
|
});
|
||
|
}
|
||
|
['createOffer', 'createAnswer'].forEach(function(method) {
|
||
|
const nativeMethod = window.RTCPeerConnection.prototype[method];
|
||
|
const methodObj = {[method]() {
|
||
|
const args = arguments;
|
||
|
const isLegacyCall = arguments.length &&
|
||
|
typeof arguments[0] === 'function';
|
||
|
if (isLegacyCall) {
|
||
|
return nativeMethod.apply(this, [
|
||
|
(description) => {
|
||
|
const desc = replaceInternalStreamId(this, description);
|
||
|
args[0].apply(null, [desc]);
|
||
|
},
|
||
|
(err) => {
|
||
|
if (args[1]) {
|
||
|
args[1].apply(null, err);
|
||
|
}
|
||
|
}, arguments[2]
|
||
|
]);
|
||
|
}
|
||
|
return nativeMethod.apply(this, arguments)
|
||
|
.then(description => replaceInternalStreamId(this, description));
|
||
|
}};
|
||
|
window.RTCPeerConnection.prototype[method] = methodObj[method];
|
||
|
});
|
||
|
|
||
|
const origSetLocalDescription =
|
||
|
window.RTCPeerConnection.prototype.setLocalDescription;
|
||
|
window.RTCPeerConnection.prototype.setLocalDescription =
|
||
|
function setLocalDescription() {
|
||
|
if (!arguments.length || !arguments[0].type) {
|
||
|
return origSetLocalDescription.apply(this, arguments);
|
||
|
}
|
||
|
arguments[0] = replaceExternalStreamId(this, arguments[0]);
|
||
|
return origSetLocalDescription.apply(this, arguments);
|
||
|
};
|
||
|
|
||
|
// TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier
|
||
|
|
||
|
const origLocalDescription = Object.getOwnPropertyDescriptor(
|
||
|
window.RTCPeerConnection.prototype, 'localDescription');
|
||
|
Object.defineProperty(window.RTCPeerConnection.prototype,
|
||
|
'localDescription', {
|
||
|
get() {
|
||
|
const description = origLocalDescription.get.apply(this);
|
||
|
if (description.type === '') {
|
||
|
return description;
|
||
|
}
|
||
|
return replaceInternalStreamId(this, description);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
window.RTCPeerConnection.prototype.removeTrack =
|
||
|
function removeTrack(sender) {
|
||
|
if (this.signalingState === 'closed') {
|
||
|
throw new DOMException(
|
||
|
'The RTCPeerConnection\'s signalingState is \'closed\'.',
|
||
|
'InvalidStateError');
|
||
|
}
|
||
|
// We can not yet check for sender instanceof RTCRtpSender
|
||
|
// since we shim RTPSender. So we check if sender._pc is set.
|
||
|
if (!sender._pc) {
|
||
|
throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +
|
||
|
'does not implement interface RTCRtpSender.', 'TypeError');
|
||
|
}
|
||
|
const isLocal = sender._pc === this;
|
||
|
if (!isLocal) {
|
||
|
throw new DOMException('Sender was not created by this connection.',
|
||
|
'InvalidAccessError');
|
||
|
}
|
||
|
|
||
|
// Search for the native stream the senders track belongs to.
|
||
|
this._streams = this._streams || {};
|
||
|
let stream;
|
||
|
Object.keys(this._streams).forEach(streamid => {
|
||
|
const hasTrack = this._streams[streamid].getTracks()
|
||
|
.find(track => sender.track === track);
|
||
|
if (hasTrack) {
|
||
|
stream = this._streams[streamid];
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (stream) {
|
||
|
if (stream.getTracks().length === 1) {
|
||
|
// if this is the last track of the stream, remove the stream. This
|
||
|
// takes care of any shimmed _senders.
|
||
|
this.removeStream(this._reverseStreams[stream.id]);
|
||
|
} else {
|
||
|
// relying on the same odd chrome behaviour as above.
|
||
|
stream.removeTrack(sender.track);
|
||
|
}
|
||
|
this.dispatchEvent(new Event('negotiationneeded'));
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function shimPeerConnection(window, browserDetails) {
|
||
|
if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {
|
||
|
// very basic support for old versions.
|
||
|
window.RTCPeerConnection = window.webkitRTCPeerConnection;
|
||
|
}
|
||
|
if (!window.RTCPeerConnection) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// shim implicit creation of RTCSessionDescription/RTCIceCandidate
|
||
|
if (browserDetails.version < 53) {
|
||
|
['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];
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Attempt to fix ONN in plan-b mode.
|
||
|
export function fixNegotiationNeeded(window, browserDetails) {
|
||
|
utils.wrapPeerConnectionEvent(window, 'negotiationneeded', e => {
|
||
|
const pc = e.target;
|
||
|
if (browserDetails.version < 72 || (pc.getConfiguration &&
|
||
|
pc.getConfiguration().sdpSemantics === 'plan-b')) {
|
||
|
if (pc.signalingState !== 'stable') {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
return e;
|
||
|
});
|
||
|
}
|