@webex/plugin-meetings 3.0.0-beta.18 → 3.0.0-beta.19

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": "3.0.0-beta.18",
3
+ "version": "3.0.0-beta.19",
4
4
  "description": "",
5
5
  "license": "Cisco EULA (https://www.cisco.com/c/en/us/products/end-user-license-agreement.html)",
6
6
  "contributors": [
@@ -32,12 +32,12 @@
32
32
  "build": "yarn run -T tsc --declaration true --declarationDir ./types"
33
33
  },
34
34
  "devDependencies": {
35
- "@webex/plugin-meetings": "3.0.0-beta.18",
36
- "@webex/test-helper-chai": "3.0.0-beta.18",
37
- "@webex/test-helper-mocha": "3.0.0-beta.18",
38
- "@webex/test-helper-mock-webex": "3.0.0-beta.18",
39
- "@webex/test-helper-retry": "3.0.0-beta.18",
40
- "@webex/test-helper-test-users": "3.0.0-beta.18",
35
+ "@webex/plugin-meetings": "3.0.0-beta.19",
36
+ "@webex/test-helper-chai": "3.0.0-beta.19",
37
+ "@webex/test-helper-mocha": "3.0.0-beta.19",
38
+ "@webex/test-helper-mock-webex": "3.0.0-beta.19",
39
+ "@webex/test-helper-retry": "3.0.0-beta.19",
40
+ "@webex/test-helper-test-users": "3.0.0-beta.19",
41
41
  "chai": "^4.3.4",
42
42
  "chai-as-promised": "^7.1.1",
43
43
  "jsdom-global": "3.0.2",
@@ -45,18 +45,18 @@
45
45
  "typed-emitter": "^2.1.0"
46
46
  },
47
47
  "dependencies": {
48
- "@webex/common": "3.0.0-beta.18",
48
+ "@webex/common": "3.0.0-beta.19",
49
49
  "@webex/internal-media-core": "1.33.0",
50
- "@webex/internal-plugin-conversation": "3.0.0-beta.18",
51
- "@webex/internal-plugin-device": "3.0.0-beta.18",
52
- "@webex/internal-plugin-llm": "3.0.0-beta.18",
53
- "@webex/internal-plugin-mercury": "3.0.0-beta.18",
54
- "@webex/internal-plugin-metrics": "3.0.0-beta.18",
55
- "@webex/internal-plugin-support": "3.0.0-beta.18",
56
- "@webex/internal-plugin-user": "3.0.0-beta.18",
57
- "@webex/plugin-people": "3.0.0-beta.18",
58
- "@webex/plugin-rooms": "3.0.0-beta.18",
59
- "@webex/webex-core": "3.0.0-beta.18",
50
+ "@webex/internal-plugin-conversation": "3.0.0-beta.19",
51
+ "@webex/internal-plugin-device": "3.0.0-beta.19",
52
+ "@webex/internal-plugin-llm": "3.0.0-beta.19",
53
+ "@webex/internal-plugin-mercury": "3.0.0-beta.19",
54
+ "@webex/internal-plugin-metrics": "3.0.0-beta.19",
55
+ "@webex/internal-plugin-support": "3.0.0-beta.19",
56
+ "@webex/internal-plugin-user": "3.0.0-beta.19",
57
+ "@webex/plugin-people": "3.0.0-beta.19",
58
+ "@webex/plugin-rooms": "3.0.0-beta.19",
59
+ "@webex/webex-core": "3.0.0-beta.19",
60
60
  "ampersand-collection": "^2.0.2",
61
61
  "bowser": "^2.11.0",
62
62
  "btoa": "^1.2.1",
@@ -130,6 +130,7 @@ export const MEDIA_UPDATE_TYPE = {
130
130
  AUDIO: 'AUDIO',
131
131
  VIDEO: 'VIDEO',
132
132
  SHARE: 'SHARE',
133
+ LAMBDA: 'LAMBDA',
133
134
  };
134
135
 
135
136
  /**
@@ -5351,7 +5352,14 @@ export default class Meeting extends StatelessWebexPlugin {
5351
5352
  * @private
5352
5353
  * @memberof Meeting
5353
5354
  */
5354
- private enqueueMediaUpdate(mediaUpdateType: string, options: object) {
5355
+ private enqueueMediaUpdate(mediaUpdateType: string, options: any) {
5356
+ if (mediaUpdateType === MEDIA_UPDATE_TYPE.LAMBDA && typeof options?.lambda !== 'function') {
5357
+ return Promise.reject(
5358
+ new Error('lambda must be specified when enqueuing MEDIA_UPDATE_TYPE.LAMBDA')
5359
+ );
5360
+ }
5361
+ const canUpdateMediaNow = this.canUpdateMedia();
5362
+
5355
5363
  return new Promise((resolve, reject) => {
5356
5364
  const queueItem = {
5357
5365
  pendingPromiseResolve: resolve,
@@ -5364,6 +5372,10 @@ export default class Meeting extends StatelessWebexPlugin {
5364
5372
  `Meeting:index#enqueueMediaUpdate --> enqueuing media update type=${mediaUpdateType}`
5365
5373
  );
5366
5374
  this.queuedMediaUpdates.push(queueItem);
5375
+
5376
+ if (canUpdateMediaNow) {
5377
+ this.processNextQueuedMediaUpdate();
5378
+ }
5367
5379
  });
5368
5380
  }
5369
5381
 
@@ -5416,6 +5428,9 @@ export default class Meeting extends StatelessWebexPlugin {
5416
5428
  case MEDIA_UPDATE_TYPE.SHARE:
5417
5429
  this.updateShare(options).then(pendingPromiseResolve, pendingPromiseReject);
5418
5430
  break;
5431
+ case MEDIA_UPDATE_TYPE.LAMBDA:
5432
+ options.lambda().then(pendingPromiseResolve, pendingPromiseReject);
5433
+ break;
5419
5434
  default:
5420
5435
  LoggerProxy.logger.error(
5421
5436
  `Peer-connection-manager:index#processNextQueuedMediaUpdate --> unsupported media update type ${mediaUpdateType} found in the queue`
@@ -5500,17 +5515,26 @@ export default class Meeting extends StatelessWebexPlugin {
5500
5515
  // now it's called independently from the roap message (so might be before it), check if that's OK
5501
5516
  // if not, ensure it's called after (now it's called after roap message is sent out, but we're not
5502
5517
  // waiting for sendRoapMediaRequest() to be resolved)
5503
- .then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
5504
- .then((startShare) => {
5505
- // This is a special case if we do an /floor grant followed by /media
5506
- // we actually get a OFFER from the server and a GLAR condition happens
5507
- if (startShare) {
5508
- // We are assuming that the clients are connected when doing an update
5509
- return this.requestScreenShareFloor();
5510
- }
5511
-
5512
- return Promise.resolve();
5513
- })
5518
+ .then(() =>
5519
+ this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
5520
+ lambda: () => {
5521
+ return Promise.resolve()
5522
+ .then(() =>
5523
+ this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus)
5524
+ )
5525
+ .then((startShare) => {
5526
+ // This is a special case if we do an /floor grant followed by /media
5527
+ // we actually get a OFFER from the server and a GLAR condition happens
5528
+ if (startShare) {
5529
+ // We are assuming that the clients are connected when doing an update
5530
+ return this.requestScreenShareFloor();
5531
+ }
5532
+
5533
+ return Promise.resolve();
5534
+ });
5535
+ },
5536
+ })
5537
+ )
5514
5538
  );
5515
5539
  }
5516
5540
 
@@ -5706,13 +5730,17 @@ export default class Meeting extends StatelessWebexPlugin {
5706
5730
  remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
5707
5731
  },
5708
5732
  })
