/**
* Function that modifies the session description to configure settings for OPUS audio codec.
* @method _setSDPOpusConfig
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._setSDPOpusConfig = function(targetMid, sessionDescription) {
var sdpLines = sessionDescription.sdp.split('\r\n');
var payload = null;
var appendFmtpLineAtIndex = -1;
var userAudioSettings = this.getPeerInfo().settings.audio;
var opusSettings = {
useinbandfec: null,
usedtx: null,
maxplaybackrate: null,
stereo: false
};
if (userAudioSettings && typeof userAudioSettings === 'object') {
opusSettings.stereo = userAudioSettings.stereo === true;
opusSettings.useinbandfec = typeof userAudioSettings.useinbandfec === 'boolean' ? userAudioSettings.useinbandfec : null;
opusSettings.usedtx = typeof userAudioSettings.usedtx === 'boolean' ? userAudioSettings.usedtx : null;
opusSettings.maxplaybackrate = typeof userAudioSettings.maxplaybackrate === 'number' ? userAudioSettings.maxplaybackrate : null;
}
// Find OPUS RTPMAP line
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('a=rtpmap:') === 0 && (sdpLines[i].toLowerCase()).indexOf('opus/48000') > 0) {
payload = (sdpLines[i].split(' ')[0] || '').split(':')[1] || null;
appendFmtpLineAtIndex = i;
break;
}
}
if (!payload) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Failed to find OPUS payload. Not configuring options.']);
return sessionDescription.sdp;
}
// Set OPUS FMTP line
for (var j = 0; j < sdpLines.length; j++) {
if (sdpLines[j].indexOf('a=fmtp:' + payload) === 0) {
var opusConfigs = (sdpLines[j].split('a=fmtp:' + payload)[1] || '').replace(/\s/g, '').split(';');
var updatedOpusParams = '';
for (var k = 0; k < opusConfigs.length; k++) {
if (!(opusConfigs[k] && opusConfigs[k].indexOf('=') > 0)) {
continue;
}
var params = opusConfigs[k].split('=');
if (['useinbandfec', 'usedtx', 'sprop-stereo', 'stereo', 'maxplaybackrate'].indexOf(params[0]) > -1) {
// Get default OPUS useinbandfec
if (params[0] === 'useinbandfec' && params[1] === '1' && opusSettings.useinbandfec === null) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Received OPUS useinbandfec as true by default.']);
opusSettings.useinbandfec = true;
// Get default OPUS usedtx
} else if (params[0] === 'usedtx' && params[1] === '1' && opusSettings.usedtx === null) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Received OPUS usedtx as true by default.']);
opusSettings.usedtx = true;
// Get default OPUS maxplaybackrate
} else if (params[0] === 'maxplaybackrate' && parseInt(params[1] || '0', 10) > 0 && opusSettings.maxplaybackrate === null) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Received OPUS maxplaybackrate as ' + params[1] + ' by default.']);
opusSettings.maxplaybackrate = params[1];
}
} else {
updatedOpusParams += opusConfigs[k] + ';';
}
}
if (opusSettings.stereo === true) {
updatedOpusParams += 'stereo=1;';
}
if (opusSettings.useinbandfec === true) {
updatedOpusParams += 'useinbandfec=1;';
}
if (opusSettings.usedtx === true) {
updatedOpusParams += 'usedtx=1;';
}
if (opusSettings.maxplaybackrate) {
updatedOpusParams += 'maxplaybackrate=' + opusSettings.maxplaybackrate + ';';
}
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Updated OPUS parameters ->'], updatedOpusParams);
sdpLines[j] = 'a=fmtp:' + payload + ' ' + updatedOpusParams;
appendFmtpLineAtIndex = -1;
break;
}
}
if (appendFmtpLineAtIndex > 0) {
var newFmtpLine = 'a=fmtp:' + payload + ' ';
if (opusSettings.stereo === true) {
newFmtpLine += 'stereo=1;';
}
if (opusSettings.useinbandfec === true) {
newFmtpLine += 'useinbandfec=1;';
}
if (opusSettings.usedtx === true) {
newFmtpLine += 'usedtx=1;';
}
if (opusSettings.maxplaybackrate) {
newFmtpLine += 'maxplaybackrate=' + opusSettings.maxplaybackrate + ';';
}
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Created OPUS parameters ->'], newFmtpLine);
sdpLines.splice(appendFmtpLineAtIndex + 1, 0, newFmtpLine);
}
return sdpLines.join('\r\n');
};
/**
* Function that modifies the session description to limit the maximum sending bandwidth.
* Setting this may not necessarily work in Firefox.
* @method _setSDPBitrate
* @private
* @for Skylink
* @since 0.5.10
*/
Skylink.prototype._setSDPBitrate = function(targetMid, sessionDescription) {
var sdpLines = sessionDescription.sdp.split('\r\n');
var parseFn = function (type, bw) {
var mLineType = type;
var mLineIndex = -1;
var cLineIndex = -1;
if (type === 'data') {
mLineType = 'application';
}
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('m=' + mLineType) === 0) {
mLineIndex = i;
} else if (mLineIndex > 0) {
if (sdpLines[i].indexOf('m=') === 0) {
break;
}
if (sdpLines[i].indexOf('c=') === 0) {
cLineIndex = i;
// Remove previous b:AS settings
} else if (sdpLines[i].indexOf('b=AS:') === 0 || sdpLines[i].indexOf('b:TIAS:') === 0) {
sdpLines.splice(i, 1);
i--;
}
}
}
if (!(typeof bw === 'number' && bw > 0)) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Not limiting "' + type + '" bandwidth']);
return;
}
if (cLineIndex === -1) {
log.error([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Failed setting "' +
type + '" bandwidth as c-line is missing.']);
return;
}
// Follow RFC 4566, that the b-line should follow after c-line.
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Limiting maximum sending "' + type + '" bandwidth ->'], bw);
sdpLines.splice(cLineIndex + 1, 0, window.webrtcDetectedBrowser === 'firefox' ? 'b=TIAS:' + (bw * 1024) : 'b=AS:' + bw);
};
parseFn('audio', this._streamsBandwidthSettings.bAS.audio);
parseFn('video', this._streamsBandwidthSettings.bAS.video);
parseFn('data', this._streamsBandwidthSettings.bAS.data);
// Sets the experimental google bandwidth
if ((typeof this._streamsBandwidthSettings.googleX.min === 'number') || (typeof this._streamsBandwidthSettings.googleX.max === 'number')) {
var codec = null;
var codecRtpMapLineIndex = -1;
var codecFmtpLineIndex = -1;
for (var j = 0; j < sdpLines.length; j++) {
if (sdpLines[j].indexOf('m=video') === 0) {
codec = sdpLines[j].split(' ')[3];
} else if (codec) {
if (sdpLines[j].indexOf('m=') === 0) {
break;
}
if (sdpLines[j].indexOf('a=rtpmap:' + codec + ' ') === 0) {
codecRtpMapLineIndex = j;
} else if (sdpLines[j].indexOf('a=fmtp:' + codec + ' ') === 0) {
sdpLines[j] = sdpLines[j].replace(/x-google-(min|max)-bitrate=[0-9]*[;]*/gi, '');
codecFmtpLineIndex = j;
break;
}
}
}
if (codecRtpMapLineIndex > -1) {
var xGoogleParams = '';
if (typeof this._streamsBandwidthSettings.googleX.min === 'number') {
xGoogleParams += 'x-google-min-bitrate=' + this._streamsBandwidthSettings.googleX.min + ';';
}
if (typeof this._streamsBandwidthSettings.googleX.max === 'number') {
xGoogleParams += 'x-google-max-bitrate=' + this._streamsBandwidthSettings.googleX.max + ';';
}
if (codecFmtpLineIndex > -1) {
sdpLines[codecFmtpLineIndex] += (sdpLines[codecFmtpLineIndex].split(' ')[1] ? ';' : '') + xGoogleParams;
} else {
sdpLines.splice(codecRtpMapLineIndex + 1, 0, 'a=fmtp:' + codec + ' ' + xGoogleParams);
}
}
}
return sdpLines.join('\r\n');
};
/**
* Function that modifies the session description to set the preferred audio/video codec.
* @method _setSDPCodec
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._setSDPCodec = function(targetMid, sessionDescription) {
var sdpLines = sessionDescription.sdp.split('\r\n');
var parseFn = function (type, codec) {
if (codec === 'auto') {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Not preferring any codec for "' + type + '" streaming. Using browser selection.']);
return;
}
// Find the codec first
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('a=rtpmap:') === 0 && (sdpLines[i].toLowerCase()).indexOf(codec.toLowerCase()) > 0) {
var payload = sdpLines[i].split(':')[1].split(' ')[0] || null;
if (!payload) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Not preferring "' +
codec + '" for "' + type + '" streaming as payload is not found.']);
return;
}
for (var j = 0; j < sdpLines.length; j++) {
if (sdpLines[j].indexOf('m=' + type) === 0) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Preferring "' +
codec + '" for "' + type + '" streaming.']);
var parts = sdpLines[j].split(' ');
if (parts.indexOf(payload) >= 3) {
parts.splice(parts.indexOf(payload), 1);
}
// Example: m=audio 9 UDP/TLS/RTP/SAVPF 111
parts.splice(3, 0, payload);
sdpLines[j] = parts.join(' ');
break;
}
}
}
}
};
parseFn('audio', this._selectedAudioCodec);
parseFn('video', this._selectedVideoCodec);
return sdpLines.join('\r\n');
};
/**
* Function that modifies the session description to remove the previous experimental H264
* codec that is apparently breaking connections.
* NOTE: We should perhaps not remove it since H264 is supported?
* @method _removeSDPFirefoxH264Pref
* @private
* @for Skylink
* @since 0.5.2
*/
Skylink.prototype._removeSDPFirefoxH264Pref = function(targetMid, sessionDescription) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Removing Firefox experimental H264 flag to ensure interopability reliability']);
return sessionDescription.sdp.replace(/a=fmtp:0 profile-level-id=0x42e00c;packetization-mode=1\r\n/g, '');
};
/**
* Function that modifies the session description to append the MediaStream and MediaStreamTrack IDs that seems
* to be missing from Firefox answer session description to Chrome connection causing freezes in re-negotiation.
* @method _addSDPMediaStreamTrackIDs
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._addSDPMediaStreamTrackIDs = function (targetMid, sessionDescription) {
if (!(this._peerConnections[targetMid] && this._peerConnections[targetMid].getLocalStreams().length > 0)) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Not enforcing MediaStream IDs as no Streams is sent.']);
return sessionDescription.sdp;
}
var sessionDescriptionStr = sessionDescription.sdp;
if (!this._enableIceTrickle) {
sessionDescriptionStr = sessionDescriptionStr.replace(/a=end-of-candidates\r\n/g, '');
}
var sdpLines = sessionDescriptionStr.split('\r\n');
var agent = ((this._peerInformations[targetMid] || {}).agent || {}).name || '';
var localStream = this._peerConnections[targetMid].getLocalStreams()[0];
var localStreamId = localStream.id || localStream.label;
var parseFn = function (type, tracks) {
if (tracks.length === 0) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Not enforcing "' + type + '" MediaStreamTrack IDs as no Stream "' + type + '" tracks is sent.']);
return;
}
var trackId = tracks[0].id || tracks[0].label;
var trackLabel = tracks[0].label || 'Default';
var ssrcId = null;
var hasReachedType = false;
// Get SSRC ID
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('m=' + type) === 0) {
if (!hasReachedType) {
hasReachedType = true;
continue;
} else {
break;
}
}
if (hasReachedType && sdpLines[i].indexOf('a=ssrc:') === 0) {
ssrcId = (sdpLines[i].split(':')[1] || '').split(' ')[0] || null;
var msidLine = 'a=ssrc:' + ssrcId + ' msid:' + localStreamId + ' ' + trackId;
var mslabelLine = 'a=ssrc:' + ssrcId + ' mslabel:' + trackLabel;
var labelLine = 'a=ssrc:' + ssrcId + ' label:' + trackLabel;
if (sdpLines.indexOf(msidLine) === -1) {
sdpLines.splice(i + 1, 0, msidLine);
i++;
}
if (sdpLines.indexOf(mslabelLine) === -1) {
sdpLines.splice(i + 1, 0, mslabelLine);
i++;
}
if (sdpLines.indexOf(labelLine) === -1) {
sdpLines.splice(i + 1, 0, labelLine);
i++;
}
break;
}
}
};
parseFn('audio', localStream.getAudioTracks());
parseFn('video', localStream.getVideoTracks());
// Signaling end-of-candidates
if (!this._enableIceTrickle){
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('a=candidate:') === 0) {
if (sdpLines[i + 1] ? !(sdpLines[i + 1].indexOf('a=candidate:') === 0 ||
sdpLines[i + 1].indexOf('a=end-of-candidates') === 0) : true) {
sdpLines.splice(i + 1, 0, 'a=end-of-candidates');
i++;
}
}
}
}
return sdpLines.join('\r\n');
};
/**
* Function that modifies the session description to remove VP9 and H264 apt/rtx lines to prevent plugin connection breaks.
* @method _removeSDPH264VP9AptRtxForOlderPlugin
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._removeSDPH264VP9AptRtxForOlderPlugin = function (targetMid, sessionDescription) {
var removeVP9AptRtxPayload = false;
var agent = (this._peerInformations[targetMid] || {}).agent || {};
if (agent.pluginVersion) {
// 0.8.870 supports
var parts = agent.pluginVersion.split('.');
removeVP9AptRtxPayload = parseInt(parts[0], 10) >= 0 && parseInt(parts[1], 10) >= 8 &&
parseInt(parts[2], 10) >= 870;
}
// Remove rtx or apt= lines that prevent connections for browsers without VP8 or VP9 support
// See: https://bugs.chromium.org/p/webrtc/issues/detail?id=3962
if (['chrome', 'opera'].indexOf(window.webrtcDetectedBrowser) > -1 && removeVP9AptRtxPayload) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Removing VP9/H264 apt= and rtx payload lines causing connectivity issues']);
sessionDescription.sdp = sessionDescription.sdp.replace(/a=rtpmap:\d+ rtx\/\d+\r\na=fmtp:\d+ apt=101\r\n/g, '');
sessionDescription.sdp = sessionDescription.sdp.replace(/a=rtpmap:\d+ rtx\/\d+\r\na=fmtp:\d+ apt=107\r\n/g, '');
}
return sessionDescription.sdp;
};
/**
* Function that modifies the session description to remove codecs.
* @method _removeSDPCodecs
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._removeSDPCodecs = function (targetMid, sessionDescription) {
var audioSettings = this.getPeerInfo().settings.audio;
var parseFn = function (type, codec) {
var payloadList = sessionDescription.sdp.match(new RegExp('a=rtpmap:(\\d*)\\ ' + codec + '.*', 'gi'));
if (!(Array.isArray(payloadList) && payloadList.length > 0)) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Not removing "' + codec + '" as it does not exists.']);
return;
}
for (var i = 0; i < payloadList.length; i++) {
var payload = payloadList[i].split(' ')[0].split(':')[1];
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Removing "' + codec + '" payload ->'], payload);
sessionDescription.sdp = sessionDescription.sdp.replace(
new RegExp('a=rtpmap:' + payload + '\\ .*\\r\\n', 'g'), '');
sessionDescription.sdp = sessionDescription.sdp.replace(
new RegExp('a=fmtp:' + payload + '\\ .*\\r\\n', 'g'), '');
sessionDescription.sdp = sessionDescription.sdp.replace(
new RegExp('a=rtpmap:\\d+ rtx\\/\\d+\\r\\na=fmtp:\\d+ apt=' + payload + '\\r\\n', 'g'), '');
// Remove the m-line codec
var sdpLines = sessionDescription.sdp.split('\r\n');
for (var j = 0; j < sdpLines.length; j++) {
if (sdpLines[j].indexOf('m=' + type) === 0) {
var parts = sdpLines[j].split(' ');
if (parts.indexOf(payload) >= 3) {
parts.splice(parts.indexOf(payload), 1);
}
sdpLines[j] = parts.join(' ');
break;
}
}
sessionDescription.sdp = sdpLines.join('\r\n');
}
};
if (this._disableVideoFecCodecs) {
if (this._hasMCU) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Not removing "ulpfec" or "red" codecs as connected to MCU to prevent connectivity issues.']);
} else {
parseFn('video', 'red');
parseFn('video', 'ulpfec');
}
}
if (this._disableComfortNoiseCodec && audioSettings && typeof audioSettings === 'object' && audioSettings.stereo) {
parseFn('audio', 'CN');
}
return sessionDescription.sdp;
};
/**
* Function that modifies the session description to remove REMB packets fb.
* @method _removeSDPREMBPackets
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._removeSDPREMBPackets = function (targetMid, sessionDescription) {
if (!this._disableREMB) {
return sessionDescription.sdp;
}
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Removing REMB packets.']);
return sessionDescription.sdp.replace(/a=rtcp-fb:\d+ goog-remb\r\n/g, '');
};
/**
* Function that retrieves the session description selected codec.
* @method _getSDPSelectedCodec
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._getSDPSelectedCodec = function (targetMid, sessionDescription, type) {
if (!(sessionDescription && sessionDescription.sdp)) {
return null;
}
var sdpLines = sessionDescription.sdp.split('\r\n');
var selectedCodecInfo = {
name: null,
implementation: null,
clockRate: null,
channels: null,
payloadType: null,
params: null
};
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('m=' + type) === 0) {
var parts = sdpLines[i].split(' ');
if (parts.length < 4) {
break;
}
selectedCodecInfo.payloadType = parseInt(parts[3], 10);
} else if (selectedCodecInfo.payloadType !== null) {
if (sdpLines[i].indexOf('m=') === 0) {
break;
}
if (sdpLines[i].indexOf('a=rtpmap:' + selectedCodecInfo.payloadType + ' ') === 0) {
var params = (sdpLines[i].split(' ')[1] || '').split('/');
selectedCodecInfo.name = params[0] || '';
selectedCodecInfo.clockRate = params[1] ? parseInt(params[1], 10) : null;
selectedCodecInfo.channels = params[2] ? parseInt(params[2], 10) : null;
} else if (sdpLines[i].indexOf('a=fmtp:' + selectedCodecInfo.payloadType + ' ') === 0) {
selectedCodecInfo.params = sdpLines[i].split('a=fmtp:' + selectedCodecInfo.payloadType + ' ')[1] || null;
}
}
}
log.debug([targetMid, 'RTCSessionDesription', sessionDescription.type,
'Parsing session description "' + type + '" codecs ->'], selectedCodecInfo);
return selectedCodecInfo;
};
/**
* Function that modifies the session description to remove non-relay ICE candidates.
* @method _removeSDPFilteredCandidates
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._removeSDPFilteredCandidates = function (targetMid, sessionDescription) {
// Handle Firefox MCU Peer ICE candidates
if (targetMid === 'MCU' && sessionDescription.type === this.HANDSHAKE_PROGRESS.ANSWER &&
window.webrtcDetectedBrowser === 'firefox') {
sessionDescription.sdp = sessionDescription.sdp.replace(/ generation 0/g, '');
sessionDescription.sdp = sessionDescription.sdp.replace(/ udp /g, ' UDP ');
}
if (this._forceTURN && this._hasMCU) {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Not filtering ICE candidates as ' +
'TURN connections are enforced as MCU is present (and act as a TURN itself) so filtering of ICE candidate ' +
'flags are not honoured']);
return sessionDescription.sdp;
}
if (this._filterCandidatesType.host) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Removing "host" ICE candidates.']);
sessionDescription.sdp = sessionDescription.sdp.replace(/a=candidate:.*host.*\r\n/g, '');
}
if (this._filterCandidatesType.srflx) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Removing "srflx" ICE candidates.']);
sessionDescription.sdp = sessionDescription.sdp.replace(/a=candidate:.*srflx.*\r\n/g, '');
}
if (this._filterCandidatesType.relay) {
log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Removing "relay" ICE candidates.']);
sessionDescription.sdp = sessionDescription.sdp.replace(/a=candidate:.*relay.*\r\n/g, '');
}
// sessionDescription.sdp = sessionDescription.sdp.replace(/a=candidate:(?!.*relay.*).*\r\n/g, '');
return sessionDescription.sdp;
};
/**
* Function that modifies the session description to handle the connection settings.
* This is experimental and never recommended to end-users.
* @method _handleSDPConnectionSettings
* @private
* @for Skylink
* @since 0.6.16
*/
Skylink.prototype._handleSDPConnectionSettings = function (targetMid, sessionDescription) {
var self = this;
var sdpLines = sessionDescription.sdp.split('\r\n');
var mediaType = '';
for (var i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].indexOf('m=') === 0) {
mediaType = (sdpLines[i].split('m=')[1] || '').split(' ')[0] || '';
}
if (mediaType) {
if (!self._sdpSettings.connection[mediaType === 'application' ? 'data' : mediaType] && targetMid !== 'MCU') {
sdpLines.splice(i, 1);
i--;
} else if (mediaType && ['audio', 'video'].indexOf(mediaType) > -1 &&
['a=sendrecv', 'a=sendonly', 'a=recvonly'].indexOf(sdpLines[i]) > -1) {
if (self._hasMCU) {
sdpLines[i] = targetMid === 'MCU' ? 'a=sendonly' : 'a=recvonly';
}
if (self._sdpSettings.direction[mediaType].send && !self._sdpSettings.direction[mediaType].receive) {
sdpLines[i] = sdpLines[i].indexOf('send') > -1 ? 'a=sendonly' : 'a=inactive';
} else if (!self._sdpSettings.direction[mediaType].send && self._sdpSettings.direction[mediaType].receive) {
sdpLines[i] = sdpLines[i].indexOf('recv') > -1 ? 'a=recvonly' : 'a=inactive';
} else if (!self._sdpSettings.direction[mediaType].send && !self._sdpSettings.direction[mediaType].receive) {
sdpLines[i] = 'a=inactive';
}
// MCU currently does not support a=inactive flag
if (!self._hasMCU) {
var agent = ((self._peerInformations[targetMid] || {}).agent || {}).name || '';
// Handle Chrome bundle bug. - See: https://bugs.chromium.org/p/webrtc/issues/detail?id=6280
if (window.webrtcDetectedBrowser !== 'firefox' && agent === 'firefox' &&
sessionDescription.type === self.HANDSHAKE_PROGRESS.OFFER && sdpLines[i] === 'a=recvonly') {
log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Overriding any original settings ' +
'to receive only to send and receive to resolve chrome BUNDLE errors.']);
sdpLines[i] = 'a=sendrecv';
}
}
}
}
}
return sdpLines.join('\r\n');
};