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

268 lines
8.4 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-11-20 17:22:24 +01:00
import { FC, useCallback, useEffect, useMemo, useState } from "react";
2023-09-18 15:45:48 -04:00
import { useTranslation } from "react-i18next";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { Button } from "@vector-im/compound-web";
2023-09-18 15:45:48 -04:00
import classNames from "classnames";
import { useHistory } from "react-router-dom";
2024-10-28 17:29:26 -04:00
import { logger } from "matrix-js-sdk/src/logger";
import { usePreviewTracks } from "@livekit/components-react";
import { LocalVideoTrack, Track } from "livekit-client";
import { useObservable } from "observable-hooks";
import { map } from "rxjs";
2024-11-20 17:22:24 +01:00
import { BackgroundBlur } from "@livekit/track-processors";
2022-08-02 00:46:16 +02:00
2023-09-18 15:45:48 -04:00
import inCallStyles from "./InCallView.module.css";
2024-04-23 15:15:13 +02:00
import styles from "./LobbyView.module.css";
2022-01-05 13:09:12 -08:00
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
import { useLocationNavigation } from "../useLocationNavigation";
2023-06-16 18:07:13 +02:00
import { MatrixInfo, VideoPreview } from "./VideoPreview";
2023-08-02 15:29:37 -04:00
import { MuteStates } from "./MuteStates";
2023-09-27 15:17:04 -04:00
import { InviteButton } from "../button/InviteButton";
2023-09-18 15:45:48 -04:00
import {
EndCallButton,
2023-09-18 15:45:48 -04:00
MicButton,
SettingsButton,
2024-10-28 17:29:26 -04:00
SwitchCameraButton,
2023-09-18 15:45:48 -04:00
VideoButton,
} from "../button/Button";
2023-12-01 17:43:09 -05:00
import { SettingsModal, defaultSettingsTab } from "../settings/SettingsModal";
2024-11-20 17:22:24 +01:00
import { backgroundBlur as backgroundBlurSettings } from "../settings/settings";
2023-09-18 15:45:48 -04:00
import { useMediaQuery } from "../useMediaQuery";
2024-04-23 15:15:13 +02:00
import { E2eeType } from "../e2ee/e2eeType";
import { Link } from "../button/Link";
2024-10-28 17:29:26 -04:00
import { useMediaDevices } from "../livekit/MediaDevicesContext";
import { useInitial } from "../useInitial";
2024-11-20 17:22:24 +01:00
import { useSwitchCamera as useShowSwitchCamera } from "./useSwitchCamera";
import { useSetting } from "../settings/settings";
2022-01-05 13:09:12 -08:00
2022-08-02 00:46:16 +02:00
interface Props {
client: MatrixClient;
2023-05-26 20:41:32 +02:00
matrixInfo: MatrixInfo;
2023-08-02 15:29:37 -04:00
muteStates: MuteStates;
2023-08-08 13:46:51 +02:00
onEnter: () => void;
2024-04-23 15:15:13 +02:00
enterLabel?: JSX.Element | string;
2023-09-18 20:47:47 -04:00
confineToRoom: boolean;
hideHeader: boolean;
2024-04-23 15:15:13 +02:00
participantCount: number | null;
onShareClick: (() => void) | null;
2024-04-23 15:15:13 +02:00
waitingForInvite?: boolean;
2022-08-02 00:46:16 +02:00
}
2023-08-02 15:29:37 -04:00
export const LobbyView: FC<Props> = ({
client,
2023-08-02 15:29:37 -04:00
matrixInfo,
muteStates,
onEnter,
2024-04-23 15:15:13 +02:00
enterLabel,
2023-09-18 20:47:47 -04:00
confineToRoom,
2023-08-02 15:29:37 -04:00
hideHeader,
participantCount,
onShareClick,
2024-04-23 15:15:13 +02:00
waitingForInvite,
2023-08-02 15:29:37 -04:00
}) => {
2023-05-26 20:41:32 +02:00
const { t } = useTranslation();
useLocationNavigation();
2022-02-04 12:38:40 -08:00
2023-09-18 15:45:48 -04:00
const onAudioPress = useCallback(
() => muteStates.audio.setEnabled?.((e) => !e),
2023-10-11 10:42:04 -04:00
[muteStates],
2023-09-18 15:45:48 -04:00
);
const onVideoPress = useCallback(
() => muteStates.video.setEnabled?.((e) => !e),
2023-10-11 10:42:04 -04:00
[muteStates],
2023-09-18 15:45:48 -04:00
);
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
2023-12-01 17:43:09 -05:00
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
2023-09-18 15:45:48 -04:00
const openSettings = useCallback(
() => setSettingsModalOpen(true),
2023-10-11 10:42:04 -04:00
[setSettingsModalOpen],
2023-09-18 15:45:48 -04:00
);
const closeSettings = useCallback(
() => setSettingsModalOpen(false),
2023-10-11 10:42:04 -04:00
[setSettingsModalOpen],
2023-09-18 15:45:48 -04:00
);
const history = useHistory();
const onLeaveClick = useCallback(() => history.push("/"), [history]);
const recentsButtonInFooter = useMediaQuery("(max-height: 500px)");
2023-09-18 20:47:47 -04:00
const recentsButton = !confineToRoom && (
<Link className={styles.recents} to="/">
2023-11-20 13:00:43 +00:00
{t("lobby.leave_button")}
2023-09-18 15:45:48 -04:00
</Link>
);
2022-02-04 12:38:40 -08:00
2024-10-28 17:29:26 -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).
const initialAudioOptions = useInitial(
() =>
muteStates.audio.enabled && { deviceId: devices.audioInput.selectedId },
);
2024-11-20 17:22:24 +01:00
// eslint-disable-next-line new-cap
2024-11-20 17:31:40 +01:00
const blur = useMemo(() => {
let b = undefined;
try {
// eslint-disable-next-line new-cap
2024-11-27 21:00:09 +01:00
b = BackgroundBlur(15, { delegate: "GPU" });
2024-11-20 17:31:40 +01:00
} catch (e) {
2024-12-02 11:55:22 +01:00
logger.error(
"disable background blur because its not supported by the platform.",
e,
);
2024-11-20 17:31:40 +01:00
}
return b;
}, []);
2024-11-20 17:22:24 +01:00
2024-10-28 17:29:26 -04:00
const localTrackOptions = useMemo(
() => ({
// 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.
audio: Object.assign({}, initialAudioOptions),
video: muteStates.video.enabled && {
deviceId: devices.videoInput.selectedId,
2024-11-20 17:22:24 +01:00
// It should be possible to set a processor here:
// processor: blur,
// This causes a crash currently hence we do the effect below...
2024-10-28 17:29:26 -04:00
},
}),
[
initialAudioOptions,
muteStates.video.enabled,
2024-11-20 17:22:24 +01:00
devices.videoInput.selectedId,
2024-10-28 17:29:26 -04:00
],
);
const onError = useCallback(
(error: Error) => {
logger.error("Error while creating preview Tracks:", error);
muteStates.audio.setEnabled?.(false);
muteStates.video.setEnabled?.(false);
},
[muteStates.audio, muteStates.video],
);
const tracks = usePreviewTracks(localTrackOptions, onError);
const videoTrack = useMemo(
() =>
(tracks?.find((t) => t.kind === Track.Kind.Video) ??
null) as LocalVideoTrack | null,
[tracks],
);
2024-11-20 17:22:24 +01:00
const [showBackgroundBlur] = useSetting(backgroundBlurSettings);
useEffect(() => {
2024-11-20 17:31:40 +01:00
// Fon't even try if we cannot blur on this platform
if (!blur) return;
2024-11-20 17:22:24 +01:00
const updateBlur = async (showBlur: boolean): Promise<void> => {
if (showBlur && !videoTrack?.getProcessor()) {
// eslint-disable-next-line new-cap
await videoTrack?.setProcessor(blur);
} else {
await videoTrack?.stopProcessor();
}
};
if (videoTrack) void updateBlur(showBackgroundBlur);
}, [videoTrack, showBackgroundBlur, blur]);
const showSwitchCamera = useShowSwitchCamera(
2024-10-28 17:29:26 -04:00
useObservable(
(inputs) => inputs.pipe(map(([video]) => video)),
[videoTrack],
),
);
2023-09-18 15:45:48 -04:00
// TODO: Unify this component with InCallView, so we can get slick joining
// animations and don't have to feel bad about reusing its CSS
2022-01-05 13:09:12 -08:00
return (
2023-09-18 15:45:48 -04:00
<>
<div className={classNames(styles.room, inCallStyles.inRoom)}>
{!hideHeader && (
<Header>
<LeftNav>
<RoomHeaderInfo
id={matrixInfo.roomId}
name={matrixInfo.roomName}
avatarUrl={matrixInfo.roomAvatar}
2024-04-23 15:15:13 +02:00
encrypted={matrixInfo.e2eeSystem.kind !== E2eeType.NONE}
participantCount={participantCount}
2023-09-18 15:45:48 -04:00
/>
</LeftNav>
<RightNav>
2023-09-27 15:17:04 -04:00
{onShareClick !== null && <InviteButton onClick={onShareClick} />}
2023-09-18 15:45:48 -04:00
</RightNav>
</Header>
)}
<div className={styles.content}>
2024-10-28 17:29:26 -04:00
<VideoPreview
matrixInfo={matrixInfo}
muteStates={muteStates}
videoTrack={videoTrack}
>
2022-10-24 10:17:12 -04:00
<Button
2024-04-23 15:15:13 +02:00
className={classNames(styles.join, {
[styles.wait]: waitingForInvite,
})}
size={waitingForInvite ? "sm" : "lg"}
onClick={() => {
if (!waitingForInvite) onEnter();
}}
data-testid="lobby_joinCall"
2022-10-24 10:17:12 -04:00
>
2024-04-23 15:15:13 +02:00
{enterLabel ?? t("lobby.join_button")}
2022-10-24 10:17:12 -04:00
</Button>
2023-09-18 15:45:48 -04:00
</VideoPreview>
{!recentsButtonInFooter && recentsButton}
</div>
<div className={inCallStyles.footer}>
{recentsButtonInFooter && recentsButton}
<div className={inCallStyles.buttons}>
<MicButton
muted={!muteStates.audio.enabled}
onClick={onAudioPress}
2023-09-18 15:45:48 -04:00
disabled={muteStates.audio.setEnabled === null}
/>
2023-09-25 10:39:18 -04:00
<VideoButton
muted={!muteStates.video.enabled}
onClick={onVideoPress}
2023-09-25 10:39:18 -04:00
disabled={muteStates.video.setEnabled === null}
/>
2024-11-20 17:22:24 +01:00
{showSwitchCamera && (
<SwitchCameraButton onClick={showSwitchCamera} />
)}
<SettingsButton onClick={openSettings} />
{!confineToRoom && <EndCallButton onClick={onLeaveClick} />}
2023-09-18 15:45:48 -04:00
</div>
2022-01-05 13:09:12 -08:00
</div>
</div>
2023-09-18 15:45:48 -04:00
{client && (
<SettingsModal
client={client}
open={settingsModalOpen}
onDismiss={closeSettings}
2023-12-01 17:43:09 -05:00
tab={settingsTab}
onTabChange={setSettingsTab}
2023-09-18 15:45:48 -04:00
/>
)}
</>
2022-01-05 13:09:12 -08:00
);
2023-08-02 15:29:37 -04:00
};