@webex/plugin-meetings 3.7.0-next.8 → 3.7.0-web-workers-keepalive.1
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/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
- package/dist/common/errors/join-webinar-error.js.map +1 -0
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +26 -5
- package/dist/constants.js.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +13 -2
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +30 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/index.js +903 -800
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +9 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +16 -16
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -17
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +6 -3
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +1 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/roap/index.js +10 -8
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/constants.d.ts +20 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +19 -12
- package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +1 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
- package/dist/types/meetings/index.d.ts +3 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +7 -0
- package/dist/types/metrics/constants.d.ts +1 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/webinar/index.js +354 -3
- package/dist/webinar/index.js.map +1 -1
- package/package.json +23 -22
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/join-webinar-error.ts +24 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/config.ts +1 -1
- package/src/constants.ts +23 -3
- package/src/index.ts +5 -3
- package/src/locus-info/index.ts +17 -2
- package/src/locus-info/selfUtils.ts +19 -6
- package/src/meeting/index.ts +234 -80
- package/src/meeting/locusMediaRequest.ts +7 -0
- package/src/meeting/request.ts +26 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +8 -10
- package/src/meeting-info/meeting-info-v2.ts +23 -11
- package/src/meetings/index.ts +8 -2
- package/src/meetings/util.ts +2 -1
- package/src/member/index.ts +9 -0
- package/src/member/types.ts +8 -0
- package/src/member/util.ts +34 -24
- package/src/metrics/constants.ts +1 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/roap/index.ts +10 -8
- package/src/webinar/index.ts +197 -3
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/locus-info/index.js +222 -0
- package/test/unit/spec/locus-info/selfConstant.js +7 -0
- package/test/unit/spec/locus-info/selfUtils.js +91 -1
- package/test/unit/spec/meeting/index.js +683 -103
- package/test/unit/spec/meeting/utils.js +22 -19
- package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
- package/test/unit/spec/meetings/index.js +9 -5
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/member/util.js +52 -11
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/roap/index.ts +47 -0
- package/test/unit/spec/webinar/index.ts +457 -0
- package/dist/common/errors/webinar-registration-error.js.map +0 -1
- package/src/common/errors/webinar-registration-error.ts +0 -27
|
@@ -91,14 +91,15 @@ import ParameterError from '../../../../src/common/errors/parameter';
|
|
|
91
91
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
92
92
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
93
93
|
import PermissionError from '../../../../src/common/errors/permission';
|
|
94
|
-
import
|
|
94
|
+
import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
|
|
95
95
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
96
|
+
import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';;
|
|
96
97
|
import testUtils from '../../../utils/testUtils';
|
|
97
98
|
import {
|
|
98
99
|
MeetingInfoV2CaptchaError,
|
|
99
100
|
MeetingInfoV2PasswordError,
|
|
100
101
|
MeetingInfoV2PolicyError,
|
|
101
|
-
|
|
102
|
+
MeetingInfoV2JoinWebinarError,
|
|
102
103
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
103
104
|
import {
|
|
104
105
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
@@ -652,7 +653,7 @@ describe('plugin-meetings', () => {
|
|
|
652
653
|
const fakeTurnServerInfo = {id: 'fake turn info'};
|
|
653
654
|
const fakeJoinResult = {id: 'join result'};
|
|
654
655
|
|
|
655
|
-
const joinOptions = {correlationId: '12345'};
|
|
656
|
+
const joinOptions = {correlationId: '12345', enableMultistream: true};
|
|
656
657
|
const mediaOptions = {audioEnabled: true, allowMediaInLobby: true};
|
|
657
658
|
|
|
658
659
|
let generateTurnDiscoveryRequestMessageStub;
|
|
@@ -661,7 +662,10 @@ describe('plugin-meetings', () => {
|
|
|
661
662
|
let addMediaInternalStub;
|
|
662
663
|
|
|
663
664
|
beforeEach(() => {
|
|
664
|
-
meeting.join = sinon.stub().
|
|
665
|
+
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
666
|
+
meeting.isMultistream = joinOptions.enableMultistream;
|
|
667
|
+
return Promise.resolve(fakeJoinResult)
|
|
668
|
+
});
|
|
665
669
|
addMediaInternalStub = sinon
|
|
666
670
|
.stub(meeting, 'addMediaInternal')
|
|
667
671
|
.returns(Promise.resolve(test4));
|
|
@@ -700,7 +704,7 @@ describe('plugin-meetings', () => {
|
|
|
700
704
|
mediaOptions
|
|
701
705
|
);
|
|
702
706
|
|
|
703
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
707
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
704
708
|
|
|
705
709
|
// resets joinWithMediaRetryInfo
|
|
706
710
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -733,7 +737,7 @@ describe('plugin-meetings', () => {
|
|
|
733
737
|
mediaOptions
|
|
734
738
|
);
|
|
735
739
|
|
|
736
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
740
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
737
741
|
assert.equal(meeting.turnServerUsed, false);
|
|
738
742
|
});
|
|
739
743
|
|
|
@@ -768,7 +772,7 @@ describe('plugin-meetings', () => {
|
|
|
768
772
|
mediaOptions
|
|
769
773
|
);
|
|
770
774
|
|
|
771
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
775
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
772
776
|
});
|
|
773
777
|
|
|
774
778
|
it('should reject if join() fails', async () => {
|
|
@@ -855,7 +859,8 @@ describe('plugin-meetings', () => {
|
|
|
855
859
|
}
|
|
856
860
|
);
|
|
857
861
|
|
|
858
|
-
|
|
862
|
+
// expect multistreamEnabled: false, because this test overrides the join meeting.join stub so it doesn't set the isMultistream flag
|
|
863
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: false});
|
|
859
864
|
|
|
860
865
|
// resets joinWithMediaRetryInfo
|
|
861
866
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -944,7 +949,7 @@ describe('plugin-meetings', () => {
|
|
|
944
949
|
mediaOptions,
|
|
945
950
|
});
|
|
946
951
|
|
|
947
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
952
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
948
953
|
|
|
949
954
|
assert.calledOnce(meeting.join);
|
|
950
955
|
assert.notCalled(leaveStub);
|
|
@@ -1038,6 +1043,7 @@ describe('plugin-meetings', () => {
|
|
|
1038
1043
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1039
1044
|
initiateOffer: sinon.stub().resolves({}),
|
|
1040
1045
|
on: sinon.stub(),
|
|
1046
|
+
createSendSlot: sinon.stub(),
|
|
1041
1047
|
};
|
|
1042
1048
|
|
|
1043
1049
|
/* Setup the stubs so that the first call to addMediaInternal() fails
|
|
@@ -1054,12 +1060,14 @@ describe('plugin-meetings', () => {
|
|
|
1054
1060
|
|
|
1055
1061
|
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
1056
1062
|
|
|
1063
|
+
// calling joinWithMedia() with enableMultistream=false, because this test uses real addMediaInternal() implementation
|
|
1064
|
+
// and it requires less stubs when it's without multistream
|
|
1057
1065
|
const result = await meeting.joinWithMedia({
|
|
1058
|
-
joinOptions,
|
|
1066
|
+
joinOptions: {...joinOptions, enableMultistream: false},
|
|
1059
1067
|
mediaOptions,
|
|
1060
1068
|
});
|
|
1061
1069
|
|
|
1062
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: undefined});
|
|
1070
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: undefined, multistreamEnabled: false});
|
|
1063
1071
|
|
|
1064
1072
|
assert.calledOnce(meeting.join);
|
|
1065
1073
|
|
|
@@ -1134,6 +1142,7 @@ describe('plugin-meetings', () => {
|
|
|
1134
1142
|
addMediaError.name = 'SdpOfferCreationError';
|
|
1135
1143
|
|
|
1136
1144
|
meeting.addMediaInternal.rejects(addMediaError);
|
|
1145
|
+
sinon.stub(meeting, 'leave').resolves();
|
|
1137
1146
|
|
|
1138
1147
|
await assert.isRejected(
|
|
1139
1148
|
meeting.joinWithMedia({
|
|
@@ -1162,6 +1171,7 @@ describe('plugin-meetings', () => {
|
|
|
1162
1171
|
type: addMediaError.name,
|
|
1163
1172
|
}
|
|
1164
1173
|
);
|
|
1174
|
+
assert.calledOnceWithExactly(meeting.leave, {resourceId: undefined, reason: 'joinWithMedia failure'})
|
|
1165
1175
|
});
|
|
1166
1176
|
});
|
|
1167
1177
|
|
|
@@ -1238,6 +1248,7 @@ describe('plugin-meetings', () => {
|
|
|
1238
1248
|
webex.internal.voicea.off = sinon.stub();
|
|
1239
1249
|
webex.internal.voicea.listenToEvents = sinon.stub();
|
|
1240
1250
|
webex.internal.voicea.turnOnCaptions = sinon.stub();
|
|
1251
|
+
webex.internal.voicea.deregisterEvents = sinon.stub();
|
|
1241
1252
|
});
|
|
1242
1253
|
|
|
1243
1254
|
it('should stop listening to voicea events and also trigger a stop event', () => {
|
|
@@ -1566,6 +1577,55 @@ describe('plugin-meetings', () => {
|
|
|
1566
1577
|
fakeProcessedReaction
|
|
1567
1578
|
);
|
|
1568
1579
|
});
|
|
1580
|
+
|
|
1581
|
+
it('should fail quietly if participantId does not exist in membersCollection', () => {
|
|
1582
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1583
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1584
|
+
meeting.config.receiveReactions = true;
|
|
1585
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1586
|
+
const fakeReactionPayload = {
|
|
1587
|
+
type: 'fake_type',
|
|
1588
|
+
codepoints: 'fake_codepoints',
|
|
1589
|
+
shortcodes: 'fake_shortcodes',
|
|
1590
|
+
tone: {
|
|
1591
|
+
type: 'fake_tone_type',
|
|
1592
|
+
codepoints: 'fake_tone_codepoints',
|
|
1593
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1594
|
+
},
|
|
1595
|
+
};
|
|
1596
|
+
const fakeSenderPayload = {
|
|
1597
|
+
participantId: 'fake_participant_id',
|
|
1598
|
+
};
|
|
1599
|
+
const fakeProcessedReaction = {
|
|
1600
|
+
reaction: fakeReactionPayload,
|
|
1601
|
+
sender: {
|
|
1602
|
+
id: fakeSenderPayload.participantId,
|
|
1603
|
+
name: fakeSendersName,
|
|
1604
|
+
},
|
|
1605
|
+
};
|
|
1606
|
+
const fakeRelayEvent = {
|
|
1607
|
+
data: {
|
|
1608
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1609
|
+
reaction: fakeReactionPayload,
|
|
1610
|
+
sender: fakeSenderPayload,
|
|
1611
|
+
},
|
|
1612
|
+
};
|
|
1613
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1614
|
+
assert.calledWith(
|
|
1615
|
+
LoggerProxy.logger.warn,
|
|
1616
|
+
`Meeting:index#processRelayEvent --> Skipping handling of react for ${meeting.id}. participantId fake_participant_id does not exist in membersCollection.`
|
|
1617
|
+
);
|
|
1618
|
+
assert.neverCalledWith(
|
|
1619
|
+
TriggerProxy.trigger,
|
|
1620
|
+
sinon.match.instanceOf(Meeting),
|
|
1621
|
+
{
|
|
1622
|
+
file: 'meeting/index',
|
|
1623
|
+
function: 'join',
|
|
1624
|
+
},
|
|
1625
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1626
|
+
fakeProcessedReaction
|
|
1627
|
+
);
|
|
1628
|
+
});
|
|
1569
1629
|
});
|
|
1570
1630
|
|
|
1571
1631
|
describe('#handleLLMOnline', () => {
|
|
@@ -1705,6 +1765,12 @@ describe('plugin-meetings', () => {
|
|
|
1705
1765
|
sinon.assert.called(setCorrelationIdSpy);
|
|
1706
1766
|
assert.equal(meeting.correlationId, '123');
|
|
1707
1767
|
});
|
|
1768
|
+
|
|
1769
|
+
it('should not send client.call.initiated if told not to', async () => {
|
|
1770
|
+
await meeting.join({sendCallInitiated: false});
|
|
1771
|
+
|
|
1772
|
+
sinon.assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
1773
|
+
});
|
|
1708
1774
|
});
|
|
1709
1775
|
|
|
1710
1776
|
describe('failure', () => {
|
|
@@ -2492,9 +2558,11 @@ describe('plugin-meetings', () => {
|
|
|
2492
2558
|
mediaSettings: {},
|
|
2493
2559
|
});
|
|
2494
2560
|
|
|
2495
|
-
const checkLogCounter = (
|
|
2561
|
+
const checkLogCounter = (delayInMinutes, expectedCounter) => {
|
|
2562
|
+
const delayInMilliseconds = delayInMinutes * 60 * 1000;
|
|
2563
|
+
|
|
2496
2564
|
// first check that the counter is not increased just before the delay
|
|
2497
|
-
clock.tick(
|
|
2565
|
+
clock.tick(delayInMilliseconds - 50);
|
|
2498
2566
|
assert.equal(logUploadCounter, expectedCounter - 1);
|
|
2499
2567
|
|
|
2500
2568
|
// and now check that it has reached expected value after the delay
|
|
@@ -2502,22 +2570,18 @@ describe('plugin-meetings', () => {
|
|
|
2502
2570
|
assert.equal(logUploadCounter, expectedCounter);
|
|
2503
2571
|
};
|
|
2504
2572
|
|
|
2505
|
-
checkLogCounter(
|
|
2506
|
-
checkLogCounter(
|
|
2507
|
-
checkLogCounter(
|
|
2508
|
-
checkLogCounter(
|
|
2509
|
-
checkLogCounter(
|
|
2510
|
-
checkLogCounter(30000, 6);
|
|
2511
|
-
checkLogCounter(30000, 7);
|
|
2512
|
-
checkLogCounter(60000, 8);
|
|
2513
|
-
checkLogCounter(60000, 9);
|
|
2514
|
-
checkLogCounter(60000, 10);
|
|
2573
|
+
checkLogCounter(0.1, 1);
|
|
2574
|
+
checkLogCounter(15, 2);
|
|
2575
|
+
checkLogCounter(30, 3);
|
|
2576
|
+
checkLogCounter(60, 4);
|
|
2577
|
+
checkLogCounter(60, 5);
|
|
2515
2578
|
|
|
2516
|
-
// simulate media connection being removed ->
|
|
2579
|
+
// simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
|
|
2517
2580
|
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
2581
|
+
checkLogCounter(60, 6);
|
|
2518
2582
|
|
|
2519
|
-
clock.tick(
|
|
2520
|
-
assert.equal(logUploadCounter,
|
|
2583
|
+
clock.tick(120 * 1000 * 60);
|
|
2584
|
+
assert.equal(logUploadCounter, 6);
|
|
2521
2585
|
|
|
2522
2586
|
clock.restore();
|
|
2523
2587
|
});
|
|
@@ -3475,6 +3539,51 @@ describe('plugin-meetings', () => {
|
|
|
3475
3539
|
});
|
|
3476
3540
|
});
|
|
3477
3541
|
|
|
3542
|
+
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
|
3543
|
+
let fakeMembersCollection = {
|
|
3544
|
+
members: {
|
|
3545
|
+
member1: { isInMeeting: true },
|
|
3546
|
+
member2: { isInMeeting: true },
|
|
3547
|
+
member3: { isInMeeting: false },
|
|
3548
|
+
},
|
|
3549
|
+
};
|
|
3550
|
+
sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
|
|
3551
|
+
const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
|
|
3552
|
+
|
|
3553
|
+
statsAnalyzerStub.emit(
|
|
3554
|
+
{ file: 'test', function: 'test' },
|
|
3555
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3556
|
+
{ data: fakeData }
|
|
3557
|
+
);
|
|
3558
|
+
|
|
3559
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3560
|
+
name: 'client.mediaquality.event',
|
|
3561
|
+
options: {
|
|
3562
|
+
meetingId: meeting.id,
|
|
3563
|
+
},
|
|
3564
|
+
payload: {
|
|
3565
|
+
intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
|
|
3566
|
+
},
|
|
3567
|
+
});
|
|
3568
|
+
fakeMembersCollection.members.member2.isInMeeting = false;
|
|
3569
|
+
|
|
3570
|
+
statsAnalyzerStub.emit(
|
|
3571
|
+
{ file: 'test', function: 'test' },
|
|
3572
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3573
|
+
{ data: fakeData }
|
|
3574
|
+
);
|
|
3575
|
+
|
|
3576
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3577
|
+
name: 'client.mediaquality.event',
|
|
3578
|
+
options: {
|
|
3579
|
+
meetingId: meeting.id,
|
|
3580
|
+
},
|
|
3581
|
+
payload: {
|
|
3582
|
+
intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
|
|
3583
|
+
},
|
|
3584
|
+
});
|
|
3585
|
+
});
|
|
3586
|
+
|
|
3478
3587
|
it('calls submitMQE correctly', async () => {
|
|
3479
3588
|
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
|
3480
3589
|
|
|
@@ -3552,14 +3661,6 @@ describe('plugin-meetings', () => {
|
|
|
3552
3661
|
});
|
|
3553
3662
|
});
|
|
3554
3663
|
|
|
3555
|
-
it('succeeds even if getDevices() throws', async () => {
|
|
3556
|
-
meeting.meetingState = 'ACTIVE';
|
|
3557
|
-
|
|
3558
|
-
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
|
3559
|
-
|
|
3560
|
-
await meeting.addMedia();
|
|
3561
|
-
});
|
|
3562
|
-
|
|
3563
3664
|
describe('CA ice failures checks', () => {
|
|
3564
3665
|
[
|
|
3565
3666
|
{
|
|
@@ -3701,6 +3802,93 @@ describe('plugin-meetings', () => {
|
|
|
3701
3802
|
});
|
|
3702
3803
|
});
|
|
3703
3804
|
|
|
3805
|
+
describe(`#beRightBack`, () => {
|
|
3806
|
+
const fakeMultistreamRoapMediaConnection = {
|
|
3807
|
+
createSendSlot: sinon.stub().returns({
|
|
3808
|
+
setSourceStateOverride: sinon.stub().resolves(),
|
|
3809
|
+
clearSourceStateOverride: sinon.stub().resolves(),
|
|
3810
|
+
}),
|
|
3811
|
+
};
|
|
3812
|
+
|
|
3813
|
+
beforeEach(() => {
|
|
3814
|
+
meeting.meetingRequest.setBrb = sinon.stub().resolves({body: 'test'});
|
|
3815
|
+
meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
|
|
3816
|
+
meeting.sendSlotManager.createSlot(
|
|
3817
|
+
fakeMultistreamRoapMediaConnection,
|
|
3818
|
+
MediaType.VideoMain
|
|
3819
|
+
);
|
|
3820
|
+
|
|
3821
|
+
meeting.locusUrl = 'locus url';
|
|
3822
|
+
meeting.deviceUrl = 'device url';
|
|
3823
|
+
meeting.selfId = 'self id';
|
|
3824
|
+
});
|
|
3825
|
+
|
|
3826
|
+
afterEach(() => {
|
|
3827
|
+
sinon.restore();
|
|
3828
|
+
});
|
|
3829
|
+
|
|
3830
|
+
it('should have #beRightBack', () => {
|
|
3831
|
+
assert.exists(meeting.beRightBack);
|
|
3832
|
+
});
|
|
3833
|
+
|
|
3834
|
+
describe('when in a multistream meeting', () => {
|
|
3835
|
+
|
|
3836
|
+
beforeEach(() => {
|
|
3837
|
+
meeting.isMultistream = true;
|
|
3838
|
+
});
|
|
3839
|
+
|
|
3840
|
+
it('should enable #beRightBack and return a promise', async () => {
|
|
3841
|
+
const brbResult = meeting.beRightBack(true);
|
|
3842
|
+
|
|
3843
|
+
await brbResult;
|
|
3844
|
+
assert.exists(brbResult.then);
|
|
3845
|
+
assert.calledOnce(meeting.meetingRequest.setBrb);
|
|
3846
|
+
})
|
|
3847
|
+
|
|
3848
|
+
it('should disable #beRightBack and return a promise', async () => {
|
|
3849
|
+
const brbResult = meeting.beRightBack(false);
|
|
3850
|
+
|
|
3851
|
+
await brbResult;
|
|
3852
|
+
assert.exists(brbResult.then);
|
|
3853
|
+
assert.calledOnce(meeting.meetingRequest.setBrb);
|
|
3854
|
+
})
|
|
3855
|
+
|
|
3856
|
+
it('should throw an error and reject the promise if setBrb fails', async () => {
|
|
3857
|
+
const error = new Error('setBrb failed');
|
|
3858
|
+
meeting.meetingRequest.setBrb.rejects(error);
|
|
3859
|
+
|
|
3860
|
+
try {
|
|
3861
|
+
await meeting.beRightBack(true);
|
|
3862
|
+
} catch (err) {
|
|
3863
|
+
assert.instanceOf(err, Error);
|
|
3864
|
+
assert.equal(err.message, 'setBrb failed');
|
|
3865
|
+
assert.isRejected((Promise.reject()));
|
|
3866
|
+
}
|
|
3867
|
+
})
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
describe('when in a transcoded meeting', () => {
|
|
3871
|
+
|
|
3872
|
+
beforeEach(() => {
|
|
3873
|
+
meeting.isMultistream = false;
|
|
3874
|
+
});
|
|
3875
|
+
|
|
3876
|
+
it('should ignore enabling #beRightBack', async () => {
|
|
3877
|
+
meeting.beRightBack(true);
|
|
3878
|
+
|
|
3879
|
+
assert.isRejected((Promise.reject()));
|
|
3880
|
+
assert.notCalled(meeting.meetingRequest.setBrb);
|
|
3881
|
+
})
|
|
3882
|
+
|
|
3883
|
+
it('should ignore disabling #beRightBack', async () => {
|
|
3884
|
+
meeting.beRightBack(false);
|
|
3885
|
+
|
|
3886
|
+
assert.isRejected((Promise.reject()));
|
|
3887
|
+
assert.notCalled(meeting.meetingRequest.setBrb);
|
|
3888
|
+
})
|
|
3889
|
+
});
|
|
3890
|
+
});
|
|
3891
|
+
|
|
3704
3892
|
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
3705
3893
|
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
3706
3894
|
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
@@ -3743,8 +3931,12 @@ describe('plugin-meetings', () => {
|
|
|
3743
3931
|
meeting.setMercuryListener = sinon.stub();
|
|
3744
3932
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
3745
3933
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
3746
|
-
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
|
|
3747
|
-
|
|
3934
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
|
|
3935
|
+
.stub()
|
|
3936
|
+
.resolves({id: 'fake reachability'});
|
|
3937
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
|
|
3938
|
+
.stub()
|
|
3939
|
+
.resolves({id: 'fake clientMediaPreferences'});
|
|
3748
3940
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
3749
3941
|
turnServerInfo: {
|
|
3750
3942
|
url: 'turns:turn-server-url:443?transport=tcp',
|
|
@@ -3825,6 +4017,7 @@ describe('plugin-meetings', () => {
|
|
|
3825
4017
|
initiateOffer: sinon.stub().resolves({}),
|
|
3826
4018
|
update: sinon.stub().resolves({}),
|
|
3827
4019
|
on: sinon.stub(),
|
|
4020
|
+
roapMessageReceived: sinon.stub()
|
|
3828
4021
|
};
|
|
3829
4022
|
|
|
3830
4023
|
fakeMultistreamRoapMediaConnection = {
|
|
@@ -3911,8 +4104,10 @@ describe('plugin-meetings', () => {
|
|
|
3911
4104
|
};
|
|
3912
4105
|
|
|
3913
4106
|
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
3914
|
-
const simulateRoapOffer = async () => {
|
|
3915
|
-
|
|
4107
|
+
const simulateRoapOffer = async (stubWaitingForAnswer = true) => {
|
|
4108
|
+
if (stubWaitingForAnswer) {
|
|
4109
|
+
meeting.deferSDPAnswer = {resolve: sinon.stub()};
|
|
4110
|
+
}
|
|
3916
4111
|
const roapListener = getRoapListener();
|
|
3917
4112
|
|
|
3918
4113
|
await roapListener({roapMessage: roapOfferMessage});
|
|
@@ -3930,8 +4125,14 @@ describe('plugin-meetings', () => {
|
|
|
3930
4125
|
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
3931
4126
|
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
3932
4127
|
|
|
3933
|
-
assert.calledWith(
|
|
3934
|
-
|
|
4128
|
+
assert.calledWith(
|
|
4129
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences,
|
|
4130
|
+
meeting.isMultistream,
|
|
4131
|
+
0
|
|
4132
|
+
);
|
|
4133
|
+
assert.calledWith(
|
|
4134
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
|
|
4135
|
+
);
|
|
3935
4136
|
|
|
3936
4137
|
assert.calledWith(locusMediaRequestStub, {
|
|
3937
4138
|
method: 'PUT',
|
|
@@ -4015,8 +4216,9 @@ describe('plugin-meetings', () => {
|
|
|
4015
4216
|
remoteQualityLevel,
|
|
4016
4217
|
expectedDebugId,
|
|
4017
4218
|
meetingId,
|
|
4219
|
+
expectMultistream = isMultistream,
|
|
4018
4220
|
}) => {
|
|
4019
|
-
if (
|
|
4221
|
+
if (expectMultistream) {
|
|
4020
4222
|
const {iceServers} = mediaConnectionConfig;
|
|
4021
4223
|
|
|
4022
4224
|
assert.calledOnceWithMatch(
|
|
@@ -4176,7 +4378,6 @@ describe('plugin-meetings', () => {
|
|
|
4176
4378
|
});
|
|
4177
4379
|
|
|
4178
4380
|
it('addMedia() works correctly when media is enabled with streams to publish', async () => {
|
|
4179
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4180
4381
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
4181
4382
|
await simulateRoapOffer();
|
|
4182
4383
|
await simulateRoapOk();
|
|
@@ -4207,12 +4408,9 @@ describe('plugin-meetings', () => {
|
|
|
4207
4408
|
|
|
4208
4409
|
// and that these were the only /media requests that were sent
|
|
4209
4410
|
assert.calledTwice(locusMediaRequestStub);
|
|
4210
|
-
|
|
4211
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4212
4411
|
});
|
|
4213
4412
|
|
|
4214
4413
|
it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
|
|
4215
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4216
4414
|
fakeMicrophoneStream.userMuted = true;
|
|
4217
4415
|
|
|
4218
4416
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
@@ -4244,7 +4442,6 @@ describe('plugin-meetings', () => {
|
|
|
4244
4442
|
|
|
4245
4443
|
// and that these were the only /media requests that were sent
|
|
4246
4444
|
assert.calledTwice(locusMediaRequestStub);
|
|
4247
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4248
4445
|
});
|
|
4249
4446
|
|
|
4250
4447
|
it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
|
|
@@ -4316,7 +4513,6 @@ describe('plugin-meetings', () => {
|
|
|
4316
4513
|
});
|
|
4317
4514
|
|
|
4318
4515
|
it('addMedia() works correctly when media is disabled with streams to publish', async () => {
|
|
4319
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4320
4516
|
await meeting.addMedia({
|
|
4321
4517
|
localStreams: {microphone: fakeMicrophoneStream},
|
|
4322
4518
|
audioEnabled: false,
|
|
@@ -4350,20 +4546,6 @@ describe('plugin-meetings', () => {
|
|
|
4350
4546
|
|
|
4351
4547
|
// and that these were the only /media requests that were sent
|
|
4352
4548
|
assert.calledTwice(locusMediaRequestStub);
|
|
4353
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4354
|
-
});
|
|
4355
|
-
|
|
4356
|
-
it('handleDeviceLogging not called when media is disabled', async () => {
|
|
4357
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4358
|
-
await meeting.addMedia({
|
|
4359
|
-
localStreams: {microphone: fakeMicrophoneStream},
|
|
4360
|
-
audioEnabled: false,
|
|
4361
|
-
videoEnabled: false,
|
|
4362
|
-
});
|
|
4363
|
-
await simulateRoapOffer();
|
|
4364
|
-
await simulateRoapOk();
|
|
4365
|
-
|
|
4366
|
-
assert.notCalled(handleDeviceLoggingSpy);
|
|
4367
4549
|
});
|
|
4368
4550
|
|
|
4369
4551
|
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
@@ -4399,20 +4581,6 @@ describe('plugin-meetings', () => {
|
|
|
4399
4581
|
assert.calledTwice(locusMediaRequestStub);
|
|
4400
4582
|
});
|
|
4401
4583
|
|
|
4402
|
-
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
4403
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4404
|
-
await meeting.addMedia({audioEnabled: false});
|
|
4405
|
-
//calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
|
|
4406
|
-
assert.calledWith(handleDeviceLoggingSpy, false, true);
|
|
4407
|
-
});
|
|
4408
|
-
|
|
4409
|
-
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4410
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4411
|
-
await meeting.addMedia({videoEnabled: false});
|
|
4412
|
-
//calling handleDeviceLogging audioEnabled as true videoEnabled as false
|
|
4413
|
-
assert.calledWith(handleDeviceLoggingSpy, true, false);
|
|
4414
|
-
});
|
|
4415
|
-
|
|
4416
4584
|
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4417
4585
|
await meeting.addMedia({videoEnabled: false});
|
|
4418
4586
|
await simulateRoapOffer();
|
|
@@ -4479,13 +4647,6 @@ describe('plugin-meetings', () => {
|
|
|
4479
4647
|
assert.calledTwice(locusMediaRequestStub);
|
|
4480
4648
|
});
|
|
4481
4649
|
|
|
4482
|
-
it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
|
|
4483
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4484
|
-
await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
|
|
4485
|
-
//calling handleDeviceLogging with audioEnabled true and videoEnabled as true
|
|
4486
|
-
assert.calledWith(handleDeviceLoggingSpy, true, true);
|
|
4487
|
-
});
|
|
4488
|
-
|
|
4489
4650
|
describe('publishStreams()/unpublishStreams() calls', () => {
|
|
4490
4651
|
[
|
|
4491
4652
|
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
@@ -4881,6 +5042,211 @@ describe('plugin-meetings', () => {
|
|
|
4881
5042
|
assert.notCalled(fakeRoapMediaConnection.update);
|
|
4882
5043
|
})
|
|
4883
5044
|
);
|
|
5045
|
+
|
|
5046
|
+
if (isMultistream) {
|
|
5047
|
+
describe('fallback from multistream to transcoded', () => {
|
|
5048
|
+
let multistreamEventListeners;
|
|
5049
|
+
let transcodedEventListeners;
|
|
5050
|
+
let mockStatsAnalyzerCtor;
|
|
5051
|
+
|
|
5052
|
+
const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
|
|
5053
|
+
fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
|
|
5054
|
+
eventListeners[eventName] = cb;
|
|
5055
|
+
});
|
|
5056
|
+
fakeRoapMediaConnection.initiateOffer.callsFake(() => {
|
|
5057
|
+
// simulate offer being generated
|
|
5058
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
5059
|
+
|
|
5060
|
+
return Promise.resolve();
|
|
5061
|
+
});
|
|
5062
|
+
};
|
|
5063
|
+
|
|
5064
|
+
beforeEach(() => {
|
|
5065
|
+
multistreamEventListeners = {};
|
|
5066
|
+
transcodedEventListeners = {};
|
|
5067
|
+
|
|
5068
|
+
meeting.config.stats.enableStatsAnalyzer = true;
|
|
5069
|
+
|
|
5070
|
+
setupFakeRoapMediaConnection(fakeRoapMediaConnection, transcodedEventListeners);
|
|
5071
|
+
setupFakeRoapMediaConnection(
|
|
5072
|
+
fakeMultistreamRoapMediaConnection,
|
|
5073
|
+
multistreamEventListeners
|
|
5074
|
+
);
|
|
5075
|
+
|
|
5076
|
+
mockStatsAnalyzerCtor = sinon
|
|
5077
|
+
.stub(InternalMediaCoreModule, 'StatsAnalyzer')
|
|
5078
|
+
.callsFake(() => {
|
|
5079
|
+
return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
|
|
5080
|
+
});
|
|
5081
|
+
|
|
5082
|
+
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
5083
|
+
sinon.stub();
|
|
5084
|
+
|
|
5085
|
+
// setup the mock so that we get an SDP answer not from Homer
|
|
5086
|
+
locusMediaRequestStub.callsFake(() => {
|
|
5087
|
+
return Promise.resolve({
|
|
5088
|
+
body: {
|
|
5089
|
+
locus: {},
|
|
5090
|
+
mediaConnections: [
|
|
5091
|
+
{
|
|
5092
|
+
remoteSdp:
|
|
5093
|
+
'{"audioMuted":false,"videoMuted":true,"roapMessage":{"messageType":"ANSWER","version":"2","seq":1,"sdps":["v=0\\r\\no=linus 0 1 IN IP4 23.89.67.4\\r\\ns=-\\r\\nc=IN IP4 23.89.67.4\\r\\n"],"headers":["noOkInTransaction"]},"type":"SDP"}',
|
|
5094
|
+
},
|
|
5095
|
+
],
|
|
5096
|
+
},
|
|
5097
|
+
});
|
|
5098
|
+
});
|
|
5099
|
+
|
|
5100
|
+
sinon.stub(meeting, 'closePeerConnections');
|
|
5101
|
+
sinon.stub(meeting.mediaProperties, 'unsetPeerConnection');
|
|
5102
|
+
sinon.stub(meeting.locusMediaRequest, 'downgradeFromMultistreamToTranscoded');
|
|
5103
|
+
});
|
|
5104
|
+
|
|
5105
|
+
const runCheck = async (turnServerInfo, forceTurnDiscovery) => {
|
|
5106
|
+
// we're calling addMediaInternal() with mic stream,
|
|
5107
|
+
// so that we also verify that audioMute, videoMute info is correctly sent to backend
|
|
5108
|
+
const addMediaPromise = meeting.addMediaInternal(
|
|
5109
|
+
() => '',
|
|
5110
|
+
turnServerInfo,
|
|
5111
|
+
forceTurnDiscovery,
|
|
5112
|
+
{
|
|
5113
|
+
localStreams: {microphone: fakeMicrophoneStream},
|
|
5114
|
+
}
|
|
5115
|
+
);
|
|
5116
|
+
await testUtils.flushPromises();
|
|
5117
|
+
await simulateRoapOffer(false);
|
|
5118
|
+
|
|
5119
|
+
// check MultistreamRoapMediaConnection was created correctly
|
|
5120
|
+
checkMediaConnectionCreated({
|
|
5121
|
+
expectMultistream: true,
|
|
5122
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5123
|
+
localStreams: {
|
|
5124
|
+
audio: fakeMicrophoneStream,
|
|
5125
|
+
video: undefined,
|
|
5126
|
+
screenShareVideo: undefined,
|
|
5127
|
+
screenShareAudio: undefined,
|
|
5128
|
+
},
|
|
5129
|
+
direction: {
|
|
5130
|
+
audio: 'sendrecv',
|
|
5131
|
+
video: 'sendrecv',
|
|
5132
|
+
screenShare: 'recvonly',
|
|
5133
|
+
},
|
|
5134
|
+
remoteQualityLevel: 'HIGH',
|
|
5135
|
+
expectedDebugId,
|
|
5136
|
+
meetingId: meeting.id,
|
|
5137
|
+
});
|
|
5138
|
+
|
|
5139
|
+
// check that stats analyzer was created with the right config and store the reference to it so that we can later check that it was stopped
|
|
5140
|
+
assert.calledOnceWithExactly(
|
|
5141
|
+
mockStatsAnalyzerCtor,
|
|
5142
|
+
sinon.match({
|
|
5143
|
+
isMultistream: true,
|
|
5144
|
+
})
|
|
5145
|
+
);
|
|
5146
|
+
const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];
|
|
5147
|
+
mockStatsAnalyzerCtor.resetHistory();
|
|
5148
|
+
|
|
5149
|
+
// TURN discovery was done (if needed)
|
|
5150
|
+
if (turnServerInfo) {
|
|
5151
|
+
assert.notCalled(meeting.roap.doTurnDiscovery);
|
|
5152
|
+
} else {
|
|
5153
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false, false);
|
|
5154
|
+
}
|
|
5155
|
+
|
|
5156
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
5157
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5158
|
+
|
|
5159
|
+
await testUtils.flushPromises();
|
|
5160
|
+
|
|
5161
|
+
// at this point the meeting should have been downgraded to transcoded
|
|
5162
|
+
assert.equal(meeting.isMultistream, false);
|
|
5163
|
+
|
|
5164
|
+
// old stats analyzer stopped and new one created
|
|
5165
|
+
assert.calledOnce(initialStatsAnalyzer.stopAnalyzer);
|
|
5166
|
+
assert.calledOnceWithExactly(
|
|
5167
|
+
mockStatsAnalyzerCtor,
|
|
5168
|
+
sinon.match({
|
|
5169
|
+
isMultistream: false,
|
|
5170
|
+
})
|
|
5171
|
+
);
|
|
5172
|
+
|
|
5173
|
+
// and correct cleanup of other things should have been done
|
|
5174
|
+
assert.calledOnceWithExactly(meeting.closePeerConnections, false);
|
|
5175
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.unsetPeerConnection);
|
|
5176
|
+
assert.calledOnceWithExactly(
|
|
5177
|
+
meeting.locusMediaRequest.downgradeFromMultistreamToTranscoded
|
|
5178
|
+
);
|
|
5179
|
+
|
|
5180
|
+
// new connection should have been created
|
|
5181
|
+
checkMediaConnectionCreated({
|
|
5182
|
+
expectMultistream: false,
|
|
5183
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5184
|
+
localStreams: {
|
|
5185
|
+
audio: fakeMicrophoneStream,
|
|
5186
|
+
video: undefined,
|
|
5187
|
+
screenShareVideo: undefined,
|
|
5188
|
+
screenShareAudio: undefined,
|
|
5189
|
+
},
|
|
5190
|
+
direction: {
|
|
5191
|
+
audio: 'sendrecv',
|
|
5192
|
+
video: 'sendrecv',
|
|
5193
|
+
screenShare: 'recvonly',
|
|
5194
|
+
},
|
|
5195
|
+
remoteQualityLevel: 'HIGH',
|
|
5196
|
+
expectedDebugId,
|
|
5197
|
+
meetingId: meeting.id,
|
|
5198
|
+
});
|
|
5199
|
+
|
|
5200
|
+
// and new TURN discovery done (no matter if it was being done before or not)
|
|
5201
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, true, true);
|
|
5202
|
+
|
|
5203
|
+
// simulate new offer
|
|
5204
|
+
await simulateRoapOffer(false);
|
|
5205
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5206
|
+
|
|
5207
|
+
// overall there should have been 2 calls to locusMediaRequestStub, because 2 offers were sent
|
|
5208
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
5209
|
+
|
|
5210
|
+
// simulate answer being processed correctly
|
|
5211
|
+
transcodedEventListeners[MediaConnectionEventNames.REMOTE_SDP_ANSWER_PROCESSED]();
|
|
5212
|
+
|
|
5213
|
+
// check that addMedia finally resolved
|
|
5214
|
+
await addMediaPromise;
|
|
5215
|
+
};
|
|
5216
|
+
|
|
5217
|
+
it('addMedia() falls back to transcoded if SDP answer is not from Homer', async () => {
|
|
5218
|
+
// call addMediaInternal like addMedia() does it
|
|
5219
|
+
await runCheck(undefined, false);
|
|
5220
|
+
});
|
|
5221
|
+
|
|
5222
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() case)', async () => {
|
|
5223
|
+
// call addMediaInternal the way joinWithMedia() does it - with TURN info already provided
|
|
5224
|
+
// and check that when we fallback to transcoded we still do another TURN discovery
|
|
5225
|
+
await runCheck(
|
|
5226
|
+
{
|
|
5227
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5228
|
+
username: 'turn user',
|
|
5229
|
+
password: 'turn password',
|
|
5230
|
+
},
|
|
5231
|
+
false
|
|
5232
|
+
);
|
|
5233
|
+
});
|
|
5234
|
+
|
|
5235
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() retry case)', async () => {
|
|
5236
|
+
// call addMediaInternal the way joinWithMedia() does it when it does a retry - with TURN info already provided
|
|
5237
|
+
// but also with forceTurnDiscovery=true - this shouldn't affect the flow for fallback to transcoded in any way
|
|
5238
|
+
// but doing it just for completeness
|
|
5239
|
+
await runCheck(
|
|
5240
|
+
{
|
|
5241
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5242
|
+
username: 'turn user',
|
|
5243
|
+
password: 'turn password',
|
|
5244
|
+
},
|
|
5245
|
+
true
|
|
5246
|
+
);
|
|
5247
|
+
});
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
4884
5250
|
})
|
|
4885
5251
|
);
|
|
4886
5252
|
|
|
@@ -4958,6 +5324,11 @@ describe('plugin-meetings', () => {
|
|
|
4958
5324
|
meeting.logger.error = sinon.stub().returns(true);
|
|
4959
5325
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
4960
5326
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
5327
|
+
meeting.stopTranscription = sinon.stub();
|
|
5328
|
+
meeting.transcription = {};
|
|
5329
|
+
|
|
5330
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
5331
|
+
webex.internal.llm.off = sinon.stub();
|
|
4961
5332
|
|
|
4962
5333
|
// A meeting needs to be joined to leave
|
|
4963
5334
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -4978,6 +5349,9 @@ describe('plugin-meetings', () => {
|
|
|
4978
5349
|
assert.calledOnce(meeting.closePeerConnections);
|
|
4979
5350
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
4980
5351
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
5352
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
5353
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
5354
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
4981
5355
|
});
|
|
4982
5356
|
|
|
4983
5357
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -6332,29 +6706,74 @@ describe('plugin-meetings', () => {
|
|
|
6332
6706
|
assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
|
|
6333
6707
|
});
|
|
6334
6708
|
|
|
6335
|
-
it('handles
|
|
6709
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
|
|
6336
6710
|
meeting.destination = FAKE_DESTINATION;
|
|
6337
6711
|
meeting.destinationType = FAKE_TYPE;
|
|
6338
6712
|
meeting.attrs.meetingInfoProvider = {
|
|
6339
6713
|
fetchMeetingInfo: sinon
|
|
6340
6714
|
.stub()
|
|
6341
6715
|
.throws(
|
|
6342
|
-
new
|
|
6716
|
+
new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
|
|
6343
6717
|
),
|
|
6344
6718
|
};
|
|
6345
6719
|
|
|
6346
6720
|
await assert.isRejected(
|
|
6347
6721
|
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6348
|
-
|
|
6722
|
+
JoinWebinarError
|
|
6349
6723
|
);
|
|
6350
6724
|
|
|
6351
6725
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6352
|
-
assert.equal(meeting.meetingInfoFailureCode, 403021);
|
|
6353
6726
|
assert.equal(
|
|
6354
6727
|
meeting.meetingInfoFailureReason,
|
|
6355
6728
|
MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
|
|
6356
6729
|
);
|
|
6357
6730
|
});
|
|
6731
|
+
|
|
6732
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
|
|
6733
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6734
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6735
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6736
|
+
fetchMeetingInfo: sinon
|
|
6737
|
+
.stub()
|
|
6738
|
+
.throws(
|
|
6739
|
+
new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
|
|
6740
|
+
),
|
|
6741
|
+
};
|
|
6742
|
+
|
|
6743
|
+
await assert.isRejected(
|
|
6744
|
+
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6745
|
+
JoinWebinarError
|
|
6746
|
+
);
|
|
6747
|
+
|
|
6748
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6749
|
+
assert.equal(
|
|
6750
|
+
meeting.meetingInfoFailureReason,
|
|
6751
|
+
MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
|
|
6752
|
+
);
|
|
6753
|
+
});
|
|
6754
|
+
|
|
6755
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
|
|
6756
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6757
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6758
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6759
|
+
fetchMeetingInfo: sinon
|
|
6760
|
+
.stub()
|
|
6761
|
+
.throws(
|
|
6762
|
+
new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
|
|
6763
|
+
),
|
|
6764
|
+
};
|
|
6765
|
+
|
|
6766
|
+
await assert.isRejected(
|
|
6767
|
+
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6768
|
+
JoinWebinarError
|
|
6769
|
+
);
|
|
6770
|
+
|
|
6771
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6772
|
+
assert.equal(
|
|
6773
|
+
meeting.meetingInfoFailureReason,
|
|
6774
|
+
MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
|
|
6775
|
+
);
|
|
6776
|
+
});
|
|
6358
6777
|
});
|
|
6359
6778
|
|
|
6360
6779
|
describe('#refreshPermissionToken', () => {
|
|
@@ -6815,6 +7234,9 @@ describe('plugin-meetings', () => {
|
|
|
6815
7234
|
meeting.transcription = {};
|
|
6816
7235
|
meeting.stopTranscription = sinon.stub();
|
|
6817
7236
|
|
|
7237
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
7238
|
+
webex.internal.llm.off = sinon.stub();
|
|
7239
|
+
|
|
6818
7240
|
// A meeting needs to be joined to end
|
|
6819
7241
|
meeting.meetingState = 'ACTIVE';
|
|
6820
7242
|
meeting.state = 'JOINED';
|
|
@@ -6835,6 +7257,9 @@ describe('plugin-meetings', () => {
|
|
|
6835
7257
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
6836
7258
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
6837
7259
|
assert.calledOnce(meeting?.stopTranscription);
|
|
7260
|
+
|
|
7261
|
+
assert.called(meeting.annotation.deregisterEvents);
|
|
7262
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
6838
7263
|
});
|
|
6839
7264
|
});
|
|
6840
7265
|
|
|
@@ -7817,7 +8242,9 @@ describe('plugin-meetings', () => {
|
|
|
7817
8242
|
});
|
|
7818
8243
|
|
|
7819
8244
|
it('should collect ice candidates', () => {
|
|
7820
|
-
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8245
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8246
|
+
candidate: {candidate: 'candidate'},
|
|
8247
|
+
});
|
|
7821
8248
|
|
|
7822
8249
|
assert.equal(meeting.iceCandidatesCount, 1);
|
|
7823
8250
|
});
|
|
@@ -8123,10 +8550,10 @@ describe('plugin-meetings', () => {
|
|
|
8123
8550
|
meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
|
|
8124
8551
|
meeting.reconnectionManager = {
|
|
8125
8552
|
reconnect: sinon.stub().resolves(),
|
|
8126
|
-
resetReconnectionTimer: () => {}
|
|
8553
|
+
resetReconnectionTimer: () => {},
|
|
8127
8554
|
};
|
|
8128
8555
|
meeting.currentMediaStatus = {
|
|
8129
|
-
video: true
|
|
8556
|
+
video: true,
|
|
8130
8557
|
};
|
|
8131
8558
|
|
|
8132
8559
|
await mockFailedEvent();
|
|
@@ -8408,8 +8835,7 @@ describe('plugin-meetings', () => {
|
|
|
8408
8835
|
assert.calledWith(meeting.roapMessageReceived, fakeAnswer);
|
|
8409
8836
|
});
|
|
8410
8837
|
|
|
8411
|
-
|
|
8412
|
-
const fakeError = new Error('fake error');
|
|
8838
|
+
const runOfferSendingFailureTest = async (fakeError, canProceed, expectedErrorCode) => {
|
|
8413
8839
|
const clock = sinon.useFakeTimers();
|
|
8414
8840
|
sinon.spy(clock, 'clearTimeout');
|
|
8415
8841
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
|
@@ -8447,19 +8873,31 @@ describe('plugin-meetings', () => {
|
|
|
8447
8873
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
|
8448
8874
|
|
|
8449
8875
|
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
8450
|
-
clientErrorCode:
|
|
8876
|
+
clientErrorCode: expectedErrorCode,
|
|
8451
8877
|
});
|
|
8452
8878
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8453
8879
|
name: 'client.media-engine.remote-sdp-received',
|
|
8454
8880
|
payload: {
|
|
8455
|
-
canProceed
|
|
8456
|
-
errors: [{errorCode:
|
|
8881
|
+
canProceed,
|
|
8882
|
+
errors: [{errorCode: expectedErrorCode, fatal: true}],
|
|
8457
8883
|
},
|
|
8458
8884
|
options: {
|
|
8459
8885
|
meetingId: meeting.id,
|
|
8460
8886
|
rawError: fakeError,
|
|
8461
8887
|
},
|
|
8462
8888
|
});
|
|
8889
|
+
};
|
|
8890
|
+
|
|
8891
|
+
it('handles OFFER message correctly when request fails', async () => {
|
|
8892
|
+
const fakeError = new Error('fake error');
|
|
8893
|
+
|
|
8894
|
+
await runOfferSendingFailureTest(fakeError, false, 2007);
|
|
8895
|
+
});
|
|
8896
|
+
|
|
8897
|
+
it('handles OFFER message correctly when we get a non-homer answer', async () => {
|
|
8898
|
+
const fakeError = new MultistreamNotSupportedError();
|
|
8899
|
+
|
|
8900
|
+
await runOfferSendingFailureTest(fakeError, true, 2012);
|
|
8463
8901
|
});
|
|
8464
8902
|
|
|
8465
8903
|
it('handles ANSWER message correctly', () => {
|
|
@@ -8662,6 +9100,7 @@ describe('plugin-meetings', () => {
|
|
|
8662
9100
|
});
|
|
8663
9101
|
});
|
|
8664
9102
|
});
|
|
9103
|
+
|
|
8665
9104
|
describe('#setUpLocusInfoSelfListener', () => {
|
|
8666
9105
|
it('listens to the self unadmitted guest event', (done) => {
|
|
8667
9106
|
meeting.startKeepAlive = sinon.stub();
|
|
@@ -8756,6 +9195,26 @@ describe('plugin-meetings', () => {
|
|
|
8756
9195
|
);
|
|
8757
9196
|
});
|
|
8758
9197
|
|
|
9198
|
+
it('listens to the brb state changed event', () => {
|
|
9199
|
+
const assertBrb = (enabled) => {
|
|
9200
|
+
meeting.locusInfo.emit(
|
|
9201
|
+
{ function: 'test', file: 'test' },
|
|
9202
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
9203
|
+
{ brb: { enabled } },
|
|
9204
|
+
)
|
|
9205
|
+
assert.calledWithExactly(
|
|
9206
|
+
TriggerProxy.trigger,
|
|
9207
|
+
meeting,
|
|
9208
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
9209
|
+
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
|
|
9210
|
+
{ payload: { brb: { enabled } } },
|
|
9211
|
+
);
|
|
9212
|
+
}
|
|
9213
|
+
|
|
9214
|
+
assertBrb(true);
|
|
9215
|
+
assertBrb(false);
|
|
9216
|
+
})
|
|
9217
|
+
|
|
8759
9218
|
it('listens to the interpretation changed event', () => {
|
|
8760
9219
|
meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
|
|
8761
9220
|
|
|
@@ -9054,7 +9513,7 @@ describe('plugin-meetings', () => {
|
|
|
9054
9513
|
{state}
|
|
9055
9514
|
);
|
|
9056
9515
|
|
|
9057
|
-
assert.calledOnceWithExactly(
|
|
9516
|
+
assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
|
|
9058
9517
|
assert.calledWith(
|
|
9059
9518
|
TriggerProxy.trigger,
|
|
9060
9519
|
meeting,
|
|
@@ -9538,14 +9997,39 @@ describe('plugin-meetings', () => {
|
|
|
9538
9997
|
it('should close the webrtc media connection, and return a promise', async () => {
|
|
9539
9998
|
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
9540
9999
|
meeting.mediaProperties.webrtcMediaConnection = {close: sinon.stub()};
|
|
10000
|
+
|
|
10001
|
+
meeting.audio = {id: 'fakeAudioMuteState'};
|
|
10002
|
+
meeting.video = {id: 'fakeVideoMuteState'};
|
|
10003
|
+
|
|
9541
10004
|
const pcs = meeting.closePeerConnections();
|
|
9542
10005
|
|
|
9543
10006
|
assert.exists(pcs.then);
|
|
9544
10007
|
await pcs;
|
|
9545
10008
|
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.close);
|
|
9546
10009
|
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10010
|
+
assert.equal(meeting.audio, null);
|
|
10011
|
+
assert.equal(meeting.video, null);
|
|
10012
|
+
});
|
|
10013
|
+
|
|
10014
|
+
it('should close the webrtc media connection, but keep audio and video props unchanged if called with resetMuteStates=false', async () => {
|
|
10015
|
+
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
10016
|
+
meeting.mediaProperties.webrtcMediaConnection = {close: sinon.stub()};
|
|
10017
|
+
|
|
10018
|
+
const fakeAudio = {id: 'fakeAudioMuteState'};
|
|
10019
|
+
const fakeVideo = {id: 'fakeVideoMuteState'};
|
|
10020
|
+
|
|
10021
|
+
meeting.audio = fakeAudio;
|
|
10022
|
+
meeting.video = fakeVideo;
|
|
10023
|
+
|
|
10024
|
+
await meeting.closePeerConnections(false);
|
|
10025
|
+
|
|
10026
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.close);
|
|
10027
|
+
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10028
|
+
assert.equal(meeting.audio, fakeAudio);
|
|
10029
|
+
assert.equal(meeting.video, fakeVideo);
|
|
9547
10030
|
});
|
|
9548
10031
|
});
|
|
10032
|
+
|
|
9549
10033
|
describe('#unsetPeerConnections', () => {
|
|
9550
10034
|
it('should unset the peer connections', () => {
|
|
9551
10035
|
meeting.mediaProperties.unsetPeerConnection = sinon.stub().returns(true);
|
|
@@ -10674,6 +11158,7 @@ describe('plugin-meetings', () => {
|
|
|
10674
11158
|
meeting.webex.internal.llm.on = sinon.stub();
|
|
10675
11159
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
10676
11160
|
meeting.processRelayEvent = sinon.stub();
|
|
11161
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
10677
11162
|
});
|
|
10678
11163
|
|
|
10679
11164
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -10805,6 +11290,19 @@ describe('plugin-meetings', () => {
|
|
|
10805
11290
|
meeting.processRelayEvent
|
|
10806
11291
|
);
|
|
10807
11292
|
});
|
|
11293
|
+
|
|
11294
|
+
|
|
11295
|
+
it('connect ps data channel if ps started in webinar', async () => {
|
|
11296
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
11297
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
|
|
11298
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
|
|
11299
|
+
await meeting.updateLLMConnection();
|
|
11300
|
+
|
|
11301
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
11302
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
|
|
11303
|
+
|
|
11304
|
+
});
|
|
11305
|
+
|
|
10808
11306
|
});
|
|
10809
11307
|
|
|
10810
11308
|
describe('#setLocus', () => {
|
|
@@ -10996,6 +11494,7 @@ describe('plugin-meetings', () => {
|
|
|
10996
11494
|
beforeEach(() => {
|
|
10997
11495
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
10998
11496
|
meeting.deviceUrl = 'my-web-url';
|
|
11497
|
+
meeting.locusInfo.info = {isWebinar: false};
|
|
10999
11498
|
});
|
|
11000
11499
|
|
|
11001
11500
|
const USER_IDS = {
|
|
@@ -11221,13 +11720,24 @@ describe('plugin-meetings', () => {
|
|
|
11221
11720
|
|
|
11222
11721
|
activeSharingId.whiteboard = beneficiaryId;
|
|
11223
11722
|
|
|
11224
|
-
eventTrigger.share.push({
|
|
11723
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11724
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11725
|
+
functionName: 'remoteShare',
|
|
11726
|
+
eventPayload: {
|
|
11727
|
+
memberId: null,
|
|
11728
|
+
url,
|
|
11729
|
+
shareInstanceId,
|
|
11730
|
+
annotationInfo: undefined,
|
|
11731
|
+
resourceType: undefined,
|
|
11732
|
+
},
|
|
11733
|
+
} : {
|
|
11225
11734
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11226
11735
|
functionName: 'startWhiteboardShare',
|
|
11227
11736
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11228
11737
|
});
|
|
11229
11738
|
|
|
11230
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11739
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11740
|
+
|
|
11231
11741
|
}
|
|
11232
11742
|
|
|
11233
11743
|
if (eventTrigger.member) {
|
|
@@ -11259,13 +11769,24 @@ describe('plugin-meetings', () => {
|
|
|
11259
11769
|
newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
|
|
11260
11770
|
newPayload.current.content.beneficiaryId = otherBeneficiaryId;
|
|
11261
11771
|
|
|
11262
|
-
eventTrigger.share.push({
|
|
11772
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11773
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11774
|
+
functionName: 'remoteShare',
|
|
11775
|
+
eventPayload: {
|
|
11776
|
+
memberId: null,
|
|
11777
|
+
url,
|
|
11778
|
+
shareInstanceId,
|
|
11779
|
+
annotationInfo: undefined,
|
|
11780
|
+
resourceType: undefined,
|
|
11781
|
+
},
|
|
11782
|
+
} : {
|
|
11263
11783
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11264
11784
|
functionName: 'startWhiteboardShare',
|
|
11265
11785
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11266
11786
|
});
|
|
11267
11787
|
|
|
11268
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11788
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11789
|
+
|
|
11269
11790
|
} else {
|
|
11270
11791
|
eventTrigger.share.push({
|
|
11271
11792
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
|
|
@@ -11392,6 +11913,38 @@ describe('plugin-meetings', () => {
|
|
|
11392
11913
|
assert.exists(meeting.setUpLocusMediaSharesListener);
|
|
11393
11914
|
});
|
|
11394
11915
|
|
|
11916
|
+
describe('Whiteboard Share - Webinar Attendee', () => {
|
|
11917
|
+
it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
|
|
11918
|
+
// Set the webinar attendee flag
|
|
11919
|
+
meeting.webinar = { selfIsAttendee: true };
|
|
11920
|
+
meeting.locusInfo.info.isWebinar = true;
|
|
11921
|
+
|
|
11922
|
+
// Step 1: Start sharing whiteboard A
|
|
11923
|
+
const data1 = generateData(
|
|
11924
|
+
blankPayload, // Initial payload
|
|
11925
|
+
true, // isGranting: Granting share
|
|
11926
|
+
false, // isContent: Whiteboard (not content)
|
|
11927
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
11928
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
11929
|
+
);
|
|
11930
|
+
|
|
11931
|
+
// Step 2: Stop sharing whiteboard A
|
|
11932
|
+
const data2 = generateData(
|
|
11933
|
+
data1.payload, // Updated payload from Step 1
|
|
11934
|
+
false, // isGranting: Stopping share
|
|
11935
|
+
false, // isContent: Whiteboard
|
|
11936
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
11937
|
+
);
|
|
11938
|
+
|
|
11939
|
+
// Validate the payload changes and status updates
|
|
11940
|
+
payloadTestHelper([data1]);
|
|
11941
|
+
|
|
11942
|
+
// Specific assertions for webinar attendee status
|
|
11943
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
11944
|
+
});
|
|
11945
|
+
});
|
|
11946
|
+
|
|
11947
|
+
|
|
11395
11948
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
11396
11949
|
it('Scenario #1: you share both whiteboards', () => {
|
|
11397
11950
|
const data1 = generateData(
|
|
@@ -12067,9 +12620,12 @@ describe('plugin-meetings', () => {
|
|
|
12067
12620
|
it('startKeepAlive starts the keep alive', async () => {
|
|
12068
12621
|
meeting.meetingRequest.keepAlive = sinon.stub().returns(Promise.resolve());
|
|
12069
12622
|
|
|
12623
|
+
const keepAliveUrl1 = 'keep.alive.url1';
|
|
12624
|
+
const keepAliveUrl2 = 'keep.alive.url2';
|
|
12625
|
+
|
|
12070
12626
|
assert.isNull(meeting.keepAliveTimerId);
|
|
12071
12627
|
meeting.joinedWith = {
|
|
12072
|
-
keepAliveUrl:
|
|
12628
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12073
12629
|
keepAliveSecs: defaultKeepAliveSecs,
|
|
12074
12630
|
};
|
|
12075
12631
|
meeting.startKeepAlive();
|
|
@@ -12078,12 +12634,15 @@ describe('plugin-meetings', () => {
|
|
|
12078
12634
|
assert.notCalled(meeting.meetingRequest.keepAlive);
|
|
12079
12635
|
await progressTime(defaultExpectedInterval);
|
|
12080
12636
|
assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
|
|
12081
|
-
keepAliveUrl:
|
|
12637
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12082
12638
|
});
|
|
12639
|
+
// joinedWith keep alive url might change (when we fallback from multistream to transcoded)
|
|
12640
|
+
meeting.joinedWith.keepAliveUrl = keepAliveUrl2;
|
|
12641
|
+
|
|
12083
12642
|
await progressTime(defaultExpectedInterval);
|
|
12084
12643
|
assert.calledTwice(meeting.meetingRequest.keepAlive);
|
|
12085
|
-
assert.
|
|
12086
|
-
keepAliveUrl:
|
|
12644
|
+
assert.calledWith(meeting.meetingRequest.keepAlive, {
|
|
12645
|
+
keepAliveUrl: keepAliveUrl2,
|
|
12087
12646
|
});
|
|
12088
12647
|
});
|
|
12089
12648
|
it('startKeepAlive handles existing keepAliveTimerId', async () => {
|
|
@@ -12681,5 +13240,26 @@ describe('plugin-meetings', () => {
|
|
|
12681
13240
|
assert.calledOnceWithExactly(getMediaServer, 'fake sdp');
|
|
12682
13241
|
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'homer');
|
|
12683
13242
|
});
|
|
13243
|
+
|
|
13244
|
+
it('throws MultistreamNotSupportedError if we get a non-homer SDP answer', async () => {
|
|
13245
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
|
13246
|
+
|
|
13247
|
+
meeting.isMultistream = true;
|
|
13248
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
13249
|
+
roapMessageReceived: sinon.stub(),
|
|
13250
|
+
};
|
|
13251
|
+
|
|
13252
|
+
sinon.stub(MeetingsUtil, 'getMediaServer').returns('linus');
|
|
13253
|
+
|
|
13254
|
+
try {
|
|
13255
|
+
await meeting.roapMessageReceived(fakeMessage);
|
|
13256
|
+
|
|
13257
|
+
assert.fail('Expected MultistreamNotSupportedError to be thrown');
|
|
13258
|
+
} catch(e) {
|
|
13259
|
+
assert.isTrue(e instanceof MultistreamNotSupportedError);
|
|
13260
|
+
}
|
|
13261
|
+
|
|
13262
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
|
|
13263
|
+
});
|
|
12684
13264
|
});
|
|
12685
13265
|
});
|