@webex/plugin-meetings 3.0.0-beta.1 → 3.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/errors/webex-errors.js +5 -29
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/constants.js +15 -74
- package/dist/constants.js.map +1 -1
- package/dist/media/index.js +68 -213
- package/dist/media/index.js.map +1 -1
- package/dist/media/internal-media-core-wrapper.js +22 -0
- package/dist/media/internal-media-core-wrapper.js.map +1 -0
- package/dist/media/properties.js +20 -25
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +0 -27
- package/dist/media/util.js.map +1 -1
- package/dist/meeting/index.js +694 -432
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +1 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +3 -44
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +64 -5
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +24 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/members/index.js +68 -0
- package/dist/members/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +132 -0
- package/dist/multistream/mediaRequestManager.js.map +1 -0
- package/dist/multistream/multistreamMedia.js +116 -0
- package/dist/multistream/multistreamMedia.js.map +1 -0
- package/dist/multistream/receiveSlot.js +209 -0
- package/dist/multistream/receiveSlot.js.map +1 -0
- package/dist/multistream/receiveSlotManager.js +195 -0
- package/dist/multistream/receiveSlotManager.js.map +1 -0
- package/dist/multistream/remoteMedia.js +284 -0
- package/dist/multistream/remoteMedia.js.map +1 -0
- package/dist/multistream/remoteMediaGroup.js +243 -0
- package/dist/multistream/remoteMediaGroup.js.map +1 -0
- package/dist/multistream/remoteMediaManager.js +1113 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -0
- package/dist/reconnection-manager/index.js +109 -130
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +57 -240
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +2 -114
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +11 -5
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +2 -0
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +39 -36
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/package.json +20 -19
- package/src/common/errors/webex-errors.js +0 -18
- package/src/constants.ts +139 -180
- package/src/media/index.js +60 -194
- package/src/media/internal-media-core-wrapper.ts +9 -0
- package/src/media/properties.js +19 -25
- package/src/media/util.js +0 -22
- package/src/meeting/index.js +565 -320
- package/src/meeting/request.js +1 -0
- package/src/meeting/util.js +3 -46
- package/src/meetings/index.js +30 -1
- package/src/meetings/util.js +23 -2
- package/src/members/index.js +48 -0
- package/src/multistream/mediaRequestManager.ts +164 -0
- package/src/multistream/multistreamMedia.ts +92 -0
- package/src/multistream/receiveSlot.ts +141 -0
- package/src/multistream/receiveSlotManager.ts +142 -0
- package/src/multistream/remoteMedia.ts +219 -0
- package/src/multistream/remoteMediaGroup.ts +224 -0
- package/src/multistream/remoteMediaManager.ts +911 -0
- package/src/reconnection-manager/index.js +40 -53
- package/src/roap/index.js +47 -207
- package/src/roap/request.js +1 -72
- package/src/roap/turnDiscovery.ts +12 -6
- package/src/statsAnalyzer/global.js +2 -0
- package/src/statsAnalyzer/index.js +32 -46
- package/test/integration/spec/journey.js +1 -1
- package/test/unit/spec/media/index.ts +223 -0
- package/test/unit/spec/media/properties.ts +73 -82
- package/test/unit/spec/meeting/effectsState.js +1 -3
- package/test/unit/spec/meeting/index.js +420 -228
- package/test/unit/spec/meeting/muteState.js +7 -0
- package/test/unit/spec/meeting/utils.js +61 -2
- package/test/unit/spec/meetings/index.js +0 -4
- package/test/unit/spec/members/index.js +164 -2
- package/test/unit/spec/multistream/mediaRequestManager.ts +511 -0
- package/test/unit/spec/multistream/receiveSlot.ts +104 -0
- package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
- package/test/unit/spec/multistream/remoteMedia.ts +217 -0
- package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
- package/test/unit/spec/multistream/remoteMediaManager.ts +1251 -0
- package/test/unit/spec/roap/index.ts +63 -35
- package/test/unit/spec/stats-analyzer/index.js +19 -22
- package/dist/peer-connection-manager/index.js +0 -794
- package/dist/peer-connection-manager/index.js.map +0 -1
- package/dist/roap/collection.js +0 -73
- package/dist/roap/collection.js.map +0 -1
- package/dist/roap/handler.js +0 -337
- package/dist/roap/handler.js.map +0 -1
- package/dist/roap/state.js +0 -164
- package/dist/roap/state.js.map +0 -1
- package/dist/roap/util.js +0 -102
- package/dist/roap/util.js.map +0 -1
- package/src/peer-connection-manager/index.js +0 -723
- package/src/roap/collection.js +0 -63
- package/src/roap/handler.js +0 -252
- package/src/roap/state.js +0 -149
- package/src/roap/util.js +0 -93
- package/test/unit/spec/peerconnection-manager/index.js +0 -188
- package/test/unit/spec/peerconnection-manager/utils.js +0 -48
- package/test/unit/spec/roap/util.js +0 -30
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {cloneDeep} from 'lodash';
|
|
2
|
+
import {MediaConnection as MC} from '@webex/internal-media-core';
|
|
2
3
|
|
|
3
4
|
import EventsScope from '../common/events/events-scope';
|
|
4
|
-
import {DEFAULT_GET_STATS_FILTER,
|
|
5
|
+
import {DEFAULT_GET_STATS_FILTER, STATS, MQA_INTEVAL, NETWORK_TYPE, MEDIA_DEVICES, _UNKNOWN_} from '../constants';
|
|
5
6
|
import mqaData from '../mediaQualityMetrics/config';
|
|
6
7
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
8
|
|
|
@@ -175,7 +176,7 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
/**
|
|
178
|
-
* captures MQA data from
|
|
179
|
+
* captures MQA data from media connection
|
|
179
180
|
*
|
|
180
181
|
* @public
|
|
181
182
|
* @memberof StatsAnalyzer
|
|
@@ -232,8 +233,8 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
232
233
|
// Adding peripheral information
|
|
233
234
|
mqaData.intervals[0].intervalMetadata.peripherals = [];
|
|
234
235
|
mqaData.intervals[0].intervalMetadata.peripherals.push({information: _UNKNOWN_, name: MEDIA_DEVICES.SPEAKER});
|
|
235
|
-
mqaData.intervals[0].intervalMetadata.peripherals.push({information: this.
|
|
236
|
-
mqaData.intervals[0].intervalMetadata.peripherals.push({information: this.
|
|
236
|
+
mqaData.intervals[0].intervalMetadata.peripherals.push({information: this.statsResults[STATS.AUDIO_CORRELATE][STATS.SEND_DIRECTION].trackLabel || _UNKNOWN_, name: MEDIA_DEVICES.MICROPHONE});
|
|
237
|
+
mqaData.intervals[0].intervalMetadata.peripherals.push({information: this.statsResults[STATS.VIDEO_CORRELATE][STATS.SEND_DIRECTION].trackLabel || _UNKNOWN_, name: MEDIA_DEVICES.CAMERA});
|
|
237
238
|
|
|
238
239
|
|
|
239
240
|
mqaData.networkType = this.statsResults.connectionType.local.networkType;
|
|
@@ -263,15 +264,15 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
/**
|
|
266
|
-
* updated the
|
|
267
|
+
* updated the media connection when changed
|
|
267
268
|
*
|
|
268
269
|
* @private
|
|
269
|
-
* @memberof
|
|
270
|
-
* @param {
|
|
270
|
+
* @memberof StatsAnalyzer
|
|
271
|
+
* @param {MC.RoapMediaConnection} mediaConnection
|
|
271
272
|
* @returns {void}
|
|
272
273
|
*/
|
|
273
|
-
|
|
274
|
-
this.
|
|
274
|
+
updateMediaConnection(mediaConnection) {
|
|
275
|
+
this.mediaConnection = mediaConnection;
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
/**
|
|
@@ -279,13 +280,13 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
279
280
|
*
|
|
280
281
|
* @public
|
|
281
282
|
* @memberof StatsAnalyzer
|
|
282
|
-
* @param {
|
|
283
|
+
* @param {MC.RoapMediaConnection} mediaConnection
|
|
283
284
|
* @returns {Promise}
|
|
284
285
|
*/
|
|
285
|
-
startAnalyzer(
|
|
286
|
+
startAnalyzer(mediaConnection) {
|
|
286
287
|
if (!this.statsStarted) {
|
|
287
288
|
this.statsStarted = true;
|
|
288
|
-
this.
|
|
289
|
+
this.mediaConnection = mediaConnection;
|
|
289
290
|
|
|
290
291
|
return this.getStatsAndParse()
|
|
291
292
|
.then(() => {
|
|
@@ -326,10 +327,10 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
326
327
|
if (sendOneLastMqa) {
|
|
327
328
|
return this.getStatsAndParse().then(() => {
|
|
328
329
|
this.sendMqaData();
|
|
329
|
-
this.
|
|
330
|
+
this.mediaConnection = null;
|
|
330
331
|
});
|
|
331
332
|
}
|
|
332
|
-
this.
|
|
333
|
+
this.mediaConnection = null;
|
|
333
334
|
|
|
334
335
|
return Promise.resolve();
|
|
335
336
|
}
|
|
@@ -634,53 +635,38 @@ export class StatsAnalyzer extends EventsScope {
|
|
|
634
635
|
* @returns {Promise}
|
|
635
636
|
*/
|
|
636
637
|
getStatsAndParse() {
|
|
637
|
-
if (!this.
|
|
638
|
+
if (!this.mediaConnection) {
|
|
638
639
|
return Promise.resolve();
|
|
639
640
|
}
|
|
640
641
|
|
|
641
|
-
if (this.
|
|
642
|
-
LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse -->
|
|
642
|
+
if (this.mediaConnection && this.mediaConnection.getConnectionState() === MC.ConnectionState.Failed) {
|
|
643
|
+
LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse --> media connection is in failed state');
|
|
643
644
|
|
|
644
645
|
return Promise.resolve();
|
|
645
646
|
}
|
|
646
647
|
|
|
647
648
|
LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse --> Collecting Stats');
|
|
648
649
|
|
|
649
|
-
return
|
|
650
|
-
this.
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
this.
|
|
655
|
-
|
|
656
|
-
}),
|
|
657
|
-
|
|
658
|
-
this.peerConnection.audioTransceiver.sender.getStats().then((res) => {
|
|
659
|
-
this.filterAndParseGetStatsResults(res, STATS.AUDIO_CORRELATE, true);
|
|
660
|
-
}),
|
|
661
|
-
|
|
662
|
-
this.peerConnection.audioTransceiver.receiver.getStats().then((res) => {
|
|
663
|
-
this.filterAndParseGetStatsResults(res, STATS.AUDIO_CORRELATE, false);
|
|
664
|
-
}),
|
|
665
|
-
|
|
666
|
-
// TODO: add checks for screen share
|
|
667
|
-
this.peerConnection.shareTransceiver.sender.getStats().then((res) => {
|
|
668
|
-
this.filterAndParseGetStatsResults(res, STATS.SHARE_CORRELATE, true);
|
|
669
|
-
}),
|
|
650
|
+
return this.mediaConnection.getTransceiverStats().then((transceiverStats) => {
|
|
651
|
+
this.filterAndParseGetStatsResults(transceiverStats.video.sender, STATS.VIDEO_CORRELATE, true);
|
|
652
|
+
this.filterAndParseGetStatsResults(transceiverStats.video.receiver, STATS.VIDEO_CORRELATE, false);
|
|
653
|
+
this.filterAndParseGetStatsResults(transceiverStats.audio.sender, STATS.AUDIO_CORRELATE, true);
|
|
654
|
+
this.filterAndParseGetStatsResults(transceiverStats.audio.receiver, STATS.AUDIO_CORRELATE, false);
|
|
655
|
+
this.filterAndParseGetStatsResults(transceiverStats.screenShareVideo.sender, STATS.SHARE_CORRELATE, true);
|
|
656
|
+
this.filterAndParseGetStatsResults(transceiverStats.screenShareVideo.receiver, STATS.SHARE_CORRELATE, false);
|
|
670
657
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
658
|
+
// updates the current direction of media
|
|
659
|
+
this.statsResults[STATS.AUDIO_CORRELATE].direction = transceiverStats.audio.currentDirection;
|
|
660
|
+
this.statsResults[STATS.VIDEO_CORRELATE].direction = transceiverStats.video.currentDirection;
|
|
661
|
+
this.statsResults[STATS.SHARE_CORRELATE].direction = transceiverStats.screenShareVideo.currentDirection;
|
|
674
662
|
|
|
675
|
-
|
|
676
|
-
this.statsResults[STATS.
|
|
677
|
-
this.statsResults[STATS.VIDEO_CORRELATE].direction = this.peerConnection.videoTransceiver.currentDirection;
|
|
678
|
-
this.statsResults[STATS.SHARE_CORRELATE].direction = this.peerConnection.shareTransceiver.currentDirection;
|
|
663
|
+
this.statsResults[STATS.AUDIO_CORRELATE][STATS.SEND_DIRECTION].trackLabel = transceiverStats.audio.localTrackLabel;
|
|
664
|
+
this.statsResults[STATS.VIDEO_CORRELATE][STATS.SEND_DIRECTION].trackLabel = transceiverStats.video.localTrackLabel;
|
|
679
665
|
|
|
680
|
-
// Process Stats results every 5 seconds
|
|
681
666
|
this.compareLastStatsResult();
|
|
682
667
|
|
|
683
668
|
// Save the last results to compare with the current
|
|
669
|
+
// DO Deep copy, for some reason it takes the reference all the time rather then old value set
|
|
684
670
|
this.lastStatsResults = JSON.parse(JSON.stringify(this.statsResults));
|
|
685
671
|
|
|
686
672
|
LoggerProxy.logger.trace('StatsAnalyzer:index#getStatsAndParse --> Finished Collecting Stats');
|
|
@@ -364,7 +364,7 @@ skipInNode(describe)('plugin-meetings', () => {
|
|
|
364
364
|
stream: response[0]
|
|
365
365
|
})
|
|
366
366
|
.then(() => {
|
|
367
|
-
console.log('AUDIO ', alice.meeting.mediaProperties.
|
|
367
|
+
console.log('AUDIO ', alice.meeting.mediaProperties.audioTrack);
|
|
368
368
|
assert.equal(alice.meeting.mediaProperties.audioTrack.id, response[0].getAudioTracks()[0].id);
|
|
369
369
|
assert.equal(alice.meeting.mediaProperties.videoTrack.id, oldVideoTrackId);
|
|
370
370
|
})),
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as internalMediaModule from '@webex/plugin-meetings/src/media/internal-media-core-wrapper';
|
|
2
|
+
import Media from '@webex/plugin-meetings/src/media/index';
|
|
3
|
+
import {assert} from '@webex/test-helper-chai';
|
|
4
|
+
import sinon from 'sinon';
|
|
5
|
+
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
6
|
+
|
|
7
|
+
describe('createMediaConnection', () => {
|
|
8
|
+
const fakeRoapMediaConnection = {
|
|
9
|
+
id: 'roap media connection',
|
|
10
|
+
};
|
|
11
|
+
const fakeAudioTrack = {
|
|
12
|
+
id: 'audio track',
|
|
13
|
+
};
|
|
14
|
+
const fakeVideoTrack = {
|
|
15
|
+
id: 'video track',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
sinon.restore();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('creates a RoapMediaConnection when multistream is disabled', () => {
|
|
23
|
+
const roapMediaConnectionConstructorStub = sinon
|
|
24
|
+
.stub(internalMediaModule, 'RoapMediaConnection')
|
|
25
|
+
.returns(fakeRoapMediaConnection);
|
|
26
|
+
|
|
27
|
+
StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
|
|
28
|
+
|
|
29
|
+
const ENABLE_EXTMAP = false;
|
|
30
|
+
const ENABLE_RTX = true;
|
|
31
|
+
|
|
32
|
+
Media.createMediaConnection(
|
|
33
|
+
{
|
|
34
|
+
mediaDirection: {
|
|
35
|
+
sendAudio: true,
|
|
36
|
+
sendVideo: true,
|
|
37
|
+
sendShare: false,
|
|
38
|
+
receiveAudio: true,
|
|
39
|
+
receiveVideo: true,
|
|
40
|
+
receiveShare: true,
|
|
41
|
+
},
|
|
42
|
+
audioTrack: fakeAudioTrack,
|
|
43
|
+
videoTrack: fakeVideoTrack,
|
|
44
|
+
shareTrack: null,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
isMultistream: false,
|
|
48
|
+
remoteQualityLevel: 'HIGH',
|
|
49
|
+
enableRtx: ENABLE_RTX,
|
|
50
|
+
enableExtmap: ENABLE_EXTMAP,
|
|
51
|
+
turnServerInfo: {
|
|
52
|
+
url: 'turn server url',
|
|
53
|
+
username: 'turn username',
|
|
54
|
+
password: 'turn password',
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
assert.calledOnce(roapMediaConnectionConstructorStub);
|
|
59
|
+
assert.calledWith(
|
|
60
|
+
roapMediaConnectionConstructorStub,
|
|
61
|
+
{
|
|
62
|
+
iceServers: [
|
|
63
|
+
{
|
|
64
|
+
urls: 'turn server url',
|
|
65
|
+
username: 'turn username',
|
|
66
|
+
credential: 'turn password',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
skipInactiveTransceivers: false,
|
|
70
|
+
requireH264: true,
|
|
71
|
+
sdpMunging: {
|
|
72
|
+
convertPort9to0: false,
|
|
73
|
+
addContentSlides: true,
|
|
74
|
+
bandwidthLimits: {
|
|
75
|
+
audio: 123,
|
|
76
|
+
video: 456,
|
|
77
|
+
},
|
|
78
|
+
startBitrate: 999,
|
|
79
|
+
periodicKeyframes: 20,
|
|
80
|
+
disableExtmap: !ENABLE_EXTMAP,
|
|
81
|
+
disableRtx: !ENABLE_RTX,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
send: {
|
|
86
|
+
audio: fakeAudioTrack,
|
|
87
|
+
video: fakeVideoTrack,
|
|
88
|
+
screenShareVideo: null,
|
|
89
|
+
},
|
|
90
|
+
receive: {
|
|
91
|
+
audio: true,
|
|
92
|
+
video: true,
|
|
93
|
+
screenShareVideo: true,
|
|
94
|
+
remoteQualityLevel: 'HIGH',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
'mc'
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('creates a MultistreamRoapMediaConnection when multistream is enabled', () => {
|
|
102
|
+
const multistreamRoapMediaConnectionConstructorStub = sinon
|
|
103
|
+
.stub(internalMediaModule, 'MultistreamRoapMediaConnection')
|
|
104
|
+
.returns(fakeRoapMediaConnection);
|
|
105
|
+
|
|
106
|
+
Media.createMediaConnection(
|
|
107
|
+
{},
|
|
108
|
+
{
|
|
109
|
+
isMultistream: true,
|
|
110
|
+
turnServerInfo: {
|
|
111
|
+
url: 'turn server url',
|
|
112
|
+
username: 'turn username',
|
|
113
|
+
password: 'turn password',
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
|
|
118
|
+
assert.calledWith(
|
|
119
|
+
multistreamRoapMediaConnectionConstructorStub,
|
|
120
|
+
{
|
|
121
|
+
iceServers: [
|
|
122
|
+
{
|
|
123
|
+
urls: 'turn server url',
|
|
124
|
+
username: 'turn username',
|
|
125
|
+
credential: 'turn password',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
'mc'
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('passes empty ICE servers array to MultistreamRoapMediaConnection if turnServerInfo is undefined (multistream enabled)', () => {
|
|
134
|
+
const multistreamRoapMediaConnectionConstructorStub = sinon
|
|
135
|
+
.stub(internalMediaModule, 'MultistreamRoapMediaConnection')
|
|
136
|
+
.returns(fakeRoapMediaConnection);
|
|
137
|
+
|
|
138
|
+
Media.createMediaConnection(
|
|
139
|
+
{},
|
|
140
|
+
{
|
|
141
|
+
isMultistream: true,
|
|
142
|
+
turnServerInfo: undefined,
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
|
|
146
|
+
assert.calledWith(
|
|
147
|
+
multistreamRoapMediaConnectionConstructorStub,
|
|
148
|
+
{
|
|
149
|
+
iceServers: [],
|
|
150
|
+
},
|
|
151
|
+
'mc'
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('passes empty ICE servers array to RoapMediaConnection if turnServerInfo is undefined (multistream disabled)', () => {
|
|
156
|
+
const roapMediaConnectionConstructorStub = sinon
|
|
157
|
+
.stub(internalMediaModule, 'RoapMediaConnection')
|
|
158
|
+
.returns(fakeRoapMediaConnection);
|
|
159
|
+
|
|
160
|
+
StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
|
|
161
|
+
|
|
162
|
+
const ENABLE_EXTMAP = false;
|
|
163
|
+
const ENABLE_RTX = true;
|
|
164
|
+
|
|
165
|
+
Media.createMediaConnection(
|
|
166
|
+
{
|
|
167
|
+
mediaDirection: {
|
|
168
|
+
sendAudio: true,
|
|
169
|
+
sendVideo: true,
|
|
170
|
+
sendShare: true,
|
|
171
|
+
receiveAudio: true,
|
|
172
|
+
receiveVideo: true,
|
|
173
|
+
receiveShare: true,
|
|
174
|
+
},
|
|
175
|
+
audioTrack: fakeAudioTrack,
|
|
176
|
+
videoTrack: null,
|
|
177
|
+
shareTrack: fakeVideoTrack,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
isMultistream: false,
|
|
181
|
+
remoteQualityLevel: 'HIGH',
|
|
182
|
+
enableRtx: ENABLE_RTX,
|
|
183
|
+
enableExtmap: ENABLE_EXTMAP,
|
|
184
|
+
turnServerInfo: undefined,
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
assert.calledOnce(roapMediaConnectionConstructorStub);
|
|
188
|
+
assert.calledWith(
|
|
189
|
+
roapMediaConnectionConstructorStub,
|
|
190
|
+
{
|
|
191
|
+
iceServers: [],
|
|
192
|
+
skipInactiveTransceivers: false,
|
|
193
|
+
requireH264: true,
|
|
194
|
+
sdpMunging: {
|
|
195
|
+
convertPort9to0: false,
|
|
196
|
+
addContentSlides: true,
|
|
197
|
+
bandwidthLimits: {
|
|
198
|
+
audio: 123,
|
|
199
|
+
video: 456,
|
|
200
|
+
},
|
|
201
|
+
startBitrate: 999,
|
|
202
|
+
periodicKeyframes: 20,
|
|
203
|
+
disableExtmap: !ENABLE_EXTMAP,
|
|
204
|
+
disableRtx: !ENABLE_RTX,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
send: {
|
|
209
|
+
audio: fakeAudioTrack,
|
|
210
|
+
video: null,
|
|
211
|
+
screenShareVideo: fakeVideoTrack,
|
|
212
|
+
},
|
|
213
|
+
receive: {
|
|
214
|
+
audio: true,
|
|
215
|
+
video: true,
|
|
216
|
+
screenShareVideo: true,
|
|
217
|
+
remoteQualityLevel: 'HIGH',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
'mc'
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {assert} from '@webex/test-helper-chai';
|
|
2
2
|
import sinon from 'sinon';
|
|
3
|
+
import {MediaConnection as MC} from '@webex/internal-media-core';
|
|
3
4
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
|
4
5
|
import MediaUtil from '@webex/plugin-meetings/src/media/util';
|
|
5
6
|
import testUtils from '../../../utils/testUtils';
|
|
@@ -8,47 +9,39 @@ import {Defer} from '@webex/common';
|
|
|
8
9
|
|
|
9
10
|
describe('MediaProperties', () => {
|
|
10
11
|
let mediaProperties;
|
|
11
|
-
let
|
|
12
|
+
let mockMC;
|
|
12
13
|
let clock;
|
|
13
14
|
|
|
14
15
|
beforeEach(() => {
|
|
15
16
|
clock = sinon.useFakeTimers();
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
mockMC = {
|
|
18
19
|
getStats: sinon.stub().resolves([]),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
on: sinon.stub(),
|
|
21
|
+
off: sinon.stub(),
|
|
22
|
+
getConnectionState: sinon.stub().returns(MC.ConnectionState.Connected),
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
sinon.stub(MediaUtil, 'createPeerConnection').returns(mockPc);
|
|
25
|
-
|
|
26
25
|
mediaProperties = new MediaProperties();
|
|
26
|
+
mediaProperties.setMediaPeerConnection(mockMC);
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
afterEach(() => {
|
|
30
30
|
clock.restore();
|
|
31
31
|
sinon.restore();
|
|
32
32
|
});
|
|
33
|
-
describe('
|
|
33
|
+
describe('waitForMediaConnectionConnected', () => {
|
|
34
34
|
it('resolves immediately if ice state is connected', async () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await mediaProperties.waitForIceConnectedState();
|
|
38
|
-
});
|
|
39
|
-
it('resolves immediately if ice state is completed', async () => {
|
|
40
|
-
mockPc.iceConnectionState = 'completed';
|
|
41
|
-
|
|
42
|
-
await mediaProperties.waitForIceConnectedState();
|
|
35
|
+
await mediaProperties.waitForMediaConnectionConnected();
|
|
43
36
|
});
|
|
44
37
|
it('rejects after timeout if ice state does not reach connected/completed', async () => {
|
|
45
|
-
|
|
38
|
+
mockMC.getConnectionState.returns(MC.ConnectionState.Connecting);
|
|
46
39
|
|
|
47
40
|
let promiseResolved = false;
|
|
48
41
|
let promiseRejected = false;
|
|
49
42
|
|
|
50
43
|
mediaProperties
|
|
51
|
-
.
|
|
44
|
+
.waitForMediaConnectionConnected()
|
|
52
45
|
.then(() => {
|
|
53
46
|
promiseResolved = true;
|
|
54
47
|
})
|
|
@@ -66,128 +59,126 @@ describe('MediaProperties', () => {
|
|
|
66
59
|
assert.equal(promiseRejected, true);
|
|
67
60
|
|
|
68
61
|
// check that listener was registered and removed
|
|
69
|
-
assert.calledOnce(
|
|
70
|
-
assert.equal(
|
|
71
|
-
const listener =
|
|
62
|
+
assert.calledOnce(mockMC.on);
|
|
63
|
+
assert.equal(mockMC.on.getCall(0).args[0], MC.Event.CONNECTION_STATE_CHANGED);
|
|
64
|
+
const listener = mockMC.on.getCall(0).args[1];
|
|
72
65
|
|
|
73
|
-
assert.calledOnce(
|
|
74
|
-
assert.calledWith(
|
|
66
|
+
assert.calledOnce(mockMC.off);
|
|
67
|
+
assert.calledWith(mockMC.off, MC.Event.CONNECTION_STATE_CHANGED, listener);
|
|
75
68
|
});
|
|
76
69
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
mockPc.iceConnectionState = 'connecting';
|
|
70
|
+
it(`resolves when media connection reaches "connected" state`, async () => {
|
|
71
|
+
mockMC.getConnectionState.returns(MC.ConnectionState.Connecting);
|
|
80
72
|
|
|
81
|
-
|
|
73
|
+
const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
|
|
82
74
|
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
let promiseResolved = false;
|
|
76
|
+
let promiseRejected = false;
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
78
|
+
mediaProperties
|
|
79
|
+
.waitForMediaConnectionConnected()
|
|
80
|
+
.then(() => {
|
|
81
|
+
promiseResolved = true;
|
|
82
|
+
})
|
|
83
|
+
.catch(() => {
|
|
84
|
+
promiseRejected = true;
|
|
85
|
+
});
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
assert.equal(promiseResolved, false);
|
|
88
|
+
assert.equal(promiseRejected, false);
|
|
97
89
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
// check the right listener was registered
|
|
91
|
+
assert.calledOnce(mockMC.on);
|
|
92
|
+
assert.equal(mockMC.on.getCall(0).args[0], MC.Event.CONNECTION_STATE_CHANGED);
|
|
93
|
+
const listener = mockMC.on.getCall(0).args[1];
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
// call the listener and pretend we are now connected
|
|
96
|
+
mockMC.getConnectionState.returns(MC.ConnectionState.Connected);
|
|
97
|
+
listener();
|
|
98
|
+
await testUtils.flushPromises();
|
|
107
99
|
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
assert.equal(promiseResolved, true);
|
|
101
|
+
assert.equal(promiseRejected, false);
|
|
110
102
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
103
|
+
// check that listener was removed
|
|
104
|
+
assert.calledOnce(mockMC.off);
|
|
105
|
+
assert.calledWith(mockMC.off, MC.Event.CONNECTION_STATE_CHANGED, listener);
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
107
|
+
assert.calledOnce(clearTimeoutSpy);
|
|
108
|
+
});
|
|
118
109
|
});
|
|
119
110
|
|
|
120
111
|
describe('getCurrentConnectionType', () => {
|
|
121
|
-
it('calls
|
|
122
|
-
const spy = sinon.stub(mediaProperties, '
|
|
112
|
+
it('calls waitForMediaConnectionConnected', async () => {
|
|
113
|
+
const spy = sinon.stub(mediaProperties, 'waitForMediaConnectionConnected');
|
|
123
114
|
|
|
124
115
|
await mediaProperties.getCurrentConnectionType();
|
|
125
116
|
|
|
126
117
|
assert.calledOnce(spy);
|
|
127
118
|
});
|
|
128
|
-
it('calls getStats() only after
|
|
129
|
-
const
|
|
119
|
+
it('calls getStats() only after waitForMediaConnectionConnected resolves', async () => {
|
|
120
|
+
const waitForMediaConnectionConnectedResult = new Defer();
|
|
130
121
|
|
|
131
|
-
const
|
|
132
|
-
.stub(mediaProperties, '
|
|
133
|
-
.returns(
|
|
122
|
+
const waitForMediaConnectionConnectedStub = sinon
|
|
123
|
+
.stub(mediaProperties, 'waitForMediaConnectionConnected')
|
|
124
|
+
.returns(waitForMediaConnectionConnectedResult.promise);
|
|
134
125
|
|
|
135
126
|
const result = mediaProperties.getCurrentConnectionType();
|
|
136
127
|
|
|
137
128
|
await testUtils.flushPromises();
|
|
138
129
|
|
|
139
|
-
assert.called(
|
|
140
|
-
assert.notCalled(
|
|
130
|
+
assert.called(waitForMediaConnectionConnectedStub);
|
|
131
|
+
assert.notCalled(mockMC.getStats);
|
|
141
132
|
|
|
142
|
-
|
|
133
|
+
waitForMediaConnectionConnectedResult.resolve();
|
|
143
134
|
await testUtils.flushPromises();
|
|
144
135
|
|
|
145
|
-
assert.called(
|
|
136
|
+
assert.called(mockMC.getStats);
|
|
146
137
|
await result;
|
|
147
138
|
});
|
|
148
|
-
it('rejects if
|
|
149
|
-
const
|
|
139
|
+
it('rejects if waitForMediaConnectionConnected rejects', async () => {
|
|
140
|
+
const waitForMediaConnectionConnectedResult = new Defer();
|
|
150
141
|
|
|
151
|
-
const
|
|
152
|
-
.stub(mediaProperties, '
|
|
153
|
-
.returns(
|
|
142
|
+
const waitForMediaConnectionConnectedStub = sinon
|
|
143
|
+
.stub(mediaProperties, 'waitForMediaConnectionConnected')
|
|
144
|
+
.returns(waitForMediaConnectionConnectedResult.promise);
|
|
154
145
|
|
|
155
146
|
const result = mediaProperties.getCurrentConnectionType();
|
|
156
147
|
|
|
157
148
|
await testUtils.flushPromises();
|
|
158
149
|
|
|
159
|
-
assert.called(
|
|
150
|
+
assert.called(waitForMediaConnectionConnectedStub);
|
|
160
151
|
|
|
161
|
-
|
|
152
|
+
waitForMediaConnectionConnectedResult.reject(new Error('fake error'));
|
|
162
153
|
await testUtils.flushPromises();
|
|
163
154
|
|
|
164
|
-
assert.notCalled(
|
|
155
|
+
assert.notCalled(mockMC.getStats);
|
|
165
156
|
|
|
166
157
|
await assert.isRejected(result);
|
|
167
158
|
});
|
|
168
159
|
it('returns "unknown" if getStats() fails', async () => {
|
|
169
|
-
|
|
160
|
+
mockMC.getStats.rejects(new Error());
|
|
170
161
|
|
|
171
162
|
const connectionType = await mediaProperties.getCurrentConnectionType();
|
|
172
163
|
assert.equal(connectionType, 'unknown');
|
|
173
164
|
});
|
|
174
165
|
|
|
175
166
|
it('returns "unknown" if getStats() returns no candidate pairs', async () => {
|
|
176
|
-
|
|
167
|
+
mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
|
|
177
168
|
|
|
178
169
|
const connectionType = await mediaProperties.getCurrentConnectionType();
|
|
179
170
|
assert.equal(connectionType, 'unknown');
|
|
180
171
|
});
|
|
181
172
|
|
|
182
173
|
it('returns "unknown" if getStats() returns no successful candidate pair', async () => {
|
|
183
|
-
|
|
174
|
+
mockMC.getStats.resolves([{type: 'candidate-pair', id: '1234', state: 'inprogress'}]);
|
|
184
175
|
|
|
185
176
|
const connectionType = await mediaProperties.getCurrentConnectionType();
|
|
186
177
|
assert.equal(connectionType, 'unknown');
|
|
187
178
|
});
|
|
188
179
|
|
|
189
180
|
it('returns "unknown" if getStats() returns a successful candidate pair but local candidate is missing', async () => {
|
|
190
|
-
|
|
181
|
+
mockMC.getStats.resolves([
|
|
191
182
|
{type: 'candidate-pair', id: '1234', state: 'succeeded', localCandidateId: 'wrong id'},
|
|
192
183
|
]);
|
|
193
184
|
|
|
@@ -196,7 +187,7 @@ describe('MediaProperties', () => {
|
|
|
196
187
|
});
|
|
197
188
|
|
|
198
189
|
it('returns "UDP" if getStats() returns a successful candidate pair with udp local candidate', async () => {
|
|
199
|
-
|
|
190
|
+
mockMC.getStats.resolves([
|
|
200
191
|
{
|
|
201
192
|
type: 'candidate-pair',
|
|
202
193
|
id: 'some candidate pair id',
|
|
@@ -212,7 +203,7 @@ describe('MediaProperties', () => {
|
|
|
212
203
|
});
|
|
213
204
|
|
|
214
205
|
it('returns "TCP" if getStats() returns a successful candidate pair with tcp local candidate', async () => {
|
|
215
|
-
|
|
206
|
+
mockMC.getStats.resolves([
|
|
216
207
|
{
|
|
217
208
|
type: 'candidate-pair',
|
|
218
209
|
id: 'some candidate pair id',
|
|
@@ -233,7 +224,7 @@ describe('MediaProperties', () => {
|
|
|
233
224
|
{relayProtocol: 'udp', expectedConnectionType: 'TURN-UDP'},
|
|
234
225
|
].forEach(({relayProtocol, expectedConnectionType}) =>
|
|
235
226
|
it(`returns "${expectedConnectionType}" if getStats() returns a successful candidate pair with a local candidate with relayProtocol=${relayProtocol}`, async () => {
|
|
236
|
-
|
|
227
|
+
mockMC.getStats.resolves([
|
|
237
228
|
{
|
|
238
229
|
type: 'candidate-pair',
|
|
239
230
|
id: 'some candidate pair id',
|
|
@@ -265,7 +256,7 @@ describe('MediaProperties', () => {
|
|
|
265
256
|
// in real life this will never happen and all active candidate pairs will have same transport,
|
|
266
257
|
// but here we're simulating a situation where they have different transports and just checking
|
|
267
258
|
// that the code still works and just returns the first one
|
|
268
|
-
|
|
259
|
+
mockMC.getStats.resolves([
|
|
269
260
|
{
|
|
270
261
|
type: 'inbound-rtp',
|
|
271
262
|
id: 'whatever',
|