@webex/plugin-meetings 2.13.0 → 2.14.0

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.
@@ -0,0 +1,292 @@
1
+ /* eslint-disable camelcase */
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import sinon from 'sinon';
4
+ import MockWebex from '@webex/test-helper-mock-webex';
5
+
6
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
7
+ import {BNR_STATUS} from '@webex/plugin-meetings/src/constants';
8
+ import Meeting from '@webex/plugin-meetings/src/meeting';
9
+ import Meetings from '@webex/plugin-meetings';
10
+ import Metrics from '@webex/plugin-meetings/src/metrics';
11
+ import MediaUtil from '@webex/plugin-meetings/src/media/util';
12
+ import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
13
+ import createEffectsState from '@webex/plugin-meetings/src/meeting/effectsState';
14
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
15
+ import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
16
+
17
+ describe('plugin-meetings', () => {
18
+ const logger = {
19
+ info: () => {},
20
+ log: () => {},
21
+ error: () => {},
22
+ warn: () => {},
23
+ trace: () => {},
24
+ debug: () => {}
25
+ };
26
+
27
+ beforeEach(() => {
28
+ sinon.stub(Metrics, 'sendBehavioralMetric');
29
+ });
30
+ afterEach(() => {
31
+ sinon.restore();
32
+ });
33
+
34
+ Object.defineProperty(global.window.navigator, 'mediaDevices', {
35
+ writable: true,
36
+ value: {
37
+ getSupportedConstraints: sinon.stub().returns({
38
+ sampleRate: true
39
+ })
40
+ },
41
+ });
42
+ LoggerConfig.set({verboseEvents: true, enable: false});
43
+ LoggerProxy.set(logger);
44
+
45
+ let webex;
46
+ let meeting;
47
+ let uuid1;
48
+
49
+ const fakeMediaTrack = () => ({
50
+ id: Date.now().toString(),
51
+ stop: () => {},
52
+ readyState: 'live',
53
+ enabled: true,
54
+ getSettings: () => ({
55
+ sampleRate: 48000
56
+ })
57
+ });
58
+
59
+ class FakeMediaStream {
60
+ constructor(tracks) {
61
+ this.active = false;
62
+ this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
63
+ this.tracks = tracks;
64
+ }
65
+
66
+ addTrack = () => undefined;
67
+
68
+ getAudioTracks = () => this.tracks;
69
+ }
70
+
71
+ class FakeAudioContext {
72
+ constructor() {
73
+ this.state = 'running';
74
+ this.baseLatency = 0.005333333333333333;
75
+ this.currentTime = 2.7946666666666666;
76
+ this.sampleRate = 48000;
77
+ this.audioWorklet = {
78
+ addModule: async () => undefined,
79
+ };
80
+ }
81
+
82
+ onstatechange = null;
83
+
84
+ createMediaStreamSource() {
85
+ return {
86
+ connect: () => undefined,
87
+ mediaStream: {
88
+ getAudioTracks() {
89
+ // eslint-disable-next-line no-undef
90
+ return [new MediaStreamTrack()];
91
+ },
92
+ },
93
+ };
94
+ }
95
+
96
+ createMediaStreamDestination() {
97
+ return {
98
+ stream: {
99
+ getAudioTracks() {
100
+ // eslint-disable-next-line no-undef
101
+ return [new MediaStreamTrack()];
102
+ },
103
+ },
104
+ };
105
+ }
106
+ }
107
+
108
+ class FakeAudioWorkletNode {
109
+ constructor() {
110
+ this.port = {
111
+ postMessage: () => undefined,
112
+ };
113
+ }
114
+
115
+ connect() {
116
+ /* placeholder method */
117
+ }
118
+ }
119
+
120
+ class FakeMediaStreamTrack {
121
+ constructor() {
122
+ this.kind = 'audio';
123
+ this.enabled = true;
124
+ this.label = 'Default - MacBook Pro Microphone (Built-in)';
125
+ this.muted = false;
126
+ this.readyState = 'live';
127
+ this.contentHint = '';
128
+ }
129
+
130
+ getSettings() {
131
+ return {
132
+ sampleRate: 48000
133
+ };
134
+ }
135
+ }
136
+ Object.defineProperty(global, 'MediaStream', {
137
+ writable: true,
138
+ value: FakeMediaStream,
139
+ });
140
+
141
+ Object.defineProperty(global, 'AudioContext', {
142
+ writable: true,
143
+ value: FakeAudioContext,
144
+ });
145
+
146
+ Object.defineProperty(global, 'AudioWorkletNode', {
147
+ writable: true,
148
+ value: FakeAudioWorkletNode,
149
+ });
150
+
151
+ Object.defineProperty(global, 'MediaStreamTrack', {
152
+ writable: true,
153
+ value: FakeMediaStreamTrack,
154
+ });
155
+
156
+ let effects;
157
+
158
+ beforeEach(() => {
159
+ webex = new MockWebex({
160
+ children: {
161
+ meetings: Meetings
162
+ }
163
+ });
164
+ MediaUtil.createPeerConnection = sinon.stub().returns({});
165
+ meeting = new Meeting(
166
+ {
167
+ userId: uuid1
168
+ },
169
+ {
170
+ parent: webex
171
+ }
172
+ );
173
+
174
+ effects = createEffectsState('BNR');
175
+ meeting.canUpdateMedia = sinon.stub().returns(true);
176
+ MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
177
+ MeetingUtil.updateTransceiver = sinon.stub();
178
+
179
+ meeting.addMedia = sinon.stub().returns(Promise.resolve());
180
+ meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
181
+ sinon.replace(meeting, 'addMedia', () => {
182
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
183
+ sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
184
+ receiveAudio: true
185
+ });
186
+ });
187
+ });
188
+
189
+ describe('bnr effect library', () => {
190
+ beforeEach(async () => {
191
+ await meeting.getMediaStreams();
192
+ await meeting.addMedia();
193
+ });
194
+ describe('#enableBNR', () => {
195
+ it('should have #enableBnr', () => {
196
+ assert.exists(effects.enableBNR);
197
+ });
198
+
199
+ it('does bnr effect enable on audio track', async () => {
200
+ assert.isTrue(await effects.handleClientRequest(true, meeting));
201
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
202
+
203
+ assert(Metrics.sendBehavioralMetric.calledOnce);
204
+ assert.calledWith(
205
+ Metrics.sendBehavioralMetric,
206
+ BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
207
+ );
208
+ });
209
+
210
+ it('does resolve request if bnr is already enabled', async () => {
211
+ effects.state.bnr.enabled = BNR_STATUS.ENABLED;
212
+ assert.isTrue(await effects.handleClientRequest(true, meeting));
213
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
214
+ });
215
+
216
+ it('if called twice, does bnr effect enable on audio track for the first request and resolves second', async () => {
217
+ Promise.all([effects.handleClientRequest(true, meeting), effects.handleClientRequest(true, meeting)])
218
+ .then((resolveFirst, resolveSecond) => {
219
+ assert.isTrue(resolveFirst);
220
+ assert.isTrue(resolveSecond);
221
+ assert.calledOnce(MediaUtil.createMediaStream);
222
+ });
223
+ });
224
+
225
+ it('should throw error for inappropriate sample rate and send error metrics', async () => {
226
+ const fakeMediaTrack1 = () => ({
227
+ id: Date.now().toString(),
228
+ stop: () => {},
229
+ readyState: 'live',
230
+ getSettings: () => ({
231
+ sampleRate: 49000
232
+ })
233
+ });
234
+
235
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack1());
236
+
237
+ // eslint-disable-next-line no-undef
238
+ MediaUtil.createMediaStream = sinon.stub().returns(new MediaStream([fakeMediaTrack1()]));
239
+ try {
240
+ await effects.handleClientRequest(true, meeting);
241
+ }
242
+ catch (err) {
243
+ assert(Metrics.sendBehavioralMetric.calledOnce);
244
+ assert.calledWith(
245
+ Metrics.sendBehavioralMetric,
246
+ BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE, {
247
+ reason: err.message,
248
+ stack: err.stack
249
+ }
250
+ );
251
+ assert.equal(err.message, 'Sample rate of 49000 is not supported.');
252
+ }
253
+ });
254
+ });
255
+
256
+ describe('#disableBNR', () => {
257
+ beforeEach(() => {
258
+ effects.state.bnr.enabled = BNR_STATUS.ENABLED;
259
+ });
260
+ it('should have #disableBnr', () => {
261
+ assert.exists(effects.disableBNR);
262
+ });
263
+
264
+ it('does bnr disable on audio track', async () => {
265
+ assert.isTrue(await effects.handleClientRequest(false, meeting));
266
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.NOT_ENABLED);
267
+
268
+ assert(Metrics.sendBehavioralMetric.calledOnce);
269
+ assert.calledWith(
270
+ Metrics.sendBehavioralMetric,
271
+ BEHAVIORAL_METRICS.DISABLE_BNR_SUCCESS,
272
+ );
273
+ });
274
+
275
+ it('reject request for disable bnr if not enabled', async () => {
276
+ try {
277
+ await effects.handleClientRequest(false, meeting);
278
+ }
279
+ catch (e) {
280
+ assert.equal(e.message, 'Can not disable as BNR is not enabled');
281
+ assert.equal(effects.state.bnr.enabled, BNR_STATUS.ENABLED);
282
+
283
+ assert(Metrics.sendBehavioralMetric.calledOnce);
284
+ assert.calledWith(
285
+ Metrics.sendBehavioralMetric,
286
+ BEHAVIORAL_METRICS.DISABLE_BNR_FAILURE,
287
+ );
288
+ }
289
+ });
290
+ });
291
+ });
292
+ });
@@ -466,108 +466,7 @@ describe('plugin-meetings', () => {
466
466
  })
