@webex/plugin-meetings 2.12.1 → 2.13.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.12.1",
3
+ "version": "2.13.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -24,21 +24,21 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime-corejs2": "^7.14.8",
27
- "@webex/webex-core": "2.12.1",
28
- "@webex/internal-plugin-mercury": "2.12.1",
29
- "@webex/internal-plugin-conversation": "2.12.1",
27
+ "@webex/webex-core": "2.13.0",
28
+ "@webex/internal-plugin-mercury": "2.13.0",
29
+ "@webex/internal-plugin-conversation": "2.13.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.12.1",
35
+ "@webex/common": "2.13.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.12.1",
39
+ "@webex/common-timers": "2.13.0",
40
40
  "btoa": "^1.2.1",
41
- "@webex/internal-media-core": "^0.0.4-beta",
41
+ "@webex/internal-media-core": "^0.0.6-beta",
42
42
  "javascript-state-machine": "^3.1.0",
43
43
  "envify": "^4.1.0"
44
44
  }
package/src/constants.js CHANGED
@@ -1241,3 +1241,10 @@ export const MEETING_INFO_FAILURE_REASON = {
1241
1241
  WRONG_CAPTCHA: 'WRONG_CAPTCHA', // wbxappapi requires a captcha code or a wrong captcha code was provided
1242
1242
  OTHER: 'OTHER' // any other error (network, etc)
1243
1243
  };
1244
+
1245
+ export const BNR_STATUS = {
1246
+ SHOULD_ENABLE: 'SHOULD_ENABLE',
1247
+ ENABLED: 'ENABLED',
1248
+ SHOULD_DISABLE: 'SHOULD_DISABLE',
1249
+ NOT_ENABLED: 'NOT_ENABLED'
1250
+ };
@@ -72,6 +72,7 @@ import {
72
72
  STATS,
73
73
  VIDEO_RESOLUTIONS,
74
74
  VIDEO,
75
+ BNR_STATUS,
75
76
  HTTP_VERBS
76
77
  } from '../constants';
77
78
  import BEHAVIORAL_METRICS from '../metrics/constants';
