@webex/plugin-meetings 3.0.0-beta.145 → 3.0.0-beta.147
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/annotation/annotation.types.js.map +1 -1
- package/dist/annotation/constants.js +6 -5
- package/dist/annotation/constants.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/webex-errors.js +3 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/config.js +1 -7
- package/dist/config.js.map +1 -1
- package/dist/constants.js +7 -15
- package/dist/constants.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/media/index.js +5 -56
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +15 -93
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/index.js +1106 -1876
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.js +88 -184
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +2 -2
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -23
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +1 -2
- package/dist/meetings/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +153 -134
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +8 -7
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/annotation.types.d.ts +9 -1
- package/dist/types/annotation/constants.d.ts +5 -5
- package/dist/types/common/errors/webex-errors.d.ts +1 -1
- package/dist/types/config.d.ts +0 -6
- package/dist/types/constants.d.ts +1 -18
- package/dist/types/index.d.ts +1 -1
- package/dist/types/media/properties.d.ts +16 -38
- package/dist/types/meeting/index.d.ts +92 -352
- package/dist/types/meeting/muteState.d.ts +36 -38
- package/dist/types/meeting/request.d.ts +2 -1
- package/dist/types/meeting/util.d.ts +2 -4
- package/package.json +19 -19
- package/src/annotation/annotation.types.ts +10 -1
- package/src/annotation/constants.ts +5 -5
- package/src/common/errors/webex-errors.ts +6 -2
- package/src/config.ts +0 -6
- package/src/constants.ts +1 -14
- package/src/index.ts +1 -0
- package/src/media/index.ts +10 -53
- package/src/media/properties.ts +32 -92
- package/src/meeting/index.ts +532 -1564
- package/src/meeting/muteState.ts +87 -178
- package/src/meeting/request.ts +4 -3
- package/src/meeting/util.ts +3 -24
- package/src/meetings/index.ts +0 -1
- package/src/reconnection-manager/index.ts +4 -9
- package/src/roap/index.ts +13 -14
- package/test/integration/spec/converged-space-meetings.js +59 -3
- package/test/integration/spec/journey.js +330 -256
- package/test/integration/spec/space-meeting.js +75 -3
- package/test/unit/spec/meeting/index.js +776 -1344
- package/test/unit/spec/meeting/muteState.js +238 -394
- package/test/unit/spec/meeting/request.js +4 -4
- package/test/unit/spec/meeting/utils.js +2 -9
- package/test/unit/spec/multistream/receiveSlot.ts +1 -1
- package/test/unit/spec/roap/index.ts +2 -2
- package/test/utils/integrationTestUtils.js +5 -23
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
import 'jsdom-global/register';
|
|
5
5
|
import {cloneDeep, forEach, isEqual} from 'lodash';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
+
import * as internalMediaModule from '@webex/internal-media-core';
|
|
7
8
|
import StateMachine from 'javascript-state-machine';
|
|
8
9
|
import uuid from 'uuid';
|
|
9
10
|
import {assert} from '@webex/test-helper-chai';
|
|
10
|
-
import {Credentials, Token} from '@webex/webex-core';
|
|
11
|
+
import {Credentials, Token, WebexPlugin} from '@webex/webex-core';
|
|
11
12
|
import Support from '@webex/internal-plugin-support';
|
|
12
13
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
14
|
+
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
13
15
|
import {
|
|
14
16
|
FLOOR_ACTION,
|
|
15
17
|
SHARE_STATUS,
|
|
@@ -30,10 +32,12 @@ import {
|
|
|
30
32
|
Event,
|
|
31
33
|
Errors,
|
|
32
34
|
ErrorType,
|
|
33
|
-
LocalTrackEvents,
|
|
34
35
|
RemoteTrackType,
|
|
35
36
|
MediaType,
|
|
36
37
|
} from '@webex/internal-media-core';
|
|
38
|
+
import {
|
|
39
|
+
LocalTrackEvents,
|
|
40
|
+
} from '@webex/media-helpers';
|
|
37
41
|
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
38
42
|
import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
|
|
39
43
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
@@ -42,6 +46,7 @@ import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
|
42
46
|
import Members from '@webex/plugin-meetings/src/members';
|
|
43
47
|
import * as MembersImport from '@webex/plugin-meetings/src/members';
|
|
44
48
|
import Roap from '@webex/plugin-meetings/src/roap';
|
|
49
|
+
import RoapRequest from '@webex/plugin-meetings/src/roap/request';
|
|
45
50
|
import MeetingRequest from '@webex/plugin-meetings/src/meeting/request';
|
|
46
51
|
import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/request';
|
|
47
52
|
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
@@ -86,6 +91,7 @@ import {
|
|
|
86
91
|
MeetingInfoV2PasswordError,
|
|
87
92
|
MeetingInfoV2PolicyError,
|
|
88
93
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
94
|
+
import {ANNOTATION_POLICY} from "../../../../src/annotation/constants";
|
|
89
95
|
|
|
90
96
|
const {getBrowserName, getOSVersion} = BrowserDetection();
|
|
91
97
|
|
|
@@ -150,6 +156,15 @@ describe('plugin-meetings', () => {
|
|
|
150
156
|
},
|
|
151
157
|
});
|
|
152
158
|
|
|
159
|
+
Object.defineProperty(global.window.navigator, 'permissions', {
|
|
160
|
+
writable: true,
|
|
161
|
+
value: {
|
|
162
|
+
query: sinon.stub().callsFake(async (arg) => {
|
|
163
|
+
return {state: 'granted', name: arg.name};
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
153
168
|
Object.defineProperty(global.window, 'MediaStream', {
|
|
154
169
|
writable: true,
|
|
155
170
|
value: MediaStream,
|
|
@@ -195,6 +210,7 @@ describe('plugin-meetings', () => {
|
|
|
195
210
|
metrics: {},
|
|
196
211
|
stats: {},
|
|
197
212
|
experimental: {enableUnifiedMeetings: true},
|
|
213
|
+
degradationPreferences: { maxMacroblocksLimit: 8192 },
|
|
198
214
|
},
|
|
199
215
|
metrics: {
|
|
200
216
|
type: ['behavioral'],
|
|
@@ -468,246 +484,7 @@ describe('plugin-meetings', () => {
|
|
|
468
484
|
assert.instanceOf(members, Members);
|
|
469
485
|
});
|
|
470
486
|
});
|
|
471
|
-
describe('#isAudioMuted', () => {
|
|
472
|
-
it('should have #isAudioMuted', () => {
|
|
473
|
-
assert.exists(meeting.invite);
|
|
474
|
-
});
|
|
475
|
-
it('should get the audio muted status and return as a boolean', () => {
|
|
476
|
-
const muted = meeting.isAudioMuted();
|
|
477
|
-
|
|
478
|
-
assert.isNotOk(muted);
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
describe('#isAudioSelf', () => {
|
|
482
|
-
it('should have #isAudioSelf', () => {
|
|
483
|
-
assert.exists(meeting.invite);
|
|
484
|
-
});
|
|
485
|
-
it('should get the audio self status and return as a boolean', () => {
|
|
486
|
-
const self = meeting.isAudioSelf();
|
|
487
|
-
|
|
488
|
-
assert.isNotOk(self);
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
describe('#isVideoMuted', () => {
|
|
492
|
-
it('should have #isVideoMuted', () => {
|
|
493
|
-
assert.exists(meeting.isVideoMuted);
|
|
494
|
-
});
|
|
495
|
-
it('should get the video muted status and return as a boolean', () => {
|
|
496
|
-
const muted = meeting.isVideoMuted();
|
|
497
|
-
|
|
498
|
-
assert.isNotOk(muted);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
describe('#isVideoSelf', () => {
|
|
502
|
-
it('should have #isVideoSelf', () => {
|
|
503
|
-
assert.exists(meeting.invite);
|
|
504
|
-
});
|
|
505
|
-
it('should get the video self status and return as a boolean', () => {
|
|
506
|
-
const self = meeting.isVideoSelf();
|
|
507
|
-
|
|
508
|
-
assert.isNotOk(self);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
describe('#muteAudio', () => {
|
|
512
|
-
it('should have #muteAudio', () => {
|
|
513
|
-
assert.exists(meeting.muteAudio);
|
|
514
|
-
});
|
|
515
|
-
describe('before audio is defined', () => {
|
|
516
|
-
it('should reject and return a promise', async () => {
|
|
517
|
-
await meeting.muteAudio().catch((err) => {
|
|
518
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
519
|
-
});
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('should reject and return a promise', async () => {
|
|
523
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
524
|
-
await meeting.muteAudio().catch((err) => {
|
|
525
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
|
|
529
|
-
it('should reject and return a promise', async () => {
|
|
530
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
531
|
-
meeting.mediaId = 'mediaId';
|
|
532
|
-
await meeting.muteAudio().catch((err) => {
|
|
533
|
-
assert.instanceOf(err, ParameterError);
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
describe('after audio is defined', () => {
|
|
538
|
-
let handleClientRequest;
|
|
539
|
-
|
|
540
|
-
beforeEach(() => {
|
|
541
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
542
|
-
meeting.audio = {handleClientRequest};
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
it('should return a promise resolution', async () => {
|
|
546
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
547
|
-
meeting.mediaId = 'mediaId';
|
|
548
|
-
|
|
549
|
-
const audio = meeting.muteAudio();
|
|
550
|
-
|
|
551
|
-
assert.exists(audio.then);
|
|
552
|
-
await audio;
|
|
553
|
-
assert.calledOnce(handleClientRequest);
|
|
554
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
describe('#unmuteAudio', () => {
|
|
559
|
-
it('should have #unmuteAudio', () => {
|
|
560
|
-
assert.exists(meeting.unmuteAudio);
|
|
561
|
-
});
|
|
562
|
-
describe('before audio is defined', () => {
|
|
563
|
-
it('should reject when user not joined', async () => {
|
|
564
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
565
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
566
|
-
});
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
it('should reject when no media is established yet ', async () => {
|
|
570
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
571
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
572
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
it('should reject when audio is not there or established', async () => {
|
|
577
|
-
meeting.mediaId = 'mediaId';
|
|
578
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
579
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
580
|
-
assert.instanceOf(err, ParameterError);
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
describe('after audio is defined', () => {
|
|
585
|
-
let handleClientRequest;
|
|
586
|
-
|
|
587
|
-
beforeEach(() => {
|
|
588
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
589
|
-
meeting.mediaId = 'mediaId';
|
|
590
|
-
meeting.audio = {handleClientRequest};
|
|
591
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
it('should return a promise resolution', async () => {
|
|
595
|
-
meeting.audio = {handleClientRequest};
|
|
596
|
-
|
|
597
|
-
const audio = meeting.unmuteAudio();
|
|
598
|
-
|
|
599
|
-
assert.exists(audio.then);
|
|
600
|
-
await audio;
|
|
601
|
-
assert.calledOnce(handleClientRequest);
|
|
602
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
603
|
-
});
|
|
604
|
-
});
|
|
605
|
-
});
|
|
606
|
-
describe('BNR', () => {
|
|
607
|
-
const fakeMediaTrack = () => ({
|
|
608
|
-
id: Date.now().toString(),
|
|
609
|
-
stop: () => {},
|
|
610
|
-
readyState: 'live',
|
|
611
|
-
enabled: true,
|
|
612
|
-
getSettings: () => ({
|
|
613
|
-
sampleRate: 48000,
|
|
614
|
-
}),
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
beforeEach(() => {
|
|
618
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
|
|
619
|
-
sinon.replace(meeting, 'addMedia', () => {
|
|
620
|
-
sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
|
|
621
|
-
sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
|
|
622
|
-
receiveAudio: true,
|
|
623
|
-
});
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
describe('#muteVideo', () => {
|
|
628
|
-
it('should have #muteVideo', () => {
|
|
629
|
-
assert.exists(meeting.muteVideo);
|
|
630
|
-
});
|
|
631
|
-
describe('before video is defined', () => {
|
|
632
|
-
it('should reject when user not joined', async () => {
|
|
633
|
-
await meeting.muteVideo().catch((err) => {
|
|
634
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
635
|
-
});
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
it('should reject when no media is established', async () => {
|
|
639
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
640
|
-
await meeting.muteVideo().catch((err) => {
|
|
641
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
642
|
-
});
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
it('should reject when no video added or established', async () => {
|
|
646
|
-
meeting.mediaId = 'mediaId';
|
|
647
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
648
|
-
await meeting.muteVideo().catch((err) => {
|
|
649
|
-
assert.instanceOf(err, ParameterError);
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
describe('after video is defined', () => {
|
|
654
|
-
it('should return a promise resolution', async () => {
|
|
655
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
656
|
-
|
|
657
|
-
meeting.mediaId = 'mediaId';
|
|
658
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
659
|
-
meeting.video = {handleClientRequest};
|
|
660
|
-
const video = meeting.muteVideo();
|
|
661
|
-
|
|
662
|
-
assert.exists(video.then);
|
|
663
|
-
await video;
|
|
664
|
-
assert.calledOnce(handleClientRequest);
|
|
665
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
describe('#unmuteVideo', () => {
|
|
670
|
-
it('should have #unmuteVideo', () => {
|
|
671
|
-
assert.exists(meeting.unmuteVideo);
|
|
672
|
-
});
|
|
673
|
-
describe('before video is defined', () => {
|
|
674
|
-
it('should reject no user joined', async () => {
|
|
675
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
676
|
-
assert.instanceOf(err, Error);
|
|
677
|
-
});
|
|
678
|
-
});
|
|
679
487
|
|
|
680
|
-
it('should reject no media established', async () => {
|
|
681
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
682
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
683
|
-
assert.instanceOf(err, Error);
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
it('should reject when no video added or established', async () => {
|
|
688
|
-
meeting.mediaId = 'mediaId';
|
|
689
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
690
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
691
|
-
assert.instanceOf(err, Error);
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
});
|
|
695
|
-
describe('after video is defined', () => {
|
|
696
|
-
it('should return a promise resolution', async () => {
|
|
697
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
698
|
-
|
|
699
|
-
meeting.mediaId = 'mediaId';
|
|
700
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
701
|
-
meeting.video = {handleClientRequest};
|
|
702
|
-
const video = meeting.unmuteVideo();
|
|
703
|
-
|
|
704
|
-
assert.exists(video.then);
|
|
705
|
-
await video;
|
|
706
|
-
assert.calledOnce(handleClientRequest);
|
|
707
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
488
|
describe('#joinWithMedia', () => {
|
|
712
489
|
it('should have #joinWithMedia', () => {
|
|
713
490
|
assert.exists(meeting.joinWithMedia);
|
|
@@ -715,125 +492,21 @@ describe('plugin-meetings', () => {
|
|
|
715
492
|
describe('resolution', () => {
|
|
716
493
|
it('should success and return a promise', async () => {
|
|
717
494
|
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
718
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([test2, test3]));
|
|
719
495
|
meeting.addMedia = sinon.stub().returns(Promise.resolve(test4));
|
|
720
|
-
await meeting.joinWithMedia({});
|
|
496
|
+
const result = await meeting.joinWithMedia({});
|
|
721
497
|
assert.calledOnce(meeting.join);
|
|
722
|
-
assert.calledOnce(meeting.
|
|
498
|
+
assert.calledOnce(meeting.addMedia);
|
|
499
|
+
assert.deepEqual(result, {join: test1, media: test4});
|
|
723
500
|
});
|
|
724
501
|
});
|
|
725
502
|
describe('rejection', () => {
|
|
726
503
|
it('should error out and return a promise', async () => {
|
|
727
504
|
meeting.join = sinon.stub().returns(Promise.reject());
|
|
728
|
-
meeting.getMediaStreams = sinon.stub().returns(true);
|
|
729
505
|
assert.isRejected(meeting.joinWithMedia({}));
|
|
730
506
|
});
|
|
731
507
|
});
|
|
732
508
|
});
|
|
733
|
-
describe('#getMediaStreams', () => {
|
|
734
|
-
beforeEach(() => {
|
|
735
|
-
sinon
|
|
736
|
-
.stub(Media, 'getSupportedDevice')
|
|
737
|
-
.callsFake((options) =>
|
|
738
|
-
Promise.resolve({sendAudio: options.sendAudio, sendVideo: options.sendVideo})
|
|
739
|
-
);
|
|
740
|
-
sinon.stub(Media, 'getUserMedia').returns(Promise.resolve(['stream1', 'stream2']));
|
|
741
|
-
});
|
|
742
|
-
afterEach(() => {
|
|
743
|
-
sinon.restore();
|
|
744
|
-
});
|
|
745
|
-
it('should have #getMediaStreams', () => {
|
|
746
|
-
assert.exists(meeting.getMediaStreams);
|
|
747
|
-
});
|
|
748
|
-
it('should proxy Media getUserMedia, and return a promise', async () => {
|
|
749
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: true});
|
|
750
|
-
|
|
751
|
-
assert.calledOnce(Media.getUserMedia);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
it('uses the preferred video device if set', async () => {
|
|
755
|
-
const videoDevice = 'video1';
|
|
756
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
757
|
-
const audioVideoSettings = {};
|
|
758
|
-
|
|
759
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(videoDevice);
|
|
760
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('480p');
|
|
761
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
762
|
-
|
|
763
|
-
assert.calledWith(
|
|
764
|
-
Media.getUserMedia,
|
|
765
|
-
{
|
|
766
|
-
...mediaDirection,
|
|
767
|
-
isSharing: false,
|
|
768
|
-
},
|
|
769
|
-
{
|
|
770
|
-
video: {
|
|
771
|
-
width: {max: 640, ideal: 640},
|
|
772
|
-
height: {max: 480, ideal: 480},
|
|
773
|
-
deviceId: videoDevice,
|
|
774
|
-
},
|
|
775
|
-
}
|
|
776
|
-
);
|
|
777
|
-
});
|
|
778
|
-
it('will set a new preferred video input device if passed in', async () => {
|
|
779
|
-
// if audioVideo settings parameter specifies a new video device it
|
|
780
|
-
// will store that device as the preferred video device.
|
|
781
|
-
// Which is the case with meeting.updateVideo()
|
|
782
|
-
const oldVideoDevice = 'video1';
|
|
783
|
-
const newVideoDevice = 'video2';
|
|
784
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
785
|
-
const audioVideoSettings = {video: {deviceId: newVideoDevice}};
|
|
786
|
-
|
|
787
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(oldVideoDevice);
|
|
788
|
-
sinon.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
789
509
|
|
|
790
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
791
|
-
|
|
792
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, newVideoDevice);
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
it('uses the passed custom video resolution', async () => {
|
|
796
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
797
|
-
const customAudioVideoSettings = {
|
|
798
|
-
video: {
|
|
799
|
-
width: {
|
|
800
|
-
max: 400,
|
|
801
|
-
ideal: 400,
|
|
802
|
-
},
|
|
803
|
-
height: {
|
|
804
|
-
max: 200,
|
|
805
|
-
ideal: 200,
|
|
806
|
-
},
|
|
807
|
-
frameRate: {
|
|
808
|
-
ideal: 15,
|
|
809
|
-
max: 30,
|
|
810
|
-
},
|
|
811
|
-
facingMode: {
|
|
812
|
-
ideal: 'user',
|
|
813
|
-
},
|
|
814
|
-
},
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('200p');
|
|
818
|
-
await meeting.getMediaStreams(mediaDirection, customAudioVideoSettings);
|
|
819
|
-
|
|
820
|
-
assert.calledWith(
|
|
821
|
-
Media.getUserMedia,
|
|
822
|
-
{
|
|
823
|
-
...mediaDirection,
|
|
824
|
-
isSharing: false,
|
|
825
|
-
},
|
|
826
|
-
customAudioVideoSettings
|
|
827
|
-
);
|
|
828
|
-
});
|
|
829
|
-
it('should not access camera if sendVideo is false ', async () => {
|
|
830
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: false});
|
|
831
|
-
|
|
832
|
-
assert.calledOnce(Media.getUserMedia);
|
|
833
|
-
|
|
834
|
-
assert.equal(Media.getUserMedia.args[0][0].sendVideo, false);
|
|
835
|
-
});
|
|
836
|
-
});
|
|
837
510
|
describe('#isTranscriptionSupported', () => {
|
|
838
511
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
839
512
|
meeting.locusInfo.controls = {transcribe: {transcribing: false}};
|
|
@@ -1158,7 +831,7 @@ describe('plugin-meetings', () => {
|
|
|
1158
831
|
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
1159
832
|
meeting.audio = muteStateStub;
|
|
1160
833
|
meeting.video = muteStateStub;
|
|
1161
|
-
|
|
834
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
1162
835
|
meeting.setMercuryListener = sinon.stub().returns(true);
|
|
1163
836
|
meeting.setupMediaConnectionListeners = sinon.stub();
|
|
1164
837
|
meeting.setMercuryListener = sinon.stub();
|
|
@@ -1239,6 +912,7 @@ describe('plugin-meetings', () => {
|
|
|
1239
912
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1240
913
|
reason: err.message,
|
|
1241
914
|
stack: err.stack,
|
|
915
|
+
code: err.code,
|
|
1242
916
|
turnDiscoverySkippedReason: 'config',
|
|
1243
917
|
turnServerUsed: false,
|
|
1244
918
|
isMultistream: false,
|
|
@@ -1702,649 +1376,810 @@ describe('plugin-meetings', () => {
|
|
|
1702
1376
|
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
1703
1377
|
});
|
|
1704
1378
|
});
|
|
1705
|
-
describe('#acknowledge', () => {
|
|
1706
|
-
it('should have #acknowledge', () => {
|
|
1707
|
-
assert.exists(meeting.acknowledge);
|
|
1708
|
-
});
|
|
1709
|
-
beforeEach(() => {
|
|
1710
|
-
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
1711
|
-
});
|
|
1712
|
-
it('should acknowledge incoming and return a promise', async () => {
|
|
1713
|
-
const ack = meeting.acknowledge('INCOMING', false);
|
|
1714
1379
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1380
|
+
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
1381
|
+
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
1382
|
+
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
1383
|
+
to @webex/internal-media-core when addMedia, updateMedia, publishTracks, unpublishTracks are called
|
|
1384
|
+
in various combinations.
|
|
1385
|
+
*/
|
|
1386
|
+
[true,false].forEach((isMultistream) =>
|
|
1387
|
+
describe(`addMedia/updateMedia semi-integration tests (${isMultistream ? 'multistream' : 'transcoded'})`, () => {
|
|
1388
|
+
const webrtcAudioTrack = {
|
|
1389
|
+
id: 'underlying audio track',
|
|
1390
|
+
getSettings: sinon.stub().returns({deviceId: 'fake device id for audio track'}),
|
|
1391
|
+
};
|
|
1721
1392
|
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
it('should have #decline', () => {
|
|
1729
|
-
assert.exists(meeting.decline);
|
|
1730
|
-
});
|
|
1731
|
-
beforeEach(() => {
|
|
1732
|
-
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
1733
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1734
|
-
});
|
|
1735
|
-
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
1736
|
-
await meeting.decline();
|
|
1737
|
-
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
1738
|
-
});
|
|
1739
|
-
});
|
|
1740
|
-
describe('#leave', () => {
|
|
1741
|
-
let sandbox;
|
|
1393
|
+
let fakeMicrophoneTrack;
|
|
1394
|
+
let fakeRoapMediaConnection;
|
|
1395
|
+
let fakeMultistreamRoapMediaConnection;
|
|
1396
|
+
let roapMediaConnectionConstructorStub;
|
|
1397
|
+
let multistreamRoapMediaConnectionConstructorStub;
|
|
1398
|
+
let locusMediaRequestStub; // stub for /media requests to Locus
|
|
1742
1399
|
|
|
1743
|
-
|
|
1744
|
-
assert.exists(meeting.leave);
|
|
1745
|
-
});
|
|
1400
|
+
const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
|
|
1746
1401
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
assert.instanceOf(err, MeetingNotActiveError);
|
|
1750
|
-
});
|
|
1751
|
-
});
|
|
1402
|
+
let expectedMediaConnectionConfig;
|
|
1403
|
+
let expectedDebugId;
|
|
1752
1404
|
|
|
1753
|
-
|
|
1754
|
-
meeting.meetingState = 'ACTIVE';
|
|
1755
|
-
await meeting.leave().catch((err) => {
|
|
1756
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
1757
|
-
});
|
|
1758
|
-
});
|
|
1405
|
+
let clock;
|
|
1759
1406
|
|
|
1760
1407
|
beforeEach(() => {
|
|
1761
|
-
|
|
1762
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1763
|
-
meeting.meetingFiniteStateMachine.join();
|
|
1764
|
-
meeting.meetingRequest.leaveMeeting = sinon
|
|
1765
|
-
.stub()
|
|
1766
|
-
.returns(Promise.resolve({body: 'test'}));
|
|
1767
|
-
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
1768
|
-
// the 3 need to be promises because we do closeLocalStream.then(closeLocalShare.then) etc in the src code
|
|
1769
|
-
meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
|
|
1770
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
1771
|
-
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
1772
|
-
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
1773
|
-
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
1774
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
1775
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
1776
|
-
meeting.unsetRemoteTracks = sinon.stub();
|
|
1777
|
-
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
1778
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
1779
|
-
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
1780
|
-
meeting.logger.error = sinon.stub().returns(true);
|
|
1781
|
-
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
1408
|
+
clock = sinon.useFakeTimers();
|
|
1782
1409
|
|
|
1783
|
-
|
|
1410
|
+
meeting.deviceUrl = 'deviceUrl';
|
|
1411
|
+
meeting.config.deviceType = 'web';
|
|
1412
|
+
meeting.isMultistream = isMultistream;
|
|
1784
1413
|
meeting.meetingState = 'ACTIVE';
|
|
1785
|
-
meeting.
|
|
1414
|
+
meeting.mediaId = 'fake media id';
|
|
1415
|
+
meeting.selfUrl = 'selfUrl';
|
|
1416
|
+
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
1417
|
+
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
1418
|
+
meeting.setMercuryListener = sinon.stub();
|
|
1419
|
+
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
1420
|
+
meeting.webex.meetings.reachability = {
|
|
1421
|
+
isAnyClusterReachable: sinon.stub().resolves(true),
|
|
1422
|
+
};
|
|
1423
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
1424
|
+
.stub()
|
|
1425
|
+
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
|
|
1426
|
+
|
|
1427
|
+
StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
|
|
1428
|
+
|
|
1429
|
+
Metrics.postEvent = sinon.stub();
|
|
1430
|
+
|
|
1431
|
+
// setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
|
|
1432
|
+
expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
|
|
1433
|
+
expectedMediaConnectionConfig = {
|
|
1434
|
+
iceServers: [ { urls: undefined, username: '', credential: '' } ],
|
|
1435
|
+
skipInactiveTransceivers: false,
|
|
1436
|
+
requireH264: true,
|
|
1437
|
+
sdpMunging: {
|
|
1438
|
+
convertPort9to0: false,
|
|
1439
|
+
addContentSlides: true,
|
|
1440
|
+
bandwidthLimits: {
|
|
1441
|
+
audio: StaticConfig.meetings.bandwidth.audio,
|
|
1442
|
+
video: StaticConfig.meetings.bandwidth.video,
|
|
1443
|
+
},
|
|
1444
|
+
startBitrate: StaticConfig.meetings.bandwidth.startBitrate,
|
|
1445
|
+
periodicKeyframes: 20,
|
|
1446
|
+
disableExtmap: !meeting.config.enableExtmap,
|
|
1447
|
+
disableRtx: !meeting.config.enableRtx,
|
|
1448
|
+
},
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
// setup stubs
|
|
1452
|
+
fakeMicrophoneTrack = {
|
|
1453
|
+
id: 'fake mic',
|
|
1454
|
+
on: sinon.stub(),
|
|
1455
|
+
off: sinon.stub(),
|
|
1456
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1457
|
+
setMuted: sinon.stub(),
|
|
1458
|
+
setPublished: sinon.stub(),
|
|
1459
|
+
muted: false,
|
|
1460
|
+
underlyingTrack: webrtcAudioTrack
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
fakeRoapMediaConnection = {
|
|
1464
|
+
id: 'roap media connection',
|
|
1465
|
+
close: sinon.stub(),
|
|
1466
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1467
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1468
|
+
update: sinon.stub().resolves({}),
|
|
1469
|
+
on: sinon.stub(),
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
fakeMultistreamRoapMediaConnection = {
|
|
1473
|
+
id: 'multistream roap media connection',
|
|
1474
|
+
close: sinon.stub(),
|
|
1475
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1476
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1477
|
+
publishTrack: sinon.stub().resolves({}),
|
|
1478
|
+
unpublishTrack: sinon.stub().resolves({}),
|
|
1479
|
+
on: sinon.stub(),
|
|
1480
|
+
requestMedia: sinon.stub(),
|
|
1481
|
+
createReceiveSlot: sinon.stub().resolves({on: sinon.stub()}),
|
|
1482
|
+
enableMultistreamAudio: sinon.stub(),
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
roapMediaConnectionConstructorStub = sinon
|
|
1486
|
+
.stub(internalMediaModule, 'RoapMediaConnection')
|
|
1487
|
+
.returns(fakeRoapMediaConnection);
|
|
1488
|
+
|
|
1489
|
+
multistreamRoapMediaConnectionConstructorStub = sinon
|
|
1490
|
+
.stub(internalMediaModule, 'MultistreamRoapMediaConnection')
|
|
1491
|
+
.returns(fakeMultistreamRoapMediaConnection);
|
|
1492
|
+
|
|
1493
|
+
locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({body: {locus: { fullState: {}}}});
|
|
1786
1494
|
});
|
|
1495
|
+
|
|
1787
1496
|
afterEach(() => {
|
|
1788
|
-
|
|
1789
|
-
sandbox = null;
|
|
1497
|
+
clock.restore();
|
|
1790
1498
|
});
|
|
1791
|
-
it('should leave the meeting and return promise', async () => {
|
|
1792
|
-
const leave = meeting.leave();
|
|
1793
1499
|
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
describe('after audio/video is defined', () => {
|
|
1807
|
-
let handleClientRequest;
|
|
1500
|
+
// helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
|
|
1501
|
+
const stableState = async () => {
|
|
1502
|
+
await testUtils.flushPromises();
|
|
1503
|
+
clock.tick(1); // needed because LocusMediaRequest uses Lodash.defer()
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
const resetHistory = () => {
|
|
1507
|
+
locusMediaRequestStub.resetHistory();
|
|
1508
|
+
fakeRoapMediaConnection.update.resetHistory();
|
|
1509
|
+
fakeMultistreamRoapMediaConnection.publishTrack.resetHistory();
|
|
1510
|
+
fakeMultistreamRoapMediaConnection.unpublishTrack.resetHistory();
|
|
1511
|
+
};
|
|
1808
1512
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1513
|
+
const getRoapListener = () => {
|
|
1514
|
+
const roapMediaConnectionToCheck = isMultistream ? fakeMultistreamRoapMediaConnection : fakeRoapMediaConnection;
|
|
1811
1515
|
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1516
|
+
for(let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx+= 1) {
|
|
1517
|
+
if (roapMediaConnectionToCheck.on.getCall(idx).args[0] === Event.ROAP_MESSAGE_TO_SEND) {
|
|
1518
|
+
return roapMediaConnectionToCheck.on.getCall(idx).args[1];
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
assert.fail('listener for "roap:messageToSend" (Event.ROAP_MESSAGE_TO_SEND) was not registered')
|
|
1522
|
+
}
|
|
1815
1523
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1524
|
+
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
1525
|
+
const simulateRoapOffer = async () => {
|
|
1526
|
+
const roapListener = getRoapListener();
|
|
1818
1527
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1528
|
+
await roapListener({roapMessage: roapOfferMessage});
|
|
1529
|
+
await stableState();
|
|
1530
|
+
}
|
|
1821
1531
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1532
|
+
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
1533
|
+
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
1534
|
+
|
|
1535
|
+
assert.calledWith(locusMediaRequestStub,
|
|
1536
|
+
{
|
|
1537
|
+
method: 'PUT',
|
|
1538
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1539
|
+
body: {
|
|
1540
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1541
|
+
correlationId: meeting.correlationId,
|
|
1542
|
+
localMedias: [
|
|
1543
|
+
{
|
|
1544
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}"}}`,
|
|
1545
|
+
mediaId: 'fake media id'
|
|
1546
|
+
}
|
|
1547
|
+
],
|
|
1548
|
+
clientMediaPreferences: {
|
|
1549
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1550
|
+
joinCookie: undefined
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
const checkLocalMuteSentToLocus = ({audioMuted, videoMuted}) => {
|
|
1557
|
+
assert.calledWith(locusMediaRequestStub, {
|
|
1558
|
+
method: 'PUT',
|
|
1559
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1560
|
+
body: {
|
|
1561
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1562
|
+
correlationId: meeting.correlationId,
|
|
1563
|
+
localMedias: [
|
|
1564
|
+
{
|
|
1565
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted}}`,
|
|
1566
|
+
mediaId: 'fake media id'
|
|
1567
|
+
}
|
|
1568
|
+
],
|
|
1569
|
+
clientMediaPreferences: {
|
|
1570
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1571
|
+
},
|
|
1572
|
+
respOnlySdp: true,
|
|
1573
|
+
usingResource: null,
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
const checkMediaConnectionCreated = ({mediaConnectionConfig, localTracks, direction, remoteQualityLevel, expectedDebugId}) => {
|
|
1579
|
+
if (isMultistream) {
|
|
1580
|
+
const {iceServers} = mediaConnectionConfig;
|
|
1581
|
+
|
|
1582
|
+
assert.calledOnceWithExactly(multistreamRoapMediaConnectionConstructorStub, {
|
|
1583
|
+
iceServers,
|
|
1584
|
+
enableMainAudio: direction.audio !== 'inactive',
|
|
1585
|
+
enableMainVideo: true
|
|
1586
|
+
}, expectedDebugId);
|
|
1587
|
+
|
|
1588
|
+
Object.values(localTracks).forEach((track) => {
|
|
1589
|
+
if (track) {
|
|
1590
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, track);
|
|
1591
|
+
}
|
|
1592
|
+
})
|
|
1593
|
+
} else {
|
|
1594
|
+
assert.calledOnceWithExactly(roapMediaConnectionConstructorStub, mediaConnectionConfig,
|
|
1595
|
+
{
|
|
1596
|
+
localTracks: {
|
|
1597
|
+
audio: localTracks.audio?.underlyingTrack,
|
|
1598
|
+
video: localTracks.video?.underlyingTrack,
|
|
1599
|
+
screenShareVideo: localTracks.screenShareVideo?.underlyingTrack,
|
|
1600
|
+
},
|
|
1601
|
+
direction,
|
|
1602
|
+
remoteQualityLevel,
|
|
1603
|
+
},
|
|
1604
|
+
expectedDebugId);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
it('addMedia() works correctly when media is enabled without tracks to publish', async () => {
|
|
1609
|
+
await meeting.addMedia();
|
|
1610
|
+
await simulateRoapOffer();
|
|
1611
|
+
|
|
1612
|
+
// check RoapMediaConnection was created correctly
|
|
1613
|
+
checkMediaConnectionCreated({
|
|
1614
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1615
|
+
localTracks: {
|
|
1616
|
+
audio: undefined,
|
|
1617
|
+
video: undefined,
|
|
1618
|
+
screenShareVideo: undefined,
|
|
1619
|
+
},
|
|
1620
|
+
direction: {
|
|
1621
|
+
audio: 'sendrecv',
|
|
1622
|
+
video: 'sendrecv',
|
|
1623
|
+
screenShareVideo: 'recvonly',
|
|
1624
|
+
},
|
|
1625
|
+
remoteQualityLevel: 'HIGH',
|
|
1626
|
+
expectedDebugId,
|
|
1824
1627
|
});
|
|
1628
|
+
|
|
1629
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1630
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1631
|
+
|
|
1632
|
+
// and that it was the only /media request that was sent
|
|
1633
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1825
1634
|
});
|
|
1826
|
-
it('should leave the meeting without leaving resource', async () => {
|
|
1827
|
-
const leave = meeting.leave({resourceId: null});
|
|
1828
1635
|
|
|
1829
|
-
|
|
1830
|
-
await
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1636
|
+
it('addMedia() works correctly when media is enabled with tracks to publish', async () => {
|
|
1637
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1638
|
+
await simulateRoapOffer();
|
|
1639
|
+
|
|
1640
|
+
// check RoapMediaConnection was created correctly
|
|
1641
|
+
checkMediaConnectionCreated({
|
|
1642
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1643
|
+
localTracks: {
|
|
1644
|
+
audio: fakeMicrophoneTrack,
|
|
1645
|
+
video: undefined,
|
|
1646
|
+
screenShareVideo: undefined,
|
|
1647
|
+
},
|
|
1648
|
+
direction: {
|
|
1649
|
+
audio: 'sendrecv',
|
|
1650
|
+
video: 'sendrecv',
|
|
1651
|
+
screenShareVideo: 'recvonly',
|
|
1652
|
+
},
|
|
1653
|
+
remoteQualityLevel: 'HIGH',
|
|
1654
|
+
expectedDebugId
|
|
1837
1655
|
});
|
|
1656
|
+
|
|
1657
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1658
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1659
|
+
|
|
1660
|
+
// and no other local mute requests were sent to Locus
|
|
1661
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1838
1662
|
});
|
|
1839
|
-
it('should leave the meeting on the resource', async () => {
|
|
1840
|
-
const leave = meeting.leave();
|
|
1841
1663
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1664
|
+
it('addMedia() works correctly when media is enabled with tracks to publish and track is muted', async () => {
|
|
1665
|
+
fakeMicrophoneTrack.muted = true;
|
|
1666
|
+
|
|
1667
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1668
|
+
await simulateRoapOffer();
|
|
1669
|
+
|
|
1670
|
+
// check RoapMediaConnection was created correctly
|
|
1671
|
+
checkMediaConnectionCreated({
|
|
1672
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1673
|
+
localTracks: {
|
|
1674
|
+
audio: fakeMicrophoneTrack,
|
|
1675
|
+
video: undefined,
|
|
1676
|
+
screenShareVideo: undefined,
|
|
1677
|
+
},
|
|
1678
|
+
direction: {
|
|
1679
|
+
audio: 'sendrecv',
|
|
1680
|
+
video: 'sendrecv',
|
|
1681
|
+
screenShareVideo: 'recvonly',
|
|
1682
|
+
},
|
|
1683
|
+
remoteQualityLevel: 'HIGH',
|
|
1684
|
+
expectedDebugId,
|
|
1850
1685
|
});
|
|
1686
|
+
|
|
1687
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1688
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1689
|
+
|
|
1690
|
+
// and no other local mute requests were sent to Locus
|
|
1691
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1851
1692
|
});
|
|
1852
|
-
it('should leave the meeting on the resource with reason', async () => {
|
|
1853
|
-
const leave = meeting.leave({resourceId: meeting.resourceId, reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST});
|
|
1854
1693
|
|
|
1855
|
-
|
|
1856
|
-
await
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1694
|
+
it('addMedia() works correctly when media is disabled with tracks to publish', async () => {
|
|
1695
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}, audioEnabled: false});
|
|
1696
|
+
await simulateRoapOffer();
|
|
1697
|
+
|
|
1698
|
+
// check RoapMediaConnection was created correctly
|
|
1699
|
+
checkMediaConnectionCreated({
|
|
1700
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1701
|
+
localTracks: {
|
|
1702
|
+
audio: fakeMicrophoneTrack,
|
|
1703
|
+
video: undefined,
|
|
1704
|
+
screenShareVideo: undefined,
|
|
1705
|
+
},
|
|
1706
|
+
direction: {
|
|
1707
|
+
audio: 'inactive',
|
|
1708
|
+
video: 'sendrecv',
|
|
1709
|
+
screenShareVideo: 'recvonly',
|
|
1710
|
+
},
|
|
1711
|
+
remoteQualityLevel: 'HIGH',
|
|
1712
|
+
expectedDebugId
|
|
1864
1713
|
});
|
|
1865
|
-
});
|
|
1866
|
-
});
|
|
1867
|
-
describe('#requestScreenShareFloor', () => {
|
|
1868
|
-
it('should have #requestScreenShareFloor', () => {
|
|
1869
|
-
assert.exists(meeting.requestScreenShareFloor);
|
|
1870
|
-
});
|
|
1871
|
-
beforeEach(() => {
|
|
1872
|
-
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
1873
|
-
meeting.locusInfo.self = {url: url1};
|
|
1874
|
-
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
1875
|
-
});
|
|
1876
|
-
it('should send the share', async () => {
|
|
1877
|
-
const share = meeting.requestScreenShareFloor();
|
|
1878
1714
|
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1715
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1716
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1717
|
+
|
|
1718
|
+
// and no other local mute requests were sent to Locus
|
|
1719
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1882
1720
|
});
|
|
1883
|
-
});
|
|
1884
1721
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1722
|
+
it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
|
|
1723
|
+
await meeting.addMedia({audioEnabled: false});
|
|
1724
|
+
await simulateRoapOffer();
|
|
1887
1725
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1726
|
+
// check RoapMediaConnection was created correctly
|
|
1727
|
+
checkMediaConnectionCreated({
|
|
1728
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1729
|
+
localTracks: {
|
|
1730
|
+
audio: undefined,
|
|
1731
|
+
video: undefined,
|
|
1732
|
+
screenShareVideo: undefined,
|
|
1733
|
+
},
|
|
1734
|
+
direction: {
|
|
1735
|
+
audio: 'inactive',
|
|
1736
|
+
video: 'sendrecv',
|
|
1737
|
+
screenShareVideo: 'recvonly',
|
|
1738
|
+
},
|
|
1739
|
+
remoteQualityLevel: 'HIGH',
|
|
1740
|
+
expectedDebugId
|
|
1741
|
+
});
|
|
1894
1742
|
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
});
|
|
1743
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1744
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1898
1745
|
|
|
1899
|
-
|
|
1900
|
-
assert.
|
|
1746
|
+
// and no other local mute requests were sent to Locus
|
|
1747
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1901
1748
|
});
|
|
1902
1749
|
|
|
1903
|
-
describe('
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1750
|
+
describe('publishTracks()/unpublishTracks() calls', () => {
|
|
1751
|
+
[
|
|
1752
|
+
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
1753
|
+
{mediaEnabled: false, expected: {direction: 'inactive', localMuteSentValue: undefined}}
|
|
1754
|
+
]
|
|
1755
|
+
.forEach(({mediaEnabled, expected}) => {
|
|
1756
|
+
it(`first publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1757
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1758
|
+
await simulateRoapOffer();
|
|
1759
|
+
|
|
1760
|
+
resetHistory();
|
|
1761
|
+
|
|
1762
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1763
|
+
await stableState();
|
|
1764
|
+
|
|
1765
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1766
|
+
// check local mute was sent and it was the only /media request
|
|
1767
|
+
checkLocalMuteSentToLocus({audioMuted: expected.localMuteSentValue, videoMuted: true});
|
|
1768
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1769
|
+
} else {
|
|
1770
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1771
|
+
}
|
|
1772
|
+
if (isMultistream) {
|
|
1773
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack);
|
|
1774
|
+
} else {
|
|
1775
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1776
|
+
localTracks: { audio: webrtcAudioTrack, video: null, screenShareVideo: null },
|
|
1777
|
+
direction: {
|
|
1778
|
+
audio: expected.direction,
|
|
1779
|
+
video: 'sendrecv',
|
|
1780
|
+
screenShareVideo: 'recvonly',
|
|
1781
|
+
},
|
|
1782
|
+
remoteQualityLevel: 'HIGH'
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1908
1786
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1787
|
+
it(`second publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1788
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1789
|
+
await simulateRoapOffer();
|
|
1790
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1791
|
+
await stableState();
|
|
1792
|
+
|
|
1793
|
+
resetHistory();
|
|
1794
|
+
|
|
1795
|
+
const webrtcAudioTrack2 = {id: 'underlying audio track 2'};
|
|
1796
|
+
const fakeMicrophoneTrack2 = {
|
|
1797
|
+
id: 'fake mic 2',
|
|
1798
|
+
on: sinon.stub(),
|
|
1799
|
+
off: sinon.stub(),
|
|
1800
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1801
|
+
setMuted: sinon.stub(),
|
|
1802
|
+
setPublished: sinon.stub(),
|
|
1803
|
+
muted: false,
|
|
1804
|
+
underlyingTrack: webrtcAudioTrack2
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack2});
|
|
1808
|
+
await stableState();
|
|
1809
|
+
|
|
1810
|
+
// only the roap media connection should be updated
|
|
1811
|
+
if (isMultistream) {
|
|
1812
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack2);
|
|
1813
|
+
} else {
|
|
1814
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1815
|
+
localTracks: { audio: webrtcAudioTrack2, video: null, screenShareVideo: null },
|
|
1816
|
+
direction: {
|
|
1817
|
+
audio: expected.direction,
|
|
1818
|
+
video: 'sendrecv',
|
|
1819
|
+
screenShareVideo: 'recvonly',
|
|
1820
|
+
},
|
|
1821
|
+
remoteQualityLevel: 'HIGH'
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1913
1824
|
|
|
1914
|
-
|
|
1915
|
-
|
|
1825
|
+
// and no other roap messages or local mute requests were sent
|
|
1826
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1827
|
+
});
|
|
1916
1828
|
|
|
1917
|
-
|
|
1918
|
-
|
|
1829
|
+
it(`unpublishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1830
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1831
|
+
await simulateRoapOffer();
|
|
1832
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1833
|
+
await stableState();
|
|
1919
1834
|
|
|
1920
|
-
|
|
1921
|
-
await meeting.shareScreen();
|
|
1835
|
+
resetHistory();
|
|
1922
1836
|
|
|
1923
|
-
|
|
1924
|
-
|
|
1837
|
+
await meeting.unpublishTracks([fakeMicrophoneTrack]);
|
|
1838
|
+
await stableState();
|
|
1925
1839
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1840
|
+
// the roap media connection should be updated
|
|
1841
|
+
if (isMultistream) {
|
|
1842
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.unpublishTrack, fakeMicrophoneTrack);
|
|
1843
|
+
} else {
|
|
1844
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1845
|
+
localTracks: { audio: null, video: null, screenShareVideo: null },
|
|
1846
|
+
direction: {
|
|
1847
|
+
audio: expected.direction,
|
|
1848
|
+
video: 'sendrecv',
|
|
1849
|
+
screenShareVideo: 'recvonly',
|
|
1850
|
+
},
|
|
1851
|
+
remoteQualityLevel: 'HIGH'
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1928
1854
|
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1855
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1856
|
+
// and local mute sent to Locus
|
|
1857
|
+
checkLocalMuteSentToLocus({audioMuted: !expected.localMuteSentValue /* negation, because we're un-publishing */, videoMuted: true});
|
|
1858
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1859
|
+
} else {
|
|
1860
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1933
1863
|
});
|
|
1934
|
-
});
|
|
1935
1864
|
});
|
|
1936
1865
|
|
|
1937
|
-
describe('
|
|
1938
|
-
let sandbox;
|
|
1866
|
+
describe('updateMedia()', () => {
|
|
1939
1867
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1868
|
+
const addMedia = async (enableMedia, track) => {
|
|
1869
|
+
await meeting.addMedia({audioEnabled: enableMedia, localTracks: {microphone: track}});
|
|
1870
|
+
await simulateRoapOffer();
|
|
1943
1871
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1872
|
+
resetHistory();
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
const checkAudioEnabled = (expectedTrack, expectedDirection) => {
|
|
1876
|
+
if (isMultistream) {
|
|
1877
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.enableMultistreamAudio, expectedDirection !== 'inactive');
|
|
1878
|
+
} else {
|
|
1879
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1880
|
+
localTracks: { audio: expectedTrack, video: null, screenShareVideo: null },
|
|
1881
|
+
direction: {
|
|
1882
|
+
audio: expectedDirection,
|
|
1883
|
+
video: 'sendrecv',
|
|
1884
|
+
screenShareVideo: 'recvonly',
|
|
1885
|
+
},
|
|
1886
|
+
remoteQualityLevel: 'HIGH'
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
it('updateMedia() disables media when nothing is published', async () => {
|
|
1892
|
+
await addMedia(true);
|
|
1893
|
+
|
|
1894
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1895
|
+
|
|
1896
|
+
// the roap media connection should be updated
|
|
1897
|
+
checkAudioEnabled(null, 'inactive');
|
|
1898
|
+
|
|
1899
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1900
|
+
await simulateRoapOffer();
|
|
1901
|
+
|
|
1902
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1903
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1904
|
+
|
|
1905
|
+
// and no other local mute requests were sent to Locus
|
|
1906
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1947
1907
|
});
|
|
1948
1908
|
|
|
1949
|
-
it('
|
|
1950
|
-
|
|
1951
|
-
const receiveShare = false;
|
|
1952
|
-
const stream = 'stream';
|
|
1909
|
+
it('updateMedia() enables media when nothing is published', async () => {
|
|
1910
|
+
await addMedia(false);
|
|
1953
1911
|
|
|
1954
|
-
|
|
1955
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1956
|
-
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1957
|
-
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1912
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1958
1913
|
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
});
|
|
1914
|
+
// the roap media connection should be updated
|
|
1915
|
+
checkAudioEnabled(null, 'sendrecv');
|
|
1916
|
+
|
|
1917
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1918
|
+
await simulateRoapOffer();
|
|
1965
1919
|
|
|
1966
|
-
|
|
1920
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1921
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1922
|
+
|
|
1923
|
+
// and no other local mute requests were sent to Locus
|
|
1924
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1967
1925
|
});
|
|
1968
1926
|
|
|
1969
|
-
it('
|
|
1970
|
-
|
|
1971
|
-
const fakeTrack = {
|
|
1972
|
-
getSettings: sinon.stub().returns({}),
|
|
1973
|
-
};
|
|
1927
|
+
it('updateMedia() disables media when track is published', async () => {
|
|
1928
|
+
await addMedia(true, fakeMicrophoneTrack);
|
|
1974
1929
|
|
|
1975
|
-
|
|
1930
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1931
|
+
await stableState();
|
|
1976
1932
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
listeners[event] = listener;
|
|
1980
|
-
}),
|
|
1981
|
-
};
|
|
1982
|
-
sinon.stub(InternalMediaCoreModule, 'LocalDisplayTrack').returns(fakeLocalDisplayTrack);
|
|
1933
|
+
// the roap media connection should be updated
|
|
1934
|
+
checkAudioEnabled(webrtcAudioTrack, 'inactive');
|
|
1983
1935
|
|
|
1984
|
-
|
|
1985
|
-
sandbox.stub(mediaProperties, 'setMediaSettings');
|
|
1986
|
-
sandbox.stub(meeting, 'stopShare').resolves(true);
|
|
1987
|
-
meeting.setLocalShareTrack(fakeTrack);
|
|
1936
|
+
checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
|
|
1988
1937
|
|
|
1989
|
-
|
|
1990
|
-
assert.calledWith(fakeLocalDisplayTrack.on, LocalTrackEvents.Ended, sinon.match.any);
|
|
1991
|
-
assert.isNotNull(listeners[LocalTrackEvents.Ended]);
|
|
1938
|
+
locusMediaRequestStub.resetHistory();
|
|
1992
1939
|
|
|
1993
|
-
|
|
1940
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1941
|
+
await simulateRoapOffer();
|
|
1994
1942
|
|
|
1995
|
-
|
|
1943
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1944
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1945
|
+
|
|
1946
|
+
// and no other local mute requests were sent to Locus
|
|
1947
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1996
1948
|
});
|
|
1997
1949
|
|
|
1998
|
-
it('
|
|
1999
|
-
|
|
2000
|
-
abc: 123,
|
|
2001
|
-
receiveShare: false,
|
|
2002
|
-
sendShare: false,
|
|
2003
|
-
};
|
|
1950
|
+
it('updateMedia() enables media when track is published', async () => {
|
|
1951
|
+
await addMedia(false, fakeMicrophoneTrack);
|
|
2004
1952
|
|
|
2005
|
-
|
|
2006
|
-
|
|
1953
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1954
|
+
await stableState();
|
|
2007
1955
|
|
|
2008
|
-
|
|
1956
|
+
// the roap media connection should be updated
|
|
1957
|
+
checkAudioEnabled(webrtcAudioTrack, 'sendrecv');
|
|
2009
1958
|
|
|
2010
|
-
|
|
1959
|
+
checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
|
|
1960
|
+
|
|
1961
|
+
locusMediaRequestStub.resetHistory();
|
|
1962
|
+
|
|
1963
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1964
|
+
await simulateRoapOffer();
|
|
1965
|
+
|
|
1966
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1967
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1968
|
+
|
|
1969
|
+
// and no other local mute requests were sent to Locus
|
|
1970
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
2011
1971
|
});
|
|
2012
1972
|
});
|
|
2013
1973
|
|
|
2014
|
-
|
|
2015
|
-
|
|
1974
|
+
[
|
|
1975
|
+
{mute: true, title: 'muting a track before confluence is created'},
|
|
1976
|
+
{mute: false, title: 'unmuting a track before confluence is created'}
|
|
1977
|
+
].forEach(({mute, title}) =>
|
|
1978
|
+
it(title, async () => {
|
|
1979
|
+
// initialize the microphone mute state to opposite of what we do in the test
|
|
1980
|
+
fakeMicrophoneTrack.muted = !mute;
|
|
2016
1981
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
});
|
|
1982
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1983
|
+
await stableState();
|
|
2020
1984
|
|
|
2021
|
-
|
|
2022
|
-
sandbox.restore();
|
|
2023
|
-
sandbox = null;
|
|
2024
|
-
});
|
|
1985
|
+
resetHistory();
|
|
2025
1986
|
|
|
2026
|
-
|
|
2027
|
-
const
|
|
1987
|
+
assert.equal(fakeMicrophoneTrack.on.getCall(0).args[0], LocalTrackEvents.Muted);
|
|
1988
|
+
const mutedListener = fakeMicrophoneTrack.on.getCall(0).args[1];
|
|
1989
|
+
// simulate track being muted
|
|
1990
|
+
mutedListener({trackState: {muted: mute}});
|
|
2028
1991
|
|
|
2029
|
-
|
|
1992
|
+
await stableState();
|
|
2030
1993
|
|
|
2031
|
-
|
|
1994
|
+
// nothing should happen
|
|
1995
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1996
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2032
1997
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
sinon.match.instanceOf(Meeting),
|
|
2036
|
-
{
|
|
2037
|
-
file: 'meeting/index',
|
|
2038
|
-
function: 'handleShareTrackEnded',
|
|
2039
|
-
},
|
|
2040
|
-
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
2041
|
-
{
|
|
2042
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
2043
|
-
}
|
|
2044
|
-
);
|
|
2045
|
-
});
|
|
2046
|
-
});
|
|
2047
|
-
});
|
|
1998
|
+
// now simulate roap offer
|
|
1999
|
+
await simulateRoapOffer();
|
|
2048
2000
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
const config = DefaultSDKConfig.meetings;
|
|
2052
|
-
const {resolution} = config;
|
|
2053
|
-
const shareOptions = {
|
|
2054
|
-
sendShare: true,
|
|
2055
|
-
sendAudio: false,
|
|
2056
|
-
};
|
|
2057
|
-
const fireFoxOptions = {
|
|
2058
|
-
audio: false,
|
|
2059
|
-
video: {
|
|
2060
|
-
audio: shareOptions.sendAudio,
|
|
2061
|
-
video: shareOptions.sendShare,
|
|
2062
|
-
},
|
|
2063
|
-
};
|
|
2001
|
+
// it should be sent with the right mute status
|
|
2002
|
+
checkSdpOfferSent({audioMuted: mute, videoMuted: true});
|
|
2064
2003
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
};
|
|
2004
|
+
// nothing else should happen
|
|
2005
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
2006
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
}));
|
|
2072
2010
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
};
|
|
2011
|
+
describe('#acknowledge', () => {
|
|
2012
|
+
it('should have #acknowledge', () => {
|
|
2013
|
+
assert.exists(meeting.acknowledge);
|
|
2014
|
+
});
|
|
2015
|
+
beforeEach(() => {
|
|
2016
|
+
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
2017
|
+
});
|
|
2018
|
+
it('should acknowledge incoming and return a promise', async () => {
|
|
2019
|
+
const ack = meeting.acknowledge('INCOMING', false);
|
|
2080
2020
|
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2021
|
+
assert.exists(ack.then);
|
|
2022
|
+
await ack;
|
|
2023
|
+
assert.calledOnce(meeting.meetingRequest.acknowledgeMeeting);
|
|
2024
|
+
});
|
|
2025
|
+
it('should acknowledge a non incoming and return a promise', async () => {
|
|
2026
|
+
const ack = meeting.acknowledge(test1, false);
|
|
2084
2027
|
|
|
2085
|
-
|
|
2086
|
-
|
|
2028
|
+
assert.exists(ack.then);
|
|
2029
|
+
await ack;
|
|
2030
|
+
assert.notCalled(meeting.meetingRequest.acknowledgeMeeting);
|
|
2031
|
+
});
|
|
2032
|
+
});
|
|
2033
|
+
describe('#decline', () => {
|
|
2034
|
+
it('should have #decline', () => {
|
|
2035
|
+
assert.exists(meeting.decline);
|
|
2036
|
+
});
|
|
2037
|
+
beforeEach(() => {
|
|
2038
|
+
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
2039
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
2040
|
+
});
|
|
2041
|
+
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
2042
|
+
await meeting.decline();
|
|
2043
|
+
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
describe('#leave', () => {
|
|
2047
|
+
let sandbox;
|
|
2087
2048
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2049
|
+
it('should have #leave', () => {
|
|
2050
|
+
assert.exists(meeting.leave);
|
|
2051
|
+
});
|
|
2090
2052
|
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
getDisplayMedia: null,
|
|
2095
|
-
},
|
|
2096
|
-
};
|
|
2097
|
-
}
|
|
2098
|
-
_getDisplayMedia = global.navigator.mediaDevices.getDisplayMedia;
|
|
2099
|
-
Object.defineProperty(global.navigator.mediaDevices, 'getDisplayMedia', {
|
|
2100
|
-
value: sinon.stub().returns(Promise.resolve(MediaStream)),
|
|
2101
|
-
writable: true,
|
|
2053
|
+
it('should reject if meeting is already inactive', async () => {
|
|
2054
|
+
await meeting.leave().catch((err) => {
|
|
2055
|
+
assert.instanceOf(err, MeetingNotActiveError);
|
|
2102
2056
|
});
|
|
2103
2057
|
});
|
|
2104
2058
|
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
writable: true,
|
|
2059
|
+
it('should reject if meeting is already left', async () => {
|
|
2060
|
+
meeting.meetingState = 'ACTIVE';
|
|
2061
|
+
await meeting.leave().catch((err) => {
|
|
2062
|
+
assert.instanceOf(err, UserNotJoinedError);
|
|
2110
2063
|
});
|
|
2111
2064
|
});
|
|
2112
2065
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
sharePreferences: {shareConstraints},
|
|
2131
|
-
},
|
|
2132
|
-
config
|
|
2133
|
-
);
|
|
2066
|
+
beforeEach(() => {
|
|
2067
|
+
sandbox = sinon.createSandbox();
|
|
2068
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
2069
|
+
meeting.meetingFiniteStateMachine.join();
|
|
2070
|
+
meeting.meetingRequest.leaveMeeting = sinon
|
|
2071
|
+
.stub()
|
|
2072
|
+
.returns(Promise.resolve({body: 'test'}));
|
|
2073
|
+
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
2074
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
2075
|
+
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
2076
|
+
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
2077
|
+
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
2078
|
+
meeting.unsetRemoteTracks = sinon.stub();
|
|
2079
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2080
|
+
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
2081
|
+
meeting.logger.error = sinon.stub().returns(true);
|
|
2082
|
+
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
2134
2083
|
|
|
2135
|
-
//
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
browserConditionalValue({
|
|
2139
|
-
default: {
|
|
2140
|
-
video: {...shareConstraints},
|
|
2141
|
-
},
|
|
2142
|
-
// Firefox is being handled differently
|
|
2143
|
-
firefox: fireFoxOptions,
|
|
2144
|
-
})
|
|
2145
|
-
);
|
|
2084
|
+
// A meeting needs to be joined to leave
|
|
2085
|
+
meeting.meetingState = 'ACTIVE';
|
|
2086
|
+
meeting.state = 'JOINED';
|
|
2146
2087
|
});
|
|
2088
|
+
afterEach(() => {
|
|
2089
|
+
sandbox.restore();
|
|
2090
|
+
sandbox = null;
|
|
2091
|
+
});
|
|
2092
|
+
it('should leave the meeting and return promise', async () => {
|
|
2093
|
+
const leave = meeting.leave();
|
|
2147
2094
|
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
},
|
|
2157
|
-
},
|
|
2158
|
-
config
|
|
2159
|
-
);
|
|
2160
|
-
|
|
2161
|
-
// eslint-disable-next-line no-undef
|
|
2162
|
-
assert.calledWith(
|
|
2163
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
2164
|
-
browserConditionalValue({
|
|
2165
|
-
default: {
|
|
2166
|
-
video: {
|
|
2167
|
-
...MediaConstraint,
|
|
2168
|
-
frameRate: config.videoShareFrameRate,
|
|
2169
|
-
width: resolution.idealWidth,
|
|
2170
|
-
height: resolution.idealHeight,
|
|
2171
|
-
maxWidth: resolution.maxWidth,
|
|
2172
|
-
maxHeight: resolution.maxHeight,
|
|
2173
|
-
idealWidth: resolution.idealWidth,
|
|
2174
|
-
idealHeight: resolution.idealHeight,
|
|
2175
|
-
},
|
|
2176
|
-
},
|
|
2177
|
-
firefox: fireFoxOptions,
|
|
2178
|
-
})
|
|
2179
|
-
);
|
|
2095
|
+
assert.exists(leave.then);
|
|
2096
|
+
await leave;
|
|
2097
|
+
assert.calledOnce(meeting.meetingRequest.leaveMeeting);
|
|
2098
|
+
assert.calledOnce(meeting.cleanupLocalTracks);
|
|
2099
|
+
assert.calledOnce(meeting.closeRemoteTracks);
|
|
2100
|
+
assert.calledOnce(meeting.closePeerConnections);
|
|
2101
|
+
assert.calledOnce(meeting.unsetRemoteTracks);
|
|
2102
|
+
assert.calledOnce(meeting.unsetPeerConnections);
|
|
2180
2103
|
});
|
|
2104
|
+
describe('after audio/video is defined', () => {
|
|
2105
|
+
let handleClientRequest;
|
|
2181
2106
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
getDisplayMedia(shareOptions);
|
|
2185
|
-
const {screenResolution} = config;
|
|
2107
|
+
beforeEach(() => {
|
|
2108
|
+
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
2186
2109
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
browserConditionalValue({
|
|
2191
|
-
default: {
|
|
2192
|
-
video: {
|
|
2193
|
-
...MediaConstraint,
|
|
2194
|
-
width: screenResolution.idealWidth,
|
|
2195
|
-
height: screenResolution.idealHeight,
|
|
2196
|
-
},
|
|
2197
|
-
},
|
|
2198
|
-
firefox: fireFoxOptions,
|
|
2199
|
-
})
|
|
2200
|
-
);
|
|
2201
|
-
});
|
|
2110
|
+
meeting.audio = {handleClientRequest};
|
|
2111
|
+
meeting.video = {handleClientRequest};
|
|
2112
|
+
});
|
|
2202
2113
|
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
it('will use SDK config screenResolution if set, with shareConstraints and highFrameRate being undefined', () => {
|
|
2206
|
-
const SHARE_WIDTH = 800;
|
|
2207
|
-
const SHARE_HEIGHT = 600;
|
|
2208
|
-
const customConfig = {
|
|
2209
|
-
screenResolution: {
|
|
2210
|
-
maxWidth: SHARE_WIDTH,
|
|
2211
|
-
maxHeight: SHARE_HEIGHT,
|
|
2212
|
-
idealWidth: SHARE_WIDTH,
|
|
2213
|
-
idealHeight: SHARE_HEIGHT,
|
|
2214
|
-
},
|
|
2215
|
-
};
|
|
2114
|
+
it('should delete audio and video state machines when leaving the meeting', async () => {
|
|
2115
|
+
const leave = meeting.leave();
|
|
2216
2116
|
|
|
2217
|
-
|
|
2117
|
+
assert.exists(leave.then);
|
|
2118
|
+
await leave;
|
|
2218
2119
|
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
browserConditionalValue({
|
|
2223
|
-
default: {
|
|
2224
|
-
video: {
|
|
2225
|
-
...MediaConstraint,
|
|
2226
|
-
width: SHARE_WIDTH,
|
|
2227
|
-
height: SHARE_HEIGHT,
|
|
2228
|
-
maxWidth: SHARE_WIDTH,
|
|
2229
|
-
maxHeight: SHARE_HEIGHT,
|
|
2230
|
-
idealWidth: SHARE_WIDTH,
|
|
2231
|
-
idealHeight: SHARE_HEIGHT,
|
|
2232
|
-
},
|
|
2233
|
-
},
|
|
2234
|
-
firefox: fireFoxOptions,
|
|
2235
|
-
})
|
|
2236
|
-
);
|
|
2120
|
+
assert.isNull(meeting.audio);
|
|
2121
|
+
assert.isNull(meeting.video);
|
|
2122
|
+
});
|
|
2237
2123
|
});
|
|
2124
|
+
it('should leave the meeting without leaving resource', async () => {
|
|
2125
|
+
const leave = meeting.leave({resourceId: null});
|
|
2238
2126
|
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
};
|
|
2127
|
+
assert.exists(leave.then);
|
|
2128
|
+
await leave;
|
|
2129
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2130
|
+
locusUrl: meeting.locusUrl,
|
|
2131
|
+
correlationId: meeting.correlationId,
|
|
2132
|
+
selfId: meeting.selfId,
|
|
2133
|
+
resourceId: null,
|
|
2134
|
+
deviceUrl: meeting.deviceUrl,
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
it('should leave the meeting on the resource', async () => {
|
|
2138
|
+
const leave = meeting.leave();
|
|
2252
2139
|
|
|
2253
|
-
|
|
2140
|
+
assert.exists(leave.then);
|
|
2141
|
+
await leave;
|
|
2142
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2143
|
+
locusUrl: meeting.locusUrl,
|
|
2144
|
+
correlationId: meeting.correlationId,
|
|
2145
|
+
selfId: meeting.selfId,
|
|
2146
|
+
resourceId: meeting.resourceId,
|
|
2147
|
+
deviceUrl: meeting.deviceUrl
|
|
2148
|
+
});
|
|
2149
|
+
});
|
|
2150
|
+
it('should leave the meeting on the resource with reason', async () => {
|
|
2151
|
+
const leave = meeting.leave({resourceId: meeting.resourceId, reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST});
|
|
2254
2152
|
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
maxWidth: SHARE_WIDTH,
|
|
2266
|
-
maxHeight: SHARE_HEIGHT,
|
|
2267
|
-
idealWidth: SHARE_WIDTH,
|
|
2268
|
-
idealHeight: SHARE_HEIGHT,
|
|
2269
|
-
},
|
|
2270
|
-
},
|
|
2271
|
-
firefox: fireFoxOptions,
|
|
2272
|
-
})
|
|
2273
|
-
);
|
|
2153
|
+
assert.exists(leave.then);
|
|
2154
|
+
await leave;
|
|
2155
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2156
|
+
locusUrl: meeting.locusUrl,
|
|
2157
|
+
correlationId: meeting.correlationId,
|
|
2158
|
+
selfId: meeting.selfId,
|
|
2159
|
+
resourceId: meeting.resourceId,
|
|
2160
|
+
deviceUrl: meeting.deviceUrl,
|
|
2161
|
+
reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST
|
|
2162
|
+
});
|
|
2274
2163
|
});
|
|
2275
2164
|
});
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
assert.exists(meeting.stopShare);
|
|
2165
|
+
describe('#requestScreenShareFloor', () => {
|
|
2166
|
+
it('should have #requestScreenShareFloor', () => {
|
|
2167
|
+
assert.exists(meeting.requestScreenShareFloor);
|
|
2280
2168
|
});
|
|
2281
2169
|
beforeEach(() => {
|
|
2282
|
-
meeting.
|
|
2283
|
-
meeting.
|
|
2170
|
+
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
2171
|
+
meeting.locusInfo.self = {url: url1};
|
|
2172
|
+
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
2173
|
+
meeting.mediaProperties.shareTrack = {}
|
|
2174
|
+
meeting.mediaProperties.mediaDirection.sendShare = true;
|
|
2175
|
+
meeting.state = 'JOINED';
|
|
2284
2176
|
});
|
|
2285
|
-
it('should
|
|
2286
|
-
const share = meeting.
|
|
2177
|
+
it('should send the share', async () => {
|
|
2178
|
+
const share = meeting.requestScreenShareFloor();
|
|
2287
2179
|
|
|
2288
2180
|
assert.exists(share.then);
|
|
2289
2181
|
await share;
|
|
2290
|
-
assert.calledOnce(meeting.
|
|
2291
|
-
});
|
|
2292
|
-
});
|
|
2293
|
-
|
|
2294
|
-
describe('#updateAudio', () => {
|
|
2295
|
-
const FAKE_AUDIO_TRACK = {
|
|
2296
|
-
id: 'fake audio track',
|
|
2297
|
-
getSettings: sinon.stub().returns({}),
|
|
2298
|
-
};
|
|
2299
|
-
|
|
2300
|
-
describe('when canUpdateMedia is true', () => {
|
|
2301
|
-
beforeEach(() => {
|
|
2302
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2303
|
-
});
|
|
2304
|
-
describe('when options are valid', () => {
|
|
2305
|
-
beforeEach(() => {
|
|
2306
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2307
|
-
meeting.mediaProperties.mediaDirection = {
|
|
2308
|
-
sendAudio: false,
|
|
2309
|
-
sendVideo: true,
|
|
2310
|
-
sendShare: false,
|
|
2311
|
-
receiveAudio: false,
|
|
2312
|
-
receiveVideo: true,
|
|
2313
|
-
receiveShare: true,
|
|
2314
|
-
};
|
|
2315
|
-
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2316
|
-
update: sinon.stub(),
|
|
2317
|
-
};
|
|
2318
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
|
|
2319
|
-
});
|
|
2320
|
-
it('calls this.mediaProperties.webrtcMediaConnection.update', () =>
|
|
2321
|
-
meeting
|
|
2322
|
-
.updateAudio({
|
|
2323
|
-
sendAudio: true,
|
|
2324
|
-
receiveAudio: true,
|
|
2325
|
-
stream: {id: 'fake stream'},
|
|
2326
|
-
})
|
|
2327
|
-
.then(() => {
|
|
2328
|
-
assert.calledOnce(
|
|
2329
|
-
meeting.mediaProperties.webrtcMediaConnection.update
|
|
2330
|
-
);
|
|
2331
|
-
assert.calledWith(
|
|
2332
|
-
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2333
|
-
{
|
|
2334
|
-
localTracks: {audio: FAKE_AUDIO_TRACK},
|
|
2335
|
-
direction: {
|
|
2336
|
-
audio: 'sendrecv',
|
|
2337
|
-
video: 'sendrecv',
|
|
2338
|
-
screenShareVideo: 'recvonly',
|
|
2339
|
-
},
|
|
2340
|
-
remoteQualityLevel: 'HIGH',
|
|
2341
|
-
}
|
|
2342
|
-
);
|
|
2343
|
-
}));
|
|
2344
|
-
});
|
|
2345
|
-
afterEach(() => {
|
|
2346
|
-
sinon.restore();
|
|
2347
|
-
});
|
|
2182
|
+
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
2348
2183
|
});
|
|
2349
2184
|
});
|
|
2350
2185
|
|
|
@@ -2392,38 +2227,25 @@ describe('plugin-meetings', () => {
|
|
|
2392
2227
|
|
|
2393
2228
|
describe('#updateMedia', () => {
|
|
2394
2229
|
let sandbox;
|
|
2395
|
-
const mockLocalStream = {id: 'mock local stream'};
|
|
2396
|
-
const mockLocalShare = {id: 'mock local share stream'};
|
|
2397
|
-
const FAKE_TRACKS = {
|
|
2398
|
-
audio: {
|
|
2399
|
-
id: 'fake audio track',
|
|
2400
|
-
getSettings: sinon.stub().returns({}),
|
|
2401
|
-
},
|
|
2402
|
-
video: {
|
|
2403
|
-
id: 'fake video track',
|
|
2404
|
-
getSettings: sinon.stub().returns({}),
|
|
2405
|
-
},
|
|
2406
|
-
screenshareVideo: {
|
|
2407
|
-
id: 'fake share track',
|
|
2408
|
-
getSettings: sinon.stub().returns({}),
|
|
2409
|
-
on: sinon.stub(),
|
|
2410
|
-
},
|
|
2411
|
-
};
|
|
2412
2230
|
|
|
2231
|
+
const createFakeLocalTrack = () => ({
|
|
2232
|
+
underlyingTrack: {id: 'fake underlying track'}
|
|
2233
|
+
});
|
|
2413
2234
|
beforeEach(() => {
|
|
2414
2235
|
sandbox = sinon.createSandbox();
|
|
2415
|
-
meeting.
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2236
|
+
meeting.audio = { enable: sinon.stub()};
|
|
2237
|
+
meeting.video = { enable: sinon.stub()};
|
|
2238
|
+
meeting.mediaProperties.audioTrack = createFakeLocalTrack();
|
|
2239
|
+
meeting.mediaProperties.videoTrack = createFakeLocalTrack();
|
|
2240
|
+
meeting.mediaProperties.shareTrack = createFakeLocalTrack();
|
|
2241
|
+
meeting.mediaProperties.mediaDirection = {
|
|
2242
|
+
sendAudio: true,
|
|
2243
|
+
sendVideo: true,
|
|
2244
|
+
sendShare: true,
|
|
2245
|
+
receiveAudio: true,
|
|
2246
|
+
receiveVideo: true,
|
|
2247
|
+
receiveShare: true,
|
|
2248
|
+
}
|
|
2427
2249
|
});
|
|
2428
2250
|
|
|
2429
2251
|
afterEach(() => {
|
|
@@ -2433,52 +2255,28 @@ describe('plugin-meetings', () => {
|
|
|
2433
2255
|
|
|
2434
2256
|
forEach(
|
|
2435
2257
|
[
|
|
2436
|
-
{
|
|
2437
|
-
{
|
|
2438
|
-
{receiveAudio: false, sendAudio: true, enableMultistreamAudio: true},
|
|
2439
|
-
{receiveAudio: false, sendAudio: false, enableMultistreamAudio: false},
|
|
2258
|
+
{audioEnabled: true, enableMultistreamAudio: true},
|
|
2259
|
+
{audioEnabled: false, enableMultistreamAudio: false},
|
|
2440
2260
|
],
|
|
2441
|
-
({
|
|
2442
|
-
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and
|
|
2443
|
-
const mediaSettings = {
|
|
2444
|
-
sendAudio,
|
|
2445
|
-
receiveAudio,
|
|
2446
|
-
sendVideo: true,
|
|
2447
|
-
receiveVideo: true,
|
|
2448
|
-
sendShare: true,
|
|
2449
|
-
receiveShare: true,
|
|
2450
|
-
isSharing: true,
|
|
2451
|
-
};
|
|
2452
|
-
|
|
2261
|
+
({audioEnabled, enableMultistreamAudio}) => {
|
|
2262
|
+
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and audioEnabled: ${audioEnabled}`, async () => {
|
|
2453
2263
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2454
|
-
enableMultistreamAudio: sinon.stub().resolves(
|
|
2264
|
+
enableMultistreamAudio: sinon.stub().resolves({}),
|
|
2455
2265
|
};
|
|
2456
2266
|
meeting.isMultistream = true;
|
|
2457
2267
|
|
|
2458
|
-
|
|
2459
|
-
mediaSettings,
|
|
2460
|
-
});
|
|
2268
|
+
await meeting.updateMedia({audioEnabled});
|
|
2461
2269
|
|
|
2462
2270
|
assert.calledOnceWithExactly(
|
|
2463
2271
|
meeting.mediaProperties.webrtcMediaConnection.enableMultistreamAudio,
|
|
2464
2272
|
enableMultistreamAudio
|
|
2465
2273
|
);
|
|
2466
|
-
assert.
|
|
2274
|
+
assert.calledOnceWithExactly(meeting.audio.enable, meeting, enableMultistreamAudio);
|
|
2467
2275
|
});
|
|
2468
2276
|
}
|
|
2469
2277
|
);
|
|
2470
2278
|
|
|
2471
2279
|
it('should use a queue if currently busy', async () => {
|
|
2472
|
-
const mediaSettings = {
|
|
2473
|
-
sendAudio: true,
|
|
2474
|
-
receiveAudio: true,
|
|
2475
|
-
sendVideo: true,
|
|
2476
|
-
receiveVideo: true,
|
|
2477
|
-
sendShare: true,
|
|
2478
|
-
receiveShare: true,
|
|
2479
|
-
isSharing: true,
|
|
2480
|
-
};
|
|
2481
|
-
|
|
2482
2280
|
sandbox.stub(meeting, 'canUpdateMedia').returns(false);
|
|
2483
2281
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2484
2282
|
update: sinon.stub().resolves({}),
|
|
@@ -2487,11 +2285,7 @@ describe('plugin-meetings', () => {
|
|
|
2487
2285
|
let myPromiseResolved = false;
|
|
2488
2286
|
|
|
2489
2287
|
meeting
|
|
2490
|
-
.updateMedia({
|
|
2491
|
-
localStream: mockLocalStream,
|
|
2492
|
-
localShare: mockLocalShare,
|
|
2493
|
-
mediaSettings,
|
|
2494
|
-
})
|
|
2288
|
+
.updateMedia({audioEnabled: false, videoEnabled: false})
|
|
2495
2289
|
.then(() => {
|
|
2496
2290
|
myPromiseResolved = true;
|
|
2497
2291
|
});
|
|
@@ -2512,13 +2306,13 @@ describe('plugin-meetings', () => {
|
|
|
2512
2306
|
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2513
2307
|
{
|
|
2514
2308
|
localTracks: {
|
|
2515
|
-
audio:
|
|
2516
|
-
video:
|
|
2517
|
-
screenShareVideo:
|
|
2309
|
+
audio: meeting.mediaProperties.audioTrack.underlyingTrack,
|
|
2310
|
+
video: meeting.mediaProperties.videoTrack.underlyingTrack,
|
|
2311
|
+
screenShareVideo: meeting.mediaProperties.shareTrack.underlyingTrack,
|
|
2518
2312
|
},
|
|
2519
2313
|
direction: {
|
|
2520
|
-
audio: '
|
|
2521
|
-
video: '
|
|
2314
|
+
audio: 'inactive',
|
|
2315
|
+
video: 'inactive',
|
|
2522
2316
|
screenShareVideo: 'sendrecv',
|
|
2523
2317
|
},
|
|
2524
2318
|
remoteQualityLevel: 'HIGH',
|
|
@@ -2526,195 +2320,6 @@ describe('plugin-meetings', () => {
|
|
|
2526
2320
|
);
|
|
2527
2321
|
assert.isTrue(myPromiseResolved);
|
|
2528
2322
|
});
|
|
2529
|
-
|
|
2530
|
-
it('should request floor only after roap transaction is completed', async () => {
|
|
2531
|
-
const eventListeners = {};
|
|
2532
|
-
|
|
2533
|
-
meeting.webex.meetings.reachability = {
|
|
2534
|
-
isAnyClusterReachable: sandbox.stub().resolves(true),
|
|
2535
|
-
};
|
|
2536
|
-
|
|
2537
|
-
const fakeMediaConnection = {
|
|
2538
|
-
close: sinon.stub(),
|
|
2539
|
-
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2540
|
-
initiateOffer: sinon.stub().resolves({}),
|
|
2541
|
-
|
|
2542
|
-
// mock the on() method and store all the listeners
|
|
2543
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
2544
|
-
eventListeners[event] = listener;
|
|
2545
|
-
}),
|
|
2546
|
-
|
|
2547
|
-
update: sinon.stub().callsFake(() => {
|
|
2548
|
-
// trigger ROAP_STARTED before update() resolves
|
|
2549
|
-
if (eventListeners[Event.ROAP_STARTED]) {
|
|
2550
|
-
eventListeners[Event.ROAP_STARTED]();
|
|
2551
|
-
} else {
|
|
2552
|
-
throw new Error('ROAP_STARTED listener not registered');
|
|
2553
|
-
}
|
|
2554
|
-
return Promise.resolve();
|
|
2555
|
-
}),
|
|
2556
|
-
};
|
|
2557
|
-
|
|
2558
|
-
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
2559
|
-
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
2560
|
-
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
2561
|
-
|
|
2562
|
-
const requestScreenShareFloorStub = sandbox
|
|
2563
|
-
.stub(meeting, 'requestScreenShareFloor')
|
|
2564
|
-
.resolves({});
|
|
2565
|
-
|
|
2566
|
-
let myPromiseResolved = false;
|
|
2567
|
-
|
|
2568
|
-
meeting.meetingState = 'ACTIVE';
|
|
2569
|
-
await meeting.addMedia({
|
|
2570
|
-
mediaSettings: {},
|
|
2571
|
-
});
|
|
2572
|
-
|
|
2573
|
-
meeting
|
|
2574
|
-
.updateMedia({
|
|
2575
|
-
localShare: mockLocalShare,
|
|
2576
|
-
mediaSettings: {
|
|
2577
|
-
sendShare: true,
|
|
2578
|
-
},
|
|
2579
|
-
})
|
|
2580
|
-
.then(() => {
|
|
2581
|
-
myPromiseResolved = true;
|
|
2582
|
-
});
|
|
2583
|
-
|
|
2584
|
-
await testUtils.flushPromises();
|
|
2585
|
-
|
|
2586
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2587
|
-
assert.isFalse(myPromiseResolved);
|
|
2588
|
-
|
|
2589
|
-
// verify that requestScreenShareFloorStub was not called yet
|
|
2590
|
-
assert.notCalled(requestScreenShareFloorStub);
|
|
2591
|
-
|
|
2592
|
-
eventListeners[Event.ROAP_DONE]();
|
|
2593
|
-
await testUtils.flushPromises();
|
|
2594
|
-
|
|
2595
|
-
// now it should have been called
|
|
2596
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2597
|
-
});
|
|
2598
|
-
});
|
|
2599
|
-
|
|
2600
|
-
describe('#updateShare', () => {
|
|
2601
|
-
const mockLocalShare = {id: 'mock local share stream'};
|
|
2602
|
-
let eventListeners;
|
|
2603
|
-
let fakeMediaConnection;
|
|
2604
|
-
let requestScreenShareFloorStub;
|
|
2605
|
-
|
|
2606
|
-
const FAKE_TRACKS = {
|
|
2607
|
-
screenshareVideo: {
|
|
2608
|
-
id: 'fake share track',
|
|
2609
|
-
getSettings: sinon.stub().returns({}),
|
|
2610
|
-
on: sinon.stub(),
|
|
2611
|
-
},
|
|
2612
|
-
};
|
|
2613
|
-
|
|
2614
|
-
beforeEach(async () => {
|
|
2615
|
-
eventListeners = {};
|
|
2616
|
-
|
|
2617
|
-
sinon.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
|
|
2618
|
-
if (stream === mockLocalShare) {
|
|
2619
|
-
return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
return {audioTrack: null, videoTrack: null};
|
|
2623
|
-
});
|
|
2624
|
-
|
|
2625
|
-
meeting.webex.meetings.reachability = {
|
|
2626
|
-
isAnyClusterReachable: sinon.stub().resolves(true),
|
|
2627
|
-
};
|
|
2628
|
-
|
|
2629
|
-
fakeMediaConnection = {
|
|
2630
|
-
close: sinon.stub(),
|
|
2631
|
-
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2632
|
-
initiateOffer: sinon.stub().resolves({}),
|
|
2633
|
-
|
|
2634
|
-
// mock the on() method and store all the listeners
|
|
2635
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
2636
|
-
eventListeners[event] = listener;
|
|
2637
|
-
}),
|
|
2638
|
-
|
|
2639
|
-
update: sinon.stub().callsFake(() => {
|
|
2640
|
-
// trigger ROAP_STARTED before update() resolves
|
|
2641
|
-
if (eventListeners[Event.ROAP_STARTED]) {
|
|
2642
|
-
eventListeners[Event.ROAP_STARTED]();
|
|
2643
|
-
} else {
|
|
2644
|
-
throw new Error('ROAP_STARTED listener not registered');
|
|
2645
|
-
}
|
|
2646
|
-
return Promise.resolve();
|
|
2647
|
-
}),
|
|
2648
|
-
};
|
|
2649
|
-
|
|
2650
|
-
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
2651
|
-
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
2652
|
-
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
2653
|
-
|
|
2654
|
-
requestScreenShareFloorStub = sinon.stub(meeting, 'requestScreenShareFloor').resolves({});
|
|
2655
|
-
|
|
2656
|
-
meeting.meetingState = 'ACTIVE';
|
|
2657
|
-
await meeting.addMedia({
|
|
2658
|
-
mediaSettings: {},
|
|
2659
|
-
});
|
|
2660
|
-
});
|
|
2661
|
-
|
|
2662
|
-
afterEach(() => {
|
|
2663
|
-
sinon.restore();
|
|
2664
|
-
});
|
|
2665
|
-
|
|
2666
|
-
it('when starting share, it should request floor only after roap transaction is completed', async () => {
|
|
2667
|
-
let myPromiseResolved = false;
|
|
2668
|
-
|
|
2669
|
-
meeting
|
|
2670
|
-
.updateShare({
|
|
2671
|
-
sendShare: true,
|
|
2672
|
-
receiveShare: true,
|
|
2673
|
-
stream: mockLocalShare,
|
|
2674
|
-
})
|
|
2675
|
-
.then(() => {
|
|
2676
|
-
myPromiseResolved = true;
|
|
2677
|
-
});
|
|
2678
|
-
|
|
2679
|
-
await testUtils.flushPromises();
|
|
2680
|
-
|
|
2681
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2682
|
-
assert.isFalse(myPromiseResolved);
|
|
2683
|
-
|
|
2684
|
-
// verify that requestScreenShareFloorStub was not called yet
|
|
2685
|
-
assert.notCalled(requestScreenShareFloorStub);
|
|
2686
|
-
|
|
2687
|
-
eventListeners[Event.ROAP_DONE]();
|
|
2688
|
-
await testUtils.flushPromises();
|
|
2689
|
-
|
|
2690
|
-
// now it should have been called
|
|
2691
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2692
|
-
});
|
|
2693
|
-
|
|
2694
|
-
it('when changing screen share stream and no roap transaction happening, it requests floor immediately', async () => {
|
|
2695
|
-
let myPromiseResolved = false;
|
|
2696
|
-
|
|
2697
|
-
// simulate a case when no roap transaction is triggered by update
|
|
2698
|
-
meeting.mediaProperties.webrtcMediaConnection.update = sinon
|
|
2699
|
-
.stub()
|
|
2700
|
-
.resolves({});
|
|
2701
|
-
|
|
2702
|
-
meeting
|
|
2703
|
-
.updateShare({
|
|
2704
|
-
sendShare: true,
|
|
2705
|
-
receiveShare: true,
|
|
2706
|
-
stream: mockLocalShare,
|
|
2707
|
-
})
|
|
2708
|
-
.then(() => {
|
|
2709
|
-
myPromiseResolved = true;
|
|
2710
|
-
});
|
|
2711
|
-
|
|
2712
|
-
await testUtils.flushPromises();
|
|
2713
|
-
|
|
2714
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2715
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2716
|
-
assert.isTrue(myPromiseResolved);
|
|
2717
|
-
});
|
|
2718
2323
|
});
|
|
2719
2324
|
|
|
2720
2325
|
describe('#changeVideoLayout', () => {
|
|
@@ -2729,8 +2334,6 @@ describe('plugin-meetings', () => {
|
|
|
2729
2334
|
sendShare: false,
|
|
2730
2335
|
receiveVideo: true,
|
|
2731
2336
|
};
|
|
2732
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2733
|
-
meeting.updateVideo = sinon.stub().returns(Promise.resolve());
|
|
2734
2337
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2735
2338
|
meeting.mediaProperties.remoteVideoTrack = sinon
|
|
2736
2339
|
.stub()
|
|
@@ -2967,76 +2570,12 @@ describe('plugin-meetings', () => {
|
|
|
2967
2570
|
});
|
|
2968
2571
|
});
|
|
2969
2572
|
|
|
2970
|
-
describe('#setLocalVideoQuality', () => {
|
|
2971
|
-
let mediaDirection;
|
|
2972
|
-
|
|
2973
|
-
const fakeTrack = {getSettings: () => ({height: 720})};
|
|
2974
|
-
const USER_AGENT_CHROME_MAC =
|
|
2975
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
|
|
2976
|
-
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36';
|
|
2977
|
-
|
|
2978
|
-
beforeEach(() => {
|
|
2979
|
-
mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
2980
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2981
|
-
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2982
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2983
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2984
|
-
meeting.updateVideo = sinon.stub().resolves();
|
|
2985
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({videoTrack: fakeTrack});
|
|
2986
|
-
});
|
|
2987
|
-
|
|
2988
|
-
it('should have #setLocalVideoQuality', () => {
|
|
2989
|
-
assert.exists(meeting.setLocalVideoQuality);
|
|
2990
|
-
});
|
|
2991
|
-
|
|
2992
|
-
it('should call getMediaStreams with the proper level', () =>
|
|
2993
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2994
|
-
delete mediaDirection.receiveVideo;
|
|
2995
|
-
assert.calledWith(
|
|
2996
|
-
meeting.getMediaStreams,
|
|
2997
|
-
mediaDirection,
|
|
2998
|
-
CONSTANTS.VIDEO_RESOLUTIONS[CONSTANTS.QUALITY_LEVELS.LOW]
|
|
2999
|
-
);
|
|
3000
|
-
}));
|
|
3001
|
-
|
|
3002
|
-
it('when browser is chrome then it should stop previous video track', () => {
|
|
3003
|
-
meeting.mediaProperties.videoTrack = fakeTrack;
|
|
3004
|
-
assert.equal(BrowserDetection(USER_AGENT_CHROME_MAC).getBrowserName(), 'Chrome');
|
|
3005
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3006
|
-
assert.calledWith(Media.stopTracks, fakeTrack);
|
|
3007
|
-
});
|
|
3008
|
-
});
|
|
3009
|
-
|
|
3010
|
-
it('should set mediaProperty with the proper level', () =>
|
|
3011
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3012
|
-
assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
3013
|
-
}));
|
|
3014
|
-
|
|
3015
|
-
it('when device does not support 1080p then it should set localQualityLevel with highest possible resolution', () => {
|
|
3016
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS['1080p']).then(() => {
|
|
3017
|
-
assert.equal(
|
|
3018
|
-
meeting.mediaProperties.localQualityLevel,
|
|
3019
|
-
CONSTANTS.QUALITY_LEVELS['720p']
|
|
3020
|
-
);
|
|
3021
|
-
});
|
|
3022
|
-
});
|
|
3023
|
-
|
|
3024
|
-
it('should error if set to a invalid level', () => {
|
|
3025
|
-
assert.isRejected(meeting.setLocalVideoQuality('invalid'));
|
|
3026
|
-
});
|
|
3027
|
-
|
|
3028
|
-
it('should error if sendVideo is set to false', () => {
|
|
3029
|
-
meeting.mediaProperties.mediaDirection = {sendVideo: false};
|
|
3030
|
-
assert.isRejected(meeting.setLocalVideoQuality('LOW'));
|
|
3031
|
-
});
|
|
3032
|
-
});
|
|
3033
|
-
|
|
3034
2573
|
describe('#setRemoteQualityLevel', () => {
|
|
3035
2574
|
let mediaDirection;
|
|
3036
2575
|
|
|
3037
2576
|
beforeEach(() => {
|
|
3038
2577
|
mediaDirection = {receiveAudio: true, receiveVideo: true, receiveShare: false};
|
|
3039
|
-
meeting.
|
|
2578
|
+
meeting.updateTranscodedMediaConnection = sinon.stub().returns(Promise.resolve());
|
|
3040
2579
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
3041
2580
|
});
|
|
3042
2581
|
|
|
@@ -3049,9 +2588,9 @@ describe('plugin-meetings', () => {
|
|
|
3049
2588
|
assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
3050
2589
|
}));
|
|
3051
2590
|
|
|
3052
|
-
it('should call
|
|
2591
|
+
it('should call Meeting.updateTranscodedMediaConnection()', () =>
|
|
3053
2592
|
meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3054
|
-
assert.calledOnce(meeting.
|
|
2593
|
+
assert.calledOnce(meeting.updateTranscodedMediaConnection);
|
|
3055
2594
|
}));
|
|
3056
2595
|
|
|
3057
2596
|
it('should error if set to a invalid level', () => {
|
|
@@ -3725,16 +3264,12 @@ describe('plugin-meetings', () => {
|
|
|
3725
3264
|
.stub()
|
|
3726
3265
|
.returns(Promise.resolve({body: 'test'}));
|
|
3727
3266
|
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
3728
|
-
meeting.
|
|
3729
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
3267
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
3730
3268
|
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
3731
3269
|
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
3732
3270
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
3733
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
3734
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
3735
3271
|
meeting.unsetRemoteTracks = sinon.stub();
|
|
3736
3272
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
3737
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
3738
3273
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
3739
3274
|
meeting.logger.error = sinon.stub().returns(true);
|
|
3740
3275
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
@@ -3753,12 +3288,9 @@ describe('plugin-meetings', () => {
|
|
|
3753
3288
|
assert.exists(endMeetingForAll.then);
|
|
3754
3289
|
await endMeetingForAll;
|
|
3755
3290
|
assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
|
|
3756
|
-
assert.calledOnce(meeting?.
|
|
3757
|
-
assert.calledOnce(meeting?.closeLocalShare);
|
|
3291
|
+
assert.calledOnce(meeting?.cleanupLocalTracks);
|
|
3758
3292
|
assert.calledOnce(meeting?.closeRemoteTracks);
|
|
3759
3293
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
3760
|
-
assert.calledOnce(meeting?.unsetLocalVideoTrack);
|
|
3761
|
-
assert.calledOnce(meeting?.unsetLocalShareTrack);
|
|
3762
3294
|
assert.calledOnce(meeting?.unsetRemoteTracks);
|
|
3763
3295
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
3764
3296
|
});
|
|
@@ -3769,11 +3301,9 @@ describe('plugin-meetings', () => {
|
|
|
3769
3301
|
|
|
3770
3302
|
beforeEach(() => {
|
|
3771
3303
|
sandbox = sinon.createSandbox();
|
|
3772
|
-
sandbox.stub(meeting, '
|
|
3773
|
-
sandbox.stub(meeting, 'closeLocalShare');
|
|
3304
|
+
sandbox.stub(meeting, 'cleanupLocalTracks');
|
|
3774
3305
|
|
|
3775
3306
|
sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
|
|
3776
|
-
sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
|
|
3777
3307
|
|
|
3778
3308
|
sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
|
|
3779
3309
|
sandbox
|
|
@@ -3846,14 +3376,12 @@ describe('plugin-meetings', () => {
|
|
|
3846
3376
|
|
|
3847
3377
|
// beacuse we are calling callback so we need to wait
|
|
3848
3378
|
|
|
3849
|
-
assert.called(meeting.
|
|
3850
|
-
assert.called(meeting.closeLocalShare);
|
|
3379
|
+
assert.called(meeting.cleanupLocalTracks);
|
|
3851
3380
|
|
|
3852
3381
|
// give queued Promise callbacks a chance to run
|
|
3853
3382
|
await Promise.resolve();
|
|
3854
3383
|
|
|
3855
3384
|
assert.called(meeting.mediaProperties.setMediaDirection);
|
|
3856
|
-
assert.called(meeting.mediaProperties.unsetMediaTracks);
|
|
3857
3385
|
|
|
3858
3386
|
assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
|
|
3859
3387
|
mediaDirection: {
|
|
@@ -4002,8 +3530,8 @@ describe('plugin-meetings', () => {
|
|
|
4002
3530
|
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
4003
3531
|
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
4004
3532
|
meeting.mediaProperties.mediaDirection = {
|
|
4005
|
-
sendAudio:
|
|
4006
|
-
sendVideo:
|
|
3533
|
+
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
|
3534
|
+
sendVideo: 'fake value',
|
|
4007
3535
|
sendShare: false,
|
|
4008
3536
|
};
|
|
4009
3537
|
meeting.isMultistream = true;
|
|
@@ -4011,6 +3539,8 @@ describe('plugin-meetings', () => {
|
|
|
4011
3539
|
publishTrack: sinon.stub().resolves({}),
|
|
4012
3540
|
unpublishTrack: sinon.stub().resolves({}),
|
|
4013
3541
|
};
|
|
3542
|
+
meeting.audio = { handleLocalTrackChange: sinon.stub()};
|
|
3543
|
+
meeting.video = { handleLocalTrackChange: sinon.stub()};
|
|
4014
3544
|
|
|
4015
3545
|
const createFakeLocalTrack = (originalTrack) => ({
|
|
4016
3546
|
on: sinon.stub(),
|
|
@@ -4051,33 +3581,25 @@ describe('plugin-meetings', () => {
|
|
|
4051
3581
|
});
|
|
4052
3582
|
|
|
4053
3583
|
const checkAudioPublished = (track) => {
|
|
4054
|
-
assert.
|
|
4055
|
-
createMuteStateStub,
|
|
4056
|
-
'audio',
|
|
4057
|
-
meeting,
|
|
4058
|
-
meeting.mediaProperties.mediaDirection
|
|
4059
|
-
);
|
|
3584
|
+
assert.calledOnceWithExactly(meeting.audio.handleLocalTrackChange, meeting);
|
|
4060
3585
|
assert.calledWith(
|
|
4061
3586
|
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
4062
3587
|
track
|
|
4063
3588
|
);
|
|
4064
3589
|
assert.equal(meeting.mediaProperties.audioTrack, track);
|
|
4065
|
-
|
|
3590
|
+
// check that sendAudio hasn't been touched
|
|
3591
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
4066
3592
|
};
|
|
4067
3593
|
|
|
4068
3594
|
const checkVideoPublished = (track) => {
|
|
4069
|
-
assert.
|
|
4070
|
-
createMuteStateStub,
|
|
4071
|
-
'video',
|
|
4072
|
-
meeting,
|
|
4073
|
-
meeting.mediaProperties.mediaDirection
|
|
4074
|
-
);
|
|
3595
|
+
assert.calledOnceWithExactly(meeting.video.handleLocalTrackChange, meeting);
|
|
4075
3596
|
assert.calledWith(
|
|
4076
3597
|
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
4077
3598
|
track
|
|
4078
3599
|
);
|
|
4079
3600
|
assert.equal(meeting.mediaProperties.videoTrack, track);
|
|
4080
|
-
|
|
3601
|
+
// check that sendVideo hasn't been touched
|
|
3602
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
4081
3603
|
};
|
|
4082
3604
|
|
|
4083
3605
|
const checkScreenShareVideoPublished = (track) => {
|
|
@@ -4098,18 +3620,16 @@ describe('plugin-meetings', () => {
|
|
|
4098
3620
|
checkScreenShareVideoPublished(videoShareTrack);
|
|
4099
3621
|
});
|
|
4100
3622
|
|
|
4101
|
-
it('
|
|
3623
|
+
it('updates MuteState instance and publishes the track for main audio', async () => {
|
|
4102
3624
|
await meeting.publishTracks({microphone: audioTrack});
|
|
4103
3625
|
|
|
4104
|
-
assert.calledOnce(createMuteStateStub);
|
|
4105
3626
|
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4106
3627
|
checkAudioPublished(audioTrack);
|
|
4107
3628
|
});
|
|
4108
3629
|
|
|
4109
|
-
it('
|
|
3630
|
+
it('updates MuteState instance and publishes the track for main video', async () => {
|
|
4110
3631
|
await meeting.publishTracks({camera: videoTrack});
|
|
4111
3632
|
|
|
4112
|
-
assert.calledOnce(createMuteStateStub);
|
|
4113
3633
|
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4114
3634
|
checkVideoPublished(videoTrack);
|
|
4115
3635
|
});
|
|
@@ -4123,13 +3643,20 @@ describe('plugin-meetings', () => {
|
|
|
4123
3643
|
},
|
|
4124
3644
|
});
|
|
4125
3645
|
|
|
4126
|
-
assert.calledTwice(createMuteStateStub);
|
|
4127
3646
|
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4128
3647
|
checkAudioPublished(audioTrack);
|
|
4129
3648
|
checkVideoPublished(videoTrack);
|
|
4130
3649
|
checkScreenShareVideoPublished(videoShareTrack);
|
|
4131
3650
|
});
|
|
4132
3651
|
});
|
|
3652
|
+
it('creates instance and publishes with annotation info', async () => {
|
|
3653
|
+
const annotationInfo = {
|
|
3654
|
+
version: '1',
|
|
3655
|
+
policy: ANNOTATION_POLICY.APPROVAL,
|
|
3656
|
+
};
|
|
3657
|
+
await meeting.publishTracks({annotationInfo});
|
|
3658
|
+
assert.equal(meeting.annotationInfo, annotationInfo);
|
|
3659
|
+
});
|
|
4133
3660
|
|
|
4134
3661
|
describe('unpublishTracks', () => {
|
|
4135
3662
|
beforeEach(async () => {
|
|
@@ -4147,7 +3674,7 @@ describe('plugin-meetings', () => {
|
|
|
4147
3674
|
);
|
|
4148
3675
|
|
|
4149
3676
|
assert.equal(meeting.mediaProperties.audioTrack, null);
|
|
4150
|
-
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio,
|
|
3677
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
4151
3678
|
};
|
|
4152
3679
|
|
|
4153
3680
|
const checkVideoUnpublished = () => {
|
|
@@ -4157,7 +3684,7 @@ describe('plugin-meetings', () => {
|
|
|
4157
3684
|
);
|
|
4158
3685
|
|
|
4159
3686
|
assert.equal(meeting.mediaProperties.videoTrack, null);
|
|
4160
|
-
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo,
|
|
3687
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
4161
3688
|
};
|
|
4162
3689
|
|
|
4163
3690
|
const checkScreenShareVideoUnpublished = () => {
|
|
@@ -4389,82 +3916,6 @@ describe('plugin-meetings', () => {
|
|
|
4389
3916
|
);
|
|
4390
3917
|
});
|
|
4391
3918
|
});
|
|
4392
|
-
describe('#closeLocalShare', () => {
|
|
4393
|
-
it('should stop the stream, and trigger a media:stopped event when the local share stream stops', async () => {
|
|
4394
|
-
await meeting.closeLocalShare();
|
|
4395
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4396
|
-
|
|
4397
|
-
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:stopped');
|
|
4398
|
-
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'localShare'});
|
|
4399
|
-
});
|
|
4400
|
-
});
|
|
4401
|
-
describe('#closeLocalStream', () => {
|
|
4402
|
-
it('should stop the stream, and trigger a media:stopped event when the local stream stops', async () => {
|
|
4403
|
-
await meeting.closeLocalStream();
|
|
4404
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4405
|
-
assert.calledWith(
|
|
4406
|
-
TriggerProxy.trigger,
|
|
4407
|
-
sinon.match.instanceOf(Meeting),
|
|
4408
|
-
{file: 'meeting/index', function: 'closeLocalStream'},
|
|
4409
|
-
'media:stopped',
|
|
4410
|
-
{type: 'local'}
|
|
4411
|
-
);
|
|
4412
|
-
});
|
|
4413
|
-
});
|
|
4414
|
-
describe('#setLocalTracks', () => {
|
|
4415
|
-
it('stores the current video device as the preferred video device', () => {
|
|
4416
|
-
const videoDevice = 'video1';
|
|
4417
|
-
const fakeTrack = {getSettings: () => ({deviceId: videoDevice})};
|
|
4418
|
-
const fakeStream = 'stream1';
|
|
4419
|
-
|
|
4420
|
-
sandbox.stub(MeetingUtil, 'getTrack').returns({audioTrack: null, videoTrack: fakeTrack});
|
|
4421
|
-
sandbox.stub(meeting.mediaProperties, 'setMediaSettings');
|
|
4422
|
-
sandbox.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
4423
|
-
|
|
4424
|
-
meeting.setLocalTracks(fakeStream);
|
|
4425
|
-
|
|
4426
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, videoDevice);
|
|
4427
|
-
});
|
|
4428
|
-
});
|
|
4429
|
-
describe('#setLocalShareTrack', () => {
|
|
4430
|
-
it('should trigger a media:ready event with local share stream', () => {
|
|
4431
|
-
const track = {
|
|
4432
|
-
getSettings: sinon.stub().returns({
|
|
4433
|
-
aspectRatio: '1.7',
|
|
4434
|
-
frameRate: 30,
|
|
4435
|
-
height: 1980,
|
|
4436
|
-
width: 1080,
|
|
4437
|
-
displaySurface: true,
|
|
4438
|
-
cursor: true,
|
|
4439
|
-
}),
|
|
4440
|
-
};
|
|
4441
|
-
|
|
4442
|
-
const listeners = {};
|
|
4443
|
-
const fakeLocalDisplayTrack = {
|
|
4444
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
4445
|
-
listeners[event] = listener;
|
|
4446
|
-
}),
|
|
4447
|
-
};
|
|
4448
|
-
sinon.stub(InternalMediaCoreModule, 'LocalDisplayTrack').returns(fakeLocalDisplayTrack);
|
|
4449
|
-
|
|
4450
|
-
meeting.mediaProperties.setLocalShareTrack = sinon.stub().returns(true);
|
|
4451
|
-
meeting.stopShare = sinon.stub().resolves(true);
|
|
4452
|
-
meeting.mediaProperties.mediaDirection = {};
|
|
4453
|
-
meeting.setLocalShareTrack(track);
|
|
4454
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4455
|
-
assert.calledWith(
|
|
4456
|
-
TriggerProxy.trigger,
|
|
4457
|
-
sinon.match.instanceOf(Meeting),
|
|
4458
|
-
{file: 'meeting/index', function: 'setLocalShareTrack'},
|
|
4459
|
-
'media:ready'
|
|
4460
|
-
);
|
|
4461
|
-
assert.calledOnce(meeting.mediaProperties.setLocalShareTrack);
|
|
4462
|
-
assert.equal(meeting.mediaProperties.localStream, undefined);
|
|
4463
|
-
assert.isNotNull(listeners[LocalTrackEvents.Ended]);
|
|
4464
|
-
listeners[LocalTrackEvents.Ended]();
|
|
4465
|
-
assert.calledOnce(meeting.stopShare);
|
|
4466
|
-
});
|
|
4467
|
-
});
|
|
4468
3919
|
describe('#setupMediaConnectionListeners', () => {
|
|
4469
3920
|
let eventListeners;
|
|
4470
3921
|
|
|
@@ -5372,20 +4823,6 @@ describe('plugin-meetings', () => {
|
|
|
5372
4823
|
assert.calledOnce(meeting.mediaProperties.unsetRemoteTracks);
|
|
5373
4824
|
});
|
|
5374
4825
|
});
|
|
5375
|
-
describe('#unsetLocalVideoTrack', () => {
|
|
5376
|
-
it('should unset the local stream and return null', () => {
|
|
5377
|
-
meeting.mediaProperties.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
5378
|
-
meeting.unsetLocalVideoTrack();
|
|
5379
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalVideoTrack);
|
|
5380
|
-
});
|
|
5381
|
-
});
|
|
5382
|
-
describe('#unsetLocalShareTrack', () => {
|
|
5383
|
-
it('should unset the local share stream and return null', () => {
|
|
5384
|
-
meeting.mediaProperties.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
5385
|
-
meeting.unsetLocalShareTrack();
|
|
5386
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalShareTrack);
|
|
5387
|
-
});
|
|
5388
|
-
});
|
|
5389
4826
|
// TODO: remove
|
|
5390
4827
|
describe('#setMercuryListener', () => {
|
|
5391
4828
|
it('should listen to mercury events', () => {
|
|
@@ -6055,11 +5492,6 @@ describe('plugin-meetings', () => {
|
|
|
6055
5492
|
describe('setUpLocusMediaSharesListener', () => {
|
|
6056
5493
|
beforeEach(() => {
|
|
6057
5494
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
6058
|
-
sinon.stub(meeting, 'updateShare').returns(Promise.resolve());
|
|
6059
|
-
});
|
|
6060
|
-
|
|
6061
|
-
afterEach(() => {
|
|
6062
|
-
meeting.updateShare.restore();
|
|
6063
5495
|
});
|
|
6064
5496
|
|
|
6065
5497
|
const USER_IDS = {
|