467
467
  });
468
468
 
469
- class FakeMediaStream {
470
- constructor(tracks) {
471
- this.active = false;
472
- this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
473
- this.tracks = tracks;
474
- }
475
-
476
- addTrack = () => undefined;
477
-
478
- getAudioTracks = () => this.tracks;
479
- }
480
-
481
- class FakeAudioContext {
482
- constructor() {
483
- this.state = 'running';
484
- this.baseLatency = 0.005333333333333333;
485
- this.currentTime = 2.7946666666666666;
486
- this.sampleRate = 48000;
487
- this.audioWorklet = {
488
- addModule: async () => undefined,
489
- };
490
- }
491
-
492
- onstatechange = null;
493
-
494
- createMediaStreamSource() {
495
- return {
496
- connect: () => undefined,
497
- mediaStream: {
498
- getAudioTracks() {
499
- // eslint-disable-next-line no-undef
500
- return [new MediaStreamTrack()];
501
- },
502
- },
503
- };
504
- }
505
-
506
- createMediaStreamDestination() {
507
- return {
508
- stream: {
509
- getAudioTracks() {
510
- // eslint-disable-next-line no-undef
511
- return [new MediaStreamTrack()];
512
- },
513
- },
514
- };
515
- }
516
- }
517
-
518
- class FakeAudioWorkletNode {
519
- constructor() {
520
- this.port = {
521
- postMessage: () => undefined,
522
- };
523
- }
524
-
525
- connect() {
526
- /* placeholder method */
527
- }
528
- }
529
-
530
- class FakeMediaStreamTrack {
531
- constructor() {
532
- this.kind = 'audio';
533
- this.enabled = true;
534
- this.label = 'Default - MacBook Pro Microphone (Built-in)';
535
- this.muted = false;
536
- this.readyState = 'live';
537
- this.contentHint = '';
538
- }
539
-
540
- getSettings() {
541
- return {
542
- sampleRate: 48000
543
- };
544
- }
545
- }
546
- Object.defineProperty(global, 'MediaStream', {
547
- writable: true,
548
- value: FakeMediaStream,
549
- });
550
-
551
- Object.defineProperty(global, 'AudioContext', {
552
- writable: true,
553
- value: FakeAudioContext,
554
- });
555
-
556
- Object.defineProperty(global, 'AudioWorkletNode', {
557
- writable: true,
558
- value: FakeAudioWorkletNode,
559
- });
560
-
561
- Object.defineProperty(global, 'MediaStreamTrack', {
562
- writable: true,
563
- value: FakeMediaStreamTrack,
564
- });
565
-
566
469
  beforeEach(() => {
567
- meeting.canUpdateMedia = sinon.stub().returns(true);
568
- MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
569
- MeetingUtil.updateTransceiver = sinon.stub();
570
-
571
470
  meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
572
471
  sinon.replace(meeting, 'addMedia', () => {
573
472
  sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
@@ -575,11 +474,7 @@ describe('plugin-meetings', () => {
575
474
  receiveAudio: true
576
475
  });
577
476
  });
578
-
579
- // eslint-disable-next-line no-undef
580
- MediaUtil.createMediaStream = sinon.stub().returns(new MediaStream([fakeMediaTrack()]));
581
477
  });