5709
- .then(() => {
5710
- if (startShare) {
5711
- return this.requestScreenShareFloor();
5712
- }
5733
+ .then(() =>
5734
+ this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
5735
+ lambda: async () => {
5736
+ if (startShare) {
5737
+ return this.requestScreenShareFloor();
5738
+ }
5713
5739
 
5714
- return Promise.resolve();
5715
- })
5740
+ return undefined;
5741
+ },
5742
+ })
5743
+ )
5716
5744
  )
5717
5745
  .then(() => {
5718
5746
  this.mediaProperties.mediaDirection.sendShare = sendShare;
@@ -2209,6 +2209,190 @@ describe('plugin-meetings', () => {
2209
2209
  );
2210
2210
  assert.isTrue(myPromiseResolved);
2211
2211
  });
2212
+
2213
+ it('should request floor only after roap transaction is completed', async () => {
2214
+ const eventListeners = {};
2215
+
2216
+ meeting.webex.meetings.reachability = {
2217
+ isAnyClusterReachable: sandbox.stub().resolves(true)
2218
+ };
2219
+
2220
+ const fakeMediaConnection = {
2221
+ close: sinon.stub(),
2222
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2223
+ initiateOffer: sinon.stub().resolves({}),
2224
+
2225
+ // mock the on() method and store all the listeners
2226
+ on: sinon.stub().callsFake((event, listener) => {
2227
+ eventListeners[event] = listener;
2228
+ }),
2229
+
2230
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2231
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2232
+ if (eventListeners[Event.ROAP_STARTED]) {
2233
+ eventListeners[Event.ROAP_STARTED]();
2234
+ } else {
2235
+ throw new Error('ROAP_STARTED listener not registered')
2236
+ }
2237
+ return Promise.resolve();
2238
+ }),
2239
+ };
2240
+
2241
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2242
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2243
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2244
+
2245
+ const requestScreenShareFloorStub = sandbox.stub(meeting, 'requestScreenShareFloor').resolves({});
2246
+
2247
+ let myPromiseResolved = false;
2248
+
2249
+ meeting.meetingState = 'ACTIVE';
2250
+ await meeting.addMedia({
2251
+ mediaSettings: {},
2252
+ });
2253
+
2254
+ meeting
2255
+ .updateMedia({
2256
+ localShare: mockLocalShare,
2257
+ mediaSettings: {
2258
+ sendShare: true,
2259
+ },
2260
+ })
2261
+ .then(() => {
2262
+ myPromiseResolved = true;
2263
+ });
2264
+
2265
+ await testUtils.flushPromises();
2266
+
2267
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2268
+ assert.isFalse(myPromiseResolved);
2269
+
2270
+ // verify that requestScreenShareFloorStub was not called yet
2271
+ assert.notCalled(requestScreenShareFloorStub);
2272
+
2273
+ eventListeners[Event.ROAP_DONE]();
2274
+ await testUtils.flushPromises();
2275
+
2276
+ // now it should have been called
2277
+ assert.calledOnce(requestScreenShareFloorStub);
2278
+ });
2279
+ });
2280
+
2281
+ describe('#updateShare', () => {
2282
+ const mockLocalShare = {id: 'mock local share stream'};
2283
+ let eventListeners;
2284
+ let fakeMediaConnection;
2285
+ let requestScreenShareFloorStub;
2286
+
2287
+ const FAKE_TRACKS = {
2288
+ screenshareVideo: {
2289
+ id: 'fake share track',
2290
+ getSettings: sinon.stub().returns({}),
2291
+ },
2292
+ };
2293
+
2294
+ beforeEach(async () => {
2295
+ eventListeners = {};
2296
+
2297
+ sinon.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
2298
+ if (stream === mockLocalShare) {
2299
+ return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
2300
+ }
2301
+
2302
+ return {audioTrack: null, videoTrack: null};
2303
+ });
2304
+
2305
+ meeting.webex.meetings.reachability = {
2306
+ isAnyClusterReachable: sinon.stub().resolves(true)
2307
+ };
2308
+
2309
+ fakeMediaConnection = {
2310
+ close: sinon.stub(),
2311
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2312
+ initiateOffer: sinon.stub().resolves({}),
2313
+
2314
+ // mock the on() method and store all the listeners
2315
+ on: sinon.stub().callsFake((event, listener) => {
2316
+ eventListeners[event] = listener;
2317
+ }),
2318
+
2319
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2320
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2321
+ if (eventListeners[Event.ROAP_STARTED]) {
2322
+ eventListeners[Event.ROAP_STARTED]();
2323
+ } else {
2324
+ throw new Error('ROAP_STARTED listener not registered')
2325
+ }
2326
+ return Promise.resolve();
2327
+ }),
2328
+ };
2329
+
2330
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2331
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2332
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2333
+
2334
+ requestScreenShareFloorStub = sinon.stub(meeting, 'requestScreenShareFloor').resolves({});
2335
+
2336
+ meeting.meetingState = 'ACTIVE';
2337
+ await meeting.addMedia({
2338
+ mediaSettings: {},
2339
+ });
2340
+ });
2341
+
2342
+ afterEach(() => {
2343
+ sinon.restore();
2344
+ });
2345
+
2346
+ it('when starting share, it should request floor only after roap transaction is completed', async () => {
2347
+ let myPromiseResolved = false;
2348
+
2349
+ meeting
2350
+ .updateShare({
2351
+ sendShare: true,
2352
+ receiveShare: true,
2353
+ stream: mockLocalShare,
2354
+ })
2355
+ .then(() => {
2356
+ myPromiseResolved = true;
2357
+ });
2358
+
2359
+ await testUtils.flushPromises();
2360
+
2361
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2362
+ assert.isFalse(myPromiseResolved);
2363
+
2364
+ // verify that requestScreenShareFloorStub was not called yet
2365
+ assert.notCalled(requestScreenShareFloorStub);
2366
+
2367
+ eventListeners[Event.ROAP_DONE]();
2368
+ await testUtils.flushPromises();
2369
+
2370
+ // now it should have been called
2371
+ assert.calledOnce(requestScreenShareFloorStub);
2372
+ });
2373
+
2374
+ it('when changing screen share stream and no roap transaction happening, it requests floor immediately', async () => {
2375
+ let myPromiseResolved = false;
2376
+
2377
+ // simulate a case when no roap transaction is triggered by updateSendReceiveOptions
2378
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions = sinon.stub().resolves({});
2379
+
2380
+ meeting
2381
+ .updateShare({
2382
+ sendShare: true,
2383
+ receiveShare: true,
2384
+ stream: mockLocalShare,
2385
+ })
2386
+ .then(() => {
2387
+ myPromiseResolved = true;
2388
+ });
2389
+
2390
+ await testUtils.flushPromises();
2391
+
2392
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2393
+ assert.calledOnce(requestScreenShareFloorStub);
2394
+ assert.isTrue(myPromiseResolved);
2395
+ });
2212
2396
  });
2213
2397
 
2214
2398
  describe('#changeVideoLayout', () => {