@@ -914,6 +915,14 @@ export default class Meeting extends StatelessWebexPlugin {
914
915
  */
915
916
  this.meetingInfoFailureReason = undefined;
916
917
 
918
+ /**
919
+ * Indicates the current status of BNR in the meeting
920
+ * @type {BNR_STATUS}
921
+ * @private
922
+ * @memberof Meeting
923
+ */
924
+ this.bnrStatus = BNR_STATUS.NOT_ENABLED;
925
+
917
926
  this.setUpLocusInfoListeners();
918
927
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
919
928
  this.hasJoinedOnce = false;
@@ -4482,18 +4491,23 @@ export default class Meeting extends StatelessWebexPlugin {
4482
4491
  * @public
4483
4492
  * @memberof Meeting
4484
4493
  */
4485
- updateAudio(options) {
4494
+ async updateAudio(options) {
4486
4495
  if (!this.canUpdateMedia()) {
4487
4496
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
4488
4497
  }
4489
4498
  const {sendAudio, receiveAudio, stream} = options;
4490
4499
  const {audioTransceiver} = this.mediaProperties.peerConnection;
4491
- const track = MeetingUtil.getTrack(stream).audioTrack;
4500
+ let track = MeetingUtil.getTrack(stream).audioTrack;
4492
4501
 
4493
4502
  if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
4494
4503
  return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
4495
4504
  }
4496
4505
 
4506
+ if (sendAudio && !this.isAudioMuted() && (this.bnrStatus === BNR_STATUS.ENABLED || this.bnrStatus === BNR_STATUS.SHOULD_ENABLE)) {
4507
+ track = await this.internal_enableBNR(track);
4508
+ this.bnrStatus = BNR_STATUS.ENABLED;
4509
+ }
4510
+
4497
4511
  return MeetingUtil.validateOptions({sendAudio, localStream: stream})
4498
4512
  .then(() => {
4499
4513
  let previousMediaDirection = {};
@@ -5750,6 +5764,38 @@ export default class Meeting extends StatelessWebexPlugin {
5750
5764
  }
5751
5765
  };
5752
5766
 
5767
+ /**
5768
+ * Internal API to return status of BNR
5769
+ * @returns {Boolean}
5770
+ * @public
5771
+ * @memberof Meeting
5772
+ */
5773
+ isBnrEnabled() {
5774
+ return this.bnrStatus === BNR_STATUS.ENABLED;
5775
+ }
5776
+
5777
+ /**
5778
+ * Internal API to obtain BNR enabled MediaStream
5779
+ * @returns {Promise<MediaStreamTrack>}
5780
+ * @private
5781
+ * @param {MedaiStreamTrack} audioTrack from updateAudio
5782
+ * @memberof Meeting
5783
+ */
5784
+ async internal_enableBNR(audioTrack) {
5785
+ try {
5786
+ LoggerProxy.logger.info('Meeting:index#internal_enableBNR. Internal enable BNR called');
5787
+ const bnrAudioTrack = await WebRTCMedia.Effects.BNR.enableBNR(audioTrack);
5788
+
5789
+ LoggerProxy.logger.info('Meeting:index#internal_enableBNR. BNR enabled track obtained from WebRTC & returned as stream');
5790
+
5791
+ return bnrAudioTrack;
5792
+ }
5793
+ catch (error) {
5794
+ LoggerProxy.logger.error('Meeting:index#internal_enableBNR.', error);
5795
+ throw error;
5796
+ }
5797
+ }
5798
+
5753
5799
  /**
5754
5800
  * enableBNR API
5755
5801
  * @returns {Promise<Boolean>}
@@ -5764,24 +5810,29 @@ export default class Meeting extends StatelessWebexPlugin {
5764
5810
  if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5765
5811
  throw new Error("Meeting doesn't have an audioTrack attached");
5766
5812
  }
5767
- this.mediaProperties.audioTrack = await WebRTCMedia.Effects.BNR.enableBNR(this.mediaProperties.audioTrack);
5813
+ else if (this.isAudioMuted()) {
5814
+ throw new Error('Cannot enable BNR while meeting is muted');
5815
+ }
5816
+
5817
+
5818
+ this.bnrStatus = BNR_STATUS.SHOULD_ENABLE;
5819
+
5768
5820
  const audioStream = MediaUtil.createMediaStream([this.mediaProperties.audioTrack]);
5769
5821
 
5770
- LoggerProxy.logger.info('Meeting:index#enableBNR. BNR enabled track obtained from WebRTC & sent to updateAudio');
5822
+ LoggerProxy.logger.info('Meeting:index#enableBNR. MediaStream created from meeting & sent to updateAudio');
5771
5823
  await this.updateAudio({
5772
5824
  sendAudio: true,
5773
- receiveAudio: true,
5825
+ receiveAudio: this.mediaProperties.mediaDirection.receiveAudio,
5774
5826
  stream: audioStream
5775
5827
  });
5776
- this.isBnrEnabled = true;
5828
+ this.bnrStatus = BNR_STATUS.ENABLED;
5777
5829
  isSuccess = true;
5778
5830
  Metrics.sendBehavioralMetric(
5779
5831
  BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
5780
5832
  );
5781
5833
  }
5782
5834
  catch (error) {
5783
- LoggerProxy.logger.error(`Meeting:index#enableBNR. ${error}`);
5784
-
5835
+ this.bnrStatus = BNR_STATUS.NOT_ENABLED;
5785
5836
  Metrics.sendBehavioralMetric(
5786
5837
  BEHAVIORAL_METRICS.ENABLE_BNR_FAILURE,
5787
5838
  {
@@ -5789,7 +5840,7 @@ export default class Meeting extends StatelessWebexPlugin {
5789
5840
  stack: error.stack
5790
5841
  }
5791
5842
  );
5792
-
5843
+ LoggerProxy.logger.error('Meeting:index#enableBNR.', error);
5793
5844
  throw error;
5794
5845
  }
5795
5846
 
@@ -5807,19 +5858,27 @@ export default class Meeting extends StatelessWebexPlugin {
5807
5858
  let isSuccess = false;
5808
5859
 
5809
5860
  try {
5810
- if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5861
+ if (!this.isBnrEnabled()) {
5862
+ throw new Error('Can not disable as BNR is not enabled');
5863
+ }
5864
+ else if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5811
5865
  throw new Error("Meeting doesn't have an audioTrack attached");
5812
5866
  }
5813
- this.mediaProperties.audioTrack = WebRTCMedia.Effects.BNR.disableBNR(this.mediaProperties.audioTrack);
5814
- const audioStream = MediaUtil.createMediaStream([this.mediaProperties.audioTrack]);
5867
+ const audioTrack = WebRTCMedia.Effects.BNR.disableBNR(this.mediaProperties.audioTrack);
5868
+ const audioStream = MediaUtil.createMediaStream([audioTrack]);
5815
5869
 
5816
5870
  LoggerProxy.logger.info('Meeting:index#disableBNR. Raw media track obtained from WebRTC & sent to updateAudio');
5871
+
5872
+ this.bnrStatus = BNR_STATUS.SHOULD_DISABLE;
5873
+
5817
5874
  await this.updateAudio({
5818
5875
  sendAudio: true,
5819
- receiveAudio: true,
5876
+ receiveAudio: this.mediaProperties.mediaDirection.receiveAudio,
5820
5877
  stream: audioStream
5821
5878
  });
5822
- this.isBnrEnabled = false;
5879
+
5880
+ this.bnrStatus = BNR_STATUS.NOT_ENABLED;
5881
+
5823
5882
  isSuccess = true;
5824
5883
 
5825
5884
  Metrics.sendBehavioralMetric(
@@ -5827,6 +5886,7 @@ export default class Meeting extends StatelessWebexPlugin {
5827
5886
  );
5828
5887
  }
5829
5888
  catch (error) {
5889
+ this.bnrStatus = BNR_STATUS.ENABLED;
5830
5890
  LoggerProxy.logger.error(`Meeting:index#disableBNR. ${error}`);
5831
5891
 
5832
5892
  Metrics.sendBehavioralMetric(
@@ -456,13 +456,26 @@ describe('plugin-meetings', () => {
456
456
  });
457
457
  });
458
458
  describe('BNR', () => {
459
+ const fakeMediaTrack = () => ({
460
+ id: Date.now().toString(),
461
+ stop: () => {},
462
+ readyState: 'live',
463
+ enabled: true,
464
+ getSettings: () => ({
465
+ sampleRate: 48000
466
+ })
467
+ });
468
+
459
469
  class FakeMediaStream {
460
- constructor() {
470
+ constructor(tracks) {
461
471
  this.active = false;
462
472
  this.id = '5146425f-c240-48cc-b86b-27d422988fb7';
473
+ this.tracks = tracks;
463
474
  }
464
475
 
465
476
  addTrack = () => undefined;
477
+
478
+ getAudioTracks = () => this.tracks;
466
479
  }
467
480
 
468
481
  class FakeAudioContext {
@@ -523,6 +536,12 @@ describe('plugin-meetings', () => {
523
536
  this.readyState = 'live';
524
537
  this.contentHint = '';
525
538
  }
539
+
540
+ getSettings() {
541
+ return {
542
+ sampleRate: 48000
543
+ };
544
+ }
526
545
  }
527
546
  Object.defineProperty(global, 'MediaStream', {
528
547
  writable: true,
@@ -544,24 +563,21 @@ describe('plugin-meetings', () => {
544
563
  value: FakeMediaStreamTrack,
545
564
  });
546
565
 
547
- beforeEach(async () => {
566
+ beforeEach(() => {
548
567
  meeting.canUpdateMedia = sinon.stub().returns(true);
549
568
  MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
550
569
  MeetingUtil.updateTransceiver = sinon.stub();
551
- const fakeMediaTrack = () => ({
552
- stop: () => {},
553
- readyState: 'live',
554
- getSettings: () => ({
555
- sampleRate: 48000
556
- })
557
- });
558
570
 
559
571
  meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
560
572
  sinon.replace(meeting, 'addMedia', () => {
561
573
  sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
574
+ sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
575
+ receiveAudio: true
576
+ });
562
577
  });
563
- await meeting.getMediaStreams();
564
- await meeting.addMedia();
578
+
579
+ // eslint-disable-next-line no-undef
580
+ MediaUtil.createMediaStream = sinon.stub().returns(new MediaStream([fakeMediaTrack()]));
565
581
  });
566
582
 
567
583
  describe('#enableBNR', () => {
@@ -578,22 +594,37 @@ describe('plugin-meetings', () => {
578
594
  });
579
595
 
580
596
  describe('after audio attached to meeting', () => {
597
+ beforeEach(async () => {
598
+ await meeting.getMediaStreams();
599
+ await meeting.addMedia();
600
+ });
601
+
581
602
  it('should return true for appropriate sample rate', async () => {
582
603
  const response = await meeting.enableBNR();
583
604
 
605
+ assert(Metrics.sendBehavioralMetric.calledOnce);
606
+ assert.calledWith(
607
+ Metrics.sendBehavioralMetric,
608
+ BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
609
+ );
610
+
584
611
  assert.equal(response, true);
585
612
  });
586
613
 
587
- it('should throw error for inappropriate sample rate and send error metrics', async () => {
588
- const fakeMediaTrack = () => ({
589
- stop: () => {},
590
- readyState: 'live',
591
- getSettings: () => ({
592
- sampleRate: 49000
593
- })
594
- });
614
+ it('should throw error if meeting audio is muted', async () => {
615
+ await meeting.getMediaStreams();
616
+ await meeting.addMedia();
617
+ const handleClientRequest = (meeting, mute) => {
618
+ meeting.mediaProperties.audioTrack.enabled = !mute;
595
619
 
596
- sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
620
+ return Promise.resolve();
621
+ };
622
+ const isMuted = () => !meeting.mediaProperties.audioTrack.enabled;
623
+
624
+ meeting.mediaId = 'mediaId';
625
+ meeting.audio = {handleClientRequest, isMuted};
626
+ meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
627
+ await meeting.muteAudio();
597
628
  await meeting.enableBNR().catch((err) => {
598
629
  assert(Metrics.sendBehavioralMetric.calledOnce);
599
630
  assert.calledWith(
@@ -603,29 +634,56 @@ describe('plugin-meetings', () => {
603
634
  stack: err.stack
604
635
  }
605
636
  );
606
- assert.equal(err.message, 'Sample rate of 49000 is not supported.');
637
+ assert.equal(err.message, 'Cannot enable BNR while meeting is muted');
607
638
  });
608
639
  });
609
640
 
610
- it('should send metrics for enable bnr success', async () => {
611
- const response = await meeting.enableBNR();
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
+ });
612
650
 
613
- assert(Metrics.sendBehavioralMetric.calledOnce);
614
- assert.calledWith(
615
- Metrics.sendBehavioralMetric,
616
- BEHAVIORAL_METRICS.ENABLE_BNR_SUCCESS,
617
- );
618
- assert.equal(response, true);
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
+ });
619
671
  });
620
672
  });
621
673
  });
622
674
 
623
675
  describe('#disableBNR', () => {
676
+ beforeEach(async () => {
677
+ await meeting.getMediaStreams();
678
+ await meeting.addMedia();
679
+ });
680
+
624
681
  it('should have #disableBnr', () => {
625
682
  assert.exists(meeting.disableBNR);
626
683
  });
627
684
 
628
685
  it('should return true if bnr is disabled on bnr enabled track', async () => {
686
+ await meeting.enableBNR();
629
687
  const response = await meeting.disableBNR();
630
688
 
631
689
  assert.equal(response, true);