@twilio/webrtc
Advanced tools
Comparing version 2.1.4 to 2.2.0
@@ -0,1 +1,21 @@ | ||
2.2.0 (January 10, 2019) | ||
======================== | ||
New Features | ||
------------ | ||
- `getStats` on Firefox will now consume the spec-compliant `RTCIceCandidateStats` | ||
available in [versions 65 and above](https://www.fxsitecompat.com/en-CA/docs/2018/rtcicecandidatestats-has-been-updated-to-the-latest-spec/). (JSDK-2235) | ||
- Added support for Unified Plan SDPs on Safari 12.1. (JSDK-2231) | ||
- Removed workaround for this [Safari bug](https://bugs.webkit.org/show_bug.cgi?id=174323). | ||
- Removed workaround for this Chrome [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=774303). | ||
Now, we no longer suppress the RTCPeerConnection's native `RTCTrackEvent`. | ||
- Worked around the [deprecation](https://blog.mozilla.org/webrtc/getstats-isremote-65/) of the | ||
`isRemote` property in `RTCInboundRTPStreamStats` and `RTCOutboundRTPStreamStats` in Firefox. | ||
Bug Fixes | ||
--------- | ||
- Worked around Chrome [Bug 894231](https://bugs.chromium.org/p/chromium/issues/detail?id=894231). | ||
2.1.4 (December 5, 2018) | ||
@@ -2,0 +22,0 @@ ========================= |
@@ -1,6 +0,12 @@ | ||
/* global webkitRTCPeerConnection, mozRTCPeerConnection */ | ||
/* global RTCRtpTransceiver */ | ||
'use strict'; | ||
var flatMap = require('./util').flatMap; | ||
var guessBrowser = require('./util').guessBrowser; | ||
var guess = guessBrowser(); | ||
var isChrome = guess === 'chrome'; | ||
var isFirefox = guess === 'firefox'; | ||
var isSafari = guess === 'safari'; | ||
/** | ||
@@ -40,11 +46,14 @@ * Get the standardized {@link RTCPeerConnection} statistics. | ||
var trackStatsPromises = flatMap([ | ||
[localAudioTracks, 'localAudioTrackStats'], | ||
[localVideoTracks, 'localVideoTrackStats'], | ||
[remoteAudioTracks, 'remoteAudioTrackStats'], | ||
[remoteVideoTracks, 'remoteVideoTrackStats'] | ||
], function(pair) { | ||
var tracks = pair[0]; | ||
var statsArrayName = pair[1]; | ||
[localAudioTracks, 'localAudioTrackStats', false], | ||
[localVideoTracks, 'localVideoTrackStats', false], | ||
[remoteAudioTracks, 'remoteAudioTrackStats', true], | ||
[remoteVideoTracks, 'remoteVideoTrackStats', true] | ||
], function(triple) { | ||
var tracks = triple[0]; | ||
var statsArrayName = triple[1]; | ||
var isRemote = triple[2]; | ||
return tracks.map(function(track) { | ||
return getTrackStats(peerConnection, track, options).then(function(stats) { | ||
return getTrackStats(peerConnection, track, Object.assign({ | ||
isRemote: isRemote | ||
}, options)).then(function(stats) { | ||
stats.trackId = track.id; | ||
@@ -74,8 +83,8 @@ statsResponse[statsArrayName].push(stats); | ||
if (typeof options.testForChrome !== 'undefined' | ||
|| typeof webkitRTCPeerConnection !== 'undefined') { | ||
return peerConnection.getStats().then(standardizeChromeActiveIceCandidatePairStats); | ||
if (typeof options.testForChrome !== 'undefined' || isChrome | ||
|| typeof options.testForSafari !== 'undefined' || isSafari) { | ||
return peerConnection.getStats().then( | ||
standardizeChromeOrSafariActiveIceCandidatePairStats); | ||
} | ||
if (typeof options.testForFirefox !== 'undefined' | ||
|| typeof mozRTCPeerConnection !== 'undefined') { | ||
if (typeof options.testForFirefox !== 'undefined' || isFirefox) { | ||
return peerConnection.getStats().then(standardizeFirefoxActiveIceCandidatePairStats); | ||
@@ -87,7 +96,7 @@ } | ||
/** | ||
* Standardize the active RTCIceCandidate pair's statistics in Chrome. | ||
* Standardize the active RTCIceCandidate pair's statistics in Chrome or Safari. | ||
* @param {RTCStatsReport} stats | ||
* @returns {?StandardizedActiveIceCandidatePairStatsReport} | ||
*/ | ||
function standardizeChromeActiveIceCandidatePairStats(stats) { | ||
function standardizeChromeOrSafariActiveIceCandidatePairStats(stats) { | ||
var activeCandidatePairStats = Array.from(stats.values()).find(function(stat) { | ||
@@ -188,6 +197,6 @@ return stat.type === 'candidate-pair' && stat.nominated; | ||
{ key: 'candidateType', type: 'string' }, | ||
{ key: 'ip', ffKey: 'ipAddress', type: 'string' }, | ||
{ key: 'port', ffKey: 'portNumber', type: 'number' }, | ||
{ key: 'ip', ffKeys: ['address', 'ipAddress'], type: 'string' }, | ||
{ key: 'port', ffKeys: ['portNumber'], type: 'number' }, | ||
{ key: 'priority', type: 'number' }, | ||
{ key: 'protocol', ffKey: 'transport', type: 'string' }, | ||
{ key: 'protocol', ffKeys: ['transport'], type: 'string' }, | ||
{ key: 'url', type: 'string' } | ||
@@ -210,3 +219,5 @@ ]; | ||
? standardizedLocalCandidateStatsKeys.reduce(function(report, keyInfo) { | ||
var key = keyInfo.ffKey || keyInfo.key; | ||
var key = keyInfo.ffKeys && keyInfo.ffKeys.find(function(key) { | ||
return key in activeLocalCandidateStats; | ||
}) || keyInfo.key; | ||
report[keyInfo.key] = typeof activeLocalCandidateStats[key] === keyInfo.type | ||
@@ -223,3 +234,5 @@ ? key === 'candidateType' | ||
? standardizedCandidateStatsKeys.reduce(function(report, keyInfo) { | ||
var key = keyInfo.ffKey || keyInfo.key; | ||
var key = keyInfo.ffKeys && keyInfo.ffKeys.find(function(key) { | ||
return key in activeRemoteCandidateStats; | ||
}) || keyInfo.key; | ||
report[keyInfo.key] = typeof activeRemoteCandidateStats[key] === keyInfo.type | ||
@@ -300,10 +313,21 @@ ? key === 'candidateType' | ||
if (typeof options.testForChrome !== 'undefined' || | ||
typeof webkitRTCPeerConnection !== 'undefined') { | ||
return chromeGetTrackStats(peerConnection, track); | ||
if (typeof options.testForChrome !== 'undefined' || isChrome) { | ||
return chromeOrSafariGetTrackStats(peerConnection, track); | ||
} | ||
if (typeof options.testForFirefox !== 'undefined' || | ||
typeof mozRTCPeerConnection !== 'undefined') { | ||
return firefoxGetTrackStats(peerConnection, track); | ||
if (typeof options.testForFirefox !== 'undefined' || isFirefox) { | ||
return firefoxGetTrackStats(peerConnection, track, options.isRemote); | ||
} | ||
if (typeof options.testForSafari !== 'undefined' || isSafari) { | ||
if (typeof options.testForSafari !== 'undefined' | ||
|| 'currentDirection' in RTCRtpTransceiver.prototype) { | ||
return chromeOrSafariGetTrackStats(peerConnection, track); | ||
} | ||
// NOTE(syerrapragada): getStats() is not supported on | ||
// Safari versions where plan-b is the SDP format | ||
// due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601 | ||
return Promise.reject(new Error([ | ||
'getStats() is not supported on this version of Safari', | ||
'due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601' | ||
].join(' '))); | ||
} | ||
return Promise.reject(new Error('RTCPeerConnection#getStats() not supported')); | ||
@@ -313,3 +337,3 @@ } | ||
/** | ||
* Get the standardized statistics for a particular MediaStreamTrack in Chrome. | ||
* Get the standardized statistics for a particular MediaStreamTrack in Chrome or Safari. | ||
* @param {RTCPeerConnection} peerConnection | ||
@@ -319,7 +343,7 @@ * @param {MediaStreamTrack} track | ||
*/ | ||
function chromeGetTrackStats(peerConnection, track) { | ||
function chromeOrSafariGetTrackStats(peerConnection, track) { | ||
return new Promise(function(resolve, reject) { | ||
peerConnection.getStats(function(response) { | ||
resolve(standardizeChromeStats(response, track)); | ||
}, null, reject); | ||
peerConnection.getStats(track).then(function(response) { | ||
resolve(standardizeChromeOrSafariStats(response)); | ||
}, reject); | ||
}); | ||
@@ -332,8 +356,9 @@ } | ||
* @param {MediaStreamTrack} track | ||
* @param {boolean} isRemote | ||
* @returns {Promise.<StandardizedTrackStatsReport>} | ||
*/ | ||
function firefoxGetTrackStats(peerConnection, track) { | ||
function firefoxGetTrackStats(peerConnection, track, isRemote) { | ||
return new Promise(function(resolve, reject) { | ||
peerConnection.getStats(track).then(function(response) { | ||
resolve(standardizeFirefoxStats(response)); | ||
resolve(standardizeFirefoxStats(response, isRemote)); | ||
}, reject); | ||
@@ -344,72 +369,133 @@ }); | ||
/** | ||
* Standardize the MediaStreamTrack's statistics in Chrome. | ||
* Standardize the MediaStreamTrack's statistics in Chrome or Safari. | ||
* @param {RTCStatsResponse} response | ||
* @param {MediaStreamTrack} track | ||
* @returns {StandardizedTrackStatsReport} | ||
*/ | ||
function standardizeChromeStats(response, track) { | ||
var ssrcReport = response.result().find(function(report) { | ||
return report.type === 'ssrc' && report.stat('googTrackId') === track.id; | ||
function standardizeChromeOrSafariStats(response) { | ||
var inbound = null; | ||
var outbound = null; | ||
var track = null; | ||
var codec = null; | ||
response.forEach(function(stat) { | ||
switch (stat.type) { | ||
case 'inbound-rtp': | ||
inbound = stat; | ||
break; | ||
case 'outbound-rtp': | ||
outbound = stat; | ||
break; | ||
case 'track': | ||
track = stat; | ||
break; | ||
case 'codec': | ||
codec = stat; | ||
break; | ||
} | ||
}); | ||
var isRemote = track && track.remoteSource; | ||
var standardizedStats = {}; | ||
if (ssrcReport) { | ||
standardizedStats.timestamp = Math.round(Number(ssrcReport.timestamp)); | ||
standardizedStats = ssrcReport.names().reduce(function(stats, name) { | ||
switch (name) { | ||
case 'googCodecName': | ||
stats.codecName = ssrcReport.stat(name); | ||
break; | ||
case 'googRtt': | ||
stats.roundTripTime = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googJitterReceived': | ||
stats.jitter = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameWidthInput': | ||
stats.frameWidthInput = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameHeightInput': | ||
stats.frameHeightInput = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameWidthSent': | ||
stats.frameWidthSent = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameHeightSent': | ||
stats.frameHeightSent = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameWidthReceived': | ||
stats.frameWidthReceived = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameHeightReceived': | ||
stats.frameHeightReceived = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameRateInput': | ||
stats.frameRateInput = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameRateSent': | ||
stats.frameRateSent = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'googFrameRateReceived': | ||
stats.frameRateReceived = Number(ssrcReport.stat(name)); | ||
break; | ||
case 'ssrc': | ||
stats[name] = ssrcReport.stat(name); | ||
break; | ||
case 'bytesReceived': | ||
case 'bytesSent': | ||
case 'packetsLost': | ||
case 'packetsReceived': | ||
case 'packetsSent': | ||
case 'audioInputLevel': | ||
case 'audioOutputLevel': | ||
stats[name] = Number(ssrcReport.stat(name)); | ||
break; | ||
} | ||
var first = isRemote ? inbound : outbound; | ||
var second = track; | ||
var third = codec; | ||
return stats; | ||
}, standardizedStats); | ||
function getStatValue(name) { | ||
if (first && typeof first[name] !== 'undefined') { | ||
return first[name]; | ||
} | ||
if (second && typeof second[name] !== 'undefined') { | ||
return second[name]; | ||
} | ||
if (third && typeof third[name] !== 'undefined') { | ||
return third[name]; | ||
} | ||
return null; | ||
} | ||
var ssrc = getStatValue('ssrc'); | ||
if (typeof ssrc === 'number') { | ||
standardizedStats.ssrc = String(ssrc); | ||
} | ||
var timestamp = getStatValue('timestamp'); | ||
standardizedStats.timestamp = Math.round(timestamp); | ||
var mimeType = getStatValue('mimeType'); | ||
if (typeof mimeType === 'string') { | ||
mimeType = mimeType.split('/'); | ||
standardizedStats.codecName = mimeType[mimeType.length - 1]; | ||
} | ||
var roundTripTime = getStatValue('roundTripTime'); | ||
if (typeof roundTripTime === 'number') { | ||
standardizedStats.roundTripTime = roundTripTime; | ||
} | ||
var jitter = getStatValue('jitter'); | ||
if (typeof jitter === 'number') { | ||
standardizedStats.jitter = Math.round(jitter * 1000); | ||
} | ||
var frameWidth = getStatValue('frameWidth'); | ||
if (typeof frameWidth === 'number') { | ||
if (isRemote) { | ||
standardizedStats.frameWidthReceived = frameWidth; | ||
} else { | ||
standardizedStats.frameWidthSent = frameWidth; | ||
} | ||
} | ||
var frameHeight = getStatValue('frameHeight'); | ||
if (typeof frameHeight === 'number') { | ||
if (isRemote) { | ||
standardizedStats.frameHeightReceived = frameHeight; | ||
} else { | ||
standardizedStats.frameHeightSent = frameHeight; | ||
} | ||
} | ||
var framesPerSecond = getStatValue('framesPerSecond'); | ||
if (typeof framesPerSecond === 'number') { | ||
standardizedStats.frameRateSent = framesPerSecond; | ||
} | ||
var bytesReceived = getStatValue('bytesReceived'); | ||
if (typeof bytesReceived === 'number') { | ||
standardizedStats.bytesReceived = bytesReceived; | ||
} | ||
var bytesSent = getStatValue('bytesSent'); | ||
if (typeof bytesSent === 'number') { | ||
standardizedStats.bytesSent = bytesSent; | ||
} | ||
var packetsLost = getStatValue('packetsLost'); | ||
if (typeof packetsLost === 'number') { | ||
standardizedStats.packetsLost = packetsLost; | ||
} | ||
var packetsReceived = getStatValue('packetsReceived'); | ||
if (typeof packetsReceived === 'number') { | ||
standardizedStats.packetsReceived = packetsReceived; | ||
} | ||
var packetsSent = getStatValue('packetsSent'); | ||
if (typeof packetsSent === 'number') { | ||
standardizedStats.packetsSent = packetsSent; | ||
} | ||
var audioLevel = getStatValue('audioLevel'); | ||
if (typeof audioLevel === 'number') { | ||
if (isRemote) { | ||
standardizedStats.audioOutputLevel = audioLevel; | ||
} else { | ||
standardizedStats.audioInputLevel = audioLevel; | ||
} | ||
} | ||
return standardizedStats; | ||
@@ -421,5 +507,6 @@ } | ||
* @param {RTCStatsReport} response | ||
* @param {boolean} isRemote | ||
* @returns {StandardizedTrackStatsReport} | ||
*/ | ||
function standardizeFirefoxStats(response) { | ||
function standardizeFirefoxStats(response, isRemote) { | ||
// NOTE(mroberts): If getStats is called on a closed RTCPeerConnection, | ||
@@ -433,32 +520,44 @@ // Firefox returns undefined instead of an RTCStatsReport. We workaround this | ||
var inbound = Array.from(response.values()).find(function(stat) { | ||
return stat.type === 'inbound-rtp'; | ||
}); | ||
var inbound = null; | ||
var outbound = null; | ||
var outbound = Array.from(response.values()).find(function(stat) { | ||
return stat.type === 'outbound-rtp'; | ||
// NOTE(mmalavalli): Starting from Firefox 63, RTC{Inbound, Outbound}RTPStreamStats.isRemote | ||
// will be deprecated, followed by its removal in Firefox 66. Also, trying to | ||
// access members of the remote RTC{Inbound, Outbound}RTPStreamStats without | ||
// using RTCStatsReport.get(remoteId) will trigger console warnings. So, we | ||
// no longer depend on "isRemote", and we call RTCStatsReport.get(remoteId) | ||
// to access the remote RTC{Inbound, Outbound}RTPStreamStats. | ||
// | ||
// Source: https://blog.mozilla.org/webrtc/getstats-isremote-65/ | ||
// | ||
response.forEach(function(stat) { | ||
if (stat.isRemote) { | ||
return; | ||
} | ||
switch (stat.type) { | ||
case 'inbound-rtp': | ||
inbound = stat; | ||
outbound = response.get(stat.remoteId); | ||
break; | ||
case 'outbound-rtp': | ||
outbound = stat; | ||
inbound = response.get(stat.remoteId); | ||
break; | ||
} | ||
}); | ||
var standardizedStats = {}; | ||
var first = isRemote ? inbound : outbound; | ||
var second = isRemote ? outbound : inbound; | ||
function getStatValue(name) { | ||
var first = outbound; | ||
var second = inbound; | ||
if (outbound && outbound.isRemote) { | ||
first = inbound; | ||
second = outbound; | ||
} | ||
if (first && typeof first[name] !== 'undefined') { | ||
return first[name]; | ||
} | ||
if (second && typeof second[name] !== 'undefined') { | ||
return second[name]; | ||
} | ||
return null; | ||
} | ||
var standardizedStats = {}; | ||
var timestamp = getStatValue('timestamp'); | ||
@@ -468,4 +567,4 @@ standardizedStats.timestamp = Math.round(timestamp); | ||
var ssrc = getStatValue('ssrc'); | ||
if (typeof ssrc === 'string') { | ||
standardizedStats.ssrc = ssrc; | ||
if (typeof ssrc === 'number') { | ||
standardizedStats.ssrc = String(ssrc); | ||
} | ||
@@ -488,3 +587,3 @@ | ||
var roundTripTime = getStatValue('mozRtt'); | ||
var roundTripTime = getStatValue('roundTripTime'); | ||
if (typeof roundTripTime === 'number') { | ||
@@ -522,3 +621,2 @@ standardizedStats.roundTripTime = roundTripTime; | ||
/** | ||
@@ -525,0 +623,0 @@ * Standardized RTCIceCandidate statistics. |
@@ -46,9 +46,2 @@ /* globals RTCDataChannel, RTCSessionDescription, webkitRTCPeerConnection */ | ||
// NOTE(mmalavalli): Because of a bug related to "ontrack", we prevent it | ||
// from being delegated to ChromeRTCPeerConnection. For now, this bug | ||
// manifests when we run Chrome with the flag: --enable-blink-features=RTCRtpSender | ||
// Existing bug: https://bugs.chromium.org/p/chromium/issues/detail?id=774303 | ||
// Bug filed by us: https://bugs.chromium.org/p/chromium/issues/detail?id=783433 | ||
util.interceptEvent(this, 'track'); | ||
/* eslint new-cap:0 */ | ||
@@ -138,9 +131,13 @@ var peerConnection = new PeerConnection(newConfiguration, constraints); | ||
// supported natively in Chrome. | ||
// NOTE(mmalavalli): This shim also works around a Chrome bug in "unified-plan" | ||
// SDPs where adding a MediaStreamTrack that was previously added and removed | ||
// generates an SDP where the MSID does not match the MediaStreamTrack ID. | ||
// | ||
// Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=894231 | ||
// | ||
ChromeRTCPeerConnection.prototype.addTrack = function addTrack() { | ||
var args = [].slice.call(arguments); | ||
if (this._peerConnection.addTrack) { | ||
return this._peerConnection.addTrack.apply(this._peerConnection, args); | ||
} | ||
var track = args[0]; | ||
var sender = this._senders.get(track); | ||
var track = args[0]; | ||
if (this._peerConnection.signalingState === 'closed') { | ||
@@ -150,4 +147,2 @@ throw new Error('Cannot add MediaStreamTrack [' + track.id + ', ' | ||
} | ||
var sender = this._senders.get(track); | ||
if (sender && sender.track) { | ||
@@ -157,2 +152,12 @@ throw new Error('Cannot add MediaStreamTrack [' + track.id + ', ' | ||
} | ||
if (this._peerConnection.addTrack) { | ||
if (this._sdpSemantics === 'unified-plan') { | ||
sender = getActiveSenders(this._peerConnection).get(track) | ||
|| this._peerConnection.addTrack.apply(this._peerConnection, args); | ||
this._senders.set(track, sender); | ||
return sender; | ||
} | ||
return this._peerConnection.addTrack.apply(this._peerConnection, args); | ||
} | ||
this._peerConnection.removeStream(this._localStream); | ||
@@ -167,2 +172,18 @@ this._localStream.addTrack(track); | ||
// NOTE(mmalavalli): This shim works around a Chrome bug in "unified-plan" | ||
// SDPs where adding a MediaStreamTrack that was previously added and removed | ||
// generates an SDP where the MSID does not match the MediaStreamTrack ID. | ||
// | ||
// Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=894231 | ||
// | ||
if (PeerConnection.prototype.addTransceiver) { | ||
ChromeRTCPeerConnection.prototype.addTransceiver = function addTransceiver() { | ||
var transceiver = this._peerConnection.addTransceiver.apply(this._peerConnection, arguments); | ||
var sender = transceiver.sender; | ||
var track = sender.track; | ||
this._senders.set(track, sender); | ||
return transceiver; | ||
}; | ||
} | ||
// NOTE(mmalavalli): This shim supports our limited case of removing | ||
@@ -172,2 +193,8 @@ // MediaStreamTracks from one MediaStream. It has been implemented this | ||
// supported natively in Chrome. | ||
// NOTE(mmalavalli): This shim also works around a Chrome bug in "unified-plan" | ||
// SDPs where adding a MediaStreamTrack that was previously added and removed | ||
// generates an SDP where the MSID does not match the MediaStreamTrack ID. | ||
// | ||
// Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=894231 | ||
// | ||
ChromeRTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { | ||
@@ -180,3 +207,9 @@ if (this._peerConnection.signalingState === 'closed') { | ||
try { | ||
this._peerConnection.removeTrack(sender); | ||
if (this._sdpSemantics === 'unified-plan') { | ||
if (sender.track) { | ||
this._senders.set(sender.track, new RTCRtpSenderShim(null)); | ||
} | ||
} else { | ||
this._peerConnection.removeTrack(sender); | ||
} | ||
} | ||
@@ -206,6 +239,5 @@ catch (error) { | ||
ChromeRTCPeerConnection.prototype.getSenders = function getSenders() { | ||
if (this._peerConnection.getSenders) { | ||
return this._peerConnection.getSenders(); | ||
} | ||
return Array.from(this._senders.values()); | ||
return this._peerConnection.getSenders && this._sdpSemantics === 'plan-b' | ||
? this._peerConnection.getSenders() | ||
: Array.from(this._senders.values()); | ||
}; | ||
@@ -257,8 +289,3 @@ | ||
if (this._pendingRemoteOffer) { | ||
var mediaStreamTracks = util.flatMap(this.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}); | ||
promise = this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(function setRemoteDescriptionSucceeded() { | ||
maybeDispatchTrackEvents(self, mediaStreamTracks); | ||
// NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection | ||
@@ -340,19 +367,2 @@ // and the underlying RTCPeerConnection implementation have converged. We | ||
// Dispatch 'track' events to ChromeRTCPeerConnection if new | ||
// MediaStreamTracks have been added. This is a temporary workaround | ||
// for the unreliable MediaStreamTrack#addtrack event. Do this only if | ||
// the native RTCPeerConnection has not implemented 'ontrack'. | ||
function maybeDispatchTrackEvents(peerConnection, mediaStreamTracks) { | ||
var currentMediaStreamTracks = util.flatMap(peerConnection.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}); | ||
var mediaStreamTracksAdded = util.difference(currentMediaStreamTracks, mediaStreamTracks); | ||
mediaStreamTracksAdded.forEach(function(mediaStreamTrack) { | ||
var newEvent = new Event('track'); | ||
newEvent.track = mediaStreamTrack; | ||
peerConnection.dispatchEvent(newEvent); | ||
}); | ||
} | ||
// NOTE(mroberts): We workaround Chrome's lack of rollback support, per the | ||
@@ -381,6 +391,2 @@ // workaround suggested here: https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3 | ||
var mediaStreamTracks = util.flatMap(peerConnection.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}); | ||
var pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer; | ||
@@ -435,14 +441,6 @@ var pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer; | ||
return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description)).then(function() { | ||
if (!local) { | ||
maybeDispatchTrackEvents(peerConnection, mediaStreamTracks); | ||
} | ||
}); | ||
return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description)); | ||
} | ||
function setRemoteAnswer(peerConnection, answer) { | ||
var mediaStreamTracks = util.flatMap(peerConnection.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}); | ||
// Apply the pending local offer. | ||
@@ -454,3 +452,2 @@ var pendingLocalOffer = peerConnection._pendingLocalOffer; | ||
}).then(function setRemoteAnswerSucceeded() { | ||
maybeDispatchTrackEvents(peerConnection, mediaStreamTracks); | ||
// NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection | ||
@@ -526,2 +523,15 @@ // and the underlying RTCPeerConnection implementation have converged. We | ||
/** | ||
* Gets the active RTCRtpSenders of the RTCPeerConnection. | ||
* @param peerConnection | ||
* @returns {Map<MediaStreamTrack, RTCRtpSender>} | ||
*/ | ||
function getActiveSenders(peerConnection) { | ||
return new Map(peerConnection.getSenders().filter(function(sender) { | ||
return sender.track; | ||
}).map(function(sender) { | ||
return [sender.track, sender]; | ||
})); | ||
} | ||
/** | ||
* Get the actual `sdpSemantics`. | ||
@@ -532,5 +542,10 @@ * @param {SdpSemantics} sdpSemantics | ||
function getSdpSemantics(sdpSemantics) { | ||
// NOTE(mmalavalli): Once Chrome stops supporting "plan-b" SDPs, it will | ||
// ignore the "sdpSemantics" flag. So, in order to differentiate between | ||
// versions of Chrome which support only "plan-b" SDPs and versions of Chrome | ||
// which support only "unified-plan" SDPs, which check whether PeerConnection's | ||
// addStream() method is present. | ||
return sdpUtils.checkIfSdpSemanticsIsSupported() | ||
? sdpSemantics | ||
: 'plan-b'; | ||
: PeerConnection.prototype.addStream ? 'plan-b' : 'unified-plan'; | ||
} | ||
@@ -537,0 +552,0 @@ |
@@ -1,2 +0,2 @@ | ||
/* globals RTCPeerConnection, RTCSessionDescription */ | ||
/* globals RTCPeerConnection, RTCRtpTransceiver, RTCSessionDescription */ | ||
'use strict'; | ||
@@ -8,5 +8,11 @@ | ||
var RTCRtpSenderShim = require('../rtcrtpsender'); | ||
var updateTracksToSSRCs = require('../util/sdp').updatePlanBTrackIdsToSSRCs; | ||
var sdpUtils = require('../util/sdp'); | ||
var util = require('../util'); | ||
var isUnifiedPlan = 'currentDirection' in RTCRtpTransceiver.prototype; | ||
var updateTrackIdsToSSRCs = isUnifiedPlan | ||
? sdpUtils.updateUnifiedPlanTrackIdsToSSRCs | ||
: sdpUtils.updatePlanBTrackIdsToSSRCs; | ||
function SafariRTCPeerConnection(configuration) { | ||
@@ -59,12 +65,6 @@ if (!(this instanceof SafariRTCPeerConnection)) { | ||
}, | ||
// NOTE(mroberts): Keep this here until the following is fixed. | ||
// | ||
// https://bugs.webkit.org/show_bug.cgi?id=174323 | ||
// | ||
localDescription: { | ||
enumerable: true, | ||
get: function() { | ||
return this._isClosed | ||
? null | ||
: this._pendingLocalOffer || this._peerConnection.localDescription; | ||
return this._pendingLocalOffer || this._peerConnection.localDescription; | ||
} | ||
@@ -91,5 +91,3 @@ }, | ||
get: function() { | ||
return this._isClosed | ||
? null | ||
: this._pendingRemoteOffer || this._peerConnection.remoteDescription; | ||
return this._pendingRemoteOffer || this._peerConnection.remoteDescription; | ||
} | ||
@@ -165,10 +163,22 @@ }, | ||
// good enough for our application. | ||
if (options.offerToReceiveAudio && !this._audioTransceiver) { | ||
if (options.offerToReceiveAudio && !this._audioTransceiver && !(isUnifiedPlan && hasSendersForTracksOfKind(this, 'audio'))) { | ||
delete options.offerToReceiveAudio; | ||
this._audioTransceiver = this.addTransceiver('audio'); | ||
try { | ||
this._audioTransceiver = isUnifiedPlan | ||
? this.addTransceiver('audio', { direction: 'recvonly' }) | ||
: this.addTransceiver('audio'); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
if (options.offerToReceiveVideo && !this._videoTransceiver) { | ||
if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasSendersForTracksOfKind(this, 'video'))) { | ||
delete options.offerToReceiveVideo; | ||
this._videoTransceiver = this.addTransceiver('video'); | ||
try { | ||
this._videoTransceiver = isUnifiedPlan | ||
? this.addTransceiver('video', { direction: 'recvonly' }) | ||
: this.addTransceiver('video'); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
@@ -179,3 +189,3 @@ | ||
type: offer.type, | ||
sdp: updateTracksToSSRCs(self._tracksToSSRCs, offer.sdp) | ||
sdp: updateTrackIdsToSSRCs(self._tracksToSSRCs, offer.sdp) | ||
}); | ||
@@ -194,3 +204,6 @@ }); | ||
self._pendingRemoteOffer = null; | ||
return answer; | ||
return isUnifiedPlan ? new RTCSessionDescription({ | ||
type: answer.type, | ||
sdp: updateTrackIdsToSSRCs(self._tracksToSSRCs, answer.sdp) | ||
}) : answer; | ||
}, function setRemoteDescriptionOrCreateAnswerFailed(error) { | ||
@@ -202,3 +215,8 @@ self._pendingRemoteOffer = null; | ||
return this._peerConnection.createAnswer(options); | ||
return this._peerConnection.createAnswer(options).then(function(answer) { | ||
return isUnifiedPlan ? new RTCSessionDescription({ | ||
type: answer.type, | ||
sdp: updateTrackIdsToSSRCs(self._tracksToSSRCs, answer.sdp) | ||
}) : answer; | ||
}); | ||
}; | ||
@@ -259,2 +277,18 @@ | ||
// NOTE(mmalavalli): This shim works around a Safari bug in "unified-plan" | ||
// SDPs where adding a MediaStreamTrack that was previously added and removed | ||
// generates an SDP where the MSID does not match the MediaStreamTrack ID. | ||
// | ||
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=192101 | ||
// | ||
if (RTCPeerConnection.prototype.addTransceiver) { | ||
SafariRTCPeerConnection.prototype.addTransceiver = function addTransceiver() { | ||
var transceiver = this._peerConnection.addTransceiver.apply(this._peerConnection, arguments); | ||
var sender = transceiver.sender; | ||
var track = sender.track; | ||
this._senders.set(track, sender); | ||
return transceiver; | ||
}; | ||
} | ||
// NOTE(mroberts): We can't really remove tracks right now, at least if we | ||
@@ -265,2 +299,8 @@ // ever want to add them back... | ||
// | ||
// NOTE(mmalavalli): This shim also works around a Safari bug in "unified-plan" | ||
// SDPs where adding a MediaStreamTrack that was previously added and removed | ||
// generates an SDP where the MSID does not match the MediaStreamTrack ID. | ||
// | ||
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=192101 | ||
// | ||
SafariRTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { | ||
@@ -362,2 +402,15 @@ if (this._isClosed) { | ||
/** | ||
* Whether a SafariRTCPeerConnection has any RTCRtpSender(s) for the given | ||
* MediaStreamTrack kind. | ||
* @param {SafariRTCPeerConnection} peerConnection | ||
* @param {'audio' | 'video'} kind | ||
* @returns {boolean} | ||
*/ | ||
function hasSendersForTracksOfKind(peerConnection, kind) { | ||
return !!peerConnection.getTransceivers().find(function(transceiver) { | ||
return transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === kind; | ||
}); | ||
} | ||
/** | ||
* Shim an RTCDataChannel. This function mutates the RTCDataChannel. | ||
@@ -364,0 +417,0 @@ * @param {RTCDataChannel} dataChannel |
{ | ||
"name": "@twilio/webrtc", | ||
"version": "2.1.4", | ||
"version": "2.2.0", | ||
"description": "WebRTC-related APIs and shims used by twilio-video.js", | ||
@@ -46,3 +46,3 @@ "scripts": { | ||
"karma-mocha": "^1.3.0", | ||
"karma-safaritechpreview-launcher": "0.0.6", | ||
"karma-safari-launcher": "^1.0.0", | ||
"karma-spec-reporter": "0.0.31", | ||
@@ -55,4 +55,4 @@ "mocha": "^3.5.0", | ||
"watchify": "^3.9.0", | ||
"webrtc-adapter": "^6.0.1" | ||
"webrtc-adapter": "^6.4.8" | ||
} | ||
} |
@@ -80,5 +80,5 @@ twilio-webrtc.js | ||
more information. | ||
* Does not depend on the native "track" event (currently behind the flag: `--enable-blink-features=RTCRtpSender`) because | ||
of [this bug](https://bugs.chromium.org/p/chromium/issues/detail?id=774303), which partly refers to "ontrack" not firing when expected. | ||
We have filed [this bug](https://bugs.chromium.org/p/chromium/issues/detail?id=783433) specifically for this. | ||
* Works around a [this bug](https://bugs.chromium.org/p/chromium/issues/detail?id=894231) | ||
in "unified-plan" SDPs where adding a `MediaStreamTrack` that was previously added and | ||
removed generates an SDP where the MSID does not match the `MediaStreamTrack` ID. | ||
@@ -106,4 +106,2 @@ #### Firefox | ||
`MediaStreamTrack`. | ||
* Provides a workaround for [this bug](https://bugs.webkit.org/show_bug.cgi?id=174323), where | ||
trying to access the `localDescription` or `remoteDescription` throws an exception. | ||
* Provides a shim for the `removeTrack` method in order to work around [this bug](https://bugs.webkit.org/show_bug.cgi?id=174327). | ||
@@ -110,0 +108,0 @@ * Provides a shim for the `addTrack` method. Since `removeTrack` is shimmed, there is a necessity to |
116025
2693
124