@webex/plugin-meetings 3.0.0-beta.41 → 3.0.0-beta.43

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.
Files changed (36) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +3 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/controls-options-manager/enums.js +1 -0
  6. package/dist/controls-options-manager/enums.js.map +1 -1
  7. package/dist/controls-options-manager/index.js +51 -14
  8. package/dist/controls-options-manager/index.js.map +1 -1
  9. package/dist/controls-options-manager/util.js +12 -1
  10. package/dist/controls-options-manager/util.js.map +1 -1
  11. package/dist/meeting/in-meeting-actions.js +4 -0
  12. package/dist/meeting/in-meeting-actions.js.map +1 -1
  13. package/dist/meeting/index.js +50 -24
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/multistream/receiveSlotManager.js +19 -32
  16. package/dist/multistream/receiveSlotManager.js.map +1 -1
  17. package/dist/types/constants.d.ts +2 -0
  18. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  19. package/dist/types/controls-options-manager/index.d.ts +9 -1
  20. package/dist/types/controls-options-manager/util.d.ts +2 -0
  21. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  22. package/dist/types/meeting/index.d.ts +10 -0
  23. package/dist/types/multistream/receiveSlotManager.d.ts +7 -4
  24. package/package.json +18 -18
  25. package/src/constants.ts +2 -0
  26. package/src/controls-options-manager/enums.ts +1 -0
  27. package/src/controls-options-manager/index.ts +55 -20
  28. package/src/controls-options-manager/util.ts +10 -0
  29. package/src/meeting/in-meeting-actions.ts +8 -0
  30. package/src/meeting/index.ts +44 -7
  31. package/src/multistream/receiveSlotManager.ts +18 -20
  32. package/test/unit/spec/controls-options-manager/index.js +56 -0
  33. package/test/unit/spec/controls-options-manager/util.js +20 -0
  34. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  35. package/test/unit/spec/meeting/index.js +82 -2
  36. package/test/unit/spec/multistream/receiveSlotManager.ts +21 -27
@@ -1,11 +1,13 @@
1
1
  /* eslint-disable valid-jsdoc */
2
2
  /* eslint-disable import/prefer-default-export */
3
- import {MediaType} from '@webex/internal-media-core';
4
-
3
+ import {MediaType, ReceiveSlot as WcmeReceiveSlot} from '@webex/internal-media-core';
5
4
  import LoggerProxy from '../common/logs/logger-proxy';
6
- import Meeting from '../meeting';
7
5
 
8
- import {CSI, ReceiveSlot} from './receiveSlot';
6
+ import {FindMemberIdCallback, ReceiveSlot} from './receiveSlot';
7
+
8
+ export type CreateSlotCallback = (mediaType: MediaType) => Promise<WcmeReceiveSlot>;
9
+
10
+ export type {CSI, FindMemberIdCallback} from './receiveSlot';
9
11
 
10
12
  /**
11
13
  * Manages all receive slots used by a meeting. WMCE receive slots cannot be ever deleted,
@@ -16,13 +18,18 @@ export class ReceiveSlotManager {
16
18
 
17
19
  private freeSlots: {[key in MediaType]: ReceiveSlot[]};
18
20
 
19
- private meeting: Meeting;
21
+ private createSlotCallback: CreateSlotCallback;
22
+
23
+ private findMemberIdByCsiCallback: FindMemberIdCallback;
20
24
 
21
25
  /**
22
26
  * Constructor
23
27
  * @param {Meeting} meeting
24
28
  */
