2022-05-04 17:09:48 +01:00
|
|
|
/*
|
2023-01-03 16:55:26 +00:00
|
|
|
Copyright 2022 New Vector Ltd
|
2022-05-04 17:09:48 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2023-06-30 18:21:18 -04:00
|
|
|
import { useCallback, useEffect, useState } from "react";
|
2022-01-05 15:35:12 -08:00
|
|
|
import { useHistory } from "react-router-dom";
|
2022-08-02 00:46:16 +02:00
|
|
|
import { GroupCall, GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
2022-08-12 16:46:53 -04:00
|
|
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
2022-10-10 09:19:10 -04:00
|
|
|
import { useTranslation } from "react-i18next";
|
2023-06-23 14:17:51 -04:00
|
|
|
import { Room } from "livekit-client";
|
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
2022-08-02 00:46:16 +02:00
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
2023-06-23 14:17:51 -04:00
|
|
|
import { widget, ElementWidgetActions, JoinCallData } from "../widget";
|
2022-04-07 14:22:36 -07:00
|
|
|
import { useGroupCall } from "./useGroupCall";
|
2022-01-05 15:35:12 -08:00
|
|
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
|
|
|
import { LobbyView } from "./LobbyView";
|
2023-05-26 20:41:32 +02:00
|
|
|
import { MatrixInfo } from "./VideoPreview";
|
2022-01-05 15:35:12 -08:00
|
|
|
import { CallEndedView } from "./CallEndedView";
|
|
|
|
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
2023-03-01 13:47:36 +01:00
|
|
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
2023-05-26 20:41:32 +02:00
|
|
|
import { useProfile } from "../profile/useProfile";
|
2023-06-16 18:07:13 +02:00
|
|
|
import { UserChoices } from "../livekit/useLiveKit";
|
2023-06-23 14:17:51 -04:00
|
|
|
import { findDeviceByName } from "../media-utils";
|
2023-06-28 16:35:56 +01:00
|
|
|
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
2023-06-30 18:12:58 +01:00
|
|
|
import { ActiveCall } from "./InCallView";
|
2022-09-09 02:10:45 -04:00
|
|
|
|
2022-08-02 00:46:16 +02:00
|
|
|
declare global {
|
|
|
|
interface Window {
|
2022-09-09 02:04:53 -04:00
|
|
|
groupCall?: GroupCall;
|
2022-08-02 00:46:16 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-09 02:04:53 -04:00
|
|
|
|
2022-08-02 00:46:16 +02:00
|
|
|
interface Props {
|
|
|
|
client: MatrixClient;
|
|
|
|
isPasswordlessUser: boolean;
|
|
|
|
isEmbedded: boolean;
|
2022-09-09 02:10:45 -04:00
|
|
|
preload: boolean;
|
2022-09-09 02:04:53 -04:00
|
|
|
hideHeader: boolean;
|
2022-08-05 16:16:59 -04:00
|
|
|
roomIdOrAlias: string;
|
2022-08-02 00:46:16 +02:00
|
|
|
groupCall: GroupCall;
|
|
|
|
}
|
2022-09-09 02:04:53 -04:00
|
|
|
|
2022-01-05 15:35:12 -08:00
|
|
|
export function GroupCallView({
|
|
|
|
client,
|
|
|
|
isPasswordlessUser,
|
2022-06-28 15:08:14 +01:00
|
|
|
isEmbedded,
|
2022-09-09 02:10:45 -04:00
|
|
|
preload,
|
2022-09-09 02:04:53 -04:00
|
|
|
hideHeader,
|
2022-07-27 16:14:05 -04:00
|
|
|
roomIdOrAlias,
|
2022-01-05 15:35:12 -08:00
|
|
|
groupCall,
|
2022-08-02 00:46:16 +02:00
|
|
|
}: Props) {
|
2022-01-05 15:35:12 -08:00
|
|
|
const {
|
|
|
|
state,
|
|
|
|
error,
|
|
|
|
enter,
|
|
|
|
leave,
|
2022-04-22 18:05:48 -07:00
|
|
|
participants,
|
2022-06-09 21:56:58 +01:00
|
|
|
unencryptedEventsFromUsers,
|
2023-03-16 14:41:55 +00:00
|
|
|
otelGroupCallMembership,
|
|
|
|
} = useGroupCall(groupCall, client);
|
2022-01-05 15:35:12 -08:00
|
|
|
|
2022-10-10 09:19:10 -04:00
|
|
|
const { t } = useTranslation();
|
2022-05-18 19:00:59 -04:00
|
|
|
|
2022-01-05 15:35:12 -08:00
|
|
|
useEffect(() => {
|
|
|
|
window.groupCall = groupCall;
|
2022-09-09 02:10:45 -04:00
|
|
|
return () => {
|
|
|
|
delete window.groupCall;
|
|
|
|
};
|
|
|
|
}, [groupCall]);
|
2022-07-08 20:55:18 +01:00
|
|
|
|
2023-05-26 20:41:32 +02:00
|
|
|
const { displayName, avatarUrl } = useProfile(client);
|
|
|
|
|
|
|
|
const matrixInfo: MatrixInfo = {
|
2023-06-27 12:23:19 +01:00
|
|
|
displayName,
|
2023-05-26 20:41:32 +02:00
|
|
|
avatarUrl,
|
|
|
|
roomName: groupCall.room.name,
|
2023-06-09 17:22:34 -04:00
|
|
|
roomIdOrAlias,
|
2023-05-26 20:41:32 +02:00
|
|
|
};
|
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
useEffect(() => {
|
|
|
|
if (widget && preload) {
|
|
|
|
// In preload mode, wait for a join action before entering
|
|
|
|
const onJoin = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
2023-06-23 14:17:51 -04:00
|
|
|
const devices = await Room.getLocalDevices();
|
|
|
|
|
|
|
|
const { audioInput, videoInput } = ev.detail
|
|
|
|
.data as unknown as JoinCallData;
|
|
|
|
const newChoices = {} as UserChoices;
|
|
|
|
|
|
|
|
if (audioInput !== null) {
|
|
|
|
const deviceId = await findDeviceByName(
|
|
|
|
audioInput,
|
|
|
|
"audioinput",
|
|
|
|
devices
|
|
|
|
);
|
|
|
|
if (!deviceId) {
|
|
|
|
logger.warn("Unknown audio input: " + audioInput);
|
|
|
|
} else {
|
|
|
|
logger.debug(
|
|
|
|
`Found audio input ID ${deviceId} for name ${audioInput}`
|
|
|
|
);
|
|
|
|
newChoices.audio = { selectedId: deviceId, enabled: true };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (videoInput !== null) {
|
|
|
|
const deviceId = await findDeviceByName(
|
|
|
|
videoInput,
|
|
|
|
"videoinput",
|
|
|
|
devices
|
|
|
|
);
|
|
|
|
if (!deviceId) {
|
|
|
|
logger.warn("Unknown video input: " + videoInput);
|
|
|
|
} else {
|
|
|
|
logger.debug(
|
|
|
|
`Found video input ID ${deviceId} for name ${videoInput}`
|
|
|
|
);
|
|
|
|
newChoices.video = { selectedId: deviceId, enabled: true };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setUserChoices(newChoices);
|
2023-04-17 18:47:46 +01:00
|
|
|
await enter();
|
2022-12-19 12:16:59 +01:00
|
|
|
|
|
|
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
|
|
|
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
|
|
|
|
2022-09-09 02:10:45 -04:00
|
|
|
await Promise.all([
|
|
|
|
widget.api.setAlwaysOnScreen(true),
|
|
|
|
widget.api.transport.reply(ev.detail, {}),
|
|
|
|
]);
|
|
|
|
};
|
|
|
|
|
|
|
|
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
|
|
|
return () => {
|
|
|
|
widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
|
|
|
};
|
|
|
|
}
|
2023-06-09 17:22:34 -04:00
|
|
|
}, [groupCall, preload, enter]);
|
2022-09-09 02:10:45 -04:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (isEmbedded && !preload) {
|
|
|
|
// In embedded mode, bypass the lobby and just enter the call straight away
|
2023-04-17 18:47:46 +01:00
|
|
|
enter();
|
2022-12-19 12:16:59 +01:00
|
|
|
|
|
|
|
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
|
|
|
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
2022-09-09 02:10:45 -04:00
|
|
|
}
|
2023-04-17 18:47:46 +01:00
|
|
|
}, [groupCall, isEmbedded, preload, enter]);
|
2022-01-05 15:35:12 -08:00
|
|
|
|
|
|
|
useSentryGroupCallHandler(groupCall);
|
|
|
|
|
|
|
|
const [left, setLeft] = useState(false);
|
|
|
|
const history = useHistory();
|
|
|
|
|
2023-01-03 17:09:21 +01:00
|
|
|
const onLeave = useCallback(async () => {
|
2022-09-23 15:35:05 +01:00
|
|
|
setLeft(true);
|
2022-11-04 13:07:14 +01:00
|
|
|
|
2022-11-21 12:39:48 -05:00
|
|
|
let participantCount = 0;
|
|
|
|
for (const deviceMap of groupCall.participants.values()) {
|
|
|
|
participantCount += deviceMap.size;
|
|
|
|
}
|
|
|
|
|
2023-01-03 17:09:21 +01:00
|
|
|
// 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.
|
|
|
|
const sendInstantly = !!widget;
|
2022-11-04 13:07:14 +01:00
|
|
|
PosthogAnalytics.instance.eventCallEnded.track(
|
2022-12-19 12:16:59 +01:00
|
|
|
groupCall.groupCallId,
|
2023-01-03 17:09:21 +01:00
|
|
|
participantCount,
|
|
|
|
sendInstantly
|
2022-11-04 13:07:14 +01:00
|
|
|
);
|
|
|
|
|
2022-01-05 15:35:12 -08:00
|
|
|
leave();
|
2022-09-09 02:10:45 -04:00
|
|
|
if (widget) {
|
2023-01-05 00:01:57 +01:00
|
|
|
// 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
|
|
|
|
widget.api.setAlwaysOnScreen(false);
|
2023-01-03 17:09:21 +01:00
|
|
|
PosthogAnalytics.instance.logout();
|
2022-09-09 02:10:45 -04:00
|
|
|
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
|
|
|
}
|
2022-01-05 15:35:12 -08:00
|
|
|
|
2023-06-07 16:22:44 +02:00
|
|
|
if (
|
|
|
|
!isPasswordlessUser &&
|
|
|
|
!isEmbedded &&
|
|
|
|
!PosthogAnalytics.instance.isEnabled()
|
|
|
|
) {
|
2022-01-05 15:35:12 -08:00
|
|
|
history.push("/");
|
|
|
|
}
|
2022-11-21 12:39:48 -05:00
|
|
|
}, [groupCall, leave, isPasswordlessUser, isEmbedded, history]);
|
2022-09-09 02:10:45 -04:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (widget && state === GroupCallState.Entered) {
|
|
|
|
const onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
|
|
|
leave();
|
|
|
|
await widget.api.transport.reply(ev.detail, {});
|
2022-09-12 22:53:30 -04:00
|
|
|
widget.api.setAlwaysOnScreen(false);
|
2022-09-09 02:10:45 -04:00
|
|
|
};
|
|
|
|
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
|
|
|
return () => {
|
|
|
|
widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}, [groupCall, state, leave]);
|
2022-01-05 15:35:12 -08:00
|
|
|
|
2023-06-16 18:07:13 +02:00
|
|
|
const [userChoices, setUserChoices] = useState<UserChoices | undefined>(
|
|
|
|
undefined
|
|
|
|
);
|
|
|
|
|
2023-07-06 12:12:28 +02:00
|
|
|
const livekitServiceURL = groupCall.foci[0]?.livekitServiceUrl;
|
2023-07-04 17:43:40 +02:00
|
|
|
if (!livekitServiceURL) {
|
2023-07-03 16:21:56 +01:00
|
|
|
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
|
|
|
}
|
|
|
|
|
2022-01-05 15:35:12 -08:00
|
|
|
if (error) {
|
|
|
|
return <ErrorView error={error} />;
|
2023-06-16 18:07:13 +02:00
|
|
|
} else if (state === GroupCallState.Entered && userChoices) {
|
2023-05-26 20:41:32 +02:00
|
|
|
return (
|
2023-07-03 16:21:56 +01:00
|
|
|
<OpenIDLoader
|
2023-05-26 20:41:32 +02:00
|
|
|
client={client}
|
2023-07-04 17:43:40 +02:00
|
|
|
livekitServiceURL={livekitServiceURL}
|
2023-07-03 16:21:56 +01:00
|
|
|
roomName={matrixInfo.roomName}
|
|
|
|
>
|
2023-06-30 18:12:58 +01:00
|
|
|
<ActiveCall
|
|
|
|
client={client}
|
|
|
|
groupCall={groupCall}
|
|
|
|
participants={participants}
|
|
|
|
onLeave={onLeave}
|
|
|
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
|
|
|
hideHeader={hideHeader}
|
|
|
|
matrixInfo={matrixInfo}
|
|
|
|
userChoices={userChoices}
|
|
|
|
otelGroupCallMembership={otelGroupCallMembership}
|
|
|
|
/>
|
|
|
|
</OpenIDLoader>
|
2023-05-26 20:41:32 +02:00
|
|
|
);
|
2022-01-05 15:35:12 -08:00
|
|
|
} else if (left) {
|
2023-06-07 14:19:53 -04:00
|
|
|
// The call ended view is shown for two reasons: prompting guests to create
|
|
|
|
// an account, and prompting users that have opted into analytics to provide
|
|
|
|
// feedback. We don't show a feedback prompt to widget users however (at
|
|
|
|
// least for now), because we don't yet have designs that would allow widget
|
|
|
|
// users to dismiss the feedback prompt and close the call window without
|
|
|
|
// submitting anything.
|
|
|
|
if (
|
|
|
|
isPasswordlessUser ||
|
|
|
|
(PosthogAnalytics.instance.isEnabled() && !isEmbedded)
|
|
|
|
) {
|
2023-06-07 16:22:44 +02:00
|
|
|
return (
|
|
|
|
<CallEndedView
|
|
|
|
endedCallId={groupCall.groupCallId}
|
|
|
|
client={client}
|
|
|
|
isPasswordlessUser={isPasswordlessUser}
|
|
|
|
/>
|
|
|
|
);
|
2022-09-23 15:35:05 +01:00
|
|
|
} else {
|
|
|
|
// If the user is a regular user, we'll have sent them back to the homepage,
|
|
|
|
// so just sit here & do nothing: otherwise we would (briefly) mount the
|
|
|
|
// LobbyView again which would open capture devices again.
|
|
|
|
return null;
|
|
|
|
}
|
2022-09-09 02:10:45 -04:00
|
|
|
} else if (preload) {
|
|
|
|
return null;
|
|
|
|
} else if (isEmbedded) {
|
|
|
|
return (
|
|
|
|
<FullScreenView>
|
2023-02-13 09:55:32 +05:30
|
|
|
<h1>{t("Loading…")}</h1>
|
2022-09-09 02:10:45 -04:00
|
|
|
</FullScreenView>
|
|
|
|
);
|
2023-06-16 18:07:13 +02:00
|
|
|
} else {
|
2022-09-09 02:10:45 -04:00
|
|
|
return (
|
|
|
|
<LobbyView
|
2023-05-26 20:41:32 +02:00
|
|
|
matrixInfo={matrixInfo}
|
2023-06-16 18:07:13 +02:00
|
|
|
onEnter={(choices: UserChoices) => {
|
|
|
|
setUserChoices(choices);
|
|
|
|
enter();
|
|
|
|
}}
|
2022-09-09 02:10:45 -04:00
|
|
|
isEmbedded={isEmbedded}
|
|
|
|
hideHeader={hideHeader}
|
|
|
|
/>
|
|
|
|
);
|
2022-01-05 15:35:12 -08:00
|
|
|
}
|
|
|
|
}
|