Implement somewhat working drag & drop and improve render memoization
This commit is contained in:
parent
eedf8a6d1b
commit
0915e327e1
6 changed files with 256 additions and 146 deletions
16
src/useMergedRefs.ts
Normal file
16
src/useMergedRefs.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { MutableRefObject, RefCallback, useCallback } from "react";
|
||||||
|
|
||||||
|
export const useMergedRefs = <T>(
|
||||||
|
...refs: (MutableRefObject<T | null> | RefCallback<T | null>)[]
|
||||||
|
): RefCallback<T | null> =>
|
||||||
|
useCallback(
|
||||||
|
(value) =>
|
||||||
|
refs.forEach((ref) => {
|
||||||
|
if (typeof ref === "function") {
|
||||||
|
ref(value);
|
||||||
|
} else {
|
||||||
|
ref.current = value;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
refs
|
||||||
|
);
|
|
@ -1,5 +1,5 @@
|
||||||
import { SpringRef, TransitionFn, useTransition } from "@react-spring/web";
|
import { SpringRef, TransitionFn, useTransition } from "@react-spring/web";
|
||||||
import { useDrag, useScroll } from "@use-gesture/react";
|
import { EventTypes, Handler, useScroll } from "@use-gesture/react";
|
||||||
import React, {
|
import React, {
|
||||||
FC,
|
FC,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
|
@ -15,6 +15,7 @@ import { VideoGridProps as Props } from "./VideoGrid";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
import TinyQueue from "tinyqueue";
|
import TinyQueue from "tinyqueue";
|
||||||
import { zipWith } from "lodash";
|
import { zipWith } from "lodash";
|
||||||
|
import { useMergedRefs } from "../useMergedRefs";
|
||||||
|
|
||||||
interface Cell {
|
interface Cell {
|
||||||
/**
|
/**
|
||||||
|
@ -66,8 +67,10 @@ interface TileSpring {
|
||||||
|
|
||||||
interface DragState {
|
interface DragState {
|
||||||
tileId: string;
|
tileId: string;
|
||||||
x: number;
|
tileX: number;
|
||||||
y: number;
|
tileY: number;
|
||||||
|
cursorX: number;
|
||||||
|
cursorY: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dijkstra = (g: Grid): number[] => {
|
const dijkstra = (g: Grid): number[] => {
|
||||||
|
@ -377,7 +380,10 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [slotGrid, setSlotGrid] = useState<HTMLDivElement | null>(null);
|
const [slotGrid, setSlotGrid] = useState<HTMLDivElement | null>(null);
|
||||||
const [slotGridGeneration, setSlotGridGeneration] = useState(0);
|
const [slotGridGeneration, setSlotGridGeneration] = useState(0);
|
||||||
const [gridRef, gridBounds] = useMeasure();
|
|
||||||
|
const [gridRef1, gridBounds] = useMeasure();
|
||||||
|
const gridRef2 = useRef<HTMLDivElement | null>(null);
|
||||||
|
const gridRef = useMergedRefs(gridRef1, gridRef2);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (slotGrid !== null) {
|
if (slotGrid !== null) {
|
||||||
|
@ -549,23 +555,21 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
};
|
};
|
||||||
}, [grid]);
|
}, [grid]);
|
||||||
|
|
||||||
const animateDraggedTile = (endOfGesture: boolean) =>
|
const animateDraggedTile = (endOfGesture: boolean) => {
|
||||||
springRef.start((_i, controller) => {
|
const { tileId, tileX, tileY, cursorX, cursorY } = dragState.current!;
|
||||||
const { tileId, x, y } = dragState.current!;
|
const tile = tiles.find((t) => t.item.id === tileId)!;
|
||||||
|
|
||||||
// react-spring appears to not update a controller's item as long as the
|
springRef.start((_i, controller) => {
|
||||||
// key remains stable, so we can use it to look up the tile's ID but not
|
|
||||||
// its position
|
|
||||||
if ((controller.item as Tile).item.id === tileId) {
|
if ((controller.item as Tile).item.id === tileId) {
|
||||||
if (endOfGesture) {
|
if (endOfGesture) {
|
||||||
const tile = tiles.find((t) => t.item.id === tileId)!;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scale: 1,
|
scale: 1,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
shadow: 1,
|
shadow: 1,
|
||||||
x: tile.x,
|
x: tile.x,
|
||||||
y: tile.y,
|
y: tile.y,
|
||||||
|
width: tile.width,
|
||||||
|
height: tile.height,
|
||||||
immediate: disableAnimations || ((key) => key === "zIndex"),
|
immediate: disableAnimations || ((key) => key === "zIndex"),
|
||||||
// Allow the tile's position to settle before pushing its
|
// Allow the tile's position to settle before pushing its
|
||||||
// z-index back down
|
// z-index back down
|
||||||
|
@ -576,8 +580,8 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
scale: 1.1,
|
scale: 1.1,
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
shadow: 15,
|
shadow: 15,
|
||||||
x,
|
x: tileX,
|
||||||
y,
|
y: tileY,
|
||||||
immediate:
|
immediate:
|
||||||
disableAnimations ||
|
disableAnimations ||
|
||||||
((key) => key === "zIndex" || key === "x" || key === "y"),
|
((key) => key === "zIndex" || key === "x" || key === "y"),
|
||||||
|
@ -588,41 +592,78 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const bindTile = useDrag(
|
const overTile = tiles.find(
|
||||||
({ tap, args, delta: [dx, dy], last }) => {
|
(t) =>
|
||||||
const tileId = args[0] as string;
|
cursorX >= t.x &&
|
||||||
|
cursorX < t.x + t.width &&
|
||||||
|
cursorY >= t.y &&
|
||||||
|
cursorY < t.y + t.height
|
||||||
|
);
|
||||||
|
if (overTile !== undefined && overTile.item.id !== tileId) {
|
||||||
|
setGrid((g) => ({
|
||||||
|
...g,
|
||||||
|
cells: g.cells.map((c) => {
|
||||||
|
if (c?.item === overTile.item) return { ...c, item: tile.item };
|
||||||
|
if (c?.item === tile.item) return { ...c, item: overTile.item };
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (tap) {
|
const onTileDrag = (
|
||||||
setGrid((g) => cycleTileSize(tileId, g));
|
tileId: string,
|
||||||
} else {
|
{
|
||||||
const tileSpring = springRef.current
|
tap,
|
||||||
.find((c) => (c.item as Tile).item.id === tileId)!
|
initial: [initialX, initialY],
|
||||||
.get();
|
delta: [dx, dy],
|
||||||
|
last,
|
||||||
|
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||||
|
) => {
|
||||||
|
if (tap) {
|
||||||
|
setGrid((g) => cycleTileSize(tileId, g));
|
||||||
|
} else {
|
||||||
|
const tileSpring = springRef.current
|
||||||
|
.find((c) => (c.item as Tile).item.id === tileId)!
|
||||||
|
.get();
|
||||||
|
|
||||||
if (dragState.current === null) {
|
if (dragState.current === null) {
|
||||||
dragState.current = { tileId, x: tileSpring.x, y: tileSpring.y };
|
dragState.current = {
|
||||||
}
|
tileId,
|
||||||
dragState.current.x += dx;
|
tileX: tileSpring.x,
|
||||||
dragState.current.y += dy;
|
tileY: tileSpring.y,
|
||||||
|
cursorX: initialX - gridBounds.x,
|
||||||
animateDraggedTile(last);
|
cursorY: initialY - gridBounds.y + scrollOffset.current,
|
||||||
|
};
|
||||||
if (last) dragState.current = null;
|
|
||||||
}
|
}
|
||||||
},
|
dragState.current.tileX += dx;
|
||||||
{ filterTaps: true, pointer: { buttons: [1] } }
|
dragState.current.tileY += dy;
|
||||||
);
|
dragState.current.cursorX += dx;
|
||||||
|
dragState.current.cursorY += dy;
|
||||||
|
|
||||||
|
animateDraggedTile(last);
|
||||||
|
|
||||||
|
if (last) dragState.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTileDragRef = useRef(onTileDrag);
|
||||||
|
onTileDragRef.current = onTileDrag;
|
||||||
|
|
||||||
const scrollOffset = useRef(0);
|
const scrollOffset = useRef(0);
|
||||||
|
|
||||||
const bindGrid = useScroll(({ xy: [, y], delta: [, dy] }) => {
|
useScroll(
|
||||||
scrollOffset.current = y;
|
({ xy: [, y], delta: [, dy] }) => {
|
||||||
|
scrollOffset.current = y;
|
||||||
|
|
||||||
if (dragState.current !== null) {
|
if (dragState.current !== null) {
|
||||||
dragState.current.y += dy;
|
dragState.current.tileY += dy;
|
||||||
animateDraggedTile(false);
|
dragState.current.cursorY += dy;
|
||||||
}
|
animateDraggedTile(false);
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
{ target: gridRef2 }
|
||||||
|
);
|
||||||
|
|
||||||
const slots = useMemo(() => {
|
const slots = useMemo(() => {
|
||||||
const slots = new Array<ReactNode>(items.length);
|
const slots = new Array<ReactNode>(items.length);
|
||||||
|
@ -639,7 +680,7 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...bindGrid()} ref={gridRef} className={styles.grid}>
|
<div ref={gridRef} className={styles.grid}>
|
||||||
<div
|
<div
|
||||||
style={slotGridStyle}
|
style={slotGridStyle}
|
||||||
ref={setSlotGrid}
|
ref={setSlotGrid}
|
||||||
|
@ -648,21 +689,14 @@ export const NewVideoGrid: FC<Props> = ({
|
||||||
>
|
>
|
||||||
{slots}
|
{slots}
|
||||||
</div>
|
</div>
|
||||||
{tileTransitions(({ shadow, width, height, ...style }, tile) =>
|
{tileTransitions((style, tile) =>
|
||||||
children({
|
children({
|
||||||
...bindTile(tile.item.id),
|
...style,
|
||||||
key: tile.item.id,
|
key: tile.item.id,
|
||||||
style: {
|
targetWidth: tile.width,
|
||||||
boxShadow: shadow.to(
|
targetHeight: tile.height,
|
||||||
(s) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px 0px`
|
|
||||||
),
|
|
||||||
"--tileWidth": width.to((w) => `${w}px`),
|
|
||||||
"--tileHeight": height.to((h) => `${h}px`),
|
|
||||||
...style,
|
|
||||||
},
|
|
||||||
width: tile.width,
|
|
||||||
height: tile.height,
|
|
||||||
item: tile.item,
|
item: tile.item,
|
||||||
|
onDragRef: onTileDragRef,
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -694,14 +694,17 @@ interface DragTileData {
|
||||||
|
|
||||||
interface ChildrenProperties extends ReactDOMAttributes {
|
interface ChildrenProperties extends ReactDOMAttributes {
|
||||||
key: Key;
|
key: Key;
|
||||||
style: {
|
targetWidth: number;
|
||||||
scale: SpringValue<number>;
|
targetHeight: number;
|
||||||
opacity: SpringValue<number>;
|
|
||||||
boxShadow: Interpolation<number, string>;
|
|
||||||
};
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
item: TileDescriptor;
|
item: TileDescriptor;
|
||||||
|
opacity: SpringValue<number>;
|
||||||
|
scale: SpringValue<number>;
|
||||||
|
shadow: SpringValue<number>;
|
||||||
|
zIndex: SpringValue<number>;
|
||||||
|
x: SpringValue<number>;
|
||||||
|
y: SpringValue<number>;
|
||||||
|
width: SpringValue<number>;
|
||||||
|
height: SpringValue<number>;
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { forwardRef } from "react";
|
import React, { ForwardedRef, forwardRef } from "react";
|
||||||
import { animated } from "@react-spring/web";
|
import { animated, SpringValue } from "@react-spring/web";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
@ -44,9 +44,17 @@ interface Props {
|
||||||
showOptions?: boolean;
|
showOptions?: boolean;
|
||||||
isLocal?: boolean;
|
isLocal?: boolean;
|
||||||
disableSpeakingIndicator?: boolean;
|
disableSpeakingIndicator?: boolean;
|
||||||
|
opacity: SpringValue<number>;
|
||||||
|
scale: SpringValue<number>;
|
||||||
|
shadow: SpringValue<number>;
|
||||||
|
zIndex: SpringValue<number>;
|
||||||
|
x: SpringValue<number>;
|
||||||
|
y: SpringValue<number>;
|
||||||
|
width: SpringValue<number>;
|
||||||
|
height: SpringValue<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
export const VideoTile = forwardRef<HTMLElement, Props>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
name,
|
name,
|
||||||
|
@ -68,6 +76,14 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||||
isLocal,
|
isLocal,
|
||||||
// TODO: disableSpeakingIndicator is not used atm.
|
// TODO: disableSpeakingIndicator is not used atm.
|
||||||
disableSpeakingIndicator,
|
disableSpeakingIndicator,
|
||||||
|
opacity,
|
||||||
|
scale,
|
||||||
|
shadow,
|
||||||
|
zIndex,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
|
@ -122,7 +138,19 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||||
[styles.screenshare]: screenshare,
|
[styles.screenshare]: screenshare,
|
||||||
[styles.maximised]: maximised,
|
[styles.maximised]: maximised,
|
||||||
})}
|
})}
|
||||||
ref={ref}
|
style={{
|
||||||
|
opacity,
|
||||||
|
scale,
|
||||||
|
boxShadow: shadow.to(
|
||||||
|
(s) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px 0px`
|
||||||
|
),
|
||||||
|
zIndex,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
"--tileWidth": width.to((w) => `${w}px`),
|
||||||
|
"--tileHeight": height.to((h) => `${h}px`),
|
||||||
|
}}
|
||||||
|
ref={ref as ForwardedRef<HTMLDivElement>}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{toolbarButtons.length > 0 && !maximised && (
|
{toolbarButtons.length > 0 && !maximised && (
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
|
||||||
import React from "react";
|
import React, { FC, memo, RefObject } from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
|
@ -26,11 +26,13 @@ import { VideoTile } from "./VideoTile";
|
||||||
import { VideoTileSettingsModal } from "./VideoTileSettingsModal";
|
import { VideoTileSettingsModal } from "./VideoTileSettingsModal";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { TileDescriptor } from "./TileDescriptor";
|
import { TileDescriptor } from "./TileDescriptor";
|
||||||
|
import { SpringValue } from "@react-spring/web";
|
||||||
|
import { EventTypes, Handler, useDrag } from "@use-gesture/react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: TileDescriptor;
|
item: TileDescriptor;
|
||||||
width?: number;
|
targetWidth: number;
|
||||||
height?: number;
|
targetHeight: number;
|
||||||
getAvatar: (
|
getAvatar: (
|
||||||
roomMember: RoomMember,
|
roomMember: RoomMember,
|
||||||
width: number,
|
width: number,
|
||||||
|
@ -42,86 +44,113 @@ interface Props {
|
||||||
maximised: boolean;
|
maximised: boolean;
|
||||||
fullscreen: boolean;
|
fullscreen: boolean;
|
||||||
onFullscreen: (item: TileDescriptor) => void;
|
onFullscreen: (item: TileDescriptor) => void;
|
||||||
|
opacity: SpringValue<number>;
|
||||||
|
scale: SpringValue<number>;
|
||||||
|
shadow: SpringValue<number>;
|
||||||
|
zIndex: SpringValue<number>;
|
||||||
|
x: SpringValue<number>;
|
||||||
|
y: SpringValue<number>;
|
||||||
|
width: SpringValue<number>;
|
||||||
|
height: SpringValue<number>;
|
||||||
|
onDragRef: RefObject<
|
||||||
|
(
|
||||||
|
tileId: string,
|
||||||
|
state: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||||
|
) => void
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoTileContainer({
|
export const VideoTileContainer: FC<Props> = memo(
|
||||||
item,
|
({
|
||||||
width,
|
item,
|
||||||
height,
|
targetWidth,
|
||||||
getAvatar,
|
targetHeight,
|
||||||
audioContext,
|
getAvatar,
|
||||||
audioDestination,
|
|
||||||
disableSpeakingIndicator,
|
|
||||||
maximised,
|
|
||||||
fullscreen,
|
|
||||||
onFullscreen,
|
|
||||||
...rest
|
|
||||||
}: Props) {
|
|
||||||
const {
|
|
||||||
isLocal,
|
|
||||||
audioMuted,
|
|
||||||
videoMuted,
|
|
||||||
localVolume,
|
|
||||||
hasAudio,
|
|
||||||
speaking,
|
|
||||||
stream,
|
|
||||||
purpose,
|
|
||||||
} = useCallFeed(item.callFeed);
|
|
||||||
const { rawDisplayName } = useRoomMemberName(item.member);
|
|
||||||
const [tileRef, mediaRef] = useSpatialMediaStream(
|
|
||||||
stream ?? null,
|
|
||||||
audioContext,
|
audioContext,
|
||||||
audioDestination,
|
audioDestination,
|
||||||
localVolume,
|
disableSpeakingIndicator,
|
||||||
// The feed is muted if it's local audio (because we don't want our own audio,
|
maximised,
|
||||||
// but it's a hook and we can't call it conditionally so we're stuck with it)
|
fullscreen,
|
||||||
// or if there's a maximised feed in which case we always render audio via audio
|
onFullscreen,
|
||||||
// elements because we wire it up at the video tile container level and only one
|
onDragRef,
|
||||||
// video tile container is displayed.
|
...rest
|
||||||
isLocal || maximised
|
}) => {
|
||||||
);
|
const {
|
||||||
const {
|
isLocal,
|
||||||
modalState: videoTileSettingsModalState,
|
audioMuted,
|
||||||
modalProps: videoTileSettingsModalProps,
|
videoMuted,
|
||||||
} = useModalTriggerState();
|
localVolume,
|
||||||
const onOptionsPress = () => {
|
hasAudio,
|
||||||
videoTileSettingsModalState.open();
|
speaking,
|
||||||
};
|
stream,
|
||||||
|
purpose,
|
||||||
|
} = useCallFeed(item.callFeed);
|
||||||
|
const { rawDisplayName } = useRoomMemberName(item.member);
|
||||||
|
|
||||||
const onFullscreenCallback = useCallback(() => {
|
const [tileRef, mediaRef] = useSpatialMediaStream(
|
||||||
onFullscreen(item);
|
stream ?? null,
|
||||||
}, [onFullscreen, item]);
|
audioContext,
|
||||||
|
audioDestination,
|
||||||
|
localVolume,
|
||||||
|
// The feed is muted if it's local audio (because we don't want our own audio,
|
||||||
|
// but it's a hook and we can't call it conditionally so we're stuck with it)
|
||||||
|
// or if there's a maximised feed in which case we always render audio via audio
|
||||||
|
// elements because we wire it up at the video tile container level and only one
|
||||||
|
// video tile container is displayed.
|
||||||
|
isLocal || maximised
|
||||||
|
);
|
||||||
|
|
||||||
// Firefox doesn't respect the disablePictureInPicture attribute
|
useDrag((state) => onDragRef.current!(item.id, state), {
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
|
target: tileRef,
|
||||||
|
filterTaps: true,
|
||||||
|
pointer: { buttons: [1] },
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
const {
|
||||||
<>
|
modalState: videoTileSettingsModalState,
|
||||||
<VideoTile
|
modalProps: videoTileSettingsModalProps,
|
||||||
isLocal={isLocal}
|
} = useModalTriggerState();
|
||||||
speaking={speaking && !disableSpeakingIndicator}
|
const onOptionsPress = () => {
|
||||||
audioMuted={audioMuted}
|
videoTileSettingsModalState.open();
|
||||||
videoMuted={videoMuted}
|
};
|
||||||
screenshare={purpose === SDPStreamMetadataPurpose.Screenshare}
|
|
||||||
name={rawDisplayName}
|
const onFullscreenCallback = useCallback(() => {
|
||||||
connectionState={item.connectionState}
|
onFullscreen(item);
|
||||||
ref={tileRef}
|
}, [onFullscreen, item]);
|
||||||
mediaRef={mediaRef}
|
|
||||||
avatar={getAvatar && getAvatar(item.member, width, height)}
|
// Firefox doesn't respect the disablePictureInPicture attribute
|
||||||
onOptionsPress={onOptionsPress}
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
|
||||||
localVolume={localVolume}
|
|
||||||
hasAudio={hasAudio}
|
return (
|
||||||
maximised={maximised}
|
<>
|
||||||
fullscreen={fullscreen}
|
<VideoTile
|
||||||
onFullscreen={onFullscreenCallback}
|
isLocal={isLocal}
|
||||||
{...rest}
|
speaking={speaking && !disableSpeakingIndicator}
|
||||||
/>
|
audioMuted={audioMuted}
|
||||||
{videoTileSettingsModalState.isOpen && !maximised && item.callFeed && (
|
videoMuted={videoMuted}
|
||||||
<VideoTileSettingsModal
|
screenshare={purpose === SDPStreamMetadataPurpose.Screenshare}
|
||||||
{...videoTileSettingsModalProps}
|
name={rawDisplayName}
|
||||||
feed={item.callFeed}
|
connectionState={item.connectionState}
|
||||||
|
ref={tileRef}
|
||||||
|
mediaRef={mediaRef}
|
||||||
|
avatar={
|
||||||
|
getAvatar && getAvatar(item.member, targetWidth, targetHeight)
|
||||||
|
}
|
||||||
|
onOptionsPress={onOptionsPress}
|
||||||
|
localVolume={localVolume}
|
||||||
|
hasAudio={hasAudio}
|
||||||
|
maximised={maximised}
|
||||||
|
fullscreen={fullscreen}
|
||||||
|
onFullscreen={onFullscreenCallback}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)}
|
{videoTileSettingsModalState.isOpen && !maximised && item.callFeed && (
|
||||||
</>
|
<VideoTileSettingsModal
|
||||||
);
|
{...videoTileSettingsModalProps}
|
||||||
}
|
feed={item.callFeed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -158,8 +158,8 @@ export const useSpatialMediaStream = (
|
||||||
audioDestination: AudioNode,
|
audioDestination: AudioNode,
|
||||||
localVolume: number,
|
localVolume: number,
|
||||||
mute = false
|
mute = false
|
||||||
): [RefObject<HTMLDivElement>, RefObject<MediaElement>] => {
|
): [RefObject<HTMLElement>, RefObject<MediaElement>] => {
|
||||||
const tileRef = useRef<HTMLDivElement | null>(null);
|
const tileRef = useRef<HTMLElement | null>(null);
|
||||||
const [spatialAudio] = useSpatialAudio();
|
const [spatialAudio] = useSpatialAudio();
|
||||||
|
|
||||||
// This media stream is only used for the video - the audio goes via the audio
|
// This media stream is only used for the video - the audio goes via the audio
|
||||||
|
|
Loading…
Add table
Reference in a new issue