mirror of
https://github.com/vector-im/element-call.git
synced 2026-10-16 23:45:50 +00:00
Use the unstable LiveKit transport type
UnstableLivekitTransport covers every shape a homeserver may advertise while MSC4195 evolves: a LiveKit SFU `url`, a `livekit_service_url`, or both. Switching to it means a service URL is no longer guaranteed, so the remaining code that relies on one checks for it explicitly: delegation probing skips transports without one, the connection map keys transports by whichever URL they carry, and the OpenID flow rejects transports without a service URL until the client-server API flow lands.
This commit is contained in:
@@ -16,12 +16,12 @@ import classNames from "classnames";
|
||||
import { Modal } from "./Modal";
|
||||
import styles from "./RTCConnectionStats.module.css";
|
||||
import mediaViewStyles from "../src/tile/MediaView.module.css";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
interface Props {
|
||||
audio?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||
video?: RTCInboundRtpStreamStats | RTCOutboundRtpStreamStats;
|
||||
transport?: LivekitTransport;
|
||||
transport?: UnstableLivekitTransport;
|
||||
rtcBackendIdentity?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
import { useEarpieceAudioConfig } from "../MediaDevicesContext";
|
||||
import { useReactiveState } from "../useReactiveState";
|
||||
import * as controls from "../controls";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
export interface MatrixAudioRendererProps {
|
||||
transport: LivekitTransport;
|
||||
transport: UnstableLivekitTransport;
|
||||
livekitRoom: LivekitRoom;
|
||||
/**
|
||||
* The list of participant identities to render audio for.
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { act } from "react";
|
||||
import { type RoomMember } from "matrix-js-sdk";
|
||||
import {
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type CallMembership,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
@@ -46,7 +46,7 @@ vitest.mock("../useAudioContext");
|
||||
vitest.mock("../soundUtils");
|
||||
vitest.mock("../rtcSessionHelpers", async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
makeTransport: (): [LivekitTransport] => [exampleTransport],
|
||||
makeTransport: (): [UnstableLivekitTransport] => [exampleTransport],
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -699,7 +699,12 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
livekitAlias: connectionItem.livekitAlias,
|
||||
// TODO compute is local or tag it in the livekit room items already
|
||||
isLocal: undefined,
|
||||
url: connectionItem.transport.livekit_service_url,
|
||||
url:
|
||||
// It should be relatively clear to the user which kind of URL
|
||||
// this is, based on the scheme (ws:// vs. http://)
|
||||
"url" in connectionItem.transport
|
||||
? connectionItem.transport.url
|
||||
: connectionItem.transport.livekit_service_url,
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
type CallMembership,
|
||||
type IRTCNotificationContent,
|
||||
MatrixRTCSessionEvent,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||
|
||||
@@ -97,7 +97,7 @@ vi.mock(
|
||||
"../state/CallViewModel/localMember/localTransport",
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
makeTransport: async (): Promise<LivekitTransport> =>
|
||||
makeTransport: async (): Promise<UnstableLivekitTransport> =>
|
||||
Promise.resolve(exampleTransport),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -46,7 +46,7 @@ import {
|
||||
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
MembershipManagerEvent,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type MatrixRTCSession,
|
||||
type RTCCallIntent,
|
||||
type RTCNotificationType,
|
||||
@@ -217,7 +217,7 @@ export interface CallViewModelOptions {
|
||||
*/
|
||||
windowSize$: Behavior<{ width: number; height: number }>;
|
||||
/** Optional value overriding the local transport, for testing purposes. */
|
||||
localTransport?: LivekitTransport;
|
||||
localTransport?: UnstableLivekitTransport;
|
||||
/** Optional value overriding the connection factory, for testing purposes. */
|
||||
connectionFactory?: ConnectionFactory;
|
||||
/** The version & compatibility mode of MatrixRTC that we should use. */
|
||||
@@ -278,7 +278,7 @@ interface LayoutScanState {
|
||||
export type LivekitRoomItem = {
|
||||
livekitRoom: LivekitRoom;
|
||||
participants: string[];
|
||||
transport: LivekitTransport;
|
||||
transport: UnstableLivekitTransport;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -619,7 +619,7 @@ export function createCallViewModel$(
|
||||
),
|
||||
muteStates,
|
||||
joinMatrixRTC: (
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
delayedLeaveTimings: ResolvedDelayedLeaveTimings,
|
||||
) => {
|
||||
return enterRTCSession(
|
||||
|
||||
@@ -8,7 +8,7 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import {
|
||||
Status as RTCMemberStatus,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type MatrixRTCSession,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import {
|
||||
@@ -127,7 +127,7 @@ const mockedClient = {
|
||||
};
|
||||
|
||||
describe("enterRTCSession", () => {
|
||||
const transport: LivekitTransport = {
|
||||
const transport: UnstableLivekitTransport = {
|
||||
livekit_service_url: "http://my-livekit-service-url.com",
|
||||
type: "livekit",
|
||||
};
|
||||
@@ -306,7 +306,7 @@ describe("LocalMembership", () => {
|
||||
|
||||
const mockTransport = {
|
||||
livekit_service_url: "a",
|
||||
} as LivekitTransport;
|
||||
} as UnstableLivekitTransport;
|
||||
|
||||
const connectionTransportAConnected = {
|
||||
livekitRoom: mockLivekitRoom({
|
||||
@@ -459,7 +459,7 @@ describe("LocalMembership", () => {
|
||||
it("tracks livekit state correctly", async () => {
|
||||
const scope = new ObservableScope();
|
||||
const connectionMap = new ConnectionMap();
|
||||
const localTransport$ = new Subject<LivekitTransport>();
|
||||
const localTransport$ = new Subject<UnstableLivekitTransport>();
|
||||
|
||||
const connectionMap$ = new BehaviorSubject(new Epoch(connectionMap));
|
||||
const publishers: Publisher[] = [];
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { observeParticipantEvents } from "@livekit/components-core";
|
||||
import {
|
||||
Status as RTCSessionStatus,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type MatrixRTCSession,
|
||||
type RTCCallIntent,
|
||||
type RTCNotificationType,
|
||||
@@ -141,13 +141,13 @@ interface Props {
|
||||
connectionMap$: Behavior<Epoch<ConnectionMap>>;
|
||||
createPublisherFactory: (connection: Connection) => Publisher;
|
||||
joinMatrixRTC: (
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
delayedLeaveTimings: ResolvedDelayedLeaveTimings,
|
||||
) => void;
|
||||
homeserverConnected: HomeserverConnected;
|
||||
roomId: string;
|
||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||
localTransport$: Observable<LivekitTransport>;
|
||||
localTransport$: Observable<UnstableLivekitTransport>;
|
||||
client: ClientOpenIDParts;
|
||||
matrixRTCSession: Pick<
|
||||
MatrixRTCSession,
|
||||
@@ -307,15 +307,16 @@ export const createLocalMembership$ = ({
|
||||
const joinParams$ = scope.behavior(
|
||||
localTransport$.pipe(
|
||||
switchMap(async (transport) => {
|
||||
const transportSupportsDelegation = checkDelegationSupport(
|
||||
transport.livekit_service_url + "/delegate_delayed_leave",
|
||||
`transport ${transport.livekit_service_url}`,
|
||||
);
|
||||
const transportSupportsDelegation =
|
||||
"livekit_service_url" in transport &&
|
||||
(await checkDelegationSupport(
|
||||
transport.livekit_service_url + "/delegate_delayed_leave",
|
||||
`transport ${transport.livekit_service_url}`,
|
||||
));
|
||||
return {
|
||||
transport,
|
||||
delegationSupported:
|
||||
(await homeserverSupportsDelegation) ||
|
||||
(await transportSupportsDelegation),
|
||||
transportSupportsDelegation || (await homeserverSupportsDelegation),
|
||||
};
|
||||
}),
|
||||
),
|
||||
@@ -715,6 +716,8 @@ export const createLocalMembership$ = ({
|
||||
async ([joinParams, delayId]) => {
|
||||
if (joinParams?.delegationSupported && delayId !== null) {
|
||||
try {
|
||||
if (!("livekit_service_url" in joinParams.transport))
|
||||
throw new Error("Transport has no livekit_service_url");
|
||||
// This will technically cause the service to issue a new JWT token,
|
||||
// but it's safe to discard. We're only interested in triggering
|
||||
// delegation.
|
||||
@@ -731,10 +734,7 @@ export const createLocalMembership$ = ({
|
||||
});
|
||||
} catch (e) {
|
||||
// TODO: Surface this to the user as a service interruption?
|
||||
logger.error(
|
||||
`Failed to delegate leave to ${joinParams.transport.livekit_service_url}`,
|
||||
e,
|
||||
);
|
||||
logger.error(`Failed to delegate leave`, e);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -967,7 +967,7 @@ interface EnterRTCSessionOptions {
|
||||
*
|
||||
* @param rtcSession - The MatrixRTCSession to join.
|
||||
* @param ownMembershipIdentity - Options for entering the RTC session.
|
||||
* @param transport - The LivekitTransport to use for this session.
|
||||
* @param transport - The UnstableLivekitTransport to use for this session.
|
||||
* @param options - `encryptMedia`: Whether to encrypt media. `matrixRTCMode`: The
|
||||
* Matrix RTC mode to use. `delayedLeaveTimings`: The preferred timings for
|
||||
* delayed leave events. `sendNotificationType`: Whether and what kind of
|
||||
@@ -978,7 +978,7 @@ interface EnterRTCSessionOptions {
|
||||
export function enterRTCSession(
|
||||
rtcSession: MatrixRTCSession,
|
||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
{
|
||||
encryptMedia,
|
||||
matrixRTCMode,
|
||||
|
||||
@@ -5,7 +5,7 @@ SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
@@ -20,7 +20,7 @@ import { RtcTransportAutoDiscovery } from "./RtcTransportAutoDiscovery.ts";
|
||||
*/
|
||||
export async function getLocalTransport(
|
||||
client: Pick<MatrixClient, "getDomain" | "_unstable_getRTCTransports">,
|
||||
): Promise<LivekitTransport> {
|
||||
): Promise<UnstableLivekitTransport> {
|
||||
const discovery = new RtcTransportAutoDiscovery({
|
||||
client,
|
||||
resolvedConfig: Config.get(),
|
||||
@@ -29,7 +29,7 @@ export async function getLocalTransport(
|
||||
const customUrl = customLivekitUrl.value$.value;
|
||||
|
||||
// Respect the user's custom URL, if set
|
||||
const transport: LivekitTransport | null = customUrl
|
||||
const transport: UnstableLivekitTransport | null = customUrl
|
||||
? { type: "livekit", livekit_service_url: customUrl }
|
||||
: await discovery.discoverPreferredTransport();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { MatrixError } from "matrix-js-sdk";
|
||||
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type Transport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
@@ -28,12 +28,12 @@ import {
|
||||
|
||||
type DiscoveryClient = RtcTransportAutoDiscoveryProps["client"];
|
||||
|
||||
const backendTransport: LivekitTransport = {
|
||||
const backendTransport: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://backend.example.org",
|
||||
};
|
||||
|
||||
const configTransport: LivekitTransport = {
|
||||
const configTransport: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://config.example.org",
|
||||
};
|
||||
|
||||
@@ -5,8 +5,8 @@ SPDX-License-IdFentifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
import {
|
||||
isLivekitTransport,
|
||||
type LivekitTransport,
|
||||
isUnstableLivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||
@@ -40,7 +40,7 @@ export class RtcTransportAutoDiscovery {
|
||||
this.logger = logger.getChild("[RtcTransportAutoDiscovery]");
|
||||
}
|
||||
|
||||
public async discoverPreferredTransport(): Promise<LivekitTransport | null> {
|
||||
public async discoverPreferredTransport(): Promise<UnstableLivekitTransport | null> {
|
||||
// 1) backend transports
|
||||
const backendTransport = await this.tryBackendTransports();
|
||||
if (backendTransport) {
|
||||
@@ -67,7 +67,7 @@ export class RtcTransportAutoDiscovery {
|
||||
* This will not throw errors, but instead just log them and return null if the expected config is not found or malformed.
|
||||
* @private
|
||||
*/
|
||||
private async tryBackendTransports(): Promise<LivekitTransport | null> {
|
||||
private async tryBackendTransports(): Promise<UnstableLivekitTransport | null> {
|
||||
const client = this.client;
|
||||
// MSC4143: Attempt to fetch transports from backend.
|
||||
this.logger.info("First try to use getRTCTransports end point ...");
|
||||
@@ -75,7 +75,7 @@ export class RtcTransportAutoDiscovery {
|
||||
const transportList = await doNetworkOperationWithRetry(async () =>
|
||||
client._unstable_getRTCTransports(),
|
||||
);
|
||||
const first = transportList.find(isLivekitTransport);
|
||||
const first = transportList.find(isUnstableLivekitTransport);
|
||||
if (first) {
|
||||
return first;
|
||||
} else {
|
||||
@@ -90,7 +90,7 @@ export class RtcTransportAutoDiscovery {
|
||||
return null;
|
||||
}
|
||||
|
||||
private tryConfigTransport(): LivekitTransport | null {
|
||||
private tryConfigTransport(): UnstableLivekitTransport | null {
|
||||
const url = this.resolvedConfig.livekit?.livekit_service_url;
|
||||
if (url) {
|
||||
return {
|
||||
|
||||
@@ -29,7 +29,7 @@ import fetchMock from "fetch-mock";
|
||||
import EventEmitter from "events";
|
||||
import { type IOpenIDToken } from "matrix-js-sdk";
|
||||
import { logger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
import {
|
||||
Connection,
|
||||
@@ -54,8 +54,9 @@ let fakeLocalParticipant: MockedObject<LocalParticipant>;
|
||||
const ROOM_ID = "!roomID:example.org";
|
||||
const SLOT_ID = "m.call#ROOM";
|
||||
|
||||
const livekitFocus: LivekitTransport = {
|
||||
livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt",
|
||||
const livekitServiceUrl = "https://matrix-rtc.example.org/livekit/jwt";
|
||||
const livekitFocus: UnstableLivekitTransport = {
|
||||
livekit_service_url: livekitServiceUrl,
|
||||
type: "livekit",
|
||||
};
|
||||
|
||||
@@ -122,7 +123,7 @@ function setupRemoteConnection(): Connection {
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/get_token`, () => {
|
||||
fetchMock.post(`${livekitServiceUrl}/get_token`, () => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
@@ -250,19 +251,16 @@ describe("Start connection states", () => {
|
||||
|
||||
const deferredSFU = Promise.withResolvers<void>();
|
||||
// mock the /get_token call
|
||||
fetchMock.post(
|
||||
`${livekitFocus.livekit_service_url}/get_token`,
|
||||
async () => {
|
||||
await deferredSFU.promise;
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
errcode: "M_LOOKUP_FAILED",
|
||||
error: "Failed to look up user info from homeserver",
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
fetchMock.post(`${livekitServiceUrl}/get_token`, async () => {
|
||||
await deferredSFU.promise;
|
||||
return {
|
||||
status: 500,
|
||||
body: {
|
||||
errcode: "M_LOOKUP_FAILED",
|
||||
error: "Failed to look up user info from homeserver",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
connection.start().catch(() => {
|
||||
// expected to throw
|
||||
@@ -317,7 +315,7 @@ describe("Start connection states", () => {
|
||||
|
||||
const deferredSFU = Promise.withResolvers<void>();
|
||||
// mock the /get_token call
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/get_token`, () => {
|
||||
fetchMock.post(`${livekitServiceUrl}/get_token`, () => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
type Track,
|
||||
type TrackPublication,
|
||||
} from "livekit-client";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
@@ -50,7 +50,7 @@ export interface ConnectionOpts {
|
||||
/** Whether we want to publish or only subscribe on this connection. */
|
||||
role: "publisher" | "subscriber";
|
||||
/** The media transport to connect to. */
|
||||
transport: LivekitTransport;
|
||||
transport: UnstableLivekitTransport;
|
||||
/** The Matrix client to use for OpenID and SFU config requests. */
|
||||
client: ClientOpenIDParts;
|
||||
/** The ID of the Matrix room in which the session takes place. */
|
||||
@@ -97,7 +97,7 @@ export class Connection {
|
||||
/**
|
||||
* The media transport to connect to.
|
||||
*/
|
||||
public readonly transport: LivekitTransport;
|
||||
public readonly transport: UnstableLivekitTransport;
|
||||
|
||||
public readonly livekitRoom: LivekitRoom;
|
||||
|
||||
@@ -377,6 +377,8 @@ export class Connection {
|
||||
}
|
||||
|
||||
protected async getSFUConfig(): Promise<SFUConfig> {
|
||||
if (!("livekit_service_url" in this.transport))
|
||||
throw new Error("Transport has no livekit_service_url");
|
||||
return await getSFUConfig({
|
||||
client: this.client,
|
||||
membership: this.ownMembershipIdentity,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { logger, type Logger } from "matrix-js-sdk/lib/logger";
|
||||
// imported as inline to support worker when loaded from a cdn (cross domain)
|
||||
import E2EEWorker from "livekit-client/e2ee-worker?worker&inline";
|
||||
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
import { type ObservableScope } from "../../ObservableScope.ts";
|
||||
import { Connection } from "./Connection.ts";
|
||||
@@ -42,7 +42,7 @@ export interface ConnectionFactory {
|
||||
createConnection(
|
||||
scope: ObservableScope,
|
||||
role: "publisher" | "subscriber",
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||
logger: Logger,
|
||||
): Connection;
|
||||
@@ -103,7 +103,7 @@ export class ECConnectionFactory implements ConnectionFactory {
|
||||
public createConnection(
|
||||
scope: ObservableScope,
|
||||
role: "publisher" | "subscriber",
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
ownMembershipIdentity: CallMembershipIdentityParts,
|
||||
logger: Logger,
|
||||
): Connection {
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type RemoteParticipant } from "livekit-client";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
@@ -21,18 +21,18 @@ import { type Behavior } from "../../Behavior.ts";
|
||||
|
||||
// Some test constants
|
||||
|
||||
const TRANSPORT_1: LivekitTransport = {
|
||||
const TRANSPORT_1: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://lk.example.org",
|
||||
};
|
||||
|
||||
const TRANSPORT_2: LivekitTransport = {
|
||||
const TRANSPORT_2: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://lk.sample.com",
|
||||
};
|
||||
|
||||
function findConnection(
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
connections: Connection[],
|
||||
): Connection | undefined {
|
||||
return connections.find((c) =>
|
||||
@@ -53,7 +53,11 @@ beforeEach(() => {
|
||||
vi.mocked(fakeConnectionFactory).createConnection = vi
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(scope: ObservableScope, _role: unknown, transport: LivekitTransport) => {
|
||||
(
|
||||
scope: ObservableScope,
|
||||
_role: unknown,
|
||||
transport: UnstableLivekitTransport,
|
||||
) => {
|
||||
const mockConnection = {
|
||||
transport,
|
||||
remoteParticipants$: new BehaviorSubject([]),
|
||||
@@ -224,15 +228,17 @@ describe("connectionMap$", () => {
|
||||
// Used in test to control fake connections' remoteParticipants$ streams
|
||||
let fakeRemoteParticipantsStreams: Map<string, Behavior<RemoteParticipant[]>>;
|
||||
|
||||
function keyForTransport(transport: LivekitTransport): string {
|
||||
return `${transport.livekit_service_url}`;
|
||||
function keyForTransport(transport: UnstableLivekitTransport): string {
|
||||
return "livekit_service_url" in transport
|
||||
? transport.livekit_service_url
|
||||
: transport.url;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
fakeRemoteParticipantsStreams = new Map();
|
||||
|
||||
function getRemoteParticipantsFor(
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
): Behavior<RemoteParticipant[]> {
|
||||
return (
|
||||
fakeRemoteParticipantsStreams.get(keyForTransport(transport)) ??
|
||||
@@ -247,7 +253,7 @@ describe("connectionMap$", () => {
|
||||
(
|
||||
scope: ObservableScope,
|
||||
_role: unknown,
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
) => {
|
||||
const fakeRemoteParticipants$ = new BehaviorSubject<
|
||||
RemoteParticipant[]
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { combineLatest, map, of, switchMap, tap } from "rxjs";
|
||||
import { type Logger } from "matrix-js-sdk/lib/logger";
|
||||
import { type RemoteParticipant } from "livekit-client";
|
||||
@@ -23,7 +23,17 @@ import { generateItemsWithEpoch } from "../../../utils/observable.ts";
|
||||
import { areLivekitTransportsEqual } from "./MatrixLivekitMembers.ts";
|
||||
import { type ConnectionFactory } from "./ConnectionFactory.ts";
|
||||
|
||||
function removeDuplicateTransports<T extends LivekitTransport>(
|
||||
/**
|
||||
* The URL by which a transport is identified: its JWT service URL, or failing
|
||||
* that, its SFU URL.
|
||||
*/
|
||||
function urlOf(transport: UnstableLivekitTransport): string {
|
||||
return "livekit_service_url" in transport
|
||||
? transport.livekit_service_url
|
||||
: transport.url;
|
||||
}
|
||||
|
||||
function removeDuplicateTransports<T extends UnstableLivekitTransport>(
|
||||
transports: T[],
|
||||
): T[] {
|
||||
return transports.reduce((acc, transport) => {
|
||||
@@ -56,10 +66,10 @@ export class ConnectionMap {
|
||||
}
|
||||
}
|
||||
|
||||
private getKey(transport: LivekitTransport): string {
|
||||
private getKey(transport: UnstableLivekitTransport): string {
|
||||
// This is enough as a key because the ConnectionMap is already scoped by room.
|
||||
// We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`.
|
||||
return transport.livekit_service_url;
|
||||
return urlOf(transport);
|
||||
}
|
||||
|
||||
public getConnections(): Connection[] {
|
||||
@@ -67,13 +77,13 @@ export class ConnectionMap {
|
||||
}
|
||||
|
||||
public getConnectionForTransport(
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
): Connection | null {
|
||||
return this.map.get(this.getKey(transport))?.connection ?? null;
|
||||
}
|
||||
|
||||
public getParticipantsForTransport(
|
||||
transport: LivekitTransport,
|
||||
transport: UnstableLivekitTransport,
|
||||
): RemoteParticipant[] {
|
||||
const existing = this.map.get(this.getKey(transport));
|
||||
if (existing) {
|
||||
@@ -86,8 +96,8 @@ export class ConnectionMap {
|
||||
interface Props {
|
||||
scope: ObservableScope;
|
||||
connectionFactory: ConnectionFactory;
|
||||
localTransport: LivekitTransport;
|
||||
remoteTransports$: Behavior<Epoch<LivekitTransport[]>>;
|
||||
localTransport: UnstableLivekitTransport;
|
||||
remoteTransports$: Behavior<Epoch<UnstableLivekitTransport[]>>;
|
||||
|
||||
logger: Logger;
|
||||
ownMembershipIdentity: CallMembershipIdentityParts;
|
||||
@@ -118,7 +128,7 @@ export function createConnectionMap$({
|
||||
* All transports currently represented in the connection map. This list does
|
||||
* not include duplicate transports.
|
||||
*/
|
||||
const localAndRemoteTransports$: Behavior<Epoch<LivekitTransport[]>> =
|
||||
const localAndRemoteTransports$: Behavior<Epoch<UnstableLivekitTransport[]>> =
|
||||
scope.behavior(
|
||||
remoteTransports$.pipe(
|
||||
mapEpoch((remoteTransports) =>
|
||||
@@ -142,17 +152,14 @@ export function createConnectionMap$({
|
||||
const role = areLivekitTransportsEqual(transport, localTransport)
|
||||
? ("publisher" as const)
|
||||
: ("subscriber" as const);
|
||||
yield {
|
||||
keys: [role, transport.livekit_service_url],
|
||||
data: undefined,
|
||||
};
|
||||
yield { keys: [role, urlOf(transport)], data: transport };
|
||||
}
|
||||
},
|
||||
(scope, _data$, role, serviceUrl) => {
|
||||
(scope, transport$, role, _url) => {
|
||||
const connection = connectionFactory.createConnection(
|
||||
scope,
|
||||
role,
|
||||
{ type: "livekit", livekit_service_url: serviceUrl },
|
||||
transport$.value,
|
||||
ownMembershipIdentity,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
type CallMembership,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { BehaviorSubject, combineLatest, map, type Observable } from "rxjs";
|
||||
|
||||
@@ -38,12 +38,12 @@ let testScope: ObservableScope;
|
||||
const fallbackMemberId = (userId: string, deviceId: string): string =>
|
||||
`${userId}:${deviceId}`;
|
||||
|
||||
const transportA: LivekitTransport = {
|
||||
const transportA: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://lk.example.org",
|
||||
};
|
||||
|
||||
const transportB: LivekitTransport = {
|
||||
const transportB: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://lk.sample.com",
|
||||
};
|
||||
@@ -111,7 +111,9 @@ test("should signal participant not yet connected to livekit", async () => {
|
||||
function createEpochedMemberships$(m$: Observable<CallMembership[]>): {
|
||||
memberships$: Observable<Epoch<CallMembership[]>>;
|
||||
membershipsWithTransport$: Observable<
|
||||
Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]>
|
||||
Epoch<
|
||||
{ membership: CallMembership; transport?: UnstableLivekitTransport }[]
|
||||
>
|
||||
>;
|
||||
} {
|
||||
const memberships$ = m$.pipe(trackEpoch());
|
||||
@@ -122,7 +124,9 @@ function createEpochedMemberships$(m$: Observable<CallMembership[]>): {
|
||||
return {
|
||||
membership: m,
|
||||
transport:
|
||||
tr?.type === "livekit" ? (tr as LivekitTransport) : undefined,
|
||||
tr?.type === "livekit"
|
||||
? (tr as UnstableLivekitTransport)
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
}),
|
||||
|
||||
@@ -8,10 +8,11 @@ Please see LICENSE in the repository root for full details.
|
||||
import { type LocalParticipant, type RemoteParticipant } from "livekit-client";
|
||||
import {
|
||||
type CallMembership,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { combineLatest, filter, map } from "rxjs";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||
|
||||
import { type Behavior } from "../../Behavior";
|
||||
import { type ConnectionMap } from "./ConnectionMap";
|
||||
@@ -60,7 +61,9 @@ export interface RemoteMatrixLivekitMember extends MatrixLivekitMember {
|
||||
interface Props {
|
||||
scope: ObservableScope;
|
||||
membershipsWithTransport$: Behavior<
|
||||
Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]>
|
||||
Epoch<
|
||||
{ membership: CallMembership; transport?: UnstableLivekitTransport }[]
|
||||
>
|
||||
>;
|
||||
connectionMap$: Behavior<Epoch<ConnectionMap>>;
|
||||
localUser: { deviceId: string; userId: string };
|
||||
@@ -162,12 +165,12 @@ export function createRemoteMatrixLivekitMembers$({
|
||||
// TODO add back in the callviewmodel pauseWhen(this.pretendToBeDisconnected$)
|
||||
|
||||
// TODO add this to the JS-SDK
|
||||
export function areLivekitTransportsEqual<T extends LivekitTransport>(
|
||||
export function areLivekitTransportsEqual<T extends UnstableLivekitTransport>(
|
||||
t1: T | null,
|
||||
t2: T | null,
|
||||
): boolean {
|
||||
if (t1 && t2) {
|
||||
return t1.livekit_service_url === t2.livekit_service_url;
|
||||
return deepCompare(t1, t2);
|
||||
}
|
||||
return !t1 && !t2;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BehaviorSubject } from "rxjs";
|
||||
import { type Room as LivekitRoom } from "livekit-client";
|
||||
import EventEmitter from "events";
|
||||
import fetchMock from "fetch-mock";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import {
|
||||
@@ -152,7 +152,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
||||
a: expect.toSatisfy((co) =>
|
||||
areLivekitTransportsEqual(
|
||||
co.transport,
|
||||
bobMembership.transports[0]! as LivekitTransport,
|
||||
bobMembership.transports[0]! as UnstableLivekitTransport,
|
||||
),
|
||||
),
|
||||
});
|
||||
@@ -189,7 +189,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
||||
expect(
|
||||
areLivekitTransportsEqual(
|
||||
connection.transport,
|
||||
carlMembership.transports[0]! as LivekitTransport,
|
||||
carlMembership.transports[0]! as UnstableLivekitTransport,
|
||||
),
|
||||
).toBe(true);
|
||||
return true;
|
||||
@@ -219,7 +219,7 @@ test("bob, carl, then bob joining no tracks yet", () => {
|
||||
expect(
|
||||
areLivekitTransportsEqual(
|
||||
connection.transport,
|
||||
daveMembership.transports[0]! as LivekitTransport,
|
||||
daveMembership.transports[0]! as UnstableLivekitTransport,
|
||||
),
|
||||
).toBe(true);
|
||||
return true;
|
||||
|
||||
@@ -7,10 +7,10 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import {
|
||||
type CallMembership,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type MatrixRTCSession,
|
||||
MatrixRTCSessionEvent,
|
||||
isLivekitTransport,
|
||||
isUnstableLivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { fromEvent } from "rxjs";
|
||||
|
||||
@@ -31,15 +31,17 @@ export const membershipsAndTransports$ = (
|
||||
memberships$: Behavior<Epoch<CallMembership[]>>,
|
||||
): {
|
||||
membershipsWithTransport$: Behavior<
|
||||
Epoch<{ membership: CallMembership; transport?: LivekitTransport }[]>
|
||||
Epoch<
|
||||
{ membership: CallMembership; transport?: UnstableLivekitTransport }[]
|
||||
>
|
||||
>;
|
||||
transports$: Behavior<Epoch<LivekitTransport[]>>;
|
||||
transports$: Behavior<Epoch<UnstableLivekitTransport[]>>;
|
||||
} => {
|
||||
const membershipsWithTransport$: Behavior<
|
||||
Epoch<
|
||||
{
|
||||
membership: CallMembership;
|
||||
transport: LivekitTransport | undefined;
|
||||
transport: UnstableLivekitTransport | undefined;
|
||||
}[]
|
||||
>
|
||||
> = scope.behavior(
|
||||
@@ -49,18 +51,21 @@ export const membershipsAndTransports$ = (
|
||||
const transport = membership.getTransport();
|
||||
return {
|
||||
membership,
|
||||
transport: isLivekitTransport(transport) ? transport : undefined,
|
||||
transport: isUnstableLivekitTransport(transport)
|
||||
? transport
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const transports$: Behavior<Epoch<LivekitTransport[]>> = scope.behavior(
|
||||
membershipsWithTransport$.pipe(
|
||||
mapEpoch((mts) => mts.flatMap(({ transport: t }) => (t ? [t] : []))),
|
||||
),
|
||||
);
|
||||
const transports$: Behavior<Epoch<UnstableLivekitTransport[]>> =
|
||||
scope.behavior(
|
||||
membershipsWithTransport$.pipe(
|
||||
mapEpoch((mts) => mts.flatMap(({ transport: t }) => (t ? [t] : []))),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
membershipsWithTransport$,
|
||||
|
||||
@@ -40,7 +40,7 @@ import { E2eeType } from "../../e2ee/e2eeType";
|
||||
import { observeInboundRtpStreamStats$ } from "./observeRtpStreamStats";
|
||||
import { type UserMediaViewModel } from "./UserMediaViewModel";
|
||||
import { type ScreenShareViewModel } from "./ScreenShareViewModel";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
// TODO: Encryption status is kinda broken and thus unused right now. Remove?
|
||||
export enum EncryptionStatus {
|
||||
@@ -63,7 +63,7 @@ export interface BaseMemberMediaViewModel extends BaseMediaViewModel {
|
||||
* The LiveKit transport on which this member should be publishing. Exposed
|
||||
* for debugging.
|
||||
*/
|
||||
transport$: Behavior<LivekitTransport | undefined>;
|
||||
transport$: Behavior<UnstableLivekitTransport | undefined>;
|
||||
/**
|
||||
* Whether there should be a warning that this media is unencrypted.
|
||||
*/
|
||||
@@ -76,7 +76,7 @@ export interface MemberMediaInputs extends BaseMediaViewModel {
|
||||
livekitRoom$: Behavior<LivekitRoom | undefined>;
|
||||
audioSource: AudioSource;
|
||||
videoSource: VideoSource;
|
||||
transport$: Behavior<LivekitTransport | undefined>;
|
||||
transport$: Behavior<UnstableLivekitTransport | undefined>;
|
||||
encryptionSystem: EncryptionSystem;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ import { type RemoteUserMediaViewModel } from "../state/media/RemoteUserMediaVie
|
||||
import { type UserMediaViewModel } from "../state/media/UserMediaViewModel";
|
||||
import { type RingingMediaViewModel } from "../state/media/RingingMediaViewModel";
|
||||
import { RingingStatus } from "./RingingStatus";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
interface TileProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
@@ -104,7 +104,7 @@ interface UserMediaTileProps extends TileProps {
|
||||
playbackMuted: boolean;
|
||||
waitingForMedia?: boolean;
|
||||
primaryButton?: ReactNode;
|
||||
transport: LivekitTransport | undefined;
|
||||
transport: UnstableLivekitTransport | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@ import { ReactionIndicator } from "../reactions/ReactionIndicator";
|
||||
import { RTCConnectionStats } from "../RTCConnectionStats";
|
||||
import videoPlaceholder from "../graphics/video-placeholder.gif";
|
||||
import { autoVideoFit } from "../utils/videoFit";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
interface Props extends ComponentProps<typeof animated.div> {
|
||||
className?: string;
|
||||
@@ -70,7 +70,7 @@ interface Props extends ComponentProps<typeof animated.div> {
|
||||
/**
|
||||
* The LiveKit transport, mainly for debugging purposes.
|
||||
*/
|
||||
transport?: LivekitTransport;
|
||||
transport?: UnstableLivekitTransport;
|
||||
/**
|
||||
* Called whenever the aspect ratio of the video content becomes known or
|
||||
* otherwise changes.
|
||||
|
||||
@@ -54,7 +54,7 @@ import { platform } from "../Platform";
|
||||
import { type RingingMediaViewModel } from "../state/media/RingingMediaViewModel";
|
||||
import { RingingStatus } from "./RingingStatus";
|
||||
import { useRootElement } from "../RootElementContext";
|
||||
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
|
||||
interface SpotlightItemBaseProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
@@ -75,7 +75,7 @@ interface SpotlightItemBaseProps {
|
||||
interface SpotlightMemberMediaItemBaseProps extends SpotlightItemBaseProps {
|
||||
video: TrackReferenceOrPlaceholder | undefined;
|
||||
unencryptedWarning: boolean;
|
||||
transport: LivekitTransport | undefined;
|
||||
transport: UnstableLivekitTransport | undefined;
|
||||
}
|
||||
|
||||
interface SpotlightUserMediaItemBaseProps extends SpotlightMemberMediaItemBaseProps {
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import {
|
||||
import {
|
||||
CallMembership,
|
||||
type LivekitFocusSelection,
|
||||
type LivekitTransport,
|
||||
type UnstableLivekitTransport,
|
||||
type MatrixRTCSession,
|
||||
MatrixRTCSessionEvent,
|
||||
type MatrixRTCSessionEventHandlerMap,
|
||||
@@ -208,7 +208,7 @@ export function mockEmitter<T>(): EmitterMock<T> {
|
||||
};
|
||||
}
|
||||
|
||||
export const exampleTransport: LivekitTransport = {
|
||||
export const exampleTransport: UnstableLivekitTransport = {
|
||||
type: "livekit",
|
||||
livekit_service_url: "https://lk.example.org",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user