@webex/plugin-meetings 3.0.0-beta.147 → 3.0.0-beta.149

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.
@@ -2200,6 +2200,29 @@ export default class Meeting extends StatelessWebexPlugin {
2200
2200
  });
2201
2201
  }
2202
2202
 
2203
+ /**
2204
+ * Trigger annotation info update event
2205
+ @returns {undefined}
2206
+ @param {object} contentShare
2207
+ @param {object} previousContentShare
2208
+ */
2209
+ private triggerAnnotationInfoEvent(contentShare, previousContentShare) {
2210
+ if (
2211
+ contentShare?.annotation &&
2212
+ !isEqual(contentShare?.annotation, previousContentShare?.annotation)
2213
+ ) {
2214
+ Trigger.trigger(
2215
+ this,
2216
+ {
2217
+ file: 'meeting/index',
2218
+ function: 'triggerAnnotationInfoEvent',
2219
+ },
2220
+ EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
2221
+ contentShare.annotation
2222
+ );
2223
+ }
2224
+ }
2225
+
2203
2226
  /**
2204
2227
  * Set up the locus info media shares listener
2205
2228
  * update content and whiteboard sharing id value for members, and updates the member
@@ -2215,17 +2238,7 @@ export default class Meeting extends StatelessWebexPlugin {
2215
2238
  const previousContentShare = payload.previous?.content;
2216
2239
  const previousWhiteboardShare = payload.previous?.whiteboard;
2217
2240
 
2218
- if (!isEqual(contentShare?.annotation, previousContentShare?.annotation)) {
2219
- Trigger.trigger(
2220
- this,
2221
- {
2222
- file: 'meetings/index',
2223
- function: 'remoteShare',
2224
- },
2225
- EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
2226
- contentShare.annotation
2227
- );
2228
- }
2241
+ this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
2229
2242
 
2230
2243
  if (
2231
2244
  contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable valid-jsdoc */
2
2
  /* eslint-disable require-jsdoc */
3
3
  /* eslint-disable import/prefer-default-export */
4
+ import {forEach} from 'lodash';
4
5
  import LoggerProxy from '../common/logs/logger-proxy';
5
6
 
6
7
  import {getMaxFs, RemoteMedia, RemoteVideoResolution} from './remoteMedia';
@@ -66,6 +67,53 @@ export class RemoteMediaGroup {
66
67
  return [...this.unpinnedRemoteMedia, ...this.pinnedRemoteMedia];
67
68
  }
68
69
 
