2022-05-04 17:09:48 +01:00
|
|
|
/*
|
2023-05-05 11:44:35 +02:00
|
|
|
Copyright 2022 - 2023 New Vector Ltd
|
2022-05-04 17:09:48 +01:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-09-14 19:05:05 -04:00
|
|
|
import { ResizeObserver } from "@juggle/resize-observer";
|
2023-06-02 14:49:11 +02:00
|
|
|
import {
|
|
|
|
useLocalParticipant,
|
|
|
|
useParticipants,
|
|
|
|
useTracks,
|
|
|
|
} from "@livekit/components-react";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { usePreventScroll } from "@react-aria/overlays";
|
|
|
|
import classNames from "classnames";
|
|
|
|
import { Room, Track } from "livekit-client";
|
|
|
|
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";
|
2023-06-13 12:33:46 -04:00
|
|
|
import React, { Ref, useCallback, useEffect, useMemo, useRef } from "react";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import useMeasure from "react-use-measure";
|
2023-05-05 11:44:35 +02:00
|
|
|
import { OverlayTriggerState } from "@react-stately/overlays";
|
2022-10-14 09:40:21 -04:00
|
|
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
2022-08-02 00:46:16 +02:00
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
2022-01-05 15:06:51 -08:00
|
|
|
import {
|
|
|
|
HangupButton,
|
|
|
|
MicButton,
|
|
|
|
VideoButton,
|
|
|
|
ScreenshareButton,
|
2023-05-05 11:44:35 +02:00
|
|
|
SettingsButton,
|
|
|
|
InviteButton,
|
2022-01-05 15:06:51 -08:00
|
|
|
} from "../button";
|
2022-06-09 21:56:58 +01:00
|
|
|
import {
|
|
|
|
Header,
|
|
|
|
LeftNav,
|
|
|
|
RightNav,
|
|
|
|
RoomHeaderInfo,
|
|
|
|
VersionMismatchWarning,
|
|
|
|
} from "../Header";
|
2023-02-13 21:57:57 -05:00
|
|
|
import {
|
|
|
|
VideoGrid,
|
|
|
|
useVideoGridLayout,
|
2023-06-09 17:22:34 -04:00
|
|
|
TileDescriptor,
|
2023-02-13 21:57:57 -05:00
|
|
|
} from "../video-grid/VideoGrid";
|
2023-06-16 10:59:57 -04:00
|
|
|
import {
|
|
|
|
useShowInspector,
|
|
|
|
useShowConnectionStats,
|
|
|
|
} from "../settings/useSetting";
|
2022-05-17 15:36:13 +01:00
|
|
|
import { useModalTriggerState } from "../Modal";
|
2023-03-01 13:47:36 +01:00
|
|
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
2022-10-14 16:17:50 +02:00
|
|
|
import { useUrlParams } from "../UrlParams";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
|
|
|
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
|
|
|
import { ElementWidgetActions, widget } from "../widget";
|
|
|
|
import { GridLayoutMenu } from "./GridLayoutMenu";
|
|
|
|
import { GroupCallInspector } from "./GroupCallInspector";
|
|
|
|
import styles from "./InCallView.module.css";
|
2023-06-02 14:49:11 +02:00
|
|
|
import { MatrixInfo } from "./VideoPreview";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { useJoinRule } from "./useJoinRule";
|
|
|
|
import { ParticipantInfo } from "./useGroupCall";
|
2023-06-13 12:33:46 -04:00
|
|
|
import { ItemData, TileContent } from "../video-grid/VideoTile";
|
2023-06-07 20:16:24 +02:00
|
|
|
import { Config } from "../config/Config";
|
2023-01-18 10:52:12 -05:00
|
|
|
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
2023-03-16 14:41:55 +00:00
|
|
|
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
2023-05-05 11:44:35 +02:00
|
|
|
import { SettingsModal } from "../settings/SettingsModal";
|
|
|
|
import { InviteModal } from "./InviteModal";
|
2023-05-22 15:30:29 -04:00
|
|
|
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
|
|
|
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
2023-06-12 18:06:18 -04:00
|
|
|
import { VideoTile } from "../video-grid/VideoTile";
|
2023-06-16 18:07:13 +02:00
|
|
|
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
|
|
|
import { useMediaDevices } from "../livekit/useMediaDevices";
|
2022-01-05 15:06:51 -08:00
|
|
|
|
2022-07-03 10:36:16 -04:00
|
|
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
2022-01-05 15:06:51 -08:00
|
|
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
|
|
|
// or with getUsermedia and getDisplaymedia being used within the same session.
|
|
|
|
// For now we can disable screensharing in Safari.
|
|
|
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
|
|
|
2023-06-16 18:07:13 +02:00
|
|
|
interface ActiveCallProps extends Omit<Props, "livekitRoom"> {
|
|
|
|
userChoices: UserChoices;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function ActiveCall(props: ActiveCallProps) {
|
|
|
|
const livekitRoom = useLiveKit(props.userChoices, {
|
|
|
|
sfuUrl: Config.get().livekit!.server_url,
|
|
|
|
jwtUrl: `${Config.get().livekit!.jwt_service_url}/token`,
|
|
|
|
roomName: props.matrixInfo.roomName,
|
|
|
|
userName: props.matrixInfo.userName,
|
|
|
|
userIdentity: `${props.client.getUserId()}:${props.client.getDeviceId()}`,
|
|
|
|
});
|
|
|
|
|
|
|
|
return livekitRoom && <InCallView {...props} livekitRoom={livekitRoom} />;
|
2023-06-12 15:35:54 +02:00
|
|
|
}
|
|
|
|
|
2022-08-02 00:46:16 +02:00
|
|
|
interface Props {
|
|
|
|
client: MatrixClient;
|
|
|
|
groupCall: GroupCall;
|
2023-06-16 18:07:13 +02:00
|
|
|
livekitRoom: Room;
|
2022-11-21 12:39:48 -05:00
|
|
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
2022-08-02 00:46:16 +02:00
|
|
|
onLeave: () => void;
|
|
|
|
unencryptedEventsFromUsers: Set<string>;
|
2022-09-09 02:04:53 -04:00
|
|
|
hideHeader: boolean;
|
2023-06-02 14:49:11 +02:00
|
|
|
matrixInfo: MatrixInfo;
|
2023-03-16 14:41:55 +00:00
|
|
|
otelGroupCallMembership: OTelGroupCallMembership;
|
2022-08-02 00:46:16 +02:00
|
|
|
}
|
2022-08-08 20:01:58 +02:00
|
|
|
|
2022-01-05 15:06:51 -08:00
|
|
|
export function InCallView({
|
|
|
|
client,
|
|
|
|
groupCall,
|
2023-06-16 18:07:13 +02:00
|
|
|
livekitRoom,
|
2022-10-21 17:24:56 +01:00
|
|
|
participants,
|
2022-01-05 15:06:51 -08:00
|
|
|
onLeave,
|
2022-06-09 21:56:58 +01:00
|
|
|
unencryptedEventsFromUsers,
|
2022-09-09 02:04:53 -04:00
|
|
|
hideHeader,
|
2023-06-02 14:49:11 +02:00
|
|
|
matrixInfo,
|
2023-03-16 14:41:55 +00:00
|
|
|
otelGroupCallMembership,
|
2022-08-02 00:46:16 +02:00
|
|
|
}: Props) {
|
2022-10-10 09:19:10 -04:00
|
|
|
const { t } = useTranslation();
|
2022-02-16 11:17:33 -08:00
|
|
|
usePreventScroll();
|
2022-10-14 09:40:21 -04:00
|
|
|
|
2022-09-14 19:05:05 -04:00
|
|
|
const containerRef1 = useRef<HTMLDivElement | null>(null);
|
|
|
|
const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver });
|
2022-11-02 22:37:36 -04:00
|
|
|
const boundsValid = bounds.height > 0;
|
2022-09-14 19:05:05 -04:00
|
|
|
// Merge the refs so they can attach to the same element
|
|
|
|
const containerRef = useCallback(
|
|
|
|
(el: HTMLDivElement) => {
|
|
|
|
containerRef1.current = el;
|
|
|
|
containerRef2(el);
|
|
|
|
},
|
|
|
|
[containerRef1, containerRef2]
|
|
|
|
);
|
|
|
|
|
2023-06-16 18:07:13 +02:00
|
|
|
// Managed media devices state coupled with an active room.
|
|
|
|
const roomMediaDevices = useMediaDevices(livekitRoom);
|
2023-06-02 14:49:11 +02:00
|
|
|
|
|
|
|
const screenSharingTracks = useTracks(
|
|
|
|
[{ source: Track.Source.ScreenShare, withPlaceholder: false }],
|
|
|
|
{
|
|
|
|
room: livekitRoom,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
const { layout, setLayout } = useVideoGridLayout(
|
|
|
|
screenSharingTracks.length > 0
|
|
|
|
);
|
2022-08-12 09:53:44 +02:00
|
|
|
|
2022-05-31 10:43:05 -04:00
|
|
|
const [showInspector] = useShowInspector();
|
2023-06-16 10:59:57 -04:00
|
|
|
const [showConnectionStats] = useShowConnectionStats();
|
2022-05-31 10:43:05 -04:00
|
|
|
|
2022-10-14 16:17:50 +02:00
|
|
|
const { hideScreensharing } = useUrlParams();
|
|
|
|
|
2023-06-02 14:49:11 +02:00
|
|
|
const {
|
|
|
|
isMicrophoneEnabled,
|
|
|
|
isCameraEnabled,
|
|
|
|
isScreenShareEnabled,
|
|
|
|
localParticipant,
|
|
|
|
} = useLocalParticipant({ room: livekitRoom });
|
|
|
|
|
|
|
|
const toggleMicrophone = useCallback(async () => {
|
|
|
|
await localParticipant.setMicrophoneEnabled(!isMicrophoneEnabled);
|
|
|
|
}, [localParticipant, isMicrophoneEnabled]);
|
|
|
|
const toggleCamera = useCallback(async () => {
|
|
|
|
await localParticipant.setCameraEnabled(!isCameraEnabled);
|
|
|
|
}, [localParticipant, isCameraEnabled]);
|
2023-06-09 17:22:34 -04:00
|
|
|
const toggleScreensharing = useCallback(async () => {
|
2023-06-02 14:49:11 +02:00
|
|
|
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled);
|
|
|
|
}, [localParticipant, isScreenShareEnabled]);
|
|
|
|
|
2023-05-05 11:44:35 +02:00
|
|
|
const joinRule = useJoinRule(groupCall.room);
|
|
|
|
|
2023-06-16 18:07:13 +02:00
|
|
|
// 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!
|
2023-01-13 11:52:40 +00:00
|
|
|
useCallViewKeyboardShortcuts(
|
2023-04-19 15:51:44 -04:00
|
|
|
containerRef1,
|
2023-06-02 14:49:11 +02:00
|
|
|
toggleMicrophone,
|
2023-06-02 21:29:22 +02:00
|
|
|
toggleCamera,
|
2023-06-02 14:49:11 +02:00
|
|
|
async (muted) => await localParticipant.setMicrophoneEnabled(!muted)
|
2023-01-12 17:31:19 +00:00
|
|
|
);
|
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
useEffect(() => {
|
|
|
|
widget?.api.transport.send(
|
|
|
|
layout === "freedom"
|
|
|
|
? ElementWidgetActions.TileLayout
|
|
|
|
: ElementWidgetActions.SpotlightLayout,
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
}, [layout]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (widget) {
|
|
|
|
const onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
|
|
|
setLayout("freedom");
|
|
|
|
await widget.api.transport.reply(ev.detail, {});
|
|
|
|
};
|
|
|
|
const onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
|
|
|
setLayout("spotlight");
|
|
|
|
await widget.api.transport.reply(ev.detail, {});
|
|
|
|
};
|
|
|
|
|
|
|
|
widget.lazyActions.on(ElementWidgetActions.TileLayout, onTileLayout);
|
|
|
|
widget.lazyActions.on(
|
|
|
|
ElementWidgetActions.SpotlightLayout,
|
|
|
|
onSpotlightLayout
|
|
|
|
);
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
widget.lazyActions.off(ElementWidgetActions.TileLayout, onTileLayout);
|
|
|
|
widget.lazyActions.off(
|
|
|
|
ElementWidgetActions.SpotlightLayout,
|
|
|
|
onSpotlightLayout
|
|
|
|
);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}, [setLayout]);
|
|
|
|
|
2022-12-13 18:20:13 -05:00
|
|
|
const reducedControls = boundsValid && bounds.width <= 400;
|
|
|
|
const noControls = reducedControls && bounds.height <= 400;
|
|
|
|
|
2023-06-06 13:16:36 +02:00
|
|
|
const items = useParticipantTiles(livekitRoom, participants);
|
|
|
|
|
2023-06-09 17:22:34 -04:00
|
|
|
// The maximised participant is the focused (active) participant, given the
|
2022-09-14 19:05:05 -04:00
|
|
|
// window is too small to show everyone
|
|
|
|
const maximisedParticipant = useMemo(
|
|
|
|
() =>
|
2023-06-09 17:22:34 -04:00
|
|
|
noControls
|
|
|
|
? items.find((item) => item.focused) ?? items.at(0) ?? null
|
|
|
|
: null,
|
|
|
|
[noControls, items]
|
2022-09-14 19:05:05 -04:00
|
|
|
);
|
2022-01-05 15:06:51 -08:00
|
|
|
|
2023-06-13 12:47:45 -04:00
|
|
|
const Grid =
|
|
|
|
items.length > 12 && layout === "freedom" ? NewVideoGrid : VideoGrid;
|
2023-06-14 17:12:03 +02:00
|
|
|
|
2022-10-31 11:46:17 -04:00
|
|
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
|
|
|
2022-09-14 19:05:05 -04:00
|
|
|
const renderContent = (): JSX.Element => {
|
2022-08-07 19:09:45 +02:00
|
|
|
if (items.length === 0) {
|
|
|
|
return (
|
|
|
|
<div className={styles.centerMessage}>
|
2022-10-10 09:19:10 -04:00
|
|
|
<p>{t("Waiting for other participants…")}</p>
|
2022-08-07 19:09:45 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-09-14 19:05:05 -04:00
|
|
|
if (maximisedParticipant) {
|
2022-08-07 19:09:45 +02:00
|
|
|
return (
|
2023-06-12 18:06:18 -04:00
|
|
|
<VideoTile
|
2023-02-13 21:57:57 -05:00
|
|
|
targetHeight={bounds.height}
|
|
|
|
targetWidth={bounds.width}
|
2022-09-14 19:05:05 -04:00
|
|
|
key={maximisedParticipant.id}
|
2023-06-13 12:33:46 -04:00
|
|
|
data={maximisedParticipant.data}
|
2023-06-16 10:35:29 -04:00
|
|
|
showSpeakingIndicator={false}
|
2023-06-16 10:59:57 -04:00
|
|
|
showConnectionStats={showConnectionStats}
|
2022-08-07 19:09:45 +02:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-02-13 20:36:42 -05:00
|
|
|
<Grid
|
2022-10-31 11:46:17 -04:00
|
|
|
items={items}
|
|
|
|
layout={layout}
|
|
|
|
disableAnimations={prefersReducedMotion || isSafari}
|
|
|
|
>
|
2023-06-12 18:06:18 -04:00
|
|
|
{(props) => (
|
2023-06-16 10:35:29 -04:00
|
|
|
<VideoTile
|
|
|
|
showSpeakingIndicator={items.length > 2}
|
2023-06-16 10:59:57 -04:00
|
|
|
showConnectionStats={showConnectionStats}
|
2023-06-16 10:35:29 -04:00
|
|
|
{...props}
|
|
|
|
ref={props.ref as Ref<HTMLDivElement>}
|
|
|
|
/>
|
2022-08-07 19:09:45 +02:00
|
|
|
)}
|
2023-02-13 20:36:42 -05:00
|
|
|
</Grid>
|
2022-08-07 19:09:45 +02:00
|
|
|
);
|
2022-09-14 19:05:05 -04:00
|
|
|
};
|
2022-08-07 19:09:45 +02:00
|
|
|
|
2022-02-04 16:55:57 -08:00
|
|
|
const {
|
|
|
|
modalState: rageshakeRequestModalState,
|
|
|
|
modalProps: rageshakeRequestModalProps,
|
|
|
|
} = useRageshakeRequestModal(groupCall.room.roomId);
|
|
|
|
|
|
|
|
const {
|
2023-05-05 11:44:35 +02:00
|
|
|
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]);
|
2022-02-04 16:55:57 -08:00
|
|
|
|
2022-09-14 19:05:05 -04:00
|
|
|
const containerClasses = classNames(styles.inRoom, {
|
2023-06-06 13:16:36 +02:00
|
|
|
[styles.maximised]: undefined,
|
2022-08-07 19:09:45 +02:00
|
|
|
});
|
|
|
|
|
2022-12-13 18:20:13 -05:00
|
|
|
let footer: JSX.Element | null;
|
|
|
|
|
|
|
|
if (noControls) {
|
|
|
|
footer = null;
|
|
|
|
} else {
|
2023-03-02 18:48:32 +01:00
|
|
|
const buttons: JSX.Element[] = [];
|
|
|
|
|
|
|
|
buttons.push(
|
|
|
|
<MicButton
|
|
|
|
key="1"
|
2023-06-09 17:22:34 -04:00
|
|
|
muted={!isMicrophoneEnabled}
|
|
|
|
onPress={toggleMicrophone}
|
2023-04-25 16:42:27 +01:00
|
|
|
data-testid="incall_mute"
|
2023-03-02 18:48:32 +01:00
|
|
|
/>,
|
|
|
|
<VideoButton
|
|
|
|
key="2"
|
2023-06-09 17:22:34 -04:00
|
|
|
muted={!isCameraEnabled}
|
|
|
|
onPress={toggleCamera}
|
2023-04-25 16:42:27 +01:00
|
|
|
data-testid="incall_videomute"
|
2023-03-02 18:48:32 +01:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!reducedControls) {
|
|
|
|
if (canScreenshare && !hideScreensharing && !isSafari) {
|
|
|
|
buttons.push(
|
2022-12-13 18:20:13 -05:00
|
|
|
<ScreenshareButton
|
2023-03-02 18:48:32 +01:00
|
|
|
key="3"
|
2023-06-02 14:49:11 +02:00
|
|
|
enabled={isScreenShareEnabled}
|
2022-12-13 18:20:13 -05:00
|
|
|
onPress={toggleScreensharing}
|
2023-04-25 16:42:27 +01:00
|
|
|
data-testid="incall_screenshare"
|
2022-12-13 18:20:13 -05:00
|
|
|
/>
|
2023-03-02 18:48:32 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!maximisedParticipant) {
|
2023-05-05 11:44:35 +02:00
|
|
|
buttons.push(<SettingsButton key="4" onPress={openSettings} />);
|
2023-03-02 18:48:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-12 16:25:24 +01:00
|
|
|
buttons.push(
|
|
|
|
<HangupButton key="6" onPress={onLeave} data-testid="incall_leave" />
|
2022-12-13 18:20:13 -05:00
|
|
|
);
|
2023-03-02 18:48:32 +01:00
|
|
|
footer = <div className={styles.footer}>{buttons}</div>;
|
2022-12-13 18:20:13 -05:00
|
|
|
}
|
|
|
|
|
2022-01-05 15:06:51 -08:00
|
|
|
return (
|
2022-09-14 19:05:05 -04:00
|
|
|
<div className={containerClasses} ref={containerRef}>
|
2023-06-06 13:16:36 +02:00
|
|
|
{!hideHeader && (
|
2022-08-07 19:09:45 +02:00
|
|
|
<Header>
|
|
|
|
<LeftNav>
|
2023-06-02 14:49:11 +02:00
|
|
|
<RoomHeaderInfo
|
|
|
|
roomName={matrixInfo.roomName}
|
|
|
|
avatarUrl={matrixInfo.avatarUrl}
|
|
|
|
/>
|
2022-08-07 19:09:45 +02:00
|
|
|
<VersionMismatchWarning
|
|
|
|
users={unencryptedEventsFromUsers}
|
|
|
|
room={groupCall.room}
|
2022-01-07 16:20:55 -08:00
|
|
|
/>
|
2022-08-07 19:09:45 +02:00
|
|
|
</LeftNav>
|
|
|
|
<RightNav>
|
|
|
|
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
2023-05-05 11:44:35 +02:00
|
|
|
{joinRule === JoinRule.Public && (
|
|
|
|
<InviteButton variant="icon" onClick={openInvite} />
|
|
|
|
)}
|
2022-08-07 19:09:45 +02:00
|
|
|
</RightNav>
|
|
|
|
</Header>
|
2022-01-05 15:06:51 -08:00
|
|
|
)}
|
2023-04-19 14:43:37 -04:00
|
|
|
<div className={styles.controlsOverlay}>
|
|
|
|
{renderContent()}
|
|
|
|
{footer}
|
|
|
|
</div>
|
2022-01-05 15:06:51 -08:00
|
|
|
<GroupCallInspector
|
|
|
|
client={client}
|
|
|
|
groupCall={groupCall}
|
2023-03-16 14:41:55 +00:00
|
|
|
otelGroupCallMembership={otelGroupCallMembership}
|
2022-01-05 15:06:51 -08:00
|
|
|
show={showInspector}
|
|
|
|
/>
|
2023-05-22 15:30:29 -04:00
|
|
|
{rageshakeRequestModalState.isOpen && !noControls && (
|
2022-02-23 15:52:53 -08:00
|
|
|
<RageshakeRequestModal
|
|
|
|
{...rageshakeRequestModalProps}
|
2023-06-09 17:22:34 -04:00
|
|
|
roomIdOrAlias={matrixInfo.roomIdOrAlias}
|
2023-05-22 15:30:29 -04:00
|
|
|
/>
|
|
|
|
)}
|
2023-05-05 11:44:35 +02:00
|
|
|
{settingsModalState.isOpen && (
|
|
|
|
<SettingsModal
|
|
|
|
client={client}
|
2023-05-22 15:30:29 -04:00
|
|
|
roomId={groupCall.room.roomId}
|
2023-06-16 18:07:13 +02:00
|
|
|
mediaDevices={roomMediaDevices}
|
2023-05-05 11:44:35 +02:00
|
|
|
{...settingsModalProps}
|
2022-02-23 15:52:53 -08:00
|
|
|
/>
|
2022-02-04 16:55:57 -08:00
|
|
|
)}
|
2023-05-05 11:44:35 +02:00
|
|
|
{inviteModalState.isOpen && (
|
2023-06-09 17:22:34 -04:00
|
|
|
<InviteModal
|
|
|
|
roomIdOrAlias={matrixInfo.roomIdOrAlias}
|
|
|
|
{...inviteModalProps}
|
2022-02-23 15:52:53 -08:00
|
|
|
/>
|
2022-02-04 16:55:57 -08:00
|
|
|
)}
|
2022-01-05 15:06:51 -08:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2023-06-05 20:51:01 +02:00
|
|
|
|
|
|
|
function useParticipantTiles(
|
|
|
|
livekitRoom: Room,
|
2023-06-06 13:16:36 +02:00
|
|
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>
|
2023-06-05 20:51:01 +02:00
|
|
|
): TileDescriptor<ItemData>[] {
|
|
|
|
const sfuParticipants = useParticipants({
|
|
|
|
room: livekitRoom,
|
|
|
|
});
|
|
|
|
|
|
|
|
const items = useMemo(() => {
|
2023-06-12 19:58:06 +02:00
|
|
|
// The IDs of the participants who published membership event to the room (i.e. are present from Matrix perspective).
|
|
|
|
const matrixParticipants: Map<string, RoomMember> = new Map(
|
|
|
|
[...participants.entries()].flatMap(([user, devicesMap]) => {
|
|
|
|
return [...devicesMap.keys()].map((deviceId) => [
|
|
|
|
`${user.userId}:${deviceId}`,
|
|
|
|
user,
|
|
|
|
]);
|
|
|
|
})
|
|
|
|
);
|
2023-06-05 20:51:01 +02:00
|
|
|
|
2023-06-16 18:57:47 +02:00
|
|
|
const hasPresenter =
|
|
|
|
sfuParticipants.find((p) => p.isScreenShareEnabled) !== undefined;
|
2023-06-14 22:14:06 +02:00
|
|
|
|
2023-06-16 18:57:47 +02:00
|
|
|
const speakActiveTime = new Date();
|
|
|
|
speakActiveTime.setSeconds(speakActiveTime.getSeconds() - 10);
|
2023-06-12 19:58:06 +02:00
|
|
|
// Iterate over SFU participants (those who actually are present from the SFU perspective) and create tiles for them.
|
|
|
|
const tiles: TileDescriptor<ItemData>[] = sfuParticipants.flatMap(
|
|
|
|
(sfuParticipant) => {
|
2023-06-16 18:57:47 +02:00
|
|
|
const hadSpokedInTime =
|
|
|
|
!hasPresenter && sfuParticipant.lastSpokeAt
|
|
|
|
? sfuParticipant.lastSpokeAt > speakActiveTime
|
|
|
|
: false;
|
|
|
|
|
2023-06-12 19:58:06 +02:00
|
|
|
const id = sfuParticipant.identity;
|
|
|
|
const member = matrixParticipants.get(id);
|
2023-06-06 13:16:36 +02:00
|
|
|
const userMediaTile = {
|
2023-06-05 20:51:01 +02:00
|
|
|
id,
|
2023-06-16 18:57:47 +02:00
|
|
|
focused: false,
|
|
|
|
isPresenter: sfuParticipant.isScreenShareEnabled,
|
|
|
|
isSpeaker:
|
|
|
|
(sfuParticipant.isSpeaking || hadSpokedInTime) &&
|
|
|
|
!sfuParticipant.isLocal,
|
|
|
|
hasVideo: sfuParticipant.isCameraEnabled,
|
2023-06-06 13:16:36 +02:00
|
|
|
local: sfuParticipant.isLocal,
|
2023-06-05 20:51:01 +02:00
|
|
|
data: {
|
|
|
|
member,
|
|
|
|
sfuParticipant,
|
2023-06-06 13:16:36 +02:00
|
|
|
content: TileContent.UserMedia,
|
2023-06-05 20:51:01 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-06-06 13:16:36 +02:00
|
|
|
// If there is a screen sharing enabled for this participant, create a tile for it as well.
|
2023-06-12 19:58:06 +02:00
|
|
|
let screenShareTile: TileDescriptor<ItemData> | undefined;
|
2023-06-06 13:16:36 +02:00
|
|
|
if (sfuParticipant.isScreenShareEnabled) {
|
2023-06-12 19:58:06 +02:00
|
|
|
screenShareTile = {
|
2023-06-06 13:16:36 +02:00
|
|
|
...userMediaTile,
|
|
|
|
id: `${id}:screen-share`,
|
|
|
|
focused: true,
|
|
|
|
data: {
|
|
|
|
...userMediaTile.data,
|
|
|
|
content: TileContent.ScreenShare,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2023-06-12 19:58:06 +02:00
|
|
|
|
|
|
|
return screenShareTile
|
|
|
|
? [userMediaTile, screenShareTile]
|
|
|
|
: [userMediaTile];
|
2023-06-05 20:51:01 +02:00
|
|
|
}
|
2023-06-12 19:58:06 +02:00
|
|
|
);
|
2023-06-05 20:51:01 +02:00
|
|
|
|
|
|
|
PosthogAnalytics.instance.eventCallEnded.cacheParticipantCountChanged(
|
|
|
|
tiles.length
|
|
|
|
);
|
|
|
|
|
|
|
|
return tiles;
|
2023-06-06 13:16:36 +02:00
|
|
|
}, [participants, sfuParticipants]);
|
2023-06-05 20:51:01 +02:00
|
|
|
|
|
|
|
return items;
|
|
|
|
}
|