@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/dist/meeting/index.js +150 -0
- package/dist/meeting/index.js.map +1 -1
- package/package.json +7 -6
- package/src/meeting/index.js +70 -0
- package/test/unit/spec/meeting/index.js +167 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-meetings",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
28
|
-
"@webex/internal-plugin-mercury": "2.
|
|
29
|
-
"@webex/internal-plugin-conversation": "2.
|
|
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.
|
|
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.
|
|
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
|
}
|
package/src/meeting/index.js
CHANGED
|
@@ -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);
|