Merge branch 'main' of github.com:vector-im/matrix-video-chat

This commit is contained in:
Robert Long 2021-10-06 11:20:00 -07:00
commit 0067212512
12 changed files with 198 additions and 28 deletions

8
.env Normal file
View file

@ -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

View file

@ -4,10 +4,42 @@ Testbed for full mesh video chat.
## Getting Started ## 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 cd matrix-video-chat
yarn yarn
yarn link matrix-js-sdk
yarn link matrix-react-sdk
yarn dev yarn dev
``` ```
## Config
Configuration options are documented in the `.env` file.

View file

@ -6,19 +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",
"lodash-move": "^1.1.1",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robertlong/group-call", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#robertlong/group-call",
"matrix-react-sdk": "github:matrix-org/matrix-react-sdk#robertlong/group-call", "matrix-react-sdk": "github:matrix-org/matrix-react-sdk#robertlong/group-call",
"re-resizable": "^6.9.0", "re-resizable": "^6.9.0",
"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",
"react-use-measure": "^2.0.4"
}, },
"devDependencies": { "devDependencies": {
"sass": "^1.42.1", "sass": "^1.42.1",

View file

@ -277,24 +277,56 @@ function sortRooms(client, rooms) {
}); });
} }
export function useRooms(client) { export function useGroupCallRooms(client) {
const [rooms, setRooms] = useState([]); const [rooms, setRooms] = useState([]);
useEffect(() => { useEffect(() => {
function updateRooms() { function updateRooms() {
const visibleRooms = client.getVisibleRooms(); const groupCalls = client.groupCallEventHandler.groupCalls.values();
const sortedRooms = sortRooms(client, visibleRooms); const rooms = Array.from(groupCalls).map((groupCall) => groupCall.room);
setRooms(sortedRooms); 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(); updateRooms();
client.on("Room", updateRooms); client.on("GroupCall.incoming", updateRooms);
client.on("GroupCall.participants", updateRooms);
return () => { return () => {
client.removeListener("Room", updateRooms); client.removeListener("GroupCall.incoming", updateRooms);
client.removeListener("GroupCall.participants", updateRooms);
}; };
}, []); }, []);
return rooms; 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;
}

23
src/Facepile.jsx Normal file
View file

@ -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 (
<div
className={styles.facepile}
title={participants.map((member) => member.name).join(", ")}
>
{participants.map((member) => (
<div
className={styles.avatar}
style={{ backgroundColor: colorHash.hex(member.name) }}
>
<span>{member.name.slice(0, 1).toUpperCase()}</span>
</div>
))}
</div>
);
}

25
src/Facepile.module.css Normal file
View file

@ -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;
}

View file

@ -16,7 +16,10 @@ limitations under the License.
import React, { useCallback, useRef, useState } from "react"; import React, { useCallback, useRef, useState } from "react";
import { useHistory, Link } from "react-router-dom"; import { useHistory, Link } from "react-router-dom";
import { useRooms } from "./ConferenceCallManagerHooks"; import {
useGroupCallRooms,
usePublicRooms,
} from "./ConferenceCallManagerHooks";
import { Header, LeftNav, UserNav } from "./Header"; import { Header, LeftNav, UserNav } from "./Header";
import ColorHash from "color-hash"; import ColorHash from "color-hash";
import styles from "./Home.module.css"; import styles from "./Home.module.css";
@ -26,6 +29,7 @@ import {
GroupCallIntent, GroupCallIntent,
GroupCallType, GroupCallType,
} from "matrix-js-sdk/src/browser-index"; } from "matrix-js-sdk/src/browser-index";
import { Facepile } from "./Facepile";
const colorHash = new ColorHash({ lightness: 0.3 }); const colorHash = new ColorHash({ lightness: 0.3 });
@ -34,7 +38,11 @@ export function Home({ client, onLogout }) {
const roomNameRef = useRef(); const roomNameRef = useRef();
const guestAccessRef = useRef(); const guestAccessRef = useRef();
const [createRoomError, setCreateRoomError] = useState(); 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( const onCreateRoom = useCallback(
(e) => { (e) => {
@ -139,10 +147,32 @@ export function Home({ client, onLogout }) {
</FieldRow> </FieldRow>
</form> </form>
</section> </section>
{publicRooms.length > 0 && (
<section>
<h3>Public Rooms</h3>
<div className={styles.roomList}>
{publicRooms.map((room) => (
<Link
className={styles.roomListItem}
key={room.room_id}
to={`/room/${room.room_id}`}
>
<div
className={styles.roomAvatar}
style={{ backgroundColor: colorHash.hex(room.name) }}
>
<span>{room.name.slice(0, 1)}</span>
</div>
<div className={styles.roomName}>{room.name}</div>
</Link>
))}
</div>
</section>
)}
<section> <section>
<h3>Recent Rooms</h3> <h3>Recent Rooms</h3>
<div className={styles.roomList}> <div className={styles.roomList}>
{rooms.map((room) => ( {rooms.map(({ room, participants }) => (
<Link <Link
className={styles.roomListItem} className={styles.roomListItem}
key={room.roomId} key={room.roomId}
@ -155,6 +185,7 @@ export function Home({ client, onLogout }) {
<span>{room.name.slice(0, 1)}</span> <span>{room.name.slice(0, 1)}</span>
</div> </div>
<div className={styles.roomName}>{room.name}</div> <div className={styles.roomName}>{room.name}</div>
<Facepile participants={participants} />
</Link> </Link>
))} ))}
</div> </div>

View file

@ -22,6 +22,7 @@ import {
MicButton, MicButton,
VideoButton, VideoButton,
LayoutToggleButton, LayoutToggleButton,
ScreenshareButton,
} from "./RoomButton"; } from "./RoomButton";
import { Header, LeftNav, RightNav, CenterNav } from "./Header"; import { Header, LeftNav, RightNav, CenterNav } from "./Header";
import { Button, ErrorMessage } from "./Input"; import { Button, ErrorMessage } from "./Input";
@ -270,8 +271,6 @@ function InRoomView({
}); });
} }
console.log("items changed", participants);
return participants; return participants;
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]); }, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
@ -303,7 +302,10 @@ function InRoomView({
enabled={localVideoMuted} enabled={localVideoMuted}
onClick={toggleLocalVideoMuted} onClick={toggleLocalVideoMuted}
/> />
<VideoButton enabled={isScreensharing} onClick={toggleScreensharing} /> <ScreenshareButton
enabled={isScreensharing}
onClick={toggleScreensharing}
/>
<HangupButton onClick={onLeave} /> <HangupButton onClick={onLeave} />
</div> </div>
</> </>

View file

@ -9,6 +9,7 @@ import { ReactComponent as HangupIcon } from "./icons/Hangup.svg";
import { ReactComponent as SettingsIcon } from "./icons/Settings.svg"; import { ReactComponent as SettingsIcon } from "./icons/Settings.svg";
import { ReactComponent as GridIcon } from "./icons/Grid.svg"; import { ReactComponent as GridIcon } from "./icons/Grid.svg";
import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg"; import { ReactComponent as SpeakerIcon } from "./icons/Speaker.svg";
import { ReactComponent as ScreenshareIcon } from "./icons/Screenshare.svg";
export function RoomButton({ on, className, children, ...rest }) { export function RoomButton({ on, className, children, ...rest }) {
return ( return (
@ -37,6 +38,18 @@ export function VideoButton({ enabled, ...rest }) {
); );
} }
export function ScreenshareButton({ enabled, className, ...rest }) {
return (
<RoomButton
className={classNames(styles.screenshareButton, className)}
{...rest}
on={enabled}
>
<ScreenshareIcon />
</RoomButton>
);
}
export function HangupButton({ className, ...rest }) { export function HangupButton({ className, ...rest }) {
return ( return (
<RoomButton <RoomButton

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
.roomButton, .headerButton { .roomButton,
.headerButton {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -32,11 +33,10 @@ limitations under the License.
} }
.roomButton:hover { .roomButton:hover {
background-color: #8D97A5; background-color: #8d97a5;
} }
.roomButton:active { .roomButton:active {
} }
.roomButton.on { .roomButton.on {
@ -50,11 +50,11 @@ limitations under the License.
} }
.headerButton svg * { .headerButton svg * {
fill: #8E99A4; fill: #8e99a4;
} }
.headerButton:hover { .headerButton:hover {
background-color: #8D97A5; background-color: #8d97a5;
} }
.headerButton:hover svg * { .headerButton:hover svg * {
@ -62,9 +62,14 @@ limitations under the License.
} }
.headerButton.on svg * { .headerButton.on svg * {
fill: #0DBD8B fill: #0dbd8b;
} }
.hangupButton, .hangupButton:hover { .hangupButton,
background-color: #FF5B55; .hangupButton:hover {
background-color: #ff5b55;
}
.screenshareButton.on svg * {
fill: #0dbd8b;
} }

View file

@ -0,0 +1,3 @@
<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 0.5C2.09315 0.5 0.75 1.84314 0.75 3.5V13.5713C0.75 15.2282 2.09315 16.5713 3.75 16.5713H20.2499C21.9067 16.5713 23.2499 15.2282 23.2499 13.5713V3.5C23.2499 1.84315 21.9067 0.5 20.2499 0.5H3.75ZM12.7998 11.9106C12.7998 12.3524 12.4416 12.7106 11.9998 12.7106C11.558 12.7106 11.1998 12.3524 11.1998 11.9106L11.1998 7.09206L9.43158 8.86029C9.11916 9.17271 8.61263 9.17271 8.30021 8.86029C7.98779 8.54787 7.98779 8.04134 8.30021 7.72892L11.4341 4.59501C11.7465 4.28259 12.2531 4.28259 12.5655 4.59501L15.6994 7.72892C16.0118 8.04134 16.0118 8.54787 15.6994 8.86029C15.387 9.17271 14.8805 9.17271 14.568 8.86029L12.7998 7.09206L12.7998 11.9106Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 814 B

View file

@ -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": "matrix-js-sdk@github:matrix-org/matrix-js-sdk#robertlong/group-call":
version "13.0.0" 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: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
another-json "^0.2.0" 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": "matrix-react-sdk@github:matrix-org/matrix-react-sdk#robertlong/group-call":
version "3.31.0" 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: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
"@react-spring/web" "^9.2.4" "@react-spring/web" "^9.2.4"