in src/audiovideocontroller/DefaultAudioVideoController.ts [440:723]
private async actionConnect(reconnecting: boolean): Promise<void> {
this.initSignalingClient();
// We no longer need to watch for the early connection dropping; we're back where
// we otherwise would have been had we not pre-started.
this.uninstallPreStartObserver();
// Note that some of the assignments in this function exist to clean up previous connections.
// All future 'clean up' assignments should go in `AudioVideoControllerState.resetConnectionSpecificState`
// for consolidation purposes.
this.meetingSessionContext.mediaStreamBroker = this._mediaStreamBroker;
this.meetingSessionContext.realtimeController = this._realtimeController;
this.meetingSessionContext.audioMixController = this._audioMixController;
this.meetingSessionContext.audioVideoController = this;
this.enableSimulcast =
this.configuration.enableSimulcastForUnifiedPlanChromiumBasedBrowsers &&
new DefaultBrowserBehavior().hasChromiumWebRTC();
if (this.enableSimulcast && this.configuration.enableSVC) {
this.logger.warn(
'SVC cannot be enabled at the same time as simulcast. Disabling SVC, using simulcast.'
);
}
this.enableSVC =
!this.enableSimulcast &&
this.configuration.enableSVC &&
new DefaultBrowserBehavior().supportsScalableVideoCoding();
const useAudioConnection: boolean = !!this.configuration.urls.audioHostURL;
if (!useAudioConnection) {
this.logger.info(`Using video only transceiver controller`);
this.meetingSessionContext.transceiverController = new VideoOnlyTransceiverController(
this.logger,
this.meetingSessionContext.browserBehavior,
this.meetingSessionContext
);
} else if (this.enableSimulcast) {
this.logger.info(`Using transceiver controller with simulcast support`);
if (
new DefaultModality(this.configuration.credentials.attendeeId).hasModality(
DefaultModality.MODALITY_CONTENT
)
) {
this.meetingSessionContext.transceiverController = new SimulcastContentShareTransceiverController(
this.logger,
this.meetingSessionContext.browserBehavior,
this.meetingSessionContext
);
} else {
this.meetingSessionContext.transceiverController = new SimulcastTransceiverController(
this.logger,
this.meetingSessionContext.browserBehavior,
this.meetingSessionContext
);
}
} else {
this.logger.info(`Using default transceiver controller`);
this.meetingSessionContext.transceiverController = new DefaultTransceiverController(
this.logger,
this.meetingSessionContext.browserBehavior,
this.meetingSessionContext
);
}
this.meetingSessionContext.volumeIndicatorAdapter = new DefaultVolumeIndicatorAdapter(
this.logger,
this._realtimeController,
DefaultAudioVideoController.MIN_VOLUME_DECIBELS,
DefaultAudioVideoController.MAX_VOLUME_DECIBELS,
this.configuration.credentials.attendeeId
);
this.meetingSessionContext.videoTileController = this._videoTileController;
this.meetingSessionContext.videoDownlinkBandwidthPolicy = this.configuration.videoDownlinkBandwidthPolicy;
this.meetingSessionContext.videoUplinkBandwidthPolicy = this.configuration.videoUplinkBandwidthPolicy;
this.meetingSessionContext.enableSimulcast = this.enableSimulcast;
this.meetingSessionContext.enableSVC = this.enableSVC;
if (this.enableSimulcast) {
let simulcastPolicy = this.meetingSessionContext
.videoUplinkBandwidthPolicy as SimulcastUplinkPolicy;
if (!simulcastPolicy) {
simulcastPolicy = new DefaultSimulcastUplinkPolicy(
this.configuration.credentials.attendeeId,
this.meetingSessionContext.logger
);
this.meetingSessionContext.videoUplinkBandwidthPolicy = simulcastPolicy;
}
simulcastPolicy.addObserver(this);
if (!this.meetingSessionContext.videoDownlinkBandwidthPolicy) {
this.meetingSessionContext.videoDownlinkBandwidthPolicy = new VideoAdaptiveProbePolicy(
this.meetingSessionContext.logger
);
}
this.meetingSessionContext.videoStreamIndex = new SimulcastVideoStreamIndex(this.logger);
} else {
this.meetingSessionContext.enableSimulcast = false;
this.meetingSessionContext.videoStreamIndex = new DefaultVideoStreamIndex(this.logger);
if (!this.meetingSessionContext.videoUplinkBandwidthPolicy) {
this.meetingSessionContext.videoUplinkBandwidthPolicy = new NScaleVideoUplinkBandwidthPolicy(
this.configuration.credentials.attendeeId,
!this.meetingSessionContext.browserBehavior.disableResolutionScaleDown(),
this.meetingSessionContext.logger,
this.meetingSessionContext.browserBehavior
);
this.meetingSessionContext.videoUplinkBandwidthPolicy.setSVCEnabled(this.enableSVC);
}
if (!this.meetingSessionContext.videoDownlinkBandwidthPolicy) {
this.meetingSessionContext.videoDownlinkBandwidthPolicy = new AllHighestVideoBandwidthPolicy(
this.configuration.credentials.attendeeId
);
}
if (
this.meetingSessionContext.videoUplinkBandwidthPolicy.setTransceiverController &&
this.meetingSessionContext.videoUplinkBandwidthPolicy.updateTransceiverController
) {
this.useUpdateTransceiverControllerForUplink = true;
this.meetingSessionContext.videoUplinkBandwidthPolicy.setTransceiverController(
this.meetingSessionContext.transceiverController
);
}
this.meetingSessionContext.audioProfile = this._audioProfile;
}
if (
new DefaultModality(this.configuration.credentials.attendeeId).hasModality(
DefaultModality.MODALITY_CONTENT
)
) {
const enableUhdContent =
this.configuration.meetingFeatures.contentMaxResolution ===
VideoQualitySettings.VideoResolutionUHD;
if (enableUhdContent) {
// Increase default bandwidth for content share since this is not yet configuration that can be exposed
// without using simulcast
this.setVideoMaxBandwidthKbps(2500);
}
this.meetingSessionContext.videoUplinkBandwidthPolicy.setHighResolutionFeatureEnabled(
enableUhdContent
);
} else {
const enableFhdVideo =
this.configuration.meetingFeatures.videoMaxResolution ===
VideoQualitySettings.VideoResolutionFHD;
this.meetingSessionContext.videoUplinkBandwidthPolicy.setHighResolutionFeatureEnabled(
enableFhdVideo
);
}
if (this.meetingSessionContext.videoUplinkBandwidthPolicy && this.maxUplinkBandwidthKbps) {
this.meetingSessionContext.videoUplinkBandwidthPolicy.setIdealMaxBandwidthKbps(
this.maxUplinkBandwidthKbps
);
}
if (this.meetingSessionContext.videoDownlinkBandwidthPolicy.bindToTileController) {
this.meetingSessionContext.videoDownlinkBandwidthPolicy.bindToTileController(
this._videoTileController
);
}
if (this.meetingSessionContext.videoDownlinkBandwidthPolicy.setWantsResubscribeObserver) {
this.meetingSessionContext.videoDownlinkBandwidthPolicy.setWantsResubscribeObserver(() =>
this.update({ needsRenegotiation: false })
);
}
this.meetingSessionContext.lastKnownVideoAvailability = new MeetingSessionVideoAvailability();
this.meetingSessionContext.videoCaptureAndEncodeParameter = new DefaultVideoCaptureAndEncodeParameter(
0,
0,
0,
0,
false
);
this.meetingSessionContext.videosToReceive = new DefaultVideoStreamIdSet();
this.meetingSessionContext.videosPaused = new DefaultVideoStreamIdSet();
this.meetingSessionContext.statsCollector = new StatsCollector(this, this.logger);
this.meetingSessionContext.connectionMonitor = new SignalingAndMetricsConnectionMonitor(
this,
this._realtimeController,
this.connectionHealthData,
new DefaultPingPong(
this.meetingSessionContext.signalingClient,
DefaultAudioVideoController.PING_PONG_INTERVAL_MS,
this.logger
),
this.meetingSessionContext.statsCollector
);
this.meetingSessionContext.reconnectController = this._reconnectController;
this.meetingSessionContext.videoDeviceInformation = {};
this._videoTileController.registerVideoTileResolutionObserver(
this.meetingSessionContext.statsCollector
);
if (!reconnecting) {
this.totalRetryCount = 0;
this._reconnectController.reset();
this.startAudioVideoTimestamp = Date.now();
this.forEachObserver(observer => {
Maybe.of(observer.audioVideoDidStartConnecting).map(f => f.bind(observer)(false));
});
this.eventController?.publishEvent('meetingStartRequested');
}
this.meetingSessionContext.startAudioVideoTimestamp = this.startAudioVideoTimestamp;
if (this._reconnectController.hasStartedConnectionAttempt()) {
// This does not reset the reconnect deadline, but declare it's not the first connection.
this._reconnectController.startedConnectionAttempt(false);
} else {
this._reconnectController.startedConnectionAttempt(true);
}
// No attendee presence event will be triggered if there is no audio connection.
// Waiting for attendee presence is explicitly executed
// if `attendeePresenceTimeoutMs` is configured to larger than 0.
const needsToWaitForAttendeePresence =
useAudioConnection &&
this.meetingSessionContext.meetingSessionConfiguration.attendeePresenceTimeoutMs > 0;
this.logger.info('Needs to wait for attendee presence? ' + needsToWaitForAttendeePresence);
const connect = this.connectWithPromises(needsToWaitForAttendeePresence);
// The rest.
try {
await connect.run();
this.connectionHealthData.setConnectionStartTime();
const isContentShare = new DefaultModality(
this.configuration.credentials.attendeeId
).hasModality(DefaultModality.MODALITY_CONTENT);
this.connectionHealthData.setIsContentShare(isContentShare);
this._mediaStreamBroker.addMediaStreamBrokerObserver(this);
this.sessionStateController.perform(SessionStateControllerAction.FinishConnecting, () => {
/* istanbul ignore else */
if (this.eventController) {
this.meetingSessionContext.meetingStartDurationMs =
Date.now() - this.startAudioVideoTimestamp;
this.eventController.publishEvent('meetingStartSucceeded', {
maxVideoTileCount: this.meetingSessionContext.maxVideoTileCount,
poorConnectionCount: this.meetingSessionContext.poorConnectionCount,
retryCount: this.totalRetryCount,
signalingOpenDurationMs: this.meetingSessionContext.signalingOpenDurationMs,
iceGatheringDurationMs: this.meetingSessionContext.iceGatheringDurationMs,
meetingStartDurationMs: this.meetingSessionContext.meetingStartDurationMs,
});
}
this.meetingSessionContext.startTimeMs = Date.now();
this.actionFinishConnecting();
});
} catch (error) {
this.signalingTask = undefined;
const status = new MeetingSessionStatus(
this.getMeetingStatusCode(error) || MeetingSessionStatusCode.TaskFailed
);
this.logger.info(`Start failed: ${status} due to error ${error}.`);
// I am not able to successfully reach this state in the test suite with mock
// websockets -- it always ends up in 'Disconnecting' instead. As such, this
// has to be marked for Istanbul.
/* istanbul ignore if */
if (this.sessionStateController.state() === SessionStateControllerState.NotConnected) {
// There's no point trying to 'disconnect', because we're not connected.
// The session state controller will bail.
this.logger.info('Start failed and not connected. Not cleaning up.');
return;
}
this.sessionStateController.perform(SessionStateControllerAction.Fail, async () => {
await this.actionDisconnect(status, true, error);
if (!this.handleMeetingSessionStatus(status, error)) {
this.notifyStop(status, error);
}
});
}
}