@webex/plugin-meetings 2.4.1 → 2.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.4.1",
3
+ "version": "2.6.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -24,20 +24,21 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime-corejs2": "^7.14.8",
27
- "@webex/webex-core": "2.4.1",
28
- "@webex/internal-plugin-mercury": "2.4.1",
29
- "@webex/internal-plugin-conversation": "2.4.1",
27
+ "@webex/webex-core": "2.6.0",
28
+ "@webex/internal-plugin-mercury": "2.6.0",
29
+ "@webex/internal-plugin-conversation": "2.6.0",
30
30
  "webrtc-adapter": "^7.7.0",
31
31
  "lodash": "^4.17.21",
32
32
  "uuid": "^3.3.2",
33
33
  "global": "^4.4.0",
34
34
  "ip-anonymize": "^0.1.0",
35
- "@webex/common": "2.4.1",
35
+ "@webex/common": "2.6.0",
36
36
  "bowser": "^2.11.0",
37
37
  "sdp-transform": "^2.12.0",
38
38
  "readable-stream": "^3.6.0",
39
- "@webex/common-timers": "2.4.1",
39
+ "@webex/common-timers": "2.6.0",
40
40
  "btoa": "^1.2.1",
41
+ "@webex/internal-media-core": "^0.0.4-beta",
41
42
  "javascript-state-machine": "^3.1.0",
42
43
  "envify": "^4.1.0"
43
44
  }
@@ -1,6 +1,7 @@
1
1
  import uuid from 'uuid';
2
2
  import {cloneDeep, isEqual, pick} from 'lodash';
3
3
  import {StatelessWebexPlugin} from '@webex/webex-core';
4
+ import {Media as WebRTCMedia} from '@webex/internal-media-core';
4
5
 
5
6
  import {
6
7
  MeetingNotActiveError, createMeetingsError, UserInLobbyError,
@@ -83,6 +84,7 @@ import RoapCollection from '../roap/collection';
83
84
 
84
85
  import InMeetingActions from './in-meeting-actions';
85
86
 
87
+
86
88
  const {isBrowser} = BrowserDetection();
87
89
 
88
90
  const logRequest = (request, {header = '', success = '', failure = ''}) => {
@@ -5739,4 +5741,72 @@ export default class Meeting extends StatelessWebexPlugin {
5739
5741
  this.transcription = undefined;
5740
5742
  }
5741
5743
  };
5744
+
5745
+ /**
5746
+ * enableBNR API
5747
+ * @returns {Promise<Boolean>}
5748
+ * @public
5749
+ * @memberof Meeting
5750
+ */
5751
+ async enableBNR() {
5752
+ LoggerProxy.logger.info('Meeting:index#enableBNR. Enable BNR called');
5753
+ let isSuccess = false;
5754
+
5755
+ try {
5756
+ if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5757
+ throw new Error("Meeting doesn't have an audioTrack attached");
5758
+ }
5759
+ this.mediaProperties.audioTrack = await WebRTCMedia.Effects.BNR.enableBNR(this.mediaProperties.audioTrack);
5760
+ const audioStream = MediaUtil.createMediaStream([this.mediaProperties.audioTrack]);
5761
+
5762
+ LoggerProxy.logger.info('Meeting:index#enableBNR. BNR enabled track obtained from WebRTC & sent to updateAudio');
5763
+ await this.updateAudio({
5764
+ sendAudio: true,
5765
+ receiveAudio: true,
5766
+ stream: audioStream
5767
+ });
5768
+ this.isBnrEnabled = true;
5769
+ isSuccess = true;
5770
+ }
5771
+ catch (error) {
5772
+ LoggerProxy.logger.error(`Meeting:index#enableBNR. ${error}`);
5773
+ throw error;
5774
+ }
5775
+
5776
+ return isSuccess;
5777
+ }
5778
+
5779
+ /**
5780
+ * disableBNR API
5781
+ * @returns {Promise<Boolean>}
5782
+ * @public
5783
+ * @memberof Meeting
5784
+ */
5785
+ async disableBNR() {
5786
+ LoggerProxy.logger.info('Meeting:index#disableBNR. Disable BNR called');
5787
+ let isSuccess = false;
5788
+
5789
+ try {
5790
+ if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5791
+ throw new Error("Meeting doesn't have an audioTrack attached");
5792
+ }
5793
+ this.mediaProperties.audioTrack = WebRTCMedia.Effects.BNR.disableBNR(this.mediaProperties.audioTrack);
5794
+ const audioStream = MediaUtil.createMediaStream([this.mediaProperties.audioTrack]);
5795
+
5796
+ LoggerProxy.logger.info('Meeting:index#disableBNR. Raw media track obtained from WebRTC & sent to updateAudio');
5797
+ await this.updateAudio({
5798
+ sendAudio: true,
5799
+ receiveAudio: true,
5800
+ stream: audioStream
5801
+ });
5802
+ this.isBnrEnabled = false;
5803
+ isSuccess = true;
5804
+ }
5805
+ catch (error) {
5806
+ LoggerProxy.logger.error(`Meeting:index#disableBNR. ${error}`);
5807
+ throw error;
5808
+ }
5809
+
5810
+ return isSuccess;
5811
+ }
5742
5812
  }
@@ -105,6 +105,9 @@ describe('plugin-meetings', () => {
105
105
  groupId: '29d9339cc77bffdd24cb69ee80f6d3200481099bcd0f29267558672de0430777',
106
106
  }
107
107
  ])),
