2023-08-18 16:03:21 +08:00
|
|
|
/*
|
2024-09-06 16:22:13 +08:00
|
|
|
Copyright 2023, 2024 New Vector Ltd.
|
2023-08-18 16:03:21 +08:00
|
|
|
|
2024-09-06 16:22:13 +08:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
Please see LICENSE in the repository root for full details.
|
2023-08-18 16:03:21 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
2024-06-19 22:41:52 +08:00
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
import {
|
|
|
|
LivekitFocus,
|
|
|
|
LivekitFocusActive,
|
|
|
|
isLivekitFocus,
|
|
|
|
isLivekitFocusConfig,
|
|
|
|
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
2023-08-18 16:03:21 +08:00
|
|
|
|
|
|
|
import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
|
|
|
|
import { Config } from "./config/Config";
|
2023-10-31 00:30:30 +08:00
|
|
|
import { ElementWidgetActions, WidgetHelpers, widget } from "./widget";
|
2023-08-18 16:03:21 +08:00
|
|
|
|
2024-06-19 22:41:52 +08:00
|
|
|
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
2023-08-18 16:03:21 +08:00
|
|
|
|
2024-06-19 22:41:52 +08:00
|
|
|
export function makeActiveFocus(): LivekitFocusActive {
|
2023-08-18 16:03:21 +08:00
|
|
|
return {
|
|
|
|
type: "livekit",
|
2024-06-19 22:41:52 +08:00
|
|
|
focus_selection: "oldest_membership",
|
2023-08-18 16:03:21 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-06-19 22:41:52 +08:00
|
|
|
async function makePreferredLivekitFoci(
|
|
|
|
rtcSession: MatrixRTCSession,
|
|
|
|
livekitAlias: string,
|
|
|
|
): Promise<LivekitFocus[]> {
|
|
|
|
logger.log("Start building foci_preferred list: ", rtcSession.room.roomId);
|
|
|
|
|
|
|
|
const preferredFoci: LivekitFocus[] = [];
|
|
|
|
|
|
|
|
// Make the Focus from the running rtc session the highest priority one
|
|
|
|
// This minimizes how often we need to switch foci during a call.
|
|
|
|
const focusInUse = rtcSession.getFocusInUse();
|
|
|
|
if (focusInUse && isLivekitFocus(focusInUse)) {
|
|
|
|
logger.log("Adding livekit focus from oldest member: ", focusInUse);
|
|
|
|
preferredFoci.push(focusInUse);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prioritize the client well known over the configured sfu.
|
|
|
|
const wellKnownFoci =
|
|
|
|
rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY];
|
|
|
|
if (Array.isArray(wellKnownFoci)) {
|
|
|
|
preferredFoci.push(
|
|
|
|
...wellKnownFoci
|
|
|
|
.filter((f) => !!f)
|
|
|
|
.filter(isLivekitFocusConfig)
|
|
|
|
.map((wellKnownFocus) => {
|
|
|
|
logger.log("Adding livekit focus from well known: ", wellKnownFocus);
|
|
|
|
return { ...wellKnownFocus, livekit_alias: livekitAlias };
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const urlFromConf = Config.get().livekit?.livekit_service_url;
|
|
|
|
if (urlFromConf) {
|
|
|
|
const focusFormConf: LivekitFocus = {
|
|
|
|
type: "livekit",
|
|
|
|
livekit_service_url: urlFromConf,
|
|
|
|
livekit_alias: livekitAlias,
|
|
|
|
};
|
|
|
|
logger.log("Adding livekit focus from config: ", focusFormConf);
|
|
|
|
preferredFoci.push(focusFormConf);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preferredFoci.length === 0)
|
|
|
|
throw new Error(
|
|
|
|
`No livekit_service_url is configured so we could not create a focus.
|
|
|
|
Currently we skip computing a focus based on other users in the room.`,
|
|
|
|
);
|
2024-09-10 15:49:35 +08:00
|
|
|
return Promise.resolve(preferredFoci);
|
2024-06-19 22:41:52 +08:00
|
|
|
|
|
|
|
// TODO: we want to do something like this:
|
|
|
|
//
|
|
|
|
// const focusOtherMembers = await focusFromOtherMembers(
|
|
|
|
// rtcSession,
|
|
|
|
// livekitAlias,
|
|
|
|
// );
|
|
|
|
// if (focusOtherMembers) preferredFoci.push(focusOtherMembers);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function enterRTCSession(
|
2023-10-17 00:45:06 +08:00
|
|
|
rtcSession: MatrixRTCSession,
|
|
|
|
encryptMedia: boolean,
|
2024-06-19 22:41:52 +08:00
|
|
|
): Promise<void> {
|
2023-08-18 16:03:21 +08:00
|
|
|
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.
|
2023-10-25 19:44:33 +08:00
|
|
|
// groupCallOTelMembership?.onJoinCall();
|
2023-08-18 16:03:21 +08:00
|
|
|
|
2023-10-10 20:14:39 +08:00
|
|
|
// right now we assume everything is a room-scoped call
|
2023-08-18 16:03:21 +08:00
|
|
|
const livekitAlias = rtcSession.room.roomId;
|
2024-11-12 00:53:37 +08:00
|
|
|
const { features, matrix_rtc_session: matrixRtcSessionConfig } = Config.get();
|
2024-07-05 20:10:16 +08:00
|
|
|
const useDeviceSessionMemberEvents =
|
2024-11-12 00:53:37 +08:00
|
|
|
features?.feature_use_device_session_member_events;
|
2024-06-19 22:41:52 +08:00
|
|
|
rtcSession.joinRoomSession(
|
|
|
|
await makePreferredLivekitFoci(rtcSession, livekitAlias),
|
|
|
|
makeActiveFocus(),
|
2024-07-05 20:10:16 +08:00
|
|
|
{
|
|
|
|
manageMediaKeys: encryptMedia,
|
|
|
|
...(useDeviceSessionMemberEvents !== undefined && {
|
|
|
|
useLegacyMemberEvents: !useDeviceSessionMemberEvents,
|
|
|
|
}),
|
2024-11-12 00:53:37 +08:00
|
|
|
membershipServerSideExpiryTimeout:
|
|
|
|
matrixRtcSessionConfig?.membership_server_side_expiry_timeout,
|
|
|
|
membershipKeepAlivePeriod:
|
|
|
|
matrixRtcSessionConfig?.membership_keep_alive_period,
|
|
|
|
makeKeyDelay: matrixRtcSessionConfig?.key_rotation_on_leave_delay,
|
2024-07-05 20:10:16 +08:00
|
|
|
},
|
2024-06-19 22:41:52 +08:00
|
|
|
);
|
2023-08-18 16:03:21 +08:00
|
|
|
}
|
|
|
|
|
2023-10-31 00:30:30 +08:00
|
|
|
const widgetPostHangupProcedure = async (
|
|
|
|
widget: WidgetHelpers,
|
|
|
|
): Promise<void> => {
|
|
|
|
// we need to wait until the callEnded event is tracked on posthog.
|
|
|
|
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
|
|
|
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
|
|
|
PosthogAnalytics.instance.logout();
|
|
|
|
|
2024-09-10 15:49:35 +08:00
|
|
|
try {
|
|
|
|
await widget.api.setAlwaysOnScreen(false);
|
|
|
|
} catch (e) {
|
|
|
|
logger.error("Failed to set call widget `alwaysOnScreen` to false", e);
|
|
|
|
}
|
|
|
|
|
2023-10-31 00:30:30 +08:00
|
|
|
// We send the hangup event after the memberships have been updated
|
|
|
|
// calling leaveRTCSession.
|
|
|
|
// We need to wait because this makes the client hosting this widget killing the IFrame.
|
2024-09-10 15:49:35 +08:00
|
|
|
await widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
2023-10-31 00:30:30 +08:00
|
|
|
};
|
|
|
|
|
2023-10-05 00:27:07 +08:00
|
|
|
export async function leaveRTCSession(
|
2023-10-11 22:42:04 +08:00
|
|
|
rtcSession: MatrixRTCSession,
|
2023-10-05 00:27:07 +08:00
|
|
|
): Promise<void> {
|
|
|
|
await rtcSession.leaveRoomSession();
|
2023-10-31 00:30:30 +08:00
|
|
|
if (widget) {
|
|
|
|
await widgetPostHangupProcedure(widget);
|
|
|
|
}
|
2023-08-18 16:03:21 +08:00
|
|
|
}
|