Files
element-call/src/room/MuteStates.ts
T

153 lines
4.2 KiB
TypeScript
Raw Normal View History

2023-08-02 15:29:37 -04:00
/*
Copyright 2023, 2024 New Vector Ltd.
2023-08-02 15:29:37 -04:00
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
2023-08-02 15:29:37 -04:00
*/
import {
type Dispatch,
type SetStateAction,
useCallback,
useEffect,
useMemo,
} from "react";
import { type IWidgetApiRequest } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
2023-08-02 15:29:37 -04:00
import {
type MediaDevice,
useMediaDevices,
} from "../livekit/MediaDevicesContext";
2023-08-02 15:29:37 -04:00
import { useReactiveState } from "../useReactiveState";
import { ElementWidgetActions, widget } from "../widget";
import { Config } from "../config/Config";
import { useUrlParams } from "../UrlParams";
2023-08-02 15:29:37 -04:00
/**
2024-04-23 15:15:13 +02:00
* If there already are this many participants in the call, we automatically mute
* the user.
2023-08-02 15:29:37 -04:00
*/
2024-04-23 15:15:13 +02:00
export const MUTE_PARTICIPANT_COUNT = 8;
2023-08-02 15:29:37 -04:00
interface DeviceAvailable {
enabled: boolean;
setEnabled: Dispatch<SetStateAction<boolean>>;
}
interface DeviceUnavailable {
enabled: false;
setEnabled: null;
}
const deviceUnavailable: DeviceUnavailable = {
enabled: false,
setEnabled: null,
};
type MuteState = DeviceAvailable | DeviceUnavailable;
export interface MuteStates {
audio: MuteState;
video: MuteState;
}
function useMuteState(
device: MediaDevice,
2023-10-11 10:42:04 -04:00
enabledByDefault: () => boolean,
2023-08-02 15:29:37 -04:00
): MuteState {
2024-04-23 15:15:13 +02:00
const [enabled, setEnabled] = useReactiveState<boolean | undefined>(
// Determine the default value once devices are actually connected
2024-04-23 15:15:13 +02:00
(prev) =>
prev ?? (device.available.size > 0 ? enabledByDefault() : undefined),
2023-10-11 10:42:04 -04:00
[device],
2023-08-02 15:29:37 -04:00
);
return useMemo(
() =>
2024-11-21 14:43:30 -05:00
device.available.size === 0
2023-08-02 15:29:37 -04:00
? deviceUnavailable
2024-04-23 15:15:13 +02:00
: {
enabled: enabled ?? false,
setEnabled: setEnabled as Dispatch<SetStateAction<boolean>>,
},
2023-10-11 10:42:04 -04:00
[device, enabled, setEnabled],
2023-08-02 15:29:37 -04:00
);
}
2024-04-23 15:15:13 +02:00
export function useMuteStates(): MuteStates {
2023-08-02 15:29:37 -04:00
const devices = useMediaDevices();
const { skipLobby } = useUrlParams();
const audio = useMuteState(devices.audioInput, () => {
return Config.get().media_devices.enable_audio && !skipLobby;
});
const video = useMuteState(
devices.videoInput,
() => Config.get().media_devices.enable_video && !skipLobby,
);
2023-08-02 15:29:37 -04:00
useEffect(() => {
widget?.api.transport
.send(ElementWidgetActions.DeviceMute, {
audio_enabled: audio.enabled,
video_enabled: video.enabled,
})
.catch((e) =>
logger.warn("Could not send DeviceMute action to widget", e),
);
}, [audio, video]);
const onMuteStateChangeRequest = useCallback(
(ev: CustomEvent<IWidgetApiRequest>) => {
// First copy the current state into our new state.
const newState = {
audio_enabled: audio.enabled,
video_enabled: video.enabled,
};
// Update new state if there are any requested changes from the widget action
// in `ev.detail.data`.
if (
ev.detail.data.audio_enabled != null &&
typeof ev.detail.data.audio_enabled === "boolean"
) {
audio.setEnabled?.(ev.detail.data.audio_enabled);
newState.audio_enabled = ev.detail.data.audio_enabled;
}
if (
ev.detail.data.video_enabled != null &&
typeof ev.detail.data.video_enabled === "boolean"
) {
video.setEnabled?.(ev.detail.data.video_enabled);
newState.video_enabled = ev.detail.data.video_enabled;
}
// Always reply with the new (now "current") state.
// This allows to also use this action to just get the unaltered current state
// by using a fromWidget request with: `ev.detail.data = {}`
widget!.api.transport.reply(ev.detail, newState);
},
[audio, video],
);
useEffect(() => {
// We setup a event listener for the widget action ElementWidgetActions.DeviceMute.
if (widget) {
// only setup the listener in widget mode
widget.lazyActions.on(
ElementWidgetActions.DeviceMute,
onMuteStateChangeRequest,
);
return (): void => {
// return a call to `off` so that we always clean up our listener.
widget?.lazyActions.off(
ElementWidgetActions.DeviceMute,
onMuteStateChangeRequest,
);
};
}
}, [onMuteStateChangeRequest]);
2023-08-02 15:29:37 -04:00
return useMemo(() => ({ audio, video }), [audio, video]);
}