@webex/plugin-meetings 3.0.0-beta.146 → 3.0.0-beta.148
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/index.js +0 -2
- package/dist/annotation/index.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 +1112 -1873
- 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/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/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 +97 -353
- package/dist/types/meeting/muteState.d.ts +36 -38
- package/dist/types/meeting/util.d.ts +2 -4
- package/package.json +19 -19
- package/src/annotation/index.ts +0 -2
- 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 +544 -1567
- package/src/meeting/muteState.ts +87 -178
- 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/annotation/index.ts +4 -4
- package/test/unit/spec/meeting/index.js +811 -1367
- package/test/unit/spec/meeting/muteState.js +238 -394
- 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';
|
|
@@ -151,6 +156,15 @@ describe('plugin-meetings', () => {
|
|
|
151
156
|
},
|
|
152
157
|
});
|
|
153
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
|
+
|
|
154
168
|
Object.defineProperty(global.window, 'MediaStream', {
|
|
155
169
|
writable: true,
|
|
156
170
|
value: MediaStream,
|
|
@@ -196,6 +210,7 @@ describe('plugin-meetings', () => {
|
|
|
196
210
|
metrics: {},
|
|
197
211
|
stats: {},
|
|
198
212
|
experimental: {enableUnifiedMeetings: true},
|
|
213
|
+
degradationPreferences: { maxMacroblocksLimit: 8192 },
|
|
199
214
|
},
|
|
200
215
|
metrics: {
|
|
201
216
|
type: ['behavioral'],
|
|
@@ -469,246 +484,7 @@ describe('plugin-meetings', () => {
|
|
|
469
484
|
assert.instanceOf(members, Members);
|
|
470
485
|
});
|
|
471
486
|
});
|
|
472
|
-
describe('#isAudioMuted', () => {
|
|
473
|
-
it('should have #isAudioMuted', () => {
|
|
474
|
-
assert.exists(meeting.invite);
|
|
475
|
-
});
|
|
476
|
-
it('should get the audio muted status and return as a boolean', () => {
|
|
477
|
-
const muted = meeting.isAudioMuted();
|
|
478
|
-
|
|
479
|
-
assert.isNotOk(muted);
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
describe('#isAudioSelf', () => {
|
|
483
|
-
it('should have #isAudioSelf', () => {
|
|
484
|
-
assert.exists(meeting.invite);
|
|
485
|
-
});
|
|
486
|
-
it('should get the audio self status and return as a boolean', () => {
|
|
487
|
-
const self = meeting.isAudioSelf();
|
|
488
|
-
|
|
489
|
-
assert.isNotOk(self);
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
describe('#isVideoMuted', () => {
|
|
493
|
-
it('should have #isVideoMuted', () => {
|
|
494
|
-
assert.exists(meeting.isVideoMuted);
|
|
495
|
-
});
|
|
496
|
-
it('should get the video muted status and return as a boolean', () => {
|
|
497
|
-
const muted = meeting.isVideoMuted();
|
|
498
|
-
|
|
499
|
-
assert.isNotOk(muted);
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
describe('#isVideoSelf', () => {
|
|
503
|
-
it('should have #isVideoSelf', () => {
|
|
504
|
-
assert.exists(meeting.invite);
|
|
505
|
-
});
|
|
506
|
-
it('should get the video self status and return as a boolean', () => {
|
|
507
|
-
const self = meeting.isVideoSelf();
|
|
508
|
-
|
|
509
|
-
assert.isNotOk(self);
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
describe('#muteAudio', () => {
|
|
513
|
-
it('should have #muteAudio', () => {
|
|
514
|
-
assert.exists(meeting.muteAudio);
|
|
515
|
-
});
|
|
516
|
-
describe('before audio is defined', () => {
|
|
517
|
-
it('should reject and return a promise', async () => {
|
|
518
|
-
await meeting.muteAudio().catch((err) => {
|
|
519
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
it('should reject and return a promise', async () => {
|
|
524
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
525
|
-
await meeting.muteAudio().catch((err) => {
|
|
526
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
527
|
-
});
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('should reject and return a promise', async () => {
|
|
531
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
532
|
-
meeting.mediaId = 'mediaId';
|
|
533
|
-
await meeting.muteAudio().catch((err) => {
|
|
534
|
-
assert.instanceOf(err, ParameterError);
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
});
|
|
538
|
-
describe('after audio is defined', () => {
|
|
539
|
-
let handleClientRequest;
|
|
540
|
-
|
|
541
|
-
beforeEach(() => {
|
|
542
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
543
|
-
meeting.audio = {handleClientRequest};
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it('should return a promise resolution', async () => {
|
|
547
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
548
|
-
meeting.mediaId = 'mediaId';
|
|
549
|
-
|
|
550
|
-
const audio = meeting.muteAudio();
|
|
551
|
-
|
|
552
|
-
assert.exists(audio.then);
|
|
553
|
-
await audio;
|
|
554
|
-
assert.calledOnce(handleClientRequest);
|
|
555
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
describe('#unmuteAudio', () => {
|
|
560
|
-
it('should have #unmuteAudio', () => {
|
|
561
|
-
assert.exists(meeting.unmuteAudio);
|
|
562
|
-
});
|
|
563
|
-
describe('before audio is defined', () => {
|
|
564
|
-
it('should reject when user not joined', async () => {
|
|
565
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
566
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
it('should reject when no media is established yet ', async () => {
|
|
571
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
572
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
573
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it('should reject when audio is not there or established', async () => {
|
|
578
|
-
meeting.mediaId = 'mediaId';
|
|
579
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
580
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
581
|
-
assert.instanceOf(err, ParameterError);
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
describe('after audio is defined', () => {
|
|
586
|
-
let handleClientRequest;
|
|
587
|
-
|
|
588
|
-
beforeEach(() => {
|
|
589
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
590
|
-
meeting.mediaId = 'mediaId';
|
|
591
|
-
meeting.audio = {handleClientRequest};
|
|
592
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
it('should return a promise resolution', async () => {
|
|
596
|
-
meeting.audio = {handleClientRequest};
|
|
597
|
-
|
|
598
|
-
const audio = meeting.unmuteAudio();
|
|
599
|
-
|
|
600
|
-
assert.exists(audio.then);
|
|
601
|
-
await audio;
|
|
602
|
-
assert.calledOnce(handleClientRequest);
|
|
603
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
604
|
-
});
|
|
605
|
-
});
|
|
606
|
-
});
|
|
607
|
-
describe('BNR', () => {
|
|
608
|
-
const fakeMediaTrack = () => ({
|
|
609
|
-
id: Date.now().toString(),
|
|
610
|
-
stop: () => {},
|
|
611
|
-
readyState: 'live',
|
|
612
|
-
enabled: true,
|
|
613
|
-
getSettings: () => ({
|
|
614
|
-
sampleRate: 48000,
|
|
615
|
-
}),
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
beforeEach(() => {
|
|
619
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
|
|
620
|
-
sinon.replace(meeting, 'addMedia', () => {
|
|
621
|
-
sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
|
|
622
|
-
sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
|
|
623
|
-
receiveAudio: true,
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
describe('#muteVideo', () => {
|
|
629
|
-
it('should have #muteVideo', () => {
|
|
630
|
-
assert.exists(meeting.muteVideo);
|
|
631
|
-
});
|
|
632
|
-
describe('before video is defined', () => {
|
|
633
|
-
it('should reject when user not joined', async () => {
|
|
634
|
-
await meeting.muteVideo().catch((err) => {
|
|
635
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
it('should reject when no media is established', async () => {
|
|
640
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
641
|
-
await meeting.muteVideo().catch((err) => {
|
|
642
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
643
|
-
});
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
it('should reject when no video added or established', async () => {
|
|
647
|
-
meeting.mediaId = 'mediaId';
|
|
648
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
649
|
-
await meeting.muteVideo().catch((err) => {
|
|
650
|
-
assert.instanceOf(err, ParameterError);
|
|
651
|
-
});
|
|
652
|
-
});
|
|
653
|
-
});
|
|
654
|
-
describe('after video is defined', () => {
|
|
655
|
-
it('should return a promise resolution', async () => {
|
|
656
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
657
|
-
|
|
658
|
-
meeting.mediaId = 'mediaId';
|
|
659
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
660
|
-
meeting.video = {handleClientRequest};
|
|
661
|
-
const video = meeting.muteVideo();
|
|
662
|
-
|
|
663
|
-
assert.exists(video.then);
|
|
664
|
-
await video;
|
|
665
|
-
assert.calledOnce(handleClientRequest);
|
|
666
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
});
|
|
670
|
-
describe('#unmuteVideo', () => {
|
|
671
|
-
it('should have #unmuteVideo', () => {
|
|
672
|
-
assert.exists(meeting.unmuteVideo);
|
|
673
|
-
});
|
|
674
|
-
describe('before video is defined', () => {
|
|
675
|
-
it('should reject no user joined', async () => {
|
|
676
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
677
|
-
assert.instanceOf(err, Error);
|
|
678
|
-
});
|
|
679
|
-
});
|
|
680
487
|
|
|
681
|
-
it('should reject no media established', async () => {
|
|
682
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
683
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
684
|
-
assert.instanceOf(err, Error);
|
|
685
|
-
});
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
it('should reject when no video added or established', async () => {
|
|
689
|
-
meeting.mediaId = 'mediaId';
|
|
690
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
691
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
692
|
-
assert.instanceOf(err, Error);
|
|
693
|
-
});
|
|
694
|
-
});
|
|
695
|
-
});
|
|
696
|
-
describe('after video is defined', () => {
|
|
697
|
-
it('should return a promise resolution', async () => {
|
|
698
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
699
|
-
|
|
700
|
-
meeting.mediaId = 'mediaId';
|
|
701
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
702
|
-
meeting.video = {handleClientRequest};
|
|
703
|
-
const video = meeting.unmuteVideo();
|
|
704
|
-
|
|
705
|
-
assert.exists(video.then);
|
|
706
|
-
await video;
|
|
707
|
-
assert.calledOnce(handleClientRequest);
|
|
708
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
488
|
describe('#joinWithMedia', () => {
|
|
713
489
|
it('should have #joinWithMedia', () => {
|
|
714
490
|
assert.exists(meeting.joinWithMedia);
|
|
@@ -716,125 +492,21 @@ describe('plugin-meetings', () => {
|
|
|
716
492
|
describe('resolution', () => {
|
|
717
493
|
it('should success and return a promise', async () => {
|
|
718
494
|
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
719
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([test2, test3]));
|
|
720
495
|
meeting.addMedia = sinon.stub().returns(Promise.resolve(test4));
|
|
721
|
-
await meeting.joinWithMedia({});
|
|
496
|
+
const result = await meeting.joinWithMedia({});
|
|
722
497
|
assert.calledOnce(meeting.join);
|
|
723
|
-
assert.calledOnce(meeting.
|
|
498
|
+
assert.calledOnce(meeting.addMedia);
|
|
499
|
+
assert.deepEqual(result, {join: test1, media: test4});
|
|
724
500
|
});
|
|
725
501
|
});
|
|
726
502
|
describe('rejection', () => {
|
|
727
503
|
it('should error out and return a promise', async () => {
|
|
728
504
|
meeting.join = sinon.stub().returns(Promise.reject());
|
|
729
|
-
meeting.getMediaStreams = sinon.stub().returns(true);
|
|
730
505
|
assert.isRejected(meeting.joinWithMedia({}));
|
|
731
506
|
});
|
|
732
507
|
});
|
|
733
508
|
});
|
|
734
|
-
describe('#getMediaStreams', () => {
|
|
735
|
-
beforeEach(() => {
|
|
736
|
-
sinon
|
|
737
|
-
.stub(Media, 'getSupportedDevice')
|
|
738
|
-
.callsFake((options) =>
|
|
739
|
-
Promise.resolve({sendAudio: options.sendAudio, sendVideo: options.sendVideo})
|
|
740
|
-
);
|
|
741
|
-
sinon.stub(Media, 'getUserMedia').returns(Promise.resolve(['stream1', 'stream2']));
|
|
742
|
-
});
|
|
743
|
-
afterEach(() => {
|
|
744
|
-
sinon.restore();
|
|
745
|
-
});
|
|
746
|
-
it('should have #getMediaStreams', () => {
|
|
747
|
-
assert.exists(meeting.getMediaStreams);
|
|
748
|
-
});
|
|
749
|
-
it('should proxy Media getUserMedia, and return a promise', async () => {
|
|
750
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: true});
|
|
751
|
-
|
|
752
|
-
assert.calledOnce(Media.getUserMedia);
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
it('uses the preferred video device if set', async () => {
|
|
756
|
-
const videoDevice = 'video1';
|
|
757
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
758
|
-
const audioVideoSettings = {};
|
|
759
|
-
|
|
760
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(videoDevice);
|
|
761
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('480p');
|
|
762
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
763
|
-
|
|
764
|
-
assert.calledWith(
|
|
765
|
-
Media.getUserMedia,
|
|
766
|
-
{
|
|
767
|
-
...mediaDirection,
|
|
768
|
-
isSharing: false,
|
|
769
|
-
},
|
|
770
|
-
{
|
|
771
|
-
video: {
|
|
772
|
-
width: {max: 640, ideal: 640},
|
|
773
|
-
height: {max: 480, ideal: 480},
|
|
774
|
-
deviceId: videoDevice,
|
|
775
|
-
},
|
|
776
|
-
}
|
|
777
|
-
);
|
|
778
|
-
});
|
|
779
|
-
it('will set a new preferred video input device if passed in', async () => {
|
|
780
|
-
// if audioVideo settings parameter specifies a new video device it
|
|
781
|
-
// will store that device as the preferred video device.
|
|
782
|
-
// Which is the case with meeting.updateVideo()
|
|
783
|
-
const oldVideoDevice = 'video1';
|
|
784
|
-
const newVideoDevice = 'video2';
|
|
785
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
786
|
-
const audioVideoSettings = {video: {deviceId: newVideoDevice}};
|
|
787
|
-
|
|
788
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(oldVideoDevice);
|
|
789
|
-
sinon.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
790
509
|
|
|
791
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
792
|
-
|
|
793
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, newVideoDevice);
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
it('uses the passed custom video resolution', async () => {
|
|
797
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
798
|
-
const customAudioVideoSettings = {
|
|
799
|
-
video: {
|
|
800
|
-
width: {
|
|
801
|
-
max: 400,
|
|
802
|
-
ideal: 400,
|
|
803
|
-
},
|
|
804
|
-
height: {
|
|
805
|
-
max: 200,
|
|
806
|
-
ideal: 200,
|
|
807
|
-
},
|
|
808
|
-
frameRate: {
|
|
809
|
-
ideal: 15,
|
|
810
|
-
max: 30,
|
|
811
|
-
},
|
|
812
|
-
facingMode: {
|
|
813
|
-
ideal: 'user',
|
|
814
|
-
},
|
|
815
|
-
},
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('200p');
|
|
819
|
-
await meeting.getMediaStreams(mediaDirection, customAudioVideoSettings);
|
|
820
|
-
|
|
821
|
-
assert.calledWith(
|
|
822
|
-
Media.getUserMedia,
|
|
823
|
-
{
|
|
824
|
-
...mediaDirection,
|
|
825
|
-
isSharing: false,
|
|
826
|
-
},
|
|
827
|
-
customAudioVideoSettings
|
|
828
|
-
);
|
|
829
|
-
});
|
|
830
|
-
it('should not access camera if sendVideo is false ', async () => {
|
|
831
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: false});
|
|
832
|
-
|
|
833
|
-
assert.calledOnce(Media.getUserMedia);
|
|
834
|
-
|
|
835
|
-
assert.equal(Media.getUserMedia.args[0][0].sendVideo, false);
|
|
836
|
-
});
|
|
837
|
-
});
|
|
838
510
|
describe('#isTranscriptionSupported', () => {
|
|
839
511
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
840
512
|
meeting.locusInfo.controls = {transcribe: {transcribing: false}};
|
|
@@ -1159,7 +831,7 @@ describe('plugin-meetings', () => {
|
|
|
1159
831
|
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
1160
832
|
meeting.audio = muteStateStub;
|
|
1161
833
|
meeting.video = muteStateStub;
|
|
1162
|
-
|
|
834
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
1163
835
|
meeting.setMercuryListener = sinon.stub().returns(true);
|
|
1164
836
|
meeting.setupMediaConnectionListeners = sinon.stub();
|
|
1165
837
|
meeting.setMercuryListener = sinon.stub();
|
|
@@ -1240,6 +912,7 @@ describe('plugin-meetings', () => {
|
|
|
1240
912
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1241
913
|
reason: err.message,
|
|
1242
914
|
stack: err.stack,
|
|
915
|
+
code: err.code,
|
|
1243
916
|
turnDiscoverySkippedReason: 'config',
|
|
1244
917
|
turnServerUsed: false,
|
|
1245
918
|
isMultistream: false,
|
|
@@ -1703,649 +1376,810 @@ describe('plugin-meetings', () => {
|
|
|
1703
1376
|
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
1704
1377
|
});
|
|
1705
1378
|
});
|
|
1706
|
-
describe('#acknowledge', () => {
|
|
1707
|
-
it('should have #acknowledge', () => {
|
|
1708
|
-
assert.exists(meeting.acknowledge);
|
|
1709
|
-
});
|
|
1710
|
-
beforeEach(() => {
|
|
1711
|
-
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
1712
|
-
});
|
|
1713
|
-
it('should acknowledge incoming and return a promise', async () => {
|
|
1714
|
-
const ack = meeting.acknowledge('INCOMING', false);
|
|
1715
1379
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
+
};
|
|
1722
1392
|
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
it('should have #decline', () => {
|
|
1730
|
-
assert.exists(meeting.decline);
|
|
1731
|
-
});
|
|
1732
|
-
beforeEach(() => {
|
|
1733
|
-
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
1734
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1735
|
-
});
|
|
1736
|
-
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
1737
|
-
await meeting.decline();
|
|
1738
|
-
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
1739
|
-
});
|
|
1740
|
-
});
|
|
1741
|
-
describe('#leave', () => {
|
|
1742
|
-
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
|
|
1743
1399
|
|
|
1744
|
-
|
|
1745
|
-
assert.exists(meeting.leave);
|
|
1746
|
-
});
|
|
1400
|
+
const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
|
|
1747
1401
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
assert.instanceOf(err, MeetingNotActiveError);
|
|
1751
|
-
});
|
|
1752
|
-
});
|
|
1402
|
+
let expectedMediaConnectionConfig;
|
|
1403
|
+
let expectedDebugId;
|
|
1753
1404
|
|
|
1754
|
-
|
|
1755
|
-
meeting.meetingState = 'ACTIVE';
|
|
1756
|
-
await meeting.leave().catch((err) => {
|
|
1757
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
1758
|
-
});
|
|
1759
|
-
});
|
|
1405
|
+
let clock;
|
|
1760
1406
|
|
|
1761
1407
|
beforeEach(() => {
|
|
1762
|
-
|
|
1763
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1764
|
-
meeting.meetingFiniteStateMachine.join();
|
|
1765
|
-
meeting.meetingRequest.leaveMeeting = sinon
|
|
1766
|
-
.stub()
|
|
1767
|
-
.returns(Promise.resolve({body: 'test'}));
|
|
1768
|
-
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
1769
|
-
// the 3 need to be promises because we do closeLocalStream.then(closeLocalShare.then) etc in the src code
|
|
1770
|
-
meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
|
|
1771
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
1772
|
-
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
1773
|
-
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
1774
|
-
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
1775
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
1776
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
1777
|
-
meeting.unsetRemoteTracks = sinon.stub();
|
|
1778
|
-
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
1779
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
1780
|
-
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
1781
|
-
meeting.logger.error = sinon.stub().returns(true);
|
|
1782
|
-
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
1408
|
+
clock = sinon.useFakeTimers();
|
|
1783
1409
|
|
|
1784
|
-
|
|
1410
|
+
meeting.deviceUrl = 'deviceUrl';
|
|
1411
|
+
meeting.config.deviceType = 'web';
|
|
1412
|
+
meeting.isMultistream = isMultistream;
|
|
1785
1413
|
meeting.meetingState = 'ACTIVE';
|
|
1786
|
-
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: {}}}});
|
|
1787
1494
|
});
|
|
1495
|
+
|
|
1788
1496
|
afterEach(() => {
|
|
1789
|
-
|
|
1790
|
-
sandbox = null;
|
|
1497
|
+
clock.restore();
|
|
1791
1498
|
});
|
|
1792
|
-
it('should leave the meeting and return promise', async () => {
|
|
1793
|
-
const leave = meeting.leave();
|
|
1794
1499
|
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1807
|
-
describe('after audio/video is defined', () => {
|
|
1808
|
-
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
|
+
};
|
|
1809
1512
|
|
|
1810
|
-
|
|
1811
|
-
|
|
1513
|
+
const getRoapListener = () => {
|
|
1514
|
+
const roapMediaConnectionToCheck = isMultistream ? fakeMultistreamRoapMediaConnection : fakeRoapMediaConnection;
|
|
1812
1515
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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
|
+
}
|
|
1816
1523
|
|
|
1817
|
-
|
|
1818
|
-
|
|
1524
|
+
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
1525
|
+
const simulateRoapOffer = async () => {
|
|
1526
|
+
const roapListener = getRoapListener();
|
|
1819
1527
|
|
|
1820
|
-
|
|
1821
|
-
|
|
1528
|
+
await roapListener({roapMessage: roapOfferMessage});
|
|
1529
|
+
await stableState();
|
|
1530
|
+
}
|
|
1822
1531
|
|
|
1823
|
-
|
|
1824
|
-
|
|
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,
|
|
1825
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);
|
|
1826
1634
|
});
|
|
1827
|
-
it('should leave the meeting without leaving resource', async () => {
|
|
1828
|
-
const leave = meeting.leave({resourceId: null});
|
|
1829
1635
|
|
|
1830
|
-
|
|
1831
|
-
await
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
|
1838
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);
|
|
1839
1662
|
});
|
|
1840
|
-
it('should leave the meeting on the resource', async () => {
|
|
1841
|
-
const leave = meeting.leave();
|
|
1842
1663
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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,
|
|
1851
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);
|
|
1852
1692
|
});
|
|
1853
|
-
it('should leave the meeting on the resource with reason', async () => {
|
|
1854
|
-
const leave = meeting.leave({resourceId: meeting.resourceId, reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST});
|
|
1855
1693
|
|
|
1856
|
-
|
|
1857
|
-
await
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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
|
|
1865
1713
|
});
|
|
1866
|
-
});
|
|
1867
|
-
});
|
|
1868
|
-
describe('#requestScreenShareFloor', () => {
|
|
1869
|
-
it('should have #requestScreenShareFloor', () => {
|
|
1870
|
-
assert.exists(meeting.requestScreenShareFloor);
|
|
1871
|
-
});
|
|
1872
|
-
beforeEach(() => {
|
|
1873
|
-
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
1874
|
-
meeting.locusInfo.self = {url: url1};
|
|
1875
|
-
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
1876
|
-
});
|
|
1877
|
-
it('should send the share', async () => {
|
|
1878
|
-
const share = meeting.requestScreenShareFloor();
|
|
1879
1714
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
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);
|
|
1883
1720
|
});
|
|
1884
|
-
});
|
|
1885
1721
|
|
|
1886
|
-
|
|
1887
|
-
|
|
1722
|
+
it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
|
|
1723
|
+
await meeting.addMedia({audioEnabled: false});
|
|
1724
|
+
await simulateRoapOffer();
|
|
1888
1725
|
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
+
});
|
|
1895
1742
|
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
});
|
|
1743
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1744
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1899
1745
|
|
|
1900
|
-
|
|
1901
|
-
assert.
|
|
1746
|
+
// and no other local mute requests were sent to Locus
|
|
1747
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1902
1748
|
});
|
|
1903
1749
|
|
|
1904
|
-
describe('
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
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
|
+
});
|
|
1909
1786
|
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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
|
+
}
|
|
1914
1824
|
|
|
1915
|
-
|
|
1916
|
-
|
|
1825
|
+
// and no other roap messages or local mute requests were sent
|
|
1826
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1827
|
+
});
|
|
1917
1828
|
|
|
1918
|
-
|
|
1919
|
-
|
|
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();
|
|
1920
1834
|
|
|
1921
|
-
|
|
1922
|
-
await meeting.shareScreen();
|
|
1835
|
+
resetHistory();
|
|
1923
1836
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1837
|
+
await meeting.unpublishTracks([fakeMicrophoneTrack]);
|
|
1838
|
+
await stableState();
|
|
1926
1839
|
|
|
1927
|
-
|
|
1928
|
-
|
|
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
|
+
}
|
|
1929
1854
|
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
+
});
|
|
1934
1863
|
});
|
|
1935
|
-
});
|
|
1936
1864
|
});
|
|
1937
1865
|
|
|
1938
|
-
describe('
|
|
1939
|
-
let sandbox;
|
|
1866
|
+
describe('updateMedia()', () => {
|
|
1940
1867
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1868
|
+
const addMedia = async (enableMedia, track) => {
|
|
1869
|
+
await meeting.addMedia({audioEnabled: enableMedia, localTracks: {microphone: track}});
|
|
1870
|
+
await simulateRoapOffer();
|
|
1944
1871
|
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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);
|
|
1948
1907
|
});
|
|
1949
1908
|
|
|
1950
|
-
it('
|
|
1951
|
-
|
|
1952
|
-
const receiveShare = false;
|
|
1953
|
-
const stream = 'stream';
|
|
1909
|
+
it('updateMedia() enables media when nothing is published', async () => {
|
|
1910
|
+
await addMedia(false);
|
|
1954
1911
|
|
|
1955
|
-
|
|
1956
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve(true));
|
|
1957
|
-
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1958
|
-
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1912
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1959
1913
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
});
|
|
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();
|
|
1966
1919
|
|
|
1967
|
-
|
|
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);
|
|
1968
1925
|
});
|
|
1969
1926
|
|
|
1970
|
-
it('
|
|
1971
|
-
|
|
1972
|
-
const fakeTrack = {
|
|
1973
|
-
getSettings: sinon.stub().returns({}),
|
|
1974
|
-
};
|
|
1927
|
+
it('updateMedia() disables media when track is published', async () => {
|
|
1928
|
+
await addMedia(true, fakeMicrophoneTrack);
|
|
1975
1929
|
|
|
1976
|
-
|
|
1930
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1931
|
+
await stableState();
|
|
1977
1932
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
};
|
|
1983
|
-
sinon.stub(InternalMediaCoreModule, 'LocalDisplayTrack').returns(fakeLocalDisplayTrack);
|
|
1933
|
+
// the roap media connection should be updated
|
|
1934
|
+
checkAudioEnabled(webrtcAudioTrack, 'inactive');
|
|
1935
|
+
|
|
1936
|
+
checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
|
|
1984
1937
|
|
|
1985
|
-
|
|
1986
|
-
sandbox.stub(mediaProperties, 'setMediaSettings');
|
|
1987
|
-
sandbox.stub(meeting, 'stopShare').resolves(true);
|
|
1988
|
-
meeting.setLocalShareTrack(fakeTrack);
|
|
1938
|
+
locusMediaRequestStub.resetHistory();
|
|
1989
1939
|
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
assert.isNotNull(listeners[LocalTrackEvents.Ended]);
|
|
1940
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1941
|
+
await simulateRoapOffer();
|
|
1993
1942
|
|
|
1994
|
-
|
|
1943
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1944
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1995
1945
|
|
|
1996
|
-
|
|
1946
|
+
// and no other local mute requests were sent to Locus
|
|
1947
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1997
1948
|
});
|
|
1998
1949
|
|
|
1999
|
-
it('
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
1950
|
+
it('updateMedia() enables media when track is published', async () => {
|
|
1951
|
+
await addMedia(false, fakeMicrophoneTrack);
|
|
1952
|
+
|
|
1953
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1954
|
+
await stableState();
|
|
1955
|
+
|
|
1956
|
+
// the roap media connection should be updated
|
|
1957
|
+
checkAudioEnabled(webrtcAudioTrack, 'sendrecv');
|
|
1958
|
+
|
|
1959
|
+
checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
|
|
2005
1960
|
|
|
2006
|
-
|
|
2007
|
-
sandbox.stub(meeting.mediaProperties, 'mediaDirection').value(false);
|
|
1961
|
+
locusMediaRequestStub.resetHistory();
|
|
2008
1962
|
|
|
2009
|
-
|
|
1963
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1964
|
+
await simulateRoapOffer();
|
|
2010
1965
|
|
|
2011
|
-
|
|
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);
|
|
2012
1971
|
});
|
|
2013
1972
|
});
|
|
2014
1973
|
|
|
2015
|
-
|
|
2016
|
-
|
|
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;
|
|
2017
1981
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
});
|
|
1982
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1983
|
+
await stableState();
|
|
2021
1984
|
|
|
2022
|
-
|
|
2023
|
-
sandbox.restore();
|
|
2024
|
-
sandbox = null;
|
|
2025
|
-
});
|
|
1985
|
+
resetHistory();
|
|
2026
1986
|
|
|
2027
|
-
|
|
2028
|
-
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}});
|
|
2029
1991
|
|
|
2030
|
-
|
|
1992
|
+
await stableState();
|
|
2031
1993
|
|
|
2032
|
-
|
|
1994
|
+
// nothing should happen
|
|
1995
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1996
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2033
1997
|
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
sinon.match.instanceOf(Meeting),
|
|
2037
|
-
{
|
|
2038
|
-
file: 'meeting/index',
|
|
2039
|
-
function: 'handleShareTrackEnded',
|
|
2040
|
-
},
|
|
2041
|
-
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
2042
|
-
{
|
|
2043
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
2044
|
-
}
|
|
2045
|
-
);
|
|
2046
|
-
});
|
|
2047
|
-
});
|
|
2048
|
-
});
|
|
1998
|
+
// now simulate roap offer
|
|
1999
|
+
await simulateRoapOffer();
|
|
2049
2000
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
const config = DefaultSDKConfig.meetings;
|
|
2053
|
-
const {resolution} = config;
|
|
2054
|
-
const shareOptions = {
|
|
2055
|
-
sendShare: true,
|
|
2056
|
-
sendAudio: false,
|
|
2057
|
-
};
|
|
2058
|
-
const fireFoxOptions = {
|
|
2059
|
-
audio: false,
|
|
2060
|
-
video: {
|
|
2061
|
-
audio: shareOptions.sendAudio,
|
|
2062
|
-
video: shareOptions.sendShare,
|
|
2063
|
-
},
|
|
2064
|
-
};
|
|
2001
|
+
// it should be sent with the right mute status
|
|
2002
|
+
checkSdpOfferSent({audioMuted: mute, videoMuted: true});
|
|
2065
2003
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
};
|
|
2004
|
+
// nothing else should happen
|
|
2005
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
2006
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
}));
|
|
2073
2010
|
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
};
|
|
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);
|
|
2081
2020
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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);
|
|
2085
2027
|
|
|
2086
|
-
|
|
2087
|
-
|
|
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;
|
|
2088
2048
|
|
|
2089
|
-
|
|
2090
|
-
|
|
2049
|
+
it('should have #leave', () => {
|
|
2050
|
+
assert.exists(meeting.leave);
|
|
2051
|
+
});
|
|
2091
2052
|
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
getDisplayMedia: null,
|
|
2096
|
-
},
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2099
|
-
_getDisplayMedia = global.navigator.mediaDevices.getDisplayMedia;
|
|
2100
|
-
Object.defineProperty(global.navigator.mediaDevices, 'getDisplayMedia', {
|
|
2101
|
-
value: sinon.stub().returns(Promise.resolve(MediaStream)),
|
|
2102
|
-
writable: true,
|
|
2053
|
+
it('should reject if meeting is already inactive', async () => {
|
|
2054
|
+
await meeting.leave().catch((err) => {
|
|
2055
|
+
assert.instanceOf(err, MeetingNotActiveError);
|
|
2103
2056
|
});
|
|
2104
2057
|
});
|
|
2105
2058
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
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);
|
|
2111
2063
|
});
|
|
2112
2064
|
});
|
|
2113
2065
|
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
sharePreferences: {shareConstraints},
|
|
2132
|
-
},
|
|
2133
|
-
config
|
|
2134
|
-
);
|
|
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());
|
|
2135
2083
|
|
|
2136
|
-
//
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
browserConditionalValue({
|
|
2140
|
-
default: {
|
|
2141
|
-
video: {...shareConstraints},
|
|
2142
|
-
},
|
|
2143
|
-
// Firefox is being handled differently
|
|
2144
|
-
firefox: fireFoxOptions,
|
|
2145
|
-
})
|
|
2146
|
-
);
|
|
2084
|
+
// A meeting needs to be joined to leave
|
|
2085
|
+
meeting.meetingState = 'ACTIVE';
|
|
2086
|
+
meeting.state = 'JOINED';
|
|
2147
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();
|
|
2148
2094
|
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
},
|
|
2158
|
-
},
|
|
2159
|
-
config
|
|
2160
|
-
);
|
|
2161
|
-
|
|
2162
|
-
// eslint-disable-next-line no-undef
|
|
2163
|
-
assert.calledWith(
|
|
2164
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
2165
|
-
browserConditionalValue({
|
|
2166
|
-
default: {
|
|
2167
|
-
video: {
|
|
2168
|
-
...MediaConstraint,
|
|
2169
|
-
frameRate: config.videoShareFrameRate,
|
|
2170
|
-
width: resolution.idealWidth,
|
|
2171
|
-
height: resolution.idealHeight,
|
|
2172
|
-
maxWidth: resolution.maxWidth,
|
|
2173
|
-
maxHeight: resolution.maxHeight,
|
|
2174
|
-
idealWidth: resolution.idealWidth,
|
|
2175
|
-
idealHeight: resolution.idealHeight,
|
|
2176
|
-
},
|
|
2177
|
-
},
|
|
2178
|
-
firefox: fireFoxOptions,
|
|
2179
|
-
})
|
|
2180
|
-
);
|
|
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);
|
|
2181
2103
|
});
|
|
2104
|
+
describe('after audio/video is defined', () => {
|
|
2105
|
+
let handleClientRequest;
|
|
2182
2106
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
getDisplayMedia(shareOptions);
|
|
2186
|
-
const {screenResolution} = config;
|
|
2107
|
+
beforeEach(() => {
|
|
2108
|
+
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
2187
2109
|
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
browserConditionalValue({
|
|
2192
|
-
default: {
|
|
2193
|
-
video: {
|
|
2194
|
-
...MediaConstraint,
|
|
2195
|
-
width: screenResolution.idealWidth,
|
|
2196
|
-
height: screenResolution.idealHeight,
|
|
2197
|
-
},
|
|
2198
|
-
},
|
|
2199
|
-
firefox: fireFoxOptions,
|
|
2200
|
-
})
|
|
2201
|
-
);
|
|
2202
|
-
});
|
|
2110
|
+
meeting.audio = {handleClientRequest};
|
|
2111
|
+
meeting.video = {handleClientRequest};
|
|
2112
|
+
});
|
|
2203
2113
|
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
it('will use SDK config screenResolution if set, with shareConstraints and highFrameRate being undefined', () => {
|
|
2207
|
-
const SHARE_WIDTH = 800;
|
|
2208
|
-
const SHARE_HEIGHT = 600;
|
|
2209
|
-
const customConfig = {
|
|
2210
|
-
screenResolution: {
|
|
2211
|
-
maxWidth: SHARE_WIDTH,
|
|
2212
|
-
maxHeight: SHARE_HEIGHT,
|
|
2213
|
-
idealWidth: SHARE_WIDTH,
|
|
2214
|
-
idealHeight: SHARE_HEIGHT,
|
|
2215
|
-
},
|
|
2216
|
-
};
|
|
2114
|
+
it('should delete audio and video state machines when leaving the meeting', async () => {
|
|
2115
|
+
const leave = meeting.leave();
|
|
2217
2116
|
|
|
2218
|
-
|
|
2117
|
+
assert.exists(leave.then);
|
|
2118
|
+
await leave;
|
|
2219
2119
|
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
browserConditionalValue({
|
|
2224
|
-
default: {
|
|
2225
|
-
video: {
|
|
2226
|
-
...MediaConstraint,
|
|
2227
|
-
width: SHARE_WIDTH,
|
|
2228
|
-
height: SHARE_HEIGHT,
|
|
2229
|
-
maxWidth: SHARE_WIDTH,
|
|
2230
|
-
maxHeight: SHARE_HEIGHT,
|
|
2231
|
-
idealWidth: SHARE_WIDTH,
|
|
2232
|
-
idealHeight: SHARE_HEIGHT,
|
|
2233
|
-
},
|
|
2234
|
-
},
|
|
2235
|
-
firefox: fireFoxOptions,
|
|
2236
|
-
})
|
|
2237
|
-
);
|
|
2120
|
+
assert.isNull(meeting.audio);
|
|
2121
|
+
assert.isNull(meeting.video);
|
|
2122
|
+
});
|
|
2238
2123
|
});
|
|
2124
|
+
it('should leave the meeting without leaving resource', async () => {
|
|
2125
|
+
const leave = meeting.leave({resourceId: null});
|
|
2239
2126
|
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
};
|
|
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();
|
|
2253
2139
|
|
|
2254
|
-
|
|
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});
|
|
2255
2152
|
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
maxWidth: SHARE_WIDTH,
|
|
2267
|
-
maxHeight: SHARE_HEIGHT,
|
|
2268
|
-
idealWidth: SHARE_WIDTH,
|
|
2269
|
-
idealHeight: SHARE_HEIGHT,
|
|
2270
|
-
},
|
|
2271
|
-
},
|
|
2272
|
-
firefox: fireFoxOptions,
|
|
2273
|
-
})
|
|
2274
|
-
);
|
|
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
|
+
});
|
|
2275
2163
|
});
|
|
2276
2164
|
});
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
assert.exists(meeting.stopShare);
|
|
2165
|
+
describe('#requestScreenShareFloor', () => {
|
|
2166
|
+
it('should have #requestScreenShareFloor', () => {
|
|
2167
|
+
assert.exists(meeting.requestScreenShareFloor);
|
|
2281
2168
|
});
|
|
2282
2169
|
beforeEach(() => {
|
|
2283
|
-
meeting.
|
|
2284
|
-
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';
|
|
2285
2176
|
});
|
|
2286
|
-
it('should
|
|
2287
|
-
const share = meeting.
|
|
2177
|
+
it('should send the share', async () => {
|
|
2178
|
+
const share = meeting.requestScreenShareFloor();
|
|
2288
2179
|
|
|
2289
2180
|
assert.exists(share.then);
|
|
2290
2181
|
await share;
|
|
2291
|
-
assert.calledOnce(meeting.
|
|
2292
|
-
});
|
|
2293
|
-
});
|
|
2294
|
-
|
|
2295
|
-
describe('#updateAudio', () => {
|
|
2296
|
-
const FAKE_AUDIO_TRACK = {
|
|
2297
|
-
id: 'fake audio track',
|
|
2298
|
-
getSettings: sinon.stub().returns({}),
|
|
2299
|
-
};
|
|
2300
|
-
|
|
2301
|
-
describe('when canUpdateMedia is true', () => {
|
|
2302
|
-
beforeEach(() => {
|
|
2303
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2304
|
-
});
|
|
2305
|
-
describe('when options are valid', () => {
|
|
2306
|
-
beforeEach(() => {
|
|
2307
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2308
|
-
meeting.mediaProperties.mediaDirection = {
|
|
2309
|
-
sendAudio: false,
|
|
2310
|
-
sendVideo: true,
|
|
2311
|
-
sendShare: false,
|
|
2312
|
-
receiveAudio: false,
|
|
2313
|
-
receiveVideo: true,
|
|
2314
|
-
receiveShare: true,
|
|
2315
|
-
};
|
|
2316
|
-
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2317
|
-
update: sinon.stub(),
|
|
2318
|
-
};
|
|
2319
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
|
|
2320
|
-
});
|
|
2321
|
-
it('calls this.mediaProperties.webrtcMediaConnection.update', () =>
|
|
2322
|
-
meeting
|
|
2323
|
-
.updateAudio({
|
|
2324
|
-
sendAudio: true,
|
|
2325
|
-
receiveAudio: true,
|
|
2326
|
-
stream: {id: 'fake stream'},
|
|
2327
|
-
})
|
|
2328
|
-
.then(() => {
|
|
2329
|
-
assert.calledOnce(
|
|
2330
|
-
meeting.mediaProperties.webrtcMediaConnection.update
|
|
2331
|
-
);
|
|
2332
|
-
assert.calledWith(
|
|
2333
|
-
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2334
|
-
{
|
|
2335
|
-
localTracks: {audio: FAKE_AUDIO_TRACK},
|
|
2336
|
-
direction: {
|
|
2337
|
-
audio: 'sendrecv',
|
|
2338
|
-
video: 'sendrecv',
|
|
2339
|
-
screenShareVideo: 'recvonly',
|
|
2340
|
-
},
|
|
2341
|
-
remoteQualityLevel: 'HIGH',
|
|
2342
|
-
}
|
|
2343
|
-
);
|
|
2344
|
-
}));
|
|
2345
|
-
});
|
|
2346
|
-
afterEach(() => {
|
|
2347
|
-
sinon.restore();
|
|
2348
|
-
});
|
|
2182
|
+
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
2349
2183
|
});
|
|
2350
2184
|
});
|
|
2351
2185
|
|
|
@@ -2393,38 +2227,25 @@ describe('plugin-meetings', () => {
|
|
|
2393
2227
|
|
|
2394
2228
|
describe('#updateMedia', () => {
|
|
2395
2229
|
let sandbox;
|
|
2396
|
-
const mockLocalStream = {id: 'mock local stream'};
|
|
2397
|
-
const mockLocalShare = {id: 'mock local share stream'};
|
|
2398
|
-
const FAKE_TRACKS = {
|
|
2399
|
-
audio: {
|
|
2400
|
-
id: 'fake audio track',
|
|
2401
|
-
getSettings: sinon.stub().returns({}),
|
|
2402
|
-
},
|
|
2403
|
-
video: {
|
|
2404
|
-
id: 'fake video track',
|
|
2405
|
-
getSettings: sinon.stub().returns({}),
|
|
2406
|
-
},
|
|
2407
|
-
screenshareVideo: {
|
|
2408
|
-
id: 'fake share track',
|
|
2409
|
-
getSettings: sinon.stub().returns({}),
|
|
2410
|
-
on: sinon.stub(),
|
|
2411
|
-
},
|
|
2412
|
-
};
|
|
2413
2230
|
|
|
2231
|
+
const createFakeLocalTrack = () => ({
|
|
2232
|
+
underlyingTrack: {id: 'fake underlying track'}
|
|
2233
|
+
});
|
|
2414
2234
|
beforeEach(() => {
|
|
2415
2235
|
sandbox = sinon.createSandbox();
|
|
2416
|
-
meeting.
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
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
|
+
}
|
|
2428
2249
|
});
|
|
2429
2250
|
|
|
2430
2251
|
afterEach(() => {
|
|
@@ -2434,52 +2255,28 @@ describe('plugin-meetings', () => {
|
|
|
2434
2255
|
|
|
2435
2256
|
forEach(
|
|
2436
2257
|
[
|
|
2437
|
-
{
|
|
2438
|
-
{
|
|
2439
|
-
{receiveAudio: false, sendAudio: true, enableMultistreamAudio: true},
|
|
2440
|
-
{receiveAudio: false, sendAudio: false, enableMultistreamAudio: false},
|
|
2258
|
+
{audioEnabled: true, enableMultistreamAudio: true},
|
|
2259
|
+
{audioEnabled: false, enableMultistreamAudio: false},
|
|
2441
2260
|
],
|
|
2442
|
-
({
|
|
2443
|
-
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and
|
|
2444
|
-
const mediaSettings = {
|
|
2445
|
-
sendAudio,
|
|
2446
|
-
receiveAudio,
|
|
2447
|
-
sendVideo: true,
|
|
2448
|
-
receiveVideo: true,
|
|
2449
|
-
sendShare: true,
|
|
2450
|
-
receiveShare: true,
|
|
2451
|
-
isSharing: true,
|
|
2452
|
-
};
|
|
2453
|
-
|
|
2261
|
+
({audioEnabled, enableMultistreamAudio}) => {
|
|
2262
|
+
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and audioEnabled: ${audioEnabled}`, async () => {
|
|
2454
2263
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2455
|
-
enableMultistreamAudio: sinon.stub().resolves(
|
|
2264
|
+
enableMultistreamAudio: sinon.stub().resolves({}),
|
|
2456
2265
|
};
|
|
2457
2266
|
meeting.isMultistream = true;
|
|
2458
2267
|
|
|
2459
|
-
|
|
2460
|
-
mediaSettings,
|
|
2461
|
-
});
|
|
2268
|
+
await meeting.updateMedia({audioEnabled});
|
|
2462
2269
|
|
|
2463
2270
|
assert.calledOnceWithExactly(
|
|
2464
2271
|
meeting.mediaProperties.webrtcMediaConnection.enableMultistreamAudio,
|
|
2465
2272
|
enableMultistreamAudio
|
|
2466
2273
|
);
|
|
2467
|
-
assert.
|
|
2274
|
+
assert.calledOnceWithExactly(meeting.audio.enable, meeting, enableMultistreamAudio);
|
|
2468
2275
|
});
|
|
2469
2276
|
}
|
|
2470
2277
|
);
|
|
2471
2278
|
|
|
2472
2279
|
it('should use a queue if currently busy', async () => {
|
|
2473
|
-
const mediaSettings = {
|
|
2474
|
-
sendAudio: true,
|
|
2475
|
-
receiveAudio: true,
|
|
2476
|
-
sendVideo: true,
|
|
2477
|
-
receiveVideo: true,
|
|
2478
|
-
sendShare: true,
|
|
2479
|
-
receiveShare: true,
|
|
2480
|
-
isSharing: true,
|
|
2481
|
-
};
|
|
2482
|
-
|
|
2483
2280
|
sandbox.stub(meeting, 'canUpdateMedia').returns(false);
|
|
2484
2281
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2485
2282
|
update: sinon.stub().resolves({}),
|
|
@@ -2488,11 +2285,7 @@ describe('plugin-meetings', () => {
|
|
|
2488
2285
|
let myPromiseResolved = false;
|
|
2489
2286
|
|
|
2490
2287
|
meeting
|
|
2491
|
-
.updateMedia({
|
|
2492
|
-
localStream: mockLocalStream,
|
|
2493
|
-
localShare: mockLocalShare,
|
|
2494
|
-
mediaSettings,
|
|
2495
|
-
})
|
|
2288
|
+
.updateMedia({audioEnabled: false, videoEnabled: false})
|
|
2496
2289
|
.then(() => {
|
|
2497
2290
|
myPromiseResolved = true;
|
|
2498
2291
|
});
|
|
@@ -2513,13 +2306,13 @@ describe('plugin-meetings', () => {
|
|
|
2513
2306
|
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2514
2307
|
{
|
|
2515
2308
|
localTracks: {
|
|
2516
|
-
audio:
|
|
2517
|
-
video:
|
|
2518
|
-
screenShareVideo:
|
|
2309
|
+
audio: meeting.mediaProperties.audioTrack.underlyingTrack,
|
|
2310
|
+
video: meeting.mediaProperties.videoTrack.underlyingTrack,
|
|
2311
|
+
screenShareVideo: meeting.mediaProperties.shareTrack.underlyingTrack,
|
|
2519
2312
|
},
|
|
2520
2313
|
direction: {
|
|
2521
|
-
audio: '
|
|
2522
|
-
video: '
|
|
2314
|
+
audio: 'inactive',
|
|
2315
|
+
video: 'inactive',
|
|
2523
2316
|
screenShareVideo: 'sendrecv',
|
|
2524
2317
|
},
|
|
2525
2318
|
remoteQualityLevel: 'HIGH',
|
|
@@ -2527,195 +2320,6 @@ describe('plugin-meetings', () => {
|
|
|
2527
2320
|
);
|
|
2528
2321
|
assert.isTrue(myPromiseResolved);
|
|
2529
2322
|
});
|
|
2530
|
-
|
|
2531
|
-
it('should request floor only after roap transaction is completed', async () => {
|
|
2532
|
-
const eventListeners = {};
|
|
2533
|
-
|
|
2534
|
-
meeting.webex.meetings.reachability = {
|
|
2535
|
-
isAnyClusterReachable: sandbox.stub().resolves(true),
|
|
2536
|
-
};
|
|
2537
|
-
|
|
2538
|
-
const fakeMediaConnection = {
|
|
2539
|
-
close: sinon.stub(),
|
|
2540
|
-
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2541
|
-
initiateOffer: sinon.stub().resolves({}),
|
|
2542
|
-
|
|
2543
|
-
// mock the on() method and store all the listeners
|
|
2544
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
2545
|
-
eventListeners[event] = listener;
|
|
2546
|
-
}),
|
|
2547
|
-
|
|
2548
|
-
update: sinon.stub().callsFake(() => {
|
|
2549
|
-
// trigger ROAP_STARTED before update() resolves
|
|
2550
|
-
if (eventListeners[Event.ROAP_STARTED]) {
|
|
2551
|
-
eventListeners[Event.ROAP_STARTED]();
|
|
2552
|
-
} else {
|
|
2553
|
-
throw new Error('ROAP_STARTED listener not registered');
|
|
2554
|
-
}
|
|
2555
|
-
return Promise.resolve();
|
|
2556
|
-
}),
|
|
2557
|
-
};
|
|
2558
|
-
|
|
2559
|
-
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
2560
|
-
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
2561
|
-
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
2562
|
-
|
|
2563
|
-
const requestScreenShareFloorStub = sandbox
|
|
2564
|
-
.stub(meeting, 'requestScreenShareFloor')
|
|
2565
|
-
.resolves({});
|
|
2566
|
-
|
|
2567
|
-
let myPromiseResolved = false;
|
|
2568
|
-
|
|
2569
|
-
meeting.meetingState = 'ACTIVE';
|
|
2570
|
-
await meeting.addMedia({
|
|
2571
|
-
mediaSettings: {},
|
|
2572
|
-
});
|
|
2573
|
-
|
|
2574
|
-
meeting
|
|
2575
|
-
.updateMedia({
|
|
2576
|
-
localShare: mockLocalShare,
|
|
2577
|
-
mediaSettings: {
|
|
2578
|
-
sendShare: true,
|
|
2579
|
-
},
|
|
2580
|
-
})
|
|
2581
|
-
.then(() => {
|
|
2582
|
-
myPromiseResolved = true;
|
|
2583
|
-
});
|
|
2584
|
-
|
|
2585
|
-
await testUtils.flushPromises();
|
|
2586
|
-
|
|
2587
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2588
|
-
assert.isFalse(myPromiseResolved);
|
|
2589
|
-
|
|
2590
|
-
// verify that requestScreenShareFloorStub was not called yet
|
|
2591
|
-
assert.notCalled(requestScreenShareFloorStub);
|
|
2592
|
-
|
|
2593
|
-
eventListeners[Event.ROAP_DONE]();
|
|
2594
|
-
await testUtils.flushPromises();
|
|
2595
|
-
|
|
2596
|
-
// now it should have been called
|
|
2597
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2598
|
-
});
|
|
2599
|
-
});
|
|
2600
|
-
|
|
2601
|
-
describe('#updateShare', () => {
|
|
2602
|
-
const mockLocalShare = {id: 'mock local share stream'};
|
|
2603
|
-
let eventListeners;
|
|
2604
|
-
let fakeMediaConnection;
|
|
2605
|
-
let requestScreenShareFloorStub;
|
|
2606
|
-
|
|
2607
|
-
const FAKE_TRACKS = {
|
|
2608
|
-
screenshareVideo: {
|
|
2609
|
-
id: 'fake share track',
|
|
2610
|
-
getSettings: sinon.stub().returns({}),
|
|
2611
|
-
on: sinon.stub(),
|
|
2612
|
-
},
|
|
2613
|
-
};
|
|
2614
|
-
|
|
2615
|
-
beforeEach(async () => {
|
|
2616
|
-
eventListeners = {};
|
|
2617
|
-
|
|
2618
|
-
sinon.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
|
|
2619
|
-
if (stream === mockLocalShare) {
|
|
2620
|
-
return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
return {audioTrack: null, videoTrack: null};
|
|
2624
|
-
});
|
|
2625
|
-
|
|
2626
|
-
meeting.webex.meetings.reachability = {
|
|
2627
|
-
isAnyClusterReachable: sinon.stub().resolves(true),
|
|
2628
|
-
};
|
|
2629
|
-
|
|
2630
|
-
fakeMediaConnection = {
|
|
2631
|
-
close: sinon.stub(),
|
|
2632
|
-
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
2633
|
-
initiateOffer: sinon.stub().resolves({}),
|
|
2634
|
-
|
|
2635
|
-
// mock the on() method and store all the listeners
|
|
2636
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
2637
|
-
eventListeners[event] = listener;
|
|
2638
|
-
}),
|
|
2639
|
-
|
|
2640
|
-
update: sinon.stub().callsFake(() => {
|
|
2641
|
-
// trigger ROAP_STARTED before update() resolves
|
|
2642
|
-
if (eventListeners[Event.ROAP_STARTED]) {
|
|
2643
|
-
eventListeners[Event.ROAP_STARTED]();
|
|
2644
|
-
} else {
|
|
2645
|
-
throw new Error('ROAP_STARTED listener not registered');
|
|
2646
|
-
}
|
|
2647
|
-
return Promise.resolve();
|
|
2648
|
-
}),
|
|
2649
|
-
};
|
|
2650
|
-
|
|
2651
|
-
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
2652
|
-
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
2653
|
-
Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
|
|
2654
|
-
|
|
2655
|
-
requestScreenShareFloorStub = sinon.stub(meeting, 'requestScreenShareFloor').resolves({});
|
|
2656
|
-
|
|
2657
|
-
meeting.meetingState = 'ACTIVE';
|
|
2658
|
-
await meeting.addMedia({
|
|
2659
|
-
mediaSettings: {},
|
|
2660
|
-
});
|
|
2661
|
-
});
|
|
2662
|
-
|
|
2663
|
-
afterEach(() => {
|
|
2664
|
-
sinon.restore();
|
|
2665
|
-
});
|
|
2666
|
-
|
|
2667
|
-
it('when starting share, it should request floor only after roap transaction is completed', async () => {
|
|
2668
|
-
let myPromiseResolved = false;
|
|
2669
|
-
|
|
2670
|
-
meeting
|
|
2671
|
-
.updateShare({
|
|
2672
|
-
sendShare: true,
|
|
2673
|
-
receiveShare: true,
|
|
2674
|
-
stream: mockLocalShare,
|
|
2675
|
-
})
|
|
2676
|
-
.then(() => {
|
|
2677
|
-
myPromiseResolved = true;
|
|
2678
|
-
});
|
|
2679
|
-
|
|
2680
|
-
await testUtils.flushPromises();
|
|
2681
|
-
|
|
2682
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2683
|
-
assert.isFalse(myPromiseResolved);
|
|
2684
|
-
|
|
2685
|
-
// verify that requestScreenShareFloorStub was not called yet
|
|
2686
|
-
assert.notCalled(requestScreenShareFloorStub);
|
|
2687
|
-
|
|
2688
|
-
eventListeners[Event.ROAP_DONE]();
|
|
2689
|
-
await testUtils.flushPromises();
|
|
2690
|
-
|
|
2691
|
-
// now it should have been called
|
|
2692
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2693
|
-
});
|
|
2694
|
-
|
|
2695
|
-
it('when changing screen share stream and no roap transaction happening, it requests floor immediately', async () => {
|
|
2696
|
-
let myPromiseResolved = false;
|
|
2697
|
-
|
|
2698
|
-
// simulate a case when no roap transaction is triggered by update
|
|
2699
|
-
meeting.mediaProperties.webrtcMediaConnection.update = sinon
|
|
2700
|
-
.stub()
|
|
2701
|
-
.resolves({});
|
|
2702
|
-
|
|
2703
|
-
meeting
|
|
2704
|
-
.updateShare({
|
|
2705
|
-
sendShare: true,
|
|
2706
|
-
receiveShare: true,
|
|
2707
|
-
stream: mockLocalShare,
|
|
2708
|
-
})
|
|
2709
|
-
.then(() => {
|
|
2710
|
-
myPromiseResolved = true;
|
|
2711
|
-
});
|
|
2712
|
-
|
|
2713
|
-
await testUtils.flushPromises();
|
|
2714
|
-
|
|
2715
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2716
|
-
assert.calledOnce(requestScreenShareFloorStub);
|
|
2717
|
-
assert.isTrue(myPromiseResolved);
|
|
2718
|
-
});
|
|
2719
2323
|
});
|
|
2720
2324
|
|
|
2721
2325
|
describe('#changeVideoLayout', () => {
|
|
@@ -2730,8 +2334,6 @@ describe('plugin-meetings', () => {
|
|
|
2730
2334
|
sendShare: false,
|
|
2731
2335
|
receiveVideo: true,
|
|
2732
2336
|
};
|
|
2733
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2734
|
-
meeting.updateVideo = sinon.stub().returns(Promise.resolve());
|
|
2735
2337
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2736
2338
|
meeting.mediaProperties.remoteVideoTrack = sinon
|
|
2737
2339
|
.stub()
|
|
@@ -2968,76 +2570,12 @@ describe('plugin-meetings', () => {
|
|
|
2968
2570
|
});
|
|
2969
2571
|
});
|
|
2970
2572
|
|
|
2971
|
-
describe('#setLocalVideoQuality', () => {
|
|
2972
|
-
let mediaDirection;
|
|
2973
|
-
|
|
2974
|
-
const fakeTrack = {getSettings: () => ({height: 720})};
|
|
2975
|
-
const USER_AGENT_CHROME_MAC =
|
|
2976
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
|
|
2977
|
-
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36';
|
|
2978
|
-
|
|
2979
|
-
beforeEach(() => {
|
|
2980
|
-
mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
2981
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2982
|
-
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2983
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2984
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2985
|
-
meeting.updateVideo = sinon.stub().resolves();
|
|
2986
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({videoTrack: fakeTrack});
|
|
2987
|
-
});
|
|
2988
|
-
|
|
2989
|
-
it('should have #setLocalVideoQuality', () => {
|
|
2990
|
-
assert.exists(meeting.setLocalVideoQuality);
|
|
2991
|
-
});
|
|
2992
|
-
|
|
2993
|
-
it('should call getMediaStreams with the proper level', () =>
|
|
2994
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2995
|
-
delete mediaDirection.receiveVideo;
|
|
2996
|
-
assert.calledWith(
|
|
2997
|
-
meeting.getMediaStreams,
|
|
2998
|
-
mediaDirection,
|
|
2999
|
-
CONSTANTS.VIDEO_RESOLUTIONS[CONSTANTS.QUALITY_LEVELS.LOW]
|
|
3000
|
-
);
|
|
3001
|
-
}));
|
|
3002
|
-
|
|
3003
|
-
it('when browser is chrome then it should stop previous video track', () => {
|
|
3004
|
-
meeting.mediaProperties.videoTrack = fakeTrack;
|
|
3005
|
-
assert.equal(BrowserDetection(USER_AGENT_CHROME_MAC).getBrowserName(), 'Chrome');
|
|
3006
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3007
|
-
assert.calledWith(Media.stopTracks, fakeTrack);
|
|
3008
|
-
});
|
|
3009
|
-
});
|
|
3010
|
-
|
|
3011
|
-
it('should set mediaProperty with the proper level', () =>
|
|
3012
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3013
|
-
assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
3014
|
-
}));
|
|
3015
|
-
|
|
3016
|
-
it('when device does not support 1080p then it should set localQualityLevel with highest possible resolution', () => {
|
|
3017
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS['1080p']).then(() => {
|
|
3018
|
-
assert.equal(
|
|
3019
|
-
meeting.mediaProperties.localQualityLevel,
|
|
3020
|
-
CONSTANTS.QUALITY_LEVELS['720p']
|
|
3021
|
-
);
|
|
3022
|
-
});
|
|
3023
|
-
});
|
|
3024
|
-
|
|
3025
|
-
it('should error if set to a invalid level', () => {
|
|
3026
|
-
assert.isRejected(meeting.setLocalVideoQuality('invalid'));
|
|
3027
|
-
});
|
|
3028
|
-
|
|
3029
|
-
it('should error if sendVideo is set to false', () => {
|
|
3030
|
-
meeting.mediaProperties.mediaDirection = {sendVideo: false};
|
|
3031
|
-
assert.isRejected(meeting.setLocalVideoQuality('LOW'));
|
|
3032
|
-
});
|
|
3033
|
-
});
|
|
3034
|
-
|
|
3035
2573
|
describe('#setRemoteQualityLevel', () => {
|
|
3036
2574
|
let mediaDirection;
|
|
3037
2575
|
|
|
3038
2576
|
beforeEach(() => {
|
|
3039
2577
|
mediaDirection = {receiveAudio: true, receiveVideo: true, receiveShare: false};
|
|
3040
|
-
meeting.
|
|
2578
|
+
meeting.updateTranscodedMediaConnection = sinon.stub().returns(Promise.resolve());
|
|
3041
2579
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
3042
2580
|
});
|
|
3043
2581
|
|
|
@@ -3050,9 +2588,9 @@ describe('plugin-meetings', () => {
|
|
|
3050
2588
|
assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
3051
2589
|
}));
|
|
3052
2590
|
|
|
3053
|
-
it('should call
|
|
2591
|
+
it('should call Meeting.updateTranscodedMediaConnection()', () =>
|
|
3054
2592
|
meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
3055
|
-
assert.calledOnce(meeting.
|
|
2593
|
+
assert.calledOnce(meeting.updateTranscodedMediaConnection);
|
|
3056
2594
|
}));
|
|
3057
2595
|
|
|
3058
2596
|
it('should error if set to a invalid level', () => {
|
|
@@ -3726,16 +3264,12 @@ describe('plugin-meetings', () => {
|
|
|
3726
3264
|
.stub()
|
|
3727
3265
|
.returns(Promise.resolve({body: 'test'}));
|
|
3728
3266
|
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
3729
|
-
meeting.
|
|
3730
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
3267
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
3731
3268
|
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
3732
3269
|
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
3733
3270
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
3734
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
3735
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
3736
3271
|
meeting.unsetRemoteTracks = sinon.stub();
|
|
3737
3272
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
3738
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
3739
3273
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
3740
3274
|
meeting.logger.error = sinon.stub().returns(true);
|
|
3741
3275
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
@@ -3754,12 +3288,9 @@ describe('plugin-meetings', () => {
|
|
|
3754
3288
|
assert.exists(endMeetingForAll.then);
|
|
3755
3289
|
await endMeetingForAll;
|
|
3756
3290
|
assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
|
|
3757
|
-
assert.calledOnce(meeting?.
|
|
3758
|
-
assert.calledOnce(meeting?.closeLocalShare);
|
|
3291
|
+
assert.calledOnce(meeting?.cleanupLocalTracks);
|
|
3759
3292
|
assert.calledOnce(meeting?.closeRemoteTracks);
|
|
3760
3293
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
3761
|
-
assert.calledOnce(meeting?.unsetLocalVideoTrack);
|
|
3762
|
-
assert.calledOnce(meeting?.unsetLocalShareTrack);
|
|
3763
3294
|
assert.calledOnce(meeting?.unsetRemoteTracks);
|
|
3764
3295
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
3765
3296
|
});
|
|
@@ -3770,11 +3301,9 @@ describe('plugin-meetings', () => {
|
|
|
3770
3301
|
|
|
3771
3302
|
beforeEach(() => {
|
|
3772
3303
|
sandbox = sinon.createSandbox();
|
|
3773
|
-
sandbox.stub(meeting, '
|
|
3774
|
-
sandbox.stub(meeting, 'closeLocalShare');
|
|
3304
|
+
sandbox.stub(meeting, 'cleanupLocalTracks');
|
|
3775
3305
|
|
|
3776
3306
|
sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
|
|
3777
|
-
sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
|
|
3778
3307
|
|
|
3779
3308
|
sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
|
|
3780
3309
|
sandbox
|
|
@@ -3847,14 +3376,12 @@ describe('plugin-meetings', () => {
|
|
|
3847
3376
|
|
|
3848
3377
|
// beacuse we are calling callback so we need to wait
|
|
3849
3378
|
|
|
3850
|
-
assert.called(meeting.
|
|
3851
|
-
assert.called(meeting.closeLocalShare);
|
|
3379
|
+
assert.called(meeting.cleanupLocalTracks);
|
|
3852
3380
|
|
|
3853
3381
|
// give queued Promise callbacks a chance to run
|
|
3854
3382
|
await Promise.resolve();
|
|
3855
3383
|
|
|
3856
3384
|
assert.called(meeting.mediaProperties.setMediaDirection);
|
|
3857
|
-
assert.called(meeting.mediaProperties.unsetMediaTracks);
|
|
3858
3385
|
|
|
3859
3386
|
assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
|
|
3860
3387
|
mediaDirection: {
|
|
@@ -4003,8 +3530,8 @@ describe('plugin-meetings', () => {
|
|
|
4003
3530
|
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
4004
3531
|
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
4005
3532
|
meeting.mediaProperties.mediaDirection = {
|
|
4006
|
-
sendAudio:
|
|
4007
|
-
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',
|
|
4008
3535
|
sendShare: false,
|
|
4009
3536
|
};
|
|
4010
3537
|
meeting.isMultistream = true;
|
|
@@ -4012,6 +3539,8 @@ describe('plugin-meetings', () => {
|
|
|
4012
3539
|
publishTrack: sinon.stub().resolves({}),
|
|
4013
3540
|
unpublishTrack: sinon.stub().resolves({}),
|
|
4014
3541
|
};
|
|
3542
|
+
meeting.audio = { handleLocalTrackChange: sinon.stub()};
|
|
3543
|
+
meeting.video = { handleLocalTrackChange: sinon.stub()};
|
|
4015
3544
|
|
|
4016
3545
|
const createFakeLocalTrack = (originalTrack) => ({
|
|
4017
3546
|
on: sinon.stub(),
|
|
@@ -4052,33 +3581,25 @@ describe('plugin-meetings', () => {
|
|
|
4052
3581
|
});
|
|
4053
3582
|
|
|
4054
3583
|
const checkAudioPublished = (track) => {
|
|
4055
|
-
assert.
|
|
4056
|
-
createMuteStateStub,
|
|
4057
|
-
'audio',
|
|
4058
|
-
meeting,
|
|
4059
|
-
meeting.mediaProperties.mediaDirection
|
|
4060
|
-
);
|
|
3584
|
+
assert.calledOnceWithExactly(meeting.audio.handleLocalTrackChange, meeting);
|
|
4061
3585
|
assert.calledWith(
|
|
4062
3586
|
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
4063
3587
|
track
|
|
4064
3588
|
);
|
|
4065
3589
|
assert.equal(meeting.mediaProperties.audioTrack, track);
|
|
4066
|
-
|
|
3590
|
+
// check that sendAudio hasn't been touched
|
|
3591
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
4067
3592
|
};
|
|
4068
3593
|
|
|
4069
3594
|
const checkVideoPublished = (track) => {
|
|
4070
|
-
assert.
|
|
4071
|
-
createMuteStateStub,
|
|
4072
|
-
'video',
|
|
4073
|
-
meeting,
|
|
4074
|
-
meeting.mediaProperties.mediaDirection
|
|
4075
|
-
);
|
|
3595
|
+
assert.calledOnceWithExactly(meeting.video.handleLocalTrackChange, meeting);
|
|
4076
3596
|
assert.calledWith(
|
|
4077
3597
|
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
4078
3598
|
track
|
|
4079
3599
|
);
|
|
4080
3600
|
assert.equal(meeting.mediaProperties.videoTrack, track);
|
|
4081
|
-
|
|
3601
|
+
// check that sendVideo hasn't been touched
|
|
3602
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
4082
3603
|
};
|
|
4083
3604
|
|
|
4084
3605
|
const checkScreenShareVideoPublished = (track) => {
|
|
@@ -4099,18 +3620,16 @@ describe('plugin-meetings', () => {
|
|
|
4099
3620
|
checkScreenShareVideoPublished(videoShareTrack);
|
|
4100
3621
|
});
|
|
4101
3622
|
|
|
4102
|
-
it('
|
|
3623
|
+
it('updates MuteState instance and publishes the track for main audio', async () => {
|
|
4103
3624
|
await meeting.publishTracks({microphone: audioTrack});
|
|
4104
3625
|
|
|
4105
|
-
assert.calledOnce(createMuteStateStub);
|
|
4106
3626
|
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4107
3627
|
checkAudioPublished(audioTrack);
|
|
4108
3628
|
});
|
|
4109
3629
|
|
|
4110
|
-
it('
|
|
3630
|
+
it('updates MuteState instance and publishes the track for main video', async () => {
|
|
4111
3631
|
await meeting.publishTracks({camera: videoTrack});
|
|
4112
3632
|
|
|
4113
|
-
assert.calledOnce(createMuteStateStub);
|
|
4114
3633
|
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4115
3634
|
checkVideoPublished(videoTrack);
|
|
4116
3635
|
});
|
|
@@ -4124,7 +3643,6 @@ describe('plugin-meetings', () => {
|
|
|
4124
3643
|
},
|
|
4125
3644
|
});
|
|
4126
3645
|
|
|
4127
|
-
assert.calledTwice(createMuteStateStub);
|
|
4128
3646
|
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
4129
3647
|
checkAudioPublished(audioTrack);
|
|
4130
3648
|
checkVideoPublished(videoTrack);
|
|
@@ -4156,7 +3674,7 @@ describe('plugin-meetings', () => {
|
|
|
4156
3674
|
);
|
|
4157
3675
|
|
|
4158
3676
|
assert.equal(meeting.mediaProperties.audioTrack, null);
|
|
4159
|
-
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio,
|
|
3677
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
4160
3678
|
};
|
|
4161
3679
|
|
|
4162
3680
|
const checkVideoUnpublished = () => {
|
|
@@ -4166,7 +3684,7 @@ describe('plugin-meetings', () => {
|
|
|
4166
3684
|
);
|
|
4167
3685
|
|
|
4168
3686
|
assert.equal(meeting.mediaProperties.videoTrack, null);
|
|
4169
|
-
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo,
|
|
3687
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
4170
3688
|
};
|
|
4171
3689
|
|
|
4172
3690
|
const checkScreenShareVideoUnpublished = () => {
|
|
@@ -4398,82 +3916,6 @@ describe('plugin-meetings', () => {
|
|
|
4398
3916
|
);
|
|
4399
3917
|
});
|
|
4400
3918
|
});
|
|
4401
|
-
describe('#closeLocalShare', () => {
|
|
4402
|
-
it('should stop the stream, and trigger a media:stopped event when the local share stream stops', async () => {
|
|
4403
|
-
await meeting.closeLocalShare();
|
|
4404
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4405
|
-
|
|
4406
|
-
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:stopped');
|
|
4407
|
-
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'localShare'});
|
|
4408
|
-
});
|
|
4409
|
-
});
|
|
4410
|
-
describe('#closeLocalStream', () => {
|
|
4411
|
-
it('should stop the stream, and trigger a media:stopped event when the local stream stops', async () => {
|
|
4412
|
-
await meeting.closeLocalStream();
|
|
4413
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4414
|
-
assert.calledWith(
|
|
4415
|
-
TriggerProxy.trigger,
|
|
4416
|
-
sinon.match.instanceOf(Meeting),
|
|
4417
|
-
{file: 'meeting/index', function: 'closeLocalStream'},
|
|
4418
|
-
'media:stopped',
|
|
4419
|
-
{type: 'local'}
|
|
4420
|
-
);
|
|
4421
|
-
});
|
|
4422
|
-
});
|
|
4423
|
-
describe('#setLocalTracks', () => {
|
|
4424
|
-
it('stores the current video device as the preferred video device', () => {
|
|
4425
|
-
const videoDevice = 'video1';
|
|
4426
|
-
const fakeTrack = {getSettings: () => ({deviceId: videoDevice})};
|
|
4427
|
-
const fakeStream = 'stream1';
|
|
4428
|
-
|
|
4429
|
-
sandbox.stub(MeetingUtil, 'getTrack').returns({audioTrack: null, videoTrack: fakeTrack});
|
|
4430
|
-
sandbox.stub(meeting.mediaProperties, 'setMediaSettings');
|
|
4431
|
-
sandbox.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
4432
|
-
|
|
4433
|
-
meeting.setLocalTracks(fakeStream);
|
|
4434
|
-
|
|
4435
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, videoDevice);
|
|
4436
|
-
});
|
|
4437
|
-
});
|
|
4438
|
-
describe('#setLocalShareTrack', () => {
|
|
4439
|
-
it('should trigger a media:ready event with local share stream', () => {
|
|
4440
|
-
const track = {
|
|
4441
|
-
getSettings: sinon.stub().returns({
|
|
4442
|
-
aspectRatio: '1.7',
|
|
4443
|
-
frameRate: 30,
|
|
4444
|
-
height: 1980,
|
|
4445
|
-
width: 1080,
|
|
4446
|
-
displaySurface: true,
|
|
4447
|
-
cursor: true,
|
|
4448
|
-
}),
|
|
4449
|
-
};
|
|
4450
|
-
|
|
4451
|
-
const listeners = {};
|
|
4452
|
-
const fakeLocalDisplayTrack = {
|
|
4453
|
-
on: sinon.stub().callsFake((event, listener) => {
|
|
4454
|
-
listeners[event] = listener;
|
|
4455
|
-
}),
|
|
4456
|
-
};
|
|
4457
|
-
sinon.stub(InternalMediaCoreModule, 'LocalDisplayTrack').returns(fakeLocalDisplayTrack);
|
|
4458
|
-
|
|
4459
|
-
meeting.mediaProperties.setLocalShareTrack = sinon.stub().returns(true);
|
|
4460
|
-
meeting.stopShare = sinon.stub().resolves(true);
|
|
4461
|
-
meeting.mediaProperties.mediaDirection = {};
|
|
4462
|
-
meeting.setLocalShareTrack(track);
|
|
4463
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
4464
|
-
assert.calledWith(
|
|
4465
|
-
TriggerProxy.trigger,
|
|
4466
|
-
sinon.match.instanceOf(Meeting),
|
|
4467
|
-
{file: 'meeting/index', function: 'setLocalShareTrack'},
|
|
4468
|
-
'media:ready'
|
|
4469
|
-
);
|
|
4470
|
-
assert.calledOnce(meeting.mediaProperties.setLocalShareTrack);
|
|
4471
|
-
assert.equal(meeting.mediaProperties.localStream, undefined);
|
|
4472
|
-
assert.isNotNull(listeners[LocalTrackEvents.Ended]);
|
|
4473
|
-
listeners[LocalTrackEvents.Ended]();
|
|
4474
|
-
assert.calledOnce(meeting.stopShare);
|
|
4475
|
-
});
|
|
4476
|
-
});
|
|
4477
3919
|
describe('#setupMediaConnectionListeners', () => {
|
|
4478
3920
|
let eventListeners;
|
|
4479
3921
|
|
|
@@ -5381,20 +4823,6 @@ describe('plugin-meetings', () => {
|
|
|
5381
4823
|
assert.calledOnce(meeting.mediaProperties.unsetRemoteTracks);
|
|
5382
4824
|
});
|
|
5383
4825
|
});
|
|
5384
|
-
describe('#unsetLocalVideoTrack', () => {
|
|
5385
|
-
it('should unset the local stream and return null', () => {
|
|
5386
|
-
meeting.mediaProperties.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
5387
|
-
meeting.unsetLocalVideoTrack();
|
|
5388
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalVideoTrack);
|
|
5389
|
-
});
|
|
5390
|
-
});
|
|
5391
|
-
describe('#unsetLocalShareTrack', () => {
|
|
5392
|
-
it('should unset the local share stream and return null', () => {
|
|
5393
|
-
meeting.mediaProperties.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
5394
|
-
meeting.unsetLocalShareTrack();
|
|
5395
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalShareTrack);
|
|
5396
|
-
});
|
|
5397
|
-
});
|
|
5398
4826
|
// TODO: remove
|
|
5399
4827
|
describe('#setMercuryListener', () => {
|
|
5400
4828
|
it('should listen to mercury events', () => {
|
|
@@ -6061,14 +5489,53 @@ describe('plugin-meetings', () => {
|
|
|
6061
5489
|
});
|
|
6062
5490
|
});
|
|
6063
5491
|
describe('share scenarios', () => {
|
|
5492
|
+
|
|
5493
|
+
describe('triggerAnnotationInfoEvent', () => {
|
|
5494
|
+
it('check triggerAnnotationInfoEvent event', () => {
|
|
5495
|
+
|
|
5496
|
+
TriggerProxy.trigger.reset();
|
|
5497
|
+
const annotationInfo = {version: '1', policy: 'Approval'};
|
|
5498
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{});
|
|
5499
|
+
|
|
5500
|
+
assert.calledWith(
|
|
5501
|
+
TriggerProxy.trigger,
|
|
5502
|
+
meeting,
|
|
5503
|
+
{
|
|
5504
|
+
file: 'meeting/index',
|
|
5505
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5506
|
+
},
|
|
5507
|
+
'meeting:updateAnnotationInfo',
|
|
5508
|
+
annotationInfo
|
|
5509
|
+
);
|
|
5510
|
+
|
|
5511
|
+
TriggerProxy.trigger.reset();
|
|
5512
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{annotation:annotationInfo});
|
|
5513
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5514
|
+
|
|
5515
|
+
TriggerProxy.trigger.reset();
|
|
5516
|
+
const annotationInfoUpdated = {version: '1', policy: 'AnnotationNotAllowed'};
|
|
5517
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfoUpdated},{annotation:annotationInfo});
|
|
5518
|
+
assert.calledWith(
|
|
5519
|
+
TriggerProxy.trigger,
|
|
5520
|
+
meeting,
|
|
5521
|
+
{
|
|
5522
|
+
file: 'meeting/index',
|
|
5523
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5524
|
+
},
|
|
5525
|
+
'meeting:updateAnnotationInfo',
|
|
5526
|
+
annotationInfoUpdated
|
|
5527
|
+
);
|
|
5528
|
+
|
|
5529
|
+
TriggerProxy.trigger.reset();
|
|
5530
|
+
meeting.triggerAnnotationInfoEvent(null,{annotation:annotationInfoUpdated});
|
|
5531
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5532
|
+
|
|
5533
|
+
});
|
|
5534
|
+
});
|
|
5535
|
+
|
|
6064
5536
|
describe('setUpLocusMediaSharesListener', () => {
|
|
6065
5537
|
beforeEach(() => {
|
|
6066
5538
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
6067
|
-
sinon.stub(meeting, 'updateShare').returns(Promise.resolve());
|
|
6068
|
-
});
|
|
6069
|
-
|
|
6070
|
-
afterEach(() => {
|
|
6071
|
-
meeting.updateShare.restore();
|
|
6072
5539
|
});
|
|
6073
5540
|
|
|
6074
5541
|
const USER_IDS = {
|
|
@@ -6756,29 +6223,6 @@ describe('plugin-meetings', () => {
|
|
|
6756
6223
|
payloadTestHelper([data1, data2, data3]);
|
|
6757
6224
|
});
|
|
6758
6225
|
});
|
|
6759
|
-
|
|
6760
|
-
describe('annotation policy', () => {
|
|
6761
|
-
|
|
6762
|
-
it('Scenario #1: blank annotation', () => {
|
|
6763
|
-
const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
|
|
6764
|
-
const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
|
|
6765
|
-
const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
|
|
6766
|
-
const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
|
|
6767
|
-
|
|
6768
|
-
payloadTestHelper([data1, data2, data3, data4]);
|
|
6769
|
-
});
|
|
6770
|
-
|
|
6771
|
-
it('Scenario #2: annotation', () => {
|
|
6772
|
-
const annotationInfo = {version: '1', policy: 'Approval'};
|
|
6773
|
-
const data1 = generateData(blankPayload, true, true, USER_IDS.ME, annotationInfo);
|
|
6774
|
-
const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
|
|
6775
|
-
const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
|
|
6776
|
-
const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
|
|
6777
|
-
|
|
6778
|
-
payloadTestHelper([data1, data2, data3, data4]);
|
|
6779
|
-
});
|
|
6780
|
-
});
|
|
6781
|
-
|
|
6782
6226
|
describe('Desktop A --> Desktop B', () => {
|
|
6783
6227
|
it('Scenario #1: you share desktop A and then share desktop B', () => {
|
|
6784
6228
|
const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
|