@webex/plugin-meetings 3.7.0-next.32 → 3.7.0-next.34
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/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +256 -166
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +9 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/roap/index.js +10 -8
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/constants.d.ts +5 -0
- package/dist/types/meeting/index.d.ts +11 -2
- package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -21
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/constants.ts +5 -0
- package/src/meeting/index.ts +110 -27
- package/src/meeting/locusMediaRequest.ts +7 -0
- package/src/meetings/util.ts +2 -1
- package/src/roap/index.ts +10 -8
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/meeting/index.js +367 -21
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/roap/index.ts +47 -0
package/src/meeting/index.ts
CHANGED
|
@@ -161,6 +161,7 @@ import {LocusMediaRequest} from './locusMediaRequest';
|
|
|
161
161
|
import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
|
|
162
162
|
import JoinWebinarError from '../common/errors/join-webinar-error';
|
|
163
163
|
import Member from '../member';
|
|
164
|
+
import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
|
|
164
165
|
|
|
165
166
|
// default callback so we don't call an undefined function, but in practice it should never be used
|
|
166
167
|
const DEFAULT_ICE_PHASE_CALLBACK = () => 'JOIN_MEETING_FINAL';
|
|
@@ -4627,11 +4628,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4627
4628
|
* Close the peer connections and remove them from the class.
|
|
4628
4629
|
* Cleanup any media connection related things.
|
|
4629
4630
|
*
|
|
4631
|
+
* @param {boolean} resetMuteStates whether to also reset the audio/video mute state information
|
|
4630
4632
|
* @returns {Promise}
|
|
4631
4633
|
* @public
|
|
4632
4634
|
* @memberof Meeting
|
|
4633
4635
|
*/
|
|
4634
|
-
public closePeerConnections() {
|
|
4636
|
+
public closePeerConnections(resetMuteStates = true) {
|
|
4635
4637
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
4636
4638
|
if (this.remoteMediaManager) {
|
|
4637
4639
|
this.remoteMediaManager.stop();
|
|
@@ -4648,8 +4650,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4648
4650
|
this.setNetworkStatus(undefined);
|
|
4649
4651
|
}
|
|
4650
4652
|
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
+
if (resetMuteStates) {
|
|
4654
|
+
this.audio = null;
|
|
4655
|
+
this.video = null;
|
|
4656
|
+
}
|
|
4653
4657
|
|
|
4654
4658
|
return Promise.resolve();
|
|
4655
4659
|
}
|
|
@@ -4909,7 +4913,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4909
4913
|
* @param {Object} options - options to join with media
|
|
4910
4914
|
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
4911
4915
|
* @param {AddMediaOptions} [options.mediaOptions] - see #addMedia()
|
|
4912
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
4916
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia(), multistreamEnabled: flag to indicate if we managed to join in multistream mode}
|
|
4913
4917
|
* @public
|
|
4914
4918
|
* @memberof Meeting
|
|
4915
4919
|
* @example
|
|
@@ -4999,6 +5003,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4999
5003
|
return {
|
|
5000
5004
|
join: joinResponse,
|
|
5001
5005
|
media: mediaResponse,
|
|
5006
|
+
multistreamEnabled: this.isMultistream,
|
|
5002
5007
|
};
|
|
5003
5008
|
} catch (error) {
|
|
5004
5009
|
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
@@ -5007,7 +5012,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5007
5012
|
|
|
5008
5013
|
this.roap.abortTurnDiscovery();
|
|
5009
5014
|
|
|
5010
|
-
if
|
|
5015
|
+
// if this was the first attempt, let's do a retry
|
|
5016
|
+
let shouldRetry = !isRetry;
|
|
5017
|
+
|
|
5018
|
+
if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
|
|
5019
|
+
// errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
|
|
5020
|
+
// so there is no point doing a retry
|
|
5021
|
+
shouldRetry = false;
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
// we only want to call leave if join was successful and this was a retry or we won't be doing any more retries
|
|
5025
|
+
if (joined && (isRetry || !shouldRetry)) {
|
|
5011
5026
|
try {
|
|
5012
5027
|
await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
|
|
5013
5028
|
} catch (e) {
|
|
@@ -5031,15 +5046,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5031
5046
|
}
|
|
5032
5047
|
);
|
|
5033
5048
|
|
|
5034
|
-
// if this was the first attempt, let's do a retry
|
|
5035
|
-
let shouldRetry = !isRetry;
|
|
5036
|
-
|
|
5037
|
-
if (CallDiagnosticUtils.isSdpOfferCreationError(error)) {
|
|
5038
|
-
// errors related to offer creation (for example missing H264 codec) will happen again no matter how many times we try,
|
|
5039
|
-
// so there is no point doing a retry
|
|
5040
|
-
shouldRetry = false;
|
|
5041
|
-
}
|
|
5042
|
-
|
|
5043
5049
|
if (shouldRetry) {
|
|
5044
5050
|
LoggerProxy.logger.warn('Meeting:index#joinWithMedia --> retrying call to joinWithMedia');
|
|
5045
5051
|
this.joinWithMediaRetryInfo.isRetry = true;
|
|
@@ -5295,7 +5301,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5295
5301
|
(this.config.receiveReactions || options.receiveReactions) &&
|
|
5296
5302
|
this.isReactionsSupported()
|
|
5297
5303
|
) {
|
|
5298
|
-
const
|
|
5304
|
+
const member = this.members.membersCollection.get(e.data.sender.participantId);
|
|
5305
|
+
if (!member) {
|
|
5306
|
+
// @ts-ignore -- fix type
|
|
5307
|
+
LoggerProxy.logger.warn(
|
|
5308
|
+
`Meeting:index#processRelayEvent --> Skipping handling of ${REACTION_RELAY_TYPES.REACTION} for ${this.id}. participantId ${e.data.sender.participantId} does not exist in membersCollection.`
|
|
5309
|
+
);
|
|
5310
|
+
break;
|
|
5311
|
+
}
|
|
5312
|
+
|
|
5313
|
+
const {name} = member;
|
|
5299
5314
|
const processedReaction: ProcessedReaction = {
|
|
5300
5315
|
reaction: e.data.reaction,
|
|
5301
5316
|
sender: {
|
|
@@ -5349,6 +5364,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5349
5364
|
this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
|
|
5350
5365
|
);
|
|
5351
5366
|
|
|
5367
|
+
// @ts-ignore
|
|
5368
|
+
this.webex.internal.voicea.deregisterEvents();
|
|
5369
|
+
|
|
5352
5370
|
this.areVoiceaEventsSetup = false;
|
|
5353
5371
|
this.triggerStopReceivingTranscriptionEvent();
|
|
5354
5372
|
}
|
|
@@ -6064,6 +6082,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6064
6082
|
public roapMessageReceived = (roapMessage: RoapMessage) => {
|
|
6065
6083
|
const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
|
|
6066
6084
|
|
|
6085
|
+
if (this.isMultistream && mediaServer !== 'homer') {
|
|
6086
|
+
throw new MultistreamNotSupportedError(
|
|
6087
|
+
`Client asked for multistream backend (Homer), but got ${mediaServer} instead`
|
|
6088
|
+
);
|
|
6089
|
+
}
|
|
6067
6090
|
this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
|
|
6068
6091
|
|
|
6069
6092
|
if (mediaServer) {
|
|
@@ -6186,16 +6209,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6186
6209
|
logText: `${LOG_HEADER} Roap Offer`,
|
|
6187
6210
|
}
|
|
6188
6211
|
).catch((error) => {
|
|
6212
|
+
const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
|
|
6213
|
+
|
|
6189
6214
|
// @ts-ignore
|
|
6190
6215
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
6191
6216
|
name: 'client.media-engine.remote-sdp-received',
|
|
6192
6217
|
payload: {
|
|
6193
|
-
canProceed:
|
|
6218
|
+
canProceed: multistreamNotSupported,
|
|
6194
6219
|
errors: [
|
|
6195
6220
|
// @ts-ignore
|
|
6196
6221
|
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
6197
6222
|
{
|
|
6198
|
-
clientErrorCode:
|
|
6223
|
+
clientErrorCode: multistreamNotSupported
|
|
6224
|
+
? CALL_DIAGNOSTIC_CONFIG.MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE
|
|
6225
|
+
: CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
|
|
6199
6226
|
}
|
|
6200
6227
|
),
|
|
6201
6228
|
],
|
|
@@ -6203,7 +6230,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6203
6230
|
options: {meetingId: this.id, rawError: error},
|
|
6204
6231
|
});
|
|
6205
6232
|
|
|
6206
|
-
this.deferSDPAnswer.reject(
|
|
6233
|
+
this.deferSDPAnswer.reject(error);
|
|
6207
6234
|
clearTimeout(this.sdpResponseTimer);
|
|
6208
6235
|
this.sdpResponseTimer = undefined;
|
|
6209
6236
|
});
|
|
@@ -7093,7 +7120,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7093
7120
|
|
|
7094
7121
|
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
7095
7122
|
|
|
7096
|
-
LoggerProxy.logger.info(
|
|
7123
|
+
LoggerProxy.logger.info(
|
|
7124
|
+
`${LOG_HEADER} media connection created this.isMultistream=${this.isMultistream}`
|
|
7125
|
+
);
|
|
7097
7126
|
|
|
7098
7127
|
if (this.isMultistream) {
|
|
7099
7128
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -7171,6 +7200,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7171
7200
|
}
|
|
7172
7201
|
}
|
|
7173
7202
|
|
|
7203
|
+
/**
|
|
7204
|
+
* Cleans up stats analyzer, peer connection and other things before
|
|
7205
|
+
* we can create a new transcoded media connection
|
|
7206
|
+
*
|
|
7207
|
+
* @private
|
|
7208
|
+
* @returns {Promise<void>}
|
|
7209
|
+
*/
|
|
7210
|
+
private async downgradeFromMultistreamToTranscoded(): Promise<void> {
|
|
7211
|
+
if (this.statsAnalyzer) {
|
|
7212
|
+
await this.statsAnalyzer.stopAnalyzer();
|
|
7213
|
+
}
|
|
7214
|
+
this.statsAnalyzer = null;
|
|
7215
|
+
|
|
7216
|
+
this.isMultistream = false;
|
|
7217
|
+
|
|
7218
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
7219
|
+
// close peer connection, but don't reset mute state information, because we will want to use it on the retry
|
|
7220
|
+
this.closePeerConnections(false);
|
|
7221
|
+
|
|
7222
|
+
this.mediaProperties.unsetPeerConnection();
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
this.locusMediaRequest?.downgradeFromMultistreamToTranscoded();
|
|
7226
|
+
|
|
7227
|
+
this.createStatsAnalyzer();
|
|
7228
|
+
}
|
|
7229
|
+
|
|
7174
7230
|
/**
|
|
7175
7231
|
* Sends stats report, closes peer connection and cleans up any media connection
|
|
7176
7232
|
* related things before trying to establish media connection again with turn server
|
|
@@ -7365,13 +7421,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7365
7421
|
|
|
7366
7422
|
this.createStatsAnalyzer();
|
|
7367
7423
|
|
|
7368
|
-
|
|
7369
|
-
|
|
7370
|
-
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7424
|
+
try {
|
|
7425
|
+
await this.establishMediaConnection(
|
|
7426
|
+
remoteMediaManagerConfig,
|
|
7427
|
+
bundlePolicy,
|
|
7428
|
+
forceTurnDiscovery,
|
|
7429
|
+
turnServerInfo
|
|
7430
|
+
);
|
|
7431
|
+
} catch (error) {
|
|
7432
|
+
if (error instanceof MultistreamNotSupportedError) {
|
|
7433
|
+
LoggerProxy.logger.warn(
|
|
7434
|
+
`${LOG_HEADER} we asked for multistream backend (Homer), but got transcoded backend, recreating media connection...`
|
|
7435
|
+
);
|
|
7436
|
+
|
|
7437
|
+
await this.downgradeFromMultistreamToTranscoded();
|
|
7374
7438
|
|
|
7439
|
+
// Establish new media connection with forced TURN discovery
|
|
7440
|
+
// We need to do TURN discovery again, because backend will be creating a new confluence, so it might land on a different node or cluster
|
|
7441
|
+
await this.establishMediaConnection(
|
|
7442
|
+
remoteMediaManagerConfig,
|
|
7443
|
+
bundlePolicy,
|
|
7444
|
+
true,
|
|
7445
|
+
undefined
|
|
7446
|
+
);
|
|
7447
|
+
} else {
|
|
7448
|
+
throw error;
|
|
7449
|
+
}
|
|
7450
|
+
}
|
|
7375
7451
|
if (this.mediaProperties.hasLocalShareStream()) {
|
|
7376
7452
|
await this.enqueueScreenShareFloorRequest();
|
|
7377
7453
|
}
|
|
@@ -8341,7 +8417,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8341
8417
|
if (layoutType) {
|
|
8342
8418
|
if (!LAYOUT_TYPES.includes(layoutType)) {
|
|
8343
8419
|
return this.rejectWithErrorLog(
|
|
8344
|
-
|
|
8420
|
+
`Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType "${layoutType}" received.`
|
|
8345
8421
|
);
|
|
8346
8422
|
}
|
|
8347
8423
|
|
|
@@ -8699,6 +8775,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8699
8775
|
this.stopTranscription();
|
|
8700
8776
|
this.transcription = undefined;
|
|
8701
8777
|
}
|
|
8778
|
+
|
|
8779
|
+
this.annotation.deregisterEvents();
|
|
8780
|
+
|
|
8781
|
+
// @ts-ignore - fix types
|
|
8782
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
8702
8783
|
};
|
|
8703
8784
|
|
|
8704
8785
|
/**
|
|
@@ -8736,10 +8817,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
8736
8817
|
|
|
8737
8818
|
return;
|
|
8738
8819
|
}
|
|
8739
|
-
|
|
8820
|
+
|
|
8740
8821
|
const keepAliveInterval = (this.joinedWith.keepAliveSecs - 1) * 750; // taken from UCF
|
|
8741
8822
|
|
|
8742
8823
|
this.keepAliveTimerId = setInterval(() => {
|
|
8824
|
+
const {keepAliveUrl} = this.joinedWith;
|
|
8825
|
+
|
|
8743
8826
|
this.meetingRequest.keepAlive({keepAliveUrl}).catch((error) => {
|
|
8744
8827
|
LoggerProxy.logger.warn(
|
|
8745
8828
|
`Meeting:index#startKeepAlive --> Stopping sending keepAlives to ${keepAliveUrl} after error ${error}`
|
|
@@ -342,4 +342,11 @@ export class LocusMediaRequest extends WebexPlugin {
|
|
|
342
342
|
public isConfluenceCreated() {
|
|
343
343
|
return this.confluenceState === 'created';
|
|
344
344
|
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* This method needs to be called when we downgrade from multistream to transcoded connection.
|
|
348
|
+
*/
|
|
349
|
+
public downgradeFromMultistreamToTranscoded() {
|
|
350
|
+
this.config.preferTranscoding = true;
|
|
351
|
+
}
|
|
345
352
|
}
|
package/src/meetings/util.ts
CHANGED
package/src/roap/index.ts
CHANGED
|
@@ -231,14 +231,16 @@ export default class Roap extends StatelessWebexPlugin {
|
|
|
231
231
|
headers,
|
|
232
232
|
} = remoteSdp.roapMessage;
|
|
233
233
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
234
|
+
if (messageType === ROAP.ROAP_TYPES.ANSWER) {
|
|
235
|
+
roapAnswer = {
|
|
236
|
+
seq: answerSeq,
|
|
237
|
+
messageType,
|
|
238
|
+
sdp: sdps[0],
|
|
239
|
+
errorType,
|
|
240
|
+
errorCause,
|
|
241
|
+
headers,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
246
|
|
|
@@ -413,6 +413,51 @@ describe('live-annotation', () => {
|
|
|
413
413
|
});
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
|
-
});
|
|
417
416
|
|
|
417
|
+
describe('#deregisterEvents', () => {
|
|
418
|
+
let llmOn;
|
|
419
|
+
let llmOff;
|
|
420
|
+
let mercuryOn;
|
|
421
|
+
let mercuryOff;
|
|
422
|
+
|
|
423
|
+
beforeEach(() => {
|
|
424
|
+
llmOn = sinon.spy(webex.internal.llm, 'on');
|
|
425
|
+
llmOff = sinon.spy(webex.internal.llm, 'off');
|
|
426
|
+
mercuryOn = sinon.spy(webex.internal.mercury, 'on');
|
|
427
|
+
mercuryOff = sinon.spy(webex.internal.mercury, 'off');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('cleans up events', () => {
|
|
431
|
+
annotationService.locusUrlUpdate(locusUrl);
|
|
432
|
+
assert.calledWith(
|
|
433
|
+
mercuryOn,
|
|
434
|
+
'event:locus.approval_request',
|
|
435
|
+
annotationService.eventCommandProcessor,
|
|
436
|
+
annotationService
|
|
437
|
+
);
|
|
438
|
+
assert.calledWith(
|
|
439
|
+
llmOn,
|
|
440
|
+
'event:relay.event',
|
|
441
|
+
annotationService.eventDataProcessor,
|
|
442
|
+
annotationService
|
|
443
|
+
);
|
|
444
|
+
assert.match(annotationService.hasSubscribedToEvents, true);
|
|
445
|
+
|
|
446
|
+
annotationService.deregisterEvents();
|
|
447
|
+
assert.calledWith(llmOff, 'event:relay.event', annotationService.eventDataProcessor);
|
|
448
|
+
assert.calledWith(
|
|
449
|
+
mercuryOff,
|
|
450
|
+
'event:locus.approval_request',
|
|
451
|
+
annotationService.eventCommandProcessor
|
|
452
|
+
);
|
|
453
|
+
assert.match(annotationService.hasSubscribedToEvents, false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('does not call llm off if events have not been registered', () => {
|
|
457
|
+
annotationService.deregisterEvents();
|
|
458
|
+
assert.notCalled(llmOff);
|
|
459
|
+
assert.notCalled(mercuryOff);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
418
463
|
});
|