diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..a2592e5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +# Ignore list: +/* +# Except list: +!/src/**/*.js +!/*.js +!/demo +!/test \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..20aad76 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + trailingComma: 'none', + tabWidth: 4, + semi: true, + singleQuote: true +}; diff --git a/Gruntfile.js b/Gruntfile.js index 0458513..af86f5d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function (grunt) { +module.exports = function(grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { @@ -14,24 +14,20 @@ module.exports = function (grunt) { }, browserify: { connectRtcGlobalObjectDebug: { - src: [ - './src/js/connect-rtc.js' - ], + src: ['./src/js/connect-rtc.js'], dest: './out/connect-rtc-debug.js', options: { browserifyOptions: { debug: true }, - transform: [["babelify", { "presets": ["env"] }]], + transform: [['babelify', { presets: ['env'] }]] } }, connectRtcGlobalObject: { - src: [ - './src/js/connect-rtc.js' - ], + src: ['./src/js/connect-rtc.js'], dest: './out/connect-rtc.js', options: { - transform: [["babelify", { "presets": ["env"] }]], + transform: [['babelify', { presets: ['env'] }]] } } }, diff --git a/demo/index.html b/demo/index.html index c1c66c5..0a2bba7 100644 --- a/demo/index.html +++ b/demo/index.html @@ -1,69 +1,112 @@ + + connect-rtc-js demo + - - connect-rtc-js demo - - - - - - - -

Amazon Connect RTC Demo

-

See amazon-connect-streams for how to get the softphone media info

-

Note: stringify the object returned by contact.getAgentConnection().getSoftphoneMediaInfo() before pasting it - here -

- - -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - + + + + +

Amazon Connect RTC Demo

+

+ See + amazon-connect-streams + for how to get the softphone media info +

+

+ Note: stringify the object returned by + contact.getAgentConnection().getSoftphoneMediaInfo() before + pasting it here +

