mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Basic CSS Grid based video grid demo
This commit is contained in:
parent
6addd838b4
commit
f9d799ff05
130
package-lock.json
generated
130
package-lock.json
generated
@ -7,13 +7,15 @@
|
|||||||
"": {
|
"": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-spring/web": "^9.2.4",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"color-hash": "^2.0.1",
|
"color-hash": "^2.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"matrix-js-sdk": "^12.0.1",
|
"matrix-js-sdk": "^12.0.1",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0",
|
"react-dom": "^17.0.0",
|
||||||
"react-router-dom": "^5.2.0"
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-use-gesture": "^9.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react-refresh": "^1.3.1",
|
"@vitejs/plugin-react-refresh": "^1.3.1",
|
||||||
@ -390,6 +392,69 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-spring/animated": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-AfV6ZM8pCCAT29GY5C8/1bOPjZrv/7kD0vedjiE/tEYvNDwg9GlscrvsTViWR2XykJoYrDfdkYArrldWpsCJ5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-spring/core": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-R+PwyfsjiuYCWqaTTfCpYpRmsP0h87RNm7uxC1Uxy7QAHUfHEm2sAHn+AdHPwq/MbVwDssVT8C5yf2WGcqiXGg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@react-spring/animated": "~9.2.0",
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-spring/rafz": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-SOKf9eue+vAX+DGo7kWYNl9i9J3gPUlQjifIcV9Bzw9h3i30wPOOP0TjS7iMG/kLp2cdHQYDNFte6nt23VAZkQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@react-spring/shared": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-ZEr4l2BxmyFRUvRA2VCkPfCJii4E7cGkwbjmTBx1EmcGrOnde/V2eF5dxqCTY3k35QuCegkrWe0coRJVkh8q2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-spring/rafz": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@react-spring/types": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-zHUXrWO8nweUN/ISjrjqU7GgXXvoEbFca1CgiE0TY0H/dqJb3l+Rhx8ecPVNYimzFg3ZZ1/T0egpLop8SOv4aA=="
|
||||||
|
},
|
||||||
|
"node_modules/@react-spring/web": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-vtPvOalLFvuju/MDBtoSnCyt0xXSL6Amyv82fljOuWPl1yGd4M1WteijnYL9Zlriljl0a3oXcPunAVYTD9dbDQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@react-spring/animated": "~9.2.0",
|
||||||
|
"@react-spring/core": "~9.2.0",
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/pluginutils": {
|
"node_modules/@rollup/pluginutils": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
|
||||||
@ -1328,6 +1393,14 @@
|
|||||||
"react": ">=15"
|
"react": ">=15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-use-gesture": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.7",
|
"version": "0.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
@ -1891,6 +1964,55 @@
|
|||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@react-spring/animated": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-AfV6ZM8pCCAT29GY5C8/1bOPjZrv/7kD0vedjiE/tEYvNDwg9GlscrvsTViWR2XykJoYrDfdkYArrldWpsCJ5g==",
|
||||||
|
"requires": {
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@react-spring/core": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-R+PwyfsjiuYCWqaTTfCpYpRmsP0h87RNm7uxC1Uxy7QAHUfHEm2sAHn+AdHPwq/MbVwDssVT8C5yf2WGcqiXGg==",
|
||||||
|
"requires": {
|
||||||
|
"@react-spring/animated": "~9.2.0",
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@react-spring/rafz": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-SOKf9eue+vAX+DGo7kWYNl9i9J3gPUlQjifIcV9Bzw9h3i30wPOOP0TjS7iMG/kLp2cdHQYDNFte6nt23VAZkQ=="
|
||||||
|
},
|
||||||
|
"@react-spring/shared": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-ZEr4l2BxmyFRUvRA2VCkPfCJii4E7cGkwbjmTBx1EmcGrOnde/V2eF5dxqCTY3k35QuCegkrWe0coRJVkh8q2Q==",
|
||||||
|
"requires": {
|
||||||
|
"@react-spring/rafz": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@react-spring/types": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-zHUXrWO8nweUN/ISjrjqU7GgXXvoEbFca1CgiE0TY0H/dqJb3l+Rhx8ecPVNYimzFg3ZZ1/T0egpLop8SOv4aA=="
|
||||||
|
},
|
||||||
|
"@react-spring/web": {
|
||||||
|
"version": "9.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.2.4.tgz",
|
||||||
|
"integrity": "sha512-vtPvOalLFvuju/MDBtoSnCyt0xXSL6Amyv82fljOuWPl1yGd4M1WteijnYL9Zlriljl0a3oXcPunAVYTD9dbDQ==",
|
||||||
|
"requires": {
|
||||||
|
"@react-spring/animated": "~9.2.0",
|
||||||
|
"@react-spring/core": "~9.2.0",
|
||||||
|
"@react-spring/shared": "~9.2.0",
|
||||||
|
"@react-spring/types": "~9.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@rollup/pluginutils": {
|
"@rollup/pluginutils": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz",
|
||||||
@ -2610,6 +2732,12 @@
|
|||||||
"tiny-warning": "^1.0.0"
|
"tiny-warning": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-use-gesture": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.7",
|
"version": "0.13.7",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
|
@ -6,13 +6,15 @@
|
|||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-spring/web": "^9.2.4",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"color-hash": "^2.0.1",
|
"color-hash": "^2.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"matrix-js-sdk": "^12.0.1",
|
"matrix-js-sdk": "^12.0.1",
|
||||||
"react": "^17.0.0",
|
"react": "^17.0.0",
|
||||||
"react-dom": "^17.0.0",
|
"react-dom": "^17.0.0",
|
||||||
"react-router-dom": "^5.2.0"
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-use-gesture": "^9.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-react-refresh": "^1.3.1",
|
"@vitejs/plugin-react-refresh": "^1.3.1",
|
||||||
|
@ -26,6 +26,7 @@ import { useConferenceCallManager } from "./ConferenceCallManagerHooks";
|
|||||||
import { JoinOrCreateRoom } from "./JoinOrCreateRoom";
|
import { JoinOrCreateRoom } from "./JoinOrCreateRoom";
|
||||||
import { LoginOrRegister } from "./LoginOrRegister";
|
import { LoginOrRegister } from "./LoginOrRegister";
|
||||||
import { Room } from "./Room";
|
import { Room } from "./Room";
|
||||||
|
import { GridDemo } from "./GridDemo";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { protocol, host } = window.location;
|
const { protocol, host } = window.location;
|
||||||
@ -55,6 +56,9 @@ export default function App() {
|
|||||||
>
|
>
|
||||||
<Room manager={manager} />
|
<Room manager={manager} />
|
||||||
</AuthenticatedRoute>
|
</AuthenticatedRoute>
|
||||||
|
<Route exact path="/grid">
|
||||||
|
<GridDemo />
|
||||||
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
90
src/GridDemo.jsx
Normal file
90
src/GridDemo.jsx
Normal 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
32
src/GridDemo.module.css
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user