Skip to content

Commit

Permalink
review
Browse files Browse the repository at this point in the history
  • Loading branch information
toger5 committed Jun 4, 2024
1 parent 8fe79f1 commit 65c7f48
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 98 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Chat](https://img.shields.io/matrix/webrtc:matrix.org)](https://matrix.to/#/#webrtc:matrix.org)
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement-call%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element-call)

Group calls with WebRTC that leverage [Matrix](https://matrix.org) and an open-source WebRTC toolkit from [LiveKit](https://Livekit.io/).
Group calls with WebRTC that leverage [Matrix](https://matrix.org) and an open-source WebRTC toolkit from [LiveKit](https://livekit.io/).

For prior version of the Element Call that relied solely on full-mesh logic, check [`full-mesh`](https://github.com/element-hq/element-call/tree/full-mesh) branch.

Expand Down
2 changes: 1 addition & 1 deletion src/room/GroupCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export const GroupCallView: FC<Props> = ({
client={client}
matrixInfo={matrixInfo}
muteStates={muteStates}
onEnter={() => enterRTCSession(rtcSession, perParticipantE2EE)}
onEnter={() => void enterRTCSession(rtcSession, perParticipantE2EE)}
confineToRoom={confineToRoom}
hideHeader={hideHeader}
participantCount={participantCount}
Expand Down
122 changes: 26 additions & 96 deletions src/rtcSessionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ limitations under the License.
*/

import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { AutoDiscovery, RoomMember } from "matrix-js-sdk";
import { logger } from "matrix-js-sdk/src/logger";
import { Focus } from "matrix-js-sdk/src/matrixrtc/focus";
import {
LivekitFocus,
LivekitFocusActive,
LivekitFocusConfig,
isLivekitFocus,
isLivekitFocusConfig,
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
Expand All @@ -39,62 +36,63 @@ export function makeActiveFocus(): LivekitFocusActive {
};
}

async function makePreferredFoci(
async function makePreferredLivekitFoci(
rtcSession: MatrixRTCSession,
livekitAlias: string,
): Promise<LivekitFocus[]> {
logger.log("Start building foci_preferred list: ", rtcSession.room.roomId);

const preferredFoci: LivekitFocus[] = [];

// Make the foci from the running rtc session the highest priority one
// 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 focusFromMatrixRTC = rtcSession
.getOldestMembership()
?.getPreferredFoci()[0];
if (focusFromMatrixRTC && isLivekitFocus(focusFromMatrixRTC)) {
logger.log("Adding livekit focus from oldest member: ", focusFromMatrixRTC);
preferredFoci.push(focusFromMatrixRTC);
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
] as Focus[];
if (wellKnownFoci.length > 0) {
logger.log("Adding livekit focus from well known: ", wellKnownFoci);
const wellKnownFoci =
rtcSession.room.client.getClientWellKnown()?.[FOCI_WK_KEY];
if (Array.isArray(wellKnownFoci)) {
preferredFoci.push(
...wellKnownFoci
.filter((f) => !!f)
.filter(isLivekitFocusConfig)
.map((f) => ({ ...f, livekit_alias: livekitAlias })),
.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 = {
const focusFormConf: LivekitFocus = {
type: "livekit",
livekit_service_url: urlFromConf,
livekit_alias: livekitAlias,
} as LivekitFocus;
};
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.`,
`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.`,
);

return preferredFoci;

// Currently we skip computing a focus based on other users in the room.
const focusOtherMembers = await focusFromOtherMembers(
rtcSession,
livekitAlias,
);
if (focusOtherMembers) preferredFoci.push(focusOtherMembers);
// TODO: we want to do something like this:
//
// const focusOtherMembers = await focusFromOtherMembers(
// rtcSession,
// livekitAlias,
// );
// if (focusOtherMembers) preferredFoci.push(focusOtherMembers);
}

export async function enterRTCSession(
Expand All @@ -110,9 +108,8 @@ export async function enterRTCSession(

// right now we assume everything is a room-scoped call
const livekitAlias = rtcSession.room.roomId;

rtcSession.joinRoomSession(
await makePreferredFoci(rtcSession, livekitAlias),
await makePreferredLivekitFoci(rtcSession, livekitAlias),
makeActiveFocus(),
{ manageMediaKeys: encryptMedia },
);
Expand Down Expand Up @@ -141,70 +138,3 @@ export async function leaveRTCSession(
await widgetPostHangupProcedure(widget);
}
}

// Query focus from other room members

// This caches the homeserver domains per homeserver.
const livekitFocusCache = new Map<string, LivekitFocusConfig | undefined>();

// helper to fetch the domain from a member
const getDomain = (member: RoomMember): string => member.userId.split(":")[1];

async function fetchAndCacheLivekitFocus(
homeserverDomain: string,
): Promise<LivekitFocusConfig | undefined> {
const cachedHomeserverFocus = livekitFocusCache.get(homeserverDomain);
if (cachedHomeserverFocus) return cachedHomeserverFocus;

const fetchedHomeserverFocus = (
await AutoDiscovery.getRawClientConfig(homeserverDomain)
)[FOCI_WK_KEY] as LivekitFocusConfig | undefined;
// also store undefined so we don't refetch each time.
livekitFocusCache.set(homeserverDomain, fetchedHomeserverFocus);
return fetchedHomeserverFocus;
}

async function focusFromOtherMembers(
rtcSession: MatrixRTCSession,
livekitAlias: string,
skip?: boolean,
): Promise<LivekitFocus> {
if (skip) throw Error("skipping");
// Temporary convert to set to remove duplicates
const memberDomains = Array.from(
new Set(rtcSession.room.getMembers().map(getDomain)),
);

const possibleLivekitFocus = new Map(
await Promise.all(
memberDomains.map(
async (domain: string) =>
[domain, await fetchAndCacheLivekitFocus(domain)] as [
string,
LivekitFocusConfig | undefined,
],
),
),
);

const validFocus = (
s: [string, LivekitFocusConfig | undefined],
): s is [string, LivekitFocusConfig] => !!s[1];

// TODO this is a placeholder that sorts alphabetically.
// In the future we want to use this, to allow smart SFU selection (based on ping, load endpoints, etc.)
const betterSFU = (
a: [string, LivekitFocusConfig],
b: [string, LivekitFocusConfig],
): number => (a[0] < b[0] ? -1 : 1);

const [domain, config] = Array.from(possibleLivekitFocus.entries())
.filter(validFocus)
.sort(betterSFU)[0];
if (!config) {
throw new Error("No focus can be loaded from other users home-servers!");
} else {
logger.log("Using livekit Focus: ", config, `\nfrom homeserver: ${domain}`);
}
return { ...config, livekit_alias: livekitAlias };
}

0 comments on commit 65c7f48

Please sign in to comment.