diff --git a/.env b/.env new file mode 100644 index 0000000..55ce932 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +#### +# App Config +# Environment files are documented here: +# https://vitejs.dev/guide/env-and-mode.html#env-files +#### + +# The room id for the space to use for listing public group call rooms +# VITE_PUBLIC_SPACE_ROOM_ID=!hjdfshkdskjdsk:myhomeserver.com \ No newline at end of file diff --git a/README.md b/README.md index ef665b5..a9eb707 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,42 @@ Testbed for full mesh video chat. ## Getting Started -You must first run a local Synapse server on port 8008 +`matrix-video-chat` is built against the `robertlong/group-call` branch of both [matrix-js-sdk](https://github.com/matrix-org/matrix-js-sdk/pull/1902) and [matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/pull/6848). Because of how these packages are configured and Vite's requirements, you will need to clone them locally and use `yarn link` to stich things together. + +First clone, install, and link `matrix-js-sdk` ``` +git clone https://github.com/matrix-org/matrix-js-sdk.git +cd matrix-js-sdk +git checkout robertlong/group-call +yarn +yarn link +``` + +Then clone, install, link `matrix-js-sdk` into `matrix-react-sdk`, and link `matrix-react-sdk` + +``` +git clone https://github.com/matrix-org/matrix-react-sdk.git +cd matrix-react-sdk +git checkout robertlong/group-call +yarn +yarn link matrix-js-sdk +yarn link +``` + +Next you'll also need [Synapse](https://matrix-org.github.io/synapse/latest/setup/installation.html) installed locally and running on port 8008. + +Finally we can set up this project. + +``` +git clone https://github.com/vector-im/matrix-video-chat.git cd matrix-video-chat yarn +yarn link matrix-js-sdk +yarn link matrix-react-sdk yarn dev ``` + +## Config + +Configuration options are documented in the `.env` file. diff --git a/package.json b/package.json index 79914d8..a96bc72 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,15 @@ "serve": "vite preview" }, "dependencies": { - "@react-spring/web": "^9.2.4", "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", - "lodash-move": "^1.1.1", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robertlong/group-call", "matrix-react-sdk": "github:matrix-org/matrix-react-sdk#robertlong/group-call", "re-resizable": "^6.9.0", "react": "^17.0.0", "react-dom": "^17.0.0", - "react-router-dom": "^5.2.0", - "react-use-gesture": "^9.1.3", - "react-use-measure": "^2.0.4" + "react-router-dom": "^5.2.0" }, "devDependencies": { "sass": "^1.42.1", diff --git a/src/ConferenceCallManagerHooks.js b/src/ConferenceCallManagerHooks.js index 103ed47..db93e51 100644 --- a/src/ConferenceCallManagerHooks.js +++ b/src/ConferenceCallManagerHooks.js @@ -277,24 +277,56 @@ function sortRooms(client, rooms) { }); } -export function useRooms(client) { +export function useGroupCallRooms(client) { const [rooms, setRooms] = useState([]); useEffect(() => { function updateRooms() { - const visibleRooms = client.getVisibleRooms(); - const sortedRooms = sortRooms(client, visibleRooms); - setRooms(sortedRooms); + const groupCalls = client.groupCallEventHandler.groupCalls.values(); + const rooms = Array.from(groupCalls).map((groupCall) => groupCall.room); + const sortedRooms = sortRooms(client, rooms); + const items = sortedRooms.map((room) => { + const groupCall = client.getGroupCallForRoom(room.roomId); + + return { + room, + groupCall, + participants: [...groupCall.participants], + }; + }); + setRooms(items); } updateRooms(); - client.on("Room", updateRooms); + client.on("GroupCall.incoming", updateRooms); + client.on("GroupCall.participants", updateRooms); return () => { - client.removeListener("Room", updateRooms); + client.removeListener("GroupCall.incoming", updateRooms); + client.removeListener("GroupCall.participants", updateRooms); }; }, []); return rooms; } + +export function usePublicRooms(client, publicSpaceRoomId, maxRooms = 50) { + const [rooms, setRooms] = useState([]); + + useEffect(() => { + if (publicSpaceRoomId) { + client.getRoomHierarchy(publicSpaceRoomId, maxRooms).then(({ rooms }) => { + const filteredRooms = rooms.filter( + (room) => room.room_type !== "m.space" + ); + + setRooms(filteredRooms); + }); + } else { + setRooms([]); + } + }, [publicSpaceRoomId]); + + return rooms; +} diff --git a/src/Facepile.jsx b/src/Facepile.jsx new file mode 100644 index 0000000..77d158f --- /dev/null +++ b/src/Facepile.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import styles from "./Facepile.module.css"; +import ColorHash from "color-hash"; + +const colorHash = new ColorHash({ lightness: 0.3 }); + +export function Facepile({ participants }) { + return ( +
member.name).join(", ")} + > + {participants.map((member) => ( +
+ {member.name.slice(0, 1).toUpperCase()} +
+ ))} +
+ ); +} diff --git a/src/Facepile.module.css b/src/Facepile.module.css new file mode 100644 index 0000000..94b29a4 --- /dev/null +++ b/src/Facepile.module.css @@ -0,0 +1,25 @@ +.facepile { + margin: 0 16px; +} + +.facepile .avatar { + position: relative; + width: 20px; + height: 20px; + border-radius: 20px; +} + +.facepile .avatar > * { + position: absolute; + left: 0; + color: #fff; + text-align: center; + pointer-events: none; + font-weight: 600; +} + +.facepile .avatar span { + font-size: 14px; + width: 20px; + line-height: 20px; +} diff --git a/src/Home.jsx b/src/Home.jsx index 42faaa6..63c4574 100644 --- a/src/Home.jsx +++ b/src/Home.jsx @@ -16,7 +16,10 @@ limitations under the License. import React, { useCallback, useRef, useState } from "react"; import { useHistory, Link } from "react-router-dom"; -import { useRooms } from "./ConferenceCallManagerHooks"; +import { + useGroupCallRooms, + usePublicRooms, +} from "./ConferenceCallManagerHooks"; import { Header, LeftNav, UserNav } from "./Header"; import ColorHash from "color-hash"; import styles from "./Home.module.css"; @@ -26,6 +29,7 @@ import { GroupCallIntent, GroupCallType, } from "matrix-js-sdk/src/browser-index"; +import { Facepile } from "./Facepile"; const colorHash = new ColorHash({ lightness: 0.3 }); @@ -34,7 +38,11 @@ export function Home({ client, onLogout }) { const roomNameRef = useRef(); const guestAccessRef = useRef(); const [createRoomError, setCreateRoomError] = useState(); - const rooms = useRooms(client); + const rooms = useGroupCallRooms(client); + const publicRooms = usePublicRooms( + client, + import.meta.env.VITE_PUBLIC_SPACE_ROOM_ID + ); const onCreateRoom = useCallback( (e) => { @@ -139,10 +147,32 @@ export function Home({ client, onLogout }) { + {publicRooms.length > 0 && ( +
+

Public Rooms

+
+ {publicRooms.map((room) => ( + +
+ {room.name.slice(0, 1)} +
+
{room.name}
+ + ))} +
+
+ )}