108
+ getSupportedConstraints: sinon.stub().returns({
109
+ sampleRate: true
110
+ })
108
111
  },
109
112
  });
110
113
 
@@ -445,6 +448,170 @@ describe('plugin-meetings', () => {
445
448
  });
446
449
  });
447
450
  });
451
+ describe('BNR', () => {
452
+ class FakeMediaStream {
453
+ constructor() {
454
+ this.active = false;
455
+ this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
456
+ }
457
+
458
+ addTrack = () => undefined;
459
+ }
460
+
461
+ class FakeAudioContext {
462
+ constructor() {
463
+ this.state = 'running';
464
+ this.baseLatency = 0.005333333333333333;
465
+ this.currentTime = 2.7946666666666666;
466
+ this.sampleRate = 48000;
467
+ this.audioWorklet = {
468
+ addModule: async () => undefined,
469
+ };
470
+ }
471
+
472
+ onstatechange = null;
473
+
474
+ createMediaStreamSource() {
475
+ return {
476
+ connect: () => undefined,
477
+ mediaStream: {
478
+ getAudioTracks() {
479
+ // eslint-disable-next-line no-undef
480
+ return [new MediaStreamTrack()];
481
+ },
482
+ },
483
+ };
484
+ }
485
+
486
+ createMediaStreamDestination() {
487
+ return {
488
+ stream: {
489
+ getAudioTracks() {
490
+ // eslint-disable-next-line no-undef
491
+ return [new MediaStreamTrack()];
492
+ },
493
+ },
494
+ };
495
+ }
496
+ }
497
+
498
+ class FakeAudioWorkletNode {
499
+ constructor() {
500
+ this.port = {
501
+ postMessage: () => undefined,
502
+ };
503
+ }
504
+
505
+ connect() {
506
+ /* placeholder method */
507
+ }
508
+ }
509
+
510
+ class FakeMediaStreamTrack {
511
+ constructor() {
512
+ this.kind = 'audio';
513
+ this.enabled = true;
514
+ this.label = 'Default - MacBook Pro Microphone (Built-in)';
515
+ this.muted = false;
516
+ this.readyState = 'live';
517
+ this.contentHint = '';
518
+ }
519
+ }
520
+ Object.defineProperty(global, 'MediaStream', {
521
+ writable: true,
522
+ value: FakeMediaStream,
523
+ });
524
+
525
+ Object.defineProperty(global, 'AudioContext', {
526
+ writable: true,
527
+ value: FakeAudioContext,
528
+ });
529
+
530
+ Object.defineProperty(global, 'AudioWorkletNode', {
531
+ writable: true,
532
+ value: FakeAudioWorkletNode,
533
+ });
534
+
535
+ Object.defineProperty(global, 'MediaStreamTrack', {
536
+ writable: true,
537
+ value: FakeMediaStreamTrack,
538
+ });
539
+
540
+ beforeEach(async () => {
541
+ meeting.canUpdateMedia = sinon.stub().returns(true);
542
+ MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
543
+ MeetingUtil.updateTransceiver = sinon.stub();
544
+ const fakeMediaTrack = () => ({
545
+ stop: () => {},
546
+ readyState: 'live',
547
+ getSettings: () => ({
548
+ sampleRate: 48000
549
+ })
550
+ });
551
+
552
+ meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
553
+ sinon.replace(meeting, 'addMedia', () => {
554
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
555
+ });
556
+ await meeting.getMediaStreams();
557
+ await meeting.addMedia();
558
+ });
559
+
560
+ describe('#enableBNR', () => {
561
+ it('should have #enableBnr', () => {
562
+ assert.exists(meeting.enableBNR);
563
+ });
564
+
565
+ describe('before audio attached to meeting', () => {
566
+ it('should throw no audio error', async () => {
567
+ await meeting.enableBNR().catch((err) => {
568
+ assert.equal(err.toString(), 'Error: Meeting doesn\'t have an audioTrack attached');
569
+ });
570
+ });
571
+ });
572
+
573
+ describe('after audio attached to meeting', () => {
574
+ it('should return true for appropriate sample rate', async () => {
575
+ const response = await meeting.enableBNR();
576
+
577
+ assert.equal(response, true);
578
+ });
579
+
580
+ it('should throw error for inappropriate sample rate', async () => {
581
+ const fakeMediaTrack = () => ({
582
+ stop: () => {},
583
+ readyState: 'live',
584
+ getSettings: () => ({
585
+ sampleRate: 49000
586
+ })
587
+ });
588
+
589
+ sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
590
+ await meeting.enableBNR().catch((err) => {
591
+ assert.equal(err.message, 'Sample rate of 49000 is not supported.');
592
+ });
593
+ });
594
+ });
595
+ });
596
+
597
+ describe('#disableBNR', () => {
598
+ it('should have #disableBnr', () => {
599
+ assert.exists(meeting.disableBNR);
600
+ });
601
+
602
+ it('should return true if bnr is disabled on bnr enabled track', async () => {
603
+ const response = await meeting.disableBNR();
604
+
605
+ assert.equal(response, true);
606
+ });
607
+
608
+ it('should throw error if bnr is not enabled before disabling', async () => {
609
+ await meeting.disableBNR().catch((err) => {
610
+ assert.equal(err.message, 'Can not disable as BNR is not enabled');
611
+ });
612
+ });
613
+ });
614
+ });
448
615
  describe('#muteVideo', () => {
449
616
  it('should have #muteVideo', () => {
450
617
  assert.exists(meeting.muteVideo);