Skip to content

Commit

Permalink
Add correct aspect ratio for each video
Browse files Browse the repository at this point in the history
  • Loading branch information
thyal committed Jan 26, 2024
1 parent 04845c5 commit 468fdc9
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 56 deletions.
112 changes: 60 additions & 52 deletions src/lib/react/Grid/index.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 (
<div
style={{
Expand All @@ -31,7 +40,10 @@ function GridVideoCellView({
{render ? (
render()
) : participant.stream ? (
<VideoView style={{}} stream={participant.stream} onSetAspectRatio={onSetAspectRatio} />
<VideoView
stream={participant.stream}
onSetAspectRatio={({ aspectRatio }) => handleAspectRatioChange({ ar: aspectRatio })}
/>
) : null}
</div>
);
Expand All @@ -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;
Expand All @@ -52,61 +64,24 @@ interface GridProps {
function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }: GridProps) {
const { remoteParticipants, localParticipant } = roomConnection.state;
const gridRef = React.useRef<HTMLDivElement>(null);
const [videos, setVideos] = React.useState<ReturnType<typeof makeVideoCellView>[]>([]);
const [stageLayout, setStageLayout] = React.useState<ReturnType<typeof calculateLayout> | null>(null);
const [containerFrame, setContainerFrame] = React.useState<Frame | null>(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 }
)
Expand All @@ -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 (
<div
Expand All @@ -127,7 +135,7 @@ function Grid({ roomConnection, renderParticipant, videoGridGap = 0 }: GridProps
position: "relative",
}}
>
{[localParticipant, ...remoteParticipants].map((participant, i) => {
{participants.map((participant, i) => {
const cell = stageLayout?.videoGrid.cells[i];

if (!cell || !participant || !participant.stream || !cell.clientId) return null;
Expand Down
20 changes: 16 additions & 4 deletions src/lib/react/VideoView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from "react";
import * as React from "react";
import debounce from "../utils/debounce";

interface VideoViewSelfProps {
Expand All @@ -14,9 +14,21 @@ type VideoViewProps = VideoViewSelfProps &
React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;

export default ({ muted, mirror = false, stream, onResize, onSetAspectRatio, ...rest }: VideoViewProps) => {
const videoEl = useRef<HTMLVideoElement>(null);
const videoEl = React.useRef<HTMLVideoElement>(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;
}
Expand Down Expand Up @@ -51,7 +63,7 @@ export default ({ muted, mirror = false, stream, onResize, onSetAspectRatio, ...
};
}, [stream]);

useEffect(() => {
React.useEffect(() => {
if (!videoEl.current) {
return;
}
Expand Down

0 comments on commit 468fdc9

Please sign in to comment.