@webex/plugin-meetings 3.7.0-next.20 → 3.7.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.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +5 -2
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.js +31 -19
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +3 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/types/constants.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/util.d.ts +1 -0
- package/dist/webinar/index.js +36 -3
- package/dist/webinar/index.js.map +1 -1
- package/package.json +3 -3
- package/src/constants.ts +2 -0
- package/src/locus-info/index.ts +4 -2
- package/src/meeting/index.ts +32 -6
- package/src/meeting/util.ts +3 -0
- package/src/webinar/index.ts +36 -2
- package/test/unit/spec/locus-info/index.js +129 -0
- package/test/unit/spec/meeting/index.js +73 -4
- package/test/unit/spec/meeting/utils.js +11 -4
- package/test/unit/spec/webinar/index.ts +167 -26
package/src/locus-info/index.ts
CHANGED
|
@@ -1283,12 +1283,13 @@ export default class LocusInfo extends EventsScope {
|
|
|
1283
1283
|
/**
|
|
1284
1284
|
* handles when the locus.mediaShares is updated
|
|
1285
1285
|
* @param {Object} mediaShares the locus.mediaShares property
|
|
1286
|
+
* @param {boolean} forceUpdate force to update the mediaShares
|
|
1286
1287
|
* @returns {undefined}
|
|
1287
1288
|
* @memberof LocusInfo
|
|
1288
1289
|
* emits internal event locus_info_update_media_shares
|
|
1289
1290
|
*/
|
|
1290
|
-
updateMediaShares(mediaShares: object) {
|
|
1291
|
-
if (mediaShares && !isEqual(this.mediaShares, mediaShares)) {
|
|
1291
|
+
updateMediaShares(mediaShares: object, forceUpdate = false) {
|
|
1292
|
+
if (mediaShares && (!isEqual(this.mediaShares, mediaShares) || forceUpdate)) {
|
|
1292
1293
|
const parsedMediaShares = MediaSharesUtils.getMediaShares(this.mediaShares, mediaShares);
|
|
1293
1294
|
|
|
1294
1295
|
this.updateMeeting(parsedMediaShares.current);
|
|
@@ -1303,6 +1304,7 @@ export default class LocusInfo extends EventsScope {
|
|
|
1303
1304
|
{
|
|
1304
1305
|
current: parsedMediaShares.current,
|
|
1305
1306
|
previous: parsedMediaShares.previous,
|
|
1307
|
+
forceUpdate,
|
|
1306
1308
|
}
|
|
1307
1309
|
);
|
|
1308
1310
|
}
|
package/src/meeting/index.ts
CHANGED
|
@@ -849,7 +849,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
849
849
|
* @memberof Meeting
|
|
850
850
|
*/
|
|
851
851
|
// @ts-ignore
|
|
852
|
-
this.webinar = new Webinar({}, {parent: this.webex});
|
|
852
|
+
this.webinar = new Webinar({meetingId: this.id}, {parent: this.webex});
|
|
853
853
|
/**
|
|
854
854
|
* helper class for managing receive slots (for multistream media connections)
|
|
855
855
|
*/
|
|
@@ -2740,6 +2740,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2740
2740
|
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2741
2741
|
|
|
2742
2742
|
if (
|
|
2743
|
+
!payload.forceUpdate &&
|
|
2743
2744
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
2744
2745
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
2745
2746
|
contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
|
|
@@ -2786,7 +2787,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2786
2787
|
// It does not matter who requested to share the whiteboard, everyone gets the same view
|
|
2787
2788
|
else if (whiteboardShare.disposition === FLOOR_ACTION.GRANTED) {
|
|
2788
2789
|
// WHITEBOARD - sharing whiteboard
|
|
2789
|
-
|
|
2790
|
+
// Webinar attendee should receive whiteboard as remote share
|
|
2791
|
+
newShareStatus =
|
|
2792
|
+
this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee
|
|
2793
|
+
? SHARE_STATUS.REMOTE_SHARE_ACTIVE
|
|
2794
|
+
: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
2790
2795
|
}
|
|
2791
2796
|
// or if content share is either released or null and whiteboard share is either released or null, no one is sharing
|
|
2792
2797
|
else if (
|
|
@@ -2801,6 +2806,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2801
2806
|
LoggerProxy.logger.info(
|
|
2802
2807
|
`Meeting:index#setUpLocusInfoMediaInactiveListener --> this.shareStatus=${this.shareStatus} newShareStatus=${newShareStatus}`
|
|
2803
2808
|
);
|
|
2809
|
+
|
|
2804
2810
|
if (newShareStatus !== this.shareStatus) {
|
|
2805
2811
|
const oldShareStatus = this.shareStatus;
|
|
2806
2812
|
|
|
@@ -3058,7 +3064,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3058
3064
|
*/
|
|
3059
3065
|
private setUpLocusResourcesListener() {
|
|
3060
3066
|
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_RESOURCES, (payload) => {
|
|
3061
|
-
|
|
3067
|
+
if (payload) {
|
|
3068
|
+
this.webinar.updateWebcastUrl(payload);
|
|
3069
|
+
Trigger.trigger(
|
|
3070
|
+
this,
|
|
3071
|
+
{
|
|
3072
|
+
file: 'meeting/index',
|
|
3073
|
+
function: 'setUpLocusInfoMeetingInfoListener',
|
|
3074
|
+
},
|
|
3075
|
+
EVENT_TRIGGERS.MEETING_RESOURCE_LINKS_UPDATE,
|
|
3076
|
+
{
|
|
3077
|
+
payload,
|
|
3078
|
+
}
|
|
3079
|
+
);
|
|
3080
|
+
}
|
|
3062
3081
|
});
|
|
3063
3082
|
}
|
|
3064
3083
|
|
|
@@ -3377,6 +3396,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3377
3396
|
payload.newRoles?.includes(SELF_ROLES.MODERATOR)
|
|
3378
3397
|
);
|
|
3379
3398
|
this.webinar.updateRoleChanged(payload);
|
|
3399
|
+
|
|
3380
3400
|
Trigger.trigger(
|
|
3381
3401
|
this,
|
|
3382
3402
|
{
|
|
@@ -5580,17 +5600,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5580
5600
|
*/
|
|
5581
5601
|
async updateLLMConnection() {
|
|
5582
5602
|
// @ts-ignore - Fix type
|
|
5583
|
-
const {url, info: {datachannelUrl} = {}} = this.locusInfo;
|
|
5603
|
+
const {url, info: {datachannelUrl, practiceSessionDatachannelUrl} = {}} = this.locusInfo;
|
|
5584
5604
|
|
|
5585
5605
|
const isJoined = this.isJoined();
|
|
5586
5606
|
|
|
5607
|
+
// webinar panelist should use new data channel in practice session
|
|
5608
|
+
const dataChannelUrl =
|
|
5609
|
+
this.webinar.isJoinPracticeSessionDataChannel() && practiceSessionDatachannelUrl
|
|
5610
|
+
? practiceSessionDatachannelUrl
|
|
5611
|
+
: datachannelUrl;
|
|
5612
|
+
|
|
5587
5613
|
// @ts-ignore - Fix type
|
|
5588
5614
|
if (this.webex.internal.llm.isConnected()) {
|
|
5589
5615
|
if (
|
|
5590
5616
|
// @ts-ignore - Fix type
|
|
5591
5617
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
5592
5618
|
// @ts-ignore - Fix type
|
|
5593
|
-
|
|
5619
|
+
dataChannelUrl === this.webex.internal.llm.getDatachannelUrl() &&
|
|
5594
5620
|
isJoined
|
|
5595
5621
|
) {
|
|
5596
5622
|
return undefined;
|
|
@@ -5607,7 +5633,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5607
5633
|
|
|
5608
5634
|
// @ts-ignore - Fix type
|
|
5609
5635
|
return this.webex.internal.llm
|
|
5610
|
-
.registerAndConnect(url,
|
|
5636
|
+
.registerAndConnect(url, dataChannelUrl)
|
|
5611
5637
|
.then((registerAndConnectResult) => {
|
|
5612
5638
|
// @ts-ignore - Fix type
|
|
5613
5639
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
package/src/meeting/util.ts
CHANGED
|
@@ -441,6 +441,9 @@ const MeetingUtil = {
|
|
|
441
441
|
displayHints.includes(DISPLAY_HINTS.LEAVE_END_MEETING),
|
|
442
442
|
|
|
443
443
|
canManageBreakout: (displayHints) => displayHints.includes(DISPLAY_HINTS.BREAKOUT_MANAGEMENT),
|
|
444
|
+
|
|
445
|
+
canStartBreakout: (displayHints) => !displayHints.includes(DISPLAY_HINTS.DISABLE_BREAKOUT_START),
|
|
446
|
+
|
|
444
447
|
canBroadcastMessageToBreakout: (displayHints, policies = {}) =>
|
|
445
448
|
displayHints.includes(DISPLAY_HINTS.BROADCAST_MESSAGE_TO_BREAKOUT) &&
|
|
446
449
|
!!policies[SELF_POLICY.SUPPORT_BROADCAST_MESSAGE],
|
package/src/webinar/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import {WebexPlugin, config} from '@webex/webex-core';
|
|
5
5
|
import uuid from 'uuid';
|
|
6
6
|
import {get} from 'lodash';
|
|
7
|
-
import {HEADERS, HTTP_VERBS, MEETINGS, SELF_ROLES} from '../constants';
|
|
7
|
+
import {_ID_, HEADERS, HTTP_VERBS, MEETINGS, SELF_ROLES, SHARE_STATUS} from '../constants';
|
|
8
8
|
|
|
9
9
|
import WebinarCollection from './collection';
|
|
10
10
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
@@ -25,6 +25,7 @@ const Webinar = WebexPlugin.extend({
|
|
|
25
25
|
selfIsPanelist: 'boolean', // self is panelist
|
|
26
26
|
selfIsAttendee: 'boolean', // self is attendee
|
|
27
27
|
practiceSessionEnabled: 'boolean', // practice session enabled
|
|
28
|
+
meetingId: 'string',
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -68,14 +69,47 @@ const Webinar = WebexPlugin.extend({
|
|
|
68
69
|
const isPromoted =
|
|
69
70
|
oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.PANELIST);
|
|
70
71
|
const isDemoted =
|
|
71
|
-
oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE)
|
|
72
|
+
(oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE)) ||
|
|
73
|
+
(!oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.ATTENDEE)); // for attendee just join meeting case
|
|
72
74
|
this.set('selfIsPanelist', newRoles.includes(SELF_ROLES.PANELIST));
|
|
73
75
|
this.set('selfIsAttendee', newRoles.includes(SELF_ROLES.ATTENDEE));
|
|
74
76
|
this.updateCanManageWebcast(newRoles.includes(SELF_ROLES.MODERATOR));
|
|
77
|
+
this.updateStatusByRole({isPromoted, isDemoted});
|
|
75
78
|
|
|
76
79
|
return {isPromoted, isDemoted};
|
|
77
80
|
},
|
|
78
81
|
|
|
82
|
+
/**
|
|
83
|
+
* should join practice session data channel or not
|
|
84
|
+
* @param {Object} {isPromoted: boolean, isDemoted: boolean}} Role transition states
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
updateStatusByRole({isPromoted, isDemoted}) {
|
|
88
|
+
const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId);
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
(isDemoted && meeting?.shareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE) ||
|
|
92
|
+
isPromoted
|
|
93
|
+
) {
|
|
94
|
+
// attendees in webinar should subscribe streaming for whiteboard sharing
|
|
95
|
+
// while panelist still need subscribe native mode so trigger force update here
|
|
96
|
+
meeting?.locusInfo?.updateMediaShares(meeting?.locusInfo?.mediaShares, true);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.practiceSessionEnabled) {
|
|
100
|
+
// may need change data channel in practice session
|
|
101
|
+
meeting?.updateLLMConnection();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* should join practice session data channel or not
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
isJoinPracticeSessionDataChannel() {
|
|
110
|
+
return this.selfIsPanelist && this.practiceSessionEnabled;
|
|
111
|
+
},
|
|
112
|
+
|
|
79
113
|
/**
|
|
80
114
|
* start or stop practice session for webinar
|
|
81
115
|
* @param {boolean} enabled
|
|
@@ -9,6 +9,7 @@ import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
|
9
9
|
import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
|
|
10
10
|
import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
|
|
11
11
|
import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
|
|
12
|
+
import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaSharesUtils';
|
|
12
13
|
import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
|
|
13
14
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
14
15
|
|
|
@@ -1637,6 +1638,134 @@ describe('plugin-meetings', () => {
|
|
|
1637
1638
|
});
|
|
1638
1639
|
});
|
|
1639
1640
|
|
|
1641
|
+
describe('#updateMediaShares', () => {
|
|
1642
|
+
let getMediaSharesSpy;
|
|
1643
|
+
|
|
1644
|
+
beforeEach(() => {
|
|
1645
|
+
// Spy on MediaSharesUtils.getMediaShares
|
|
1646
|
+
getMediaSharesSpy = sinon.stub(MediaSharesUtils, 'getMediaShares');
|
|
1647
|
+
|
|
1648
|
+
// Stub the emitScoped method to monitor its calls
|
|
1649
|
+
sinon.stub(locusInfo, 'emitScoped');
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
afterEach(() => {
|
|
1653
|
+
getMediaSharesSpy.restore();
|
|
1654
|
+
locusInfo.emitScoped.restore();
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it('should update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES when mediaShares change', () => {
|
|
1658
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1659
|
+
const newMediaShares = { audio: false, video: true };
|
|
1660
|
+
|
|
1661
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1662
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1663
|
+
|
|
1664
|
+
const parsedMediaShares = {
|
|
1665
|
+
current: newMediaShares,
|
|
1666
|
+
previous: initialMediaShares,
|
|
1667
|
+
};
|
|
1668
|
+
|
|
1669
|
+
// Stub MediaSharesUtils.getMediaShares to return the expected parsedMediaShares
|
|
1670
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1671
|
+
|
|
1672
|
+
// Call the function
|
|
1673
|
+
locusInfo.updateMediaShares(newMediaShares);
|
|
1674
|
+
|
|
1675
|
+
// Assert that MediaSharesUtils.getMediaShares was called with correct arguments
|
|
1676
|
+
assert.calledWith(getMediaSharesSpy, initialMediaShares, newMediaShares);
|
|
1677
|
+
|
|
1678
|
+
// Assert that updateMeeting was called with the parsed current media shares
|
|
1679
|
+
assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
|
|
1680
|
+
assert.deepEqual(locusInfo.mediaShares, newMediaShares);
|
|
1681
|
+
|
|
1682
|
+
// Assert that emitScoped was called with the correct event
|
|
1683
|
+
assert.calledWith(
|
|
1684
|
+
locusInfo.emitScoped,
|
|
1685
|
+
{
|
|
1686
|
+
file: 'locus-info',
|
|
1687
|
+
function: 'updateMediaShares',
|
|
1688
|
+
},
|
|
1689
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
1690
|
+
{
|
|
1691
|
+
current: newMediaShares,
|
|
1692
|
+
previous: initialMediaShares,
|
|
1693
|
+
forceUpdate: false,
|
|
1694
|
+
}
|
|
1695
|
+
);
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
it('should force update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES even if shares are the same', () => {
|
|
1699
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1700
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1701
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1702
|
+
|
|
1703
|
+
const parsedMediaShares = {
|
|
1704
|
+
current: initialMediaShares,
|
|
1705
|
+
previous: initialMediaShares,
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1709
|
+
|
|
1710
|
+
// Call the function with forceUpdate = true
|
|
1711
|
+
locusInfo.updateMediaShares(initialMediaShares, true);
|
|
1712
|
+
|
|
1713
|
+
// Assert that MediaSharesUtils.getMediaShares was called
|
|
1714
|
+
assert.calledWith(getMediaSharesSpy, initialMediaShares, initialMediaShares);
|
|
1715
|
+
|
|
1716
|
+
// Assert that emitScoped was called with the correct event
|
|
1717
|
+
assert.calledWith(
|
|
1718
|
+
locusInfo.emitScoped,
|
|
1719
|
+
{
|
|
1720
|
+
file: 'locus-info',
|
|
1721
|
+
function: 'updateMediaShares',
|
|
1722
|
+
},
|
|
1723
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
1724
|
+
{
|
|
1725
|
+
current: initialMediaShares,
|
|
1726
|
+
previous: initialMediaShares,
|
|
1727
|
+
forceUpdate: true,
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
it('should not emit LOCUS_INFO_UPDATE_MEDIA_SHARES if mediaShares do not change and forceUpdate is false', () => {
|
|
1733
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1734
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1735
|
+
|
|
1736
|
+
// Call the function with the same mediaShares and forceUpdate = false
|
|
1737
|
+
locusInfo.updateMediaShares(initialMediaShares);
|
|
1738
|
+
|
|
1739
|
+
// Assert that MediaSharesUtils.getMediaShares was not called
|
|
1740
|
+
assert.notCalled(getMediaSharesSpy);
|
|
1741
|
+
|
|
1742
|
+
// Assert that emitScoped was not called
|
|
1743
|
+
assert.notCalled(locusInfo.emitScoped);
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
it('should update internal state correctly when mediaShares are updated', () => {
|
|
1747
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1748
|
+
const newMediaShares = { audio: false, video: true };
|
|
1749
|
+
|
|
1750
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1751
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1752
|
+
|
|
1753
|
+
const parsedMediaShares = {
|
|
1754
|
+
current: newMediaShares,
|
|
1755
|
+
previous: initialMediaShares,
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1759
|
+
|
|
1760
|
+
// Call the function
|
|
1761
|
+
locusInfo.updateMediaShares(newMediaShares);
|
|
1762
|
+
|
|
1763
|
+
// Assert that the internal state was updated correctly
|
|
1764
|
+
assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
|
|
1765
|
+
assert.deepEqual(locusInfo.mediaShares, newMediaShares);
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1640
1769
|
describe('#updateEmbeddedApps()', () => {
|
|
1641
1770
|
const newEmbeddedApps = [
|
|
1642
1771
|
{
|
|
@@ -10686,6 +10686,7 @@ describe('plugin-meetings', () => {
|
|
|
10686
10686
|
meeting.webex.internal.llm.on = sinon.stub();
|
|
10687
10687
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
10688
10688
|
meeting.processRelayEvent = sinon.stub();
|
|
10689
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
10689
10690
|
});
|
|
10690
10691
|
|
|
10691
10692
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -10817,6 +10818,19 @@ describe('plugin-meetings', () => {
|
|
|
10817
10818
|
meeting.processRelayEvent
|
|
10818
10819
|
);
|
|
10819
10820
|
});
|
|
10821
|
+
|
|
10822
|
+
|
|
10823
|
+
it('connect ps data channel if ps started in webinar', async () => {
|
|
10824
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
10825
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
|
|
10826
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
|
|
10827
|
+
await meeting.updateLLMConnection();
|
|
10828
|
+
|
|
10829
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
10830
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
|
|
10831
|
+
|
|
10832
|
+
});
|
|
10833
|
+
|
|
10820
10834
|
});
|
|
10821
10835
|
|
|
10822
10836
|
describe('#setLocus', () => {
|
|
@@ -11008,6 +11022,7 @@ describe('plugin-meetings', () => {
|
|
|
11008
11022
|
beforeEach(() => {
|
|
11009
11023
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
11010
11024
|
meeting.deviceUrl = 'my-web-url';
|
|
11025
|
+
meeting.locusInfo.info = {isWebinar: false};
|
|
11011
11026
|
});
|
|
11012
11027
|
|
|
11013
11028
|
const USER_IDS = {
|
|
@@ -11233,13 +11248,24 @@ describe('plugin-meetings', () => {
|
|
|
11233
11248
|
|
|
11234
11249
|
activeSharingId.whiteboard = beneficiaryId;
|
|
11235
11250
|
|
|
11236
|
-
eventTrigger.share.push({
|
|
11251
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11252
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11253
|
+
functionName: 'remoteShare',
|
|
11254
|
+
eventPayload: {
|
|
11255
|
+
memberId: null,
|
|
11256
|
+
url,
|
|
11257
|
+
shareInstanceId,
|
|
11258
|
+
annotationInfo: undefined,
|
|
11259
|
+
resourceType: undefined,
|
|
11260
|
+
},
|
|
11261
|
+
} : {
|
|
11237
11262
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11238
11263
|
functionName: 'startWhiteboardShare',
|
|
11239
11264
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11240
11265
|
});
|
|
11241
11266
|
|
|
11242
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11267
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11268
|
+
|
|
11243
11269
|
}
|
|
11244
11270
|
|
|
11245
11271
|
if (eventTrigger.member) {
|
|
@@ -11271,13 +11297,24 @@ describe('plugin-meetings', () => {
|
|
|
11271
11297
|
newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
|
|
11272
11298
|
newPayload.current.content.beneficiaryId = otherBeneficiaryId;
|
|
11273
11299
|
|
|
11274
|
-
eventTrigger.share.push({
|
|
11300
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11301
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11302
|
+
functionName: 'remoteShare',
|
|
11303
|
+
eventPayload: {
|
|
11304
|
+
memberId: null,
|
|
11305
|
+
url,
|
|
11306
|
+
shareInstanceId,
|
|
11307
|
+
annotationInfo: undefined,
|
|
11308
|
+
resourceType: undefined,
|
|
11309
|
+
},
|
|
11310
|
+
} : {
|
|
11275
11311
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11276
11312
|
functionName: 'startWhiteboardShare',
|
|
11277
11313
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11278
11314
|
});
|
|
11279
11315
|
|
|
11280
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11316
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11317
|
+
|
|
11281
11318
|
} else {
|
|
11282
11319
|
eventTrigger.share.push({
|
|
11283
11320
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
|
|
@@ -11404,6 +11441,38 @@ describe('plugin-meetings', () => {
|
|
|
11404
11441
|
assert.exists(meeting.setUpLocusMediaSharesListener);
|
|
11405
11442
|
});
|
|
11406
11443
|
|
|
11444
|
+
describe('Whiteboard Share - Webinar Attendee', () => {
|
|
11445
|
+
it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
|
|
11446
|
+
// Set the webinar attendee flag
|
|
11447
|
+
meeting.webinar = { selfIsAttendee: true };
|
|
11448
|
+
meeting.locusInfo.info.isWebinar = true;
|
|
11449
|
+
|
|
11450
|
+
// Step 1: Start sharing whiteboard A
|
|
11451
|
+
const data1 = generateData(
|
|
11452
|
+
blankPayload, // Initial payload
|
|
11453
|
+
true, // isGranting: Granting share
|
|
11454
|
+
false, // isContent: Whiteboard (not content)
|
|
11455
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
11456
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
11457
|
+
);
|
|
11458
|
+
|
|
11459
|
+
// Step 2: Stop sharing whiteboard A
|
|
11460
|
+
const data2 = generateData(
|
|
11461
|
+
data1.payload, // Updated payload from Step 1
|
|
11462
|
+
false, // isGranting: Stopping share
|
|
11463
|
+
false, // isContent: Whiteboard
|
|
11464
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
11465
|
+
);
|
|
11466
|
+
|
|
11467
|
+
// Validate the payload changes and status updates
|
|
11468
|
+
payloadTestHelper([data1]);
|
|
11469
|
+
|
|
11470
|
+
// Specific assertions for webinar attendee status
|
|
11471
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
11472
|
+
});
|
|
11473
|
+
});
|
|
11474
|
+
|
|
11475
|
+
|
|
11407
11476
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
11408
11477
|
it('Scenario #1: you share both whiteboards', () => {
|
|
11409
11478
|
const data1 = generateData(
|
|
@@ -26,7 +26,7 @@ describe('plugin-meetings', () => {
|
|
|
26
26
|
webex.meetings.reachability = {
|
|
27
27
|
getReachabilityReportToAttachToRoap: sinon.stub().resolves({}),
|
|
28
28
|
getClientMediaPreferences: sinon.stub().resolves({}),
|
|
29
|
-
};
|
|
29
|
+
};
|
|
30
30
|
|
|
31
31
|
const logger = {
|
|
32
32
|
info: sandbox.stub(),
|
|
@@ -409,17 +409,17 @@ describe('plugin-meetings', () => {
|
|
|
409
409
|
const FAKE_CLIENT_MEDIA_PREFERENCES = {
|
|
410
410
|
id: 'fake client media preferences',
|
|
411
411
|
};
|
|
412
|
-
|
|
412
|
+
|
|
413
413
|
webex.meetings.reachability.getReachabilityReportToAttachToRoap.resolves(FAKE_REACHABILITY_REPORT);
|
|
414
414
|
webex.meetings.reachability.getClientMediaPreferences.resolves(FAKE_CLIENT_MEDIA_PREFERENCES);
|
|
415
|
-
|
|
415
|
+
|
|
416
416
|
sinon
|
|
417
417
|
.stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
|
|
418
418
|
.get(() => true);
|
|
419
419
|
sinon
|
|
420
420
|
.stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
|
|
421
421
|
.get(() => true);
|
|
422
|
-
|
|
422
|
+
|
|
423
423
|
await MeetingUtil.joinMeeting(meeting, {
|
|
424
424
|
reachability: 'reachability',
|
|
425
425
|
roapMessage: 'roapMessage',
|
|
@@ -760,6 +760,13 @@ describe('plugin-meetings', () => {
|
|
|
760
760
|
});
|
|
761
761
|
});
|
|
762
762
|
|
|
763
|
+
describe('canStartBreakout', () => {
|
|
764
|
+
it('works as expected', () => {
|
|
765
|
+
assert.deepEqual(MeetingUtil.canStartBreakout(['DISABLE_BREAKOUT_START']), false);
|
|
766
|
+
assert.deepEqual(MeetingUtil.canStartBreakout([]), true);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
|
|
763
770
|
describe('canBroadcastMessageToBreakout', () => {
|
|
764
771
|
it('works as expected', () => {
|
|
765
772
|
assert.deepEqual(
|