25
- constructor(meeting) {
29
+ constructor(
30
+ createSlotCallback: CreateSlotCallback,
31
+ findMemberIdByCsiCallback: FindMemberIdCallback
32
+ ) {
26
33
  this.allocatedSlots = {
27
34
  [MediaType.AudioMain]: [],
28
35
  [MediaType.VideoMain]: [],
@@ -35,7 +42,8 @@ export class ReceiveSlotManager {
35
42
  [MediaType.AudioSlides]: [],
36
43
  [MediaType.VideoSlides]: [],
37
44
  };
38
- this.meeting = meeting;
45
+ this.createSlotCallback = createSlotCallback;
46
+ this.findMemberIdByCsiCallback = findMemberIdByCsiCallback;
39
47
  }
40
48
 
41
49
  /**
@@ -45,10 +53,6 @@ export class ReceiveSlotManager {
45
53
  * @returns {Promise<ReceiveSlot>}
46
54
  */
47
55
  async allocateSlot(mediaType: MediaType): Promise<ReceiveSlot> {
48
- if (!this.meeting?.mediaProperties?.webrtcMediaConnection) {
49
- return Promise.reject(new Error('Webrtc media connection is missing'));
50
- }
51
-
52
56
  // try to use one of the free ones
53
57
  const availableSlot = this.freeSlots[mediaType].pop();
54
58
 
@@ -61,15 +65,9 @@ export class ReceiveSlotManager {
61
65
  }
62
66
 
63
67
  // we have to create a new one
64
- const wcmeReceiveSlot =
65
- await this.meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot(mediaType);
66
-
67
- const receiveSlot = new ReceiveSlot(
68
- mediaType,
69
- wcmeReceiveSlot,
70
- // @ts-ignore
71
- (csi: CSI) => this.meeting.members.findMemberByCsi(csi)?.id
72
- );
68
+ const wcmeReceiveSlot = await this.createSlotCallback(mediaType);
69
+
70
+ const receiveSlot = new ReceiveSlot(mediaType, wcmeReceiveSlot, this.findMemberIdByCsiCallback);
73
71
 
74
72
  this.allocatedSlots[mediaType].push(receiveSlot);
75
73
  LoggerProxy.logger.log(`new receive slot allocated: ${receiveSlot.id}`);
@@ -119,6 +119,62 @@ describe('plugin-meetings', () => {
119
119
  });
120
120
  });
121
121
  });
122
+
123
+ describe('Mute/Unmute All', () => {
124
+ let manager;
125
+ beforeEach(() => {
126
+ request = {
127
+ request: sinon.stub().returns(Promise.resolve()),
128
+ };
129
+
130
+ manager = new ControlsOptionsManager(request);
131
+
132
+ manager.set({
133
+ locusUrl: 'test/id',
134
+ displayHints: [],
135
+ })
136
+ });
137
+
138
+ it('rejects when correct display hint is not present mutedEnabled=false', () => {
139
+ const result = manager.setMuteAll(false, false, false);
140
+
141
+ assert.notCalled(request.request);
142
+
143
+ assert.isRejected(result);
144
+ });
145
+
146
+ it('rejects when correct display hint is not present mutedEnabled=true', () => {
147
+ const result = manager.setMuteAll(true, false, false);
148
+
149
+ assert.notCalled(request.request);
150
+
151
+ assert.isRejected(result);
152
+ });
153
+
154
+ it('can set mute all when the display hint is available mutedEnabled=true', () => {
155
+ manager.setDisplayHints(['MUTE_ALL', 'ENABLE_HARD_MUTE', 'ENABLE_MUTE_ON_ENTRY']);
156
+
157
+ const result = manager.setMuteAll(true, true, true);
158
+
159
+ assert.calledWith(request.request, { uri: 'test/id/controls',
160
+ body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
161
+ method: HTTP_VERBS.PATCH});
162
+
163
+ assert.deepEqual(result, request.request.firstCall.returnValue);
164
+ });
165
+
166
+ it('can set mute all when the display hint is available mutedEnabled=false', () => {
167
+ manager.setDisplayHints(['UNMUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
168
+
169
+ const result = manager.setMuteAll(false, false, false);
170
+
171
+ assert.calledWith(request.request, { uri: 'test/id/controls',
172
+ body: { audio: { muted: false, disallowUnmute: false, muteOnEntry: false } },
173
+ method: HTTP_VERBS.PATCH});
174
+
175
+ assert.deepEqual(result, request.request.firstCall.returnValue);
176
+ });
177
+ });
122
178
  });
123
179
  });
124
180
  });
@@ -61,6 +61,26 @@ describe('plugin-meetings', () => {
61
61
  });
62
62
  });
63
63
 
64
+ describe('canSetMuteAll', () => {
65
+ it('can mute all', () => {
66
+ locusInfo.parsedLocus.info.userDisplayHints.push('MUTE_ALL');
67
+
68
+ assert.equal(ControlsOptionsUtil.canSetMuted(locusInfo.parsedLocus.info.userDisplayHints), true);
69
+ });
70
+
71
+ it('can unmute all', () => {
72
+ locusInfo.parsedLocus.info.userDisplayHints.push('UNMUTE_ALL');
73
+
74
+ assert.equal(ControlsOptionsUtil.canUnsetMuted(locusInfo.parsedLocus.info.userDisplayHints), true);
75
+ });
76
+ it('rejects when correct display hint is not present', () => {
77
+ assert.equal(ControlsOptionsUtil.canSetMuted(locusInfo.parsedLocus.info.userDisplayHints), false);
78
+ });
79
+
80
+ it('rejects when correct display hint is not present', () => {
81
+ assert.equal(ControlsOptionsUtil.canUnsetMuted(locusInfo.parsedLocus.info.userDisplayHints), false);
82
+ });
83
+ });
64
84
  });
