@webex/plugin-meetings 3.7.0-next.33 → 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.
@@ -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
- this.audio = null;
4652
- this.video = null;
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 (joined && isRetry) {
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;
@@ -6076,6 +6082,11 @@ export default class Meeting extends StatelessWebexPlugin {
6076
6082
  public roapMessageReceived = (roapMessage: RoapMessage) => {
6077
6083
  const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
6078
6084
 
6085
+ if (this.isMultistream && mediaServer !== 'homer') {
6086
+ throw new MultistreamNotSupportedError(
6087
+ `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
6088
+ );
6089
+ }
6079
6090
  this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
6080
6091
 
6081
6092
  if (mediaServer) {
@@ -6198,16 +6209,20 @@ export default class Meeting extends StatelessWebexPlugin {
6198
6209
  logText: `${LOG_HEADER} Roap Offer`,
6199
6210
  }
6200
6211
  ).catch((error) => {
6212
+ const multistreamNotSupported = error instanceof MultistreamNotSupportedError;
6213
+
6201
6214
  // @ts-ignore
6202
6215
  this.webex.internal.newMetrics.submitClientEvent({
6203
6216
  name: 'client.media-engine.remote-sdp-received',
6204
6217
  payload: {
6205
- canProceed: false,
6218
+ canProceed: multistreamNotSupported,
6206
6219
  errors: [
6207
6220
  // @ts-ignore
6208
6221
  this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
6209
6222
  {
6210
- clientErrorCode: CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
6223
+ clientErrorCode: multistreamNotSupported
6224
+ ? CALL_DIAGNOSTIC_CONFIG.MULTISTREAM_NOT_AVAILABLE_CLIENT_CODE
6225
+ : CALL_DIAGNOSTIC_CONFIG.MISSING_ROAP_ANSWER_CLIENT_CODE,
6211
6226
  }
6212
6227
  ),
6213
6228
  ],
@@ -6215,7 +6230,7 @@ export default class Meeting extends StatelessWebexPlugin {
6215
6230
  options: {meetingId: this.id, rawError: error},
6216
6231
  });
6217
6232
 
6218
- this.deferSDPAnswer.reject(new Error('failed to send ROAP SDP offer'));
6233
+ this.deferSDPAnswer.reject(error);
6219
6234
  clearTimeout(this.sdpResponseTimer);
6220
6235
  this.sdpResponseTimer = undefined;
6221
6236
  });
@@ -7105,7 +7120,9 @@ export default class Meeting extends StatelessWebexPlugin {
7105
7120
 
7106
7121
  const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
7107
7122
 
7108
- LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
7123
+ LoggerProxy.logger.info(
7124
+ `${LOG_HEADER} media connection created this.isMultistream=${this.isMultistream}`
7125
+ );
7109
7126
 
7110
7127
  if (this.isMultistream) {
7111
7128
  this.remoteMediaManager = new RemoteMediaManager(
@@ -7183,6 +7200,33 @@ export default class Meeting extends StatelessWebexPlugin {
7183
7200
  }
7184
7201
  }
7185
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
+
7186
7230
  /**
7187
7231
  * Sends stats report, closes peer connection and cleans up any media connection
7188
7232
  * related things before trying to establish media connection again with turn server
@@ -7377,13 +7421,33 @@ export default class Meeting extends StatelessWebexPlugin {
7377
7421
 
7378
7422
  this.createStatsAnalyzer();
7379
7423
 
7380
- await this.establishMediaConnection(
7381
- remoteMediaManagerConfig,
7382
- bundlePolicy,
7383
- forceTurnDiscovery,
7384
- turnServerInfo
7385
- );
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
+ );
7386
7436
 
7437
+ await this.downgradeFromMultistreamToTranscoded();
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
+ }
7387
7451
  if (this.mediaProperties.hasLocalShareStream()) {
7388
7452
  await this.enqueueScreenShareFloorRequest();
7389
7453
  }
@@ -8353,7 +8417,7 @@ export default class Meeting extends StatelessWebexPlugin {
8353
8417
  if (layoutType) {
8354
8418
  if (!LAYOUT_TYPES.includes(layoutType)) {
8355
8419
  return this.rejectWithErrorLog(
8356
- 'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType received.'
8420
+ `Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType "${layoutType}" received.`
8357
8421
  );
8358
8422
  }
8359
8423
 
@@ -8753,10 +8817,12 @@ export default class Meeting extends StatelessWebexPlugin {
8753
8817
 
8754
8818
  return;
8755
8819
  }
8756
- const {keepAliveUrl} = this.joinedWith;
8820
+
8757
8821
  const keepAliveInterval = (this.joinedWith.keepAliveSecs - 1) * 750; // taken from UCF
8758
8822
 
8759
8823
  this.keepAliveTimerId = setInterval(() => {
8824
+ const {keepAliveUrl} = this.joinedWith;
8825
+
8760
8826
  this.meetingRequest.keepAlive({keepAliveUrl}).catch((error) => {
8761
8827
  LoggerProxy.logger.warn(
8762
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
  }
@@ -90,7 +90,8 @@ MeetingsUtil.getMediaServer = (sdp) => {
90
90
  .find((line) => line.startsWith('o='))
91
91
  .split(' ')
92
92
  .shift()
93
- .replace('o=', '');
93
+ .replace('o=', '')
94
+ .toLowerCase();
94
95
  } catch {
95
96
  mediaServer = undefined;
96
97
  }
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
- roapAnswer = {
235
- seq: answerSeq,
236
- messageType,
237
- sdp: sdps[0],
238
- errorType,
239
- errorCause,
240
- headers,
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