Merge branch 'livekit-experiment' into livekit-load-test

This commit is contained in:
Daniel Abramov 2023-06-13 17:23:42 +02:00
commit 6436e66adb
126 changed files with 6789 additions and 1444 deletions

View file

@ -1,5 +1,5 @@
/*
Copyright 2022 New Vector Ltd
Copyright 2022 - 2023 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -25,15 +25,24 @@ import {
import { usePreventScroll } from "@react-aria/overlays";
import classNames from "classnames";
import { Room, Track } from "livekit-client";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
import React, { useCallback, useEffect, useMemo, useRef } 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 type { IWidgetApiRequest } from "matrix-widget-api";
import {
HangupButton,
MicButton,
VideoButton,
ScreenshareButton,
SettingsButton,
InviteButton,
} from "../button";
import {
Header,
LeftNav,
@ -41,38 +50,34 @@ import {
RoomHeaderInfo,
VersionMismatchWarning,
} from "../Header";
import { useModalTriggerState } from "../Modal";
import { PosthogAnalytics } from "../PosthogAnalytics";
import { useUrlParams } from "../UrlParams";
import { UserMenuContainer } from "../UserMenuContainer";
import {
HangupButton,
MicButton,
ScreenshareButton,
VideoButton,
} from "../button";
import { MediaDevicesState } from "../settings/mediaDevices";
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
import { useShowInspector } from "../settings/useSetting";
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
import {
TileDescriptor,
VideoGrid,
useVideoGridLayout,
TileDescriptor,
} from "../video-grid/VideoGrid";
import { useNewGrid, useShowInspector } from "../settings/useSetting";
import { useModalTriggerState } from "../Modal";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { useUrlParams } from "../UrlParams";
import { MediaDevicesState } from "../settings/mediaDevices";
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
import { ItemData, VideoTileContainer } from "../video-grid/VideoTileContainer";
import { ElementWidgetActions, widget } from "../widget";
import { GridLayoutMenu } from "./GridLayoutMenu";
import { GroupCallInspector } from "./GroupCallInspector";
import styles from "./InCallView.module.css";
import { OverflowMenu } from "./OverflowMenu";
import { RageshakeRequestModal } from "./RageshakeRequestModal";
import { MatrixInfo } from "./VideoPreview";
import { useJoinRule } from "./useJoinRule";
import { ParticipantInfo } from "./useGroupCall";
import { TileContent } from "../video-grid/VideoTile";
import { Config } from "../config/Config";
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { SettingsModal } from "../settings/SettingsModal";
import { InviteModal } from "./InviteModal";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
@ -80,6 +85,11 @@ const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// For now we can disable screensharing in Safari.
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
interface LocalUserChoices {
videoMuted: boolean;
audioMuted: boolean;
}
interface Props {
client: MatrixClient;
groupCall: GroupCall;
@ -87,10 +97,11 @@ interface Props {
onLeave: () => void;
unencryptedEventsFromUsers: Set<string>;
hideHeader: boolean;
matrixInfo: MatrixInfo;
mediaDevices: MediaDevicesState;
livekitRoom: Room;
userChoices: LocalUserChoices;
otelGroupCallMembership: OTelGroupCallMembership;
}
export function InCallView({
@ -103,10 +114,11 @@ export function InCallView({
matrixInfo,
mediaDevices,
livekitRoom,
userChoices,
otelGroupCallMembership,
}: Props) {
const { t } = useTranslation();
usePreventScroll();
const joinRule = useJoinRule(groupCall.room);
const containerRef1 = useRef<HTMLDivElement | null>(null);
const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver });
@ -142,8 +154,8 @@ export function InCallView({
token,
serverUrl: Config.get().livekit.server_url,
room: livekitRoom,
audio: true,
video: true,
audio: !userChoices.audioMuted,
video: !userChoices.videoMuted,
onConnected: () => {
console.log("connected to LiveKit room");
},
@ -167,9 +179,6 @@ export function InCallView({
const [showInspector] = useShowInspector();
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
useModalTriggerState();
const { hideScreensharing } = useUrlParams();
const {
@ -185,12 +194,14 @@ export function InCallView({
const toggleCamera = useCallback(async () => {
await localParticipant.setCameraEnabled(!isCameraEnabled);
}, [localParticipant, isCameraEnabled]);
const toggleScreenSharing = useCallback(async () => {
const toggleScreensharing = useCallback(async () => {
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled);
}, [localParticipant, isScreenShareEnabled]);
const joinRule = useJoinRule(groupCall.room);
useCallViewKeyboardShortcuts(
!feedbackModalState.isOpen,
containerRef1,
toggleMicrophone,
toggleCamera,
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
@ -235,10 +246,22 @@ export function InCallView({
const reducedControls = boundsValid && bounds.width <= 400;
const noControls = reducedControls && bounds.height <= 400;
const prefersReducedMotion = usePrefersReducedMotion();
const items = useParticipantTiles(livekitRoom, participants);
// The maximised participant is the focused (active) participant, given the
// window is too small to show everyone
const maximisedParticipant = useMemo(
() =>
noControls
? items.find((item) => item.focused) ?? items.at(0) ?? null
: null,
[noControls, items]
);
const [newGrid] = useNewGrid();
const Grid = newGrid ? NewVideoGrid : VideoGrid;
const prefersReducedMotion = usePrefersReducedMotion();
const renderContent = (): JSX.Element => {
if (items.length === 0) {
return (
@ -247,15 +270,26 @@ export function InCallView({
</div>
);
}
if (maximisedParticipant) {
return (
<VideoTileContainer
targetHeight={bounds.height}
targetWidth={bounds.width}
id={maximisedParticipant.id}
key={maximisedParticipant.id}
item={maximisedParticipant.data}
/>
);
}
return (
<VideoGrid
<Grid
items={items}
layout={layout}
disableAnimations={prefersReducedMotion || isSafari}
>
{(child) => <VideoTileContainer item={child.data} {...child} />}
</VideoGrid>
</Grid>
);
};
@ -264,6 +298,36 @@ export function InCallView({
modalProps: rageshakeRequestModalProps,
} = useRageshakeRequestModal(groupCall.room.roomId);
const {
modalState: settingsModalState,
modalProps: settingsModalProps,
}: {
modalState: OverlayTriggerState;
modalProps: {
isOpen: boolean;
onClose: () => void;
};
} = useModalTriggerState();
const openSettings = useCallback(() => {
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,
});
@ -272,36 +336,44 @@ export function InCallView({
if (noControls) {
footer = null;
} else if (reducedControls) {
footer = (
<div className={styles.footer}>
<MicButton muted={!isMicrophoneEnabled} onPress={toggleMicrophone} />
<VideoButton muted={!isCameraEnabled} onPress={toggleCamera} />
<HangupButton onPress={onLeave} />
</div>
);
} else {
footer = (
<div className={styles.footer}>
<MicButton muted={!isMicrophoneEnabled} onPress={toggleMicrophone} />
<VideoButton muted={!isCameraEnabled} onPress={toggleCamera} />
{canScreenshare && !hideScreensharing && !isSafari && (
<ScreenshareButton
enabled={isScreenShareEnabled}
onPress={toggleScreenSharing}
/>
)}
<OverflowMenu
roomId={matrixInfo.roomId}
mediaDevices={mediaDevices}
inCall
showInvite={joinRule === JoinRule.Public}
feedbackModalState={feedbackModalState}
feedbackModalProps={feedbackModalProps}
/>
<HangupButton onPress={onLeave} />
</div>
const buttons: JSX.Element[] = [];
buttons.push(
<MicButton
key="1"
muted={!isMicrophoneEnabled}
onPress={toggleMicrophone}
data-testid="incall_mute"
/>,
<VideoButton
key="2"
muted={!isCameraEnabled}
onPress={toggleCamera}
data-testid="incall_videomute"
/>
);
if (!reducedControls) {
if (canScreenshare && !hideScreensharing && !isSafari) {
buttons.push(
<ScreenshareButton
key="3"
enabled={isScreenShareEnabled}
onPress={toggleScreensharing}
data-testid="incall_screenshare"
/>
);
}
if (!maximisedParticipant) {
buttons.push(<SettingsButton key="4" onPress={openSettings} />);
}
}
buttons.push(
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
);
footer = <div className={styles.footer}>{buttons}</div>;
}
return (
@ -320,21 +392,40 @@ export function InCallView({
</LeftNav>
<RightNav>
<GridLayoutMenu layout={layout} setLayout={setLayout} />
<UserMenuContainer preventNavigation />
{joinRule === JoinRule.Public && (
<InviteButton variant="icon" onClick={openInvite} />
)}
</RightNav>
</Header>
)}
{renderContent()}
{footer}
<div className={styles.controlsOverlay}>
{renderContent()}
{footer}
</div>
<GroupCallInspector
client={client}
groupCall={groupCall}
otelGroupCallMembership={otelGroupCallMembership}
show={showInspector}
/>
{rageshakeRequestModalState.isOpen && (
{rageshakeRequestModalState.isOpen && !noControls && (
<RageshakeRequestModal
{...rageshakeRequestModalProps}
roomIdOrAlias={matrixInfo.roomId}
roomIdOrAlias={matrixInfo.roomIdOrAlias}
/>
)}
{settingsModalState.isOpen && (
<SettingsModal
client={client}
roomId={groupCall.room.roomId}
mediaDevices={mediaDevices}
{...settingsModalProps}
/>
)}
{inviteModalState.isOpen && (
<InviteModal
roomIdOrAlias={matrixInfo.roomIdOrAlias}
{...inviteModalProps}
/>
)}
</div>
@ -371,6 +462,7 @@ function useParticipantTiles(
focused: false,
local: sfuParticipant.isLocal,
data: {
id,
member,
sfuParticipant,
content: TileContent.UserMedia,