@twilio/webrtc
Advanced tools
Comparing version 2.2.0 to 2.2.1
@@ -0,1 +1,15 @@ | ||
2.2.1 (January 29, 2019) | ||
======================== | ||
Bug Fixes | ||
--------- | ||
- Fixed a bug where, in Electron 2.x, if the remote peer adds a second | ||
MediaStreamTrack after completing the negotiation for the first MediaStreamTrack, | ||
calling `getStats` did not return the StandardizedTrackStatsReport for the second | ||
remote MediaStreamTrack. (JSDK-2269) | ||
- Fixed a bug where `getStats` was throwing a TypeError in Electron 2.x and 3.x. (JSDK-2267) | ||
- Added back the workaround for this Chrome [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=774303) | ||
in order to support Electron 2.x. (JSDK-2266) | ||
2.2.0 (January 10, 2019) | ||
@@ -2,0 +16,0 @@ ======================== |
@@ -12,2 +12,6 @@ /* global RTCRtpTransceiver */ | ||
var chromeMajorVersion = isChrome | ||
? parseInt(navigator.userAgent.match(/Chrome\/([0-9]+)/)[1], 10) | ||
: null; | ||
/** | ||
@@ -284,3 +288,9 @@ * Get the standardized {@link RTCPeerConnection} statistics. | ||
var getSendersOrReceivers = localOrRemote === 'local' ? 'getSenders' : 'getReceivers'; | ||
if (peerConnection[getSendersOrReceivers]) { | ||
// NOTE(mmalavalli): In Electron 2.x (Chrome 61), if the remote peer adds a second | ||
// MediaStreamTrack after completing the negotiation for the first MediaStreamTrack, | ||
// then the array returned by RTCPeerConnection.getReceivers() will contain only | ||
// the RTCRtpReceiver for the first MediaStreamTrack. In order to work around this, | ||
// we construct the array of remote MediaStreamTracks using | ||
// RTCPeerConnection.getRemoteStreams() instead. | ||
if (peerConnection[getSendersOrReceivers] && !(chromeMajorVersion && chromeMajorVersion < 66)) { | ||
return peerConnection[getSendersOrReceivers]().map(function(senderOrReceiver) { | ||
@@ -339,2 +349,8 @@ return senderOrReceiver.track; | ||
return new Promise(function(resolve, reject) { | ||
if (chromeMajorVersion && chromeMajorVersion < 67) { | ||
peerConnection.getStats(function(response) { | ||
resolve(standardizeChromeLegacyStats(response, track)); | ||
}, null, reject); | ||
return; | ||
} | ||
peerConnection.getStats(track).then(function(response) { | ||
@@ -362,6 +378,80 @@ resolve(standardizeChromeOrSafariStats(response)); | ||
/** | ||
* Standardize the MediaStreamTrack's statistics in Chrome or Safari. | ||
* Standardize the MediaStreamTrack's legacy statistics in Chrome. | ||
* @param {RTCStatsResponse} response | ||
* @param {MediaStreamTrack} track | ||
* @returns {StandardizedTrackStatsReport} | ||
*/ | ||
function standardizeChromeLegacyStats(response, track) { | ||
var ssrcReport = response.result().find(function(report) { | ||
return report.type === 'ssrc' && report.stat('googTrackId') === track.id; | ||
}); | ||
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; | ||
} | ||
return stats; | ||
}, standardizedStats); | ||
} | ||
return standardizedStats; | ||
} | ||
/** | ||
* Standardize the MediaStreamTrack's statistics in Chrome or Safari. | ||
* @param {RTCStatsReport} response | ||
* @returns {StandardizedTrackStatsReport} | ||
*/ | ||
function standardizeChromeOrSafariStats(response) { | ||
@@ -368,0 +458,0 @@ var inbound = null; |
@@ -104,2 +104,10 @@ /* globals RTCDataChannel, RTCSessionDescription, webkitRTCPeerConnection */ | ||
if (sdpSemantics === 'plan-b') { | ||
// NOTE(mmalavalli): Because of a bug related to "ontrack" in Chrome 63 and below, | ||
// we prevent it from being delegated to ChromeRTCPeerConnection. | ||
// 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'); | ||
} | ||
peerConnection.addEventListener('datachannel', function ondatachannel(event) { | ||
@@ -279,6 +287,14 @@ shimDataChannel(event.channel); | ||
var promise; | ||
var isPlanB = this._sdpSemantics === 'plan-b'; | ||
var self = this; | ||
if (this._pendingRemoteOffer) { | ||
var mediaStreamTracks = isPlanB ? util.flatMap(this.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}) : []; | ||
promise = this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(function setRemoteDescriptionSucceeded() { | ||
if (isPlanB) { | ||
maybeDispatchTrackEvents(self, mediaStreamTracks); | ||
} | ||
// NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection | ||
@@ -360,2 +376,19 @@ // 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 | ||
@@ -390,5 +423,9 @@ // workaround suggested here: https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3 | ||
var isPlanB = peerConnection._sdpSemantics === 'plan-b'; | ||
var mediaStreamTracks = isPlanB ? util.flatMap(peerConnection.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}) : []; | ||
if (!local && pendingRemoteOffer && description.type === 'answer') { | ||
promise = setRemoteAnswer(peerConnection, description); | ||
} else if (description.type === 'offer') { | ||
@@ -434,6 +471,14 @@ if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') { | ||
return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description)); | ||
return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description)).then(function() { | ||
if (!local && isPlanB) { | ||
maybeDispatchTrackEvents(peerConnection, mediaStreamTracks); | ||
} | ||
}); | ||
} | ||
function setRemoteAnswer(peerConnection, answer) { | ||
var isPlanB = peerConnection._sdpSemantics === 'plan-b'; | ||
var mediaStreamTracks = isPlanB ? util.flatMap(peerConnection.getRemoteStreams(), function(mediaStream) { | ||
return mediaStream.getTracks(); | ||
}) : []; | ||
// Apply the pending local offer. | ||
@@ -445,2 +490,5 @@ var pendingLocalOffer = peerConnection._pendingLocalOffer; | ||
}).then(function setRemoteAnswerSucceeded() { | ||
if (isPlanB) { | ||
maybeDispatchTrackEvents(peerConnection, mediaStreamTracks); | ||
} | ||
// NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection | ||
@@ -447,0 +495,0 @@ // and the underlying RTCPeerConnection implementation have converged. We |
{ | ||
"name": "@twilio/webrtc", | ||
"version": "2.2.0", | ||
"version": "2.2.1", | ||
"description": "WebRTC-related APIs and shims used by twilio-video.js", | ||
@@ -5,0 +5,0 @@ "scripts": { |
@@ -83,3 +83,8 @@ twilio-webrtc.js | ||
removed generates an SDP where the MSID does not match the `MediaStreamTrack` ID. | ||
* Does not depend on the native "track" event for "plan-b" RTCPeerConnection 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. | ||
This workaround is essential for Electron 2.x support. | ||
#### Firefox | ||
@@ -86,0 +91,0 @@ * For new offers, adds support for calling `setLocalDescription` and `setRemoteDescription` in |
122423
2821
129