Recent Rooms

- {rooms.map((room) => ( + {rooms.map(({ room, participants }) => ( {room.name.slice(0, 1)}
{room.name}
+ ))} diff --git a/src/Room.jsx b/src/Room.jsx index 155d507..0643653 100644 --- a/src/Room.jsx +++ b/src/Room.jsx @@ -22,6 +22,7 @@ import { MicButton, VideoButton, LayoutToggleButton, + ScreenshareButton, } from "./RoomButton"; import { Header, LeftNav, RightNav, CenterNav } from "./Header"; import { Button, ErrorMessage } from "./Input"; @@ -270,8 +271,6 @@ function InRoomView({ }); } - console.log("items changed", participants); - return participants; }, [userMediaFeeds, activeSpeaker, screenshareFeeds]); @@ -303,7 +302,10 @@ function InRoomView({ enabled={localVideoMuted} onClick={toggleLocalVideoMuted} /> - + diff --git a/src/RoomButton.jsx b/src/RoomButton.jsx index b38d13a..3f49d41 100644 --- a/src/RoomButton.jsx +++ b/src/RoomButton.jsx @@ -9,6 +9,7 @@ import { ReactComponent as HangupIcon } from "./icons/Hangup.svg"; import { ReactComponent as SettingsIcon } from "./icons/Settings.svg"; import { ReactComponent as GridIcon } from "./icons/Grid.svg"; import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg"; +import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg"; export function RoomButton({ on, className, children, ...rest }) { return ( @@ -37,6 +38,18 @@ export function VideoButton({ enabled, ...rest }) { ); } +export function ScreenshareButton({ enabled, className, ...rest }) { + return ( + + + + ); +} + export function HangupButton({ className, ...rest }) { return ( + + diff --git a/yarn.lock b/yarn.lock index ffb0121..accbdf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1602,7 +1602,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 "matrix-js-sdk@github:matrix-org/matrix-js-sdk#robertlong/group-call": version "13.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/326aec9f9ea7b29c6c07bbcc84dc5288300abc58" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ba57736bf6d6d99342c64cca5eb6156ee7b9e178" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -1617,7 +1617,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 "matrix-react-sdk@github:matrix-org/matrix-react-sdk#robertlong/group-call": version "3.31.0" - resolved "https://codeload.github.com/matrix-org/matrix-react-sdk/tar.gz/7775ac9956bc8fba437fbb15f5cf56b2ee554903" + resolved "https://codeload.github.com/matrix-org/matrix-react-sdk/tar.gz/fc43d1d2f742f92a1ffd113ec557565d74e98167" dependencies: "@babel/runtime" "^7.12.5" "@react-spring/web" "^9.2.4"