- /**
- * Function that modifies the session description to configure settings for OPUS audio codec.
- * @method _setSDPCodecParams
- * @private
- * @for Skylink
- * @since 0.6.16
- */
- Skylink.prototype._setSDPCodecParams = function(targetMid, sessionDescription) {
- var self = this;
-
- var parseFn = function (type, codecName, samplingRate, settings) {
- var mLine = sessionDescription.sdp.match(new RegExp('m=' + type + '\ .*\r\n', 'gi'));
- // Find the m= line
- if (Array.isArray(mLine) && mLine.length > 0) {
- var codecsList = sessionDescription.sdp.match(new RegExp('a=rtpmap:.*\ ' + codecName + '\/' +
- (samplingRate ? samplingRate + (type === 'audio' ? '[\/]*.*' : '.*') : '.*') + '\r\n', 'gi'));
- // Get the list of codecs related to it
- if (Array.isArray(codecsList) && codecsList.length > 0) {
- for (var i = 0; i < codecsList.length; i++) {
- var payload = (codecsList[i].split('a=rtpmap:')[1] || '').split(' ')[0];
- if (!payload) {
- continue;
- }
- var fmtpLine = sessionDescription.sdp.match(new RegExp('a=fmtp:' + payload + '\ .*\r\n', 'gi'));
- var updatedFmtpLine = 'a=fmtp:' + payload + ' ';
- var addedKeys = [];
- // Check if a=fmtp: line exists
- if (Array.isArray(fmtpLine) && fmtpLine.length > 0) {
- var fmtpParts = (fmtpLine[0].split('a=fmtp:' + payload + ' ')[1] || '').replace(
- / /g, '').replace(/\r\n/g, '').split(';');
- for (var j = 0; j < fmtpParts.length; j++) {
- if (!fmtpParts[j]) {
- continue;
- }
- var keyAndValue = fmtpParts[j].split('=');
- if (settings.hasOwnProperty(keyAndValue[0])) {
- // Dont append parameter key+value if boolean and false
- updatedFmtpLine += typeof settings[keyAndValue[0]] === 'boolean' ? (settings[keyAndValue[0]] ?
- keyAndValue[0] + '=1;' : '') : keyAndValue[0] + '=' + settings[keyAndValue[0]] + ';';
- } else {
- updatedFmtpLine += fmtpParts[j] + ';';
- }
- addedKeys.push(keyAndValue[0]);
- }
- sessionDescription.sdp = sessionDescription.sdp.replace(fmtpLine[0], '');
- }
- for (var key in settings) {
- if (settings.hasOwnProperty(key) && addedKeys.indexOf(key) === -1) {
- // Dont append parameter key+value if boolean and false
- updatedFmtpLine += typeof settings[key] === 'boolean' ? (settings[key] ? key + '=1;' : '') :
- key + '=' + settings[key] + ';';
- addedKeys.push(key);
- }
- }
- if (updatedFmtpLine !== 'a=fmtp:' + payload + ' ') {
- sessionDescription.sdp = sessionDescription.sdp.replace(codecsList[i], codecsList[i] + updatedFmtpLine + '\r\n');
- }
- }
- }
- }
- };
-
- // Set audio codecs -> OPUS
- // RFC: https://tools.ietf.org/html/draft-ietf-payload-rtp-opus-11
- parseFn('audio', self.AUDIO_CODEC.OPUS, 48000, (function () {
- var opusOptions = {};
- var audioSettings = self._streams.screenshare ? self._streams.screenshare.settings.audio :
- (self._streams.userMedia ? self._streams.userMedia.settings.audio : {});
- audioSettings = audioSettings && typeof audioSettings === 'object' ? audioSettings : {};
- if (typeof self._initOptions.codecParams.audio.opus.stereo === 'boolean') {
- opusOptions.stereo = self._initOptions.codecParams.audio.opus.stereo;
- } else if (typeof audioSettings.stereo === 'boolean') {
- opusOptions.stereo = audioSettings.stereo;
- }
- if (typeof self._initOptions.codecParams.audio.opus['sprop-stereo'] === 'boolean') {
- opusOptions['sprop-stereo'] = self._initOptions.codecParams.audio.opus['sprop-stereo'];
- } else if (typeof audioSettings.stereo === 'boolean') {
- opusOptions['sprop-stereo'] = audioSettings.stereo;
- }
- if (typeof self._initOptions.codecParams.audio.opus.usedtx === 'boolean') {
- opusOptions.usedtx = self._initOptions.codecParams.audio.opus.usedtx;
- } else if (typeof audioSettings.usedtx === 'boolean') {
- opusOptions.usedtx = audioSettings.usedtx;
- }
- if (typeof self._initOptions.codecParams.audio.opus.useinbandfec === 'boolean') {
- opusOptions.useinbandfec = self._initOptions.codecParams.audio.opus.useinbandfec;
- } else if (typeof audioSettings.useinbandfec === 'boolean') {
- opusOptions.useinbandfec = audioSettings.useinbandfec;
- }
- if (typeof self._initOptions.codecParams.audio.opus.maxplaybackrate === 'number') {
- opusOptions.maxplaybackrate = self._initOptions.codecParams.audio.opus.maxplaybackrate;
- } else if (typeof audioSettings.maxplaybackrate === 'number') {
- opusOptions.maxplaybackrate = audioSettings.maxplaybackrate;
- }
- if (typeof self._initOptions.codecParams.audio.opus.minptime === 'number') {
- opusOptions.minptime = self._initOptions.codecParams.audio.opus.minptime;
- } else if (typeof audioSettings.minptime === 'number') {
- opusOptions.minptime = audioSettings.minptime;
- }
- // Possible future params: sprop-maxcapturerate, maxaveragebitrate, sprop-stereo, cbr
- // NOT recommended: maxptime, ptime, rate, minptime
- return opusOptions;
- })());
-
- // RFC: https://tools.ietf.org/html/rfc4733
- // Future: Set telephone-event: 100 0-15,66,70
-
- // RFC: https://tools.ietf.org/html/draft-ietf-payload-vp8-17
- // Set video codecs -> VP8
- parseFn('video', self.VIDEO_CODEC.VP8, null, (function () {
- var vp8Options = {};
- // NOT recommended: max-fr, max-fs (all are codec decoder capabilities)
- if (typeof self._initOptions.codecParams.video.vp8.maxFr === 'number') {
- vp8Options['max-fr'] = self._initOptions.codecParams.video.vp8.maxFr;
- }
- if (typeof self._initOptions.codecParams.video.vp8.maxFs === 'number') {
- vp8Options['max-fs'] = self._initOptions.codecParams.video.vp8.maxFs;
- }
- return vp8Options;
- })());
-
- // RFC: https://tools.ietf.org/html/draft-ietf-payload-vp9-02
- // Set video codecs -> VP9
- parseFn('video', self.VIDEO_CODEC.VP9, null, (function () {
- var vp9Options = {};
- // NOT recommended: max-fr, max-fs (all are codec decoder capabilities)
- if (typeof self._initOptions.codecParams.video.vp9.maxFr === 'number') {
- vp9Options['max-fr'] = self._initOptions.codecParams.video.vp9.maxFr;
- }
- if (typeof self._initOptions.codecParams.video.vp9.maxFs === 'number') {
- vp9Options['max-fs'] = self._initOptions.codecParams.video.vp9.maxFs;
- }
- return vp9Options;
- })());
-
- // RFC: https://tools.ietf.org/html/rfc6184
- // Set the video codecs -> H264
- parseFn('video', self.VIDEO_CODEC.H264, null, (function () {
- var h264Options = {};
- if (typeof self._initOptions.codecParams.video.h264.levelAsymmetryAllowed === 'string') {
- h264Options['profile-level-id'] = self._initOptions.codecParams.video.h264.profileLevelId;
- }
- if (typeof self._initOptions.codecParams.video.h264.levelAsymmetryAllowed === 'boolean') {
- h264Options['level-asymmetry-allowed'] = self._initOptions.codecParams.video.h264.levelAsymmetryAllowed;
- }
- if (typeof self._initOptions.codecParams.video.h264.packetizationMode === 'boolean') {
- h264Options['packetization-mode'] = self._initOptions.codecParams.video.h264.packetizationMode;
- }
- // Possible future params (remove if they are decoder/encoder capabilities or info):
- // max-recv-level, max-mbps, max-smbps, max-fs, max-cpb, max-dpb, max-br,
- // max-mbps, max-smbps, max-fs, max-cpb, max-dpb, max-br, redundant-pic-cap, sprop-parameter-sets,
- // sprop-level-parameter-sets, use-level-src-parameter-sets, in-band-parameter-sets,
- // sprop-interleaving-depth, sprop-deint-buf-req, deint-buf-cap, sprop-init-buf-time,
- // sprop-max-don-diff, max-rcmd-nalu-size, sar-understood, sar-supported
- // NOT recommended: profile-level-id (WebRTC uses "42e00a" for the moment)
- // https://bugs.chromium.org/p/chromium/issues/detail?id=645599
- return h264Options;
- })());
-
- return sessionDescription.sdp;
- };
-
- /**
- * 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 * 1000 *
- (window.webrtcDetectedVersion > 52 && window.webrtcDetectedVersion < 55 ? 1000 : 1)).toFixed(0) : 'b=AS:' + bw);
- };
-
- var bASAudioBw = this._streamsBandwidthSettings.bAS.audio;
- var bASVideoBw = this._streamsBandwidthSettings.bAS.video;
- var bASDataBw = this._streamsBandwidthSettings.bAS.data;
- var googleXMinBw = this._streamsBandwidthSettings.googleX.min;
- var googleXMaxBw = this._streamsBandwidthSettings.googleX.max;
-
- if (this._peerCustomConfigs[targetMid]) {
- if (this._peerCustomConfigs[targetMid].bandwidth &&
- typeof this._peerCustomConfigs[targetMid].bandwidth === 'object') {
- if (typeof this._peerCustomConfigs[targetMid].bandwidth.audio === 'number') {
- bASAudioBw = this._peerCustomConfigs[targetMid].bandwidth.audio;
- }
- if (typeof this._peerCustomConfigs[targetMid].bandwidth.video === 'number') {
- bASVideoBw = this._peerCustomConfigs[targetMid].bandwidth.video;
- }
- if (typeof this._peerCustomConfigs[targetMid].bandwidth.data === 'number') {
- bASDataBw = this._peerCustomConfigs[targetMid].bandwidth.data;
- }
- }
- if (this._peerCustomConfigs[targetMid].googleXBandwidth &&
- typeof this._peerCustomConfigs[targetMid].googleXBandwidth === 'object') {
- if (typeof this._peerCustomConfigs[targetMid].googleXBandwidth.min === 'number') {
- googleXMinBw = this._peerCustomConfigs[targetMid].googleXBandwidth.min;
- }
- if (typeof this._peerCustomConfigs[targetMid].googleXBandwidth.max === 'number') {
- googleXMaxBw = this._peerCustomConfigs[targetMid].googleXBandwidth.max;
- }
- }
- }
-
- parseFn('audio', bASAudioBw);
- parseFn('video', bASVideoBw);
- parseFn('data', bASDataBw);
-
- // Sets the experimental google bandwidth
- if ((typeof googleXMinBw === 'number') || (typeof googleXMaxBw === '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 googleXMinBw === 'number') {
- xGoogleParams += 'x-google-min-bitrate=' + googleXMinBw + ';';
- }
-
- if (typeof googleXMaxBw === 'number') {
- xGoogleParams += 'x-google-max-bitrate=' + googleXMaxBw + ';';
- }
-
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Limiting x-google-bitrate ->'], xGoogleParams);
-
- 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, overrideSettings) {
- var self = this;
- var parseFn = function (type, codecSettings) {
- var codec = typeof codecSettings === 'object' ? codecSettings.codec : codecSettings;
- var samplingRate = typeof codecSettings === 'object' ? codecSettings.samplingRate : null;
- var channels = typeof codecSettings === 'object' ? codecSettings.channels : null;
-
- if (codec === self[type === 'audio' ? 'AUDIO_CODEC' : 'VIDEO_CODEC'].AUTO) {
- log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Not preferring any codec for "' + type + '" streaming. Using browser selection.']);
- return;
- }
-
- var mLine = sessionDescription.sdp.match(new RegExp('m=' + type + ' .*\r\n', 'gi'));
-
- if (!(Array.isArray(mLine) && mLine.length > 0)) {
- log.error([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Not preferring any codec for "' + type + '" streaming as m= line is not found.']);
- return;
- }
-
- var setLineFn = function (codecsList, isSROk, isChnlsOk) {
- if (Array.isArray(codecsList) && codecsList.length > 0) {
- if (!isSROk) {
- samplingRate = null;
- }
- if (!isChnlsOk) {
- channels = null;
- }
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Preferring "' +
- codec + '" (samplingRate: ' + (samplingRate || 'n/a') + ', channels: ' +
- (channels || 'n/a') + ') for "' + type + '" streaming.']);
-
- var line = mLine[0];
- var lineParts = line.replace('\r\n', '').split(' ');
- // Set the m=x x UDP/xxx
- line = lineParts[0] + ' ' + lineParts[1] + ' ' + lineParts[2] + ' ';
- // Remove them to leave the codecs only
- lineParts.splice(0, 3);
- // Loop for the codecs list to append first
- for (var i = 0; i < codecsList.length; i++) {
- var parts = (codecsList[i].split('a=rtpmap:')[1] || '').split(' ');
- if (parts.length < 2) {
- continue;
- }
- line += parts[0] + ' ';
- }
- // Loop for later fallback codecs to append
- for (var j = 0; j < lineParts.length; j++) {
- if (line.indexOf(' ' + lineParts[j]) > 0) {
- lineParts.splice(j, 1);
- j--;
- } else if (sessionDescription.sdp.match(new RegExp('a=rtpmap:' + lineParts[j] +
- '\ ' + codec + '/.*\r\n', 'gi'))) {
- line += lineParts[j] + ' ';
- lineParts.splice(j, 1);
- j--;
- }
- }
- // Append the rest of the codecs
- line += lineParts.join(' ') + '\r\n';
- sessionDescription.sdp = sessionDescription.sdp.replace(mLine[0], line);
- return true;
- }
- };
-
- // If samplingRate & channels
- if (samplingRate) {
- if (type === 'audio' && channels && setLineFn(sessionDescription.sdp.match(new RegExp('a=rtpmap:.*\ ' +
- codec + '\/' + samplingRate + (channels === 1 ? '[\/1]*' : '\/' + channels) + '\r\n', 'gi')), true, true)) {
- return;
- } else if (setLineFn(sessionDescription.sdp.match(new RegExp('a=rtpmap:.*\ ' + codec + '\/' +
- samplingRate + '[\/]*.*\r\n', 'gi')), true)) {
- return;
- }
- }
- if (type === 'audio' && channels && setLineFn(sessionDescription.sdp.match(new RegExp('a=rtpmap:.*\ ' +
- codec + '\/.*\/' + channels + '\r\n', 'gi')), false, true)) {
- return;
- }
-
- setLineFn(sessionDescription.sdp.match(new RegExp('a=rtpmap:.*\ ' + codec + '\/.*\r\n', 'gi')));
- };
-
- parseFn('audio', overrideSettings ? overrideSettings.audio : self._initOptions.audioCodec);
- parseFn('video', overrideSettings ? overrideSettings.video : self._initOptions.videoCodec);
-
- return sessionDescription.sdp;
- };
-
- /**
- * 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 remove apt/rtx lines that does exists.
- * @method _removeSDPUnknownAptRtx
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._removeSDPUnknownAptRtx = function (targetMid, sessionDescription) {
- var codecsPayload = []; // m=audio 9 UDP/TLS/RTP/SAVPF [Start from index 3] 102 9 0 8 97 13 118 101
- var sdpLines = sessionDescription.sdp.split('\r\n');
- var mediaLines = sessionDescription.sdp.split('m=');
-
- // Remove unmapped rtx lines
- var formatRtx = function (str) {
- (str.match(/a=rtpmap:.*\ rtx\/.*\r\n/gi) || []).forEach(function (line) {
- var payload = (line.split('a=rtpmap:')[1] || '').split(' ')[0] || '';
- var fmtpLine = (str.match(new RegExp('a=fmtp:' + payload + '\ .*\r\n', 'gi')) || [])[0];
-
- if (!fmtpLine) {
- str = str.replace(new RegExp(line, 'g'), '');
- return;
- }
-
- var codecPayload = (fmtpLine.split(' apt=')[1] || '').replace(/\r\n/gi, '');
- var rtmpLine = str.match(new RegExp('a=rtpmap:' + codecPayload + '\ .*\r\n', 'gi'));
-
- if (!rtmpLine) {
- str = str.replace(new RegExp(line, 'g'), '');
- str = str.replace(new RegExp(fmtpLine, 'g'), '');
- }
- });
-
- return str;
- };
-
- // Remove unmapped fmtp and rtcp-fb lines
- var formatFmtpRtcpFb = function (str) {
- (str.match(/a=(fmtp|rtcp-fb):.*\ rtx\/.*\r\n/gi) || []).forEach(function (line) {
- var payload = (line.split('a=' + (line.indexOf('rtcp') > 0 ? 'rtcp-fb' : 'fmtp'))[1] || '').split(' ')[0] || '';
- var rtmpLine = str.match(new RegExp('a=rtpmap:' + payload + '\ .*\r\n', 'gi'));
-
- if (!rtmpLine) {
- str = str.replace(new RegExp(line, 'g'), '');
- }
- });
-
- return str;
- };
-
- // 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
- for (var m = 0; m < mediaLines.length; m++) {
- mediaLines[m] = formatRtx(mediaLines[m]);
- mediaLines[m] = formatFmtpRtcpFb(mediaLines[m]);
- }
-
- return mediaLines.join('m=');
- };
-
- /**
- * 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._initOptions.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._initOptions.disableComfortNoiseCodec && audioSettings && typeof audioSettings === 'object' && audioSettings.stereo) {
- parseFn('audio', 'CN');
- }
-
- if (window.webrtcDetectedBrowser === 'edge' &&
- (((this._peerInformations[targetMid] || {}).agent || {}).name || 'unknown').name !== 'edge') {
- sessionDescription.sdp = sessionDescription.sdp.replace(/a=rtcp-fb:.*\ x-message\ .*\r\n/gi, '');
- }
-
- 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._initOptions.disableREMB) {
- return sessionDescription.sdp;
- }
-
- log.info([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, beSilentOnLogs) {
- var codecInfo = {
- name: null,
- implementation: null,
- clockRate: null,
- channels: null,
- payloadType: null,
- params: null
- };
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return codecInfo;
- }
-
- sessionDescription.sdp.split('m=').forEach(function (mediaItem, index) {
- if (index === 0 || mediaItem.indexOf(type + ' ') !== 0) {
- return;
- }
-
- var codecs = (mediaItem.split('\r\n')[0] || '').split(' ');
- // Remove audio[0] 65266[1] UDP/TLS/RTP/SAVPF[2]
- codecs.splice(0, 3);
-
- for (var i = 0; i < codecs.length; i++) {
- var match = mediaItem.match(new RegExp('a=rtpmap:' + codecs[i] + '.*\r\n', 'gi'));
-
- if (!match) {
- continue;
- }
-
- // Format: codec/clockRate/channels
- var parts = ((match[0] || '').replace(/\r\n/g, '').split(' ')[1] || '').split('/');
-
- // Ignore rtcp codecs, dtmf or comfort noise
- if (['red', 'ulpfec', 'telephone-event', 'cn', 'rtx'].indexOf(parts[0].toLowerCase()) > -1) {
- continue;
- }
-
- codecInfo.name = parts[0];
- codecInfo.clockRate = parseInt(parts[1], 10) || 0;
- codecInfo.channels = parseInt(parts[2] || '1', 10) || 1;
- codecInfo.payloadType = parseInt(codecs[i], 10);
- codecInfo.params = '';
-
- // Get the list of codec parameters
- var params = mediaItem.match(new RegExp('a=fmtp:' + codecs[i] + '.*\r\n', 'gi')) || [];
- params.forEach(function (paramItem) {
- codecInfo.params += paramItem.replace(new RegExp('a=fmtp:' + codecs[i], 'gi'), '').replace(/\ /g, '').replace(/\r\n/g, '');
- });
- break;
- }
- });
-
- if (!beSilentOnLogs) {
- log.debug([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Parsing session description "' + type + '" codecs ->'], codecInfo);
- }
-
- return codecInfo;
- };
-
- /**
- * 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._initOptions.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._initOptions.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._initOptions.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._initOptions.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 retrieves the current list of support codecs.
- * @method _getCodecsSupport
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._getCodecsSupport = function (callback) {
- var self = this;
-
- if (self._currentCodecSupport) {
- callback(null);
- return;
- }
-
- self._currentCodecSupport = { audio: {}, video: {} };
-
- // Safari 11 REQUIRES a stream first before connection works, hence let's spoof it for now
- if (AdapterJS.webrtcDetectedType === 'AppleWebKit') {
- self._currentCodecSupport.audio = {
- opus: ['48000/2']
- };
- self._currentCodecSupport.video = {
- h264: ['48000']
- };
- return callback(null);
- }
-
- try {
- if (window.webrtcDetectedBrowser === 'edge') {
- var codecs = RTCRtpSender.getCapabilities().codecs;
-
- for (var i = 0; i < codecs.length; i++) {
- if (['audio','video'].indexOf(codecs[i].kind) > -1 && codecs[i].name) {
- var codec = codecs[i].name.toLowerCase();
- self._currentCodecSupport[codecs[i].kind][codec] = codecs[i].clockRate +
- (codecs[i].numChannels > 1 ? '/' + codecs[i].numChannels : '');
- }
- }
- // Ignore .fecMechanisms for now
- callback(null);
-
- } else {
- var pc = new RTCPeerConnection(null);
- var offerConstraints = AdapterJS.webrtcDetectedType !== 'plugin' ? {
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- } : {
- mandatory: {
- OfferToReceiveVideo: true,
- OfferToReceiveAudio: true
- }
- };
-
- // Prevent errors and proceed with create offer still...
- try {
- var channel = pc.createDataChannel('test');
- self._binaryChunkType = channel.binaryType || self._binaryChunkType;
- self._binaryChunkType = self._binaryChunkType.toLowerCase().indexOf('array') > -1 ?
- self.DATA_TRANSFER_DATA_TYPE.ARRAY_BUFFER : self._binaryChunkType;
- // Set the value according to the property
- for (var prop in self.DATA_TRANSFER_DATA_TYPE) {
- if (self.DATA_TRANSFER_DATA_TYPE.hasOwnProperty(prop) &&
- self._binaryChunkType.toLowerCase() === self.DATA_TRANSFER_DATA_TYPE[prop].toLowerCase()) {
- self._binaryChunkType = self.DATA_TRANSFER_DATA_TYPE[prop];
- break;
- }
- }
- } catch (e) {}
-
- pc.createOffer(function (offer) {
- self._currentCodecSupport = self._getSDPCodecsSupport(null, offer);
- callback(null);
-
- }, function (error) {
- callback(error);
- }, offerConstraints);
- }
- } catch (error) {
- callback(error);
- }
- };
-
- /**
- * 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, direction) {
- var self = this;
-
- if (!self._sdpSessions[targetMid]) {
- return sessionDescription.sdp;
- }
-
- var sessionDescriptionStr = sessionDescription.sdp;
-
- // Handle a=end-of-candidates signaling for non-trickle ICE before setting remote session description
- if (direction === 'remote' && !self.getPeerInfo(targetMid).config.enableIceTrickle) {
- sessionDescriptionStr = sessionDescriptionStr.replace(/a=end-of-candidates\r\n/g, '');
- }
-
- var sdpLines = sessionDescriptionStr.split('\r\n');
- var peerAgent = ((self._peerInformations[targetMid] || {}).agent || {}).name || '';
- var peerVersion = ((self._peerInformations[targetMid] || {}).agent || {}).version || 0;
- var mediaType = '';
- var bundleLineIndex = -1;
- var bundleLineMids = [];
- var mLineIndex = -1;
- var settings = clone(self._sdpSettings);
-
- if (targetMid === 'MCU') {
- settings.connection.audio = true;
- settings.connection.video = true;
- settings.connection.data = true;
- }
-
- // Patches for MCU sending empty video stream despite audio+video is not sending at all
- // Apply as a=inactive when supported
- if (self._hasMCU) {
- var peerStreamSettings = clone(self.getPeerInfo(targetMid)).settings || {};
- settings.direction.audio.receive = targetMid === 'MCU' ? true : !!peerStreamSettings.audio;
- settings.direction.audio.send = targetMid === 'MCU' ? true : false;
- settings.direction.video.receive = targetMid === 'MCU' ? true : !!peerStreamSettings.video;
- settings.direction.video.send = targetMid === 'MCU' ? true : false;
- }
-
- if (direction === 'remote') {
- var offerCodecs = self._getSDPCommonSupports(targetMid, sessionDescription);
-
- if (!offerCodecs.audio) {
- settings.connection.audio = false;
- }
-
- if (!offerCodecs.video) {
- settings.connection.video = false;
- }
- }
-
- // ANSWERER: Reject only the m= lines. Returned rejected m= lines as well.
- // OFFERER: Remove m= lines
-
- self._sdpSessions[targetMid][direction].mLines = [];
- self._sdpSessions[targetMid][direction].bundleLine = '';
- self._sdpSessions[targetMid][direction].connection = {
- audio: null,
- video: null,
- data: null
- };
-
- for (var i = 0; i < sdpLines.length; i++) {
- // Cache the a=group:BUNDLE line used for remote answer from Edge later
- if (sdpLines[i].indexOf('a=group:BUNDLE') === 0) {
- self._sdpSessions[targetMid][direction].bundleLine = sdpLines[i];
- bundleLineIndex = i;
-
- // Check if there's a need to reject m= line
- } else if (sdpLines[i].indexOf('m=') === 0) {
- mediaType = (sdpLines[i].split('m=')[1] || '').split(' ')[0] || '';
- mediaType = mediaType === 'application' ? 'data' : mediaType;
- mLineIndex++;
-
- self._sdpSessions[targetMid][direction].mLines[mLineIndex] = sdpLines[i];
-
- // Check if there is missing unsupported video codecs support and reject it regardles of MCU Peer or not
- if (!settings.connection[mediaType]) {
- log.log([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Removing rejected m=' + mediaType + ' line ->'], sdpLines[i]);
-
- // Check if answerer and we do not have the power to remove the m line if index is 0
- // Set as a=inactive because we do not have that power to reject it somehow..
- // first m= line cannot be rejected for BUNDLE
- if (self._peerConnectionConfig.bundlePolicy === self.BUNDLE_POLICY.MAX_BUNDLE &&
- bundleLineIndex > -1 && mLineIndex === 0 && (direction === 'remote' ?
- sessionDescription.type === this.HANDSHAKE_PROGRESS.OFFER :
- sessionDescription.type === this.HANDSHAKE_PROGRESS.ANSWER)) {
- log.warn([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Not removing rejected m=' + mediaType + ' line ->'], sdpLines[i]);
- settings.connection[mediaType] = true;
- if (['audio', 'video'].indexOf(mediaType) > -1) {
- settings.direction[mediaType].send = false;
- settings.direction[mediaType].receive = false;
- }
- continue;
- }
-
- if (window.webrtcDetectedBrowser === 'edge') {
- sdpLines.splice(i, 1);
- i--;
- continue;
- } else if (direction === 'remote' || sessionDescription.type === this.HANDSHAKE_PROGRESS.ANSWER) {
- var parts = sdpLines[i].split(' ');
- parts[1] = 0;
- sdpLines[i] = parts.join(' ');
- continue;
- }
- }
- }
-
- if (direction === 'remote' && sdpLines[i].indexOf('a=candidate:') === 0 &&
- !self.getPeerInfo(targetMid).config.enableIceTrickle) {
- if (sdpLines[i + 1] ? !(sdpLines[i + 1].indexOf('a=candidate:') === 0 ||
- sdpLines[i + 1].indexOf('a=end-of-candidates') === 0) : true) {
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Appending end-of-candidates signal for non-trickle ICE connection.']);
- sdpLines.splice(i + 1, 0, 'a=end-of-candidates');
- i++;
- }
- }
-
- if (mediaType) {
- // Remove lines if we are rejecting the media and ensure unless (rejectVideoMedia is true), MCU has to enable those m= lines
- if (!settings.connection[mediaType]) {
- sdpLines.splice(i, 1);
- i--;
-
- // Store the mids session description
- } else if (sdpLines[i].indexOf('a=mid:') === 0) {
- bundleLineMids.push(sdpLines[i].split('a=mid:')[1] || '');
-
- // Configure direction a=sendonly etc for local sessiondescription
- } else if (mediaType && ['a=sendrecv', 'a=sendonly', 'a=recvonly'].indexOf(sdpLines[i]) > -1) {
- if (['audio', 'video'].indexOf(mediaType) === -1) {
- self._sdpSessions[targetMid][direction].connection.data = sdpLines[i];
- continue;
- }
-
- if (direction === 'local') {
- if (settings.direction[mediaType].send && !settings.direction[mediaType].receive) {
- sdpLines[i] = sdpLines[i].indexOf('send') > -1 ? 'a=sendonly' : 'a=inactive';
- } else if (!settings.direction[mediaType].send && settings.direction[mediaType].receive) {
- sdpLines[i] = sdpLines[i].indexOf('recv') > -1 ? 'a=recvonly' : 'a=inactive';
- } else if (!settings.direction[mediaType].send && !settings.direction[mediaType].receive) {
- // MCU currently does not support a=inactive flag.. what do we do here?
- sdpLines[i] = 'a=inactive';
- }
-
- // Handle Chrome bundle bug. - See: https://bugs.chromium.org/p/webrtc/issues/detail?id=6280
- if (!self._hasMCU && window.webrtcDetectedBrowser !== 'firefox' && peerAgent === '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';
- settings.direction[mediaType].send = true;
- settings.direction[mediaType].receive = true;
- }
- // Patch for incorrect responses
- } else if (sessionDescription.type === self.HANDSHAKE_PROGRESS.ANSWER) {
- var localOfferRes = self._sdpSessions[targetMid].local.connection[mediaType];
- // Parse a=sendonly response
- if (localOfferRes === 'a=sendonly') {
- sdpLines[i] = ['a=inactive', 'a=recvonly'].indexOf(sdpLines[i]) === -1 ?
- (sdpLines[i] === 'a=sendonly' ? 'a=inactive' : 'a=recvonly') : sdpLines[i];
- // Parse a=recvonly
- } else if (localOfferRes === 'a=recvonly') {
- sdpLines[i] = ['a=inactive', 'a=sendonly'].indexOf(sdpLines[i]) === -1 ?
- (sdpLines[i] === 'a=recvonly' ? 'a=inactive' : 'a=sendonly') : sdpLines[i];
- // Parse a=sendrecv
- } else if (localOfferRes === 'a=inactive') {
- sdpLines[i] = 'a=inactive';
- }
- }
- self._sdpSessions[targetMid][direction].connection[mediaType] = sdpLines[i];
- }
- }
-
- // Remove weird empty characters for Edge case.. :(
- if (!(sdpLines[i] || '').replace(/\n|\r|\s|\ /gi, '')) {
- sdpLines.splice(i, 1);
- i--;
- }
- }
-
- // Fix chrome "offerToReceiveAudio" local offer not removing audio BUNDLE
- if (bundleLineIndex > -1) {
- if (self._peerConnectionConfig.bundlePolicy === self.BUNDLE_POLICY.MAX_BUNDLE) {
- sdpLines[bundleLineIndex] = 'a=group:BUNDLE ' + bundleLineMids.join(' ');
- // Remove a=group:BUNDLE line
- } else if (self._peerConnectionConfig.bundlePolicy === self.BUNDLE_POLICY.NONE) {
- sdpLines.splice(bundleLineIndex, 1);
- }
- }
-
- // Append empty space below
- if (window.webrtcDetectedBrowser !== 'edge') {
- if (!sdpLines[sdpLines.length - 1].replace(/\n|\r|\s/gi, '')) {
- sdpLines[sdpLines.length - 1] = '';
- } else {
- sdpLines.push('');
- }
- }
-
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Handling connection lines and direction ->'], settings);
-
- return sdpLines.join('\r\n');
- };
-
- /**
- * Function that parses and retrieves the session description fingerprint.
- * @method _getSDPFingerprint
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._getSDPFingerprint = function (targetMid, sessionDescription, beSilentOnLogs) {
- var fingerprint = {
- fingerprint: null,
- fingerprintAlgorithm: null,
- derBase64: null
- };
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return fingerprint;
- }
-
- var sdpLines = sessionDescription.sdp.split('\r\n');
-
- for (var i = 0; i < sdpLines.length; i++) {
- if (sdpLines[i].indexOf('a=fingerprint') === 0) {
- var parts = sdpLines[i].replace('a=fingerprint:', '').split(' ');
- fingerprint.fingerprint = parts[1];
- fingerprint.fingerprintAlgorithm = parts[0];
- break;
- }
- }
-
- if (!beSilentOnLogs) {
- log.debug([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Parsing session description fingerprint ->'], fingerprint);
- }
-
- return fingerprint;
- };
-
- /**
- * 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 _renderSDPOutput
- * @private
- * @for Skylink
- * @since 0.6.25
- */
- Skylink.prototype._renderSDPOutput = function (targetMid, sessionDescription) {
- var self = this;
- var localStream = null;
- var localStreamId = null;
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return;
- }
-
- if (!self._peerConnections[targetMid]) {
- return sessionDescription.sdp;
- }
-
- if (self._peerConnections[targetMid].localStream) {
- localStream = self._peerConnections[targetMid].localStream;
- localStreamId = self._peerConnections[targetMid].localStreamId || self._peerConnections[targetMid].localStream.id;
- }
-
- // For non-trickle ICE, remove the a=end-of-candidates line first to append it properly later
- var sdpLines = (!self._initOptions.enableIceTrickle ? sessionDescription.sdp.replace(/a=end-of-candidates\r\n/g, '') : sessionDescription.sdp).split('\r\n');
- var agent = ((self._peerInformations[targetMid] || {}).agent || {}).name || '';
-
- // Parse and replace with the correct msid to prevent unwanted streams.
- // Making it simple without replacing with the track IDs or labels, neither setting prefixing "mslabel" and "label" as required labels.
- if (localStream) {
- var ssrcId = null;
- var mediaType = '';
-
- for (var i = 0; i < sdpLines.length; i++) {
- if (sdpLines[i].indexOf('m=') === 0) {
- mediaType = (sdpLines[i].split('m=')[1] || '').split(' ')[0] || '';
- mediaType = ['audio', 'video'].indexOf(mediaType) === -1 ? '' : mediaType;
-
- } else if (mediaType) {
- if (sdpLines[i].indexOf('a=msid:') === 0) {
- var msidParts = sdpLines[i].split(' ');
- msidParts[0] = 'a=msid:' + localStreamId;
- sdpLines[i] = msidParts.join(' ');
-
- } else if (sdpLines[i].indexOf('a=ssrc:') === 0) {
- var ssrcParts = null;
-
- // Replace for "msid:" and "mslabel:"
- if (sdpLines[i].indexOf(' msid:') > 0) {
- ssrcParts = sdpLines[i].split(' msid:');
- } else if (sdpLines[i].indexOf(' mslabel:') > 0) {
- ssrcParts = sdpLines[i].split(' mslabel:');
- }
-
- if (ssrcParts) {
- var ssrcMsidParts = (ssrcParts[1] || '').split(' ');
- ssrcMsidParts[0] = localStreamId;
- ssrcParts[1] = ssrcMsidParts.join(' ');
-
- if (sdpLines[i].indexOf(' msid:') > 0) {
- sdpLines[i] = ssrcParts.join(' msid:');
- } else if (sdpLines[i].indexOf(' mslabel:') > 0) {
- sdpLines[i] = ssrcParts.join(' mslabel:');
- }
- }
- }
- }
- }
- }
-
- // For non-trickle ICE, append the signaling of end-of-candidates properly
- if (!self._initOptions.enableIceTrickle){
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Appending end-of-candidates signal for non-trickle ICE connection.']);
-
- for (var e = 0; e < sdpLines.length; e++) {
- if (sdpLines[e].indexOf('a=candidate:') === 0) {
- if (sdpLines[e + 1] ? !(sdpLines[e + 1].indexOf('a=candidate:') === 0 ||
- sdpLines[e + 1].indexOf('a=end-of-candidates') === 0) : true) {
- sdpLines.splice(e + 1, 0, 'a=end-of-candidates');
- e++;
- }
- }
- }
- }
-
- // Replace the bundle policy to prevent complete removal of m= lines for some cases that do not accept missing m= lines except edge.
- if (sessionDescription.type === this.HANDSHAKE_PROGRESS.ANSWER && this._sdpSessions[targetMid]) {
- var bundleLineIndex = -1;
- var mLineIndex = -1;
-
- for (var j = 0; j < sdpLines.length; j++) {
- if (sdpLines[j].indexOf('a=group:BUNDLE') === 0 && this._sdpSessions[targetMid].remote.bundleLine &&
- this._peerConnectionConfig.bundlePolicy === this.BUNDLE_POLICY.MAX_BUNDLE) {
- sdpLines[j] = this._sdpSessions[targetMid].remote.bundleLine;
- } else if (sdpLines[j].indexOf('m=') === 0) {
- mLineIndex++;
- var compareA = sdpLines[j].split(' ');
- var compareB = (this._sdpSessions[targetMid].remote.mLines[mLineIndex] || '').split(' ');
-
- if (compareA[0] && compareB[0] && compareA[0] !== compareB[0]) {
- compareB[1] = 0;
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Appending middle rejected m= line ->'], compareB.join(' '));
- sdpLines.splice(j, 0, compareB.join(' '));
- j++;
- mLineIndex++;
- }
- }
- }
-
- while (this._sdpSessions[targetMid].remote.mLines[mLineIndex + 1]) {
- mLineIndex++;
- var appendIndex = sdpLines.length;
- if (!sdpLines[appendIndex - 1].replace(/\s/gi, '')) {
- appendIndex -= 1;
- }
- var parts = (this._sdpSessions[targetMid].remote.mLines[mLineIndex] || '').split(' ');
- parts[1] = 0;
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Appending later rejected m= line ->'], parts.join(' '));
- sdpLines.splice(appendIndex, 0, parts.join(' '));
- }
- }
-
- // Ensure for chrome case to have empty "" at last line or it will return invalid SDP errors
- if (window.webrtcDetectedBrowser === 'edge' && sessionDescription.type === this.HANDSHAKE_PROGRESS.OFFER &&
- !sdpLines[sdpLines.length - 1].replace(/\s/gi, '')) {
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Removing last empty space for Edge browsers']);
- sdpLines.splice(sdpLines.length - 1, 1);
- }
-
- /*
- var outputStr = sdpLines.join('\r\n');
- if (window.webrtcDetectedBrowser === 'edge' && this._streams.userMedia && this._streams.userMedia.stream) {
- var correctStreamId = this._streams.userMedia.stream.id || this._streams.userMedia.stream.label;
- outputStr = outputStr.replace(new RegExp('a=msid:.*\ ', 'gi'), 'a=msid:' + correctStreamId + ' ');
- outputStr = outputStr.replace(new RegExp('\ msid:.*\ ', 'gi'), ' msid:' + correctStreamId + ' ');
- }*/
-
- log.info([targetMid, 'RTCSessionDescription', sessionDescription.type, 'Formatted output ->'], sdpLines.join('\r\n'));
-
- return sdpLines.join('\r\n');
- };
-
- /**
- * Function that parses the session description to get the MediaStream IDs.
- * NOTE: It might not completely accurate if the setRemoteDescription() fails..
- * @method _parseSDPMediaStreamIDs
- * @private
- * @for Skylink
- * @since 0.6.25
- */
- Skylink.prototype._parseSDPMediaStreamIDs = function (targetMid, sessionDescription) {
- if (!this._peerConnections[targetMid]) {
- return;
- }
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- this._peerConnections[targetMid].remoteStreamId = null;
- return;
- }
-
- var sdpLines = sessionDescription.sdp.split('\r\n');
- var currentStreamId = null;
-
- for (var i = 0; i < sdpLines.length; i++) {
- // a=msid:{31145dc5-b3e2-da4c-a341-315ef3ebac6b} {e0cac7dd-64a0-7447-b719-7d5bf042ca05}
- if (sdpLines[i].indexOf('a=msid:') === 0) {
- currentStreamId = (sdpLines[i].split('a=msid:')[1] || '').split(' ')[0];
- break;
- // a=ssrc:691169016 msid:c58721ed-b7db-4e7c-ac37-47432a7a2d6f 2e27a4b8-bc74-4118-b3d4-0f1c4ed4869b
- } else if (sdpLines[i].indexOf('a=ssrc:') === 0 && sdpLines[i].indexOf(' msid:') > 0) {
- currentStreamId = (sdpLines[i].split(' msid:')[1] || '').split(' ')[0];
- break;
- }
- }
-
- // No stream set
- if (!currentStreamId) {
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'No remote stream is sent.']);
- this._peerConnections[targetMid].remoteStreamId = null;
- // New stream set
- } else if (currentStreamId !== this._peerConnections[targetMid].remoteStreamId) {
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'New remote stream is sent ->'], currentStreamId);
- this._peerConnections[targetMid].remoteStreamId = currentStreamId;
- // Same stream set
- } else {
- log.info([targetMid, 'RTCSessionDesription', sessionDescription.type, 'Same remote stream is sent ->'], currentStreamId);
- }
- };
-
- /**
- * Function that parses and retrieves the session description ICE candidates.
- * @method _getSDPICECandidates
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._getSDPICECandidates = function (targetMid, sessionDescription, beSilentOnLogs) {
- var candidates = {
- host: [],
- srflx: [],
- relay: []
- };
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return candidates;
- }
-
- sessionDescription.sdp.split('m=').forEach(function (mediaItem, index) {
- // Ignore the v=0 lines etc..
- if (index === 0) {
- return;
- }
-
- // Remove a=mid: and \r\n
- var sdpMid = ((mediaItem.match(/a=mid:.*\r\n/gi) || [])[0] || '').replace(/a=mid:/gi, '').replace(/\r\n/, '');
- var sdpMLineIndex = index - 1;
-
- (mediaItem.match(/a=candidate:.*\r\n/gi) || []).forEach(function (item) {
- // Remove \r\n for candidate type being set at the end of candidate DOM string.
- var canType = (item.split(' ')[7] || 'host').replace(/\r\n/g, '');
- candidates[canType] = candidates[canType] || [];
- candidates[canType].push(new RTCIceCandidate({
- sdpMid: sdpMid,
- sdpMLineIndex: sdpMLineIndex,
- // Remove initial "a=" in a=candidate
- candidate: (item.split('a=')[1] || '').replace(/\r\n/g, '')
- }));
- });
- });
-
- if (!beSilentOnLogs) {
- log.debug([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Parsing session description ICE candidates ->'], candidates);
- }
-
- return candidates;
- };
-
- /**
- * Function that gets each media line SSRCs.
- * @method _getSDPMediaSSRC
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._getSDPMediaSSRC = function (targetMid, sessionDescription, beSilentOnLogs) {
- var ssrcs = {
- audio: 0,
- video: 0
- };
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return ssrcs;
- }
-
- sessionDescription.sdp.split('m=').forEach(function (mediaItem, index) {
- // Ignore the v=0 lines etc..
- if (index === 0) {
- return;
- }
-
- var mediaType = (mediaItem.split(' ')[0] || '');
- var ssrcLine = (mediaItem.match(/a=ssrc:.*\r\n/) || [])[0];
-
- if (typeof ssrcs[mediaType] !== 'number') {
- return;
- }
-
- if (ssrcLine) {
- ssrcs[mediaType] = parseInt((ssrcLine.split('a=ssrc:')[1] || '').split(' ')[0], 10) || 0;
- }
- });
-
- if (!beSilentOnLogs) {
- log.debug([targetMid, 'RTCSessionDesription', sessionDescription.type,
- 'Parsing session description media SSRCs ->'], ssrcs);
- }
-
- return ssrcs;
- };
-
- /**
- * Function that parses the current list of supported codecs from session description.
- * @method _getSDPCodecsSupport
- * @private
- * @for Skylink
- * @since 0.6.18
- */
- Skylink.prototype._getSDPCodecsSupport = function (targetMid, sessionDescription) {
- var self = this;
- var codecs = {
- audio: {},
- video: {}
- };
-
- if (!(sessionDescription && sessionDescription.sdp)) {
- return codecs;
- }
-
- 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];
- continue;
- }
-
- if (sdpLines[i].indexOf('a=rtpmap:') === 0) {
- var parts = (sdpLines[i].split(' ')[1] || '').split('/');
- var codec = (parts[0] || '').toLowerCase();
- var info = parts[1] + (parts[2] ? '/' + parts[2] : '');
-
- if (['ulpfec', 'red', 'telephone-event', 'cn', 'rtx'].indexOf(codec) > -1) {
- continue;
- }
-
- codecs[mediaType][codec] = codecs[mediaType][codec] || [];
-
- if (codecs[mediaType][codec].indexOf(info) === -1) {
- codecs[mediaType][codec].push(info);
- }
- }
- }
-
- log.info([targetMid || null, 'RTCSessionDescription', sessionDescription.type, 'Parsed codecs support ->'], codecs);
- return codecs;
- };
-
- /**
- * Function that checks if there are any common codecs supported for remote end.
- * @method _getSDPCommonSupports
- * @private
- * @for Skylink
- * @since 0.6.25
- */
- Skylink.prototype._getSDPCommonSupports = function (targetMid, sessionDescription) {
- var self = this;
- var offer = {
- audio: false,
- video: false
- };
-
- if (!targetMid || !(sessionDescription && sessionDescription.sdp)) {
- offer.video = !!(self._currentCodecSupport.video.h264 || self._currentCodecSupport.video.vp8);
- offer.audio = !!self._currentCodecSupport.audio.opus;
-
- if (targetMid) {
- var peerAgent = ((self._peerInformations[targetMid] || {}).agent || {}).name || '';
-
- if (AdapterJS.webrtcDetectedBrowser === peerAgent) {
- offer.video = Object.keys(self._currentCodecSupport.video).length > 0;
- offer.audio = Object.keys(self._currentCodecSupport.audio).length > 0;
- }
- }
- return offer;
- }
-
- var remoteCodecs = self._getSDPCodecsSupport(targetMid, sessionDescription);
- var localCodecs = self._currentCodecSupport;
-
- for (var ac in localCodecs.audio) {
- if (localCodecs.audio.hasOwnProperty(ac) && localCodecs.audio[ac] && remoteCodecs.audio[ac]) {
- offer.audio = true;
- break;
- }
- }
-
- for (var vc in localCodecs.video) {
- if (localCodecs.video.hasOwnProperty(vc) && localCodecs.video[vc] && remoteCodecs.video[vc]) {
- offer.video = true;
- break;
- }
- }
-
- return offer;
- };
-
- /**
- * Function adds SCTP port number for Firefox 63.0.3 and above if its missing in the answer from MCU
- * @method _setSCTPport
- * @private
- * @for Skylink
- * @since 0.6.35
- */
- Skylink.prototype._setSCTPport = function (targetMid, sessionDescription) {
- var self = this;
- if (AdapterJS.webrtcDetectedBrowser === 'firefox' && AdapterJS.webrtcDetectedVersion >= 63 && self._hasMCU === true) {
- var sdpLines = sessionDescription.sdp.split('\r\n');
- var mLineType = 'application';
- var mLineIndex = -1;
- var sdpType = sessionDescription.type;
-
- 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;
- }
-
- // Saving m=application line when creating offer into instance variable
- if (sdpType === 'offer') {
- self._mline = sdpLines[mLineIndex];
- break;
- }
-
- // Replacing m=application line from instance variable
- if (sdpType === 'answer') {
- sdpLines[mLineIndex] = self._mline;
- sdpLines.splice(mLineIndex + 1, 0, 'a=sctp-port:5000');
- break;
- }
- }
- }
-
- return sdpLines.join('\r\n');
- }
-
- return sessionDescription.sdp;
- };
-
- /**
- * Function sets the original DTLS role which was negotiated on first offer/ansswer exchange
- * This needs to be done until https://bugzilla.mozilla.org/show_bug.cgi?id=1240897 is released in Firefox 68
- * Estimated release date for Firefox 68 : 2019-07-09 (https://wiki.mozilla.org/Release_Management/Calendar)
- * @method _setOriginalDTLSRole
- * @private
- * @for Skylink
- * @since 0.6.35
- */
- Skylink.prototype._setOriginalDTLSRole = function (sessionDescription, isRemote) {
- var self = this;
- var sdpType = sessionDescription.type;
- var role = null;
- var aSetupPattern = null;
- var invertRoleMap = { active: 'passive', passive: 'active' };
-
- if (self._originalDTLSRole !== null || sdpType === 'offer') {
- return;
- }
-
- aSetupPattern = sessionDescription.sdp.match(/a=setup:([a-z]+)/);
-
- if (!aSetupPattern) {
- return;
- }
-
- role = aSetupPattern[1];
- self._originalDTLSRole = isRemote ? invertRoleMap[role] : role;
- };
-
-
- /**
- * Function that modifies the DTLS role in answer sdp
- * This needs to be done until https://bugzilla.mozilla.org/show_bug.cgi?id=1240897 is released in Firefox 68
- * Estimated release date for Firefox 68 : 2019-07-09 (https://wiki.mozilla.org/Release_Management/Calendar)
- * @method _modifyDTLSRole
- * @private
- * @for Skylink
- * @since 1.0.0
- */
- Skylink.prototype._modifyDTLSRole = function (sessionDescription) {
- var self = this;
- var sdpType = sessionDescription.type;
-
- if (self._originalDTLSRole === null || sdpType === 'offer') {
- return;
- }
-
- sessionDescription.sdp = sessionDescription.sdp.replace(/a=setup:[a-z]+/g, 'a=setup:' + self._originalDTLSRole);
- return sessionDescription.sdp;
- };
-