src/audiovideocontroller/AudioVideoControllerState.ts (125 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import AudioMixController from '../audiomixcontroller/AudioMixController';
import AudioProfile from '../audioprofile/AudioProfile';
import AudioVideoController from '../audiovideocontroller/AudioVideoController';
import ExtendedBrowserBehavior from '../browserbehavior/ExtendedBrowserBehavior';
import ConnectionMonitor from '../connectionmonitor/ConnectionMonitor';
import EventController from '../eventcontroller/EventController';
import Logger from '../logger/Logger';
import MediaStreamBroker from '../mediastreambroker/MediaStreamBroker';
import MeetingSessionConfiguration from '../meetingsession/MeetingSessionConfiguration';
import MeetingSessionTURNCredentials from '../meetingsession/MeetingSessionTURNCredentials';
import MeetingSessionVideoAvailability from '../meetingsession/MeetingSessionVideoAvailability';
import RealtimeController from '../realtimecontroller/RealtimeController';
import ReconnectController from '../reconnectcontroller/ReconnectController';
import RemovableObserver from '../removableobserver/RemovableObserver';
import SDP from '../sdp/SDP';
import VideoCodecCapability from '../sdp/VideoCodecCapability';
import SignalingClient from '../signalingclient/SignalingClient';
import SignalingClientVideoSubscriptionConfiguration from '../signalingclient/SignalingClientVideoSubscriptionConfiguration';
import { SdkIndexFrame, SdkStreamServiceType } from '../signalingprotocol/SignalingProtocol.js';
import StatsCollector from '../statscollector/StatsCollector';
import TransceiverController from '../transceivercontroller/TransceiverController';
import VideoCaptureAndEncodeParameter from '../videocaptureandencodeparameter/VideoCaptureAndEncodeParameter';
import VideoDownlinkBandwidthPolicy from '../videodownlinkbandwidthpolicy/VideoDownlinkBandwidthPolicy';
import VideoStreamIdSet from '../videostreamidset/VideoStreamIdSet';
import VideoStreamIndex from '../videostreamindex/VideoStreamIndex';
import VideoTileController from '../videotilecontroller/VideoTileController';
import VideoUplinkBandwidthPolicy from '../videouplinkbandwidthpolicy/VideoUplinkBandwidthPolicy';
import VolumeIndicatorAdapter from '../volumeindicatoradapter/VolumeIndicatorAdapter';
export const DEFAULT_VIDEO_SUBSCRIPTION_LIMIT = 25;
/**
* [[AudioVideoControllerState]] includes the compute resources shared by [[DefaultAudioVideoController]] and any running [[Task]].
*
* **Note**: Any additions to this class need to consider whether they need to be reset in `resetConnectionSpecificState`, `CleanStoppedSessionTask`, or
* `CleanRestartedSessionTask`, e.g. if it is relies on backend state and will go stale across reconnections. Failing
* to reset state may lead to unexpected behavior.
*/
export default class AudioVideoControllerState {
logger: Logger | null = null;
browserBehavior: ExtendedBrowserBehavior | null = null;
meetingSessionConfiguration: MeetingSessionConfiguration | null = null;
signalingClient: SignalingClient | null = null;
peer: RTCPeerConnection | null = null;
previousSdpOffer: SDP | null = null;
sdpOfferInit: RTCSessionDescriptionInit | null = null;
audioVideoController: AudioVideoController | null = null;
realtimeController: RealtimeController | null = null;
videoTileController: VideoTileController | null = null;
mediaStreamBroker: MediaStreamBroker | null = null;
activeAudioInput: MediaStream | undefined = undefined;
activeVideoInput: MediaStream | undefined = undefined;
audioMixController: AudioMixController | null = null;
transceiverController: TransceiverController | null = null;
indexFrame: SdkIndexFrame | null = null;
iceCandidates: RTCIceCandidate[] = [];
iceCandidateHandler: ((event: RTCPeerConnectionIceEvent) => void) | null = null;
iceGatheringStateEventHandler: (() => void) | null = null;
sdpAnswer: string | null = null;
turnCredentials: MeetingSessionTURNCredentials | null = null;
reconnectController: ReconnectController | null = null;
removableObservers: RemovableObserver[] = [];
audioProfile: AudioProfile | null = null;
videoStreamIndex: VideoStreamIndex | null = null;
videoDownlinkBandwidthPolicy: VideoDownlinkBandwidthPolicy | null = null;
videoUplinkBandwidthPolicy: VideoUplinkBandwidthPolicy | null = null;
lastKnownVideoAvailability: MeetingSessionVideoAvailability | null = null;
videoCaptureAndEncodeParameter: VideoCaptureAndEncodeParameter | null = null;
// An unordered list of IDs provided by the downlink policy that
// we will eventually subscribe to.
videosToReceive: VideoStreamIdSet | null = null;
// The last processed set of IDs provided by the policy, so that we can
// compare what changes were additions, stream switches, or removals.
lastVideosToReceive: VideoStreamIdSet | null = null;
// An ordered list corresponding to `videosToReceive` where the order
// itself correspond to transceivers; 0 in this list corresponds to an inactive tranceiver.
videoSubscriptions: number[] | null = null;
// The last calculated list of subscription configuration used to send a remote video
// update (i.e. not necessarily related to what is in subscribe). This is used to make the remote
// video update a differential message (i.e. only sending changes)
//
// This is stored as a map keyed by group ID for convenience
lastVideoSubscriptionConfiguration: Map<
number,
SignalingClientVideoSubscriptionConfiguration
> = new Map();
// The video subscription limit is set by the backend and is subject to change in future.
// This value is set in the `JoinAndReceiveIndexTask` when we process the `SdkJoinAckFrame`
// and is used in the `ReceiveVideoStreamIndexTask` to limit the total number of streams
// that we include in the `videosToReceive`.
videoSubscriptionLimit: number = DEFAULT_VIDEO_SUBSCRIPTION_LIMIT;
// The previous SDP answer will be used as a dictionary to seed the compression library
// during decompressing the compressed SDP answer.
previousSdpAnswerAsString: string = '';
// This flag indicates if the backend supports compression for the client.
serverSupportsCompression: boolean = false;
// Values set by `setVideoCodecSendPreferences`.
videoSendCodecPreferences: VideoCodecCapability[] = [];
// Calculated as the highest priority available codec set in the (possibly munged) SDP answer
// that is provide to the peer connection, which will be what is sent.
currentVideoSendCodec: VideoCodecCapability | undefined = undefined;
// Intersection of `videoSendCodecPreferences` and the supported receive codecs of
// all the other clients in the meeting.
meetingSupportedVideoSendCodecPreferences: VideoCodecCapability[] | undefined = undefined;
// Calculated as the list of available codec set in the (possibly munged) SDP answer
// that is provided to the peer connection, which will be ordered by priority.
prioritizedSendVideoCodecCapabilities: VideoCodecCapability[] = [];
// Video codecs that we have detected encoding issues with that we will
// avoid using even if they are provided in `videoSendCodecPreferences`
degradedVideoSendCodecs: VideoCodecCapability[] = [];
videosPaused: VideoStreamIdSet | null = null;
videoDuplexMode: SdkStreamServiceType | null = null;
volumeIndicatorAdapter: VolumeIndicatorAdapter | null = null;
statsCollector: StatsCollector | null = null;
connectionMonitor: ConnectionMonitor | null = null;
// This state is deprecated and unused.
videoInputAttachedTimestampMs: number = 0;
// This state is deprecated and unused.
audioDeviceInformation: { [id: string]: string } = {};
// This state is deprecated and unused.
videoDeviceInformation: { [id: string]: string } = {};
enableSimulcast: boolean = false;
// If set to true, the client will actively try to use Scalable Video Coding (SVC) features
// if possible. The availability of these features depends dynamically on the video codec used;
// static browser availability and any competing features (e.g. simulcast) should
// have already been checked before this is set to true.
enableSVC: boolean = false;
eventController: EventController | null = null;
signalingOpenDurationMs: number | null = null;
iceGatheringDurationMs: number | null = null;
startAudioVideoTimestamp: number | null = null;
attendeePresenceDurationMs: number | null = null;
meetingStartDurationMs: number | null = null;
poorConnectionCount: number = 0;
maxVideoTileCount: number = 0;
startTimeMs: number | null = null;
// Indicates whether the session has completed initial connection (i.e. signal channel open and first subscribe
// and subscribe-ack)
isSessionConnected: boolean = false;
/*
* Reset state corresponding to state that is dependent on a individual connection
* and may not be valid for others, e.g. on a reconnection.
*/
resetConnectionSpecificState(): void {
// For auditing reasons, we will comment on the state that we do not touch here. Note that `DefaultAudioVideoController.actionConnect`
// also resets certain state, some to cached members:
// Reset to empty/null/new state: `browserBehavior`, `transceiverController`, `volumeIndicatorAdapter`, `enableSimulcast`
// `signalingOpenDurationMs`, `iceGatheringDurationMs`, `startAudioVideoTimestamp`, `attendeePresenceDurationMs`
// `meetingStartDurationMs`, `startTimeMs`, `lastKnownVideoAvailability`, `videoCaptureAndEncodeParameter`, `videosToReceive`
// `videosPaused`, `videoStreamIndex`, `statsCollector`, `connectionMonitor`
// Reset to existing/cached values: `logger`, `meetingSessionConfiguration`, `realtimeController`, `mediaStreamBroker`,
// `audioMixController`, `reconnectController, `audioProfile`, `eventController`
// `signalingClient` can be reused from a failed/disconnected state.
if (this.peer) {
this.peer.close();
}
this.peer = null;
this.previousSdpOffer = null;
this.sdpOfferInit = null;
// `audioVideoController` members should either be reusable, or moved to `AudioVideoControllerState` and
// cleaned up here.
// We don't want to mutate `videoTileController` as most video tiles will still be there on reconnect. We can remove tiles on the
// first index we receive if they no longer exist
// `mediaStreamBroker`, `activeAudioInput`, and `activeVideoInput` are cleaned up seperately in `DefaultAudioVideoController.cleanUpMediaStreamsAfterStop`
// but only on `stop` or non-reconnectable failures. They are also set to cached `DefaultAudioVideoController` members on restart.
if (this.transceiverController !== undefined) {
this.transceiverController.reset();
}
this.indexFrame = null;
this.iceCandidates = [];
this.iceCandidateHandler = null;
this.sdpAnswer = null;
this.turnCredentials = null;
this.videoDownlinkBandwidthPolicy.reset();
if (this.videoUplinkBandwidthPolicy.reset) {
this.videoUplinkBandwidthPolicy.reset();
}
this.lastVideosToReceive = null;
this.lastVideoSubscriptionConfiguration = new Map();
this.videoSubscriptions = null;
this.videoSubscriptionLimit = DEFAULT_VIDEO_SUBSCRIPTION_LIMIT;
this.previousSdpAnswerAsString = '';
this.serverSupportsCompression = false;
// `videoSendCodecPreferences` is set by builder and needs to stay consistent.
this.currentVideoSendCodec = undefined;
this.meetingSupportedVideoSendCodecPreferences = undefined;
this.videoDuplexMode = null;
// `poorConnectionCount`and `maxVideoTileCount` is intentionally not set to 0 across reconnections.
this.isSessionConnected = false;
}
}