Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LiveKit support #3568

Draft
wants to merge 19 commits into
base: develop
Choose a base branch
from
Draft
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -216,7 +216,6 @@ import {
ServerSideSecretStorage,
ServerSideSecretStorageImpl,
} from "./secret-storage";
import { FocusInfo } from "./webrtc/callEventTypes";
import { RegisterRequest, RegisterResponse } from "./@types/registration";

export type Store = IStore;
@@ -381,7 +380,7 @@ export interface ICreateClientOpts {
*/
useE2eForGroupCall?: boolean;

foci?: FocusInfo[];
livekitServiceURL?: string;

/**
* Crypto callbacks provided by the application
@@ -1271,7 +1270,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa

private useE2eForGroupCall = true;
private toDeviceMessageQueue: ToDeviceMessageQueue;
private foci: FocusInfo[] = [];
public livekitServiceURL?: string;

private _secretStorage: ServerSideSecretStorageImpl;

@@ -1376,7 +1375,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa

if (opts.useE2eForGroupCall !== undefined) this.useE2eForGroupCall = opts.useE2eForGroupCall;

this.foci = opts.foci ?? [];
this.livekitServiceURL = opts.livekitServiceURL;

// List of which rooms have encryption enabled: separate from crypto because
// we still want to know which rooms are encrypted even if crypto is disabled:
@@ -1956,12 +1955,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
dataChannelOptions,
this.isVoipWithNoMediaAllowed,
this.useLivekitForGroupCalls,
this.foci,
this.livekitServiceURL,
).create();
}

public getFoci(): FocusInfo[] {
return this.foci;
public getLivekitServiceURL(): string | undefined {
return this.livekitServiceURL;
}

// This shouldn't need to exist, but the widget API has startup ordering problems that
// mean it doesn't know the livekit URL fast enough: remove this once this is fixed.
public setLivekitServiceURL(newURL: string): void {
this.livekitServiceURL = newURL;
}

/**
4 changes: 0 additions & 4 deletions src/webrtc/callEventTypes.ts
Original file line number Diff line number Diff line change
@@ -89,8 +89,4 @@ export interface MCallHangupReject extends MCallBase {
reason?: CallErrorCode;
}

export interface FocusInfo {
livekitServiceUrl: string;
}

/* eslint-enable camelcase */
28 changes: 17 additions & 11 deletions src/webrtc/groupCall.ts
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ import { Room } from "../models/room";
import { RoomStateEvent } from "../models/room-state";
import { logger } from "../logger";
import { ReEmitter } from "../ReEmitter";
import { FocusInfo, SDPStreamMetadataPurpose } from "./callEventTypes";
import { SDPStreamMetadataPurpose } from "./callEventTypes";
import { MatrixEvent } from "../models/event";
import { EventType } from "../@types/event";
import { CallEventHandlerEvent } from "./callEventHandler";
@@ -252,6 +252,7 @@ export class GroupCall extends TypedEventEmitter<
private initWithAudioMuted = false;
private initWithVideoMuted = false;
private initCallFeedPromise?: Promise<void>;
private _livekitServiceURL?: string;

private stats: GroupCallStats | undefined;
/**
@@ -274,12 +275,12 @@ export class GroupCall extends TypedEventEmitter<
// create the group call signaling events, with the intention that the actual media will be
// handled using livekit. The js-sdk doesn't contain any code to do the actual livekit call though.
private useLivekit = false,
foci?: FocusInfo[],
livekitServiceURL?: string,
) {
super();
this.reEmitter = new ReEmitter(this);
this.groupCallId = groupCallId ?? genCallID();
this._foci = foci ?? [];
this._livekitServiceURL = livekitServiceURL;
this.creationTs =
room.currentState.getStateEvents(EventType.GroupCallPrefix, this.groupCallId)?.getTs() ?? null;
this.updateParticipants();
@@ -328,8 +329,12 @@ export class GroupCall extends TypedEventEmitter<
this.client.groupCallEventHandler!.groupCalls.set(this.room.roomId, this);
this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);

const focus = this._foci[0];
await this.sendCallStateEvent();

return this;
}

private async sendCallStateEvent(): Promise<void> {
const groupCallState: IGroupCallRoomState = {
"m.intent": this.intent,
"m.type": this.type,
@@ -338,19 +343,20 @@ export class GroupCall extends TypedEventEmitter<
"dataChannelsEnabled": this.dataChannelsEnabled,
"dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined,
};
if (focus) {
groupCallState["io.element.livekit_service_url"] = focus.livekitServiceUrl;
if (this.livekitServiceURL) {
groupCallState["io.element.livekit_service_url"] = this.livekitServiceURL;
}

await this.client.sendStateEvent(this.room.roomId, EventType.GroupCallPrefix, groupCallState, this.groupCallId);

return this;
}

private _foci: FocusInfo[] = [];
public get livekitServiceURL(): string | undefined {
return this._livekitServiceURL;
}

public get foci(): FocusInfo[] {
return this._foci;
public updateLivekitServiceURL(newURL: string): Promise<void> {
this._livekitServiceURL = newURL;
return this.sendCallStateEvent();
}

private _state = GroupCallState.LocalCallFeedUninitialized;
19 changes: 1 addition & 18 deletions src/webrtc/groupCallEventHandler.ts
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@ import { RoomMember } from "../models/room-member";
import { logger } from "../logger";
import { EventType } from "../@types/event";
import { SyncState } from "../sync";
import { FocusInfo } from "./callEventTypes";

export enum GroupCallEventHandlerEvent {
Incoming = "GroupCall.incoming",
@@ -178,22 +177,6 @@ export class GroupCallEventHandler {
dataChannelOptions = { ordered, maxPacketLifeTime, maxRetransmits, protocol };
}

const livekitServiceUrl = content["io.element.livekit_service_url"];

let focus: FocusInfo | undefined;
if (livekitServiceUrl) {
focus = {
livekitServiceUrl: livekitServiceUrl,
};
} else {
focus = this.client.getFoci()[0]!;
content["io.element.livekit_service_url"] = focus.livekitServiceUrl;
// try to update the state event to add our livekit service url. We may not be able to
// as we don't currently have permission to send this event in embedded mode
// (although it doesn't actually appear to reject when it fails).
this.client.sendStateEvent(room.roomId, EventType.GroupCallPrefix, content, groupCallId);
}

const groupCall = new GroupCall(
this.client,
room,
@@ -207,7 +190,7 @@ export class GroupCallEventHandler {
dataChannelOptions,
this.client.isVoipWithNoMediaAllowed,
this.client.useLivekitForGroupCalls,
focus ? [focus] : [],
content["io.element.livekit_service_url"],
);

this.groupCalls.set(room.roomId, groupCall);