Event handling

You must handle these audio, video, share, and other events for the best user experience and to prevent edge cases.

For example, add some sort of notification when a user joins or leaves so that other users are aware. This is especially important if you are rendering less videos or "users" in your video layout than users who are present in the session.

Show a notification when a user joins or leaves

Subscribe to events using the on method and unsubscribe using the off method.


Session events

Use session events to be notified of connection and user events.

Connection issues and host ended session

Use the connection-change event listener to detect when the host ended a session, the SDK kicked a user from a session, or the session had connection issues.

This crucial event sends a notification of the session's status. For example, if the session reconnects due to network issues or if the host ends the session and it closes. This helps inform both the user with connection issues and the other users in the session that the user is experiencing problems and attempting to reconnect due to poor signal strength.

The payload structure for this event is as follows:

{
  /**
   * Connection state.
   */
  state: ConnectionState;
  /**
   * Reason for the change.
   */
  reason?: ReconnectReason | ClosedReason;
  /**
   * If the reason is `JoinSubsession` or `MoveToSubsession`, this is the subsession name.
   */
  subsessionName?: string;
  /**
   * Error code if the state is 'Fail'
   */
  errorCode?:number;
}

ConnectionState can have the following values:

enum ConnectionState {
    /**
     * Connected.
     */
    Connected = "Connected",
    /**
     * Reconnecting (usually occurs in failover).
     */
    Reconnecting = "Reconnecting",
    /**
     * Closed.
     */
    Closed = "Closed",
    /**
     * Failed.
     */
    Fail = "Fail",
}

We recommend listening for the connection-change event and handling each connection state as follows:

client.on("connection-change", (payload) => {
    if (payload.state === ConnectionState.Closed) {
        /**
         * Session ended by the host or the SDK kicked the user from the session
         * (use `payload.reason` to see why the SDK kicked the user).
         **/
    } else if (payload.state === ConnectionState.Reconnecting) {
        /**
         * The client-side connection to the server was lost (e.g., when driving through a tunnel) and will attempt to reconnect for a few minutes.
         * Alternatively, if the user joins or leaves a subsession, use `payload.subsessionName` to retrieve the subsession name.
         */
    } else if (payload.state === ConnectionState.Connected) {
        /**
         * SDK connected the session successfully.
         */
    } else if (payload.state === ConnectionState.Fail) {
        /**
         * SDK join session failed, use `payload.errorCode` and `payload.reason` to find out why.
         */
    }
});

user-added | user-removed | user-updated

The user-added and user-removed events allow you to track when users join or leave the session. You can use them as follows:

client.on("user-added", (payload) => {
    payload.forEach((item) => {
        console.log(`${item.userId} joined the session.`);
    });
});
client.on("user-removed", (payload) => {
    payload.forEach((item) => {
        console.log(`${item.userId} left the session.`);
    });
});
/**
 * If you don't need to track individual users and just want the latest user list,
 * call `client.getAllUser()` within these callbacks.
 */

The user-updated event is also useful for tracking changes to users' video or microphone states. It can be especially helpful when a user is reconnecting to a session and others in the session can't hear their audio or see their video.

isInFailover

The isInFailover object is part of the Participant interface. Track changes using the isInFailover property:

client.on("user-updated", (payload) => {
    payload.forEach((item) => {
        console.log(`${item.userId} properties were updated.`);
    });
});

If isInFailover is true, the user is not connected and will be removed from the session in two (2) minutes. We recommending showing something in your user interface so that others will know the user is no longer connected.


Media events

Use media events to be notified of device, media, and network changes.

device-change

This event triggers when a device (camera, microphone, or speaker) is plugged in or unplugged. This allows you to update the device selection UI accordingly:

client.on("device-change", () => {
    const cameras = stream.getCameraList();
    const microphones = stream.getMicList();
    const audioSpeakers = stream.getSpeakerList();
    const activeCamera = stream.getActiveCamera();
    const activeMic = stream.getActiveMicrophone();
    const activeSpeaker = stream.getActiveSpeaker();
    /**
     * If your UI provides a device selection list, update it with the new data.
     */
});

