@twilio/webrtc
Advanced tools
Comparing version
@@ -0,1 +1,18 @@ | ||
4.0.0 (March 15, 2019) | ||
====================== | ||
New Features | ||
------------ | ||
- SafariRTCPeerConnection will now support Unified Plan SDPs in Safari 12.1 and above. (JSDK-2306) | ||
Bug Fixes | ||
--------- | ||
- Fixed a bug where getStats was throwing a TypeError in Electron 3.x. (JSDK-2267) | ||
- Fixed a bug where createOffer(), when called in Safari 12.2 created "offerToReceive" | ||
RTCRtpTransceivers even though the RTCPeerConnection already had "sendrecv" or | ||
"recvonly" RTCRtpTransceivers. (JSDK-2286) | ||
3.2.0 (January 7, 2019) | ||
@@ -2,0 +19,0 @@ ======================= |
@@ -1,2 +0,1 @@ | ||
/* global RTCRtpTransceiver */ | ||
'use strict'; | ||
@@ -6,2 +5,3 @@ | ||
var guessBrowser = require('./util').guessBrowser; | ||
var getSdpFormat = require('./util/sdp').getSdpFormat; | ||
@@ -12,3 +12,8 @@ var guess = guessBrowser(); | ||
var isSafari = guess === 'safari'; | ||
var sdpFormat = getSdpFormat(); | ||
var chromeMajorVersion = isChrome | ||
? parseInt(navigator.userAgent.match(/Chrome\/([0-9]+)/)[1], 10) | ||
: null; | ||
/** | ||
@@ -316,4 +321,3 @@ * Get the standardized {@link RTCPeerConnection} statistics. | ||
if (typeof options.testForSafari !== 'undefined' || isSafari) { | ||
if (typeof options.testForSafari !== 'undefined' | ||
|| 'currentDirection' in RTCRtpTransceiver.prototype) { | ||
if (typeof options.testForSafari !== 'undefined' || sdpFormat === 'unified') { | ||
return chromeOrSafariGetTrackStats(peerConnection, track); | ||
@@ -340,2 +344,8 @@ } | ||
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) { | ||
@@ -363,2 +373,76 @@ resolve(standardizeChromeOrSafariStats(response)); | ||
/** | ||
* 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. | ||
@@ -365,0 +449,0 @@ * @param {RTCStatsResponse} response |
@@ -1,7 +0,5 @@ | ||
/* globals webkitMediaStream, MediaStream */ | ||
/* globals MediaStream */ | ||
'use strict'; | ||
if (typeof webkitMediaStream !== 'undefined') { | ||
module.exports = webkitMediaStream; | ||
} else if (typeof MediaStream !== 'undefined') { | ||
if (typeof MediaStream !== 'undefined') { | ||
module.exports = MediaStream; | ||
@@ -8,0 +6,0 @@ } else { |
@@ -1,2 +0,2 @@ | ||
/* globals RTCDataChannel, RTCSessionDescription, webkitRTCPeerConnection */ | ||
/* globals RTCDataChannel, RTCPeerConnection, RTCSessionDescription */ | ||
'use strict'; | ||
@@ -13,5 +13,3 @@ | ||
var PeerConnection = typeof RTCPeerConnection !== 'undefined' | ||
? RTCPeerConnection | ||
: webkitRTCPeerConnection; | ||
var sdpFormat = sdpUtils.getSdpFormat(); | ||
@@ -36,9 +34,6 @@ // NOTE(mroberts): This class wraps Chrome's RTCPeerConnection implementation. | ||
// NOTE(mhuynh): See | ||
// https://webrtc.org/web-apis/chrome/unified-plan/ | ||
// for transition from 'plan-b' default to 'unified-plan' as default. | ||
var newConfiguration = Object.assign({ sdpSemantics: 'plan-b' }, configuration); | ||
if (newConfiguration.iceTransportPolicy) { | ||
newConfiguration.iceTransports = newConfiguration.iceTransportPolicy; | ||
} | ||
configuration = configuration || {}; | ||
var newConfiguration = Object.assign(configuration.iceTransportPolicy | ||
? { iceTransports: configuration.iceTransportPolicy } | ||
: {}, { sdpSemantics: 'plan-b' }, configuration); | ||
@@ -48,5 +43,3 @@ util.interceptEvent(this, 'datachannel'); | ||
/* eslint new-cap:0 */ | ||
var peerConnection = new PeerConnection(newConfiguration, constraints); | ||
var sdpSemantics = getSdpSemantics(newConfiguration.sdpSemantics); | ||
var peerConnection = new RTCPeerConnection(newConfiguration, constraints); | ||
@@ -68,5 +61,2 @@ Object.defineProperties(this, { | ||
}, | ||
_sdpSemantics: { | ||
value: sdpSemantics | ||
}, | ||
_senders: { | ||
@@ -124,4 +114,6 @@ value: new Map() | ||
peerConnection.addStream(this._localStream); | ||
util.proxyProperties(PeerConnection.prototype, this, peerConnection); | ||
if (typeof RTCPeerConnection.prototype.addTrack !== 'function') { | ||
peerConnection.addStream(this._localStream); | ||
} | ||
util.proxyProperties(RTCPeerConnection.prototype, this, peerConnection); | ||
} | ||
@@ -131,3 +123,3 @@ | ||
if (typeof PeerConnection.prototype.addTrack !== 'function') { | ||
if (typeof RTCPeerConnection.prototype.addTrack !== 'function') { | ||
// NOTE(mmalavalli): This shim supports our limited case of adding | ||
@@ -253,5 +245,5 @@ // all MediaStreamTracks to one MediaStream. It has been implemented this | ||
self._pendingRemoteOffer = null; | ||
return new RTCSessionDescription({ | ||
return new ChromeRTCSessionDescription({ | ||
type: 'answer', | ||
sdp: updateTrackIdsToSSRCs(self._sdpSemantics, self._tracksToSSRCs, answer.sdp) | ||
sdp: updateTrackIdsToSSRCs(sdpFormat, self._tracksToSSRCs, answer.sdp) | ||
}); | ||
@@ -264,5 +256,5 @@ }, function setRemoteDescriptionOrCreateAnswerFailed(error) { | ||
promise = this._peerConnection.createAnswer().then(function(answer) { | ||
return new RTCSessionDescription({ | ||
return new ChromeRTCSessionDescription({ | ||
type: 'answer', | ||
sdp: updateTrackIdsToSSRCs(self._sdpSemantics, self._tracksToSSRCs, answer.sdp) | ||
sdp: updateTrackIdsToSSRCs(sdpFormat, self._tracksToSSRCs, answer.sdp) | ||
}); | ||
@@ -285,3 +277,3 @@ }); | ||
type: offer.type, | ||
sdp: updateTrackIdsToSSRCs(self._sdpSemantics, self._tracksToSSRCs, offer.sdp) | ||
sdp: updateTrackIdsToSSRCs(sdpFormat, self._tracksToSSRCs, offer.sdp) | ||
}); | ||
@@ -321,3 +313,3 @@ }); | ||
util.delegateMethods( | ||
PeerConnection.prototype, | ||
RTCPeerConnection.prototype, | ||
ChromeRTCPeerConnection.prototype, | ||
@@ -478,28 +470,12 @@ '_peerConnection'); | ||
/** | ||
* Get the actual `sdpSemantics`. | ||
* @param {SdpSemantics} sdpSemantics | ||
* @returns {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 | ||
: PeerConnection.prototype.addStream ? 'plan-b' : 'unified-plan'; | ||
} | ||
/** | ||
* Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both | ||
* the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method | ||
* ensures that SSRCs never change once announced. | ||
* @param {SdpSemantics} sdpSemantics | ||
* @param {Map<string, Set<string>>} trackIdsToSSRCs | ||
* @param {'planb'|'unified'} sdpFormat | ||
* @param {Map<string, Set<string>>} tracksToSSRCs | ||
* @param {string} sdp - an SDP whose format is determined by `sdpSemantics` | ||
* @returns {string} updatedSdp - updated SDP | ||
*/ | ||
function updateTrackIdsToSSRCs(sdpSemantics, tracksToSSRCs, sdp) { | ||
return sdpSemantics === 'unified-plan' | ||
function updateTrackIdsToSSRCs(sdpFormat, tracksToSSRCs, sdp) { | ||
return sdpFormat === 'unified' | ||
? sdpUtils.updateUnifiedPlanTrackIdsToSSRCs(tracksToSSRCs, sdp) | ||
@@ -506,0 +482,0 @@ : sdpUtils.updatePlanBTrackIdsToSSRCs(tracksToSSRCs, sdp); |
@@ -1,2 +0,2 @@ | ||
/* globals mozRTCPeerConnection, RTCPeerConnection */ | ||
/* globals RTCPeerConnection */ | ||
'use strict'; | ||
@@ -10,6 +10,2 @@ | ||
var PeerConnection = typeof RTCPeerConnection !== 'undefined' | ||
? RTCPeerConnection | ||
: mozRTCPeerConnection; | ||
// NOTE(mroberts): This is a short-lived workaround. Checking the user agent | ||
@@ -52,3 +48,3 @@ // string might not fix every affected Firefox instance, but it should be good | ||
/* eslint new-cap:0 */ | ||
var peerConnection = new PeerConnection(configuration); | ||
var peerConnection = new RTCPeerConnection(configuration); | ||
@@ -114,3 +110,3 @@ Object.defineProperties(this, { | ||
util.proxyProperties(PeerConnection.prototype, this, peerConnection); | ||
util.proxyProperties(RTCPeerConnection.prototype, this, peerConnection); | ||
} | ||
@@ -264,3 +260,3 @@ | ||
util.delegateMethods( | ||
PeerConnection.prototype, | ||
RTCPeerConnection.prototype, | ||
FirefoxRTCPeerConnection.prototype, | ||
@@ -267,0 +263,0 @@ '_peerConnection'); |
@@ -1,2 +0,2 @@ | ||
/* globals RTCPeerConnection, RTCRtpTransceiver, RTCSessionDescription */ | ||
/* globals RTCPeerConnection, RTCSessionDescription */ | ||
'use strict'; | ||
@@ -10,3 +10,3 @@ | ||
var isUnifiedPlan = 'currentDirection' in RTCRtpTransceiver.prototype; | ||
var isUnifiedPlan = sdpUtils.getSdpFormat() === 'unified'; | ||
@@ -153,3 +153,3 @@ var updateTrackIdsToSSRCs = isUnifiedPlan | ||
// good enough for our application. | ||
if (options.offerToReceiveAudio && !this._audioTransceiver && !(isUnifiedPlan && hasSendersForTracksOfKind(this, 'audio'))) { | ||
if (options.offerToReceiveAudio && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) { | ||
delete options.offerToReceiveAudio; | ||
@@ -165,3 +165,3 @@ try { | ||
if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasSendersForTracksOfKind(this, 'video'))) { | ||
if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) { | ||
delete options.offerToReceiveVideo; | ||
@@ -318,3 +318,3 @@ try { | ||
/** | ||
* Whether a SafariRTCPeerConnection has any RTCRtpSender(s) for the given | ||
* Whether a SafariRTCPeerConnection has any RTCRtpReceivers(s) for the given | ||
* MediaStreamTrack kind. | ||
@@ -325,5 +325,5 @@ * @param {SafariRTCPeerConnection} peerConnection | ||
*/ | ||
function hasSendersForTracksOfKind(peerConnection, kind) { | ||
function hasReceiversForTracksOfKind(peerConnection, kind) { | ||
return !!peerConnection.getTransceivers().find(function(transceiver) { | ||
return transceiver.sender && transceiver.sender.track && transceiver.sender.track.kind === kind; | ||
return transceiver.receiver && transceiver.receiver.track && transceiver.receiver.track.kind === kind; | ||
}); | ||
@@ -330,0 +330,0 @@ } |
@@ -112,11 +112,12 @@ 'use strict'; | ||
function guessBrowser() { | ||
if (typeof webkitRTCPeerConnection !== 'undefined') { | ||
return 'chrome'; | ||
} else if (typeof mozRTCPeerConnection !== 'undefined') { | ||
return 'firefox'; | ||
} else if (typeof RTCPeerConnection !== 'undefined') { | ||
if (typeof navigator !== 'undefined' && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { | ||
if (typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string') { | ||
if (/Chrome/.test(navigator.userAgent)) { | ||
return 'chrome'; | ||
} | ||
if (/Firefox/.test(navigator.userAgent)) { | ||
return 'firefox'; | ||
} | ||
if (/Safari/.test(navigator.userAgent)) { | ||
return 'safari'; | ||
} | ||
// NOTE(mroberts): Could be Edge. | ||
} | ||
@@ -123,0 +124,0 @@ return null; |
@@ -0,6 +1,81 @@ | ||
/* globals RTCPeerConnection, RTCRtpTransceiver */ | ||
'use strict'; | ||
var flatMap = require('./').flatMap; | ||
var guessBrowser = require('./').guessBrowser; | ||
// NOTE(mmalavalli): We cache Chrome's sdpSemantics support in order to prevent | ||
// instantiation of more than one RTCPeerConnection. | ||
var isSdpSemanticsSupported; | ||
/** | ||
* Check if Chrome supports specifying sdpSemantics for an RTCPeerConnection. | ||
* @return {boolean} | ||
*/ | ||
function checkIfSdpSemanticsIsSupported() { | ||
if (typeof isSdpSemanticsSupported === 'boolean') { | ||
return isSdpSemanticsSupported; | ||
} | ||
if (typeof RTCPeerConnection === 'undefined') { | ||
isSdpSemanticsSupported = false; | ||
return isSdpSemanticsSupported; | ||
} | ||
try { | ||
new RTCPeerConnection({ sdpSemantics: 'foo' }); | ||
isSdpSemanticsSupported = false; | ||
} catch (e) { | ||
isSdpSemanticsSupported = true; | ||
} | ||
return isSdpSemanticsSupported; | ||
} | ||
// NOTE(mmalavalli): We cache Chrome's SDP format in order to prevent | ||
// instantiation of more than one RTCPeerConnection. | ||
var chromeSdpFormat; | ||
/** | ||
* Get Chrome's default SDP format. | ||
* @returns {'planb'|'unified'} | ||
*/ | ||
function getChromeSdpFormat() { | ||
if (typeof chromeSdpFormat === 'string') { | ||
return chromeSdpFormat; | ||
} | ||
if (checkIfSdpSemanticsIsSupported()) { | ||
chromeSdpFormat = 'planb'; | ||
return chromeSdpFormat; | ||
} | ||
chromeSdpFormat = typeof RTCPeerConnection !== 'undefined' | ||
&& 'addStream' in RTCPeerConnection.prototype | ||
? 'planb' | ||
: 'unified'; | ||
return chromeSdpFormat; | ||
} | ||
/** | ||
* Get Safari's default SDP format. | ||
* @returns {'planb'|'unified'} | ||
*/ | ||
function getSafariSdpFormat() { | ||
return typeof RTCRtpTransceiver !== 'undefined' | ||
&& 'currentDirection' in RTCRtpTransceiver.prototype | ||
? 'unified' | ||
: 'planb'; | ||
} | ||
/** | ||
* Get the browser's default SDP format. | ||
* @returns {'planb'|'unified'} | ||
*/ | ||
function getSdpFormat() { | ||
return { | ||
chrome: getChromeSdpFormat(), | ||
firefox: 'unified', | ||
safari: getSafariSdpFormat() | ||
}[guessBrowser()] || null; | ||
} | ||
/** | ||
* Match a pattern across lines, returning the first capture group for any | ||
@@ -209,29 +284,3 @@ * matches. | ||
// NOTE(mroberts): We need to cache this result so that we don't create too many | ||
// RTCPeerConnections. | ||
var sdpSemanticsIsSupported; | ||
/** | ||
* Check whether or not `sdpSemantics` is supported. | ||
* @returns {boolean} | ||
*/ | ||
function checkIfSdpSemanticsIsSupported() { | ||
if (typeof sdpSemanticsIsSupported === 'boolean') { | ||
return sdpSemanticsIsSupported; | ||
} | ||
if (typeof RTCPeerConnection === 'undefined') { | ||
sdpSemanticsIsSupported = false; | ||
return sdpSemanticsIsSupported; | ||
} | ||
try { | ||
new RTCPeerConnection({ sdpSemantics: 'bogus' }); | ||
sdpSemanticsIsSupported = false; | ||
return sdpSemanticsIsSupported; | ||
} catch (error) { | ||
sdpSemanticsIsSupported = true; | ||
return sdpSemanticsIsSupported; | ||
} | ||
} | ||
exports.checkIfSdpSemanticsIsSupported = checkIfSdpSemanticsIsSupported; | ||
exports.getSdpFormat = getSdpFormat; | ||
exports.getMediaSections = getMediaSections; | ||
@@ -238,0 +287,0 @@ exports.getPlanBTrackIds = getPlanBTrackIds; |
{ | ||
"name": "@twilio/webrtc", | ||
"version": "3.2.0", | ||
"version": "4.0.0", | ||
"description": "WebRTC-related APIs and shims used by twilio-video.js", | ||
@@ -5,0 +5,0 @@ "scripts": { |
110892
3%2584
3.78%