diff --git a/src/livekit/LivekitFocus.ts b/src/livekit/LivekitFocus.ts new file mode 100644 index 00000000..f3a1e532 --- /dev/null +++ b/src/livekit/LivekitFocus.ts @@ -0,0 +1,23 @@ +/* +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 { Focus } from "matrix-js-sdk/src/matrixrtc/focus"; + +export interface LivekitFocus extends Focus { + type: "livekit"; + livekit_service_url: string; + livekit_alias: string; +} diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 083e95b2..c122a844 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -14,10 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { GroupCall, IOpenIDToken, MatrixClient } from "matrix-js-sdk"; +import { IOpenIDToken, MatrixClient } from "matrix-js-sdk"; import { logger } from "matrix-js-sdk/src/logger"; +import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; +import { useEffect, useState } from "react"; -import { Config } from "../config/Config"; +import { LivekitFocus } from "./LivekitFocus"; +import { useActiveFocus } from "../room/useActiveFocus"; export interface SFUConfig { url: string; @@ -30,66 +33,52 @@ export type OpenIDClientParts = Pick< "getOpenIdToken" | "getDeviceId" >; +export function useOpenIDSFU( + client: OpenIDClientParts, + rtcSession: MatrixRTCSession +) { + const [sfuConfig, setSFUConfig] = useState(undefined); + + const activeFocus = useActiveFocus(rtcSession); + + useEffect(() => { + (async () => { + const sfuConfig = activeFocus + ? await getSFUConfigWithOpenID(client, activeFocus) + : undefined; + setSFUConfig(sfuConfig); + })(); + }, [client, activeFocus]); + + return sfuConfig; +} + export async function getSFUConfigWithOpenID( client: OpenIDClientParts, - groupCall: GroupCall, - roomName: string -): Promise { + activeFocus: LivekitFocus +): Promise { const openIdToken = await client.getOpenIdToken(); logger.debug("Got openID token", openIdToken); - // if the call has a livekit service URL, try it. - if (groupCall.livekitServiceURL) { - try { - logger.info( - `Trying to get JWT from call's configured URL of ${groupCall.livekitServiceURL}...` - ); - const sfuConfig = await getLiveKitJWT( - client, - groupCall.livekitServiceURL, - roomName, - openIdToken - ); - logger.info(`Got JWT from call state event URL.`); - - return sfuConfig; - } catch (e) { - logger.warn( - `Failed to get JWT from group call's configured URL of ${groupCall.livekitServiceURL}.`, - e - ); - } - } - - // otherwise, try our configured one and, if it works, update the call's service URL in the state event - // NB. This wuill update it for everyone so we may end up with multiple clients updating this when they - // join at similar times, but we don't have a huge number of options here. - const urlFromConf = Config.get().livekit!.livekit_service_url; - logger.info(`Trying livekit service URL from our config: ${urlFromConf}...`); try { + logger.info( + `Trying to get JWT from call's active focus URL of ${activeFocus.livekit_service_url}...` + ); const sfuConfig = await getLiveKitJWT( client, - urlFromConf, - roomName, + activeFocus.livekit_service_url, + activeFocus.livekit_alias, openIdToken ); - - logger.info( - `Got JWT, updating call livekit service URL with: ${urlFromConf}...` - ); - try { - await groupCall.updateLivekitServiceURL(urlFromConf); - logger.info(`Call livekit service URL updated.`); - } catch (e) { - logger.warn( - `Failed to update call livekit service URL: continuing anyway.` - ); - } + logger.info(`Got JWT from call's active focus URL.`); return sfuConfig; } catch (e) { - logger.error("Failed to get JWT from URL defined in Config.", e); - throw e; + logger.warn( + `Failed to get JWT from RTC session's active focus URL of ${activeFocus.livekit_service_url}.`, + e + ); + return undefined; } } diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index eaf334ce..57182b04 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -21,7 +21,6 @@ import { useTranslation } from "react-i18next"; import { Room } from "livekit-client"; 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 { widget, ElementWidgetActions, JoinCallData } from "../widget"; @@ -32,11 +31,9 @@ import { CallEndedView } from "./CallEndedView"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useProfile } from "../profile/useProfile"; import { findDeviceByName } from "../media-utils"; -//import { OpenIDLoader } from "../livekit/OpenIDLoader"; import { ActiveCall } from "./InCallView"; import { MuteStates, useMuteStates } from "./MuteStates"; import { useMediaDevices, MediaDevices } from "../livekit/MediaDevicesContext"; -import { LivekitFocus } from "../livekit/LivekitFocus"; import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; import { enterRTCSession, leaveRTCSession } from "../rtcSessionHelpers"; import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState"; @@ -69,21 +66,11 @@ export function GroupCallView({ hideHeader, rtcSession, }: Props) { - /*const { - state, - error, - enter, - leave, - participants, - unencryptedEventsFromUsers, - otelGroupCallMembership, - } = useGroupCall(groupCall, client);*/ - const memberships = useMatrixRTCSessionMemberships(rtcSession); const isJoined = useMatrixRTCSessionJoinState(rtcSession); - const e2eeSharedKey = useManageRoomSharedKey(groupCall.room.roomId); - const isRoomE2EE = useIsRoomE2EE(groupCall.room.roomId); + const e2eeSharedKey = useManageRoomSharedKey(rtcSession.room.roomId); + const isRoomE2EE = useIsRoomE2EE(rtcSession.room.roomId); const { t } = useTranslation(); @@ -260,22 +247,9 @@ export function GroupCallView({ const onReconnect = useCallback(() => { setLeft(false); setLeaveError(undefined); - rtcSession.joinRoomSession(); + enterRTCSession(rtcSession); }, [rtcSession]); - const focus: Focus | undefined = rtcSession - .getOldestMembership() - ?.getActiveFoci()?.[0]; - if ( - !focus || - focus.type !== "livekit" || - !(focus as LivekitFocus).livekit_alias || - !(focus as LivekitFocus).livekit_service_url - ) { - logger.error("Incompatible focus on call", focus); - return ; - } - if (e2eeEnabled && isRoomE2EE && !e2eeSharedKey) { return ( */ - // ); } else if (left) { // The call ended view is shown for two reasons: prompting guests to create @@ -351,7 +319,7 @@ export function GroupCallView({ enter()} + onEnter={() => enterRTCSession(rtcSession)} isEmbedded={isEmbedded} hideHeader={hideHeader} /> diff --git a/src/room/useActiveFocus.ts b/src/room/useActiveFocus.ts new file mode 100644 index 00000000..a62ffafd --- /dev/null +++ b/src/room/useActiveFocus.ts @@ -0,0 +1,68 @@ +/* +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"; +import { deepCompare } from "matrix-js-sdk/src/utils"; + +import { LivekitFocus } from "../livekit/LivekitFocus"; + +function getActiveFocus( + rtcSession: MatrixRTCSession +): LivekitFocus | undefined { + const oldestMembership = rtcSession.getOldestMembership(); + return oldestMembership?.getActiveFoci()[0] as LivekitFocus; +} + +/** + * Gets the currently active (livekit) focus for a MatrixRTC session + * This logic is specific to livekit foci where the whole call must use one + * and the same focus. + */ +export function useActiveFocus( + rtcSession: MatrixRTCSession +): LivekitFocus | undefined { + const [activeFocus, setActiveFocus] = useState(() => + getActiveFocus(rtcSession) + ); + + const onMembershipsChanged = useCallback(() => { + const newActiveFocus = getActiveFocus(rtcSession); + + if (!deepCompare(activeFocus, newActiveFocus)) { + setActiveFocus(newActiveFocus); + } + }, [activeFocus, rtcSession]); + + useEffect(() => { + rtcSession.on( + MatrixRTCSessionEvent.MembershipsChanged, + onMembershipsChanged + ); + + return () => { + rtcSession.off( + MatrixRTCSessionEvent.MembershipsChanged, + onMembershipsChanged + ); + }; + }); + + return activeFocus; +} diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts new file mode 100644 index 00000000..3d62f980 --- /dev/null +++ b/src/rtcSessionHelpers.ts @@ -0,0 +1,53 @@ +/* +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 } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; + +import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; +import { LivekitFocus } from "./livekit/LivekitFocus"; +import { Config } from "./config/Config"; + +function makeFocus(livekitAlias: string): LivekitFocus { + const urlFromConf = Config.get().livekit!.livekit_service_url; + if (!urlFromConf) { + throw new Error("No livekit_service_url is configured!"); + } + + return { + type: "livekit", + livekit_service_url: urlFromConf, + livekit_alias: livekitAlias, + }; +} + +export function enterRTCSession(rtcSession: MatrixRTCSession) { + PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date()); + PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId); + + // This must be called before we start trying to join the call, as we need to + // have started tracking by the time calls start getting created. + //groupCallOTelMembership?.onJoinCall(); + + // right now we asume everything is a room-scoped call + const livekitAlias = rtcSession.room.roomId; + + rtcSession.joinRoomSession([makeFocus(livekitAlias)]); +} + +export function leaveRTCSession(rtcSession: MatrixRTCSession) { + //groupCallOTelMembership?.onLeaveCall(); + rtcSession.leaveRoomSession(); +} diff --git a/src/useMatrixRTCSessionJoinState.ts b/src/useMatrixRTCSessionJoinState.ts index 7afd6b52..c7c54563 100644 --- a/src/useMatrixRTCSessionJoinState.ts +++ b/src/useMatrixRTCSessionJoinState.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { logger } from "matrix-js-sdk/src/logger"; import { MatrixRTCSession, MatrixRTCSessionEvent, @@ -26,6 +27,11 @@ export function useMatrixRTCSessionJoinState( const [isJoined, setJoined] = useState(rtcSession.isJoined()); const onJoinStateChanged = useCallback(() => { + logger.info( + `Session in room ${rtcSession.room.roomId} changed to ${ + rtcSession.isJoined() ? "joined" : "left" + }` + ); setJoined(rtcSession.isJoined()); }, [rtcSession]); diff --git a/src/useMatrixRTCSessionMemberships.ts b/src/useMatrixRTCSessionMemberships.ts index 149aa8f6..a0d5a513 100644 --- a/src/useMatrixRTCSessionMemberships.ts +++ b/src/useMatrixRTCSessionMemberships.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { logger } from "matrix-js-sdk/src/logger"; import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership"; import { MatrixRTCSession, @@ -27,6 +28,9 @@ export function useMatrixRTCSessionMemberships( const [memberships, setMemberships] = useState(rtcSession.memberships); const onMembershipsChanged = useCallback(() => { + logger.info( + `Memberships changed for call in room ${rtcSession.room.roomId} (${rtcSession.memberships.length} members)` + ); setMemberships(rtcSession.memberships); }, [rtcSession]);