65
85
  });
66
86
  });
@@ -17,6 +17,8 @@ describe('plugin-meetings', () => {
17
17
  canUnsetMuteOnEntry: null,
18
18
  canSetDisallowUnmute: null,
19
19
  canUnsetDisallowUnmute: null,
20
+ canSetMuted: null,
21
+ canUnsetMuted: null,
20
22
  canStopRecording: null,
21
23
  canRaiseHand: null,
22
24
  canLowerAllHands: null,
@@ -23,7 +23,7 @@ import {
23
23
  PC_BAIL_TIMEOUT,
24
24
  } from '@webex/plugin-meetings/src/constants';
25
25
  import * as InternalMediaCoreModule from '@webex/internal-media-core';
26
- import {ConnectionState, Event, Errors, ErrorType, LocalTrackEvents, RemoteTrackType} from '@webex/internal-media-core';
26
+ import {ConnectionState, Event, Errors, ErrorType, LocalTrackEvents, RemoteTrackType, MediaType} from '@webex/internal-media-core';
27
27
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
28
28
  import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
29
29
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
@@ -47,8 +47,8 @@ import BrowserDetection from '@webex/plugin-meetings/src/common/browser-detectio
47
47
  import Metrics from '@webex/plugin-meetings/src/metrics';
48
48
  import {trigger, eventType} from '@webex/plugin-meetings/src/metrics/config';
49
49
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
50
- import {IceGatheringFailed} from '@webex/plugin-meetings/src/common/errors/webex-errors';
51
50
  import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
51
+ import * as ReceiveSlotManagerModule from '@webex/plugin-meetings/src/multistream/receiveSlotManager';
52
52
 
53
53
  import LLM from '@webex/internal-plugin-llm';
54
54
  import Mercury from '@webex/internal-plugin-mercury';
@@ -274,6 +274,86 @@ describe('plugin-meetings', () => {
274
274
  assert.instanceOf(meeting.mediaRequestManagers.screenShareAudio, MediaRequestManager);
275
275
  assert.instanceOf(meeting.mediaRequestManagers.screenShareVideo, MediaRequestManager);
276
276
  });
277
+
278
+ describe('creates ReceiveSlot manager instance', () => {
279
+ let mockReceiveSlotManagerCtor;
280
+ let providedCreateSlotCallback;
281
+ let providedFindMemberIdByCsiCallback;
282
+
283
+ beforeEach(() => {
284
+ mockReceiveSlotManagerCtor = sinon.stub(ReceiveSlotManagerModule, 'ReceiveSlotManager').callsFake((createSlotCallback, findMemberIdByCsiCallback) => {
285
+ providedCreateSlotCallback = createSlotCallback;
286
+ providedFindMemberIdByCsiCallback = findMemberIdByCsiCallback;
287
+
288
+ return {updateMemberIds: sinon.stub()};
289
+ });
290
+
291
+ meeting = new Meeting(
292
+ {
293
+ userId: uuid1,
294
+ resource: uuid2,
295
+ deviceUrl: uuid3,
296
+ locus: {url: url1},
297
+ destination: testDestination,
298
+ destinationType: _MEETING_ID_,
299
+ },
300
+ {
301
+ parent: webex,
302
+ }
303
+ );
304
+
305
+ meeting.mediaProperties.webrtcMediaConnection = {createReceiveSlot: sinon.stub()};
306
+ });
307
+
308
+ it('calls ReceiveSlotManager constructor', () => {
309
+ assert.calledOnce(mockReceiveSlotManagerCtor);
310
+ assert.isDefined(providedCreateSlotCallback);
311
+ assert.isDefined(providedFindMemberIdByCsiCallback);
312
+ });
313
+
314
+ it('calls createReceiveSlot on the webrtc media connection in the createSlotCallback', async () => {
315
+ assert.isDefined(providedCreateSlotCallback);
316
+
317
+ await providedCreateSlotCallback(MediaType.VideoMain);
318
+
319
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
320
+ assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot, MediaType.VideoMain);
321
+ });
322
+
323
+ it('rejects createSlotCallback if there is no webrtc media connection', () => {
324
+ assert.isDefined(providedCreateSlotCallback);
325
+
326
+ meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot.rejects({});
327
+
328
+ assert.isRejected(providedCreateSlotCallback(MediaType.VideoMain));
329
+ });
330
+
331
+ it('calls findMemberByCsi in findMemberIdByCsiCallback and returns the right value', () => {
332
+ assert.isDefined(providedFindMemberIdByCsiCallback);
333
+
334
+ const fakeMember = {id: 'aaa-bbb'};
335
+
336
+ sinon.stub(meeting.members, 'findMemberByCsi').returns(fakeMember)
337
+
338
+ const memberId = providedFindMemberIdByCsiCallback(123);
339
+
340
+ assert.calledOnce(meeting.members.findMemberByCsi);
341
+ assert.calledWith(meeting.members.findMemberByCsi, 123);
342
+ assert.equal(memberId, fakeMember.id);
343
+ });
344
+
345
+ it('returns undefined if findMemberByCsi does not find the member', () => {
346
+ assert.isDefined(providedFindMemberIdByCsiCallback);
347
+
348
+ sinon.stub(meeting.members, 'findMemberByCsi').returns(undefined)
349
+
350
+ const memberId = providedFindMemberIdByCsiCallback(123);
351
+
352
+ assert.calledOnce(meeting.members.findMemberByCsi);
353
+ assert.calledWith(meeting.members.findMemberByCsi, 123);
354
+ assert.equal(memberId, undefined);
355
+ });
356
+ })
277
357
  });