+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + diff --git a/demo/index.js b/demo/index.js index 8b12f60..04dbc36 100644 --- a/demo/index.js +++ b/demo/index.js @@ -1,18 +1,23 @@ -$(document).ready(function () { +$(document).ready(function() { var audioElement = $('#remoteAudio')[0]; var videoElement = $('#remoteVideo')[0]; if (window.location.hash) { - $('#softphoneMediaInfo').val(decodeURIComponent(window.location.hash.substr(1))); + $('#softphoneMediaInfo').val( + decodeURIComponent(window.location.hash.substr(1)) + ); } - $('#makeCall').click(function () { + $('#makeCall').click(function() { var mediaInfo = JSON.parse($('#softphoneMediaInfo').val()); - var rtcConfig = mediaInfo.webcallConfig || JSON.parse(mediaInfo.callConfigJson);//mediaInfo.webcallConfig is used internally by Amazon Connect team only - var session = new connect.RTCSession(rtcConfig.signalingEndpoint, + var rtcConfig = + mediaInfo.webcallConfig || JSON.parse(mediaInfo.callConfigJson); //mediaInfo.webcallConfig is used internally by Amazon Connect team only + var session = new connect.RTCSession( + rtcConfig.signalingEndpoint, rtcConfig.iceServers, mediaInfo.callContextToken, - console); + console + ); session.echoCancellation = $('#echoCancellationOption').is(':checked'); @@ -27,27 +32,41 @@ $(document).ready(function () { } if ($('#enable-video')[0].checked) { - $('#video-display')[0].style.display = 'block'; - session.remoteVideoElement = videoElement; - // enable video with 240p requested. - session.enableVideo = true; - session.maxVideoWidth = 426; - session.maxVideoHeight = 240; + $('#video-display')[0].style.display = 'block'; + session.remoteVideoElement = videoElement; + // enable video with 240p requested. + session.enableVideo = true; + session.maxVideoWidth = 426; + session.maxVideoHeight = 240; } else { - $('#video-display')[0].style.display = 'none'; - session.remoteVideoElement = null; + $('#video-display')[0].style.display = 'none'; + session.remoteVideoElement = null; } var statsCollector; session.onSessionConnected = () => { statsCollector = setInterval(() => { var collectTime = new Date(); - Promise.all([session.getUserAudioStats(), session.getRemoteAudioStats()]).then((streamStats) => { - console.log(collectTime + " Audio statistics : " + JSON.stringify(streamStats)); + Promise.all([ + session.getUserAudioStats(), + session.getRemoteAudioStats() + ]).then(streamStats => { + console.log( + collectTime + + ' Audio statistics : ' + + JSON.stringify(streamStats) + ); }); if ($('#enable-video')[0].checked) { - Promise.all([session.getUserVideoStats(), session.getRemoteVideoStats()]).then((streamStats) => { - console.log(collectTime + " Video statistics : " + JSON.stringify(streamStats)); + Promise.all([ + session.getUserVideoStats(), + session.getRemoteVideoStats() + ]).then(streamStats => { + console.log( + collectTime + + ' Video statistics : ' + + JSON.stringify(streamStats) + ); }); } }, 2000); @@ -68,7 +87,7 @@ $(document).ready(function () { $('#enable-video').prop('disabled', true); $('#disconnectCall').prop('disabled', false); - $('#disconnectCall').click(function () { + $('#disconnectCall').click(function() { if (session) { try { session.hangup(); @@ -83,9 +102,9 @@ $(document).ready(function () { } }); - $('#pause-local-video').click(function(){ - if(session) { - if($('#pause-local-video').is(':checked')) + $('#pause-local-video').click(function() { + if (session) { + if ($('#pause-local-video').is(':checked')) session.pauseLocalVideo(); else { session.resumeLocalVideo(); @@ -93,9 +112,9 @@ $(document).ready(function () { } }); - $('#pause-remote-video').click(function(){ - if(session) { - if($('#pause-remote-video').is(':checked')) + $('#pause-remote-video').click(function() { + if (session) { + if ($('#pause-remote-video').is(':checked')) session.pauseRemoteVideo(); else { session.resumeRemoteVideo(); @@ -103,9 +122,9 @@ $(document).ready(function () { } }); - $('#pause-local-audio').click(function(){ - if(session) { - if($('#pause-local-audio').is(':checked')) + $('#pause-local-audio').click(function() { + if (session) { + if ($('#pause-local-audio').is(':checked')) session.pauseLocalAudio(); else { session.resumeLocalAudio(); @@ -113,9 +132,9 @@ $(document).ready(function () { } }); - $('#pause-remote-audio').click(function(){ - if(session) { - if($('#pause-remote-audio').is(':checked')) + $('#pause-remote-audio').click(function() { + if (session) { + if ($('#pause-remote-audio').is(':checked')) session.pauseRemoteAudio(); else { session.resumeRemoteAudio(); diff --git a/package.json b/package.json index 32a6693..e0f6569 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,14 @@ "version": "", "postversion": "export GITTAG=\"echo $(git describe --abbrev=0 --tags | sed 's/^v//')\" && git push --force --set-upstream origin bumpVersion --follow-tags && git checkout gh-pages && git pull && cp out/connect-rtc-debug.js ./connect-rtc-debug-`$GITTAG`.js && cp out/connect-rtc.min.js ./connect-rtc-`$GITTAG`.min.js && ln -fs connect-rtc-debug-`$GITTAG`.js connect-rtc-debug-latest.js && ln -fs connect-rtc-`$GITTAG`.min.js connect-rtc-latest.min.js && git add connect-rtc*.js && git commit -m `$GITTAG` && git push --set-upstream origin gh-pages && git checkout master", "prepublish": "grunt build", + "pretty": "prettier --write \"**/*.*\"", "test": "grunt && mocha --compilers js:babel-core/register test/unit" }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } + }, "dependencies": { "webrtc-adapter": "~2.0.5", "uuid": "^3.0.1" @@ -30,8 +36,8 @@ "node": ">=6.0.0" }, "devDependencies": { - "babel-polyfill": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "^6.23.0", "babel-preset-env": "^1.3.2", "babelify": "^7.3.0", "chai": "^3.5.0", @@ -49,7 +55,10 @@ "grunt-contrib-watch": "^1.1.0", "grunt-eslint": "^21.0.0", "grunt-githooks": "^0.6.0", + "husky": "^2.2.0", "mocha": "^3.2.0", + "prettier": "^1.17.0", + "pretty-quick": "^1.10.0", "selenium-webdriver": "^3.3.0", "sinon": "^2.1.0", "tape": "^4.6.3", diff --git a/src/js/rtc_session.js b/src/js/rtc_session.js index 6219680..baf2445 100644 --- a/src/js/rtc_session.js +++ b/src/js/rtc_session.js @@ -68,7 +68,7 @@ export class GrabLocalMediaState extends RTCSessionState { onEnter() { var self = this; var startTime = Date.now(); - if (self._rtcSession._userAudioStream) { + if (self._rtcSession._localStream) { self.transit(new CreateOfferState(self._rtcSession)); } else { var gumTimeoutPromise = new Promise((resolve, reject) => { @@ -703,6 +703,9 @@ export default class RtcSession { set echoCancellation(flag) { this._echoCancellation = flag; } + set echoCancellationType(type) { + this._echoCancellationType = type; + } set enableVideo(flag) { this._enableVideo = flag; } @@ -896,9 +899,9 @@ export default class RtcSession { var timestamp = new Date(); var impl = async (stream, streamType) => { - var tracks = []; + let tracks = []; - if (! stream) { + if (!stream) { return []; } @@ -911,13 +914,16 @@ export default class RtcSession { case 'video_output': tracks = stream.getVideoTracks(); break; + case 'video_bandwidth': + tracks = stream.getVideoTracks(); + break; default: throw new Error('Unsupported stream type while trying to get stats: ' + streamType); } return await Promise.all(tracks.map(async (track) => { - var rawStats = await this._pc.getStats(track); - var digestedStats = extractMediaStatsFromStats(timestamp, rawStats, streamType); + const rawStats = await this._pc.getStats(track); + const digestedStats = extractMediaStatsFromStats(timestamp, rawStats, streamType); if (! digestedStats) { throw new Error('Failed to extract MediaRtpStats from RTCStatsReport for stream type ' + streamType); } @@ -934,8 +940,10 @@ export default class RtcSession { video: { input: await impl(this._remoteVideoStream, 'video_input'), - output: await impl(this._localStream, 'video_output') - } + output: await impl(this._localStream, 'video_output'), + // Choose either stream as the underlying data is the same + bandwidth: await impl(this._remoteVideoStream || this._localStream, 'video_bandwidth'), + }, }; // For consistency's sake, coalesce rttMilliseconds into the output for audio and video. @@ -965,6 +973,16 @@ export default class RtcSession { } } + /** + * Get a promise containing raw object all RTCPeerConnection stats. + * @return Rejected promise if failed to get MediaRtpStats. The promise is never resolved with null value. + */ + async getStatsRaw() { + if (this._pc && this._pc.signalingState === 'stable') { + return await this._pc.getStats(null); + } + return Promise.reject(new IllegalState()); + } /** * Get a promise of MediaRtpStats object for remote audio (from Amazon Connect to client). @@ -1089,6 +1107,9 @@ export default class RtcSession { if (typeof self._echoCancellation !== 'undefined') { audioConstraints.echoCancellation = !!self._echoCancellation; } + if (typeof self._echoCancellationType !== 'undefined') { + audioConstraints.echoCancellationType = self._echoCancellationType; + } if (Object.keys(audioConstraints).length > 0) { mediaConstraints.audio = audioConstraints; } else { diff --git a/src/js/rtp-stats.js b/src/js/rtp-stats.js index 640aa53..7dea634 100644 --- a/src/js/rtp-stats.js +++ b/src/js/rtp-stats.js @@ -5,16 +5,17 @@ */ import { is_defined, when_defined } from './utils'; + export function extractMediaStatsFromStats(timestamp, stats, streamType) { - var extractedStats = null; + let extractedStats = null; - for (var key in stats) { + for (let key in stats) { var statsReport = stats[key]; if (statsReport) { if (statsReport.type === 'ssrc') { //chrome, opera case. chrome reports stats for all streams, not just the stream passed in. if (is_defined(statsReport.packetsSent) && statsReport.mediaType == 'audio' && streamType === 'audio_input') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesSent: statsReport.bytesSent, @@ -22,10 +23,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { packetsLost: is_defined(statsReport.packetsLost) ? Math.max(0, statsReport.packetsLost) : 0, procMilliseconds: is_defined(statsReport.googCurrentDelayMs), rttMilliseconds: when_defined(statsReport.googRtt) - }; + }, statsReport.type, streamType); } else if (is_defined(statsReport.packetsReceived) && statsReport.mediaType == 'audio' && streamType === 'audio_output') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsReceived, bytesReceived: statsReport.bytesReceived, @@ -33,10 +34,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { packetsLost: is_defined(statsReport.packetsLost) ? Math.max(0, statsReport.packetsLost) : 0, procMilliseconds: is_defined(statsReport.googCurrentDelayMs), jbMilliseconds: when_defined(statsReport.googJitterBufferMs) - }; + }, statsReport.type, streamType); } else if (is_defined(statsReport.packetsSent) && statsReport.mediaType == 'video' && streamType === 'video_input') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesSent: statsReport.bytesSent, @@ -44,10 +45,10 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { rttMilliseconds: when_defined(statsReport.googRtt), procMilliseconds: is_defined(statsReport.googCurrentDelayMs), frameRateSent: when_defined(statsReport.googFrameRateSent), - }; + }, statsReport.type, streamType); } else if (typeof statsReport.packetsReceived !== 'undefined' && statsReport.mediaType == 'video' && streamType === 'video_output') { - extractedStats = { + extractedStats = new MediaRtpStats({ timestamp: timestamp, packetsCount: statsReport.packetsSent, bytesReceived: statsReport.bytesReceived, @@ -55,36 +56,70 @@ export function extractMediaStatsFromStats(timestamp, stats, streamType) { frameRateReceived: when_defined(statsReport.googFrameRateReceived), procMilliseconds: is_defined(statsReport.googCurrentDelayMs), jbMilliseconds: when_defined(statsReport.googJitterBufferMs) - }; + }, statsReport.type, streamType); } } else if (statsReport.type === 'inboundrtp') { // Firefox case. Firefox reports packetsLost parameter only in inboundrtp type, and doesn't report in outboundrtp type. // So we only pull from inboundrtp. Firefox reports only stats for the stream passed in. if (is_defined(statsReport.packetsLost) && is_defined(statsReport.packetsReceived)) { - extractedStats = { + extractedStats = new MediaRtpStats({ packetsLost: statsReport.packetsLost, packetsCount: statsReport.packetsReceived, audioLevel: when_defined(statsReport.audioInputLevel), - rttMilliseconds: streamType === 'audio_ouptut' || streamType === 'video_output' ? when_defined(statsReport.roundTripTime) : null, + rttMilliseconds: streamType === 'audio_output' || streamType === 'video_output' ? when_defined(statsReport.roundTripTime) : null, jbMilliseconds: streamType === 'audio_output' || streamType === 'video_output' ? when_defined(statsReport.jitter, 0) * 1000 : null - }; + }, statsReport.type, streamType); + } + } else if (statsReport.type === 'VideoBwe') { + if (streamType === 'video_bandwidth') { + extractedStats = new MediaBWEForVideoStats({ + timestamp: timestamp, + encBitrate: statsReport.googActualEncBitrate, + availReceiveBW: statsReport.googAvailableReceiveBandwidth, + availSendBW: statsReport.googAvailableSendBandwidth, + bucketDelay: statsReport.googBucketDelay, + retransmitBitrate: statsReport.googRetransmitBitrate, + targetEncBitrate: statsReport.googTargetEncBitrate, + transmitBitrate: statsReport.googTransmitBitrate + }, statsReport.type, streamType); } } } } + return extractedStats ? extractedStats : null; +} - return extractedStats ? new MediaRtpStats(extractedStats, statsReport.type, streamType) : null; +/** +* Basic statistics object, represents core properties of any statistic +*/ +class BaseStats { + constructor(params = {}, statsReportType, streamType) { + this._timestamp = params.timestamp || new Date().getTime(); + this._statsReportType = statsReportType || params._statsReportType || "unknown"; + this._streamType = streamType || params.streamType || "unknown"; + } + /** Timestamp when stats are collected. */ + get timestamp() { + return this._timestamp; + } + /** {string} the type of the stats report */ + get statsReportType() { + return this._statsReportType; + } + /** {string} the type of the stream */ + get streamType() { + return this._streamType; + } } /** * Basic RTP statistics object, represents statistics of an audio or video stream. */ -class MediaRtpStats { - constructor(paramsIn, statsReportType, streamType) { - var params = paramsIn || {}; +class MediaRtpStats extends BaseStats { + constructor(params = {}, statsReportType, streamType) { + super(params, statsReportType, streamType); - this._timestamp = params.timestamp || new Date().getTime(); this._packetsLost = when_defined(params.packetsLost); this._packetsCount = when_defined(params.packetsCount); this._audioLevel = when_defined(params.audioLevel); @@ -96,8 +131,6 @@ class MediaRtpStats { this._framesDecoded = when_defined(params.framesDecoded); this._frameRateSent = when_defined(params.frameRateSent); this._frameRateReceived = when_defined(params.frameRateReceived); - this._statsReportType = statsReportType || params._statsReportType || "unknown"; - this._streamType = streamType || params.streamType || "unknown"; } /** {number} number of packets sent to the channel */ @@ -118,10 +151,6 @@ class MediaRtpStats { get audioLevel() { return this._audioLevel; } - /** Timestamp when stats are collected. */ - get timestamp() { - return this._timestamp; - } /** {number} Round trip time calculated with RTCP reports */ get rttMilliseconds() { return this._rttMilliseconds; @@ -154,12 +183,42 @@ class MediaRtpStats { get frameRateReceived() { return this._frameRateReceived; } - /** {string} the type of the stats report */ - get statsReportType() { - return this._statsReportType; +} + +/** +* Basic BWEForVideo statistics object, represents statistics of an audio or video stream. +*/ +class MediaBWEForVideoStats extends BaseStats { + constructor(params = {}, statsReportType, streamType) { + super(params, statsReportType, streamType); + + this._encBitrate = when_defined(params.encBitrate); + this._availReceiveBW = when_defined(params.availReceiveBW); + this._availSendBW = when_defined(params.availSendBW); + this._bucketDelay = when_defined(params.bucketDelay); + this._retransmitBitrate = when_defined(params.retransmitBitrate); + this._targetEncBitrate = when_defined(params.targetEncBitrate); + this._transmitBitrate = when_defined(params.transmitBitrate); } - /** {string} the type of the stream */ - get streamType() { - return this._streamType; + get encBitrate() { + return this._encBitrate; + } + get availReceiveBW() { + return this._availReceiveBW; + } + get availSendBW() { + return this._availSendBW; + } + get bucketDelay() { + return this._bucketDelay; + } + get retransmitBitrate() { + return this._retransmitBitrate; + } + get targetEncBitrate() { + return this._targetEncBitrate; + } + get transmitBitrate() { + return this._transmitBitrate; } } diff --git a/test/unit/connect-rtc.js b/test/unit/connect-rtc.js index 1e15dfa..25ef837 100644 --- a/test/unit/connect-rtc.js +++ b/test/unit/connect-rtc.js @@ -8,7 +8,7 @@ * or in the "LICENSE" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import {RTCSession, RTCErrors} from '../../src/js/connect-rtc'; +import { RTCSession, RTCErrors } from '../../src/js/connect-rtc'; import chai from 'chai'; describe('Bundled JS', () => { @@ -16,4 +16,4 @@ describe('Bundled JS', () => { chai.expect(RTCSession).to.be.not.null; chai.expect(RTCErrors).to.be.not.null; }); -}); \ No newline at end of file +}); diff --git a/test/unit/rtc_session.js b/test/unit/rtc_session.js index 3474aa8..024289d 100644 --- a/test/unit/rtc_session.js +++ b/test/unit/rtc_session.js @@ -9,7 +9,19 @@ */ import RtcSession from '../../src/js/rtc_session'; -import { RTCSessionState, GrabLocalMediaState, CreateOfferState, SetLocalSessionDescriptionState, ConnectSignalingAndIceCollectionState, InviteAnswerState, AcceptState, TalkingState, CleanUpState, DisconnectedState, FailedState } from '../../src/js/rtc_session'; +import { + RTCSessionState, + GrabLocalMediaState, + CreateOfferState, + SetLocalSessionDescriptionState, + ConnectSignalingAndIceCollectionState, + InviteAnswerState, + AcceptState, + TalkingState, + CleanUpState, + DisconnectedState, + FailedState +} from '../../src/js/rtc_session'; import { RTC_ERRORS } from '../../src/js/rtc_const'; import { BusyException, CallNotFoundException } from '../../src/js/exceptions'; import chai from 'chai'; @@ -17,14 +29,19 @@ import sinon from 'sinon'; describe('RTC session', () => { describe('session object', () => { - var session = new RtcSession('wss://amazon-connect-rtc-server.amazonaws.com/', [], 'contactToken', console); + var session = new RtcSession( + 'wss://amazon-connect-rtc-server.amazonaws.com/', + [], + 'contactToken', + console + ); it('builds audio constraints by default', () => { var constraints = session._buildMediaConstraints(); chai.expect(!!constraints.audio).to.be.true; }); - it('generates contact ID when it\'s not provided through constructor', () => { + it("generates contact ID when it's not provided through constructor", () => { chai.expect(session.callId).to.match(/^[-A-Fa-f0-9]{36}$/); }); }); @@ -45,7 +62,7 @@ describe('RTC session', () => { state = new RTCSessionState(session); }); - it('executs transit only if it\'s current state', () => { + it("executs transit only if it's current state", () => { session._state = state; session.transit = sinon.spy(); var nextState = {}; @@ -54,7 +71,7 @@ describe('RTC session', () => { chai.assert(session.transit.calledWith(nextState)); }); - it('skips transit if it\'s not current state', () => { + it("skips transit if it's not current state", () => { session._state = null; session.transit = sinon.spy(); var nextState = {}; @@ -88,36 +105,39 @@ describe('RTC session', () => { chai.assert(session.transit.args[0][0] instanceof CreateOfferState); }); - it('notifies gum error and go to failed state if gUM times out', (done) => { + it('notifies gum error and go to failed state if gUM times out', done => { session._logger = console; session._gumTimeoutMillis = 0; session._sessionReport = {}; session._state = state; - session._buildMediaConstraints = () => { }; + session._buildMediaConstraints = () => {}; session._onGumError = sinon.spy(); - session.transit = (nextState) => { + session.transit = nextState => { chai.assert(session._onGumError.calledOnce); chai.assert(session._onGumError.calledWith(session)); chai.assert(nextState instanceof FailedState); done(); }; state._gUM = sinon.stub(); - state._gUM.returns(new Promise(() => { })); + state._gUM.returns(new Promise(() => {})); state.onEnter(); }); - it('notifies gum error and go to failed state if gUM fast fails', (done) => { + it('notifies gum error and go to failed state if gUM fast fails', done => { session._logger = console; session._gumTimeoutMillis = 2000; session._sessionReport = {}; session._state = state; - session._buildMediaConstraints = () => { }; + session._buildMediaConstraints = () => {}; session._onGumError = sinon.spy(); - session.transit = (nextState) => { + session.transit = nextState => { chai.assert(session._onGumError.calledOnce); chai.assert(session._onGumError.calledWith(session)); chai.assert(nextState instanceof FailedState); - chai.assert.equal(RTC_ERRORS.GUM_OTHER_FAILURE, nextState._failureReason); + chai.assert.equal( + RTC_ERRORS.GUM_OTHER_FAILURE, + nextState._failureReason + ); done(); }; state._gUM = sinon.stub(); @@ -125,14 +145,14 @@ describe('RTC session', () => { state.onEnter(); }); - it('notifies gum success and go to create offer state if gUM succeeds within time limit', (done) => { + it('notifies gum success and go to create offer state if gUM succeeds within time limit', done => { session._logger = console; session._gumTimeoutMillis = 2000; session._sessionReport = {}; session._state = state; - session._buildMediaConstraints = () => { }; + session._buildMediaConstraints = () => {}; session._onGumSuccess = sinon.spy(); - session.transit = (nextState) => { + session.transit = nextState => { chai.assert(session._onGumSuccess.calledOnce); chai.assert(session._onGumSuccess.calledWith(session)); chai.assert(nextState instanceof CreateOfferState); @@ -169,10 +189,12 @@ describe('RTC session', () => { session._state = state; }); - it('transits to set local description state when offer created', (done) => { + it('transits to set local description state when offer created', done => { session._pc.createOffer.returns(Promise.resolve('desc')); - session.transit = (nextState) => { - chai.assert(nextState instanceof SetLocalSessionDescriptionState); + session.transit = nextState => { + chai.assert( + nextState instanceof SetLocalSessionDescriptionState + ); chai.assert.equal('desc', session._localSessionDescription); done(); }; @@ -181,9 +203,9 @@ describe('RTC session', () => { chai.assert(session._pc.addStream.calledOnce); }); - it('transits to failed state when offer creation failed', (done) => { + it('transits to failed state when offer creation failed', done => { session._pc.createOffer.returns(Promise.reject('testFailure')); - session.transit = (nextState) => { + session.transit = nextState => { chai.expect(nextState).to.be.instanceof(FailedState); chai.expect(session._localSessionDescription).to.be.undefined; done(); @@ -195,42 +217,43 @@ describe('RTC session', () => { }); describe('SetLocalSessionDescriptionState', () => { - var sdp = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1\r\n" + - "a=rtpmap:103 ISAC/16000\r\n" + - "a=rtpmap:104 ISAC/32000\r\n" + - "a=rtpmap:9 G722/8000\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:8 PCMA/8000\r\n" + - "a=rtpmap:106 CN/32000\r\n" + - "a=rtpmap:105 CN/16000\r\n" + - "a=rtpmap:13 CN/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b"; + var sdp = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1\r\n' + + 'a=rtpmap:103 ISAC/16000\r\n' + + 'a=rtpmap:104 ISAC/32000\r\n' + + 'a=rtpmap:9 G722/8000\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:8 PCMA/8000\r\n' + + 'a=rtpmap:106 CN/32000\r\n' + + 'a=rtpmap:105 CN/16000\r\n' + + 'a=rtpmap:13 CN/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b'; /** * @type {RtcSession} @@ -258,20 +281,26 @@ describe('RTC session', () => { session._state = state; }); - it('transits to signaling state when local description is set', (done) => { + it('transits to signaling state when local description is set', done => { session._pc.setLocalDescription.returns(Promise.resolve()); - session.transit = (nextState) => { - chai.expect(session._onSessionInitialized.calledOnce).to.be.true; - chai.expect(nextState).to.be.instanceof(ConnectSignalingAndIceCollectionState); + session.transit = nextState => { + chai.expect(session._onSessionInitialized.calledOnce).to.be + .true; + chai.expect(nextState).to.be.instanceof( + ConnectSignalingAndIceCollectionState + ); done(); }; state.onEnter(); }); - it('transits to failed state when set local description failed', (done) => { - session._pc.setLocalDescription.returns(Promise.reject('testFailure')); - session.transit = (nextState) => { - chai.expect(session._onSessionInitialized.calledOnce).to.be.false; + it('transits to failed state when set local description failed', done => { + session._pc.setLocalDescription.returns( + Promise.reject('testFailure') + ); + session.transit = nextState => { + chai.expect(session._onSessionInitialized.calledOnce).to.be + .false; chai.expect(nextState).to.be.instanceof(FailedState); done(); }; @@ -296,12 +325,11 @@ describe('RTC session', () => { _createSignalingChannel: sinon.stub(), _onIceCollectionComplete: sinon.spy(), _onSignalingConnected: sinon.spy(), - _pc: { - }, + _pc: {}, _sessionReport: {} }; - state = new ConnectSignalingAndIceCollectionState(session, 2);//2 m lines - state._createLocalCandidate = (initDict) => initDict; + state = new ConnectSignalingAndIceCollectionState(session, 2); //2 m lines + state._createLocalCandidate = initDict => initDict; session._state = state; }); @@ -311,18 +339,24 @@ describe('RTC session', () => { state.onSignalingFailed(); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); }); - it('transits to failed state when ICE collection times out', (done) => { + it('transits to failed state when ICE collection times out', done => { var sig = { connect: sinon.spy() }; session._createSignalingChannel.returns(sig); session._iceTimeoutMillis = 1; - session.transit = (nextState) => { + session.transit = nextState => { chai.expect(sig.connect.calledOnce).to.be.true; - chai.expect(session._onIceCollectionComplete.calledOnce).to.be.true; - chai.expect(session._onIceCollectionComplete.args[0][1]).to.be.true; - chai.expect(session._onIceCollectionComplete.args[0][2]).to.eq(0); + chai.expect(session._onIceCollectionComplete.calledOnce).to.be + .true; + chai.expect(session._onIceCollectionComplete.args[0][1]).to.be + .true; + chai.expect(session._onIceCollectionComplete.args[0][2]).to.eq( + 0 + ); chai.expect(nextState).to.be.instanceof(FailedState); done(); }; @@ -335,9 +369,12 @@ describe('RTC session', () => { state.onIceCandidate({}); chai.expect(session._onIceCollectionComplete.calledOnce).to.be.true; - chai.expect(session._onIceCollectionComplete.args[0][1]).to.be.false; + chai.expect(session._onIceCollectionComplete.args[0][1]).to.be + .false; chai.expect(session._onIceCollectionComplete.args[0][2]).to.eq(0); - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); }); it('keeps waiting for ICE collection when signaling gets connected', () => { @@ -354,7 +391,8 @@ describe('RTC session', () => { state.onIceCandidate({ candidate: { - candidate: 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', + candidate: + 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', sdpMLineIndex: 0, sdpMid: 'audio' } @@ -362,7 +400,8 @@ describe('RTC session', () => { state.onIceCandidate({}); chai.expect(session._onIceCollectionComplete.calledOnce).to.be.true; - chai.expect(session._onIceCollectionComplete.args[0][1]).to.be.false; + chai.expect(session._onIceCollectionComplete.args[0][1]).to.be + .false; chai.expect(session._onIceCollectionComplete.args[0][2]).to.eq(1); chai.expect(session.transit.called).to.be.false; }); @@ -372,34 +411,40 @@ describe('RTC session', () => { state.onIceCandidate({ candidate: { - candidate: 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', + candidate: + 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', sdpMLineIndex: 0, sdpMid: 'audio' } }); - chai.expect(session._onIceCollectionComplete.calledOnce).to.be.false; + chai.expect(session._onIceCollectionComplete.calledOnce).to.be + .false; state.onIceCandidate({ candidate: { - candidate: 'candidate:3517520453 2 udp 41885694 172.22.116.70 56719 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', + candidate: + 'candidate:3517520453 2 udp 41885694 172.22.116.70 56719 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', sdpMLineIndex: 0, sdpMid: 'audio' } }); - chai.expect(session._onIceCollectionComplete.calledOnce).to.be.false; + chai.expect(session._onIceCollectionComplete.calledOnce).to.be + .false; state.onIceCandidate({ candidate: { - candidate: 'candidate:3517520453 1 udp 41885695 172.22.116.70 59377 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', + candidate: + 'candidate:3517520453 1 udp 41885695 172.22.116.70 59377 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', sdpMLineIndex: 1, sdpMid: 'video' } }); chai.expect(session._onIceCollectionComplete.calledOnce).to.be.true; - chai.expect(session._onIceCollectionComplete.args[0][1]).to.be.false; + chai.expect(session._onIceCollectionComplete.args[0][1]).to.be + .false; chai.expect(session._onIceCollectionComplete.args[0][2]).to.eq(3); chai.expect(session.transit.called).to.be.false; }); @@ -409,7 +454,8 @@ describe('RTC session', () => { state.onIceCandidate({ candidate: { - candidate: 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', + candidate: + 'candidate:3517520453 1 udp 41885695 172.22.116.70 59345 typ relay raddr 0.0.0.0 rport 0 generation 0 ufrag dRi5 network-id 3 network-cost 50', sdpMLineIndex: 0, sdpMid: 'audio' } @@ -420,7 +466,9 @@ describe('RTC session', () => { chai.expect(session.transit.calledOnce).to.be.true; chai.expect(session._onSignalingConnected.calledOnce).to.be.true; chai.expect(session._onIceCollectionComplete.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(InviteAnswerState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + InviteAnswerState + ); }); }); @@ -438,9 +486,7 @@ describe('RTC session', () => { var candidates; beforeEach(() => { - candidates = [ - 'cand1' - ]; + candidates = ['cand1']; session = { _logger: console, _localSessionDescription: { @@ -459,10 +505,17 @@ describe('RTC session', () => { it('notifies signaling started and invites on enter', () => { state.onEnter(); - chai.expect(session._onSignalingStarted.calledOnce, 'should have sent session event').to.be.true; + chai.expect( + session._onSignalingStarted.calledOnce, + 'should have sent session event' + ).to.be.true; chai.expect(session._signalingChannel.invite.calledOnce).to.be.true; - chai.expect(session._signalingChannel.invite.args[0][0]).to.be.eq('sdp'); - chai.expect(session._signalingChannel.invite.args[0][1]).to.be.eql(candidates); + chai.expect(session._signalingChannel.invite.args[0][0]).to.be.eq( + 'sdp' + ); + chai.expect(session._signalingChannel.invite.args[0][1]).to.be.eql( + candidates + ); }); it('transits to AcceptState when signaling answer is received', () => { @@ -472,9 +525,13 @@ describe('RTC session', () => { state.onSignalingAnswered('remoteSdp', remoteCandidates); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(AcceptState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + AcceptState + ); chai.expect(session.transit.args[0][0]._sdp).to.be.eq('remoteSdp'); - chai.expect(session.transit.args[0][0]._candidates).to.be.eql(remoteCandidates); + chai.expect(session.transit.args[0][0]._candidates).to.be.eql( + remoteCandidates + ); }); it('transits to FailedState when handshaking fails', () => { @@ -483,18 +540,28 @@ describe('RTC session', () => { state.onSignalingFailed('unknown'); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); - chai.expect(session.transit.args[0][0]._failureReason).to.be.eql(RTC_ERRORS.SIGNALLING_HANDSHAKE_FAILURE); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); + chai.expect(session.transit.args[0][0]._failureReason).to.be.eql( + RTC_ERRORS.SIGNALLING_HANDSHAKE_FAILURE + ); }); it('transits to FailedState with USER_BUSY error code when handshaking fails with BusyException', () => { session.transit = sinon.spy(); - state.onSignalingFailed(new BusyException('Agent already connected')); + state.onSignalingFailed( + new BusyException('Agent already connected') + ); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); - chai.expect(session.transit.args[0][0]._failureReason).to.be.eql(RTC_ERRORS.USER_BUSY); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); + chai.expect(session.transit.args[0][0]._failureReason).to.be.eql( + RTC_ERRORS.USER_BUSY + ); }); it('transits to FailedState with CALL_NOT_FOUND error code when handshaking fails with CallNotFoundException', () => { @@ -503,8 +570,12 @@ describe('RTC session', () => { state.onSignalingFailed(new CallNotFoundException('No such call')); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); - chai.expect(session.transit.args[0][0]._failureReason).to.be.eql(RTC_ERRORS.CALL_NOT_FOUND); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); + chai.expect(session.transit.args[0][0]._failureReason).to.be.eql( + RTC_ERRORS.CALL_NOT_FOUND + ); }); }); @@ -522,9 +593,7 @@ describe('RTC session', () => { var candidates; beforeEach(() => { - candidates = [ - 'cand1' - ]; + candidates = ['cand1']; session = { _logger: console, _stopSession: sinon.spy(), @@ -537,8 +606,8 @@ describe('RTC session', () => { state = new AcceptState(session, 'remoteSdp', candidates); session._state = state; - state._createSessionDescription = (initDict) => initDict; - state._createRemoteCandidate = (initDict) => initDict; + state._createSessionDescription = initDict => initDict; + state._createRemoteCandidate = initDict => initDict; }); it('transits to FailedState if invalid remote SDP is received', () => { @@ -549,7 +618,9 @@ describe('RTC session', () => { state.onEnter(); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); }); it('transits to FailedState if no valid candidate is received', () => { @@ -560,11 +631,13 @@ describe('RTC session', () => { state.onEnter(); chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(FailedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + FailedState + ); }); - it('transits to FailedState if setRemoteDescription fails', (done) => { - session.transit = (nextState) => { + it('transits to FailedState if setRemoteDescription fails', done => { + session.transit = nextState => { chai.expect(session._stopSession.calledOnce).to.be.true; chai.expect(nextState).to.be.instanceof(FailedState); done(); @@ -574,8 +647,8 @@ describe('RTC session', () => { state.onEnter(); }); - it('transits to FailedState if addIceCandidate fails', (done) => { - session.transit = (nextState) => { + it('transits to FailedState if addIceCandidate fails', done => { + session.transit = nextState => { chai.expect(session._stopSession.calledOnce).to.be.true; chai.expect(nextState).to.be.instanceof(FailedState); done(); @@ -596,7 +669,7 @@ describe('RTC session', () => { chai.expect(session.transit.called).to.be.false; }); - it('waits for setting remote info after handshake completes', (done) => { + it('waits for setting remote info after handshake completes', done => { session.transit = sinon.spy(); session._pc.setRemoteDescription.returns(Promise.resolve('Good')); session._pc.addIceCandidate.returns(Promise.resolve('Good')); @@ -605,7 +678,7 @@ describe('RTC session', () => { chai.expect(session.transit.called).to.be.false; - session.transit = (nextState) => { + session.transit = nextState => { chai.expect(nextState).to.be.instanceof(TalkingState); done(); }; @@ -652,7 +725,9 @@ describe('RTC session', () => { chai.expect(session._signalingChannel.hangup.calledOnce).to.be.true; chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(DisconnectedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + DisconnectedState + ); }); it('hangs up signaling when hangup is requested by client', () => { @@ -660,7 +735,9 @@ describe('RTC session', () => { chai.expect(session._signalingChannel.hangup.calledOnce).to.be.true; chai.expect(session.transit.calledOnce).to.be.true; - chai.expect(session.transit.args[0][0]).to.be.instanceof(DisconnectedState); + chai.expect(session.transit.args[0][0]).to.be.instanceof( + DisconnectedState + ); }); it('detachs media and reports session end on exit', () => { diff --git a/test/unit/signaling.js b/test/unit/signaling.js index fea09dd..1ed74f1 100644 --- a/test/unit/signaling.js +++ b/test/unit/signaling.js @@ -9,20 +9,45 @@ */ import RtcSignaling from '../../src/js/signaling'; -import { SignalingState, FailOnTimeoutState, PendingConnectState, PendingInviteState, PendingAnswerState, PendingAcceptState, PendingAcceptAckState, TalkingState, PendingReconnectState, PendingRemoteHangupState, PendingLocalHangupState, DisconnectedState, FailedState } from '../../src/js/signaling'; // eslint-disable-line no-unused-vars -import { TimeoutExceptionName, UnknownSignalingErrorName } from '../../src/js/exceptions'; +import { + SignalingState, + FailOnTimeoutState, + PendingConnectState, + PendingInviteState, + PendingAnswerState, + PendingAcceptState, + PendingAcceptAckState, + TalkingState, + PendingReconnectState, + PendingRemoteHangupState, + PendingLocalHangupState, + DisconnectedState, + FailedState +} from '../../src/js/signaling'; // eslint-disable-line no-unused-vars +import { + TimeoutExceptionName, + UnknownSignalingErrorName +} from '../../src/js/exceptions'; import chai from 'chai'; import sinon from 'sinon'; describe('signalingTest', () => { describe('signalingObject', () => { - var signaling = new RtcSignaling('call Id', 'https://myserver.com/rtc', 'co{n"t=a:c,t&T"ok}en', console, 4000); + var signaling = new RtcSignaling( + 'call Id', + 'https://myserver.com/rtc', + 'co{n"t=a:c,t&T"ok}en', + console, + 4000 + ); it('can be created and initialized', () => { chai.expect(signaling.callId).to.equal('call Id'); }); it('builds correct WSS URL with escaping', () => { - chai.expect(signaling._buildInviteUri()).to.equal('https://myserver.com/rtc?callId=call%20Id&contactCtx=co%7Bn%22t%3Da%3Ac%2Ct%26T%22ok%7Den'); + chai.expect(signaling._buildInviteUri()).to.equal( + 'https://myserver.com/rtc?callId=call%20Id&contactCtx=co%7Bn%22t%3Da%3Ac%2Ct%26T%22ok%7Den' + ); }); it('throws exit exception after calling enter on state transition', () => { @@ -31,9 +56,11 @@ describe('signalingTest', () => { onExit: initStateExit }); var nextStateEnter = sinon.stub.throws(2); - chai.expect(() => signaling.transit({ - onEnter: nextStateEnter - })).to.throw(1); + chai.expect(() => + signaling.transit({ + onEnter: nextStateEnter + }) + ).to.throw(1); }); }); @@ -52,31 +79,37 @@ describe('signalingTest', () => { signalingState = new SignalingState(signaling); }); - it('could tell it\'s not current state', () => { + it("could tell it's not current state", () => { signaling.state = null; chai.expect(signalingState.isCurrentState).to.equal(false); }); - it('could tell it\'s current state', () => { + it("could tell it's current state", () => { signaling.state = signalingState; chai.expect(signalingState.isCurrentState).to.equal(true); }); - it('calls timeout handler if it\'s current state', () => { + it("calls timeout handler if it's current state", () => { signaling.state = signalingState; signalingState.onTimeout = sinon.spy(); signalingState._onTimeoutChecked(); - chai.assert(signalingState.onTimeout.called, 'onTimeout should have been called'); + chai.assert( + signalingState.onTimeout.called, + 'onTimeout should have been called' + ); }); - it('skips calling timeout handler if it\'s not current state', () => { + it("skips calling timeout handler if it's not current state", () => { signaling.state = null; signalingState.onTimeout = sinon.spy(); signalingState._onTimeoutChecked(); - chai.assert(!signalingState.onTimeout.called, 'onTimeout should have not been called'); + chai.assert( + !signalingState.onTimeout.called, + 'onTimeout should have not been called' + ); }); - it('could schedule timeout', (done) => { + it('could schedule timeout', done => { signaling.state = signalingState; signalingState.onTimeout = done; signalingState.setStateTimeout(1); @@ -98,10 +131,15 @@ describe('signalingTest', () => { state = new FailOnTimeoutState(signaling, 1); }); - it('transit to Failed state with Timeout exception', (done) => { - signaling.transit = (nextState) => { - chai.assert(nextState instanceof FailedState, 'next state should be FailedState'); - chai.expect(nextState.exception.name).to.equal(TimeoutExceptionName); + it('transit to Failed state with Timeout exception', done => { + signaling.transit = nextState => { + chai.assert( + nextState instanceof FailedState, + 'next state should be FailedState' + ); + chai.expect(nextState.exception.name).to.equal( + TimeoutExceptionName + ); done(); }; signaling.state = state; @@ -121,8 +159,8 @@ describe('signalingTest', () => { beforeEach(() => { signaling = { - transit: sinon.spy(), - _connect: sinon.spy() + transit: sinon.spy(), + _connect: sinon.spy() }; state = new PendingConnectState(signaling, 500); }); @@ -130,22 +168,30 @@ describe('signalingTest', () => { it('transit to pending invite once WSS is open', () => { state.onOpen(); chai.expect(signaling.transit.calledOnce).to.be.true; - chai.expect(signaling.transit.args[0][0]).to.be.instanceof(PendingInviteState); + chai.expect(signaling.transit.args[0][0]).to.be.instanceof( + PendingInviteState + ); }); it('retries three times if channelDown occurs before timeout', () => { state.channelDown(); state.channelDown(); state.channelDown(); - + chai.expect(signaling.transit.calledThrice).to.be.true; chai.expect(signaling._connect.calledTwice).to.be.true; - chai.expect(signaling.transit.args[0][0]).to.be.instanceof(PendingConnectState); - chai.expect(signaling.transit.args[1][0]).to.be.instanceof(PendingConnectState); - chai.expect(signaling.transit.args[2][0]).to.be.instanceof(FailedState); - }); - - it('doesn\'t attempt to retry if the timeout has elapsed', (done) => { + chai.expect(signaling.transit.args[0][0]).to.be.instanceof( + PendingConnectState + ); + chai.expect(signaling.transit.args[1][0]).to.be.instanceof( + PendingConnectState + ); + chai.expect(signaling.transit.args[2][0]).to.be.instanceof( + FailedState + ); + }); + + it("doesn't attempt to retry if the timeout has elapsed", done => { state.channelDown(); setTimeout(function() { @@ -153,10 +199,13 @@ describe('signalingTest', () => { chai.expect(signaling.transit.calledTwice).to.be.true; chai.expect(signaling._connect.calledOnce).to.be.true; - chai.expect(signaling.transit.args[0][0]).to.be.instanceof(PendingConnectState); - chai.expect(signaling.transit.args[1][0]).to.be.instanceof(FailedState); + chai.expect(signaling.transit.args[0][0]).to.be.instanceof( + PendingConnectState + ); + chai.expect(signaling.transit.args[1][0]).to.be.instanceof( + FailedState + ); done(); - }, 1000); }); }); @@ -176,7 +225,7 @@ describe('signalingTest', () => { state = new PendingInviteState(signaling); }); - it('sends connected event to signaling object on enter', (done) => { + it('sends connected event to signaling object on enter', done => { signaling._connectedHandler = done; state.onEnter(); }); @@ -197,7 +246,9 @@ describe('signalingTest', () => { chai.assert.equal(1, inviteRequest.params.candidates.length); chai.assert.equal('cand1', inviteRequest.params.candidates[0]); chai.assert.isNotNull(inviteRequest.id); - chai.assert(signaling.transit.args[0][0] instanceof PendingAnswerState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingAnswerState + ); }); }); @@ -233,10 +284,12 @@ describe('signalingTest', () => { chai.assert(signaling.transit.calledOnce); var nextState = signaling.transit.args[0][0]; chai.assert(nextState instanceof FailedState); - chai.expect(nextState.exception.name).to.eq(UnknownSignalingErrorName); + chai.expect(nextState.exception.name).to.eq( + UnknownSignalingErrorName + ); }); - it('notifies and goes to pending accept state upon receiving success response', (done) => { + it('notifies and goes to pending accept state upon receiving success response', done => { signaling._logger = { log: sinon.spy() }; @@ -255,7 +308,9 @@ describe('signalingTest', () => { } }); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof PendingAcceptState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingAcceptState + ); }); }); @@ -287,7 +342,9 @@ describe('signalingTest', () => { chai.assert.isNotNull(acceptReq.params); chai.assert.isNotNull(acceptReq.id); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof PendingAcceptAckState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingAcceptAckState + ); }); }); @@ -353,7 +410,7 @@ describe('signalingTest', () => { state = new TalkingState(signaling); }); - it('notify handshake completion on enter', (done) => { + it('notify handshake completion on enter', done => { signaling._handshakedHandler = done; state.onEnter(); }); @@ -371,7 +428,9 @@ describe('signalingTest', () => { chai.assert.isNotNull(hangupReq.params); chai.assert.isNotNull(hangupReq.id); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof PendingRemoteHangupState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingRemoteHangupState + ); }); it('responds to server hangup', () => { @@ -383,7 +442,9 @@ describe('signalingTest', () => { id: 10 }); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof PendingLocalHangupState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingLocalHangupState + ); }); it('responds to token renewal', () => { @@ -404,7 +465,9 @@ describe('signalingTest', () => { state.channelDown(); chai.assert(signaling._reconnect.calledOnce); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof PendingReconnectState); + chai.assert( + signaling.transit.args[0][0] instanceof PendingReconnectState + ); }); }); @@ -454,7 +517,9 @@ describe('signalingTest', () => { result: {} }); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof DisconnectedState); + chai.assert( + signaling.transit.args[0][0] instanceof DisconnectedState + ); }); }); @@ -473,7 +538,7 @@ describe('signalingTest', () => { state = new PendingLocalHangupState(signaling, 8); }); - it('notifies remote hangup on enter', (done) => { + it('notifies remote hangup on enter', done => { signaling._remoteHungupHandler = done; state.onEnter(); }); @@ -490,11 +555,12 @@ describe('signalingTest', () => { chai.assert.equal(8, hangupResp.id); chai.assert.isNotNull(hangupResp.result); chai.assert(signaling.transit.calledOnce); - chai.assert(signaling.transit.args[0][0] instanceof DisconnectedState); + chai.assert( + signaling.transit.args[0][0] instanceof DisconnectedState + ); }); }); - describe('DisconnectedState', () => { /** * @type {RtcSignaling} @@ -510,7 +576,7 @@ describe('signalingTest', () => { state = new DisconnectedState(signaling); }); - it('notifies disconnected on enter', (done) => { + it('notifies disconnected on enter', done => { signaling._wss = { close: sinon.spy() }; @@ -535,7 +601,7 @@ describe('signalingTest', () => { state = new FailedState(signaling); }); - it('notifies failure on enter', (done) => { + it('notifies failure on enter', done => { signaling._wss = { close: sinon.spy() }; diff --git a/test/unit/utils.js b/test/unit/utils.js index b26674f..b0163db 100644 --- a/test/unit/utils.js +++ b/test/unit/utils.js @@ -11,119 +11,124 @@ import { transformSdp, SdpOptions } from '../../src/js/utils'; import chai from 'chai'; - describe('transformSdp', () => { - var inputSdp = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1\r\n" + - "a=rtpmap:103 ISAC/16000\r\n" + - "a=rtpmap:104 ISAC/32000\r\n" + - "a=rtpmap:9 G722/8000\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:8 PCMA/8000\r\n" + - "a=rtpmap:106 CN/32000\r\n" + - "a=rtpmap:105 CN/16000\r\n" + - "a=rtpmap:13 CN/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n"; - + var inputSdp = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1\r\n' + + 'a=rtpmap:103 ISAC/16000\r\n' + + 'a=rtpmap:104 ISAC/32000\r\n' + + 'a=rtpmap:9 G722/8000\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:8 PCMA/8000\r\n' + + 'a=rtpmap:106 CN/32000\r\n' + + 'a=rtpmap:105 CN/16000\r\n' + + 'a=rtpmap:13 CN/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n'; // transformed with default SdpOptions - var defaultOutput = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=rtpmap:103 ISAC/16000\r\n" + - "a=rtpmap:104 ISAC/32000\r\n" + - "a=rtpmap:9 G722/8000\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:8 PCMA/8000\r\n" + - "a=rtpmap:106 CN/32000\r\n" + - "a=rtpmap:105 CN/16000\r\n" + - "a=rtpmap:13 CN/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n"; + var defaultOutput = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=rtpmap:103 ISAC/16000\r\n' + + 'a=rtpmap:104 ISAC/32000\r\n' + + 'a=rtpmap:9 G722/8000\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:8 PCMA/8000\r\n' + + 'a=rtpmap:106 CN/32000\r\n' + + 'a=rtpmap:105 CN/16000\r\n' + + 'a=rtpmap:13 CN/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n'; - var expectedSdpWithOnlyOpus = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 110 111 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n"; + var expectedSdpWithOnlyOpus = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 110 111 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n'; it('Override audio codec to opus', () => { var sdpOptions = new SdpOptions(); sdpOptions.forceCodec['audio'] = 'opus'; - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(expectedSdpWithOnlyOpus); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + expectedSdpWithOnlyOpus + ); sdpOptions.forceCodec['audio'] = 'OPUS'; - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(expectedSdpWithOnlyOpus); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + expectedSdpWithOnlyOpus + ); }); it('Removes nothing without matching media type', () => { @@ -134,155 +139,165 @@ describe('transformSdp', () => { chai.expect(result.mLines).to.eq(1); }); - var expectedPcmuSdp = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 0 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n"; + var expectedPcmuSdp = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 0 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n'; it('Override audio codec to pcmu', () => { var sdpOptions = new SdpOptions(); sdpOptions.forceCodec['audio'] = 'pcmu'; - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(expectedPcmuSdp); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + expectedPcmuSdp + ); }); - var expectedSdpWithDtx = "v=0\r\n" + - "o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:E4/X\r\n" + - "a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n" + - "a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n" + - "a=setup:actpass\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=rtpmap:103 ISAC/16000\r\n" + - "a=rtpmap:104 ISAC/32000\r\n" + - "a=rtpmap:9 G722/8000\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:8 PCMA/8000\r\n" + - "a=rtpmap:106 CN/32000\r\n" + - "a=rtpmap:105 CN/16000\r\n" + - "a=rtpmap:13 CN/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n" + - "a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n" + - "a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n" + - "a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n"; - + var expectedSdpWithDtx = + 'v=0\r\n' + + 'o=- 6968650397182970779 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:E4/X\r\n' + + 'a=ice-pwd:34ijxBABGSaclsOpfc9E042R\r\n' + + 'a=fingerprint:sha-256 26:40:A1:3C:7E:67:75:2F:1B:21:B9:54:68:07:E8:CE:E5:9C:28:2A:E8:D4:36:26:04:C5:5B:0C:04:43:37:CF\r\n' + + 'a=setup:actpass\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1;usedtx=1\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=rtpmap:103 ISAC/16000\r\n' + + 'a=rtpmap:104 ISAC/32000\r\n' + + 'a=rtpmap:9 G722/8000\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:8 PCMA/8000\r\n' + + 'a=rtpmap:106 CN/32000\r\n' + + 'a=rtpmap:105 CN/16000\r\n' + + 'a=rtpmap:13 CN/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2534193841 cname:NsEUa3X+NJbQmyyN\r\n' + + 'a=ssrc:2534193841 msid:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n' + + 'a=ssrc:2534193841 mslabel:idv6kIIdFJ9x3alklN0n3uNGmI8xgFecIJkt\r\n' + + 'a=ssrc:2534193841 label:d26be488-17a9-4f63-85b4-bdcf9754bb9b\r\n'; it('Adds usedtx=1 if enableDtx is true', () => { var sdpOptions = new SdpOptions(); sdpOptions.enableOpusDtx = true; - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(expectedSdpWithDtx); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + expectedSdpWithDtx + ); }); it('Adds usedtx=0 if enableDtx is false', () => { var sdpOptions = new SdpOptions(); sdpOptions.enableOpusDtx = false; - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(defaultOutput); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + defaultOutput + ); }); it('Adds usedtx=0 if enableDtx is not set', () => { var sdpOptions = new SdpOptions(); - chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq(defaultOutput); + chai.expect(transformSdp(inputSdp, sdpOptions).sdp).to.eq( + defaultOutput + ); }); - var audioVideoSdp = "v=0\n" + - "o=mozilla...THIS_IS_SDPARTA-59.0.2 204502799607324848 0 IN IP4 0.0.0.0\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=fingerprint:sha-256 18:78:9C:01:6D:89:69:06:84:AE:1F:D1:B9:10:96:54:97:4C:FA:8D:40:9F:A1:B1:99:4D:70:2B:88:A4:8A:20\r\n" + - "a=group:BUNDLE sdparta_0 sdparta_1\r\n" + - "a=ice-options:trickle\r\n" + - "a=msid-semantic:WMS *\r\n" + - "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=sendrecv\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" + - "a=fmtp:101 0-15\r\n" + - "a=ice-pwd:24e9d315b24ee847d5844c5f7313c42b\r\n" + - "a=ice-ufrag:25047267\r\n" + - "a=mid:sdparta_0\r\n" + - "a=msid:{910b0af4-424f-41c3-8d46-c61f5d21516b} {ed11b922-7eea-4551-ad47-24270c902094}\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:109 opus/48000/2\r\n" + - "a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1;usedtx=0\r\n" + - "a=rtpmap:101 telephone-event/8000/1\r\n" + - "a=setup:actpass\r\n" + - "a=ssrc:3604426642 cname:{2fe325fe-1578-41b9-b8c1-d92e46b5d514}\r\n" + - "m=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=sendrecv\r\n" + - "a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" + - "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n" + - "a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" + - "a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n" + - "a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\r\n" + - "a=fmtp:120 max-fs=12288;max-fr=60\r\n" + - "a=fmtp:121 max-fs=12288;max-fr=60\r\n" + - "a=ice-pwd:24e9d315b24ee847d5844c5f7313c42b\r\n" + - "a=ice-ufrag:25047267\r\n" + - "a=mid:sdparta_1\r\n" + - "a=msid:{910b0af4-424f-41c3-8d46-c61f5d21516b} {5d086e46-9b16-4870-810d-0059a015528a}\r\n" + - "a=rtcp-fb:120 nack\r\n" + - "a=rtcp-fb:120 nack pli\r\n" + - "a=rtcp-fb:120 ccm fir\r\n" + - "a=rtcp-fb:120 goog-remb\r\n" + - "a=rtcp-fb:121 nack\r\n" + - "a=rtcp-fb:121 nack pli\r\n" + - "a=rtcp-fb:121 ccm fir\r\n" + - "a=rtcp-fb:121 goog-remb\r\n" + - "a=rtcp-fb:126 nack\r\n" + - "a=rtcp-fb:126 nack pli\r\n" + - "a=rtcp-fb:126 ccm fir\r\n" + - "a=rtcp-fb:126 goog-remb\r\n" + - "a=rtcp-fb:97 nack\r\n" + - "a=rtcp-fb:97 nack pli\r\n" + - "a=rtcp-fb:97 ccm fir\r\n" + - "a=rtcp-fb:97 goog-remb\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:120 VP8/90000\r\n" + - "a=rtpmap:121 VP9/90000\r\n" + - "a=rtpmap:126 H264/90000\r\n" + - "a=rtpmap:97 H264/90000\r\n" + - "a=setup:actpass\r\n" + - "a=ssrc:2521847393 cname:{2fe325fe-1578-41b9-b8c1-d92e46b5d514}\r\n"; + var audioVideoSdp = + 'v=0\n' + + 'o=mozilla...THIS_IS_SDPARTA-59.0.2 204502799607324848 0 IN IP4 0.0.0.0\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=fingerprint:sha-256 18:78:9C:01:6D:89:69:06:84:AE:1F:D1:B9:10:96:54:97:4C:FA:8D:40:9F:A1:B1:99:4D:70:2B:88:A4:8A:20\r\n' + + 'a=group:BUNDLE sdparta_0 sdparta_1\r\n' + + 'a=ice-options:trickle\r\n' + + 'a=msid-semantic:WMS *\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=sendrecv\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=extmap:2 urn:ietf:params:rtp-hdrext:sdes:mid\r\n' + + 'a=fmtp:101 0-15\r\n' + + 'a=ice-pwd:24e9d315b24ee847d5844c5f7313c42b\r\n' + + 'a=ice-ufrag:25047267\r\n' + + 'a=mid:sdparta_0\r\n' + + 'a=msid:{910b0af4-424f-41c3-8d46-c61f5d21516b} {ed11b922-7eea-4551-ad47-24270c902094}\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:109 opus/48000/2\r\n' + + 'a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1;usedtx=0\r\n' + + 'a=rtpmap:101 telephone-event/8000/1\r\n' + + 'a=setup:actpass\r\n' + + 'a=ssrc:3604426642 cname:{2fe325fe-1578-41b9-b8c1-d92e46b5d514}\r\n' + + 'm=video 9 UDP/TLS/RTP/SAVPF 120 121 126 97\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=sendrecv\r\n' + + 'a=extmap:1 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n' + + 'a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\r\n' + + 'a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid\r\n' + + 'a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\r\n' + + 'a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\r\n' + + 'a=fmtp:120 max-fs=12288;max-fr=60\r\n' + + 'a=fmtp:121 max-fs=12288;max-fr=60\r\n' + + 'a=ice-pwd:24e9d315b24ee847d5844c5f7313c42b\r\n' + + 'a=ice-ufrag:25047267\r\n' + + 'a=mid:sdparta_1\r\n' + + 'a=msid:{910b0af4-424f-41c3-8d46-c61f5d21516b} {5d086e46-9b16-4870-810d-0059a015528a}\r\n' + + 'a=rtcp-fb:120 nack\r\n' + + 'a=rtcp-fb:120 nack pli\r\n' + + 'a=rtcp-fb:120 ccm fir\r\n' + + 'a=rtcp-fb:120 goog-remb\r\n' + + 'a=rtcp-fb:121 nack\r\n' + + 'a=rtcp-fb:121 nack pli\r\n' + + 'a=rtcp-fb:121 ccm fir\r\n' + + 'a=rtcp-fb:121 goog-remb\r\n' + + 'a=rtcp-fb:126 nack\r\n' + + 'a=rtcp-fb:126 nack pli\r\n' + + 'a=rtcp-fb:126 ccm fir\r\n' + + 'a=rtcp-fb:126 goog-remb\r\n' + + 'a=rtcp-fb:97 nack\r\n' + + 'a=rtcp-fb:97 nack pli\r\n' + + 'a=rtcp-fb:97 ccm fir\r\n' + + 'a=rtcp-fb:97 goog-remb\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:120 VP8/90000\r\n' + + 'a=rtpmap:121 VP9/90000\r\n' + + 'a=rtpmap:126 H264/90000\r\n' + + 'a=rtpmap:97 H264/90000\r\n' + + 'a=setup:actpass\r\n' + + 'a=ssrc:2521847393 cname:{2fe325fe-1578-41b9-b8c1-d92e46b5d514}\r\n'; it('Discovers 2 M lines of audio and video', () => { var sdpOptions = new SdpOptions(); @@ -290,70 +305,71 @@ describe('transformSdp', () => { chai.expect(result.mLines).to.eq(2); }); + var inputSdpUnencrypted = + 'v=0\r\n' + + 'o=- 6620764343933944878 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n' + + 'm=audio 9 RTP/AVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:Y3ri\r\n' + + 'a=ice-pwd:G95asCJENMnUeWx2dMhpi/ht\r\n' + + 'a=ice-options:trickle\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=rtpmap:103 ISAC/16000\r\n' + + 'a=rtpmap:104 ISAC/32000\r\n' + + 'a=rtpmap:9 G722/8000\r\n' + + 'a=rtpmap:0 PCMU/8000\r\n' + + 'a=rtpmap:8 PCMA/8000\r\n' + + 'a=rtpmap:106 CN/32000\r\n' + + 'a=rtpmap:105 CN/16000\r\n' + + 'a=rtpmap:13 CN/8000\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2494980608 cname:5kX24X9Mu/D2Pcca\r\n' + + 'a=ssrc:2494980608 msid:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n' + + 'a=ssrc:2494980608 mslabel:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n' + + 'a=ssrc:2494980608 label:b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n'; - var inputSdpUnencrypted = "v=0\r\n" + - "o=- 6620764343933944878 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n" + - "m=audio 9 RTP/AVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:Y3ri\r\n" + - "a=ice-pwd:G95asCJENMnUeWx2dMhpi/ht\r\n" + - "a=ice-options:trickle\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=rtpmap:103 ISAC/16000\r\n" + - "a=rtpmap:104 ISAC/32000\r\n" + - "a=rtpmap:9 G722/8000\r\n" + - "a=rtpmap:0 PCMU/8000\r\n" + - "a=rtpmap:8 PCMA/8000\r\n" + - "a=rtpmap:106 CN/32000\r\n" + - "a=rtpmap:105 CN/16000\r\n" + - "a=rtpmap:13 CN/8000\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2494980608 cname:5kX24X9Mu/D2Pcca\r\n" + - "a=ssrc:2494980608 msid:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n" + - "a=ssrc:2494980608 mslabel:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n" + - "a=ssrc:2494980608 label:b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n"; - - var outputSdpUnencryptedOpusOnly = "v=0\r\n" + - "o=- 6620764343933944878 2 IN IP4 127.0.0.1\r\n" + - "s=-\r\n" + - "t=0 0\r\n" + - "a=group:BUNDLE audio\r\n" + - "a=msid-semantic: WMS epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n" + - "m=audio 9 RTP/AVPF 110 111 112 113 126\r\n" + - "c=IN IP4 0.0.0.0\r\n" + - "a=rtcp:9 IN IP4 0.0.0.0\r\n" + - "a=ice-ufrag:Y3ri\r\n" + - "a=ice-pwd:G95asCJENMnUeWx2dMhpi/ht\r\n" + - "a=ice-options:trickle\r\n" + - "a=mid:audio\r\n" + - "a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" + - "a=sendrecv\r\n" + - "a=rtcp-mux\r\n" + - "a=rtpmap:111 opus/48000/2\r\n" + - "a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n" + - "a=rtcp-fb:111 transport-cc\r\n" + - "a=rtpmap:110 telephone-event/48000\r\n" + - "a=rtpmap:112 telephone-event/32000\r\n" + - "a=rtpmap:113 telephone-event/16000\r\n" + - "a=rtpmap:126 telephone-event/8000\r\n" + - "a=ssrc:2494980608 cname:5kX24X9Mu/D2Pcca\r\n" + - "a=ssrc:2494980608 msid:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n" + - "a=ssrc:2494980608 mslabel:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n" + - "a=ssrc:2494980608 label:b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n"; + var outputSdpUnencryptedOpusOnly = + 'v=0\r\n' + + 'o=- 6620764343933944878 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=group:BUNDLE audio\r\n' + + 'a=msid-semantic: WMS epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n' + + 'm=audio 9 RTP/AVPF 110 111 112 113 126\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:Y3ri\r\n' + + 'a=ice-pwd:G95asCJENMnUeWx2dMhpi/ht\r\n' + + 'a=ice-options:trickle\r\n' + + 'a=mid:audio\r\n' + + 'a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n' + + 'a=sendrecv\r\n' + + 'a=rtcp-mux\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n' + + 'a=fmtp:111 minptime=10;useinbandfec=1;usedtx=0\r\n' + + 'a=rtcp-fb:111 transport-cc\r\n' + + 'a=rtpmap:110 telephone-event/48000\r\n' + + 'a=rtpmap:112 telephone-event/32000\r\n' + + 'a=rtpmap:113 telephone-event/16000\r\n' + + 'a=rtpmap:126 telephone-event/8000\r\n' + + 'a=ssrc:2494980608 cname:5kX24X9Mu/D2Pcca\r\n' + + 'a=ssrc:2494980608 msid:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n' + + 'a=ssrc:2494980608 mslabel:epPgfieKqbbeLwL2l9ulykxiVs3EMcKcvtnu\r\n' + + 'a=ssrc:2494980608 label:b8c6be2c-1869-4fb6-b05e-ceefefcb6d2c\r\n'; it('Properly handles SDP with unencrypted RTP', () => { var sdpOptions = new SdpOptions();