WIP refactor for removing m.call events
This commit is contained in:
@@ -15,17 +15,13 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { HTMLAttributes, ReactNode, useCallback } from "react";
|
import { HTMLAttributes, ReactNode } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import styles from "./Header.module.css";
|
import styles from "./Header.module.css";
|
||||||
import { useModalTriggerState } from "./Modal";
|
|
||||||
import { Button } from "./button";
|
|
||||||
import { ReactComponent as Logo } from "./icons/Logo.svg";
|
import { ReactComponent as Logo } from "./icons/Logo.svg";
|
||||||
import { Subtitle } from "./typography/Typography";
|
import { Subtitle } from "./typography/Typography";
|
||||||
import { IncompatibleVersionModal } from "./IncompatibleVersionModal";
|
|
||||||
|
|
||||||
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -125,34 +121,3 @@ export function RoomHeaderInfo({ roomName }: RoomHeaderInfo) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VersionMismatchWarningProps {
|
|
||||||
users: Set<string>;
|
|
||||||
room: Room;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VersionMismatchWarning({
|
|
||||||
users,
|
|
||||||
room,
|
|
||||||
}: VersionMismatchWarningProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { modalState, modalProps } = useModalTriggerState();
|
|
||||||
|
|
||||||
const onDetailsClick = useCallback(() => {
|
|
||||||
modalState.open();
|
|
||||||
}, [modalState]);
|
|
||||||
|
|
||||||
if (users.size === 0) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className={styles.versionMismatchWarning}>
|
|
||||||
{t("Incompatible versions!")}
|
|
||||||
<Button variant="link" onClick={onDetailsClick}>
|
|
||||||
{t("Details")}
|
|
||||||
</Button>
|
|
||||||
{modalState.isOpen && (
|
|
||||||
<IncompatibleVersionModal userIds={users} room={room} {...modalProps} />
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2022 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 { Room } from "matrix-js-sdk/src/models/room";
|
|
||||||
import { FC, useMemo } from "react";
|
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { Modal, ModalContent } from "./Modal";
|
|
||||||
import { Body } from "./typography/Typography";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
userIds: Set<string>;
|
|
||||||
room: Room;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IncompatibleVersionModal: FC<Props> = ({
|
|
||||||
userIds,
|
|
||||||
room,
|
|
||||||
onClose,
|
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const userLis = useMemo(
|
|
||||||
() => [...userIds].map((u) => <li>{room.getMember(u)?.name ?? u}</li>),
|
|
||||||
[userIds, room]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={t("Incompatible versions")}
|
|
||||||
isDismissable
|
|
||||||
onClose={onClose}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<ModalContent>
|
|
||||||
<Body>
|
|
||||||
<Trans>
|
|
||||||
Other users are trying to join this call from incompatible versions.
|
|
||||||
These users should ensure that they have refreshed their browsers:
|
|
||||||
<ul>{userLis}</ul>
|
|
||||||
</Trans>
|
|
||||||
</Body>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -172,7 +172,6 @@ export async function initClient(
|
|||||||
localTimeoutMs: 5000,
|
localTimeoutMs: 5000,
|
||||||
useE2eForGroupCall: e2eEnabled,
|
useE2eForGroupCall: e2eEnabled,
|
||||||
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
||||||
useLivekitForGroupCalls: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
@@ -26,7 +26,7 @@ interface Props {
|
|||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
roomIdOrAlias: string;
|
roomIdOrAlias: string;
|
||||||
viaServers: string[];
|
viaServers: string[];
|
||||||
children: (groupCall: GroupCall) => ReactNode;
|
children: (rtcSession: MatrixRTCSession) => ReactNode;
|
||||||
createPtt: boolean;
|
createPtt: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export function GroupCallLoader({
|
|||||||
</FullScreenView>
|
</FullScreenView>
|
||||||
);
|
);
|
||||||
case "loaded":
|
case "loaded":
|
||||||
return <>{children(groupCallState.groupCall)}</>;
|
return <>{children(groupCallState.rtcSession)}</>;
|
||||||
case "failed":
|
case "failed":
|
||||||
return <ErrorView error={groupCallState.error} />;
|
return <ErrorView error={groupCallState.error} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,33 +16,35 @@ limitations under the License.
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { GroupCall, GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Room } from "livekit-client";
|
import { Room } from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { Focus } from "matrix-js-sdk/src/matrixrtc/focus";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import { widget, ElementWidgetActions, JoinCallData } from "../widget";
|
import { widget, ElementWidgetActions, JoinCallData } from "../widget";
|
||||||
import { useGroupCall } from "./useGroupCall";
|
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
import { LobbyView } from "./LobbyView";
|
import { LobbyView } from "./LobbyView";
|
||||||
import { MatrixInfo } from "./VideoPreview";
|
import { MatrixInfo } from "./VideoPreview";
|
||||||
import { CallEndedView } from "./CallEndedView";
|
import { CallEndedView } from "./CallEndedView";
|
||||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { useProfile } from "../profile/useProfile";
|
import { useProfile } from "../profile/useProfile";
|
||||||
import { E2EEConfig } from "../livekit/useLiveKit";
|
import { E2EEConfig } from "../livekit/useLiveKit";
|
||||||
import { findDeviceByName } from "../media-utils";
|
import { findDeviceByName } from "../media-utils";
|
||||||
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
//import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||||
import { ActiveCall } from "./InCallView";
|
import { ActiveCall } from "./InCallView";
|
||||||
import { Config } from "../config/Config";
|
|
||||||
import { MuteStates, useMuteStates } from "./MuteStates";
|
import { MuteStates, useMuteStates } from "./MuteStates";
|
||||||
import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
|
import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext";
|
||||||
|
import { LivekitFocus } from "../livekit/LivekitFocus";
|
||||||
|
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
|
||||||
|
import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers";
|
||||||
|
import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
groupCall?: GroupCall;
|
rtcSession?: MatrixRTCSession;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +54,7 @@ interface Props {
|
|||||||
isEmbedded: boolean;
|
isEmbedded: boolean;
|
||||||
preload: boolean;
|
preload: boolean;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
groupCall: GroupCall;
|
rtcSession: MatrixRTCSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GroupCallView({
|
export function GroupCallView({
|
||||||
@@ -61,9 +63,9 @@ export function GroupCallView({
|
|||||||
isEmbedded,
|
isEmbedded,
|
||||||
preload,
|
preload,
|
||||||
hideHeader,
|
hideHeader,
|
||||||
groupCall,
|
rtcSession,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
/*const {
|
||||||
state,
|
state,
|
||||||
error,
|
error,
|
||||||
enter,
|
enter,
|
||||||
@@ -71,33 +73,36 @@ export function GroupCallView({
|
|||||||
participants,
|
participants,
|
||||||
unencryptedEventsFromUsers,
|
unencryptedEventsFromUsers,
|
||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
} = useGroupCall(groupCall, client);
|
} = useGroupCall(groupCall, client);*/
|
||||||
|
|
||||||
|
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||||
|
const isJoined = useMatrixRTCSessionJoinState(rtcSession);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.groupCall = groupCall;
|
window.rtcSession = rtcSession;
|
||||||
return () => {
|
return () => {
|
||||||
delete window.groupCall;
|
delete window.rtcSession;
|
||||||
};
|
};
|
||||||
}, [groupCall]);
|
}, [rtcSession]);
|
||||||
|
|
||||||
const { displayName, avatarUrl } = useProfile(client);
|
const { displayName, avatarUrl } = useProfile(client);
|
||||||
const matrixInfo = useMemo((): MatrixInfo => {
|
const matrixInfo = useMemo((): MatrixInfo => {
|
||||||
return {
|
return {
|
||||||
displayName: displayName!,
|
displayName: displayName!,
|
||||||
avatarUrl: avatarUrl!,
|
avatarUrl: avatarUrl!,
|
||||||
roomId: groupCall.room.roomId,
|
roomId: rtcSession.room.roomId,
|
||||||
roomName: groupCall.room.name,
|
roomName: rtcSession.room.name,
|
||||||
roomAlias: groupCall.room.getCanonicalAlias(),
|
roomAlias: rtcSession.room.getCanonicalAlias(),
|
||||||
};
|
};
|
||||||
}, [displayName, avatarUrl, groupCall]);
|
}, [displayName, avatarUrl, rtcSession]);
|
||||||
|
|
||||||
const deviceContext = useMediaDevices();
|
const deviceContext = useMediaDevices();
|
||||||
const latestDevices = useRef<MediaDevices>();
|
const latestDevices = useRef<MediaDevices>();
|
||||||
latestDevices.current = deviceContext;
|
latestDevices.current = deviceContext;
|
||||||
|
|
||||||
const muteStates = useMuteStates(participants.size);
|
const muteStates = useMuteStates(memberships.length);
|
||||||
const latestMuteStates = useRef<MuteStates>();
|
const latestMuteStates = useRef<MuteStates>();
|
||||||
latestMuteStates.current = muteStates;
|
latestMuteStates.current = muteStates;
|
||||||
|
|
||||||
@@ -154,10 +159,13 @@ export function GroupCallView({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await enter();
|
enterRTCSession(rtcSession);
|
||||||
|
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
// we only have room sessions right now, so call ID is the emprty string - we use the room ID
|
||||||
|
PosthogAnalytics.instance.eventCallStarted.track(
|
||||||
|
rtcSession.room.roomId
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
widget!.api.setAlwaysOnScreen(true),
|
widget!.api.setAlwaysOnScreen(true),
|
||||||
@@ -170,19 +178,18 @@ export function GroupCallView({
|
|||||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [groupCall, preload, enter]);
|
}, [rtcSession, preload]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEmbedded && !preload) {
|
if (isEmbedded && !preload) {
|
||||||
// In embedded mode, bypass the lobby and just enter the call straight away
|
// In embedded mode, bypass the lobby and just enter the call straight away
|
||||||
enter();
|
enterRTCSession(rtcSession);
|
||||||
|
|
||||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||||
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
// use the room ID as above
|
||||||
|
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||||
}
|
}
|
||||||
}, [groupCall, isEmbedded, preload, enter]);
|
}, [rtcSession, isEmbedded, preload]);
|
||||||
|
|
||||||
useSentryGroupCallHandler(groupCall);
|
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||||
@@ -193,21 +200,16 @@ export function GroupCallView({
|
|||||||
setLeaveError(leaveError);
|
setLeaveError(leaveError);
|
||||||
setLeft(true);
|
setLeft(true);
|
||||||
|
|
||||||
let participantCount = 0;
|
|
||||||
for (const deviceMap of groupCall.participants.values()) {
|
|
||||||
participantCount += deviceMap.size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
// In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent,
|
||||||
// therefore we want the event to be sent instantly without getting queued/batched.
|
// therefore we want the event to be sent instantly without getting queued/batched.
|
||||||
const sendInstantly = !!widget;
|
const sendInstantly = !!widget;
|
||||||
PosthogAnalytics.instance.eventCallEnded.track(
|
PosthogAnalytics.instance.eventCallEnded.track(
|
||||||
groupCall.groupCallId,
|
rtcSession.room.roomId,
|
||||||
participantCount,
|
rtcSession.memberships.length,
|
||||||
sendInstantly
|
sendInstantly
|
||||||
);
|
);
|
||||||
|
|
||||||
leave();
|
leaveRTCSession(rtcSession);
|
||||||
if (widget) {
|
if (widget) {
|
||||||
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
// we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||||
@@ -224,13 +226,13 @@ export function GroupCallView({
|
|||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[groupCall, leave, isPasswordlessUser, isEmbedded, history]
|
[rtcSession, isPasswordlessUser, isEmbedded, history]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && state === GroupCallState.Entered) {
|
if (widget && isJoined) {
|
||||||
const onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
const onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
||||||
leave();
|
leaveRTCSession(rtcSession);
|
||||||
await widget!.api.transport.reply(ev.detail, {});
|
await widget!.api.transport.reply(ev.detail, {});
|
||||||
widget!.api.setAlwaysOnScreen(false);
|
widget!.api.setAlwaysOnScreen(false);
|
||||||
};
|
};
|
||||||
@@ -239,7 +241,7 @@ export function GroupCallView({
|
|||||||
widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
widget!.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [groupCall, state, leave]);
|
}, [isJoined, rtcSession]);
|
||||||
|
|
||||||
const [e2eeConfig, setE2EEConfig] = useState<E2EEConfig | undefined>(
|
const [e2eeConfig, setE2EEConfig] = useState<E2EEConfig | undefined>(
|
||||||
undefined
|
undefined
|
||||||
@@ -248,36 +250,40 @@ export function GroupCallView({
|
|||||||
const onReconnect = useCallback(() => {
|
const onReconnect = useCallback(() => {
|
||||||
setLeft(false);
|
setLeft(false);
|
||||||
setLeaveError(undefined);
|
setLeaveError(undefined);
|
||||||
groupCall.enter();
|
rtcSession.joinRoomSession();
|
||||||
}, [groupCall]);
|
}, [rtcSession]);
|
||||||
|
|
||||||
const livekitServiceURL =
|
const focus: Focus | undefined = rtcSession
|
||||||
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
.getOldestMembership()
|
||||||
if (!livekitServiceURL) {
|
?.getActiveFoci()?.[0];
|
||||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
if (
|
||||||
|
!focus ||
|
||||||
|
focus.type !== "livekit" ||
|
||||||
|
!(focus as LivekitFocus).livekit_alias ||
|
||||||
|
!(focus as LivekitFocus).livekit_service_url
|
||||||
|
) {
|
||||||
|
logger.error("Incompatible focus on call", focus);
|
||||||
|
return <ErrorView error={new Error("Call focus is not compatible!")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (isJoined) {
|
||||||
return <ErrorView error={error} />;
|
|
||||||
} else if (state === GroupCallState.Entered) {
|
|
||||||
return (
|
return (
|
||||||
<OpenIDLoader
|
/*<OpenIDLoader
|
||||||
client={client}
|
client={client}
|
||||||
groupCall={groupCall}
|
groupCall={groupCall}
|
||||||
roomName={`${groupCall.room.roomId}-${groupCall.groupCallId}`}
|
roomName={`${groupCall.room.roomId}-${groupCall.groupCallId}`}
|
||||||
>
|
>*/
|
||||||
<ActiveCall
|
<ActiveCall
|
||||||
client={client}
|
client={client}
|
||||||
groupCall={groupCall}
|
rtcSession={rtcSession}
|
||||||
participants={participants}
|
memberships={memberships}
|
||||||
onLeave={onLeave}
|
onLeave={onLeave}
|
||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
hideHeader={hideHeader}
|
||||||
hideHeader={hideHeader}
|
muteStates={muteStates}
|
||||||
muteStates={muteStates}
|
e2eeConfig={e2eeConfig}
|
||||||
e2eeConfig={e2eeConfig}
|
//otelGroupCallMembership={otelGroupCallMembership}
|
||||||
otelGroupCallMembership={otelGroupCallMembership}
|
/>
|
||||||
/>
|
//</OpenIDLoader>
|
||||||
</OpenIDLoader>
|
|
||||||
);
|
);
|
||||||
} else if (left) {
|
} else if (left) {
|
||||||
// The call ended view is shown for two reasons: prompting guests to create
|
// The call ended view is shown for two reasons: prompting guests to create
|
||||||
@@ -293,7 +299,7 @@ export function GroupCallView({
|
|||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<CallEndedView
|
<CallEndedView
|
||||||
endedCallId={groupCall.groupCallId}
|
endedCallId={rtcSession.room.roomId}
|
||||||
client={client}
|
client={client}
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
isPasswordlessUser={isPasswordlessUser}
|
||||||
leaveError={leaveError}
|
leaveError={leaveError}
|
||||||
@@ -321,7 +327,7 @@ export function GroupCallView({
|
|||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onEnter={(e2eeConfig?: E2EEConfig) => {
|
onEnter={(e2eeConfig?: E2EEConfig) => {
|
||||||
setE2EEConfig(e2eeConfig);
|
setE2EEConfig(e2eeConfig);
|
||||||
enter();
|
enterRTCSession(rtcSession);
|
||||||
}}
|
}}
|
||||||
isEmbedded={isEmbedded}
|
isEmbedded={isEmbedded}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import classNames from "classnames";
|
|||||||
import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client";
|
import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { Ref, useCallback, useEffect, useMemo, useRef } from "react";
|
import { Ref, useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
@@ -35,6 +34,8 @@ import { OverlayTriggerState } from "@react-stately/overlays";
|
|||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { RoomEventCallbacks } from "livekit-client/dist/src/room/Room";
|
import { RoomEventCallbacks } from "livekit-client/dist/src/room/Room";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
@@ -45,22 +46,13 @@ import {
|
|||||||
SettingsButton,
|
SettingsButton,
|
||||||
InviteButton,
|
InviteButton,
|
||||||
} from "../button";
|
} from "../button";
|
||||||
import {
|
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
|
||||||
Header,
|
|
||||||
LeftNav,
|
|
||||||
RightNav,
|
|
||||||
RoomHeaderInfo,
|
|
||||||
VersionMismatchWarning,
|
|
||||||
} from "../Header";
|
|
||||||
import {
|
import {
|
||||||
useVideoGridLayout,
|
useVideoGridLayout,
|
||||||
TileDescriptor,
|
TileDescriptor,
|
||||||
VideoGrid,
|
VideoGrid,
|
||||||
} from "../video-grid/VideoGrid";
|
} from "../video-grid/VideoGrid";
|
||||||
import {
|
import { useShowConnectionStats } from "../settings/useSetting";
|
||||||
useShowInspector,
|
|
||||||
useShowConnectionStats,
|
|
||||||
} from "../settings/useSetting";
|
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
@@ -68,10 +60,8 @@ import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts";
|
|||||||
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
import { usePrefersReducedMotion } from "../usePrefersReducedMotion";
|
||||||
import { ElementWidgetActions, widget } from "../widget";
|
import { ElementWidgetActions, widget } from "../widget";
|
||||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||||
import { GroupCallInspector } from "./GroupCallInspector";
|
|
||||||
import styles from "./InCallView.module.css";
|
import styles from "./InCallView.module.css";
|
||||||
import { useJoinRule } from "./useJoinRule";
|
import { useJoinRule } from "./useJoinRule";
|
||||||
import { ParticipantInfo } from "./useGroupCall";
|
|
||||||
import { ItemData, TileContent, VideoTile } from "../video-grid/VideoTile";
|
import { ItemData, TileContent, VideoTile } from "../video-grid/VideoTile";
|
||||||
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||||
@@ -120,24 +110,22 @@ export function ActiveCall(props: ActiveCallProps) {
|
|||||||
|
|
||||||
export interface InCallViewProps {
|
export interface InCallViewProps {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
groupCall: GroupCall;
|
rtcSession: MatrixRTCSession;
|
||||||
livekitRoom: Room;
|
livekitRoom: Room;
|
||||||
muteStates: MuteStates;
|
muteStates: MuteStates;
|
||||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
memberships: CallMembership[];
|
||||||
onLeave: (error?: Error) => void;
|
onLeave: (error?: Error) => void;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InCallView({
|
export function InCallView({
|
||||||
client,
|
client,
|
||||||
groupCall,
|
rtcSession,
|
||||||
livekitRoom,
|
livekitRoom,
|
||||||
muteStates,
|
muteStates,
|
||||||
participants,
|
memberships,
|
||||||
onLeave,
|
onLeave,
|
||||||
unencryptedEventsFromUsers,
|
|
||||||
hideHeader,
|
hideHeader,
|
||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
}: InCallViewProps) {
|
}: InCallViewProps) {
|
||||||
@@ -161,7 +149,7 @@ export function InCallView({
|
|||||||
screenSharingTracks.length > 0
|
screenSharingTracks.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showInspector] = useShowInspector();
|
//const [showInspector] = useShowInspector();
|
||||||
const [showConnectionStats] = useShowConnectionStats();
|
const [showConnectionStats] = useShowConnectionStats();
|
||||||
|
|
||||||
const { hideScreensharing } = useUrlParams();
|
const { hideScreensharing } = useUrlParams();
|
||||||
@@ -179,7 +167,7 @@ export function InCallView({
|
|||||||
[muteStates]
|
[muteStates]
|
||||||
);
|
);
|
||||||
|
|
||||||
const joinRule = useJoinRule(groupCall.room);
|
const joinRule = useJoinRule(rtcSession.room);
|
||||||
|
|
||||||
// This function incorrectly assumes that there is a camera and microphone, which is not always the case.
|
// This function incorrectly assumes that there is a camera and microphone, which is not always the case.
|
||||||
// TODO: Make sure that this module is resilient when it comes to camera/microphone availability!
|
// TODO: Make sure that this module is resilient when it comes to camera/microphone availability!
|
||||||
@@ -250,7 +238,7 @@ export function InCallView({
|
|||||||
const reducedControls = boundsValid && bounds.width <= 400;
|
const reducedControls = boundsValid && bounds.width <= 400;
|
||||||
const noControls = reducedControls && bounds.height <= 400;
|
const noControls = reducedControls && bounds.height <= 400;
|
||||||
|
|
||||||
const items = useParticipantTiles(livekitRoom, participants);
|
const items = useParticipantTiles(livekitRoom, memberships);
|
||||||
const { fullscreenItem, toggleFullscreen, exitFullscreen } =
|
const { fullscreenItem, toggleFullscreen, exitFullscreen } =
|
||||||
useFullscreen(items);
|
useFullscreen(items);
|
||||||
|
|
||||||
@@ -324,7 +312,7 @@ export function InCallView({
|
|||||||
const {
|
const {
|
||||||
modalState: rageshakeRequestModalState,
|
modalState: rageshakeRequestModalState,
|
||||||
modalProps: rageshakeRequestModalProps,
|
modalProps: rageshakeRequestModalProps,
|
||||||
} = useRageshakeRequestModal(groupCall.room.roomId);
|
} = useRageshakeRequestModal(rtcSession.room.roomId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
modalState: settingsModalState,
|
modalState: settingsModalState,
|
||||||
@@ -419,11 +407,7 @@ export function InCallView({
|
|||||||
{!hideHeader && maximisedParticipant === null && (
|
{!hideHeader && maximisedParticipant === null && (
|
||||||
<Header>
|
<Header>
|
||||||
<LeftNav>
|
<LeftNav>
|
||||||
<RoomHeaderInfo roomName={groupCall.room.name} />
|
<RoomHeaderInfo roomName={rtcSession.room.name} />
|
||||||
<VersionMismatchWarning
|
|
||||||
users={unencryptedEventsFromUsers}
|
|
||||||
room={groupCall.room}
|
|
||||||
/>
|
|
||||||
<E2EELock />
|
<E2EELock />
|
||||||
</LeftNav>
|
</LeftNav>
|
||||||
<RightNav>
|
<RightNav>
|
||||||
@@ -439,31 +423,31 @@ export function InCallView({
|
|||||||
{renderContent()}
|
{renderContent()}
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</div>
|
||||||
{otelGroupCallMembership && (
|
{/*otelGroupCallMembership && (
|
||||||
<GroupCallInspector
|
<GroupCallInspector
|
||||||
client={client}
|
client={client}
|
||||||
groupCall={groupCall}
|
groupCall={groupCall}
|
||||||
otelGroupCallMembership={otelGroupCallMembership}
|
otelGroupCallMembership={otelGroupCallMembership}
|
||||||
show={showInspector}
|
show={showInspector}
|
||||||
/>
|
/>
|
||||||
)}
|
)*/}
|
||||||
{rageshakeRequestModalState.isOpen && !noControls && (
|
{rageshakeRequestModalState.isOpen && !noControls && (
|
||||||
<RageshakeRequestModal
|
<RageshakeRequestModal
|
||||||
{...rageshakeRequestModalProps}
|
{...rageshakeRequestModalProps}
|
||||||
roomId={groupCall.room.roomId}
|
roomId={rtcSession.room.roomId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{settingsModalState.isOpen && (
|
{settingsModalState.isOpen && (
|
||||||
<SettingsModal
|
<SettingsModal
|
||||||
client={client}
|
client={client}
|
||||||
roomId={groupCall.room.roomId}
|
roomId={rtcSession.room.roomId}
|
||||||
{...settingsModalProps}
|
{...settingsModalProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{inviteModalState.isOpen && (
|
{inviteModalState.isOpen && (
|
||||||
<InviteModal
|
<InviteModal
|
||||||
roomIdOrAlias={
|
roomIdOrAlias={
|
||||||
groupCall.room.getCanonicalAlias() ?? groupCall.room.roomId
|
rtcSession.room.getCanonicalAlias() ?? rtcSession.room.roomId
|
||||||
}
|
}
|
||||||
{...inviteModalProps}
|
{...inviteModalProps}
|
||||||
/>
|
/>
|
||||||
@@ -474,22 +458,26 @@ export function InCallView({
|
|||||||
|
|
||||||
function useParticipantTiles(
|
function useParticipantTiles(
|
||||||
livekitRoom: Room,
|
livekitRoom: Room,
|
||||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>
|
memberships: CallMembership[]
|
||||||
): TileDescriptor<ItemData>[] {
|
): TileDescriptor<ItemData>[] {
|
||||||
const sfuParticipants = useParticipants({
|
const sfuParticipants = useParticipants({
|
||||||
room: livekitRoom,
|
room: livekitRoom,
|
||||||
});
|
});
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
// The IDs of the participants who published membership event to the room (i.e. are present from Matrix perspective).
|
|
||||||
const matrixParticipants: Map<string, RoomMember> = new Map(
|
const matrixParticipants: Map<string, RoomMember> = new Map(
|
||||||
|
memberships.map((m) => [`${m.member.userId}:${m.deviceId}`, m.member])
|
||||||
|
);
|
||||||
|
|
||||||
|
// The IDs of the participants who published membership event to the room (i.e. are present from Matrix perspective).
|
||||||
|
/*const matrixParticipants: Map<string, RoomMember> = new Map(
|
||||||
[...participants.entries()].flatMap(([user, devicesMap]) => {
|
[...participants.entries()].flatMap(([user, devicesMap]) => {
|
||||||
return [...devicesMap.keys()].map((deviceId) => [
|
return [...devicesMap.keys()].map((deviceId) => [
|
||||||
`${user.userId}:${deviceId}`,
|
`${user.userId}:${deviceId}`,
|
||||||
user,
|
user,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
);
|
);*/
|
||||||
|
|
||||||
const hasPresenter =
|
const hasPresenter =
|
||||||
sfuParticipants.find((p) => p.isScreenShareEnabled) !== undefined;
|
sfuParticipants.find((p) => p.isScreenShareEnabled) !== undefined;
|
||||||
@@ -558,7 +546,7 @@ function useParticipantTiles(
|
|||||||
// If every item is a ghost, that probably means we're still connecting and
|
// If every item is a ghost, that probably means we're still connecting and
|
||||||
// shouldn't bother showing anything yet
|
// shouldn't bother showing anything yet
|
||||||
return allGhosts ? [] : tiles;
|
return allGhosts ? [] : tiles;
|
||||||
}, [participants, sfuParticipants]);
|
}, [memberships, sfuParticipants]);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { FC, useEffect, useState, useCallback } from "react";
|
import { FC, useEffect, useState, useCallback } from "react";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { useClientLegacy } from "../ClientContext";
|
import { useClientLegacy } from "../ClientContext";
|
||||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||||
import { RoomAuthView } from "./RoomAuthView";
|
import { RoomAuthView } from "./RoomAuthView";
|
||||||
@@ -73,10 +73,10 @@ export const RoomPage: FC = () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const groupCallView = useCallback(
|
const groupCallView = useCallback(
|
||||||
(groupCall: GroupCall) => (
|
(rtcSession: MatrixRTCSession) => (
|
||||||
<GroupCallView
|
<GroupCallView
|
||||||
client={client!}
|
client={client!}
|
||||||
groupCall={groupCall}
|
rtcSession={rtcSession}
|
||||||
isPasswordlessUser={passwordlessUser}
|
isPasswordlessUser={passwordlessUser}
|
||||||
isEmbedded={isEmbedded}
|
isEmbedded={isEmbedded}
|
||||||
preload={preload}
|
preload={preload}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2022 New Vector Ltd
|
Copyright 2022-2023 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
SummaryStatsReport,
|
SummaryStatsReport,
|
||||||
CallFeedReport,
|
CallFeedReport,
|
||||||
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
} from "matrix-js-sdk/src/webrtc/stats/statsReport";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import { usePageUnload } from "./usePageUnload";
|
import { usePageUnload } from "./usePageUnload";
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
@@ -77,7 +78,6 @@ interface UseGroupCallReturnType {
|
|||||||
screenshareFeeds: CallFeed[];
|
screenshareFeeds: CallFeed[];
|
||||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||||
hasLocalParticipant: boolean;
|
hasLocalParticipant: boolean;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
|
||||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,14 +103,14 @@ interface State {
|
|||||||
let groupCallOTelMembership: OTelGroupCallMembership | undefined;
|
let groupCallOTelMembership: OTelGroupCallMembership | undefined;
|
||||||
let groupCallOTelMembershipGroupCallId: string;
|
let groupCallOTelMembershipGroupCallId: string;
|
||||||
|
|
||||||
function getParticipants(
|
/*function getParticipants(
|
||||||
groupCall: GroupCall
|
rtcSession: MatrixRTCSession
|
||||||
): Map<RoomMember, Map<string, ParticipantInfo>> {
|
): Map<RoomMember, Map<string, ParticipantInfo>> {
|
||||||
const participants = new Map<RoomMember, Map<string, ParticipantInfo>>();
|
const participants = new Map<RoomMember, Map<string, ParticipantInfo>>();
|
||||||
|
|
||||||
for (const [member, participantsStateMap] of groupCall.participants) {
|
for (const membership of rtcSession.memberships) {
|
||||||
const participantInfoMap = new Map<string, ParticipantInfo>();
|
const participantInfoMap = new Map<string, ParticipantInfo>();
|
||||||
participants.set(member, participantInfoMap);
|
participants.set(membership.member, participantInfoMap);
|
||||||
|
|
||||||
for (const [deviceId, participant] of participantsStateMap) {
|
for (const [deviceId, participant] of participantsStateMap) {
|
||||||
const feed = groupCall.userMediaFeeds.find(
|
const feed = groupCall.userMediaFeeds.find(
|
||||||
@@ -141,10 +141,10 @@ function getParticipants(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return participants;
|
return participants;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
export function useGroupCall(
|
export function useGroupCall(
|
||||||
groupCall: GroupCall,
|
rtcSession: MatrixRTCSession,
|
||||||
client: MatrixClient
|
client: MatrixClient
|
||||||
): UseGroupCallReturnType {
|
): UseGroupCallReturnType {
|
||||||
const [
|
const [
|
||||||
@@ -171,7 +171,7 @@ export function useGroupCall(
|
|||||||
isScreensharing: false,
|
isScreensharing: false,
|
||||||
screenshareFeeds: [],
|
screenshareFeeds: [],
|
||||||
requestingScreenshare: false,
|
requestingScreenshare: false,
|
||||||
participants: getParticipants(groupCall),
|
participants: getParticipants(rtcSession),
|
||||||
hasLocalParticipant: false,
|
hasLocalParticipant: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -15,28 +15,19 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
|
||||||
import {
|
|
||||||
GroupCallType,
|
|
||||||
GroupCallIntent,
|
|
||||||
} from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEventHandler";
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { SyncState } from "matrix-js-sdk/src/sync";
|
import { SyncState } from "matrix-js-sdk/src/sync";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
|
||||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||||
import { translatedError } from "../TranslatedError";
|
|
||||||
import { widget } from "../widget";
|
|
||||||
|
|
||||||
const STATS_COLLECT_INTERVAL_TIME_MS = 10000;
|
|
||||||
|
|
||||||
export type GroupCallLoaded = {
|
export type GroupCallLoaded = {
|
||||||
kind: "loaded";
|
kind: "loaded";
|
||||||
groupCall: GroupCall;
|
rtcSession: MatrixRTCSession;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GroupCallLoadFailed = {
|
export type GroupCallLoadFailed = {
|
||||||
@@ -115,61 +106,12 @@ export const useLoadGroupCall = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchOrCreateGroupCall = async (): Promise<GroupCall> => {
|
const fetchOrCreateGroupCall = async (): Promise<MatrixRTCSession> => {
|
||||||
const room = await fetchOrCreateRoom();
|
const room = await fetchOrCreateRoom();
|
||||||
logger.debug(`Fetched / joined room ${roomIdOrAlias}`);
|
logger.debug(`Fetched / joined room ${roomIdOrAlias}`);
|
||||||
let groupCall = client.getGroupCallForRoom(room.roomId);
|
|
||||||
logger.debug("Got group call", groupCall?.groupCallId);
|
|
||||||
|
|
||||||
if (groupCall) {
|
const rtcSession = client.matrixRTC.getRoomSession(room);
|
||||||
groupCall.setGroupCallStatsInterval(STATS_COLLECT_INTERVAL_TIME_MS);
|
return rtcSession;
|
||||||
return groupCall;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!widget &&
|
|
||||||
room.currentState.mayClientSendStateEvent(
|
|
||||||
EventType.GroupCallPrefix,
|
|
||||||
client
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// The call doesn't exist, but we can create it
|
|
||||||
console.log(
|
|
||||||
`No call found in ${roomIdOrAlias}: creating ${
|
|
||||||
createPtt ? "PTT" : "video"
|
|
||||||
} call`
|
|
||||||
);
|
|
||||||
groupCall = await client.createGroupCall(
|
|
||||||
room.roomId,
|
|
||||||
createPtt ? GroupCallType.Voice : GroupCallType.Video,
|
|
||||||
createPtt,
|
|
||||||
GroupCallIntent.Room
|
|
||||||
);
|
|
||||||
groupCall.setGroupCallStatsInterval(STATS_COLLECT_INTERVAL_TIME_MS);
|
|
||||||
return groupCall;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't have permission to create the call, so all we can do is wait
|
|
||||||
// for one to come in
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const onGroupCallIncoming = (groupCall: GroupCall) => {
|
|
||||||
if (groupCall?.room.roomId === room.roomId) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
groupCall.setGroupCallStatsInterval(STATS_COLLECT_INTERVAL_TIME_MS);
|
|
||||||
client.off(
|
|
||||||
GroupCallEventHandlerEvent.Incoming,
|
|
||||||
onGroupCallIncoming
|
|
||||||
);
|
|
||||||
resolve(groupCall);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
client.on(GroupCallEventHandlerEvent.Incoming, onGroupCallIncoming);
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
client.off(GroupCallEventHandlerEvent.Incoming, onGroupCallIncoming);
|
|
||||||
reject(translatedError("Fetching group call timed out.", t));
|
|
||||||
}, 30000);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const waitForClientSyncing = async () => {
|
const waitForClientSyncing = async () => {
|
||||||
@@ -192,7 +134,7 @@ export const useLoadGroupCall = (
|
|||||||
|
|
||||||
waitForClientSyncing()
|
waitForClientSyncing()
|
||||||
.then(fetchOrCreateGroupCall)
|
.then(fetchOrCreateGroupCall)
|
||||||
.then((groupCall) => setState({ kind: "loaded", groupCall }))
|
.then((rtcSession) => setState({ kind: "loaded", rtcSession }))
|
||||||
.catch((error) => setState({ kind: "failed", error }));
|
.catch((error) => setState({ kind: "failed", error }));
|
||||||
}, [client, roomIdOrAlias, viaServers, createPtt, t]);
|
}, [client, roomIdOrAlias, viaServers, createPtt, t]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2022 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 { useEffect } from "react";
|
|
||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import { GroupCall, GroupCallEvent } from "matrix-js-sdk/src/webrtc/groupCall";
|
|
||||||
import { CallEvent, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
|
||||||
|
|
||||||
export function useSentryGroupCallHandler(groupCall: GroupCall) {
|
|
||||||
useEffect(() => {
|
|
||||||
function onHangup(call: MatrixCall) {
|
|
||||||
if (call.hangupReason === "ice_failed") {
|
|
||||||
Sentry.captureException(new Error("Call hangup due to ICE failure."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError(error: Error) {
|
|
||||||
Sentry.captureException(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupCall) {
|
|
||||||
groupCall.on(CallEvent.Hangup, onHangup);
|
|
||||||
groupCall.on(GroupCallEvent.Error, onError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (groupCall) {
|
|
||||||
groupCall.removeListener(CallEvent.Hangup, onHangup);
|
|
||||||
groupCall.removeListener(GroupCallEvent.Error, onError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [groupCall]);
|
|
||||||
}
|
|
||||||
44
src/useMatrixRTCSessionJoinState.ts
Normal file
44
src/useMatrixRTCSessionJoinState.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 {
|
||||||
|
MatrixRTCSession,
|
||||||
|
MatrixRTCSessionEvent,
|
||||||
|
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useMatrixRTCSessionJoinState(
|
||||||
|
rtcSession: MatrixRTCSession
|
||||||
|
): boolean {
|
||||||
|
const [isJoined, setJoined] = useState(rtcSession.isJoined());
|
||||||
|
|
||||||
|
const onJoinStateChanged = useCallback(() => {
|
||||||
|
setJoined(rtcSession.isJoined());
|
||||||
|
}, [rtcSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
rtcSession.on(MatrixRTCSessionEvent.JoinStateChanged, onJoinStateChanged);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
rtcSession.off(
|
||||||
|
MatrixRTCSessionEvent.JoinStateChanged,
|
||||||
|
onJoinStateChanged
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, [rtcSession, onJoinStateChanged]);
|
||||||
|
|
||||||
|
return isJoined;
|
||||||
|
}
|
||||||
48
src/useMatrixRTCSessionMemberships.ts
Normal file
48
src/useMatrixRTCSessionMemberships.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership";
|
||||||
|
import {
|
||||||
|
MatrixRTCSession,
|
||||||
|
MatrixRTCSessionEvent,
|
||||||
|
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useMatrixRTCSessionMemberships(
|
||||||
|
rtcSession: MatrixRTCSession
|
||||||
|
): CallMembership[] {
|
||||||
|
const [memberships, setMemberships] = useState(rtcSession.memberships);
|
||||||
|
|
||||||
|
const onMembershipsChanged = useCallback(() => {
|
||||||
|
setMemberships(rtcSession.memberships);
|
||||||
|
}, [rtcSession]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
rtcSession.on(
|
||||||
|
MatrixRTCSessionEvent.MembershipsChanged,
|
||||||
|
onMembershipsChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
rtcSession.off(
|
||||||
|
MatrixRTCSessionEvent.MembershipsChanged,
|
||||||
|
onMembershipsChanged
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, [rtcSession, onMembershipsChanged]);
|
||||||
|
|
||||||
|
return memberships;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user