278
358
  describe('#invite', () => {
279
359
  it('should have #invite', () => {
@@ -6,26 +6,17 @@ import {ReceiveSlotManager} from '@webex/plugin-meetings/src/multistream/receive
6
6
  import * as ReceiveSlotModule from '@webex/plugin-meetings/src/multistream/receiveSlot';
7
7
 
8
8
  describe('ReceiveSlotManager', () => {
9
- let fakeMeeting;
10
9
  let fakeWcmeSlot;
11
10
  let fakeReceiveSlots;
12
11
  let mockReceiveSlotCtor;
13
12
  let receiveSlotManager;
13
+ let createSlotCallbackStub;
14
+ let findMemberIdCallbackStub;
14
15
 
15
16
  beforeEach(() => {
16
17
  fakeWcmeSlot = {
17
18
  id: 'fake wcme slot',
18
19
  };
19
- fakeMeeting = {
20
- mediaProperties: {
21
- webrtcMediaConnection: {
22
- createReceiveSlot: sinon.stub().resolves(fakeWcmeSlot),
23
- },
24
- },
25
- members: {
26
- findMemberByCsi: sinon.stub(),
27
- },
28
- };
29
20
  fakeReceiveSlots = [];
30
21
  mockReceiveSlotCtor = sinon.stub(ReceiveSlotModule, 'ReceiveSlot').callsFake((mediaType) => {
31
22
  const fakeReceiveSlot = {
@@ -39,7 +30,10 @@ describe('ReceiveSlotManager', () => {
39
30
  return fakeReceiveSlot;
40
31
  });
41
32
 
42
- receiveSlotManager = new ReceiveSlotManager(fakeMeeting);
33
+ createSlotCallbackStub = sinon.stub().resolves(fakeWcmeSlot);
34
+ findMemberIdCallbackStub = sinon.stub();
35
+
36
+ receiveSlotManager = new ReceiveSlotManager(createSlotCallbackStub, findMemberIdCallbackStub);
43
37
  });
44
38
 
45
39
  afterEach(() => {
@@ -47,7 +41,7 @@ describe('ReceiveSlotManager', () => {
47
41
  });
48
42
 
49
43
  it('rejects if there is no media connection', async () => {
50
- fakeMeeting.mediaProperties.webrtcMediaConnection = null;
44
+ createSlotCallbackStub.rejects(new Error('Webrtc media connection is missing'));
51
45
 
52
46
  assert.isRejected(
53
47
  receiveSlotManager.allocateSlot(MediaType.VideoMain),
@@ -60,14 +54,14 @@ describe('ReceiveSlotManager', () => {
60
54
 
61
55
  const slot = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
62
56
 
63
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
57
+ assert.calledOnce(createSlotCallbackStub);
64
58
  assert.calledWith(
65
- fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot,
59
+ createSlotCallbackStub,
66
60
  MediaType.VideoMain
67
61
  );
68
62
 
69
63
  assert.calledOnce(mockReceiveSlotCtor);
70
- assert.calledWith(mockReceiveSlotCtor, MediaType.VideoMain, fakeWcmeSlot, sinon.match.func);
64
+ assert.calledWith(mockReceiveSlotCtor, MediaType.VideoMain, fakeWcmeSlot, findMemberIdCallbackStub);
71
65
  assert.strictEqual(slot, fakeReceiveSlots[0]);
72
66
 
73
67
  assert.deepEqual(receiveSlotManager.getStats(), {
@@ -79,7 +73,7 @@ describe('ReceiveSlotManager', () => {
79
73
  it('reuses previously freed slot when allocateSlot() is called and a free slot is available', async () => {
80
74
  const slot1 = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
81
75
 
82
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
76
+ assert.calledOnce(createSlotCallbackStub);
83
77
  assert.calledOnce(mockReceiveSlotCtor);
84
78
  assert.strictEqual(slot1, fakeReceiveSlots[0]);
85
79
 
@@ -91,13 +85,13 @@ describe('ReceiveSlotManager', () => {
91
85
  numFreeSlots: {'VIDEO-MAIN': 1},
92
86
  });
93
87
 
94
- fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot.resetHistory();
88
+ createSlotCallbackStub.resetHistory();
95
89
  mockReceiveSlotCtor.resetHistory();
96
90
 
97
91
  // allocate another slot, this time the previous one should be returned instead of allocating any new ones
98
92
  const slot2 = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
99
93
 
100
- assert.notCalled(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
94
+ assert.notCalled(createSlotCallbackStub);
101
95
  assert.notCalled(mockReceiveSlotCtor);
102
96
 
103
97
  // verify that in fact we got the same slot again
@@ -112,7 +106,7 @@ describe('ReceiveSlotManager', () => {
112
106
  it('does not reuse any slots after reset() is called', async () => {
113
107
  const slot1 = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
114
108
 
115
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
109
+ assert.calledOnce(createSlotCallbackStub);
116
110
  assert.calledOnce(mockReceiveSlotCtor);
117
111
  assert.strictEqual(slot1, fakeReceiveSlots[0]);
118
112
 
@@ -121,7 +115,7 @@ describe('ReceiveSlotManager', () => {
121
115
  receiveSlotManager.reset();
122
116
 
123
117
  // reset the mocks and set the ReceiveSlot constructor to return a different slot
124
- fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot.resetHistory();
118
+ createSlotCallbackStub.resetHistory();
125
119
  mockReceiveSlotCtor.resetHistory();
126
120
 
127
121
  assert.deepEqual(receiveSlotManager.getStats(), {numAllocatedSlots: {}, numFreeSlots: {}});
@@ -129,7 +123,7 @@ describe('ReceiveSlotManager', () => {
129
123
  // allocate another slot, because we called reset(), the old free slot should not be reused
130
124
  const slot2 = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
131
125
 
132
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
126
+ assert.calledOnce(createSlotCallbackStub);
133
127
  assert.calledOnce(mockReceiveSlotCtor);
134
128
 
135
129
  // verify that in fact we got a brand new slot
@@ -144,25 +138,25 @@ describe('ReceiveSlotManager', () => {
144
138
  it('does not reuse slots if they have different media type', async () => {
145
139
  const slot1 = await receiveSlotManager.allocateSlot(MediaType.VideoMain);
146
140
 
147
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
141
+ assert.calledOnce(createSlotCallbackStub);
148
142
  assert.calledOnce(mockReceiveSlotCtor);
149
143
 
150
144
  receiveSlotManager.releaseSlot(slot1);
151
145
 
152
- fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot.resetHistory();
146
+ createSlotCallbackStub.resetHistory();
153
147
  mockReceiveSlotCtor.resetHistory();
154
148
 
155
149
  // allocate another slot, this time for main audio, so it should be a completely new slot
156
150
  const slot2 = await receiveSlotManager.allocateSlot(MediaType.AudioMain);
157
151
 
158
- assert.calledOnce(fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
152
+ assert.calledOnce(createSlotCallbackStub);
159
153
  assert.calledWith(
160
- fakeMeeting.mediaProperties.webrtcMediaConnection.createReceiveSlot,
154
+ createSlotCallbackStub,
161
155
  MediaType.AudioMain
162
156
  );
163
157
 
164
158
  assert.calledOnce(mockReceiveSlotCtor);
165
- assert.calledWith(mockReceiveSlotCtor, MediaType.AudioMain, fakeWcmeSlot, sinon.match.func);
159
+ assert.calledWith(mockReceiveSlotCtor, MediaType.AudioMain, fakeWcmeSlot, findMemberIdCallbackStub);
166
160
 
167
161
  // verify that in fact we got a brand new slot
168
162
  assert.strictEqual(slot2, fakeReceiveSlots[1]);