Skip to content

Commit

Permalink
fix: Roap optimizations phase 2 (#3449)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcin-bazyl authored and mkesavan13 committed Apr 10, 2024
1 parent b0d33a6 commit d72597a
Show file tree
Hide file tree
Showing 11 changed files with 939 additions and 187 deletions.
2 changes: 1 addition & 1 deletion packages/@webex/plugin-meetings/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-ignore
import {hydraTypes} from '@webex/common';

type Enum<T extends Record<string, unknown>> = T[keyof T];
export type Enum<T extends Record<string, unknown>> = T[keyof T];

// *********** LOWERCASE / CAMELCASE STRINGS ************

Expand Down
191 changes: 132 additions & 59 deletions packages/@webex/plugin-meetings/src/meeting/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
import NetworkQualityMonitor from '../networkQualityMonitor';
import LoggerProxy from '../common/logs/logger-proxy';
import Trigger from '../common/events/trigger-proxy';
import Roap from '../roap/index';
import Roap, {
type TurnDiscoveryResult,
type TurnServerInfo,
type TurnDiscoverySkipReason,
} from '../roap/index';
import Media, {type BundlePolicy} from '../media';
import MediaProperties from '../media/properties';
import MeetingStateMachine from './state';
Expand Down Expand Up @@ -621,7 +625,7 @@ export default class Meeting extends StatelessWebexPlugin {
allowMediaInLobby: boolean;
localShareInstanceId: string;
remoteShareInstanceId: string;
turnDiscoverySkippedReason: string;
turnDiscoverySkippedReason: TurnDiscoverySkipReason;
turnServerUsed: boolean;
areVoiceaEventsSetup = false;
voiceaListenerCallbacks: object = {
Expand Down Expand Up @@ -4445,47 +4449,90 @@ export default class Meeting extends StatelessWebexPlugin {
* }
* })
*/
public joinWithMedia(
public async joinWithMedia(
options: {
joinOptions?: any;
mediaOptions?: AddMediaOptions;
} = {}
) {
const {mediaOptions, joinOptions} = options;
const {mediaOptions, joinOptions = {}} = options;

if (!mediaOptions?.allowMediaInLobby) {
return Promise.reject(
new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
);
}
this.allowMediaInLobby = true;

LoggerProxy.logger.info('Meeting:index#joinWithMedia called');

return this.join(joinOptions)
.then((joinResponse) =>
this.addMedia(mediaOptions).then((mediaResponse) => ({
join: joinResponse,
media: mediaResponse,
}))
)
.catch((error) => {
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
let joined = false;

Metrics.sendBehavioralMetric(
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
{
correlation_id: this.correlationId,
locus_id: this.locusUrl.split('/').pop(),
reason: error.message,
stack: error.stack,
},
{
type: error.name,
}
);
try {
let turnServerInfo;
let turnDiscoverySkippedReason;

return Promise.reject(error);
});
// @ts-ignore
joinOptions.reachability = await this.webex.meetings.reachability.getReachabilityResults();
const turnDiscoveryRequest = await this.roap.generateTurnDiscoveryRequestMessage(this, true);

({turnDiscoverySkippedReason} = turnDiscoveryRequest);
joinOptions.roapMessage = turnDiscoveryRequest.roapMessage;

const joinResponse = await this.join(joinOptions);

joined = true;

if (joinOptions.roapMessage) {
({turnServerInfo, turnDiscoverySkippedReason} =
await this.roap.handleTurnDiscoveryHttpResponse(this, joinResponse));

this.turnDiscoverySkippedReason = turnDiscoverySkippedReason;
this.turnServerUsed = !!turnServerInfo;

if (turnServerInfo === undefined) {
this.roap.abortTurnDiscovery();
}
}

const mediaResponse = await this.addMedia(mediaOptions, turnServerInfo);

return {
join: joinResponse,
media: mediaResponse,
};
} catch (error) {
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);

let leaveError;

this.roap.abortTurnDiscovery();

if (joined) {
try {
await this.leave({resourceId: joinOptions?.resourceId, reason: 'joinWithMedia failure'});
} catch (e) {
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> leave error', e);
leaveError = e;
}
}

Metrics.sendBehavioralMetric(
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
{
correlation_id: this.correlationId,
locus_id: this.locusUrl?.split('/').pop(), // if join fails, we may end up with no locusUrl
reason: error.message,
stack: error.stack,
leaveErrorReason: leaveError?.message,
},
{
type: error.name,
}
);

throw error;
}
}

/**
Expand Down Expand Up @@ -6328,50 +6375,66 @@ export default class Meeting extends StatelessWebexPlugin {
}
}

/**
* Performs TURN discovery as a separate call to the Locus /media API
*
* @param {boolean} isRetry
* @param {boolean} isForced
* @returns {Promise}
*/
private async doTurnDiscovery(isRetry: boolean, isForced: boolean): Promise<TurnDiscoveryResult> {
// @ts-ignore
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;

// @ts-ignore
this.webex.internal.newMetrics.submitInternalEvent({
name: 'internal.client.add-media.turn-discovery.start',
});

const turnDiscoveryResult = await this.roap.doTurnDiscovery(this, isRetry, isForced);

this.turnDiscoverySkippedReason = turnDiscoveryResult?.turnDiscoverySkippedReason;
this.turnServerUsed = !this.turnDiscoverySkippedReason;

// @ts-ignore
this.webex.internal.newMetrics.submitInternalEvent({
name: 'internal.client.add-media.turn-discovery.end',
});

if (this.turnServerUsed && turnDiscoveryResult.turnServerInfo) {
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
correlation_id: this.correlationId,
latency: cdl.getTurnDiscoveryTime(),
turnServerUsed: this.turnServerUsed,
retriedWithTurnServer: this.retriedWithTurnServer,
});
}

