@webex/plugin-meetings 3.0.0-next.2 → 3.0.0-next.21
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.d.ts +3 -9
- package/dist/constants.js +4 -9
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +3 -3
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/mediaSharesUtils.js +15 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/mediaQualityMetrics/config.d.ts +9 -1
- package/dist/mediaQualityMetrics/config.js +10 -1
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/index.d.ts +18 -7
- package/dist/meeting/index.js +745 -567
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/muteState.d.ts +2 -8
- package/dist/meeting/muteState.js +37 -25
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.d.ts +3 -0
- package/dist/meeting/request.js +32 -23
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/multistream/mediaRequestManager.d.ts +1 -2
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.d.ts +1 -1
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.d.ts +1 -2
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.d.ts +1 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/request.js +12 -10
- package/dist/reachability/request.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.d.ts +10 -2
- package/dist/roap/index.js +15 -0
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +3 -3
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +64 -17
- package/dist/roap/turnDiscovery.js +307 -126
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/index.js +61 -41
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +22 -22
- package/src/constants.ts +3 -9
- package/src/index.ts +1 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/media/index.ts +3 -1
- package/src/mediaQualityMetrics/config.ts +11 -1
- package/src/meeting/index.ts +264 -90
- package/src/meeting/muteState.ts +34 -20
- package/src/meeting/request.ts +18 -2
- package/src/meeting/util.ts +1 -0
- package/src/multistream/mediaRequestManager.ts +1 -1
- package/src/multistream/remoteMediaGroup.ts +1 -1
- package/src/multistream/remoteMediaManager.ts +1 -2
- package/src/multistream/sendSlotManager.ts +1 -2
- package/src/reachability/request.ts +15 -11
- package/src/reconnection-manager/index.ts +1 -1
- package/src/roap/index.ts +25 -3
- package/src/roap/request.ts +3 -3
- package/src/roap/turnDiscovery.ts +244 -78
- package/src/statsAnalyzer/index.ts +72 -43
- package/test/integration/spec/journey.js +14 -14
- package/test/integration/spec/space-meeting.js +1 -1
- package/test/unit/spec/interpretation/index.ts +4 -1
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
- package/test/unit/spec/media/index.ts +89 -78
- package/test/unit/spec/meeting/index.js +611 -125
- package/test/unit/spec/meeting/muteState.js +219 -67
- package/test/unit/spec/meeting/request.js +21 -0
- package/test/unit/spec/meeting/utils.js +6 -1
- package/test/unit/spec/meetings/index.js +27 -20
- package/test/unit/spec/multistream/remoteMediaGroup.ts +0 -1
- package/test/unit/spec/multistream/remoteMediaManager.ts +0 -1
- package/test/unit/spec/reachability/request.js +15 -7
- package/test/unit/spec/reconnection-manager/index.js +28 -0
- package/test/unit/spec/roap/index.ts +61 -6
- package/test/unit/spec/roap/turnDiscovery.ts +298 -16
- package/test/unit/spec/stats-analyzer/index.js +183 -8
- package/dist/member/member.types.d.ts +0 -11
- package/dist/member/member.types.js +0 -17
- package/dist/member/member.types.js.map +0 -1
- package/src/member/member.types.ts +0 -13
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
RemoteTrackType,
|
|
44
44
|
MediaType,
|
|
45
45
|
} from '@webex/internal-media-core';
|
|
46
|
-
import {
|
|
46
|
+
import {LocalStreamEventNames} from '@webex/media-helpers';
|
|
47
47
|
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
48
48
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
49
49
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
@@ -99,7 +99,6 @@ import {
|
|
|
99
99
|
MeetingInfoV2PolicyError,
|
|
100
100
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
101
101
|
import {
|
|
102
|
-
CLIENT_ERROR_CODE_TO_ERROR_PAYLOAD,
|
|
103
102
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
104
103
|
ICE_FAILED_WITHOUT_TURN_TLS_CLIENT_CODE,
|
|
105
104
|
ICE_FAILED_WITH_TURN_TLS_CLIENT_CODE,
|
|
@@ -110,9 +109,7 @@ import CallDiagnosticMetrics from '@webex/internal-plugin-metrics/src/call-diagn
|
|
|
110
109
|
import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagnostic/config';
|
|
111
110
|
import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
|
|
112
111
|
|
|
113
|
-
import {
|
|
114
|
-
EVENT_TRIGGERS as VOICEAEVENTS,
|
|
115
|
-
} from '@webex/internal-plugin-voicea';
|
|
112
|
+
import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
116
113
|
|
|
117
114
|
describe('plugin-meetings', () => {
|
|
118
115
|
const logger = {
|
|
@@ -613,36 +610,177 @@ describe('plugin-meetings', () => {
|
|
|
613
610
|
assert.exists(meeting.joinWithMedia);
|
|
614
611
|
});
|
|
615
612
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
613
|
+
const fakeRoapMessage = {id: 'fake TURN discovery message'};
|
|
614
|
+
const fakeReachabilityResults = {id: 'fake reachability'};
|
|
615
|
+
const fakeTurnServerInfo = {id: 'fake turn info'};
|
|
616
|
+
const fakeJoinResult = {id: 'join result'};
|
|
620
617
|
|
|
621
|
-
|
|
622
|
-
|
|
618
|
+
const joinOptions = {correlationId: '12345'};
|
|
619
|
+
const mediaOptions = {audioEnabled: true, allowMediaInLobby: true};
|
|
623
620
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
621
|
+
let generateTurnDiscoveryRequestMessageStub;
|
|
622
|
+
let handleTurnDiscoveryHttpResponseStub;
|
|
623
|
+
let abortTurnDiscoveryStub;
|
|
624
|
+
|
|
625
|
+
beforeEach(() => {
|
|
626
|
+
meeting.join = sinon.stub().returns(Promise.resolve(fakeJoinResult));
|
|
627
|
+
meeting.addMedia = sinon.stub().returns(Promise.resolve(test4));
|
|
628
|
+
|
|
629
|
+
webex.meetings.reachability.getReachabilityResults.resolves(fakeReachabilityResults);
|
|
630
|
+
|
|
631
|
+
generateTurnDiscoveryRequestMessageStub = sinon
|
|
632
|
+
.stub(meeting.roap, 'generateTurnDiscoveryRequestMessage')
|
|
633
|
+
.resolves({roapMessage: fakeRoapMessage});
|
|
634
|
+
handleTurnDiscoveryHttpResponseStub = sinon
|
|
635
|
+
.stub(meeting.roap, 'handleTurnDiscoveryHttpResponse')
|
|
636
|
+
.resolves({turnServerInfo: fakeTurnServerInfo, turnDiscoverySkippedReason: undefined});
|
|
637
|
+
abortTurnDiscoveryStub = sinon.stub(meeting.roap, 'abortTurnDiscovery');
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it('should work as expected', async () => {
|
|
641
|
+
const result = await meeting.joinWithMedia({
|
|
642
|
+
joinOptions,
|
|
643
|
+
mediaOptions,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// check that TURN discovery is done with join and addMedia called
|
|
647
|
+
assert.calledOnceWithExactly(meeting.join, {
|
|
648
|
+
...joinOptions,
|
|
649
|
+
roapMessage: fakeRoapMessage,
|
|
650
|
+
reachability: fakeReachabilityResults,
|
|
651
|
+
});
|
|
652
|
+
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
653
|
+
assert.calledOnceWithExactly(
|
|
654
|
+
handleTurnDiscoveryHttpResponseStub,
|
|
655
|
+
meeting,
|
|
656
|
+
fakeJoinResult
|
|
657
|
+
);
|
|
658
|
+
assert.calledOnceWithExactly(meeting.addMedia, mediaOptions, fakeTurnServerInfo);
|
|
659
|
+
|
|
660
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it("should not call handleTurnDiscoveryHttpResponse if we don't send a TURN discovery request with join", async () => {
|
|
664
|
+
generateTurnDiscoveryRequestMessageStub.resolves({roapMessage: undefined});
|
|
665
|
+
|
|
666
|
+
const result = await meeting.joinWithMedia({
|
|
667
|
+
joinOptions,
|
|
668
|
+
mediaOptions,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
// check that TURN discovery is done with join and addMedia called
|
|
672
|
+
assert.calledOnceWithExactly(meeting.join, {
|
|
673
|
+
...joinOptions,
|
|
674
|
+
roapMessage: undefined,
|
|
675
|
+
reachability: fakeReachabilityResults,
|
|
631
676
|
});
|
|
677
|
+
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
678
|
+
assert.notCalled(handleTurnDiscoveryHttpResponseStub);
|
|
679
|
+
assert.notCalled(abortTurnDiscoveryStub);
|
|
680
|
+
assert.calledOnceWithExactly(meeting.addMedia, mediaOptions, undefined);
|
|
681
|
+
|
|
682
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
683
|
+
assert.equal(meeting.turnServerUsed, false);
|
|
632
684
|
});
|
|
633
685
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
686
|
+
it('should call abortTurnDiscovery() if we do not get a TURN server info', async () => {
|
|
687
|
+
handleTurnDiscoveryHttpResponseStub.resolves({
|
|
688
|
+
turnServerInfo: undefined,
|
|
689
|
+
turnDiscoverySkippedReason: 'missing http response',
|
|
638
690
|
});
|
|
639
691
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
692
|
+
const result = await meeting.joinWithMedia({
|
|
693
|
+
joinOptions,
|
|
694
|
+
mediaOptions,
|
|
695
|
+
});
|
|
643
696
|
|
|
644
|
-
|
|
697
|
+
// check that TURN discovery is done with join and addMedia called
|
|
698
|
+
assert.calledOnceWithExactly(meeting.join, {
|
|
699
|
+
...joinOptions,
|
|
700
|
+
roapMessage: fakeRoapMessage,
|
|
701
|
+
reachability: fakeReachabilityResults,
|
|
645
702
|
});
|
|
703
|
+
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
|
704
|
+
assert.calledOnceWithExactly(
|
|
705
|
+
handleTurnDiscoveryHttpResponseStub,
|
|
706
|
+
meeting,
|
|
707
|
+
fakeJoinResult
|
|
708
|
+
);
|
|
709
|
+
assert.calledOnceWithExactly(abortTurnDiscoveryStub);
|
|
710
|
+
assert.calledOnceWithExactly(meeting.addMedia, mediaOptions, undefined);
|
|
711
|
+
|
|
712
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should reject if join() fails', async () => {
|
|
716
|
+
const error = new Error('fake');
|
|
717
|
+
meeting.join = sinon.stub().returns(Promise.reject(error));
|
|
718
|
+
meeting.locusUrl = null; // when join fails, we end up with null locusUrl
|
|
719
|
+
|
|
720
|
+
await assert.isRejected(meeting.joinWithMedia({mediaOptions: {allowMediaInLobby: true}}));
|
|
721
|
+
|
|
722
|
+
assert.calledOnceWithExactly(abortTurnDiscoveryStub);
|
|
723
|
+
|
|
724
|
+
assert.calledWith(
|
|
725
|
+
Metrics.sendBehavioralMetric,
|
|
726
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
727
|
+
{
|
|
728
|
+
correlation_id: meeting.correlationId,
|
|
729
|
+
locus_id: undefined,
|
|
730
|
+
reason: error.message,
|
|
731
|
+
stack: error.stack,
|
|
732
|
+
leaveErrorReason: undefined,
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
type: error.name,
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
it('should fail if called with allowMediaInLobby:false', async () => {
|
|
741
|
+
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
742
|
+
meeting.addMedia = sinon.stub().returns(Promise.resolve(test4));
|
|
743
|
+
|
|
744
|
+
await assert.isRejected(
|
|
745
|
+
meeting.joinWithMedia({mediaOptions: {allowMediaInLobby: false}})
|
|
746
|
+
);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('should call leave() if addMedia fails and ignore leave() failure', async () => {
|
|
750
|
+
const leaveError = new Error('leave error');
|
|
751
|
+
const addMediaError = new Error('fake addMedia error');
|
|
752
|
+
|
|
753
|
+
const leaveStub = sinon.stub(meeting, 'leave').rejects(leaveError);
|
|
754
|
+
meeting.addMedia = sinon.stub().rejects(addMediaError);
|
|
755
|
+
|
|
756
|
+
await assert.isRejected(
|
|
757
|
+
meeting.joinWithMedia({
|
|
758
|
+
joinOptions: {resourceId: 'some resource'},
|
|
759
|
+
mediaOptions: {allowMediaInLobby: true},
|
|
760
|
+
}),
|
|
761
|
+
addMediaError
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
assert.calledOnce(leaveStub);
|
|
765
|
+
assert.calledOnceWithExactly(leaveStub, {
|
|
766
|
+
resourceId: 'some resource',
|
|
767
|
+
reason: 'joinWithMedia failure',
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
assert.calledWith(
|
|
771
|
+
Metrics.sendBehavioralMetric,
|
|
772
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
773
|
+
{
|
|
774
|
+
correlation_id: meeting.correlationId,
|
|
775
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
776
|
+
reason: addMediaError.message,
|
|
777
|
+
stack: addMediaError.stack,
|
|
778
|
+
leaveErrorReason: leaveError.message,
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
type: addMediaError.name,
|
|
782
|
+
}
|
|
783
|
+
);
|
|
646
784
|
});
|
|
647
785
|
});
|
|
648
786
|
|
|
@@ -669,7 +807,7 @@ describe('plugin-meetings', () => {
|
|
|
669
807
|
|
|
670
808
|
it('should subscribe to events for the first time and avoid subscribing for future transcription starts', async () => {
|
|
671
809
|
meeting.joinedWith = {
|
|
672
|
-
state: 'JOINED'
|
|
810
|
+
state: 'JOINED',
|
|
673
811
|
};
|
|
674
812
|
meeting.areVoiceaEventsSetup = false;
|
|
675
813
|
meeting.roles = ['MODERATOR'];
|
|
@@ -679,27 +817,19 @@ describe('plugin-meetings', () => {
|
|
|
679
817
|
assert.equal(webex.internal.voicea.on.callCount, 4);
|
|
680
818
|
assert.equal(meeting.areVoiceaEventsSetup, true);
|
|
681
819
|
assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
|
|
682
|
-
assert.calledWith(
|
|
683
|
-
webex.internal.voicea.toggleTranscribing,
|
|
684
|
-
true,
|
|
685
|
-
);
|
|
820
|
+
assert.calledWith(webex.internal.voicea.toggleTranscribing, true);
|
|
686
821
|
|
|
687
822
|
await meeting.startTranscription();
|
|
688
823
|
assert.equal(webex.internal.voicea.on.callCount, 4);
|
|
689
824
|
assert.equal(meeting.areVoiceaEventsSetup, true);
|
|
690
825
|
assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
|
|
691
|
-
assert.calledTwice(
|
|
692
|
-
|
|
693
|
-
);
|
|
694
|
-
assert.calledWith(
|
|
695
|
-
webex.internal.voicea.toggleTranscribing,
|
|
696
|
-
true,
|
|
697
|
-
);
|
|
826
|
+
assert.calledTwice(webex.internal.voicea.toggleTranscribing);
|
|
827
|
+
assert.calledWith(webex.internal.voicea.toggleTranscribing, true);
|
|
698
828
|
});
|
|
699
829
|
|
|
700
830
|
it('should listen to events and not toggleTranscribing if the user is not a host', async () => {
|
|
701
831
|
meeting.joinedWith = {
|
|
702
|
-
state: 'JOINED'
|
|
832
|
+
state: 'JOINED',
|
|
703
833
|
};
|
|
704
834
|
meeting.areVoiceaEventsSetup = false;
|
|
705
835
|
meeting.roles = ['COHOST'];
|
|
@@ -709,9 +839,7 @@ describe('plugin-meetings', () => {
|
|
|
709
839
|
assert.equal(webex.internal.voicea.on.callCount, 4);
|
|
710
840
|
assert.equal(meeting.areVoiceaEventsSetup, true);
|
|
711
841
|
assert.equal(webex.internal.voicea.listenToEvents.callCount, 1);
|
|
712
|
-
assert.notCalled(
|
|
713
|
-
webex.internal.voicea.toggleTranscribing
|
|
714
|
-
);
|
|
842
|
+
assert.notCalled(webex.internal.voicea.toggleTranscribing);
|
|
715
843
|
});
|
|
716
844
|
|
|
717
845
|
it("should throw error if request doesn't work", async () => {
|
|
@@ -752,7 +880,7 @@ describe('plugin-meetings', () => {
|
|
|
752
880
|
describe('#setCaptionLanguage', () => {
|
|
753
881
|
beforeEach(() => {
|
|
754
882
|
meeting.isTranscriptionSupported = sinon.stub();
|
|
755
|
-
meeting.transcription = {
|
|
883
|
+
meeting.transcription = {languageOptions: {}};
|
|
756
884
|
webex.internal.voicea.on = sinon.stub();
|
|
757
885
|
webex.internal.voicea.off = sinon.stub();
|
|
758
886
|
webex.internal.voicea.setCaptionLanguage = sinon.stub();
|
|
@@ -778,23 +906,23 @@ describe('plugin-meetings', () => {
|
|
|
778
906
|
const languageCode = 'fr';
|
|
779
907
|
|
|
780
908
|
meeting.setCaptionLanguage(languageCode).then((resolvedLanguageCode) => {
|
|
781
|
-
assert.calledWith(
|
|
782
|
-
|
|
909
|
+
assert.calledWith(webex.internal.voicea.requestLanguage, languageCode);
|
|
910
|
+
assert.equal(resolvedLanguageCode, languageCode);
|
|
911
|
+
assert.equal(
|
|
912
|
+
meeting.transcription.languageOptions.currentCaptionLanguage,
|
|
783
913
|
languageCode
|
|
784
914
|
);
|
|
785
|
-
assert.equal(resolvedLanguageCode, languageCode);
|
|
786
|
-
assert.equal(meeting.transcription.languageOptions.currentCaptionLanguage, languageCode);
|
|
787
915
|
done();
|
|
788
916
|
});
|
|
789
917
|
|
|
790
918
|
assert.calledOnceWithMatch(
|
|
791
919
|
webex.internal.voicea.on,
|
|
792
|
-
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE
|
|
920
|
+
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE
|
|
793
921
|
);
|
|
794
922
|
|
|
795
923
|
// Trigger the event
|
|
796
924
|
const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
|
|
797
|
-
voiceaListenerLangugeUpdate({
|
|
925
|
+
voiceaListenerLangugeUpdate({statusCode: 200, languageCode});
|
|
798
926
|
});
|
|
799
927
|
|
|
800
928
|
it('should reject if the statusCode in payload is not 200', (done) => {
|
|
@@ -802,8 +930,8 @@ describe('plugin-meetings', () => {
|
|
|
802
930
|
const languageCode = 'fr';
|
|
803
931
|
const rejectPayload = {
|
|
804
932
|
statusCode: 400,
|
|
805
|
-
message: 'some error message'
|
|
806
|
-
}
|
|
933
|
+
message: 'some error message',
|
|
934
|
+
};
|
|
807
935
|
|
|
808
936
|
meeting.setCaptionLanguage(languageCode).catch((payload) => {
|
|
809
937
|
assert.equal(payload, rejectPayload);
|
|
@@ -812,20 +940,19 @@ describe('plugin-meetings', () => {
|
|
|
812
940
|
|
|
813
941
|
assert.calledOnceWithMatch(
|
|
814
942
|
webex.internal.voicea.on,
|
|
815
|
-
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE
|
|
943
|
+
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE
|
|
816
944
|
);
|
|
817
945
|
|
|
818
946
|
// Trigger the event
|
|
819
947
|
const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
|
|
820
948
|
voiceaListenerLangugeUpdate(rejectPayload);
|
|
821
949
|
});
|
|
822
|
-
|
|
823
950
|
});
|
|
824
951
|
|
|
825
952
|
describe('#setSpokenLanguage', () => {
|
|
826
953
|
beforeEach(() => {
|
|
827
954
|
meeting.isTranscriptionSupported = sinon.stub();
|
|
828
|
-
meeting.transcription = {
|
|
955
|
+
meeting.transcription = {languageOptions: {}};
|
|
829
956
|
webex.internal.voicea.on = sinon.stub();
|
|
830
957
|
webex.internal.voicea.off = sinon.stub();
|
|
831
958
|
webex.internal.voicea.setSpokenLanguage = sinon.stub();
|
|
@@ -850,47 +977,37 @@ describe('plugin-meetings', () => {
|
|
|
850
977
|
const languageCode = 'fr';
|
|
851
978
|
|
|
852
979
|
meeting.setSpokenLanguage(languageCode).then((resolvedLanguageCode) => {
|
|
853
|
-
assert.calledWith(
|
|
854
|
-
webex.internal.voicea.setSpokenLanguage,
|
|
855
|
-
languageCode
|
|
856
|
-
);
|
|
980
|
+
assert.calledWith(webex.internal.voicea.setSpokenLanguage, languageCode);
|
|
857
981
|
assert.equal(resolvedLanguageCode, languageCode);
|
|
858
982
|
assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, languageCode);
|
|
859
983
|
done();
|
|
860
984
|
});
|
|
861
985
|
|
|
862
|
-
assert.calledOnceWithMatch(
|
|
863
|
-
webex.internal.voicea.on,
|
|
864
|
-
VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
|
|
865
|
-
);
|
|
986
|
+
assert.calledOnceWithMatch(webex.internal.voicea.on, VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE);
|
|
866
987
|
|
|
867
988
|
// Trigger the event
|
|
868
989
|
const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
|
|
869
|
-
voiceaListenerLangugeUpdate({
|
|
990
|
+
voiceaListenerLangugeUpdate({languageCode});
|
|
870
991
|
});
|
|
871
992
|
|
|
872
993
|
it('should reject if the language code does not exist in payload', (done) => {
|
|
873
994
|
meeting.isTranscriptionSupported.returns(true);
|
|
874
995
|
const languageCode = 'fr';
|
|
875
996
|
const rejectPayload = {
|
|
876
|
-
|
|
877
|
-
}
|
|
997
|
+
message: 'some error message',
|
|
998
|
+
};
|
|
878
999
|
|
|
879
1000
|
meeting.setSpokenLanguage(languageCode).catch((payload) => {
|
|
880
1001
|
assert.equal(payload, rejectPayload);
|
|
881
1002
|
done();
|
|
882
1003
|
});
|
|
883
1004
|
|
|
884
|
-
assert.calledOnceWithMatch(
|
|
885
|
-
webex.internal.voicea.on,
|
|
886
|
-
VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
|
|
887
|
-
);
|
|
1005
|
+
assert.calledOnceWithMatch(webex.internal.voicea.on, VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE);
|
|
888
1006
|
|
|
889
1007
|
// Trigger the event
|
|
890
1008
|
const voiceaListenerLangugeUpdate = webex.internal.voicea.on.getCall(0).args[1];
|
|
891
1009
|
voiceaListenerLangugeUpdate(rejectPayload);
|
|
892
1010
|
});
|
|
893
|
-
|
|
894
1011
|
});
|
|
895
1012
|
|
|
896
1013
|
describe('transcription events', () => {
|
|
@@ -1134,7 +1251,7 @@ describe('plugin-meetings', () => {
|
|
|
1134
1251
|
file: 'meeting/index',
|
|
1135
1252
|
function: 'join',
|
|
1136
1253
|
},
|
|
1137
|
-
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1254
|
+
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1138
1255
|
);
|
|
1139
1256
|
});
|
|
1140
1257
|
|
|
@@ -1412,7 +1529,6 @@ describe('plugin-meetings', () => {
|
|
|
1412
1529
|
describe('#addMedia', () => {
|
|
1413
1530
|
const muteStateStub = {
|
|
1414
1531
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
1415
|
-
applyClientStateLocally: sinon.stub().returns(Promise.resolve(true)),
|
|
1416
1532
|
};
|
|
1417
1533
|
|
|
1418
1534
|
let fakeMediaConnection;
|
|
@@ -2506,6 +2622,7 @@ describe('plugin-meetings', () => {
|
|
|
2506
2622
|
|
|
2507
2623
|
beforeEach(async () => {
|
|
2508
2624
|
meeting.meetingState = 'ACTIVE';
|
|
2625
|
+
meeting.remoteShareInstanceId = '1234';
|
|
2509
2626
|
prevConfigValue = meeting.config.stats.enableStatsAnalyzer;
|
|
2510
2627
|
|
|
2511
2628
|
meeting.config.stats.enableStatsAnalyzer = true;
|
|
@@ -2611,6 +2728,66 @@ describe('plugin-meetings', () => {
|
|
|
2611
2728
|
});
|
|
2612
2729
|
});
|
|
2613
2730
|
|
|
2731
|
+
it('REMOTE_MEDIA_STARTED triggers "meeting:media:remote:start" event and sends metrics for share', async () => {
|
|
2732
|
+
statsAnalyzerStub.emit(
|
|
2733
|
+
{file: 'test', function: 'test'},
|
|
2734
|
+
StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STARTED,
|
|
2735
|
+
{type: 'share'}
|
|
2736
|
+
);
|
|
2737
|
+
|
|
2738
|
+
assert.calledWith(
|
|
2739
|
+
TriggerProxy.trigger,
|
|
2740
|
+
sinon.match.instanceOf(Meeting),
|
|
2741
|
+
{
|
|
2742
|
+
file: 'meeting/index',
|
|
2743
|
+
function: 'addMedia',
|
|
2744
|
+
},
|
|
2745
|
+
EVENT_TRIGGERS.MEETING_MEDIA_REMOTE_STARTED,
|
|
2746
|
+
{
|
|
2747
|
+
type: 'share',
|
|
2748
|
+
}
|
|
2749
|
+
);
|
|
2750
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2751
|
+
name: 'client.media.rx.start',
|
|
2752
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
|
2753
|
+
options: {
|
|
2754
|
+
meetingId: meeting.id,
|
|
2755
|
+
},
|
|
2756
|
+
});
|
|
2757
|
+
|
|
2758
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2759
|
+
name: 'client.media.render.start',
|
|
2760
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
|
2761
|
+
options: {
|
|
2762
|
+
meetingId: meeting.id,
|
|
2763
|
+
},
|
|
2764
|
+
});
|
|
2765
|
+
});
|
|
2766
|
+
|
|
2767
|
+
it('REMOTE_MEDIA_STOPPED triggers the right metrics for share', async () => {
|
|
2768
|
+
statsAnalyzerStub.emit(
|
|
2769
|
+
{file: 'test', function: 'test'},
|
|
2770
|
+
StatsAnalyzerModule.EVENTS.REMOTE_MEDIA_STOPPED,
|
|
2771
|
+
{type: 'share'}
|
|
2772
|
+
);
|
|
2773
|
+
|
|
2774
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2775
|
+
name: 'client.media.rx.stop',
|
|
2776
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
|
2777
|
+
options: {
|
|
2778
|
+
meetingId: meeting.id,
|
|
2779
|
+
},
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2783
|
+
name: 'client.media.render.stop',
|
|
2784
|
+
payload: {mediaType: 'share', shareInstanceId: meeting.remoteShareInstanceId},
|
|
2785
|
+
options: {
|
|
2786
|
+
meetingId: meeting.id,
|
|
2787
|
+
},
|
|
2788
|
+
});
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2614
2791
|
it('calls submitMQE correctly', async () => {
|
|
2615
2792
|
const fakeData = {intervalMetadata: {bla: 'bla'}};
|
|
2616
2793
|
|
|
@@ -2855,9 +3032,10 @@ describe('plugin-meetings', () => {
|
|
|
2855
3032
|
meeting.setMercuryListener = sinon.stub();
|
|
2856
3033
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
2857
3034
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
2858
|
-
meeting.roap.doTurnDiscovery = sinon
|
|
2859
|
-
|
|
2860
|
-
|
|
3035
|
+
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
3036
|
+
turnServerInfo: {url: 'turn-url', username: 'turn user', password: 'turn password'},
|
|
3037
|
+
turnDiscoverySkippedReason: 'reachability',
|
|
3038
|
+
});
|
|
2861
3039
|
meeting.deferSDPAnswer = new Defer();
|
|
2862
3040
|
meeting.deferSDPAnswer.resolve();
|
|
2863
3041
|
meeting.webex.meetings.meetingCollection = new MeetingCollection();
|
|
@@ -2868,7 +3046,7 @@ describe('plugin-meetings', () => {
|
|
|
2868
3046
|
// setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
|
|
2869
3047
|
expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
|
|
2870
3048
|
expectedMediaConnectionConfig = {
|
|
2871
|
-
iceServers: [{urls:
|
|
3049
|
+
iceServers: [{urls: 'turn-url', username: 'turn user', credential: 'turn password'}],
|
|
2872
3050
|
skipInactiveTransceivers: false,
|
|
2873
3051
|
requireH264: true,
|
|
2874
3052
|
sdpMunging: {
|
|
@@ -2892,9 +3070,13 @@ describe('plugin-meetings', () => {
|
|
|
2892
3070
|
getSettings: sinon.stub().returns({
|
|
2893
3071
|
deviceId: 'some device id',
|
|
2894
3072
|
}),
|
|
2895
|
-
|
|
3073
|
+
userMuted: false,
|
|
3074
|
+
systemMuted: false,
|
|
3075
|
+
get muted() {
|
|
3076
|
+
return this.userMuted || this.systemMuted;
|
|
3077
|
+
},
|
|
2896
3078
|
setUnmuteAllowed: sinon.stub(),
|
|
2897
|
-
|
|
3079
|
+
setUserMuted: sinon.stub(),
|
|
2898
3080
|
setServerMuted: sinon.stub(),
|
|
2899
3081
|
outputStream: {
|
|
2900
3082
|
getTracks: () => {
|
|
@@ -3129,28 +3311,52 @@ describe('plugin-meetings', () => {
|
|
|
3129
3311
|
if (stream !== undefined) {
|
|
3130
3312
|
switch (type) {
|
|
3131
3313
|
case 'audio':
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3314
|
+
if (stream?.readyState === 'ended') {
|
|
3315
|
+
assert.notCalled(
|
|
3316
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream
|
|
3317
|
+
);
|
|
3318
|
+
} else {
|
|
3319
|
+
assert.calledOnceWithExactly(
|
|
3320
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream,
|
|
3321
|
+
stream
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3136
3324
|
break;
|
|
3137
3325
|
case 'video':
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3326
|
+
if (stream?.readyState === 'ended') {
|
|
3327
|
+
assert.notCalled(
|
|
3328
|
+
meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream
|
|
3329
|
+
);
|
|
3330
|
+
} else {
|
|
3331
|
+
assert.calledOnceWithExactly(
|
|
3332
|
+
meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream,
|
|
3333
|
+
stream
|
|
3334
|
+
);
|
|
3335
|
+
}
|
|
3142
3336
|
break;
|
|
3143
3337
|
case 'screenShareAudio':
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3338
|
+
if (stream?.readyState === 'ended') {
|
|
3339
|
+
assert.notCalled(
|
|
3340
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream
|
|
3341
|
+
);
|
|
3342
|
+
} else {
|
|
3343
|
+
assert.calledOnceWithExactly(
|
|
3344
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
|
|
3345
|
+
stream
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3148
3348
|
break;
|
|
3149
3349
|
case 'screenShareVideo':
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3350
|
+
if (stream?.readyState === 'ended') {
|
|
3351
|
+
assert.notCalled(
|
|
3352
|
+
meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream
|
|
3353
|
+
);
|
|
3354
|
+
} else {
|
|
3355
|
+
assert.calledOnceWithExactly(
|
|
3356
|
+
meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream,
|
|
3357
|
+
stream
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3154
3360
|
break;
|
|
3155
3361
|
}
|
|
3156
3362
|
}
|
|
@@ -3178,7 +3384,7 @@ describe('plugin-meetings', () => {
|
|
|
3178
3384
|
}
|
|
3179
3385
|
};
|
|
3180
3386
|
|
|
3181
|
-
it('addMedia() works correctly when media is enabled without
|
|
3387
|
+
it('addMedia() works correctly when media is enabled without streams to publish', async () => {
|
|
3182
3388
|
await meeting.addMedia();
|
|
3183
3389
|
await simulateRoapOffer();
|
|
3184
3390
|
await simulateRoapOk();
|
|
@@ -3212,6 +3418,7 @@ describe('plugin-meetings', () => {
|
|
|
3212
3418
|
});
|
|
3213
3419
|
|
|
3214
3420
|
it('addMedia() works correctly when media is enabled with streams to publish', async () => {
|
|
3421
|
+
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
3215
3422
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
3216
3423
|
await simulateRoapOffer();
|
|
3217
3424
|
await simulateRoapOk();
|
|
@@ -3242,10 +3449,82 @@ describe('plugin-meetings', () => {
|
|
|
3242
3449
|
|
|
3243
3450
|
// and that these were the only /media requests that were sent
|
|
3244
3451
|
assert.calledTwice(locusMediaRequestStub);
|
|
3452
|
+
|
|
3453
|
+
assert.calledOnce(handleDeviceLoggingSpy);
|
|
3454
|
+
});
|
|
3455
|
+
|
|
3456
|
+
it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
|
|
3457
|
+
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
3458
|
+
fakeMicrophoneStream.userMuted = true;
|
|
3459
|
+
|
|
3460
|
+
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
3461
|
+
await simulateRoapOffer();
|
|
3462
|
+
await simulateRoapOk();
|
|
3463
|
+
|
|
3464
|
+
// check RoapMediaConnection was created correctly
|
|
3465
|
+
checkMediaConnectionCreated({
|
|
3466
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
3467
|
+
localStreams: {
|
|
3468
|
+
audio: fakeMicrophoneStream,
|
|
3469
|
+
video: undefined,
|
|
3470
|
+
screenShareVideo: undefined,
|
|
3471
|
+
screenShareAudio: undefined,
|
|
3472
|
+
},
|
|
3473
|
+
direction: {
|
|
3474
|
+
audio: 'sendrecv',
|
|
3475
|
+
video: 'sendrecv',
|
|
3476
|
+
screenShare: 'recvonly',
|
|
3477
|
+
},
|
|
3478
|
+
remoteQualityLevel: 'HIGH',
|
|
3479
|
+
expectedDebugId,
|
|
3480
|
+
meetingId: meeting.id,
|
|
3481
|
+
});
|
|
3482
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
3483
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
3484
|
+
// check OK was sent with the right audioMuted/videoMuted values
|
|
3485
|
+
checkOkSent({audioMuted: true, videoMuted: true});
|
|
3486
|
+
|
|
3487
|
+
// and that these were the only /media requests that were sent
|
|
3488
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
3489
|
+
assert.calledOnce(handleDeviceLoggingSpy);
|
|
3490
|
+
});
|
|
3491
|
+
|
|
3492
|
+
it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
|
|
3493
|
+
fakeMicrophoneStream.readyState = 'ended';
|
|
3494
|
+
|
|
3495
|
+
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
3496
|
+
await simulateRoapOffer();
|
|
3497
|
+
await simulateRoapOk();
|
|
3498
|
+
|
|
3499
|
+
// check RoapMediaConnection was created correctly
|
|
3500
|
+
checkMediaConnectionCreated({
|
|
3501
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
3502
|
+
localStreams: {
|
|
3503
|
+
audio: undefined,
|
|
3504
|
+
video: undefined,
|
|
3505
|
+
screenShareVideo: undefined,
|
|
3506
|
+
screenShareAudio: undefined,
|
|
3507
|
+
},
|
|
3508
|
+
direction: {
|
|
3509
|
+
audio: 'sendrecv',
|
|
3510
|
+
video: 'sendrecv',
|
|
3511
|
+
screenShare: 'recvonly',
|
|
3512
|
+
},
|
|
3513
|
+
remoteQualityLevel: 'HIGH',
|
|
3514
|
+
expectedDebugId,
|
|
3515
|
+
meetingId: meeting.id,
|
|
3516
|
+
});
|
|
3517
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
3518
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
3519
|
+
// check OK was sent with the right audioMuted/videoMuted values
|
|
3520
|
+
checkOkSent({audioMuted: true, videoMuted: true});
|
|
3521
|
+
|
|
3522
|
+
// and that these were the only /media requests that were sent
|
|
3523
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
3245
3524
|
});
|
|
3246
3525
|
|
|
3247
|
-
it('addMedia() works correctly when media is enabled with
|
|
3248
|
-
fakeMicrophoneStream.
|
|
3526
|
+
it('addMedia() works correctly when media is enabled with streams to publish and stream is system muted', async () => {
|
|
3527
|
+
fakeMicrophoneStream.systemMuted = true;
|
|
3249
3528
|
|
|
3250
3529
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
3251
3530
|
await simulateRoapOffer();
|
|
@@ -3278,7 +3557,8 @@ describe('plugin-meetings', () => {
|
|
|
3278
3557
|
assert.calledTwice(locusMediaRequestStub);
|
|
3279
3558
|
});
|
|
3280
3559
|
|
|
3281
|
-
it('addMedia() works correctly when media is disabled with
|
|
3560
|
+
it('addMedia() works correctly when media is disabled with streams to publish', async () => {
|
|
3561
|
+
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
3282
3562
|
await meeting.addMedia({
|
|
3283
3563
|
localStreams: {microphone: fakeMicrophoneStream},
|
|
3284
3564
|
audioEnabled: false,
|
|
@@ -3312,9 +3592,23 @@ describe('plugin-meetings', () => {
|
|
|
3312
3592
|
|
|
3313
3593
|
// and that these were the only /media requests that were sent
|
|
3314
3594
|
assert.calledTwice(locusMediaRequestStub);
|
|
3595
|
+
assert.calledOnce(handleDeviceLoggingSpy);
|
|
3315
3596
|
});
|
|
3316
3597
|
|
|
3317
|
-
it('
|
|
3598
|
+
it('handleDeviceLogging not called when media is disabled', async () => {
|
|
3599
|
+
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
3600
|
+
await meeting.addMedia({
|
|
3601
|
+
localStreams: {microphone: fakeMicrophoneStream},
|
|
3602
|
+
audioEnabled: false,
|
|
3603
|
+
videoEnabled: false
|
|
3604
|
+
});
|
|
3605
|
+
await simulateRoapOffer();
|
|
3606
|
+
await simulateRoapOk();
|
|
3607
|
+
|
|
3608
|
+
assert.notCalled(handleDeviceLoggingSpy);
|
|
3609
|
+
})
|
|
3610
|
+
|
|
3611
|
+
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
3318
3612
|
await meeting.addMedia({audioEnabled: false});
|
|
3319
3613
|
await simulateRoapOffer();
|
|
3320
3614
|
await simulateRoapOk();
|
|
@@ -3347,7 +3641,7 @@ describe('plugin-meetings', () => {
|
|
|
3347
3641
|
assert.calledTwice(locusMediaRequestStub);
|
|
3348
3642
|
});
|
|
3349
3643
|
|
|
3350
|
-
it('addMedia() works correctly when video is disabled with no
|
|
3644
|
+
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
3351
3645
|
await meeting.addMedia({videoEnabled: false});
|
|
3352
3646
|
await simulateRoapOffer();
|
|
3353
3647
|
await simulateRoapOk();
|
|
@@ -3380,7 +3674,7 @@ describe('plugin-meetings', () => {
|
|
|
3380
3674
|
assert.calledTwice(locusMediaRequestStub);
|
|
3381
3675
|
});
|
|
3382
3676
|
|
|
3383
|
-
it('addMedia() works correctly when screen share is disabled with no
|
|
3677
|
+
it('addMedia() works correctly when screen share is disabled with no streams to publish', async () => {
|
|
3384
3678
|
await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
|
|
3385
3679
|
await simulateRoapOffer();
|
|
3386
3680
|
await simulateRoapOk();
|
|
@@ -3481,9 +3775,13 @@ describe('plugin-meetings', () => {
|
|
|
3481
3775
|
const fakeMicrophoneStream2 = {
|
|
3482
3776
|
on: sinon.stub(),
|
|
3483
3777
|
off: sinon.stub(),
|
|
3484
|
-
|
|
3778
|
+
userMuted: false,
|
|
3779
|
+
systemMuted: false,
|
|
3780
|
+
get muted() {
|
|
3781
|
+
return this.userMuted || this.systemMuted;
|
|
3782
|
+
},
|
|
3485
3783
|
setUnmuteAllowed: sinon.stub(),
|
|
3486
|
-
|
|
3784
|
+
setUserMuted: sinon.stub(),
|
|
3487
3785
|
outputStream: {
|
|
3488
3786
|
getTracks: () => {
|
|
3489
3787
|
return [
|
|
@@ -3720,12 +4018,12 @@ describe('plugin-meetings', () => {
|
|
|
3720
4018
|
});
|
|
3721
4019
|
|
|
3722
4020
|
[
|
|
3723
|
-
{mute: true, title: 'muting a track before confluence is created'},
|
|
3724
|
-
{mute: false, title: 'unmuting a track before confluence is created'},
|
|
4021
|
+
{mute: true, title: 'user muting a track before confluence is created'},
|
|
4022
|
+
{mute: false, title: 'user unmuting a track before confluence is created'},
|
|
3725
4023
|
].forEach(({mute, title}) =>
|
|
3726
4024
|
it(title, async () => {
|
|
3727
4025
|
// initialize the microphone mute state to opposite of what we do in the test
|
|
3728
|
-
fakeMicrophoneStream.
|
|
4026
|
+
fakeMicrophoneStream.userMuted = !mute;
|
|
3729
4027
|
|
|
3730
4028
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
3731
4029
|
await stableState();
|
|
@@ -3734,10 +4032,54 @@ describe('plugin-meetings', () => {
|
|
|
3734
4032
|
|
|
3735
4033
|
assert.equal(
|
|
3736
4034
|
fakeMicrophoneStream.on.getCall(0).args[0],
|
|
3737
|
-
|
|
4035
|
+
LocalStreamEventNames.UserMuteStateChange
|
|
3738
4036
|
);
|
|
3739
4037
|
const mutedListener = fakeMicrophoneStream.on.getCall(0).args[1];
|
|
3740
4038
|
// simulate track being muted
|
|
4039
|
+
fakeMicrophoneStream.userMuted = mute;
|
|
4040
|
+
mutedListener(mute);
|
|
4041
|
+
|
|
4042
|
+
await stableState();
|
|
4043
|
+
|
|
4044
|
+
// nothing should happen
|
|
4045
|
+
assert.notCalled(locusMediaRequestStub);
|
|
4046
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
4047
|
+
|
|
4048
|
+
// now simulate roap offer and ok
|
|
4049
|
+
await simulateRoapOffer();
|
|
4050
|
+
await simulateRoapOk();
|
|
4051
|
+
|
|
4052
|
+
// it should be sent with the right mute status
|
|
4053
|
+
checkSdpOfferSent({audioMuted: mute, videoMuted: true});
|
|
4054
|
+
// check OK was sent with the right audioMuted/videoMuted values
|
|
4055
|
+
checkOkSent({audioMuted: mute, videoMuted: true});
|
|
4056
|
+
|
|
4057
|
+
// nothing else should happen
|
|
4058
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
4059
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
4060
|
+
})
|
|
4061
|
+
);
|
|
4062
|
+
|
|
4063
|
+
[
|
|
4064
|
+
{mute: true, title: 'system muting a track before confluence is created'},
|
|
4065
|
+
{mute: false, title: 'system unmuting a track before confluence is created'},
|
|
4066
|
+
].forEach(({mute, title}) =>
|
|
4067
|
+
it(title, async () => {
|
|
4068
|
+
// initialize the microphone mute state to opposite of what we do in the test
|
|
4069
|
+
fakeMicrophoneStream.systemMuted = !mute;
|
|
4070
|
+
|
|
4071
|
+
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
4072
|
+
await stableState();
|
|
4073
|
+
|
|
4074
|
+
resetHistory();
|
|
4075
|
+
|
|
4076
|
+
assert.equal(
|
|
4077
|
+
fakeMicrophoneStream.on.getCall(0).args[0],
|
|
4078
|
+
LocalStreamEventNames.UserMuteStateChange
|
|
4079
|
+
);
|
|
4080
|
+
const mutedListener = fakeMicrophoneStream.on.getCall(0).args[1];
|
|
4081
|
+
// simulate track being muted
|
|
4082
|
+
fakeMicrophoneStream.systemMuted = mute;
|
|
3741
4083
|
mutedListener(mute);
|
|
3742
4084
|
|
|
3743
4085
|
await stableState();
|
|
@@ -6138,6 +6480,65 @@ describe('plugin-meetings', () => {
|
|
|
6138
6480
|
checkScreenShareVideoPublished(videoShareStream);
|
|
6139
6481
|
checkScreenShareAudioPublished(audioShareStream);
|
|
6140
6482
|
});
|
|
6483
|
+
|
|
6484
|
+
[
|
|
6485
|
+
{
|
|
6486
|
+
endedStream: 'microphone',
|
|
6487
|
+
streams: {
|
|
6488
|
+
microphone: {
|
|
6489
|
+
readyState: 'ended',
|
|
6490
|
+
},
|
|
6491
|
+
camera: undefined,
|
|
6492
|
+
screenShare: {
|
|
6493
|
+
audio: undefined,
|
|
6494
|
+
video: undefined,
|
|
6495
|
+
},
|
|
6496
|
+
},
|
|
6497
|
+
},
|
|
6498
|
+
{
|
|
6499
|
+
endedStream: 'camera',
|
|
6500
|
+
streams: {
|
|
6501
|
+
microphone: undefined,
|
|
6502
|
+
camera: {
|
|
6503
|
+
readyState: 'ended',
|
|
6504
|
+
},
|
|
6505
|
+
screenShare: {
|
|
6506
|
+
audio: undefined,
|
|
6507
|
+
video: undefined,
|
|
6508
|
+
},
|
|
6509
|
+
},
|
|
6510
|
+
},
|
|
6511
|
+
{
|
|
6512
|
+
endedStream: 'screenShare audio',
|
|
6513
|
+
streams: {
|
|
6514
|
+
microphone: undefined,
|
|
6515
|
+
camera: undefined,
|
|
6516
|
+
screenShare: {
|
|
6517
|
+
audio: {
|
|
6518
|
+
readyState: 'ended',
|
|
6519
|
+
},
|
|
6520
|
+
video: undefined,
|
|
6521
|
+
},
|
|
6522
|
+
},
|
|
6523
|
+
},
|
|
6524
|
+
{
|
|
6525
|
+
endedStream: 'screenShare video',
|
|
6526
|
+
streams: {
|
|
6527
|
+
microphone: undefined,
|
|
6528
|
+
camera: undefined,
|
|
6529
|
+
screenShare: {
|
|
6530
|
+
audio: undefined,
|
|
6531
|
+
video: {
|
|
6532
|
+
readyState: 'ended',
|
|
6533
|
+
},
|
|
6534
|
+
},
|
|
6535
|
+
},
|
|
6536
|
+
},
|
|
6537
|
+
].forEach(({endedStream, streams}) => {
|
|
6538
|
+
it(`throws error if readyState of ${endedStream} is ended`, async () => {
|
|
6539
|
+
assert.isRejected(meeting.publishStreams(streams));
|
|
6540
|
+
});
|
|
6541
|
+
});
|
|
6141
6542
|
});
|
|
6142
6543
|
|
|
6143
6544
|
describe('unpublishStreams', () => {
|
|
@@ -6272,11 +6673,11 @@ describe('plugin-meetings', () => {
|
|
|
6272
6673
|
meeting.sendSlotManager.setNamedMediaGroups = sinon.stub().returns(undefined);
|
|
6273
6674
|
});
|
|
6274
6675
|
it('should throw error if not audio type', () => {
|
|
6275
|
-
expect(() => meeting.setSendNamedMediaGroup(MediaType.VideoMain, 20)).to.throw(
|
|
6276
|
-
|
|
6676
|
+
expect(() => meeting.setSendNamedMediaGroup(MediaType.VideoMain, 20)).to.throw(
|
|
6677
|
+
`cannot set send named media group which media type is ${MediaType.VideoMain}`
|
|
6678
|
+
);
|
|
6277
6679
|
});
|
|
6278
6680
|
it('fails if there is no media connection', () => {
|
|
6279
|
-
|
|
6280
6681
|
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
6281
6682
|
meeting.setSendNamedMediaGroup('AUDIO-MAIN', 20);
|
|
6282
6683
|
assert.notCalled(meeting.sendSlotManager.setNamedMediaGroups);
|
|
@@ -6285,8 +6686,10 @@ describe('plugin-meetings', () => {
|
|
|
6285
6686
|
it('success if there is media connection', () => {
|
|
6286
6687
|
meeting.isMultistream = true;
|
|
6287
6688
|
meeting.mediaProperties.webrtcMediaConnection = true;
|
|
6288
|
-
meeting.setSendNamedMediaGroup(
|
|
6289
|
-
assert.calledOnceWithExactly(meeting.sendSlotManager.setNamedMediaGroups,
|
|
6689
|
+
meeting.setSendNamedMediaGroup('AUDIO-MAIN', 20);
|
|
6690
|
+
assert.calledOnceWithExactly(meeting.sendSlotManager.setNamedMediaGroups, 'AUDIO-MAIN', [
|
|
6691
|
+
{type: 1, value: 20},
|
|
6692
|
+
]);
|
|
6290
6693
|
});
|
|
6291
6694
|
});
|
|
6292
6695
|
|
|
@@ -7360,6 +7763,7 @@ describe('plugin-meetings', () => {
|
|
|
7360
7763
|
});
|
|
7361
7764
|
it('listens to the self admitted guest event', (done) => {
|
|
7362
7765
|
meeting.stopKeepAlive = sinon.stub();
|
|
7766
|
+
meeting.updateLLMConnection = sinon.stub();
|
|
7363
7767
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
7364
7768
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
7365
7769
|
assert.calledThrice(TriggerProxy.trigger);
|
|
@@ -7370,6 +7774,7 @@ describe('plugin-meetings', () => {
|
|
|
7370
7774
|
'meeting:self:guestAdmitted',
|
|
7371
7775
|
{payload: test1}
|
|
7372
7776
|
);
|
|
7777
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
7373
7778
|
done();
|
|
7374
7779
|
});
|
|
7375
7780
|
|
|
@@ -8291,6 +8696,34 @@ describe('plugin-meetings', () => {
|
|
|
8291
8696
|
|
|
8292
8697
|
checkParseMeetingInfo(expectedInfoToParse);
|
|
8293
8698
|
});
|
|
8699
|
+
|
|
8700
|
+
it('should parse meeting info, set values, and return null when permissionToken is not present', () => {
|
|
8701
|
+
meeting.config.experimental = {enableMediaNegotiatedEvent: true};
|
|
8702
|
+
meeting.config.experimental.enableUnifiedMeetings = true;
|
|
8703
|
+
const FAKE_STRING_DESTINATION = 'sipUrl';
|
|
8704
|
+
const FAKE_MEETING_INFO = {
|
|
8705
|
+
conversationUrl: uuid1,
|
|
8706
|
+
locusUrl: url1,
|
|
8707
|
+
meetingJoinUrl: url2,
|
|
8708
|
+
meetingNumber: '12345',
|
|
8709
|
+
sipMeetingUri: test1,
|
|
8710
|
+
sipUrl: test1,
|
|
8711
|
+
owner: test2,
|
|
8712
|
+
};
|
|
8713
|
+
|
|
8714
|
+
meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_STRING_DESTINATION);
|
|
8715
|
+
const expectedInfoToParse = {
|
|
8716
|
+
conversationUrl: uuid1,
|
|
8717
|
+
locusUrl: url1,
|
|
8718
|
+
sipUri: test1,
|
|
8719
|
+
meetingNumber: '12345',
|
|
8720
|
+
meetingJoinUrl: url2,
|
|
8721
|
+
owner: test2,
|
|
8722
|
+
};
|
|
8723
|
+
|
|
8724
|
+
checkParseMeetingInfo(expectedInfoToParse);
|
|
8725
|
+
});
|
|
8726
|
+
|
|
8294
8727
|
it('should parse interpretation info correctly', () => {
|
|
8295
8728
|
const parseInterpretationInfo = sinon.spy(MeetingUtil, 'parseInterpretationInfo');
|
|
8296
8729
|
const mockToggleOnData = {
|
|
@@ -9427,9 +9860,16 @@ describe('plugin-meetings', () => {
|
|
|
9427
9860
|
it('check triggerAnnotationInfoEvent event', () => {
|
|
9428
9861
|
TriggerProxy.trigger.reset();
|
|
9429
9862
|
const annotationInfo = {version: '1', policy: 'Approval'};
|
|
9430
|
-
const expectAnnotationInfo = {
|
|
9863
|
+
const expectAnnotationInfo = {
|
|
9864
|
+
annotationInfo,
|
|
9865
|
+
meetingId: meeting.id,
|
|
9866
|
+
resourceType: 'FILE',
|
|
9867
|
+
};
|
|
9431
9868
|
meeting.webex.meetings = {};
|
|
9432
|
-
meeting.triggerAnnotationInfoEvent(
|
|
9869
|
+
meeting.triggerAnnotationInfoEvent(
|
|
9870
|
+
{annotation: annotationInfo, resourceType: 'FILE'},
|
|
9871
|
+
{}
|
|
9872
|
+
);
|
|
9433
9873
|
assert.calledWith(
|
|
9434
9874
|
TriggerProxy.trigger,
|
|
9435
9875
|
{},
|
|
@@ -9443,8 +9883,8 @@ describe('plugin-meetings', () => {
|
|
|
9443
9883
|
|
|
9444
9884
|
TriggerProxy.trigger.reset();
|
|
9445
9885
|
meeting.triggerAnnotationInfoEvent(
|
|
9446
|
-
{annotation: annotationInfo},
|
|
9447
|
-
{annotation: annotationInfo}
|
|
9886
|
+
{annotation: annotationInfo, resourceType: 'FILE'},
|
|
9887
|
+
{annotation: annotationInfo, resourceType: 'FILE'}
|
|
9448
9888
|
);
|
|
9449
9889
|
assert.notCalled(TriggerProxy.trigger);
|
|
9450
9890
|
|
|
@@ -9453,10 +9893,11 @@ describe('plugin-meetings', () => {
|
|
|
9453
9893
|
const expectAnnotationInfoUpdated = {
|
|
9454
9894
|
annotationInfo: annotationInfoUpdate,
|
|
9455
9895
|
meetingId: meeting.id,
|
|
9896
|
+
resourceType: 'FILE',
|
|
9456
9897
|
};
|
|
9457
9898
|
meeting.triggerAnnotationInfoEvent(
|
|
9458
|
-
{annotation: annotationInfoUpdate},
|
|
9459
|
-
{annotation: annotationInfo}
|
|
9899
|
+
{annotation: annotationInfoUpdate, resourceType: 'FILE'},
|
|
9900
|
+
{annotation: annotationInfo, resourceType: 'FILE'}
|
|
9460
9901
|
);
|
|
9461
9902
|
assert.calledWith(
|
|
9462
9903
|
TriggerProxy.trigger,
|
|
@@ -9470,7 +9911,10 @@ describe('plugin-meetings', () => {
|
|
|
9470
9911
|
);
|
|
9471
9912
|
|
|
9472
9913
|
TriggerProxy.trigger.reset();
|
|
9473
|
-
meeting.triggerAnnotationInfoEvent(null, {
|
|
9914
|
+
meeting.triggerAnnotationInfoEvent(null, {
|
|
9915
|
+
annotation: annotationInfoUpdate,
|
|
9916
|
+
resourceType: 'FILE',
|
|
9917
|
+
});
|
|
9474
9918
|
assert.notCalled(TriggerProxy.trigger);
|
|
9475
9919
|
});
|
|
9476
9920
|
});
|
|
@@ -9494,6 +9938,11 @@ describe('plugin-meetings', () => {
|
|
|
9494
9938
|
'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3',
|
|
9495
9939
|
};
|
|
9496
9940
|
|
|
9941
|
+
const SHARE_TYPE = {
|
|
9942
|
+
FILE: 'FILE',
|
|
9943
|
+
DESKTOP: 'DESKTOP',
|
|
9944
|
+
};
|
|
9945
|
+
|
|
9497
9946
|
const DEVICE_URL = {
|
|
9498
9947
|
LOCAL_WEB: 'my-web-url',
|
|
9499
9948
|
LOCAL_MAC: 'my-mac-url',
|
|
@@ -9505,11 +9954,14 @@ describe('plugin-meetings', () => {
|
|
|
9505
9954
|
beneficiaryId = null,
|
|
9506
9955
|
disposition = null,
|
|
9507
9956
|
deviceUrlSharing = null,
|
|
9508
|
-
annotation = undefined
|
|
9957
|
+
annotation = undefined,
|
|
9958
|
+
resourceType = undefined
|
|
9509
9959
|
) => ({
|
|
9510
9960
|
beneficiaryId,
|
|
9511
9961
|
disposition,
|
|
9512
9962
|
deviceUrlSharing,
|
|
9963
|
+
annotation,
|
|
9964
|
+
resourceType,
|
|
9513
9965
|
});
|
|
9514
9966
|
const generateWhiteboard = (
|
|
9515
9967
|
beneficiaryId = null,
|
|
@@ -9528,7 +9980,8 @@ describe('plugin-meetings', () => {
|
|
|
9528
9980
|
annotation,
|
|
9529
9981
|
url,
|
|
9530
9982
|
shareInstanceId,
|
|
9531
|
-
deviceUrlSharing
|
|
9983
|
+
deviceUrlSharing,
|
|
9984
|
+
resourceType
|
|
9532
9985
|
) => {
|
|
9533
9986
|
const newPayload = cloneDeep(payload);
|
|
9534
9987
|
|
|
@@ -9562,7 +10015,8 @@ describe('plugin-meetings', () => {
|
|
|
9562
10015
|
beneficiaryId,
|
|
9563
10016
|
FLOOR_ACTION.GRANTED,
|
|
9564
10017
|
deviceUrlSharing,
|
|
9565
|
-
annotation
|
|
10018
|
+
annotation,
|
|
10019
|
+
resourceType
|
|
9566
10020
|
);
|
|
9567
10021
|
|
|
9568
10022
|
if (isEqual(newPayload.current, newPayload.previous)) {
|
|
@@ -9623,6 +10077,7 @@ describe('plugin-meetings', () => {
|
|
|
9623
10077
|
url,
|
|
9624
10078
|
shareInstanceId,
|
|
9625
10079
|
annotationInfo: undefined,
|
|
10080
|
+
resourceType: undefined,
|
|
9626
10081
|
},
|
|
9627
10082
|
});
|
|
9628
10083
|
}
|
|
@@ -10464,7 +10919,8 @@ describe('plugin-meetings', () => {
|
|
|
10464
10919
|
undefined,
|
|
10465
10920
|
undefined,
|
|
10466
10921
|
undefined,
|
|
10467
|
-
DEVICE_URL.REMOTE_A
|
|
10922
|
+
DEVICE_URL.REMOTE_A,
|
|
10923
|
+
undefined
|
|
10468
10924
|
);
|
|
10469
10925
|
const data2 = generateData(
|
|
10470
10926
|
data1.payload,
|
|
@@ -10477,9 +10933,39 @@ describe('plugin-meetings', () => {
|
|
|
10477
10933
|
undefined,
|
|
10478
10934
|
undefined,
|
|
10479
10935
|
undefined,
|
|
10480
|
-
DEVICE_URL.REMOTE_B
|
|
10936
|
+
DEVICE_URL.REMOTE_B,
|
|
10937
|
+
undefined
|
|
10938
|
+
);
|
|
10939
|
+
const data3 = generateData(data2.payload, false, true, USER_IDS.REMOTE_B, undefined);
|
|
10940
|
+
|
|
10941
|
+
payloadTestHelper([data1, data2, data3]);
|
|
10942
|
+
});
|
|
10943
|
+
});
|
|
10944
|
+
|
|
10945
|
+
describe('File Share --> Desktop Share', () => {
|
|
10946
|
+
it('Scenario #1: remote person A shares file then share desktop', () => {
|
|
10947
|
+
const data1 = generateData(
|
|
10948
|
+
blankPayload,
|
|
10949
|
+
true,
|
|
10950
|
+
true,
|
|
10951
|
+
USER_IDS.ME,
|
|
10952
|
+
undefined,
|
|
10953
|
+
false,
|
|
10954
|
+
undefined,
|
|
10955
|
+
undefined,
|
|
10956
|
+
undefined,
|
|
10957
|
+
undefined,
|
|
10958
|
+
DEVICE_URL.LOCAL_WEB,
|
|
10959
|
+
SHARE_TYPE.FILE
|
|
10960
|
+
);
|
|
10961
|
+
const data2 = generateData(
|
|
10962
|
+
data1.payload,
|
|
10963
|
+
true,
|
|
10964
|
+
false,
|
|
10965
|
+
USER_IDS.ME,
|
|
10966
|
+
SHARE_TYPE.DESKTOP
|
|
10481
10967
|
);
|
|
10482
|
-
const data3 = generateData(data2.payload,
|
|
10968
|
+
const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
|
|
10483
10969
|
|
|
10484
10970
|
payloadTestHelper([data1, data2, data3]);
|
|
10485
10971
|
});
|