Clean up room-related components
This commit is contained in:
parent
8be578763d
commit
550c45b69e
9 changed files with 598 additions and 700 deletions
43
src/App.jsx
43
src/App.jsx
|
|
@ -14,26 +14,18 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import {
|
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||||
BrowserRouter as Router,
|
|
||||||
Switch,
|
|
||||||
Route,
|
|
||||||
useLocation,
|
|
||||||
useHistory,
|
|
||||||
} from "react-router-dom";
|
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { OverlayProvider } from "@react-aria/overlays";
|
import { OverlayProvider } from "@react-aria/overlays";
|
||||||
import { HomePage } from "./home/HomePage";
|
import { HomePage } from "./home/HomePage";
|
||||||
import { LoginPage } from "./LoginPage";
|
import { LoginPage } from "./LoginPage";
|
||||||
import { RegisterPage } from "./RegisterPage";
|
import { RegisterPage } from "./RegisterPage";
|
||||||
import { Room } from "./Room";
|
import { RoomPage } from "./room/RoomPage";
|
||||||
import {
|
import { RoomRedirect } from "./room/RoomRedirect";
|
||||||
ClientProvider,
|
import { ClientProvider } from "./ConferenceCallManagerHooks";
|
||||||
defaultHomeserverHost,
|
|
||||||
} from "./ConferenceCallManagerHooks";
|
|
||||||
import { LoadingView } from "./FullScreenView";
|
|
||||||
import { usePageFocusStyle } from "./usePageFocusStyle";
|
import { usePageFocusStyle } from "./usePageFocusStyle";
|
||||||
|
|
||||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||||
|
|
||||||
export default function App({ history }) {
|
export default function App({ history }) {
|
||||||
|
|
@ -54,7 +46,7 @@ export default function App({ history }) {
|
||||||
<RegisterPage />
|
<RegisterPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
<SentryRoute path="/room/:roomId?">
|
<SentryRoute path="/room/:roomId?">
|
||||||
<Room />
|
<RoomPage />
|
||||||
</SentryRoute>
|
</SentryRoute>
|
||||||
<SentryRoute path="*">
|
<SentryRoute path="*">
|
||||||
<RoomRedirect />
|
<RoomRedirect />
|
||||||
|
|
@ -65,24 +57,3 @@ export default function App({ history }) {
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RoomRedirect() {
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let roomId = pathname;
|
|
||||||
|
|
||||||
if (pathname.startsWith("/")) {
|
|
||||||
roomId = roomId.substr(1, roomId.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
|
||||||
roomId = `#${roomId}:${defaultHomeserverHost}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
history.replace(`/room/${roomId}`);
|
|
||||||
}, [pathname, history]);
|
|
||||||
|
|
||||||
return <LoadingView />;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
447
src/Room.jsx
447
src/Room.jsx
|
|
@ -1,447 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
||||||
import styles from "./Room.module.css";
|
|
||||||
import { useLocation, useParams, useHistory, Link } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
CopyButton,
|
|
||||||
HangupButton,
|
|
||||||
MicButton,
|
|
||||||
VideoButton,
|
|
||||||
ScreenshareButton,
|
|
||||||
LinkButton,
|
|
||||||
} from "./button";
|
|
||||||
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "./Header";
|
|
||||||
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import VideoGrid, {
|
|
||||||
useVideoGridLayout,
|
|
||||||
} from "matrix-react-sdk/src/components/views/voip/GroupCallView/VideoGrid";
|
|
||||||
import SimpleVideoGrid from "matrix-react-sdk/src/components/views/voip/GroupCallView/SimpleVideoGrid";
|
|
||||||
import "matrix-react-sdk/res/css/views/voip/GroupCallView/_VideoGrid.scss";
|
|
||||||
import { useGroupCall } from "matrix-react-sdk/src/hooks/useGroupCall";
|
|
||||||
import { useCallFeed } from "matrix-react-sdk/src/hooks/useCallFeed";
|
|
||||||
import { useMediaStream } from "matrix-react-sdk/src/hooks/useMediaStream";
|
|
||||||
import {
|
|
||||||
getAvatarUrl,
|
|
||||||
getRoomUrl,
|
|
||||||
useClient,
|
|
||||||
useLoadGroupCall,
|
|
||||||
useProfile,
|
|
||||||
} from "./ConferenceCallManagerHooks";
|
|
||||||
import { ErrorView, LoadingView, FullScreenView } from "./FullScreenView";
|
|
||||||
import { GroupCallInspector } from "./GroupCallInspector";
|
|
||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import { OverflowMenu } from "./OverflowMenu";
|
|
||||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
|
||||||
import { UserMenu } from "./UserMenu";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { Avatar } from "./Avatar";
|
|
||||||
import { UserMenuContainer } from "./UserMenuContainer";
|
|
||||||
import { LobbyView } from "./room/LobbyView";
|
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
|
||||||
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
|
||||||
// or with getUsermedia and getDisplaymedia being used within the same session.
|
|
||||||
// For now we can disable screensharing in Safari.
|
|
||||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
||||||
|
|
||||||
export function Room() {
|
|
||||||
const [registrationError, setRegistrationError] = useState();
|
|
||||||
const { loading, isAuthenticated, error, client, isPasswordlessUser } =
|
|
||||||
useClient();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && !isAuthenticated) {
|
|
||||||
setRegistrationError(new Error("Must be registered"));
|
|
||||||
}
|
|
||||||
}, [loading, isAuthenticated]);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <LoadingView />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (registrationError || error) {
|
|
||||||
return <ErrorView error={registrationError || error} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <GroupCall client={client} isPasswordlessUser={isPasswordlessUser} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GroupCall({ client, isPasswordlessUser }) {
|
|
||||||
const { roomId: maybeRoomId } = useParams();
|
|
||||||
const { hash, search } = useLocation();
|
|
||||||
const [simpleGrid, viaServers] = useMemo(() => {
|
|
||||||
const params = new URLSearchParams(search);
|
|
||||||
return [params.has("simple"), params.getAll("via")];
|
|
||||||
}, [search]);
|
|
||||||
const roomId = maybeRoomId || hash;
|
|
||||||
const { loading, error, groupCall } = useLoadGroupCall(
|
|
||||||
client,
|
|
||||||
roomId,
|
|
||||||
viaServers
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.groupCall = groupCall;
|
|
||||||
}, [groupCall]);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <LoadingRoomView />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <ErrorView error={error} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GroupCallView
|
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
|
||||||
client={client}
|
|
||||||
roomId={roomId}
|
|
||||||
groupCall={groupCall}
|
|
||||||
simpleGrid={simpleGrid}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GroupCallView({
|
|
||||||
client,
|
|
||||||
isPasswordlessUser,
|
|
||||||
roomId,
|
|
||||||
groupCall,
|
|
||||||
simpleGrid,
|
|
||||||
}) {
|
|
||||||
const [showInspector, setShowInspector] = useState(false);
|
|
||||||
const {
|
|
||||||
state,
|
|
||||||
error,
|
|
||||||
activeSpeaker,
|
|
||||||
userMediaFeeds,
|
|
||||||
microphoneMuted,
|
|
||||||
localVideoMuted,
|
|
||||||
localCallFeed,
|
|
||||||
initLocalCallFeed,
|
|
||||||
enter,
|
|
||||||
leave,
|
|
||||||
toggleLocalVideoMuted,
|
|
||||||
toggleMicrophoneMuted,
|
|
||||||
toggleScreensharing,
|
|
||||||
isScreensharing,
|
|
||||||
localScreenshareFeed,
|
|
||||||
screenshareFeeds,
|
|
||||||
hasLocalParticipant,
|
|
||||||
} = useGroupCall(groupCall);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function onHangup(call) {
|
|
||||||
if (call.hangupReason === "ice_failed") {
|
|
||||||
Sentry.captureException(new Error("Call hangup due to ICE failure."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError(error) {
|
|
||||||
Sentry.captureException(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupCall) {
|
|
||||||
groupCall.on("hangup", onHangup);
|
|
||||||
groupCall.on("error", onError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (groupCall) {
|
|
||||||
groupCall.removeListener("hangup", onHangup);
|
|
||||||
groupCall.removeListener("error", onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [groupCall]);
|
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const onLeave = useCallback(() => {
|
|
||||||
leave();
|
|
||||||
|
|
||||||
if (!isPasswordlessUser) {
|
|
||||||
history.push("/");
|
|
||||||
} else {
|
|
||||||
setLeft(true);
|
|
||||||
}
|
|
||||||
}, [leave, history]);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <ErrorView error={error} />;
|
|
||||||
} else if (state === GroupCallState.Entered) {
|
|
||||||
return (
|
|
||||||
<InRoomView
|
|
||||||
groupCall={groupCall}
|
|
||||||
client={client}
|
|
||||||
roomName={groupCall.room.name}
|
|
||||||
microphoneMuted={microphoneMuted}
|
|
||||||
localVideoMuted={localVideoMuted}
|
|
||||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
|
||||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
|
||||||
userMediaFeeds={userMediaFeeds}
|
|
||||||
activeSpeaker={activeSpeaker}
|
|
||||||
onLeave={onLeave}
|
|
||||||
toggleScreensharing={toggleScreensharing}
|
|
||||||
isScreensharing={isScreensharing}
|
|
||||||
localScreenshareFeed={localScreenshareFeed}
|
|
||||||
screenshareFeeds={screenshareFeeds}
|
|
||||||
simpleGrid={simpleGrid}
|
|
||||||
setShowInspector={setShowInspector}
|
|
||||||
showInspector={showInspector}
|
|
||||||
roomId={roomId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (state === GroupCallState.Entering) {
|
|
||||||
return <EnteringRoomView />;
|
|
||||||
} else if (left) {
|
|
||||||
if (isPasswordlessUser) {
|
|
||||||
return <PasswordlessUserCallEndedScreen client={client} />;
|
|
||||||
} else {
|
|
||||||
return <GuestCallEndedScreen />;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<LobbyView
|
|
||||||
client={client}
|
|
||||||
hasLocalParticipant={hasLocalParticipant}
|
|
||||||
roomName={groupCall.room.name}
|
|
||||||
state={state}
|
|
||||||
onInitLocalCallFeed={initLocalCallFeed}
|
|
||||||
localCallFeed={localCallFeed}
|
|
||||||
onEnter={enter}
|
|
||||||
microphoneMuted={microphoneMuted}
|
|
||||||
localVideoMuted={localVideoMuted}
|
|
||||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
|
||||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
|
||||||
setShowInspector={setShowInspector}
|
|
||||||
showInspector={showInspector}
|
|
||||||
roomId={roomId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LoadingRoomView() {
|
|
||||||
return (
|
|
||||||
<FullScreenView>
|
|
||||||
<h1>Loading room...</h1>
|
|
||||||
</FullScreenView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function EnteringRoomView() {
|
|
||||||
return (
|
|
||||||
<FullScreenView>
|
|
||||||
<h1>Entering room...</h1>
|
|
||||||
</FullScreenView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function InRoomView({
|
|
||||||
client,
|
|
||||||
groupCall,
|
|
||||||
roomName,
|
|
||||||
microphoneMuted,
|
|
||||||
localVideoMuted,
|
|
||||||
toggleLocalVideoMuted,
|
|
||||||
toggleMicrophoneMuted,
|
|
||||||
userMediaFeeds,
|
|
||||||
activeSpeaker,
|
|
||||||
onLeave,
|
|
||||||
toggleScreensharing,
|
|
||||||
isScreensharing,
|
|
||||||
screenshareFeeds,
|
|
||||||
simpleGrid,
|
|
||||||
setShowInspector,
|
|
||||||
showInspector,
|
|
||||||
roomId,
|
|
||||||
}) {
|
|
||||||
const [layout, setLayout] = useVideoGridLayout();
|
|
||||||
|
|
||||||
const items = useMemo(() => {
|
|
||||||
const participants = [];
|
|
||||||
|
|
||||||
for (const callFeed of userMediaFeeds) {
|
|
||||||
participants.push({
|
|
||||||
id: callFeed.stream.id,
|
|
||||||
usermediaCallFeed: callFeed,
|
|
||||||
isActiveSpeaker:
|
|
||||||
screenshareFeeds.length === 0
|
|
||||||
? callFeed.userId === activeSpeaker
|
|
||||||
: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const callFeed of screenshareFeeds) {
|
|
||||||
const participant = participants.find(
|
|
||||||
(p) => p.usermediaCallFeed.userId === callFeed.userId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (participant) {
|
|
||||||
participant.screenshareCallFeed = callFeed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return participants;
|
|
||||||
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
|
||||||
|
|
||||||
const onFocusTile = useCallback(
|
|
||||||
(tiles, focusedTile) => {
|
|
||||||
if (layout === "freedom") {
|
|
||||||
return tiles.map((tile) => {
|
|
||||||
if (tile === focusedTile) {
|
|
||||||
return { ...tile, presenter: !tile.presenter };
|
|
||||||
}
|
|
||||||
|
|
||||||
return tile;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[layout, setLayout]
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderAvatar = useCallback(
|
|
||||||
(roomMember, width, height) => {
|
|
||||||
const avatarUrl = roomMember.user?.avatarUrl;
|
|
||||||
const size = Math.round(Math.min(width, height) / 2);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
key={roomMember.userId}
|
|
||||||
style={{
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRadius: size,
|
|
||||||
fontSize: Math.round(size / 2),
|
|
||||||
}}
|
|
||||||
src={avatarUrl && getAvatarUrl(client, avatarUrl, 96)}
|
|
||||||
fallback={roomMember.name.slice(0, 1).toUpperCase()}
|
|
||||||
className={styles.avatar}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[client]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames(styles.room, styles.inRoom)}>
|
|
||||||
<Header>
|
|
||||||
<LeftNav>
|
|
||||||
<RoomHeaderInfo roomName={roomName} />
|
|
||||||
</LeftNav>
|
|
||||||
<RightNav>
|
|
||||||
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
|
||||||
<UserMenuContainer disableLogout />
|
|
||||||
</RightNav>
|
|
||||||
</Header>
|
|
||||||
{items.length === 0 ? (
|
|
||||||
<div className={styles.centerMessage}>
|
|
||||||
<p>Waiting for other participants...</p>
|
|
||||||
</div>
|
|
||||||
) : simpleGrid ? (
|
|
||||||
<SimpleVideoGrid items={items} />
|
|
||||||
) : (
|
|
||||||
<VideoGrid
|
|
||||||
items={items}
|
|
||||||
layout={layout}
|
|
||||||
getAvatar={renderAvatar}
|
|
||||||
onFocusTile={onFocusTile}
|
|
||||||
disableAnimations={isSafari}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className={styles.footer}>
|
|
||||||
<MicButton muted={microphoneMuted} onPress={toggleMicrophoneMuted} />
|
|
||||||
<VideoButton muted={localVideoMuted} onPress={toggleLocalVideoMuted} />
|
|
||||||
{canScreenshare && !isSafari && (
|
|
||||||
<ScreenshareButton
|
|
||||||
enabled={isScreensharing}
|
|
||||||
onPress={toggleScreensharing}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<OverflowMenu
|
|
||||||
roomId={roomId}
|
|
||||||
setShowInspector={setShowInspector}
|
|
||||||
showInspector={showInspector}
|
|
||||||
client={client}
|
|
||||||
/>
|
|
||||||
<HangupButton onPress={onLeave} />
|
|
||||||
</div>
|
|
||||||
<GroupCallInspector
|
|
||||||
client={client}
|
|
||||||
groupCall={groupCall}
|
|
||||||
show={showInspector}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GuestCallEndedScreen() {
|
|
||||||
return (
|
|
||||||
<FullScreenView className={styles.callEndedScreen}>
|
|
||||||
<h1>Your call is now ended</h1>
|
|
||||||
<div className={styles.callEndedContent}>
|
|
||||||
<p>Why not finish by creating an account?</p>
|
|
||||||
<p>You'll be able to:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Easily access all your previous call links</li>
|
|
||||||
<li>Set a username and avatar</li>
|
|
||||||
</ul>
|
|
||||||
<LinkButton
|
|
||||||
className={styles.callEndedButton}
|
|
||||||
size="lg"
|
|
||||||
variant="default"
|
|
||||||
to="/register"
|
|
||||||
>
|
|
||||||
Create account
|
|
||||||
</LinkButton>
|
|
||||||
</div>
|
|
||||||
<Link to="/">Not now, return to home screen</Link>
|
|
||||||
</FullScreenView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PasswordlessUserCallEndedScreen({ client }) {
|
|
||||||
const { displayName } = useProfile(client);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FullScreenView className={styles.callEndedScreen}>
|
|
||||||
<h1>{displayName}, your call is now ended</h1>
|
|
||||||
<div className={styles.callEndedContent}>
|
|
||||||
<p>Why not finish by setting up a password to keep your account?</p>
|
|
||||||
<p>
|
|
||||||
You'll be able to keep your name and set an avatar for use on future
|
|
||||||
calls
|
|
||||||
</p>
|
|
||||||
<LinkButton
|
|
||||||
className={styles.callEndedButton}
|
|
||||||
size="lg"
|
|
||||||
variant="default"
|
|
||||||
to="/register"
|
|
||||||
>
|
|
||||||
Create account
|
|
||||||
</LinkButton>
|
|
||||||
</div>
|
|
||||||
<Link to="/">Not now, return to home screen</Link>
|
|
||||||
</FullScreenView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,217 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.room {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inRoom {
|
|
||||||
position: fixed;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinRoom {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinRoomContent {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinRoomContent h1 {
|
|
||||||
display: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinRoomFooter {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.homeLink {
|
|
||||||
margin-top: 50px;
|
|
||||||
color: #0dbd8b;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview {
|
|
||||||
position: relative;
|
|
||||||
min-height: 280px;
|
|
||||||
height: 50vh;
|
|
||||||
border-radius: 24px;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--bgColor3);
|
|
||||||
margin: 40px 20px 20px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview video {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
background-color: black;
|
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.webcamPermissions {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
margin: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.previewButtons {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 66px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: rgba(23, 25, 28, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinCallButton {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 222px;
|
|
||||||
height: 40px;
|
|
||||||
bottom: 86px;
|
|
||||||
left: 50%;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.copyButton {
|
|
||||||
width: 320px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.previewButtons > * {
|
|
||||||
margin-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.previewButtons > :last-child {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerMessage {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerMessage p {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomContainer {
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer > * {
|
|
||||||
margin-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer > :last-child {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedScreen h1 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedScreen h2 {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedScreen p {
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedScreen ul {
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
text-align: initial;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedButton {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.callEndedContent {
|
|
||||||
text-align: center;
|
|
||||||
max-width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
.roomContainer {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
height: 118px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.joinRoomContent h1 {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
src/room/CallEndedView.jsx
Normal file
50
src/room/CallEndedView.jsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import styles from "./CallEndedView.module.css";
|
||||||
|
import { LinkButton } from "../button";
|
||||||
|
import { useProfile } from "../ConferenceCallManagerHooks";
|
||||||
|
import { Subtitle, Body, Link, Headline } from "../typography/Typography";
|
||||||
|
import { Header, HeaderLogo, LeftNav, RightNav } from "../Header";
|
||||||
|
|
||||||
|
export function CallEndedView({ client }) {
|
||||||
|
const { displayName } = useProfile(client);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header>
|
||||||
|
<LeftNav>
|
||||||
|
<HeaderLogo />
|
||||||
|
</LeftNav>
|
||||||
|
<RightNav />
|
||||||
|
</Header>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<Headline className={styles.headline}>
|
||||||
|
{displayName}, your call is now ended
|
||||||
|
</Headline>
|
||||||
|
<div className={styles.callEndedContent}>
|
||||||
|
<Subtitle>
|
||||||
|
Why not finish by setting up a password to keep your account?
|
||||||
|
</Subtitle>
|
||||||
|
<Subtitle>
|
||||||
|
You'll be able to keep your name and set an avatar for use on
|
||||||
|
future calls
|
||||||
|
</Subtitle>
|
||||||
|
<LinkButton
|
||||||
|
className={styles.callEndedButton}
|
||||||
|
size="lg"
|
||||||
|
variant="default"
|
||||||
|
to="/register"
|
||||||
|
>
|
||||||
|
Create account
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<Body className={styles.footer}>
|
||||||
|
<Link color="primary" to="/">
|
||||||
|
Not now, return to home screen
|
||||||
|
</Link>
|
||||||
|
</Body>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
73
src/room/CallEndedView.module.css
Normal file
73
src/room/CallEndedView.module.css
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callEndedContent {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callEndedContent h3 {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callEndedButton {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
min-height: calc(100% - 64px);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-bottom: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 800px) {
|
||||||
|
.logo {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
min-height: calc(100% - 76px);
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/room/InCallView.jsx
Normal file
166
src/room/InCallView.jsx
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
import styles from "./InCallView.module.css";
|
||||||
|
import {
|
||||||
|
HangupButton,
|
||||||
|
MicButton,
|
||||||
|
VideoButton,
|
||||||
|
ScreenshareButton,
|
||||||
|
} from "../button";
|
||||||
|
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
|
||||||
|
import VideoGrid, {
|
||||||
|
useVideoGridLayout,
|
||||||
|
} from "matrix-react-sdk/src/components/views/voip/GroupCallView/VideoGrid";
|
||||||
|
import SimpleVideoGrid from "matrix-react-sdk/src/components/views/voip/GroupCallView/SimpleVideoGrid";
|
||||||
|
import "matrix-react-sdk/res/css/views/voip/GroupCallView/_VideoGrid.scss";
|
||||||
|
import { getAvatarUrl } from "../ConferenceCallManagerHooks";
|
||||||
|
import { GroupCallInspector } from "../GroupCallInspector";
|
||||||
|
import { OverflowMenu } from "../OverflowMenu";
|
||||||
|
import { GridLayoutMenu } from "../GridLayoutMenu";
|
||||||
|
import { Avatar } from "../Avatar";
|
||||||
|
import { UserMenuContainer } from "../UserMenuContainer";
|
||||||
|
|
||||||
|
const canScreenshare = "getDisplayMedia" in navigator.mediaDevices;
|
||||||
|
// There is currently a bug in Safari our our code with cloning and sending MediaStreams
|
||||||
|
// or with getUsermedia and getDisplaymedia being used within the same session.
|
||||||
|
// For now we can disable screensharing in Safari.
|
||||||
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
|
|
||||||
|
export function InCallView({
|
||||||
|
client,
|
||||||
|
groupCall,
|
||||||
|
roomName,
|
||||||
|
microphoneMuted,
|
||||||
|
localVideoMuted,
|
||||||
|
toggleLocalVideoMuted,
|
||||||
|
toggleMicrophoneMuted,
|
||||||
|
userMediaFeeds,
|
||||||
|
activeSpeaker,
|
||||||
|
onLeave,
|
||||||
|
toggleScreensharing,
|
||||||
|
isScreensharing,
|
||||||
|
screenshareFeeds,
|
||||||
|
simpleGrid,
|
||||||
|
setShowInspector,
|
||||||
|
showInspector,
|
||||||
|
roomId,
|
||||||
|
}) {
|
||||||
|
const [layout, setLayout] = useVideoGridLayout();
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
const participants = [];
|
||||||
|
|
||||||
|
for (const callFeed of userMediaFeeds) {
|
||||||
|
participants.push({
|
||||||
|
id: callFeed.stream.id,
|
||||||
|
usermediaCallFeed: callFeed,
|
||||||
|
isActiveSpeaker:
|
||||||
|
screenshareFeeds.length === 0
|
||||||
|
? callFeed.userId === activeSpeaker
|
||||||
|
: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const callFeed of screenshareFeeds) {
|
||||||
|
const participant = participants.find(
|
||||||
|
(p) => p.usermediaCallFeed.userId === callFeed.userId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (participant) {
|
||||||
|
participant.screenshareCallFeed = callFeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return participants;
|
||||||
|
}, [userMediaFeeds, activeSpeaker, screenshareFeeds]);
|
||||||
|
|
||||||
|
const onFocusTile = useCallback(
|
||||||
|
(tiles, focusedTile) => {
|
||||||
|
if (layout === "freedom") {
|
||||||
|
return tiles.map((tile) => {
|
||||||
|
if (tile === focusedTile) {
|
||||||
|
return { ...tile, presenter: !tile.presenter };
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[layout, setLayout]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderAvatar = useCallback(
|
||||||
|
(roomMember, width, height) => {
|
||||||
|
const avatarUrl = roomMember.user?.avatarUrl;
|
||||||
|
const size = Math.round(Math.min(width, height) / 2);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
key={roomMember.userId}
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius: size,
|
||||||
|
fontSize: Math.round(size / 2),
|
||||||
|
}}
|
||||||
|
src={avatarUrl && getAvatarUrl(client, avatarUrl, 96)}
|
||||||
|
fallback={roomMember.name.slice(0, 1).toUpperCase()}
|
||||||
|
className={styles.avatar}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[client]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.inRoom}>
|
||||||
|
<Header>
|
||||||
|
<LeftNav>
|
||||||
|
<RoomHeaderInfo roomName={roomName} />
|
||||||
|
</LeftNav>
|
||||||
|
<RightNav>
|
||||||
|
<GridLayoutMenu layout={layout} setLayout={setLayout} />
|
||||||
|
<UserMenuContainer disableLogout />
|
||||||
|
</RightNav>
|
||||||
|
</Header>
|
||||||
|
{items.length === 0 ? (
|
||||||
|
<div className={styles.centerMessage}>
|
||||||
|
<p>Waiting for other participants...</p>
|
||||||
|
</div>
|
||||||
|
) : simpleGrid ? (
|
||||||
|
<SimpleVideoGrid items={items} />
|
||||||
|
) : (
|
||||||
|
<VideoGrid
|
||||||
|
items={items}
|
||||||
|
layout={layout}
|
||||||
|
getAvatar={renderAvatar}
|
||||||
|
onFocusTile={onFocusTile}
|
||||||
|
disableAnimations={isSafari}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
<MicButton muted={microphoneMuted} onPress={toggleMicrophoneMuted} />
|
||||||
|
<VideoButton muted={localVideoMuted} onPress={toggleLocalVideoMuted} />
|
||||||
|
{canScreenshare && !isSafari && (
|
||||||
|
<ScreenshareButton
|
||||||
|
enabled={isScreensharing}
|
||||||
|
onPress={toggleScreensharing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<OverflowMenu
|
||||||
|
roomId={roomId}
|
||||||
|
setShowInspector={setShowInspector}
|
||||||
|
showInspector={showInspector}
|
||||||
|
client={client}
|
||||||
|
/>
|
||||||
|
<HangupButton onPress={onLeave} />
|
||||||
|
</div>
|
||||||
|
<GroupCallInspector
|
||||||
|
client={client}
|
||||||
|
groupCall={groupCall}
|
||||||
|
show={showInspector}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
68
src/room/InCallView.module.css
Normal file
68
src/room/InCallView.module.css
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.inRoom {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerMessage {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centerMessage p {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer > * {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer > :last-child {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 800px) {
|
||||||
|
.footer {
|
||||||
|
height: 118px;
|
||||||
|
}
|
||||||
|
}
|
||||||
209
src/room/RoomPage.jsx
Normal file
209
src/room/RoomPage.jsx
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { useLocation, useParams, useHistory } from "react-router-dom";
|
||||||
|
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
|
import { useGroupCall } from "matrix-react-sdk/src/hooks/useGroupCall";
|
||||||
|
import { useClient, useLoadGroupCall } from "../ConferenceCallManagerHooks";
|
||||||
|
import { ErrorView, LoadingView, FullScreenView } from "../FullScreenView";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
import { LobbyView } from "./LobbyView";
|
||||||
|
import { InCallView } from "./InCallView";
|
||||||
|
import { CallEndedView } from "./CallEndedView";
|
||||||
|
|
||||||
|
export function RoomPage() {
|
||||||
|
const [registrationError, setRegistrationError] = useState();
|
||||||
|
const { loading, isAuthenticated, error, client, isPasswordlessUser } =
|
||||||
|
useClient();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !isAuthenticated) {
|
||||||
|
setRegistrationError(new Error("Must be registered"));
|
||||||
|
}
|
||||||
|
}, [loading, isAuthenticated]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingView />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registrationError || error) {
|
||||||
|
return <ErrorView error={registrationError || error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <GroupCall client={client} isPasswordlessUser={isPasswordlessUser} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupCall({ client, isPasswordlessUser }) {
|
||||||
|
const { roomId: maybeRoomId } = useParams();
|
||||||
|
const { hash, search } = useLocation();
|
||||||
|
const [simpleGrid, viaServers] = useMemo(() => {
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
return [params.has("simple"), params.getAll("via")];
|
||||||
|
}, [search]);
|
||||||
|
const roomId = maybeRoomId || hash;
|
||||||
|
const { loading, error, groupCall } = useLoadGroupCall(
|
||||||
|
client,
|
||||||
|
roomId,
|
||||||
|
viaServers
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.groupCall = groupCall;
|
||||||
|
}, [groupCall]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<FullScreenView>
|
||||||
|
<h1>Loading room...</h1>
|
||||||
|
</FullScreenView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorView error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GroupCallView
|
||||||
|
isPasswordlessUser={isPasswordlessUser}
|
||||||
|
client={client}
|
||||||
|
roomId={roomId}
|
||||||
|
groupCall={groupCall}
|
||||||
|
simpleGrid={simpleGrid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GroupCallView({
|
||||||
|
client,
|
||||||
|
isPasswordlessUser,
|
||||||
|
roomId,
|
||||||
|
groupCall,
|
||||||
|
simpleGrid,
|
||||||
|
}) {
|
||||||
|
const [showInspector, setShowInspector] = useState(false);
|
||||||
|
const {
|
||||||
|
state,
|
||||||
|
error,
|
||||||
|
activeSpeaker,
|
||||||
|
userMediaFeeds,
|
||||||
|
microphoneMuted,
|
||||||
|
localVideoMuted,
|
||||||
|
localCallFeed,
|
||||||
|
initLocalCallFeed,
|
||||||
|
enter,
|
||||||
|
leave,
|
||||||
|
toggleLocalVideoMuted,
|
||||||
|
toggleMicrophoneMuted,
|
||||||
|
toggleScreensharing,
|
||||||
|
isScreensharing,
|
||||||
|
localScreenshareFeed,
|
||||||
|
screenshareFeeds,
|
||||||
|
hasLocalParticipant,
|
||||||
|
} = useGroupCall(groupCall);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onHangup(call) {
|
||||||
|
if (call.hangupReason === "ice_failed") {
|
||||||
|
Sentry.captureException(new Error("Call hangup due to ICE failure."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
Sentry.captureException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupCall) {
|
||||||
|
groupCall.on("hangup", onHangup);
|
||||||
|
groupCall.on("error", onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (groupCall) {
|
||||||
|
groupCall.removeListener("hangup", onHangup);
|
||||||
|
groupCall.removeListener("error", onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [groupCall]);
|
||||||
|
|
||||||
|
const [left, setLeft] = useState(false);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const onLeave = useCallback(() => {
|
||||||
|
leave();
|
||||||
|
|
||||||
|
if (!isPasswordlessUser) {
|
||||||
|
history.push("/");
|
||||||
|
} else {
|
||||||
|
setLeft(true);
|
||||||
|
}
|
||||||
|
}, [leave, history]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <ErrorView error={error} />;
|
||||||
|
} else if (state === GroupCallState.Entered) {
|
||||||
|
return (
|
||||||
|
<InCallView
|
||||||
|
groupCall={groupCall}
|
||||||
|
client={client}
|
||||||
|
roomName={groupCall.room.name}
|
||||||
|
microphoneMuted={microphoneMuted}
|
||||||
|
localVideoMuted={localVideoMuted}
|
||||||
|
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||||
|
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||||
|
userMediaFeeds={userMediaFeeds}
|
||||||
|
activeSpeaker={activeSpeaker}
|
||||||
|
onLeave={onLeave}
|
||||||
|
toggleScreensharing={toggleScreensharing}
|
||||||
|
isScreensharing={isScreensharing}
|
||||||
|
localScreenshareFeed={localScreenshareFeed}
|
||||||
|
screenshareFeeds={screenshareFeeds}
|
||||||
|
simpleGrid={simpleGrid}
|
||||||
|
setShowInspector={setShowInspector}
|
||||||
|
showInspector={showInspector}
|
||||||
|
roomId={roomId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === GroupCallState.Entering) {
|
||||||
|
return (
|
||||||
|
<FullScreenView>
|
||||||
|
<h1>Entering room...</h1>
|
||||||
|
</FullScreenView>
|
||||||
|
);
|
||||||
|
} else if (left) {
|
||||||
|
return <CallEndedView client={client} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<LobbyView
|
||||||
|
client={client}
|
||||||
|
hasLocalParticipant={hasLocalParticipant}
|
||||||
|
roomName={groupCall.room.name}
|
||||||
|
state={state}
|
||||||
|
onInitLocalCallFeed={initLocalCallFeed}
|
||||||
|
localCallFeed={localCallFeed}
|
||||||
|
onEnter={enter}
|
||||||
|
microphoneMuted={microphoneMuted}
|
||||||
|
localVideoMuted={localVideoMuted}
|
||||||
|
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||||
|
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||||
|
setShowInspector={setShowInspector}
|
||||||
|
showInspector={showInspector}
|
||||||
|
roomId={roomId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/room/RoomRedirect.jsx
Normal file
25
src/room/RoomRedirect.jsx
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useLocation, useHistory } from "react-router-dom";
|
||||||
|
import { defaultHomeserverHost } from "../ConferenceCallManagerHooks";
|
||||||
|
import { LoadingView } from "../FullScreenView";
|
||||||
|
|
||||||
|
export function RoomRedirect() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let roomId = pathname;
|
||||||
|
|
||||||
|
if (pathname.startsWith("/")) {
|
||||||
|
roomId = roomId.substr(1, roomId.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||||
|
roomId = `#${roomId}:${defaultHomeserverHost}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
history.replace(`/room/${roomId}`);
|
||||||
|
}, [pathname, history]);
|
||||||
|
|
||||||
|
return <LoadingView />;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue