@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/dist/constants.js +9 -2
- package/dist/constants.js.map +1 -1
- package/dist/meeting/index.js +230 -103
- package/dist/meeting/index.js.map +1 -1
- package/package.json +7 -7
- package/src/constants.js +7 -0
- package/src/meeting/index.js +74 -14
- package/test/unit/spec/meeting/index.js +87 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-meetings",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
28
|
-
"@webex/internal-plugin-mercury": "2.
|
|
29
|
-
"@webex/internal-plugin-conversation": "2.
|
|
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.
|
|
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.
|
|
39
|
+
"@webex/common-timers": "2.13.0",
|
|
40
40
|
"btoa": "^1.2.1",
|
|
41
|
-
"@webex/internal-media-core": "^0.0.
|
|
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
|
+
};
|
package/src/meeting/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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:
|
|
5825
|
+
receiveAudio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5774
5826
|
stream: audioStream
|
|
5775
5827
|
});
|
|
5776
|
-
this.
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
5814
|
-
const audioStream = MediaUtil.createMediaStream([
|
|
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:
|
|
5876
|
+
receiveAudio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5820
5877
|
stream: audioStream
|
|
5821
5878
|
});
|
|
5822
|
-
|
|
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(
|
|
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
|
-
|
|
564
|
-
|
|
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
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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, '
|
|
637
|
+
assert.equal(err.message, 'Cannot enable BNR while meeting is muted');
|
|
607
638
|
});
|
|
608
639
|
});
|
|
609
640
|
|
|
610
|
-
it('should
|
|
611
|
-
const
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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);
|