return turnDiscoveryResult;
}

/**
* Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
*
* @private
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
* @param {BundlePolicy} [bundlePolicy]
* @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
* @param {TurnServerInfo} [turnServerInfo]
* @returns {Promise<void>}
*/
private async establishMediaConnection(
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
bundlePolicy?: BundlePolicy,
isForced?: boolean
isForced?: boolean,
turnServerInfo?: TurnServerInfo
): Promise<void> {
const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
// @ts-ignore
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
const isRetry = this.retriedWithTurnServer;

try {
// @ts-ignore
this.webex.internal.newMetrics.submitInternalEvent({
name: 'internal.client.add-media.turn-discovery.start',
});

const turnDiscoveryObject = await this.roap.doTurnDiscovery(this, isRetry, isForced);

this.turnDiscoverySkippedReason = turnDiscoveryObject?.turnDiscoverySkippedReason;
this.turnServerUsed = !this.turnDiscoverySkippedReason;

// @ts-ignore
this.webex.internal.newMetrics.submitInternalEvent({
name: 'internal.client.add-media.turn-discovery.end',
});

const {turnServerInfo} = turnDiscoveryObject;

if (this.turnServerUsed && turnServerInfo) {
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
correlation_id: this.correlationId,
latency: cdl.getTurnDiscoveryTime(),
turnServerUsed: this.turnServerUsed,
retriedWithTurnServer: this.retriedWithTurnServer,
});
if (!turnServerInfo) {
({turnServerInfo} = await this.doTurnDiscovery(isRetry, isForced));
}

const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
Expand Down Expand Up @@ -6488,15 +6551,21 @@ export default class Meeting extends StatelessWebexPlugin {
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
*
* @param {AddMediaOptions} options
* @param {TurnServerInfo} turnServerInfo - TURN server information (used only internally by the SDK)
* @returns {Promise<void>}
* @public
* @memberof Meeting
*/
async addMedia(options: AddMediaOptions = {}): Promise<void> {
async addMedia(
options: AddMediaOptions = {},
turnServerInfo: TurnServerInfo = undefined
): Promise<void> {
this.retriedWithTurnServer = false;
this.hasMediaConnectionConnectedAtLeastOnce = false;
const LOG_HEADER = 'Meeting:index#addMedia -->';
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
LoggerProxy.logger.info(
`${LOG_HEADER} called with: ${JSON.stringify(options)}, ${JSON.stringify(turnServerInfo)}`
);

if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
throw new MeetingNotActiveError();
Expand All @@ -6514,14 +6583,13 @@ export default class Meeting extends StatelessWebexPlugin {
shareVideoEnabled = true,
remoteMediaManagerConfig,
bundlePolicy,
allowMediaInLobby,
} = options;

this.allowMediaInLobby = options?.allowMediaInLobby;

// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
// @ts-ignore - isUserUnadmitted coming from SelfUtil
if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
throw new UserInLobbyError();
}

Expand Down Expand Up @@ -6590,7 +6658,12 @@ export default class Meeting extends StatelessWebexPlugin {

this.createStatsAnalyzer();

await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
await this.establishMediaConnection(
remoteMediaManagerConfig,
bundlePolicy,
false,
turnServerInfo
);

await Meeting.handleDeviceLogging();

Expand Down
13 changes: 11 additions & 2 deletions packages/@webex/plugin-meetings/src/meeting/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
meetingNumber: any;
permissionToken: any;
preferTranscoding: any;
reachability: any;
breakoutsSupported: boolean;
locale?: string;
deviceCapabilities?: Array<string>;
Expand All @@ -143,6 +144,7 @@ export default class MeetingRequest extends StatelessWebexPlugin {
pin,
moveToResource,
roapMessage,
reachability,
preferTranscoding,
breakoutsSupported,
locale,
Expand Down Expand Up @@ -260,8 +262,15 @@ export default class MeetingRequest extends StatelessWebexPlugin {
};
}

if (roapMessage) {
body.localMedias = roapMessage.localMedias;
if (roapMessage || reachability) {
body.localMedias = [
{
localSdp: JSON.stringify({
roapMessage,
reachability,
}),
},
];
}

/// @ts-ignore
Expand Down
1 change: 1 addition & 0 deletions packages/@webex/plugin-meetings/src/meeting/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const MeetingUtil = {
locusUrl: meeting.locusUrl,
locusClusterUrl: meeting.meetingInfo?.locusClusterUrl,
correlationId: meeting.correlationId,
reachability: options.reachability,
roapMessage: options.roapMessage,
permissionToken: meeting.permissionToken,
resourceId: options.resourceId || null,
Expand Down
28 changes: 25 additions & 3 deletions packages/@webex/plugin-meetings/src/roap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import {ROAP} from '../constants';
import LoggerProxy from '../common/logs/logger-proxy';

import RoapRequest from './request';
import TurnDiscovery from './turnDiscovery';
import TurnDiscovery, {TurnDiscoveryResult} from './turnDiscovery';
import Meeting from '../meeting';
import MeetingUtil from '../meeting/util';
import Metrics from '../metrics';
import BEHAVIORAL_METRICS from '../metrics/constants';

export {
type TurnDiscoveryResult,
type TurnServerInfo,
type TurnDiscoverySkipReason,
} from './turnDiscovery';

/**
* Roap options
* @typedef {Object} RoapOptions
Expand Down Expand Up @@ -39,7 +45,7 @@ export default class Roap extends StatelessWebexPlugin {
options: any;
roapHandler: any;
roapRequest: any;
turnDiscovery: any;
turnDiscovery: TurnDiscovery;

/**
*
Expand Down Expand Up @@ -260,7 +266,23 @@ export default class Roap extends StatelessWebexPlugin {
* @param {Boolean} [isForced]
* @returns {Promise}
*/
doTurnDiscovery(meeting: Meeting, isReconnecting: boolean, isForced?: boolean) {
doTurnDiscovery(
meeting: Meeting,
isReconnecting: boolean,
isForced?: boolean
): Promise<TurnDiscoveryResult> {
return this.turnDiscovery.doTurnDiscovery(meeting, isReconnecting, isForced);
}

generateTurnDiscoveryRequestMessage(meeting: Meeting, isForced: boolean) {
return this.turnDiscovery.generateTurnDiscoveryRequestMessage(meeting, isForced);
}

handleTurnDiscoveryHttpResponse(meeting: Meeting, httpResponse: object) {
return this.turnDiscovery.handleTurnDiscoveryHttpResponse(meeting, httpResponse);
}

abortTurnDiscovery() {
return this.turnDiscovery.abort();
}
}
Loading

0 comments on commit d72597a

Please sign in to comment.