70
+ /**
71
+ * Sets CSIs for multiple RemoteMedia instances belonging to this RemoteMediaGroup.
72
+ * For each entry in the remoteMediaCsis array:
73
+ * - if csi is specified, the RemoteMedia instance is pinned to that CSI
74
+ * - if csi is undefined, the RemoteMedia instance is unpinned
75
+ * @internal
76
+ */
77
+ public setActiveSpeakerCsis(
78
+ remoteMediaCsis: {remoteMedia: RemoteMedia; csi?: number}[],
79
+ commit = true
80
+ ): void {
81
+ forEach(remoteMediaCsis, ({csi, remoteMedia}) => {
82
+ if (csi) {
83
+ if (!(this.pinnedRemoteMedia.indexOf(remoteMedia) >= 0)) {
84
+ const unpinId = this.unpinnedRemoteMedia.indexOf(remoteMedia);
85
+ if (unpinId >= 0) {
86
+ this.unpinnedRemoteMedia.splice(unpinId, 1);
87
+ this.pinnedRemoteMedia.push(remoteMedia);
88
+ } else {
89
+ throw new Error(
90
+ `failed to pin a remote media object ${remoteMedia.id}, because it is not found in this remote media group`
91
+ );
92
+ }
93
+ }
94
+ remoteMedia.sendMediaRequest(csi, false);
95
+ } else {
96
+ if (!(this.unpinnedRemoteMedia.indexOf(remoteMedia) >= 0)) {
97
+ const pinId = this.pinnedRemoteMedia.indexOf(remoteMedia);
98
+ if (pinId >= 0) {
99
+ this.pinnedRemoteMedia.splice(pinId, 1);
100
+ this.unpinnedRemoteMedia.push(remoteMedia);
101
+ } else {
102
+ throw new Error(
103
+ `failed to unpin a remote media object ${remoteMedia.id}, because it is not found in this remote media group`
104
+ );
105
+ }
106
+ }
107
+ remoteMedia.cancelMediaRequest(false);
108
+ }
109
+ });
110
+ this.cancelActiveSpeakerMediaRequest(false);
111
+ this.sendActiveSpeakerMediaRequest(false);
112
+ if (commit) {
113
+ this.mediaRequestManager.commit();
114
+ }
115
+ }
116
+
69
117
  /**
70
118
  * Pins a specific remote media instance to a specfic CSI, so the media will
71
119
  * no longer come from active speaker, but from that CSI.
@@ -151,6 +199,10 @@ export class RemoteMediaGroup {
151
199
  throw new Error(`remote media object ${remoteMedia.id} not found in the group`);
152
200
  }
153
201
 
202
+ /**
203
+ * setPreferLiveVideo - sets preferLiveVideo to true/false
204
+ * @internal
205
+ */
154
206
  public setPreferLiveVideo(preferLiveVideo: boolean, commit: boolean) {
155
207
  if (this.options.preferLiveVideo !== preferLiveVideo) {
156
208
  this.options.preferLiveVideo = preferLiveVideo;
@@ -505,6 +505,24 @@ export class RemoteMediaManager extends EventsScope {
505
505
  this.mediaRequestManagers.video.commit();
506
506
  }
507
507
 
508
+ /**
509
+ * Sets CSIs for multiple RemoteMedia instances belonging to RemoteMediaGroup.
510
+ * For each entry in the remoteMediaCsis array:
511
+ * - if csi is specified, the RemoteMedia instance is pinned to that CSI
512
+ * - if csi is undefined, the RemoteMedia instance is unpinned
513
+ */
514
+ public setActiveSpeakerCsis(remoteMediaCsis: {remoteMedia: RemoteMedia; csi?: number}[]) {
515
+ Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
516
+ const groupRemoteMediaCsis = remoteMediaCsis.filter(({remoteMedia}) =>
517
+ remoteMediaGroup.includes(remoteMedia)
518
+ );
519
+ if (groupRemoteMediaCsis.length > 0) {
520
+ remoteMediaGroup.setActiveSpeakerCsis(groupRemoteMediaCsis, false);
521
+ }
522
+ });
523
+ this.mediaRequestManagers.video.commit();
524
+ }
525
+
508
526
  /**
509
527
  * Creates the audio slots
510
528
  */
@@ -189,7 +189,7 @@ describe('live-annotation', () => {
189
189
  });
190
190
 
191
191
 