582
-
583
478
  describe('#enableBNR', () => {
584
479
  it('should have #enableBnr', () => {
585
480
  assert.exists(meeting.enableBNR);
@@ -594,26 +489,14 @@ describe('plugin-meetings', () => {
594
489
  });
595
490
 
596
491
  describe('after audio attached to meeting', () => {
492
+ let handleClientRequest;
493
+
597
494
  beforeEach(async () => {
598
495
  await meeting.getMediaStreams();
599
496
  await meeting.addMedia();
600
497
  });
601
498
 
602
- it('should return true for appropriate sample rate', async () => {
603
- const response = await meeting.enableBNR();
604
-
605
- assert(Metrics.sendBehavioralMetric.calledOnce);
606
- assert.calledWith(
607
- Metrics.sendBehavioralMetric,
608
- BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
609
- );
610
-
611
- assert.equal(response, true);
612
- });
613
-
614
499
  it('should throw error if meeting audio is muted', async () => {
615
- await meeting.getMediaStreams();
616
- await meeting.addMedia();
617
500
  const handleClientRequest = (meeting, mute) => {
618
501
  meeting.mediaProperties.audioTrack.enabled = !mute;
619
502
 
@@ -621,82 +504,53 @@ describe('plugin-meetings', () => {
621
504
  };
622
505
  const isMuted = () => !meeting.mediaProperties.audioTrack.enabled;
623
506
 
507
+ meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
624
508
  meeting.mediaId = 'mediaId';
625
509
  meeting.audio = {handleClientRequest, isMuted};
626
- meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
627
510
  await meeting.muteAudio();
628
511
  await meeting.enableBNR().catch((err) => {
629
- assert(Metrics.sendBehavioralMetric.calledOnce);
630
- assert.calledWith(
631
- Metrics.sendBehavioralMetric,
632
- BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE, {
633
- reason: err.message,
634
- stack: err.stack
635
- }
636
- );
637
512
  assert.equal(err.message, 'Cannot enable BNR while meeting is muted');
638
513
  });
639
514
  });
640
515
 
641
- it('should throw error for inappropriate sample rate and send error metrics', async () => {
642
- const fakeMediaTrack = () => ({
643
- id: Date.now().toString(),
644
- stop: () => {},
645
- readyState: 'live',
646
- getSettings: () => ({
647
- sampleRate: 49000
648
- })
649
- });
516
+ it('should return true on enable bnr success', async () => {
517
+ handleClientRequest = sinon.stub().returns(Promise.resolve(true));
518
+ meeting.effects = {handleClientRequest};
519
+ const response = await meeting.enableBNR();
650
520
 
651
- sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
652
-
653
- // eslint-disable-next-line no-undef
654
- MediaUtil.createMediaStream = sinon.stub().returns(new MediaStream([fakeMediaTrack()]));
655
-
656
- await meeting.enableBNR()
657
- .then(() => {
658
- assert.fail('The expected Error was not thrown.');
659
- })
660
- .catch((err) => {
661
- assert(Metrics.sendBehavioralMetric.calledOnce);
662
- assert.calledWith(
663
- Metrics.sendBehavioralMetric,
664
- BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE, {
665
- reason: err.message,
666
- stack: err.stack
667
- }
668
- );
669
- assert.equal(err.message, 'Sample rate of 49000 is not supported.');
670
- });
521
+ assert.equal(response, true);
671
522
  });
672
523
  });
673
524
  });
674
525
 
675
526
  describe('#disableBNR', () => {
676
- beforeEach(async () => {
677
- await meeting.getMediaStreams();
678
- await meeting.addMedia();
679
- });
527
+ describe('before audio attached to meeting', () => {
528
+ it('should have #disableBnr', () => {
529
+ assert.exists(meeting.disableBNR);
530
+ });
680
531
 
681
- it('should have #disableBnr', () => {
682
- assert.exists(meeting.disableBNR);
532
+ it('should throw no audio error', async () => {
533
+ await meeting.disableBNR().catch((err) => {
534
+ assert.equal(err.toString(), 'Error: Meeting doesn\'t have an audioTrack attached');
535
+ });
536
+ });
683
537
  });
538
+ describe('after audio attached to meeting', () => {
539
+ beforeEach(async () => {
540
+ await meeting.getMediaStreams();
541
+ await meeting.addMedia();
542
+ });
684
543
 
685
- it('should return true if bnr is disabled on bnr enabled track', async () => {
686
- await meeting.enableBNR();
687
- const response = await meeting.disableBNR();
544
+ let handleClientRequest;
545
+ let isBnrEnabled;
688
546
 
689
- assert.equal(response, true);
690
- });
547
+ it('should return true on disable bnr success', async () => {
548
+ handleClientRequest = sinon.stub().returns(Promise.resolve(true));
549
+ isBnrEnabled = sinon.stub().returns(Promise.resolve(true));
550
+ meeting.effects = {handleClientRequest, isBnrEnabled};
551
+ const response = await meeting.disableBNR();
691
552
 
692
- it('should throw error if bnr is not enabled before disabling and send error metrics', async () => {
693
- await meeting.disableBNR().catch((err) => {
694
- assert(Metrics.sendBehavioralMetric.calledOnce);
695
- assert.calledWith(
696
- Metrics.sendBehavioralMetric,
697
- BEHAVIORAL_METRICS.DISABLE_BNR_FAILURE,
698
- );
699
- assert.equal(err.message, 'Can not disable as BNR is not enabled');
553
+ assert.equal(response, true);
700
554
  });
701
555
  });
702
556
  });