@webex/plugin-meetings 2.19.3 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webex/plugin-meetings",
3
- "version": "2.19.3",
3
+ "version": "2.20.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "contributors": [
@@ -24,15 +24,15 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime-corejs2": "^7.14.8",
27
- "@webex/webex-core": "2.19.3",
28
- "@webex/internal-plugin-mercury": "2.19.3",
29
- "@webex/internal-plugin-conversation": "2.19.3",
27
+ "@webex/webex-core": "2.20.0",
28
+ "@webex/internal-plugin-mercury": "2.20.0",
29
+ "@webex/internal-plugin-conversation": "2.20.0",
30
30
  "webrtc-adapter": "^7.7.0",
31
31
  "lodash": "^4.17.21",
32
32
  "uuid": "^3.3.2",
33
33
  "global": "^4.4.0",
34
34
  "ip-anonymize": "^0.1.0",
35
- "@webex/common": "2.19.3",
35
+ "@webex/common": "2.20.0",
36
36
  "bowser": "^2.11.0",
37
37
  "sdp-transform": "^2.12.0",
38
38
  "btoa": "^1.2.1",
package/src/constants.ts CHANGED
@@ -284,6 +284,7 @@ export const EVENT_TRIGGERS = {
284
284
  MEETING_SELF_UNMUTED_BY_OTHERS: 'meeting:self:unmutedByOthers',
285
285
  MEETING_SELF_REQUESTED_TO_UNMUTE: 'meeting:self:requestedToUnmute',
286
286
  MEETING_SELF_PHONE_AUDIO_UPDATE: 'meeting:self:phoneAudioUpdate',
287
+ MEETING_CONTROLS_LAYOUT_UPDATE: 'meeting:layout:update',
287
288
  MEMBERS_UPDATE: 'members:update',
288
289
  MEMBERS_CONTENT_UPDATE: 'members:content:update',
289
290
  MEMBERS_HOST_UPDATE: 'members:host:update',
@@ -486,6 +487,7 @@ export const LOCUS = {
486
487
 
487
488
  export const LOCUSINFO = {
488
489
  EVENTS: {
490
+ CONTROLS_MEETING_LAYOUT_UPDATED: 'CONTROLS_MEETING_LAYOUT_UPDATED',
489
491
  CONTROLS_RECORDING_UPDATED: 'CONTROLS_RECORDING_UPDATED',
490
492
  CONTROLS_MEETING_TRANSCRIBE_UPDATED: 'CONTROLS_MEETING_TRANSCRIBE_UPDATED',
491
493
  CONTROLS_MEETING_CONTAINER_UPDATED: 'CONTROLS_MEETING_CONTAINER_UPDATED',
@@ -927,6 +927,17 @@ export default class LocusInfo extends EventsScope {
927
927
  this.compareAndUpdateFlags.compareHostAndSelf = false;
928
928
  }
929
929
 
930
+ if (parsedSelves.updates.layoutChanged) {
931
+ this.emitScoped(
932
+ {
933
+ file: 'locus-info',
934
+ function: 'updateSelf'
935
+ },
936
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
937
+ {layout: parsedSelves.current.layout}
938
+ );
939
+ }
940
+
930
941
  if (parsedSelves.updates.isMediaInactiveOrReleased) {
931
942
  this.emitScoped(
932
943
  {
@@ -58,12 +58,15 @@ SelfUtils.parse = (self, deviceId) => {
58
58
  removed: self.removed,
59
59
  roles: SelfUtils.getRoles(self),
60
60
  isUserUnadmitted: self.state === _IDLE_ && joinedWith?.intent?.type === _WAIT_,
61
+ layout: SelfUtils.getLayout(self)
61
62
  };
62
63
  }
63
64
 
64
65
  return null;
65
66
  };
66
67
 
68
+ SelfUtils.getLayout = (self) => (Array.isArray(self?.controls?.layouts) ? self.controls.layouts[0].type : undefined);
69
+
67
70
  SelfUtils.getRoles = (self) => (self?.controls?.role?.roles || []).reduce((roles, role) => {
68
71
  if (role.hasRole) {
69
72
  roles.push(role.type);
@@ -85,6 +88,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
85
88
  updates.moderatorChanged = SelfUtils.moderatorChanged(previous, current);
86
89
  updates.isMediaInactiveOrReleased = SelfUtils.wasMediaInactiveOrReleased(previous, current);
87
90
  updates.isUserObserving = SelfUtils.isDeviceObserving(previous, current);
91
+ updates.layoutChanged = SelfUtils.layoutChanged(previous, current);
88
92
 
89
93
  updates.isMediaInactive = SelfUtils.isMediaInactive(previous, current);
90
94
  updates.audioStateChange = previous?.currentMediaStatus.audio !== current.currentMediaStatus.audio;
@@ -105,6 +109,15 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
105
109
  */
106
110
  SelfUtils.isJoined = (self) => self?.state === _JOINED_;
107
111
 
112
+ /**
113
+ * Validate if the Meeting Layout Controls Layout has changed.
114
+ *
115
+ * @param {Self} previous - Previous self state
116
+ * @param {Self} current - Current self state [per event]
117
+ * @returns {boolean} - If the MEeting Layout Controls Layout has changed.
118
+ */
119
+ SelfUtils.layoutChanged = (previous, current) => current?.layout && previous?.layout !== current?.layout;
120
+
108
121
 
109
122
  SelfUtils.isMediaInactive = (previous, current) => {
110
123
  if (
@@ -5216,6 +5216,20 @@ export default class Meeting extends StatelessWebexPlugin {
5216
5216
  }
5217
5217
  this.lastVideoLayoutInfo = cloneDeep(layoutInfo);
5218
5218
 
5219
+ this.locusInfo.once(LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED, (envelope) => {
5220
+ Trigger.trigger(
5221
+ this,
5222
+ {
5223
+ file: 'meeting/index',
5224
+ function: 'changeVideoLayout',
5225
+ },
5226
+ EVENT_TRIGGERS.MEETING_CONTROLS_LAYOUT_UPDATE,
5227
+ {
5228
+ layout: envelope.layout
5229
+ }
5230
+ );
5231
+ });
5232
+
5219
5233
  return this.meetingRequest
5220
5234
  .changeVideoLayoutDebounced({
5221
5235
  locusUrl: this.locusInfo.self.url,
@@ -428,6 +428,52 @@ describe('plugin-meetings', () => {
428
428
  });
429
429
 
430
430
  describe('#updateSelf', () => {
431
+ it('should trigger CONTROLS_MEETING_LAYOUT_UPDATED when the meeting layout controls change', () => {
432
+ const layoutType = 'EXAMPLE TYPE';
433
+
434
+ locusInfo.self = undefined;
435
+ const selfWithLayoutChanged = cloneDeep(self);
436
+
437
+ selfWithLayoutChanged.controls.layouts = [{
438
+ type: layoutType,
439
+ }];
440
+
441
+ locusInfo.emitScoped = sinon.stub();
442
+ locusInfo.updateSelf(selfWithLayoutChanged, []);
443
+
444
+ assert.calledWith(locusInfo.emitScoped, {
445
+ file: 'locus-info',
446
+ function: 'updateSelf'
447
+ },
448
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
449
+ {layout: layoutType});
450
+ });
451
+
452
+ it('should not trigger CONTROLS_MEETING_LAYOUT_UPDATED when the meeting layout controls did not change', () => {
453
+ const layoutType = 'EXAMPLE TYPE';
454
+
455
+ locusInfo.self = undefined;
456
+ const selfWithLayoutChanged = cloneDeep(self);
457
+
458
+ selfWithLayoutChanged.controls.layouts = [{
459
+ type: layoutType,
460
+ }];
461
+
462
+ // Set the layout prior to stubbing to validate it does not change.
463
+ locusInfo.updateSelf(selfWithLayoutChanged, []);
464
+
465
+ locusInfo.emitScoped = sinon.stub();
466
+
467
+ locusInfo.updateSelf(selfWithLayoutChanged, []);
468
+
469
+ assert.neverCalledWith(locusInfo.emitScoped, {
470
+ file: 'locus-info',
471
+ function: 'updateSelf'
472
+ },
473
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
474
+ {layout: layoutType});
475
+ });
476
+
431
477
  it('should trigger MEDIA_INACTIVITY on server media inactivity', () => {
432
478
  locusInfo.self = self;
433
479
 
@@ -1,5 +1,6 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
  import Sinon from 'sinon';
3
+ import {cloneDeep} from 'lodash';
3
4
 
4
5
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
5
6
 
@@ -7,6 +8,23 @@ import {self} from './selfConstant';
7
8
 
8
9
  describe('plugin-meetings', () => {
9
10
  describe('selfUtils', () => {
11
+ describe('layoutChanged', () => {
12
+ it('should return true if the layout has changed', () => {
13
+ const parsedSelf = SelfUtils.parse(self);
14
+ const clonedSelf = cloneDeep(parsedSelf);
15
+
16
+ clonedSelf.layout = 'DIFFERENT';
17
+
18
+ assert.deepEqual(SelfUtils.layoutChanged(parsedSelf, clonedSelf), true);
19
+ });
20
+
21
+ it('should return false if the layout has not changed', () => {
22
+ const parsedSelf = SelfUtils.parse(self);
23
+
24
+ assert.deepEqual(SelfUtils.layoutChanged(parsedSelf, parsedSelf), false);
25
+ });
26
+ });
27
+
10
28
  describe('parse', () => {
11
29
  it('parse calls getRoles and returns the resulting roles', () => {
12
30
  const getRolesSpy = Sinon.spy(SelfUtils, 'getRoles');
@@ -17,6 +35,28 @@ describe('plugin-meetings', () => {
17
35
 
18
36
  assert.deepEqual(parsedSelf.roles, ['PRESENTER']);
19
37
  });
38
+
39
+ it('calls getLayout and returns the resulting layout', () => {
40
+ const spy = Sinon.spy(SelfUtils, 'getLayout');
41
+ const parsedSelf = SelfUtils.parse(self);
42
+
43
+ assert.calledWith(spy, self);
44
+ assert.deepEqual(parsedSelf.layout, self.controls.layouts[0].type);
45
+ });
46
+ });
47
+
48
+ describe('getLayout', () => {
49
+ it('should get supplied layout', () => {
50
+ assert.deepEqual(SelfUtils.getLayout(self), self.controls.layouts[0].type);
51
+ });
52
+
53
+ it('should return undefined if the new self does not have a provided layout', () => {
54
+ const mutatedSelf = cloneDeep(self);
55
+
56
+ delete mutatedSelf.controls.layouts;
57
+
58
+ assert.deepEqual(SelfUtils.getLayout(mutatedSelf), undefined);
59
+ });
20
60
  });
21
61
 
22
62
  describe('getRoles', () => {
@@ -10,6 +10,17 @@ import {assert} from '@webex/test-helper-chai';
10
10
  import {Credentials} from '@webex/webex-core';
11
11
  import Support from '@webex/internal-plugin-support';
12
12
  import MockWebex from '@webex/test-helper-mock-webex';
13
+ import {
14
+ FLOOR_ACTION,
15
+ SHARE_STATUS,
16
+ MEETING_INFO_FAILURE_REASON,
17
+ PASSWORD_STATUS,
18
+ EVENTS,
19
+ EVENT_TRIGGERS,
20
+ _SIP_URI_,
21
+ _MEETING_ID_,
22
+ LOCUSINFO,
23
+ } from '@webex/plugin-meetings/src/constants';
13
24
 
14
25
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
15
26
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
@@ -31,16 +42,6 @@ import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy
31
42
  import BrowserDetection from '@webex/plugin-meetings/src/common/browser-detection';
32
43
  import Metrics from '@webex/plugin-meetings/src/metrics';
33
44
  import {eventType} from '@webex/plugin-meetings/src/metrics/config';
34
- import {
35
- FLOOR_ACTION,
36
- SHARE_STATUS,
37
- MEETING_INFO_FAILURE_REASON,
38
- PASSWORD_STATUS,
39
- EVENTS,
40
- EVENT_TRIGGERS,
41
- _SIP_URI_,
42
- _MEETING_ID_,
43
- } from '@webex/plugin-meetings/src/constants';
44
45
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
45
46
 
46
47
  import locus from '../fixture/locus';
@@ -1912,6 +1913,15 @@ describe('plugin-meetings', () => {
1912
1913
  };
1913
1914
  });
1914
1915
 
1916
+ it('should listen once for CONTROLS_MEETING_LAYOUT_UPDATED', async () => {
1917
+ // const spy = sinon.spy(TriggerProxy, 'trigger');
1918
+ const spy = sinon.spy(meeting.locusInfo, 'once');
1919
+
1920
+ await meeting.changeVideoLayout('Equal');
1921
+
1922
+ assert.calledWith(spy, LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED);
1923
+ });
1924
+
1915
1925
  it('should have receiveVideo true and remote video track should exist', () => {
1916
1926
  assert.equal(meeting.mediaProperties.mediaDirection.receiveVideo, true);
1917
1927
  assert.exists(meeting.mediaProperties.remoteVideoTrack);