Merge remote-tracking branch 'origin/livekit' into dbkr/smooth_focus_switch

This commit is contained in:
David Baker
2023-09-12 16:35:51 +01:00
59 changed files with 1142 additions and 911 deletions

View File

@@ -23,8 +23,7 @@ import {
useTracks,
} from "@livekit/components-react";
import { usePreventScroll } from "@react-aria/overlays";
import classNames from "classnames";
import { Room, Track, ConnectionState } from "livekit-client";
import { ConnectionState, Room, Track } from "livekit-client";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { Room as MatrixRoom } from "matrix-js-sdk/src/models/room";
@@ -32,10 +31,11 @@ import { Ref, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import useMeasure from "react-use-measure";
import { OverlayTriggerState } from "@react-stately/overlays";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { logger } from "matrix-js-sdk/src/logger";
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { ReactComponent as LogoMark } from "../icons/LogoMark.svg";
import { ReactComponent as LogoType } from "../icons/LogoType.svg";
import type { IWidgetApiRequest } from "matrix-widget-api";
import {
HangupButton,
@@ -43,7 +43,6 @@ import {
VideoButton,
ScreenshareButton,
SettingsButton,
InviteButton,
} from "../button";
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
import {
@@ -58,29 +57,27 @@ import { useUrlParams } from "../UrlParams";
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
import { ElementWidgetActions, widget } from "../widget";
import { GridLayoutMenu } from "./GridLayoutMenu";
import styles from "./InCallView.module.css";
import { useJoinRule } from "./useJoinRule";
import { ItemData, TileContent, VideoTile } from "../video-grid/VideoTile";
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { SettingsModal } from "../settings/SettingsModal";
import { InviteModal } from "./InviteModal";
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
import { RageshakeRequestModal } from "./RageshakeRequestModal";
import { E2EEConfig, useLiveKit } from "../livekit/useLiveKit";
import { useFullscreen } from "./useFullscreen";
import { useLayoutStates } from "../video-grid/Layout";
import { E2EELock } from "../E2EELock";
import { useWakeLock } from "../useWakeLock";
import { useMergedRefs } from "../useMergedRefs";
import { MuteStates } from "./MuteStates";
import { useIsRoomE2EE } from "../e2ee/sharedKeyManagement";
import { useOpenIDSFU } from "../livekit/openIDSFU";
import { MatrixInfo } from "./VideoPreview";
import { ShareButton } from "../button/ShareButton";
import { LayoutToggle } from "./LayoutToggle";
import {
ECAddonConnectionState,
ECConnectionState,
} from "../livekit/useECConnectionState";
import { useOpenIDSFU } from "../livekit/openIDSFU";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
@@ -121,24 +118,30 @@ export function ActiveCall(props: ActiveCallProps) {
export interface InCallViewProps {
client: MatrixClient;
matrixInfo: MatrixInfo;
rtcSession: MatrixRTCSession;
livekitRoom: Room;
muteStates: MuteStates;
participatingMembers: RoomMember[];
onLeave: (error?: Error) => void;
hideHeader: boolean;
otelGroupCallMembership?: OTelGroupCallMembership;
connState: ECConnectionState;
onShareClick: (() => void) | null;
}
export function InCallView({
client,
matrixInfo,
rtcSession,
livekitRoom,
muteStates,
participatingMembers,
onLeave,
hideHeader,
otelGroupCallMembership,
connState,
onShareClick,
}: InCallViewProps) {
const { t } = useTranslation();
usePreventScroll();
@@ -152,8 +155,6 @@ export function InCallView({
}
}, [connState, onLeave]);
const isRoomE2EE = useIsRoomE2EE(rtcSession.room.roomId);
const containerRef1 = useRef<HTMLDivElement | null>(null);
const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver });
const boundsValid = bounds.height > 0;
@@ -188,15 +189,13 @@ export function InCallView({
[muteStates]
);
const joinRule = useJoinRule(rtcSession.room);
// This function incorrectly assumes that there is a camera and microphone, which is not always the case.
// TODO: Make sure that this module is resilient when it comes to camera/microphone availability!
useCallViewKeyboardShortcuts(
containerRef1,
toggleMicrophone,
toggleCamera,
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
(muted) => muteStates.audio.setEnabled?.(!muted)
);
const onLeavePress = useCallback(() => {
@@ -205,7 +204,7 @@ export function InCallView({
useEffect(() => {
widget?.api.transport.send(
layout === "freedom"
layout === "grid"
? ElementWidgetActions.TileLayout
: ElementWidgetActions.SpotlightLayout,
{}
@@ -215,7 +214,7 @@ export function InCallView({
useEffect(() => {
if (widget) {
const onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
setLayout("freedom");
setLayout("grid");
await widget!.api.transport.reply(ev.detail, {});
};
const onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
@@ -239,7 +238,8 @@ export function InCallView({
}
}, [setLayout]);
const reducedControls = boundsValid && bounds.width <= 400;
const mobile = boundsValid && bounds.width <= 660;
const reducedControls = boundsValid && bounds.width <= 340;
const noControls = reducedControls && bounds.height <= 400;
const items = useParticipantTiles(livekitRoom, rtcSession.room, connState);
@@ -259,7 +259,7 @@ export function InCallView({
);
const Grid =
items.length > 12 && layout === "freedom" ? NewVideoGrid : VideoGrid;
items.length > 12 && layout === "grid" ? NewVideoGrid : VideoGrid;
const prefersReducedMotion = usePrefersReducedMotion();
@@ -333,25 +333,6 @@ export function InCallView({
settingsModalState.open();
}, [settingsModalState]);
const {
modalState: inviteModalState,
modalProps: inviteModalProps,
}: {
modalState: OverlayTriggerState;
modalProps: {
isOpen: boolean;
onClose: () => void;
};
} = useModalTriggerState();
const openInvite = useCallback(() => {
inviteModalState.open();
}, [inviteModalState]);
const containerClasses = classNames(styles.inRoom, {
[styles.maximised]: undefined,
});
const toggleScreensharing = useCallback(async () => {
exitFullscreen();
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled, {
@@ -370,19 +351,19 @@ export function InCallView({
const buttons: JSX.Element[] = [];
buttons.push(
<MicButton
key="1"
muted={!muteStates.audio.enabled}
onPress={toggleMicrophone}
disabled={muteStates.audio.setEnabled === null}
data-testid="incall_mute"
/>,
<VideoButton
key="2"
muted={!muteStates.video.enabled}
onPress={toggleCamera}
disabled={muteStates.video.setEnabled === null}
data-testid="incall_videomute"
/>,
<MicButton
key="1"
muted={!muteStates.audio.enabled}
onPress={toggleMicrophone}
disabled={muteStates.audio.setEnabled === null}
data-testid="incall_mute"
/>
);
@@ -403,21 +384,43 @@ export function InCallView({
buttons.push(
<HangupButton key="6" onPress={onLeavePress} data-testid="incall_leave" />
);
footer = <div className={styles.footer}>{buttons}</div>;
footer = (
<div className={styles.footer}>
{!mobile && !hideHeader && (
<div className={styles.logo}>
<LogoMark width={24} height={24} />
<LogoType width={80} height={11} />
</div>
)}
<div className={styles.buttons}>{buttons}</div>
{!mobile && !hideHeader && (
<LayoutToggle
className={styles.layout}
layout={layout}
setLayout={setLayout}
/>
)}
</div>
);
}
return (
<div className={containerClasses} ref={containerRef}>
<div className={styles.inRoom} ref={containerRef}>
{!hideHeader && maximisedParticipant === null && (
<Header>
<LeftNav>
<RoomHeaderInfo roomName={rtcSession.room.name} />
{!isRoomE2EE && <E2EELock />}
<RoomHeaderInfo
id={matrixInfo.roomId}
name={matrixInfo.roomName}
avatarUrl={matrixInfo.roomAvatar}
encrypted={matrixInfo.roomEncrypted}
participants={participatingMembers}
client={client}
/>
</LeftNav>
<RightNav>
<GridLayoutMenu layout={layout} setLayout={setLayout} />
{joinRule === JoinRule.Public && (
<InviteButton variant="icon" onClick={openInvite} />
{!reducedControls && onShareClick !== null && (
<ShareButton onClick={onShareClick} />
)}
</RightNav>
</Header>
@@ -448,9 +451,6 @@ export function InCallView({
{...settingsModalProps}
/>
)}
{inviteModalState.isOpen && (
<InviteModal roomId={rtcSession.room.roomId} {...inviteModalProps} />
)}
</div>
);
}
@@ -462,7 +462,8 @@ function findMatrixMember(
if (!id) return undefined;
const parts = id.split(":");
if (parts.length < 2) {
// must be at least 3 parts because we know the first part is a userId which must necessarily contain a colon
if (parts.length < 3) {
logger.warn(
"Livekit participants ID doesn't look like a userId:deviceId combination"
);