2022-05-04 17:09:48 +01:00
|
|
|
/*
|
2023-01-03 16:55:26 +00:00
|
|
|
Copyright 2022 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 {
|
|
|
|
useLiveKitRoom,
|
|
|
|
useLocalParticipant,
|
|
|
|
useParticipants,
|
|
|
|
useToken,
|
|
|
|
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 { 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";
|
2022-08-02 00:46:16 +02:00
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { Avatar } from "../Avatar";
|
2022-06-09 21:56:58 +01:00
|
|
|
import {
|
|
|
|
Header,
|
|
|
|
LeftNav,
|
|
|
|
RightNav,
|
|
|
|
RoomHeaderInfo,
|
|
|
|
VersionMismatchWarning,
|
|
|
|
} from "../Header";
|
2022-05-17 15:36:13 +01:00
|
|
|
import { useModalTriggerState } from "../Modal";
|
2022-11-04 13:07:14 +01:00
|
|
|
import { PosthogAnalytics } from "../PosthogAnalytics";
|
2022-10-14 16:17:50 +02:00
|
|
|
import { useUrlParams } from "../UrlParams";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { UserMenuContainer } from "../UserMenuContainer";
|
|
|
|
import {
|
|
|
|
HangupButton,
|
|
|
|
MicButton,
|
|
|
|
ScreenshareButton,
|
|
|
|
VideoButton,
|
|
|
|
} from "../button";
|
2023-05-30 20:56:25 +02:00
|
|
|
import { MediaDevicesState } from "../settings/mediaDevices";
|
2023-06-05 20:51:01 +02:00
|
|
|
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
|
|
|
import { useShowInspector } from "../settings/useSetting";
|
|
|
|
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
|
|
|
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
|
|
|
import {
|
|
|
|
TileDescriptor,
|
|
|
|
VideoGrid,
|
|
|
|
useVideoGridLayout,
|
|
|
|
} from "../video-grid/VideoGrid";
|
|
|
|
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";
|
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";
|
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);
|
|
|
|
|
2022-08-02 00:46:16 +02:00
|
|
|
interface Props {
|
|
|
|
client: MatrixClient;
|
|
|
|
groupCall: GroupCall;
|
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;
|
|
|
|
mediaDevices: MediaDevicesState;
|
|
|
|
livekitRoom: Room;
|
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,
|
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,
|
|
|
|
mediaDevices,
|
|
|
|
livekitRoom,
|
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
|
|
|
const joinRule = useJoinRule(groupCall.room);
|
|
|
|
|
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-02 14:49:11 +02:00
|
|
|
const userId = client.getUserId();
|
|
|
|
const deviceId = client.getDeviceId();
|
|
|
|
const options = useMemo(
|
|
|
|
() => ({
|
|
|
|
userInfo: {
|
|
|
|
name: matrixInfo.userName,
|
|
|
|
identity: `${userId}:${deviceId}`,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
[matrixInfo.userName, userId, deviceId]
|
|
|
|
);
|
|
|
|
const token = useToken(
|
|
|
|
"http://localhost:8080/token",
|
|
|
|
matrixInfo.roomName,
|
|
|
|
options
|
|
|
|
);
|
2022-01-05 15:06:51 -08:00
|
|
|
|
2023-06-02 14:49:11 +02:00
|
|
|
// Uses a hook to connect to the LiveKit room (on unmount the room will be left) and publish local media tracks (default).
|
|
|
|
useLiveKitRoom({
|
|
|
|
token,
|
|
|
|
serverUrl: "ws://localhost:7880",
|
|
|
|
room: livekitRoom,
|
2023-06-02 19:12:28 +02:00
|
|
|
audio: true,
|
|
|
|
video: true,
|
2023-06-02 14:49:11 +02:00
|
|
|
onConnected: () => {
|
|
|
|
console.log("connected to LiveKit room");
|
|
|
|
},
|
|
|
|
onDisconnected: () => {
|
|
|
|
console.log("disconnected from LiveKit room");
|
|
|
|
},
|
|
|
|
onError: (err) => {
|
|
|
|
console.error("error connecting to LiveKit room", err);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
2022-05-17 15:36:13 +01:00
|
|
|
const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
|
|
|
|
useModalTriggerState();
|
|
|
|
|
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]);
|
|
|
|
const toggleScreenSharing = useCallback(async () => {
|
|
|
|
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled);
|
|
|
|
}, [localParticipant, isScreenShareEnabled]);
|
|
|
|
|
2023-01-13 11:52:40 +00:00
|
|
|
useCallViewKeyboardShortcuts(
|
2023-01-12 17:31:19 +00:00
|
|
|
!feedbackModalState.isOpen,
|
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-05 20:51:01 +02:00
|
|
|
const items = useParticipantTiles(livekitRoom, participants, {
|
|
|
|
userId: client.getUserId()!,
|
|
|
|
deviceId: client.getDeviceId()!,
|
|
|
|
});
|
|
|
|
|
2023-06-02 14:49:11 +02:00
|
|
|
// The maximised participant: the focused (active) participant if the
|
|
|
|
// window is too small to show everyone.
|
2022-09-14 19:05:05 -04:00
|
|
|
const maximisedParticipant = useMemo(
|
2023-06-02 14:49:11 +02:00
|
|
|
() => (noControls ? items.find((item) => item.focused) ?? null : null),
|
|
|
|
[noControls, items]
|
2022-09-14 19:05:05 -04:00
|
|
|
);
|
2022-01-05 15:06:51 -08:00
|
|
|
|
2022-08-12 19:27:34 +02:00
|
|
|
const renderAvatar = useCallback(
|
|
|
|
(roomMember: RoomMember, width: number, height: number) => {
|
2022-10-24 13:17:29 -04:00
|
|
|
const avatarUrl = roomMember.getMxcAvatarUrl();
|
2022-08-12 19:27:34 +02:00
|
|
|
const size = Math.round(Math.min(width, height) / 2);
|
2022-08-02 00:46:16 +02:00
|
|
|
|
2022-08-12 19:27:34 +02:00
|
|
|
return (
|
|
|
|
<Avatar
|
|
|
|
key={roomMember.userId}
|
|
|
|
size={size}
|
2022-10-24 13:17:29 -04:00
|
|
|
src={avatarUrl ?? undefined}
|
2022-08-12 19:27:34 +02:00
|
|
|
fallback={roomMember.name.slice(0, 1).toUpperCase()}
|
|
|
|
className={styles.avatar}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
},
|
|
|
|
[]
|
|
|
|
);
|
2022-01-05 15:06:51 -08: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 (
|
|
|
|
<VideoTileContainer
|
2022-09-14 19:05:05 -04:00
|
|
|
height={bounds.height}
|
|
|
|
width={bounds.width}
|
|
|
|
key={maximisedParticipant.id}
|
2023-06-05 20:51:01 +02:00
|
|
|
item={maximisedParticipant.data}
|
2022-08-07 19:09:45 +02:00
|
|
|
getAvatar={renderAvatar}
|
2022-09-14 19:05:05 -04:00
|
|
|
maximised={Boolean(maximisedParticipant)}
|
2022-08-07 19:09:45 +02:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2022-10-31 11:46:17 -04:00
|
|
|
<VideoGrid
|
|
|
|
items={items}
|
|
|
|
layout={layout}
|
|
|
|
disableAnimations={prefersReducedMotion || isSafari}
|
|
|
|
>
|
2023-06-05 20:51:01 +02:00
|
|
|
{(child) => (
|
2022-08-07 19:09:45 +02:00
|
|
|
<VideoTileContainer
|
|
|
|
getAvatar={renderAvatar}
|
2022-09-14 19:05:05 -04:00
|
|
|
maximised={false}
|
2023-06-05 20:51:01 +02:00
|
|
|
item={child.data}
|
|
|
|
{...child}
|
2022-08-07 19:09:45 +02:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</VideoGrid>
|
|
|
|
);
|
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);
|
|
|
|
|
2022-09-14 19:05:05 -04:00
|
|
|
const containerClasses = classNames(styles.inRoom, {
|
|
|
|
[styles.maximised]: maximisedParticipant,
|
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 if (reducedControls) {
|
|
|
|
footer = (
|
|
|
|
<div className={styles.footer}>
|
2023-06-02 14:49:11 +02:00
|
|
|
<MicButton muted={!isMicrophoneEnabled} onPress={toggleMicrophone} />
|
|
|
|
<VideoButton muted={!isCameraEnabled} onPress={toggleCamera} />
|
2022-12-13 18:20:13 -05:00
|
|
|
<HangupButton onPress={onLeave} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
footer = (
|
|
|
|
<div className={styles.footer}>
|
2023-06-02 14:49:11 +02:00
|
|
|
<MicButton muted={!isMicrophoneEnabled} onPress={toggleMicrophone} />
|
|
|
|
<VideoButton muted={!isCameraEnabled} onPress={toggleCamera} />
|
2022-12-13 18:20:13 -05:00
|
|
|
{canScreenshare && !hideScreensharing && !isSafari && (
|
|
|
|
<ScreenshareButton
|
2023-06-02 14:49:11 +02:00
|
|
|
enabled={isScreenShareEnabled}
|
|
|
|
onPress={toggleScreenSharing}
|
2022-12-13 18:20:13 -05:00
|
|
|
/>
|
|
|
|
)}
|
2022-12-22 12:16:05 +00:00
|
|
|
{!maximisedParticipant && (
|
|
|
|
<OverflowMenu
|
2023-06-02 14:49:11 +02:00
|
|
|
roomId={matrixInfo.roomId}
|
2023-05-26 20:41:32 +02:00
|
|
|
mediaDevices={mediaDevices}
|
2022-12-22 12:16:05 +00:00
|
|
|
inCall
|
|
|
|
showInvite={joinRule === JoinRule.Public}
|
|
|
|
feedbackModalState={feedbackModalState}
|
|
|
|
feedbackModalProps={feedbackModalProps}
|
|
|
|
/>
|
|
|
|
)}
|
2022-12-13 18:20:13 -05:00
|
|
|
<HangupButton onPress={onLeave} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-01-05 15:06:51 -08:00
|
|
|
return (
|
2022-09-14 19:05:05 -04:00
|
|
|
<div className={containerClasses} ref={containerRef}>
|
|
|
|
{!hideHeader && !maximisedParticipant && (
|
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} />
|
|
|
|
<UserMenuContainer preventNavigation />
|
|
|
|
</RightNav>
|
|
|
|
</Header>
|
2022-01-05 15:06:51 -08:00
|
|
|
)}
|
2022-08-07 19:09:45 +02:00
|
|
|
{renderContent()}
|
2022-12-13 18:20:13 -05:00
|
|
|
{footer}
|
2022-01-05 15:06:51 -08:00
|
|
|
<GroupCallInspector
|
|
|
|
client={client}
|
|
|
|
groupCall={groupCall}
|
|
|
|
show={showInspector}
|
|
|
|
/>
|
2022-02-04 16:55:57 -08:00
|
|
|
{rageshakeRequestModalState.isOpen && (
|
2022-02-23 15:52:53 -08:00
|
|
|
<RageshakeRequestModal
|
|
|
|
{...rageshakeRequestModalProps}
|
2023-06-02 14:49:11 +02:00
|
|
|
roomIdOrAlias={matrixInfo.roomId}
|
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
|
|
|
|
|
|
|
interface ParticipantID {
|
|
|
|
userId: string;
|
|
|
|
deviceId: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
function useParticipantTiles(
|
|
|
|
livekitRoom: Room,
|
|
|
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>,
|
|
|
|
local: ParticipantID
|
|
|
|
): TileDescriptor<ItemData>[] {
|
|
|
|
const sfuParticipants = useParticipants({
|
|
|
|
room: livekitRoom,
|
|
|
|
});
|
|
|
|
|
|
|
|
const [localUserId, localDeviceId] = [local.userId, local.deviceId];
|
|
|
|
|
|
|
|
const items = useMemo(() => {
|
|
|
|
const tiles: TileDescriptor<ItemData>[] = [];
|
|
|
|
for (const [member, participantMap] of participants) {
|
|
|
|
for (const [deviceId] of participantMap) {
|
|
|
|
const id = `${member.userId}:${deviceId}`;
|
|
|
|
const sfuParticipant = sfuParticipants.find((p) => p.identity === id);
|
|
|
|
|
|
|
|
const hasScreenShare =
|
|
|
|
sfuParticipant?.getTrack(Track.Source.ScreenShare) !== undefined;
|
|
|
|
|
|
|
|
const descriptor = {
|
|
|
|
id,
|
|
|
|
focused: hasScreenShare && !sfuParticipant?.isLocal,
|
|
|
|
local: member.userId == localUserId && deviceId == localDeviceId,
|
|
|
|
data: {
|
|
|
|
member,
|
|
|
|
sfuParticipant,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
tiles.push(descriptor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PosthogAnalytics.instance.eventCallEnded.cacheParticipantCountChanged(
|
|
|
|
tiles.length
|
|
|
|
);
|
|
|
|
|
|
|
|
return tiles;
|
|
|
|
}, [localUserId, localDeviceId, participants, sfuParticipants]);
|
|
|
|
|
|
|
|
return items;
|
|
|
|
}
|