@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/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/meeting/index.js +72 -34
- package/dist/meeting/index.js.map +1 -1
- package/package.json +18 -18
- package/src/meeting/index.ts +46 -18
- package/test/unit/spec/meeting/index.js +184 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webex/plugin-meetings",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
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.
|
|
36
|
-
"@webex/test-helper-chai": "3.0.0-beta.
|
|
37
|
-
"@webex/test-helper-mocha": "3.0.0-beta.
|
|
38
|
-
"@webex/test-helper-mock-webex": "3.0.0-beta.
|
|
39
|
-
"@webex/test-helper-retry": "3.0.0-beta.
|
|
40
|
-
"@webex/test-helper-test-users": "3.0.0-beta.
|
|
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.
|
|
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.
|
|
51
|
-
"@webex/internal-plugin-device": "3.0.0-beta.
|
|
52
|
-
"@webex/internal-plugin-llm": "3.0.0-beta.
|
|
53
|
-
"@webex/internal-plugin-mercury": "3.0.0-beta.
|
|
54
|
-
"@webex/internal-plugin-metrics": "3.0.0-beta.
|
|
55
|
-
"@webex/internal-plugin-support": "3.0.0-beta.
|
|
56
|
-
"@webex/internal-plugin-user": "3.0.0-beta.
|
|
57
|
-
"@webex/plugin-people": "3.0.0-beta.
|
|
58
|
-
"@webex/plugin-rooms": "3.0.0-beta.
|
|
59
|
-
"@webex/webex-core": "3.0.0-beta.
|
|
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",
|
package/src/meeting/index.ts
CHANGED
|
@@ -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:
|
|
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(() =>
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
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
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5733
|
+
.then(() =>
|
|
5734
|
+
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
5735
|
+
lambda: async () => {
|
|
5736
|
+
if (startShare) {
|
|
5737
|
+
return this.requestScreenShareFloor();
|
|
5738
|
+
}
|
|
5713
5739
|
|
|
5714
|
-
|
|
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', () => {
|