192
- describe('encrypt/decrypt Content ', () => {
192
+ describe('encrypt/decrypt Content', () => {
193
193
  beforeEach(async () => {
194
194
  annotationService.webex.internal.encryption.encryptText = sinon.stub().returns(Promise.resolve('RETURN_VALUE'));
195
195
  annotationService.webex.internal.encryption.decryptText = sinon.stub().returns(Promise.resolve('RETURN_VALUE'));
@@ -201,7 +201,7 @@ describe('live-annotation', () => {
201
201
  assert.equal(result, 'RETURN_VALUE')
202
202
  });
203
203
 
204
- it('decryptContent ', async() => {
204
+ it('decryptContent', async() => {
205
205
  const result = await annotationService.decryptContent("decryptionKeyUrl", "content");
206
206
  assert.calledOnceWithExactly(webex.internal.encryption.decryptText, "decryptionKeyUrl", "content");
207
207
  assert.equal(result, 'RETURN_VALUE')
@@ -232,7 +232,7 @@ describe('live-annotation', () => {
232
232
  });
233
233
 
234
234
 
235
- it('works on publish Stroke Data', async () => {
235
+ it('works on publish Stroke Data', async () => {
236
236
  const strokeData = {
237
237
  content: {
238
238
  "contentsBuffer": [{
@@ -343,7 +343,7 @@ describe('live-annotation', () => {
343
343
  });
344
344
  });
345
345
 
346
- describe('change annotation options', () => {
346
+ describe('change annotation info by presenter', () => {
347
347
  it('makes change annotation options as expected', async() => {
348
348
  const options = { annotationInfo:{
349
349
  version: '1',
@@ -5489,6 +5489,50 @@ describe('plugin-meetings', () => {
5489
5489
  });
5490
5490
  });
5491
5491
  describe('share scenarios', () => {
5492
+
5493
+ describe('triggerAnnotationInfoEvent', () => {
5494
+ it('check triggerAnnotationInfoEvent event', () => {
5495
+
5496
+ TriggerProxy.trigger.reset();
5497
+ const annotationInfo = {version: '1', policy: 'Approval'};
5498
+ meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{});
5499
+
5500
+ assert.calledWith(
5501
+ TriggerProxy.trigger,
5502
+ meeting,
5503
+ {
5504
+ file: 'meeting/index',
5505
+ function: 'triggerAnnotationInfoEvent',
5506
+ },
5507
+ 'meeting:updateAnnotationInfo',
5508
+ annotationInfo
5509
+ );
5510
+
5511
+ TriggerProxy.trigger.reset();
5512
+ meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{annotation:annotationInfo});
5513
+ assert.notCalled(TriggerProxy.trigger);
5514
+
5515
+ TriggerProxy.trigger.reset();
5516
+ const annotationInfoUpdated = {version: '1', policy: 'AnnotationNotAllowed'};
5517
+ meeting.triggerAnnotationInfoEvent({annotation:annotationInfoUpdated},{annotation:annotationInfo});
5518
+ assert.calledWith(
5519
+ TriggerProxy.trigger,
5520
+ meeting,
5521
+ {
5522
+ file: 'meeting/index',
5523
+ function: 'triggerAnnotationInfoEvent',
5524
+ },
5525
+ 'meeting:updateAnnotationInfo',
5526
+ annotationInfoUpdated
5527
+ );
5528
+
5529
+ TriggerProxy.trigger.reset();
5530
+ meeting.triggerAnnotationInfoEvent(null,{annotation:annotationInfoUpdated});
5531
+ assert.notCalled(TriggerProxy.trigger);
5532
+
5533
+ });
5534
+ });
5535
+
5492
5536
  describe('setUpLocusMediaSharesListener', () => {
5493
5537
  beforeEach(() => {
5494
5538
  meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
@@ -6179,29 +6223,6 @@ describe('plugin-meetings', () => {
6179
6223
  payloadTestHelper([data1, data2, data3]);
6180
6224
  });
6181
6225
  });
6182
-
6183
- describe('annotation policy', () => {
6184
-
6185
- it('Scenario #1: blank annotation', () => {
6186
- const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
6187
- const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
6188
- const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
6189
- const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
6190
-
6191
- payloadTestHelper([data1, data2, data3, data4]);
6192
- });
6193
-
6194
- it('Scenario #2: annotation', () => {
6195
- const annotationInfo = {version: '1', policy: 'Approval'};
6196
- const data1 = generateData(blankPayload, true, true, USER_IDS.ME, annotationInfo);
6197
- const data2 = generateData(data1.payload, false, true, USER_IDS.ME);
6198
- const data3 = generateData(data2.payload, true, true, USER_IDS.ME);
6199
- const data4 = generateData(data3.payload, false, true, USER_IDS.ME);
6200
-
6201
- payloadTestHelper([data1, data2, data3, data4]);
6202
- });
6203
- });
6204
-
6205
6226
  describe('Desktop A --> Desktop B', () => {
6206
6227
  it('Scenario #1: you share desktop A and then share desktop B', () => {
6207
6228
  const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
@@ -144,6 +144,225 @@ describe('RemoteMediaGroup', () => {
144
144
 
145
145
  });
146
146
 
147
+ describe('setActiveSpeakerCsis', () => {
148
+ it('checks when there is a csi and remote media is not in pinned array', () => {
149
+ const PINNED_INDEX = 2;
150
+ const CSI = 11111;
151
+
152
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
153
+ resolution: 'medium',
154
+ preferLiveVideo: true,
155
+ });
156
+
157
+ // initially nothing should be pinned
158
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS); // by default should return 'all'
159
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
160
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS);
161
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 0);
162
+
163
+ const remoteMedia = group.getRemoteMedia('all')[PINNED_INDEX];
164
+
165
+ resetHistory();
166
+
167
+ group.setActiveSpeakerCsis([{remoteMedia, csi: CSI}], false);
168
+
169
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS); // by default should return 'all'
170
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
171
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS - 1);
172
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 1);
173
+
174
+ assert.strictEqual(group.isPinned(remoteMedia), true);
175
+ // now check that correct media requests were sent...
176
+
177
+ const expectedReceiverSelectedSlots = [fakeReceiveSlots[PINNED_INDEX]];
178
+ const expectedActiveSpeakerReceiveSlots = fakeReceiveSlots.filter(
179
+ (_, idx) => idx !== PINNED_INDEX
180
+ );
181
+
182
+ // the previous active speaker media request for the group should have been cancelled
183
+ assert.calledOnce(fakeMediaRequestManager.cancelRequest);
184
+ assert.calledWith(fakeMediaRequestManager.cancelRequest, 'fake active speaker request 1');
185
+ // a new one should be sent for active speaker and for receiver selected
186
+ assert.calledTwice(fakeMediaRequestManager.addRequest);
187
+ assert.calledWith(
188
+ fakeMediaRequestManager.addRequest,
189
+ sinon.match({
190
+ policyInfo: sinon.match({
191
+ policy: 'active-speaker',
192
+ priority: 255,
193
+ }),
194
+ receiveSlots: expectedActiveSpeakerReceiveSlots,
195
+ codecInfo: sinon.match({
196
+ codec: 'h264',
197
+ maxFs: 3600,
198
+ }),
199
+ })
200
+ );
201
+ assert.calledWith(
202
+ fakeMediaRequestManager.addRequest,
203
+ sinon.match({
204
+ policyInfo: sinon.match({
205
+ policy: 'receiver-selected',
206
+ csi: CSI,
207
+ }),
208
+ receiveSlots: expectedReceiverSelectedSlots,
209
+ codecInfo: sinon.match({
210
+ codec: 'h264',
211
+ maxFs: 3600,
212
+ }),
213
+ })
214
+ );
215
+ assert.notCalled(fakeMediaRequestManager.commit);
216
+ });
217
+
218
+ it('checks when there is csi and remoteMedia is in pinned array', () => {
219
+ const PINNED_INDEX = 4;
220
+
221
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
222
+ resolution: 'medium',
223
+ preferLiveVideo: true,
224
+ });
225
+
226
+ // take one instance of remote media from the group
227
+ const remoteMedia = group.getRemoteMedia('all')[PINNED_INDEX];
228
+
229
+ resetHistory();
230
+
231
+ // pin it so that it is in pinned array
232
+ group.setActiveSpeakerCsis([{remoteMedia, csi: 1234}], false);
233
+
234
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS); // by default should return 'all'
235
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
236
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS - 1);
237
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 1);
238
+
239
+ resetHistory();
240
+ // normally this would result in the underlying receive slot csi to be updated, because we're using fake
241
+ // receive slots, we have to do that manually:
242
+ fakeReceiveSlots[PINNED_INDEX].csi = 1234;
243
+ const expectedReceiverSelectedSlots = [fakeReceiveSlots[PINNED_INDEX]];
244
+
245
+ // pin again to same CSI
246
+ group.setActiveSpeakerCsis([{remoteMedia, csi: 1234}], false);
247
+
248
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS); // by default should return 'all'
249
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
250
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS - 1);
251
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 1);
252
+
253
+ assert.strictEqual(group.isPinned(remoteMedia), true);
254
+
255
+ assert.calledTwice(fakeMediaRequestManager.cancelRequest);
256
+ assert.calledWith(fakeMediaRequestManager.cancelRequest, 'fake receiver selected request 1');
257
+
258
+ assert.calledWith(
259
+ fakeMediaRequestManager.addRequest,
260
+ sinon.match({
261
+ policyInfo: sinon.match({
262
+ policy: 'receiver-selected',
263
+ csi: 1234,
264
+ }),
265
+ receiveSlots: expectedReceiverSelectedSlots,
266
+ codecInfo: sinon.match({
267
+ codec: 'h264',
268
+ maxFs: 3600,
269
+ }),
270
+ })
271
+ );
272
+ assert.notCalled(fakeMediaRequestManager.commit);
273
+ });
274
+
275
+ it('checks setActiveSpeakerCsis with array of remoteMedia to pin and unpin', () => {
276
+ const PINNED_INDEX = 2;
277
+ const PINNED_INDEX2 = 0;
278
+ const CSI = 11111;
279
+ const CSI2 = 12345;
280
+
281
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
282
+ resolution: 'medium',
283
+ preferLiveVideo: true,
284
+ });
285
+
286
+ // initially nothing should be pinned
287
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS); // by default should return 'all'
288
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
289
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS);
290
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 0);
291
+
292
+ const remoteMedia = group.getRemoteMedia('all')[PINNED_INDEX];
293
+
294
+ const remoteMedia2 = group.getRemoteMedia('all')[PINNED_INDEX2];
295
+
296
+ const remoteMedisCsis = [{remoteMedia, csi: CSI}, {remoteMedia: remoteMedia2, csi: CSI2}];
297
+
298
+ group.setActiveSpeakerCsis(remoteMedisCsis, false);
299
+
300
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS);
301
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
302
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS - 2);
303
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 2);
304
+
305
+ assert.strictEqual(group.isPinned(remoteMedia), true);
306
+ assert.strictEqual(group.isPinned(remoteMedia2), true);
307
+
308
+ resetHistory();
309
+
310
+ group.setActiveSpeakerCsis([{remoteMedia}], false);
311
+
312
+ // one pane should still remain pinned
313
+ assert.strictEqual(group.getRemoteMedia().length, NUM_SLOTS);
314
+ assert.strictEqual(group.getRemoteMedia('all').length, NUM_SLOTS);
315
+ assert.strictEqual(group.getRemoteMedia('unpinned').length, NUM_SLOTS - 1);
316
+ assert.strictEqual(group.getRemoteMedia('pinned').length, 1);
317
+ assert.strictEqual(group.isPinned(remoteMedia), false);
318
+ assert.strictEqual(group.isPinned(remoteMedia2), true);
319
+
320
+ assert.calledTwice(fakeMediaRequestManager.cancelRequest);
321
+ assert.calledWith(fakeMediaRequestManager.cancelRequest, 'fake receiver selected request 1');
322
+ assert.notCalled(fakeMediaRequestManager.commit);
323
+ });
324
+
325
+ it('check commit is only called once', () => {
326
+ const PINNED_INDEX = 2;
327
+ const PINNED_INDEX2 = 0;
328
+ const CSI = 11111;
329
+ const CSI2 = 12345;
330
+
331
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
332
+ resolution: 'medium',
333
+ preferLiveVideo: true,
334
+ });
335
+
336
+ const remoteMedia = group.getRemoteMedia('all')[PINNED_INDEX];
337
+
338
+ resetHistory();
339
+
340
+ const remoteMedia2 = group.getRemoteMedia('all')[PINNED_INDEX2];
341
+
342
+ const remoteMedisCsis = [{remoteMedia, csi: CSI}, {remoteMedia: remoteMedia2, csi: CSI2}, {remoteMedia}];
343
+
344
+ group.setActiveSpeakerCsis(remoteMedisCsis, true);
345
+
346
+ assert.calledOnce(fakeMediaRequestManager.commit);
347
+ });
348
+
349
+ it('throws when remoteMedia id is not in unpinned and pinned array - csi is there', () => {
350
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
351
+ resolution: 'medium',
352
+ preferLiveVideo: true,
353
+ });
354
+ assert.throws(() => group.setActiveSpeakerCsis([{remoteMedia: {id: 'r1'} as any, csi: 123}], false), 'failed to pin a remote media object r1, because it is not found in this remote media group');
355
+ });
356
+
357
+ it('throws when remoteMedia id is not in unpinned and pinned array - csi is not there', () => {
358
+ const group = new RemoteMediaGroup(fakeMediaRequestManager, fakeReceiveSlots, 255, true, {
359
+ resolution: 'medium',
360
+ preferLiveVideo: true,
361
+ });
362
+ assert.throws(() => group.setActiveSpeakerCsis([{remoteMedia: {id: 'r1'} as any}], false), 'failed to unpin a remote media object r1, because it is not found in this remote media group');
363
+ });
364
+ });
365
+
147
366
  describe('pinning', () => {
148
367
  it('works as expected', () => {
149
368
  const PINNED_INDEX = 2;
@@ -679,7 +679,6 @@ describe('RemoteMediaManager', () => {
679
679
  );
680
680
 
681
681
  remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
682
- console.log(layoutInfo.activeSpeakerVideoPanes);
683
682
  Object.values(layoutInfo.activeSpeakerVideoPanes).forEach((group) => stubs.push(sinon.stub(group, 'setPreferLiveVideo')));
684
683
  });
685
684
 
@@ -1725,6 +1724,94 @@ describe('RemoteMediaManager', () => {
1725
1724
  });
1726
1725
  });
1727
1726
 
1727
+ describe('setActiveSpeakerCsis', () => {
1728
+ it('calls setActiveSpeakerCsis on the correct remote media group', async () => {
1729
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1730
+ let setCsisStub;
1731
+
1732
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1733
+ currentLayoutInfo = layoutInfo;
1734
+ setCsisStub = sinon.stub(layoutInfo.activeSpeakerVideoPanes.main, 'setActiveSpeakerCsis');
1735
+ });
1736
+
1737
+ await remoteMediaManager.start();
1738
+ resetHistory();
1739
+
1740
+ assert.isNotNull(currentLayoutInfo);
1741
+
1742
+ if (currentLayoutInfo) {
1743
+ const remoteVideo = currentLayoutInfo.activeSpeakerVideoPanes.main.getRemoteMedia()[0];
1744
+
1745
+ remoteMediaManager.setActiveSpeakerCsis([{remoteMedia: remoteVideo}]);
1746
+
1747
+ assert.calledOnce(setCsisStub);
1748
+ assert.calledWith(setCsisStub, [{remoteMedia: remoteVideo}], false);
1749
+ assert.calledOnce(fakeMediaRequestManagers.video.commit);
1750
+ }
1751
+ });
1752
+
1753
+ it('does not call setActiveSpeakerCsis on the incorrect media group', async () => {
1754
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1755
+ let setCsisStub;
1756
+
1757
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1758
+ currentLayoutInfo = layoutInfo;
1759
+ setCsisStub = sinon.stub(layoutInfo.activeSpeakerVideoPanes.main, 'setActiveSpeakerCsis');
1760
+ });
1761
+
1762
+ await remoteMediaManager.start();
1763
+ resetHistory();
1764
+
1765
+ assert.isNotNull(currentLayoutInfo);
1766
+
1767
+ if (currentLayoutInfo) {
1768
+ remoteMediaManager.setActiveSpeakerCsis([{remoteMedia: {}}]);
1769
+
1770
+ assert.notCalled(setCsisStub);
1771
+ assert.calledOnce(fakeMediaRequestManagers.video.commit);
1772
+ }
1773
+ });
1774
+
1775
+ it('checking when there is more than one group', async () => {
1776
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1777
+ const config = cloneDeep(DefaultTestConfiguration);
1778
+ let stubs = [];
1779
+
1780
+ config.video.initialLayoutId = 'OnePlusFive';
1781
+
1782
+ remoteMediaManager = new RemoteMediaManager(
1783
+ fakeReceiveSlotManager,
1784
+ fakeMediaRequestManagers,
1785
+ config
1786
+ );
1787
+
1788
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1789
+ currentLayoutInfo = layoutInfo;
1790
+ Object.values(layoutInfo.activeSpeakerVideoPanes).forEach((group) => stubs.push(sinon.stub(group, 'setActiveSpeakerCsis')));
1791
+ });
1792
+
1793
+ await remoteMediaManager.start();
1794
+ resetHistory();
1795
+
1796
+ assert.isNotNull(currentLayoutInfo);
1797
+
1798
+ if (currentLayoutInfo) {
1799
+
1800
+ const remoteMedia1 = currentLayoutInfo.activeSpeakerVideoPanes.mainBigOne.getRemoteMedia()[0];
1801
+ const remoteMedia2 = currentLayoutInfo.activeSpeakerVideoPanes.secondarySetOfSmallPanes.getRemoteMedia()[0];
1802
+
1803
+ const remoteMediaCsis = [{remoteMedia: remoteMedia1}, {remoteMedia: remoteMedia2}];
1804
+
1805
+ remoteMediaManager.setActiveSpeakerCsis([{remoteMedia: remoteMedia1}, {remoteMedia: remoteMedia2}]);
1806
+
1807
+ stubs.forEach((stub, index) => {
1808
+ assert.calledWith(stub, [remoteMediaCsis[index]], false)
1809
+ });
1810
+ assert.calledOnce(fakeMediaRequestManagers.video.commit);
1811
+ }
1812
+ });
1813
+ });
1814
+
1728
1815
  describe('pinActiveSpeakerVideoPane() and isPinned()', () => {
1729
1816
  it('throws if called on a pane not belonging to an active speaker group', async () => {
1730
1817
  let currentLayoutInfo: VideoLayoutChangedEventData | null = null;