mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Fall back to publishing on a remote SFU if possible
Port of edf1feae from johannes/rtc-remote-publish onto the SDK split,
where
the MatrixRTC logic lives in sdk/ rather than src/state/CallViewModel/.
Where the original lands:
- getLocalTransport's remote fallback: the LiveKit backend's new
findRemotePublishTransport (sdk/src/media-backend/livekit/
RemotePublishTransport.ts), which tries the candidates from the
js-sdk's
getRemoteLivekitPublishCandidates oldest first and keeps the first
whose
homeserver issues a token with the publish grant.
discoverLocalTransport
asks the backend for it after the host's fallback, with a snapshot of
the
session's memberships from RTCParticipation, and throws
MatrixRTCTransportMissingError where that finds nothing too. Transport
discovery stays backend neutral: the MSC4195 token check is the
backend's.
- LocalMember's "no delegation on a remote SFU" and the custom
transport's
server_name: LivekitBackend.prepareLocalTransport locates the
transport on
the homeserver it names (serverNameOfTransport) and skips the
delegation
probe where that is not our own.
- SessionBehaviors' membership.getTransportServerName: the same helper
in
LivekitBackend.mediaFor.
- decodeSFUJWTPayload, SFUJWTPayload.video.canPublish and the exported
isCsApiUnavailable: sdk/src/media-backend/livekit/auth/.
- DeveloperSettingsTab, the widget capabilities, the locale string and
the
js-sdk pin (9405287c, 44.0.0): applied as is. The error boundary
snapshots follow the string.
Tests: the LocalTransport cases go to RemotePublishTransport.test.ts and
LocalTransport.test.ts, the LocalMember remote case and the
SessionBehaviors
server-name case to LivekitBackend.test.ts, plus the memberships
snapshot in
RTCParticipation.test.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4b73ce68e3
commit
8638d4f159
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
type CallMembership,
|
||||
getRemoteLivekitPublishCandidates,
|
||||
type LivekitPublishCandidate,
|
||||
type Transport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
|
||||
import { isCsApiUnavailable } from "./auth";
|
||||
import * as csApi from "./auth/csApi";
|
||||
import { decodeSFUJWTPayload } from "./auth/types";
|
||||
|
||||
export interface RemotePublishTransportParams {
|
||||
client: csApi.ClientGetTokenParts;
|
||||
/** The members whose SFUs are the candidates. */
|
||||
memberships: CallMembership[];
|
||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||
roomId: string;
|
||||
slotId: string;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SFU of another member that its homeserver lets us publish on, as the
|
||||
* transport to advertise, or null where no member's SFU will do. Candidates
|
||||
* are tried oldest membership first, and each one is checked by asking for a
|
||||
* publisher token through our homeserver (MSC4195): a homeserver may restrict
|
||||
* remote publishers, which shows as a token without the publish grant.
|
||||
*/
|
||||
export async function findRemotePublishTransport({
|
||||
client,
|
||||
memberships,
|
||||
ownMembershipIdentity,
|
||||
roomId,
|
||||
slotId,
|
||||
logger,
|
||||
}: RemotePublishTransportParams): Promise<Transport | null> {
|
||||
logger.info(
|
||||
"No local transport available, looking for another member's SFU to publish on",
|
||||
);
|
||||
const candidates = getRemoteLivekitPublishCandidates(
|
||||
memberships,
|
||||
ownMembershipIdentity,
|
||||
);
|
||||
for (const candidate of candidates) {
|
||||
const outcome = await tryCandidate(candidate);
|
||||
if (outcome === "usable") return candidate.transport;
|
||||
if (outcome === "noCsApi") return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
/** "noCsApi" ends the search: without the endpoint no candidate can work. */
|
||||
async function tryCandidate({
|
||||
url,
|
||||
serverName,
|
||||
}: LivekitPublishCandidate): Promise<"usable" | "unusable" | "noCsApi"> {
|
||||
try {
|
||||
const { jwt } = await csApi.getSFUConfig({
|
||||
client,
|
||||
membership: ownMembershipIdentity,
|
||||
url,
|
||||
serverName,
|
||||
roomId,
|
||||
slotId,
|
||||
});
|
||||
if (decodeSFUJWTPayload(jwt).video.canPublish) {
|
||||
logger.info(`Publishing on remote SFU ${url} of ${serverName}`);
|
||||
return "usable";
|
||||
}
|
||||
logger.info(`${serverName} does not let us publish on ${url}, skipping`);
|
||||
} catch (e) {
|
||||
if (isCsApiUnavailable(e)) {
|
||||
logger.warn(
|
||||
`Cannot use the MSC4195 get_token endpoint (${e}), so no remote SFU can be used`,
|
||||
);
|
||||
return "noCsApi";
|
||||
}
|
||||
logger.warn(`Failed to get a token for ${url} of ${serverName}:`, e);
|
||||
}
|
||||
return "unusable";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user