device-permission-change

This event notifies you when the browser changes the device permission status (for example, microphone or camera access). Some browsers persist the permission decision, while others, like Safari, require users to grant permission every time. This event can help you prompt users when permissions are denied or blocked.

client.on("device-permission-change", (payload) => {
    const { name, state } = payload;
    /**
     * name contains 'microphone' or 'camera'
     * state contains 'denied', 'granted' or 'prompt'
     * */
    if (state === "denied") {
        /**
         * Show a prompt guiding users to grant permission.
         */
        console.warn(`${name} permission is denied, please grant it`);
    }
});

active-media-failed

Even though the Video SDK handles many media stream exceptions, some cases—such as denied permissions, device issues, or memory constraints—require user intervention. The active-media-failed event provides detailed error information (including the exception type, error code, and suggested actions) to help developers diagnose and resolve these issues.

See the following error codes and suggested actions.

Audio exceptions

ExceptionError codeSuggested action
ActiveMediaFailedCode.AudioConnectionFailed101Refresh the browser to reconnect.
ActiveMediaFailedCode.AudioStreamEnded102Refresh the browser; the audio stream was interrupted (possibly due to device issues or another app).
ActiveMediaFailedCode.MicrophonePermissionReset103Grant the necessary microphone permissions.
ActiveMediaFailedCode.AudioStreamFailed104Close all browsers and rejoin the session.
ActiveMediaFailedCode.MicrophoneMuted105Unmute the mic via system or browser settings.
ActiveMediaFailedCode.AudioStreamMuted106The microphone was temporarily occupied and then released, typically due to scenarios like answering a phone call or another application using the microphone. Prompt the user to click on the page so that the SDK can restore audio.
ActiveMediaFailedCode.AudioPlaybackInterrupted107The audio playback of other users in the session was interrupted by the system. Prompt the user to click on the page to resume audio playback.

Video exceptions

ExceptionError codeSuggested action
ActiveMediaFailedCode.VideoConnectionFailed201Refresh the browser.
ActiveMediaFailedCode.VideoStreamEnded202Refresh the browser; the camera stream may have been blocked.
ActiveMediaFailedCode.CameraPermissionReset203Grant camera permission.
ActiveMediaFailedCode.WebGlContextInvalid204(WASM video only) Issue with the canvas required for video rendering. This could be due to using an incorrect canvas element or the browser not supporting WebGL rendering. Verify the canvas element or browser compatibility for WebGL.
ActiveMediaFailedCode.WasmOutOfMemory205Close all browsers and rejoin the session.
ActiveMediaFailedCode.VideoStreamFailed206Close all browsers and rejoin the session.
ActiveMediaFailedCode.VideoStreamMuted207The camera was temporarily occupied and then released. Prompt the user to click on the page so that the SDK can restore video.

Screen share exception

ExceptionError codeSuggested action
ActiveMediaFailedCode.SharingStreamFailed301Close all browsers and rejoin the session.

Example

client.on("active-media-failed", (payload) => {
    const { code, message } = payload;
    const {
        MicrophoneMuted,
        AudioStreamMuted,
        AudioPlaybackInterrupted,
        CameraPermissionReset,
        MicrophonePermissionReset,
    } = ActiveMediaFailedCode;
    if (
        [
            CameraPermissionReset,
            MicrophonePermissionReset,
            MicrophoneMuted,
        ].includes(code)
    ) {
        /**
         * Guide the user to grant the camera/microphone permission.
         * For example, display a modal directing them to the settings page.
         */
    } else if (
        [AudioStreamMuted, AudioPlaybackInterrupted, VideoStreamMuted].includes(
            code,
        )
    ) {
        /**
         * Prompt the user to click anywhere on the page to resume audio playback.
         * For example, use a toast notification.
         */
    } else {
        /**
         * Display a modal notifying the user of the stream failure,
         * using `message` as a description, along with a button to refresh the page.
         */
    }
});

