From 468fdc93b8e785b5259511cc5d9c724ba7009828 Mon Sep 17 00:00:00 2001 From: Thomas Frivold Date: Fri, 26 Jan 2024 15:08:51 +0100 Subject: [PATCH] Add correct aspect ratio for each video --- src/lib/react/Grid/index.tsx | 112 +++++++++++++++++++---------------- src/lib/react/VideoView.tsx | 20 +++++-- 2 files changed, 76 insertions(+), 56 deletions(-) diff --git a/src/lib/react/Grid/index.tsx b/src/lib/react/Grid/index.tsx index 1206a2e1..1eebba76 100644 --- a/src/lib/react/Grid/index.tsx +++ b/src/lib/react/Grid/index.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { LocalParticipant, RemoteParticipant, VideoView } from ".."; import { calculateLayout } from "./helpers/stageLayout"; -import { makeBounds, makeFrame } from "./helpers/layout"; +import { Bounds, Frame, Origin, makeFrame } from "./helpers/layout"; import { makeVideoCellView } from "./helpers/cellView"; import debounce from "../../../lib/utils/debounce"; import { RoomConnectionRef } from "../useRoomConnection/types"; @@ -12,11 +12,20 @@ function GridVideoCellView({ render, onSetAspectRatio, }: { - cell: { clientId: string; bounds: { width: number; height: number }; origin: { top: number; left: number } }; + cell: { aspectRatio: number; clientId: string; bounds: Bounds; origin: Origin }; participant: RemoteParticipant | LocalParticipant; render?: () => React.ReactNode; onSetAspectRatio: ({ aspectRatio }: { aspectRatio: number }) => void; }) { + const handleAspectRatioChange = React.useCallback( + ({ ar }: { ar: number }) => { + if (ar !== cell.aspectRatio) { + onSetAspectRatio({ aspectRatio: ar }); + } + }, + [cell.aspectRatio, onSetAspectRatio] + ); + return (
+ handleAspectRatioChange({ ar: aspectRatio })} + /> ) : null}
); @@ -43,7 +55,7 @@ interface GridProps { cell, participant, }: { - cell: { clientId: string; bounds: { width: number; height: number }; origin: { top: number; left: number } }; + cell: { clientId: string; bounds: Bounds; origin: Origin }; participant: RemoteParticipant | LocalParticipant; }) => React.ReactNode; videoGridGap?: number; @@ -52,61 +64,24 @@ interface GridProps { function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }: GridProps) { const { remoteParticipants, localParticipant } = roomConnection.state; const gridRef = React.useRef(null); - const [videos, setVideos] = React.useState[]>([]); - const [stageLayout, setStageLayout] = React.useState | null>(null); + const [containerFrame, setContainerFrame] = React.useState(null); const [aspectRatios, setAspectRatios] = React.useState<{ clientId: string; aspectRatio: number }[]>([]); + // Calculate container frame on resize React.useEffect(() => { - setVideos( - [localParticipant, ...remoteParticipants].map((participant) => - makeVideoCellView({ - aspectRatio: 16 / 9, - avatarSize: 0, - cellPaddings: 10, - client: participant, - }) - ) - ); - }, [remoteParticipants, localParticipant]); - - React.useEffect(() => { - setVideos((prev) => { - return prev.map((video) => { - const aspectRatio = aspectRatios.find((item) => item.clientId === video.clientId)?.aspectRatio; - - if (aspectRatio) { - return { ...video, aspectRatio }; - } - - return video; - }); - }); - }, [aspectRatios]); - - React.useEffect(() => { - if (!gridRef.current || !videos.length) { + if (!gridRef.current) { return; } const resizeObserver = new ResizeObserver( debounce( () => { - setStageLayout(() => { - return calculateLayout({ - frame: makeFrame({ - width: gridRef.current?.clientWidth, - height: gridRef.current?.clientHeight, - }), - gridGap: 0, - isConstrained: false, - roomBounds: makeBounds({ - width: gridRef.current?.clientWidth, - height: gridRef.current?.clientHeight, - }), - videos, - videoGridGap, - }); - }); + setContainerFrame( + makeFrame({ + width: gridRef.current?.clientWidth, + height: gridRef.current?.clientHeight, + }) + ); }, { delay: 60 } ) @@ -116,7 +91,40 @@ function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }: GridProps return () => { resizeObserver.disconnect(); }; - }, [videos]); + }, []); + + // Merge local and remote participants + const participants = React.useMemo(() => { + return [...(localParticipant ? [localParticipant] : []), ...remoteParticipants]; + }, [remoteParticipants, localParticipant]); + + // Make video cells + const videoCells = React.useMemo(() => { + return participants.map((participant) => { + const aspectRatio = aspectRatios.find((item) => item.clientId === participant?.id)?.aspectRatio; + + return makeVideoCellView({ + aspectRatio: aspectRatio ?? 16 / 9, + avatarSize: 0, + cellPaddings: 10, + client: participant, + }); + }); + }, [participants, aspectRatios]); + + // Calculate stage layout + const stageLayout = React.useMemo(() => { + if (!containerFrame) return null; + + return calculateLayout({ + frame: containerFrame, + gridGap: 0, + isConstrained: false, + roomBounds: containerFrame.bounds, + videos: videoCells, + videoGridGap, + }); + }, [containerFrame, videoCells, videoGridGap]); return (
- {[localParticipant, ...remoteParticipants].map((participant, i) => { + {participants.map((participant, i) => { const cell = stageLayout?.videoGrid.cells[i]; if (!cell || !participant || !participant.stream || !cell.clientId) return null; diff --git a/src/lib/react/VideoView.tsx b/src/lib/react/VideoView.tsx index 072b8644..ffc3b5c3 100644 --- a/src/lib/react/VideoView.tsx +++ b/src/lib/react/VideoView.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from "react"; +import * as React from "react"; import debounce from "../utils/debounce"; interface VideoViewSelfProps { @@ -14,9 +14,21 @@ type VideoViewProps = VideoViewSelfProps & React.DetailedHTMLProps, HTMLVideoElement>; export default ({ muted, mirror = false, stream, onResize, onSetAspectRatio, ...rest }: VideoViewProps) => { - const videoEl = useRef(null); + const videoEl = React.useRef(null); - useEffect(() => { + React.useEffect(() => { + if (!videoEl.current) return; + // Send aspect ratio on first render + if (onSetAspectRatio) { + const h = videoEl.current.videoHeight; + const w = videoEl.current.videoWidth; + if (w && h && w + h > 20) { + onSetAspectRatio({ aspectRatio: w / h }); + } + } + }, []); + + React.useEffect(() => { if (!videoEl.current) { return; } @@ -51,7 +63,7 @@ export default ({ muted, mirror = false, stream, onResize, onSetAspectRatio, ... }; }, [stream]); - useEffect(() => { + React.useEffect(() => { if (!videoEl.current) { return; }