Simplify overly complicated VideoGrid
This commit is contained in:
parent
b11ab01bbe
commit
79018606b2
4 changed files with 139 additions and 149 deletions
|
@ -14,17 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useCallback, useMemo, useRef } from "react";
|
|
||||||
import { usePreventScroll } from "@react-aria/overlays";
|
|
||||||
import useMeasure from "react-use-measure";
|
|
||||||
import { ResizeObserver } from "@juggle/resize-observer";
|
import { ResizeObserver } from "@juggle/resize-observer";
|
||||||
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 classNames from "classnames";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
|
||||||
import { Room, Track } from "livekit-client";
|
|
||||||
import {
|
import {
|
||||||
useLiveKitRoom,
|
useLiveKitRoom,
|
||||||
useLocalParticipant,
|
useLocalParticipant,
|
||||||
|
@ -32,15 +22,19 @@ import {
|
||||||
useToken,
|
useToken,
|
||||||
useTracks,
|
useTracks,
|
||||||
} from "@livekit/components-react";
|
} from "@livekit/components-react";
|
||||||
|
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 type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import styles from "./InCallView.module.css";
|
import { Avatar } from "../Avatar";
|
||||||
import {
|
|
||||||
HangupButton,
|
|
||||||
MicButton,
|
|
||||||
VideoButton,
|
|
||||||
ScreenshareButton,
|
|
||||||
} from "../button";
|
|
||||||
import {
|
import {
|
||||||
Header,
|
Header,
|
||||||
LeftNav,
|
LeftNav,
|
||||||
|
@ -48,27 +42,36 @@ import {
|
||||||
RoomHeaderInfo,
|
RoomHeaderInfo,
|
||||||
VersionMismatchWarning,
|
VersionMismatchWarning,
|
||||||
} from "../Header";
|
} from "../Header";
|
||||||
import { VideoGrid, useVideoGridLayout } from "../video-grid/VideoGrid";
|
|
||||||
import { VideoTileContainer } from "../video-grid/VideoTileContainer";
|
|
||||||
import { GroupCallInspector } from "./GroupCallInspector";
|
|
||||||
import { OverflowMenu } from "./OverflowMenu";
|
|
||||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
|
||||||
import { Avatar } from "../Avatar";
|
|
||||||
import { UserMenuContainer } from "../UserMenuContainer";
|
|
||||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
|
||||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
|
||||||
import { useShowInspector } from "../settings/useSetting";
|
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { PosthogAnalytics } from "../PosthogAnalytics";
|
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||||
import { widget, ElementWidgetActions } from "../widget";
|
|
||||||
import { useJoinRule } from "./useJoinRule";
|
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
import { UserMenuContainer } from "../UserMenuContainer";
|
||||||
import { ParticipantInfo } from "./useGroupCall";
|
import {
|
||||||
import { TileDescriptor } from "../video-grid/TileDescriptor";
|
HangupButton,
|
||||||
import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
MicButton,
|
||||||
|
ScreenshareButton,
|
||||||
|
VideoButton,
|
||||||
|
} from "../button";
|
||||||
import { MediaDevicesState } from "../settings/mediaDevices";
|
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,
|
||||||
|
} 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";
|
||||||
import { MatrixInfo } from "./VideoPreview";
|
import { MatrixInfo } from "./VideoPreview";
|
||||||
|
import { useJoinRule } from "./useJoinRule";
|
||||||
|
import { ParticipantInfo } from "./useGroupCall";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||||
|
@ -228,46 +231,14 @@ export function InCallView({
|
||||||
}
|
}
|
||||||
}, [setLayout]);
|
}, [setLayout]);
|
||||||
|
|
||||||
const sfuParticipants = useParticipants({
|
|
||||||
room: livekitRoom,
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = useMemo(() => {
|
|
||||||
const localUserId = client.getUserId()!;
|
|
||||||
const localDeviceId = client.getDeviceId()!;
|
|
||||||
|
|
||||||
// One tile for each participant, to start with (we want a tile for everyone we
|
|
||||||
// think should be in the call, even if we don't have a call feed for them yet)
|
|
||||||
const tileDescriptors: TileDescriptor[] = [];
|
|
||||||
for (const [member, participantMap] of participants) {
|
|
||||||
for (const [deviceId, { presenter }] of participantMap) {
|
|
||||||
const id = `${member.userId}:${deviceId}`;
|
|
||||||
const sfuParticipant = sfuParticipants.find((p) => p.identity === id);
|
|
||||||
|
|
||||||
const hasScreenShare =
|
|
||||||
sfuParticipant?.getTrack(Track.Source.ScreenShare) !== undefined;
|
|
||||||
|
|
||||||
tileDescriptors.push({
|
|
||||||
id,
|
|
||||||
member,
|
|
||||||
focused: hasScreenShare && !sfuParticipant?.isLocal,
|
|
||||||
isLocal: member.userId == localUserId && deviceId == localDeviceId,
|
|
||||||
presenter,
|
|
||||||
sfuParticipant,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheParticipantCountChanged(
|
|
||||||
tileDescriptors.length
|
|
||||||
);
|
|
||||||
|
|
||||||
return tileDescriptors;
|
|
||||||
}, [client, participants, sfuParticipants]);
|
|
||||||
|
|
||||||
const reducedControls = boundsValid && bounds.width <= 400;
|
const reducedControls = boundsValid && bounds.width <= 400;
|
||||||
const noControls = reducedControls && bounds.height <= 400;
|
const noControls = reducedControls && bounds.height <= 400;
|
||||||
|
|
||||||
|
const items = useParticipantTiles(livekitRoom, participants, {
|
||||||
|
userId: client.getUserId()!,
|
||||||
|
deviceId: client.getDeviceId()!,
|
||||||
|
});
|
||||||
|
|
||||||
// The maximised participant: the focused (active) participant if the
|
// The maximised participant: the focused (active) participant if the
|
||||||
// window is too small to show everyone.
|
// window is too small to show everyone.
|
||||||
const maximisedParticipant = useMemo(
|
const maximisedParticipant = useMemo(
|
||||||
|
@ -309,7 +280,7 @@ export function InCallView({
|
||||||
height={bounds.height}
|
height={bounds.height}
|
||||||
width={bounds.width}
|
width={bounds.width}
|
||||||
key={maximisedParticipant.id}
|
key={maximisedParticipant.id}
|
||||||
item={maximisedParticipant}
|
item={maximisedParticipant.data}
|
||||||
getAvatar={renderAvatar}
|
getAvatar={renderAvatar}
|
||||||
maximised={Boolean(maximisedParticipant)}
|
maximised={Boolean(maximisedParticipant)}
|
||||||
/>
|
/>
|
||||||
|
@ -322,19 +293,12 @@ export function InCallView({
|
||||||
layout={layout}
|
layout={layout}
|
||||||
disableAnimations={prefersReducedMotion || isSafari}
|
disableAnimations={prefersReducedMotion || isSafari}
|
||||||
>
|
>
|
||||||
{({
|
{(child) => (
|
||||||
item,
|
|
||||||
...rest
|
|
||||||
}: {
|
|
||||||
item: TileDescriptor;
|
|
||||||
[x: string]: unknown;
|
|
||||||
}) => (
|
|
||||||
<VideoTileContainer
|
<VideoTileContainer
|
||||||
key={item.id}
|
|
||||||
item={item}
|
|
||||||
getAvatar={renderAvatar}
|
getAvatar={renderAvatar}
|
||||||
maximised={false}
|
maximised={false}
|
||||||
{...rest}
|
item={child.data}
|
||||||
|
{...child}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</VideoGrid>
|
</VideoGrid>
|
||||||
|
@ -424,3 +388,53 @@ export function InCallView({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2022 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.
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { RoomMember } from "matrix-js-sdk";
|
|
||||||
import { LocalParticipant, RemoteParticipant } from "livekit-client";
|
|
||||||
|
|
||||||
// Represents something that should get a tile on the layout,
|
|
||||||
// ie. a user's video feed or a screen share feed.
|
|
||||||
export interface TileDescriptor {
|
|
||||||
id: string;
|
|
||||||
member: RoomMember;
|
|
||||||
focused: boolean;
|
|
||||||
presenter: boolean;
|
|
||||||
isLocal?: boolean;
|
|
||||||
sfuParticipant?: LocalParticipant | RemoteParticipant;
|
|
||||||
}
|
|
|
@ -23,7 +23,6 @@ import { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/src/typ
|
||||||
|
|
||||||
import styles from "./VideoGrid.module.css";
|
import styles from "./VideoGrid.module.css";
|
||||||
import { Layout } from "../room/GridLayoutMenu";
|
import { Layout } from "../room/GridLayoutMenu";
|
||||||
import { TileDescriptor } from "./TileDescriptor";
|
|
||||||
|
|
||||||
interface TilePosition {
|
interface TilePosition {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -33,13 +32,12 @@ interface TilePosition {
|
||||||
zIndex: number;
|
zIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Tile {
|
interface Tile<T> {
|
||||||
key: Key;
|
key: Key;
|
||||||
order: number;
|
order: number;
|
||||||
item: TileDescriptor;
|
item: TileDescriptor<T>;
|
||||||
remove: boolean;
|
remove: boolean;
|
||||||
focused: boolean;
|
focused: boolean;
|
||||||
presenter: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayoutDirection = "vertical" | "horizontal";
|
type LayoutDirection = "vertical" | "horizontal";
|
||||||
|
@ -112,7 +110,6 @@ const getPipGap = (gridAspectRatio: number, gridWidth: number): number =>
|
||||||
function getTilePositions(
|
function getTilePositions(
|
||||||
tileCount: number,
|
tileCount: number,
|
||||||
focusedTileCount: number,
|
focusedTileCount: number,
|
||||||
hasPresenter: boolean,
|
|
||||||
gridWidth: number,
|
gridWidth: number,
|
||||||
gridHeight: number,
|
gridHeight: number,
|
||||||
pipXRatio: number,
|
pipXRatio: number,
|
||||||
|
@ -120,7 +117,7 @@ function getTilePositions(
|
||||||
layout: Layout
|
layout: Layout
|
||||||
): TilePosition[] {
|
): TilePosition[] {
|
||||||
if (layout === "freedom") {
|
if (layout === "freedom") {
|
||||||
if (tileCount === 2 && !hasPresenter && focusedTileCount === 0) {
|
if (tileCount === 2 && focusedTileCount === 0) {
|
||||||
return getOneOnOneLayoutTilePositions(
|
return getOneOnOneLayoutTilePositions(
|
||||||
gridWidth,
|
gridWidth,
|
||||||
gridHeight,
|
gridHeight,
|
||||||
|
@ -654,7 +651,7 @@ function getSubGridPositions(
|
||||||
|
|
||||||
// Sets the 'order' property on tiles based on the layout param and
|
// Sets the 'order' property on tiles based on the layout param and
|
||||||
// other properties of the tiles, eg. 'focused' and 'presenter'
|
// other properties of the tiles, eg. 'focused' and 'presenter'
|
||||||
function reorderTiles(tiles: Tile[], layout: Layout) {
|
function reorderTiles<T>(tiles: Tile<T>[], layout: Layout) {
|
||||||
// We use a special layout for 1:1 to always put the local tile first.
|
// We use a special layout for 1:1 to always put the local tile first.
|
||||||
// We only do this if there are two tiles (obviously) and exactly one
|
// We only do this if there are two tiles (obviously) and exactly one
|
||||||
// of them is local: during startup we can have tiles from other users
|
// of them is local: during startup we can have tiles from other users
|
||||||
|
@ -664,16 +661,16 @@ function reorderTiles(tiles: Tile[], layout: Layout) {
|
||||||
if (
|
if (
|
||||||
layout === "freedom" &&
|
layout === "freedom" &&
|
||||||
tiles.length === 2 &&
|
tiles.length === 2 &&
|
||||||
tiles.filter((t) => t.item.isLocal).length === 1 &&
|
tiles.filter((t) => t.item.local).length === 1 &&
|
||||||
!tiles.some((t) => t.presenter || t.focused)
|
!tiles.some((t) => t.focused)
|
||||||
) {
|
) {
|
||||||
// 1:1 layout
|
// 1:1 layout
|
||||||
tiles.forEach((tile) => (tile.order = tile.item.isLocal ? 0 : 1));
|
tiles.forEach((tile) => (tile.order = tile.item.local ? 0 : 1));
|
||||||
} else {
|
} else {
|
||||||
const focusedTiles: Tile[] = [];
|
const focusedTiles: Tile<T>[] = [];
|
||||||
const otherTiles: Tile[] = [];
|
const otherTiles: Tile<T>[] = [];
|
||||||
|
|
||||||
const orderedTiles: Tile[] = new Array(tiles.length);
|
const orderedTiles: Tile<T>[] = new Array(tiles.length);
|
||||||
tiles.forEach((tile) => (orderedTiles[tile.order] = tile));
|
tiles.forEach((tile) => (orderedTiles[tile.order] = tile));
|
||||||
|
|
||||||
orderedTiles.forEach((tile) =>
|
orderedTiles.forEach((tile) =>
|
||||||
|
@ -692,8 +689,9 @@ interface DragTileData {
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChildrenProperties extends ReactDOMAttributes {
|
interface ChildrenProperties<T> extends ReactDOMAttributes {
|
||||||
key: Key;
|
key: Key;
|
||||||
|
data: T;
|
||||||
style: {
|
style: {
|
||||||
scale: SpringValue<number>;
|
scale: SpringValue<number>;
|
||||||
opacity: SpringValue<number>;
|
opacity: SpringValue<number>;
|
||||||
|
@ -701,29 +699,36 @@ interface ChildrenProperties extends ReactDOMAttributes {
|
||||||
};
|
};
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
item: TileDescriptor;
|
|
||||||
[index: string]: unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VideoGridProps {
|
interface VideoGridProps<T> {
|
||||||
items: TileDescriptor[];
|
items: TileDescriptor<T>[];
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
disableAnimations?: boolean;
|
disableAnimations: boolean;
|
||||||
children: (props: ChildrenProperties) => React.ReactNode;
|
children: (props: ChildrenProperties<T>) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoGrid({
|
// Represents something that should get a tile on the layout,
|
||||||
|
// ie. a user's video feed or a screen share feed.
|
||||||
|
export interface TileDescriptor<T> {
|
||||||
|
id: string;
|
||||||
|
focused: boolean;
|
||||||
|
local: boolean;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoGrid<T>({
|
||||||
items,
|
items,
|
||||||
layout,
|
layout,
|
||||||
disableAnimations,
|
disableAnimations,
|
||||||
children,
|
children: createChild,
|
||||||
}: VideoGridProps) {
|
}: VideoGridProps<T>) {
|
||||||
// Place the PiP in the bottom right corner by default
|
// Place the PiP in the bottom right corner by default
|
||||||
const [pipXRatio, setPipXRatio] = useState(1);
|
const [pipXRatio, setPipXRatio] = useState(1);
|
||||||
const [pipYRatio, setPipYRatio] = useState(1);
|
const [pipYRatio, setPipYRatio] = useState(1);
|
||||||
|
|
||||||
const [{ tiles, tilePositions }, setTileState] = useState<{
|
const [{ tiles, tilePositions }, setTileState] = useState<{
|
||||||
tiles: Tile[];
|
tiles: Tile<T>[];
|
||||||
tilePositions: TilePosition[];
|
tilePositions: TilePosition[];
|
||||||
}>({
|
}>({
|
||||||
tiles: [],
|
tiles: [],
|
||||||
|
@ -739,7 +744,7 @@ export function VideoGrid({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTileState(({ tiles, ...rest }) => {
|
setTileState(({ tiles, ...rest }) => {
|
||||||
const newTiles: Tile[] = [];
|
const newTiles: Tile<T>[] = [];
|
||||||
const removedTileKeys: Set<Key> = new Set();
|
const removedTileKeys: Set<Key> = new Set();
|
||||||
|
|
||||||
for (const tile of tiles) {
|
for (const tile of tiles) {
|
||||||
|
@ -766,7 +771,6 @@ export function VideoGrid({
|
||||||
item,
|
item,
|
||||||
remove,
|
remove,
|
||||||
focused,
|
focused,
|
||||||
presenter: item.presenter,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,13 +785,12 @@ export function VideoGrid({
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newTile: Tile = {
|
const newTile: Tile<T> = {
|
||||||
key: item.id,
|
key: item.id,
|
||||||
order: existingTile?.order ?? newTiles.length,
|
order: existingTile?.order ?? newTiles.length,
|
||||||
item,
|
item,
|
||||||
remove: false,
|
remove: false,
|
||||||
focused: layout === "spotlight" && item.focused,
|
focused: layout === "spotlight" && item.focused,
|
||||||
presenter: item.presenter,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existingTile) {
|
if (existingTile) {
|
||||||
|
@ -808,7 +811,7 @@ export function VideoGrid({
|
||||||
}
|
}
|
||||||
|
|
||||||
setTileState(({ tiles, ...rest }) => {
|
setTileState(({ tiles, ...rest }) => {
|
||||||
const newTiles: Tile[] = tiles
|
const newTiles: Tile<T>[] = tiles
|
||||||
.filter((tile) => !removedTileKeys.has(tile.key))
|
.filter((tile) => !removedTileKeys.has(tile.key))
|
||||||
.map((tile) => ({ ...tile })); // clone before reordering
|
.map((tile) => ({ ...tile })); // clone before reordering
|
||||||
reorderTiles(newTiles, layout);
|
reorderTiles(newTiles, layout);
|
||||||
|
@ -824,7 +827,6 @@ export function VideoGrid({
|
||||||
tilePositions: getTilePositions(
|
tilePositions: getTilePositions(
|
||||||
newTiles.length,
|
newTiles.length,
|
||||||
focusedTileCount,
|
focusedTileCount,
|
||||||
newTiles.some((t) => t.presenter),
|
|
||||||
gridBounds.width,
|
gridBounds.width,
|
||||||
gridBounds.height,
|
gridBounds.height,
|
||||||
pipXRatio,
|
pipXRatio,
|
||||||
|
@ -849,7 +851,6 @@ export function VideoGrid({
|
||||||
tilePositions: getTilePositions(
|
tilePositions: getTilePositions(
|
||||||
newTiles.length,
|
newTiles.length,
|
||||||
focusedTileCount,
|
focusedTileCount,
|
||||||
newTiles.some((t) => t.presenter),
|
|
||||||
gridBounds.width,
|
gridBounds.width,
|
||||||
gridBounds.height,
|
gridBounds.height,
|
||||||
pipXRatio,
|
pipXRatio,
|
||||||
|
@ -863,7 +864,7 @@ export function VideoGrid({
|
||||||
const tilePositionsValid = useRef(false);
|
const tilePositionsValid = useRef(false);
|
||||||
|
|
||||||
const animate = useCallback(
|
const animate = useCallback(
|
||||||
(tiles: Tile[]) => {
|
(tiles: Tile<T>[]) => {
|
||||||
// Whether the tile positions were valid at the time of the previous
|
// Whether the tile positions were valid at the time of the previous
|
||||||
// animation
|
// animation
|
||||||
const tilePositionsWereValid = tilePositionsValid.current;
|
const tilePositionsWereValid = tilePositionsValid.current;
|
||||||
|
@ -1005,7 +1006,6 @@ export function VideoGrid({
|
||||||
tilePositions: getTilePositions(
|
tilePositions: getTilePositions(
|
||||||
newTiles.length,
|
newTiles.length,
|
||||||
focusedTileCount,
|
focusedTileCount,
|
||||||
newTiles.some((t) => t.presenter),
|
|
||||||
gridBounds.width,
|
gridBounds.width,
|
||||||
gridBounds.height,
|
gridBounds.height,
|
||||||
pipXRatio,
|
pipXRatio,
|
||||||
|
@ -1037,9 +1037,9 @@ export function VideoGrid({
|
||||||
|
|
||||||
let newTiles = tiles;
|
let newTiles = tiles;
|
||||||
|
|
||||||
if (tiles.length === 2 && !tiles.some((t) => t.presenter || t.focused)) {
|
if (tiles.length === 2 && !tiles.some((t) => t.focused)) {
|
||||||
// We're in 1:1 mode, so only the local tile should be draggable
|
// We're in 1:1 mode, so only the local tile should be draggable
|
||||||
if (!dragTile.item.isLocal) return;
|
if (!dragTile.item.local) return;
|
||||||
|
|
||||||
// Position should only update on the very last event, to avoid
|
// Position should only update on the very last event, to avoid
|
||||||
// compounding the offset on every drag event
|
// compounding the offset on every drag event
|
||||||
|
@ -1179,9 +1179,10 @@ export function VideoGrid({
|
||||||
const tile = tiles[i];
|
const tile = tiles[i];
|
||||||
const tilePosition = tilePositions[tile.order];
|
const tilePosition = tilePositions[tile.order];
|
||||||
|
|
||||||
return children({
|
return createChild({
|
||||||
...bindTile(tile.key),
|
...bindTile(tile.key),
|
||||||
key: tile.key,
|
key: tile.key,
|
||||||
|
data: tile.item.data,
|
||||||
style: {
|
style: {
|
||||||
boxShadow: shadow.to(
|
boxShadow: shadow.to(
|
||||||
(s) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px 0px`
|
(s) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px 0px`
|
||||||
|
@ -1190,7 +1191,6 @@ export function VideoGrid({
|
||||||
},
|
},
|
||||||
width: tilePosition.width,
|
width: tilePosition.width,
|
||||||
height: tilePosition.height,
|
height: tilePosition.height,
|
||||||
item: tile.item,
|
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,13 +16,18 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { LocalParticipant, RemoteParticipant } from "livekit-client";
|
||||||
|
|
||||||
import { useRoomMemberName } from "./useRoomMemberName";
|
import { useRoomMemberName } from "./useRoomMemberName";
|
||||||
import { VideoTile } from "./VideoTile";
|
import { VideoTile } from "./VideoTile";
|
||||||
import { TileDescriptor } from "./TileDescriptor";
|
|
||||||
|
export interface ItemData {
|
||||||
|
member: RoomMember;
|
||||||
|
sfuParticipant?: LocalParticipant | RemoteParticipant;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: TileDescriptor;
|
item: ItemData;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
getAvatar: (
|
getAvatar: (
|
||||||
|
|
Loading…
Add table
Reference in a new issue