Basic CSS Grid based video grid demo

This commit is contained in:
Robert Long 2021-08-11 16:02:40 -07:00
commit f9d799ff05
5 changed files with 258 additions and 2 deletions

View file

@ -26,6 +26,7 @@ import { useConferenceCallManager } from "./ConferenceCallManagerHooks";
import { JoinOrCreateRoom } from "./JoinOrCreateRoom";
import { LoginOrRegister } from "./LoginOrRegister";
import { Room } from "./Room";
import { GridDemo } from "./GridDemo";
export default function App() {
const { protocol, host } = window.location;
@ -55,6 +56,9 @@ export default function App() {
>
<Room manager={manager} />
</AuthenticatedRoute>
<Route exact path="/grid">
<GridDemo />
</Route>
</Switch>
)}
</div>

90
src/GridDemo.jsx Normal file
View file

@ -0,0 +1,90 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import classNames from "classnames";
import { useDrag } from "react-use-gesture";
import { useSpring, useTransition, animated } from "@react-spring/web";
import styles from "./GridDemo.module.css";
let tileIdx = 0;
export function GridDemo() {
const [stream, setStream] = useState();
const [tiles, setTiles] = useState([]);
const startWebcam = useCallback(async () => {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
setStream(stream);
setTiles([{ stream, key: tileIdx++ }]);
}, []);
const addTile = useCallback(() => {
const newStream = stream.clone();
setTiles((tiles) => [...tiles, { stream: newStream, key: tileIdx++ }]);
}, [stream]);
const removeTile = useCallback(() => {
setTiles((tiles) => {
const newArr = [...tiles];
newArr.pop();
return newArr;
});
}, []);
useEffect(() => {
console.log(tiles);
}, [tiles]);
const tileTransitions = useTransition(tiles, {
from: { opacity: 0, scale: 0.5 },
enter: { opacity: 1, scale: 1 },
leave: { opacity: 0, scale: 0.5 },
});
return (
<div className={styles.gridDemo}>
<div className={styles.buttons}>
{!stream && <button onClick={startWebcam}>Start Webcam</button>}
{stream && <button onClick={addTile}>Add Tile</button>}
{stream && <button onClick={removeTile}>Remove Tile</button>}
</div>
<div className={styles.grid}>
{tileTransitions((style, tile) => (
<ParticipantTile key={tile.key} style={style} {...tile} />
))}
</div>
</div>
);
}
function ParticipantTile({ style, stream }) {
const videoRef = useRef();
useEffect(() => {
if (stream) {
videoRef.current.srcObject = stream;
videoRef.current.play();
} else {
videoRef.current.srcObject = null;
}
}, [stream]);
const [{ x, y }, api] = useSpring(() => ({
from: { x: 0, y: 0 },
config: {
tension: 250,
},
}));
const bind = useDrag(({ down, movement: [mx, my] }) => {
api.start({ x: down ? mx : 0, y: down ? my : 0 });
});
return (
<animated.div
{...bind()}
className={styles.participantTile}
style={{ x, y, ...style }}
>
<video ref={videoRef} playsInline />
</animated.div>
);
}

32
src/GridDemo.module.css Normal file
View file

@ -0,0 +1,32 @@
.gridDemo {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.grid {
position: relative;
overflow: hidden;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-auto-flow: dense;
grid-gap: 8px;
justify-items: stretch;
padding: 8px;
flex: 1;
}
.participantTile {
will-change: transform, opacity;
}
.participantTile video {
width: 100%;
height: 100%;
object-fit: cover;
}