current-audio-change

While the user-updated event captures general changes in a user's audio status (such as joining, leaving, or muting), the current-audio-change event provides more detailed context. It includes the specific reason for leaving audio or being muted.

Use current-audio-change to determine when and why the user left audio. For example, to take a phone call, described by LeaveAudioSource.

client.on("current-audio-change", (payload) => {
    const { action, source } = payload;
    if (action === AudioChangeAction.Leave) {
        if (
            source === LeaveAudioSource.EndedBySystem ||
            source === LeaveAudioSource.MicrophoneError
        ) {
            /**
             * Display a message to the user that the audio has ended due to a system error or microphone error.
             */
        }
    } else if (action === AudioChangeAction.Muted) {
        if (
            source === MutedSource.PassiveByMuteOne ||
            source === MutedSource.PassiveByMuteAll
        ) {
            /**
             * Display a message to the user that the audio has been muted by the host.
             */
        }
    }
});

auto-play-audio-failed

Due to browser privacy policies, audio auto-play requires user interaction. If stream.startAudio is called immediately upon joining a session, it might fail. Use the auto-play-audio-failed event to get notified of this issue so you can prompt users to interact with the page to restore audio.

client.on("auto-play-audio-failed", () => {
    /**
     * Prompt the user to click anywhere on the page to resume audio playback.
     * For example, display a toast notification with guidance.
     */
});

video-aspect-ratio-change

The aspect ratio of the captured video may vary by device. The video is rendered in a 16:9 ratio by default for desktop and landscape mobile. For portrait mode on mobile devices, it renders at 9:16. Discrepancies can occur if the sender's ratio differs. The video-aspect-ratio-change event allows you to dynamically adjust the rendering aspect ratio to match the sender's.

For more information on handling aspect ratios and responsive design, see Video: Best practices.

client.on("video-aspect-ratio-change", (payload) => {
    /**
     * This event is only triggered when the render aspect ratio differs from the sending aspect ratio,
     * so it is not expected to fire frequently.
     */
    const { userId, aspectRatio } = payload;
    /**
     * Locate the corresponding video-player element.
     * This example uses a query selector; in production, you might maintain a map of user IDs to video-player elements.
     */
    const videoPlayerElement = document.querySelector(
        `video-player[node-id="${userId}"]`,
    );
    if (videoPlayerElement) {
        videoPlayerElement.style.aspectRatio = aspectRatio;
    }
});

network-quality-change

Unstable network conditions are a key factor affecting the audio/video experience. Displaying both the local and remote users' network status can help all users understand and anticipate potential quality issues. This event provides real-time updates on network quality changes.

client.on("network-quality-change", (payload) => {
    /**
     * `type`: 'uplink' or 'downlink'
     * `level`: numeric value where:
     *      0,1 = poor quality,
     *      2 = normal,
     *      3,4,5 = good quality.
     */
    const { userId, type, level } = payload;
    /**
     * We recommend using a cellular signal-style icon to represent the network quality visually.
     */
    console.log(`User ${userId} ${type} network quality changed to ${level} `);
});

See network quality for more examples.

speaking-while-muted

This feature helps prevent awkward communication gaps and improves meeting flow by ensuring users know when they need to unmute themselves.

Use the speaking-while-muted event listener to detect when a user attempts to speak while their audio is muted. This event triggers when the SDK detects audio input from the user's microphone while their audio stream is muted, preventing situations where users are unaware they cannot be heard by other users.

We recommend implementing this event listener to provide real-time feedback, improving user experience by immediately alerting users when they speak while muted.

client.on("speaking-while-muted", () => {
    // Display a notification to inform the user they are speaking while muted.
    // Show a toast message with an unmute button for a quick solution.
    showNotification("You are speaking while muted. Click to unmute.", {
        action: () => client.unmuteAudio(),
    });
});