Files
element-call/src/room/VideoPreview.tsx
T

131 lines
3.7 KiB
TypeScript
Raw Normal View History

2022-05-04 17:09:48 +01:00
/*
Copyright 2022-2024 New Vector Ltd.
2022-05-04 17:09:48 +01:00
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
2022-05-04 17:09:48 +01:00
*/
2024-05-02 17:28:36 +02:00
import { useEffect, useMemo, useRef, FC, ReactNode, useCallback } from "react";
2022-08-02 00:46:16 +02:00
import useMeasure from "react-use-measure";
import { usePreviewTracks } from "@livekit/components-react";
2024-05-02 18:44:36 -04:00
import { LocalVideoTrack, Track } from "livekit-client";
2023-09-18 15:45:48 -04:00
import classNames from "classnames";
2023-09-25 18:04:34 +01:00
import { logger } from "matrix-js-sdk/src/logger";
2022-08-02 00:46:16 +02:00
2022-04-27 15:18:55 -07:00
import { Avatar } from "../Avatar";
import styles from "./VideoPreview.module.css";
2023-08-02 15:29:37 -04:00
import { useMediaDevices } from "../livekit/MediaDevicesContext";
import { MuteStates } from "./MuteStates";
2024-05-02 18:44:36 -04:00
import { useInitial } from "../useInitial";
2024-04-23 15:15:13 +02:00
import { EncryptionSystem } from "../e2ee/sharedKeyManagement";
2023-05-26 20:41:32 +02:00
export type MatrixInfo = {
2023-08-31 15:46:09 +02:00
userId: string;
2023-06-27 12:23:19 +01:00
displayName: string;
2023-05-26 20:41:32 +02:00
avatarUrl: string;
2023-07-04 18:46:27 +01:00
roomId: string;
2023-05-26 20:41:32 +02:00
roomName: string;
roomAlias: string | null;
roomAvatar: string | null;
2024-04-23 15:15:13 +02:00
e2eeSystem: EncryptionSystem;
2023-05-26 20:41:32 +02:00
};
2022-08-02 00:46:16 +02:00
interface Props {
2023-05-26 20:41:32 +02:00
matrixInfo: MatrixInfo;
2023-08-02 15:29:37 -04:00
muteStates: MuteStates;
2023-09-18 15:45:48 -04:00
children: ReactNode;
2022-08-02 00:46:16 +02:00
}
2022-10-10 09:19:10 -04:00
2023-09-18 15:45:48 -04:00
export const VideoPreview: FC<Props> = ({
matrixInfo,
muteStates,
children,
}) => {
2024-07-09 11:54:13 -04:00
const [previewRef, previewBounds] = useMeasure();
2022-04-27 15:18:55 -07:00
2023-08-02 15:29:37 -04:00
const devices = useMediaDevices();
// Capture the audio options as they were when we first mounted, because
// we're not doing anything with the audio anyway so we don't need to
// re-open the devices when they change (see below).
2024-05-02 18:44:36 -04:00
const initialAudioOptions = useInitial(
() =>
muteStates.audio.enabled && { deviceId: devices.audioInput.selectedId },
);
2024-05-02 17:28:36 +02:00
const localTrackOptions = useMemo(
() => ({
2023-08-02 15:29:37 -04:00
// The only reason we request audio here is to get the audio permission
// request over with at the same time. But changing the audio settings
// shouldn't cause this hook to recreate the track, which is why we
// reference the initial values here.
// We also pass in a clone because livekit mutates the object passed in,
// which would cause the devices to be re-opened on the next render.
2024-05-02 18:44:36 -04:00
audio: Object.assign({}, initialAudioOptions),
2023-08-02 15:29:37 -04:00
video: muteStates.video.enabled && {
deviceId: devices.videoInput.selectedId,
},
2024-05-02 17:28:36 +02:00
}),
2024-05-02 18:44:36 -04:00
[
initialAudioOptions,
devices.videoInput.selectedId,
muteStates.video.enabled,
],
2024-05-02 17:28:36 +02:00
);
const onError = useCallback(
(error: Error) => {
logger.error("Error while creating preview Tracks:", error);
muteStates.audio.setEnabled?.(false);
muteStates.video.setEnabled?.(false);
},
2024-05-02 17:28:36 +02:00
[muteStates.audio, muteStates.video],
);
2024-05-02 17:28:36 +02:00
const tracks = usePreviewTracks(localTrackOptions, onError);
2023-08-02 15:29:37 -04:00
const videoTrack = useMemo(
() =>
2023-08-02 15:29:37 -04:00
tracks?.find((t) => t.kind === Track.Kind.Video) as
| LocalVideoTrack
| undefined,
2023-10-11 10:42:04 -04:00
[tracks],
2023-06-16 18:07:13 +02:00
);
2023-08-02 15:29:37 -04:00
const videoEl = useRef<HTMLVideoElement | null>(null);
2023-06-16 18:07:13 +02:00
useEffect(() => {
// Effect to connect the videoTrack with the video element.
if (videoEl.current) {
videoTrack?.attach(videoEl.current);
2023-05-26 20:41:32 +02:00
}
2024-06-04 11:20:25 -04:00
return (): void => {
videoTrack?.detach();
2023-05-26 20:41:32 +02:00
};
}, [videoTrack]);
2023-05-26 20:41:32 +02:00
return (
<div className={classNames(styles.preview)} ref={previewRef}>
<video
ref={videoEl}
muted
playsInline
// There's no reason for this to be focusable
tabIndex={-1}
disablePictureInPicture
/>
2023-09-18 15:45:48 -04:00
{!muteStates.video.enabled && (
<div className={styles.avatarContainer}>
<Avatar
id={matrixInfo.userId}
name={matrixInfo.displayName}
size={Math.min(previewBounds.width, previewBounds.height) / 2}
src={matrixInfo.avatarUrl}
/>
2023-05-26 20:41:32 +02:00
</div>
2023-05-05 11:44:35 +02:00
)}
2023-09-18 15:45:48 -04:00
<div className={styles.buttonBar}>{children}</div>
2022-04-27 15:18:55 -07:00
</div>
);
2023-08-02 15:29:37 -04:00
};