Add MatrixRTCMode and refactor local membership
Remove preferStickyEvents and multiSfu in favor of a MatrixRTCMode enum/setting (Legacy, Compatibil, Matrix_2_0). Move session join/leave, track pause/resume, and config error handling out of CallViewModel into the localMembership module. Update developer settings UI, i18n strings, and related RTC session helpers and wiring accordingly.
This commit is contained in:
@@ -12,12 +12,7 @@ import {
|
||||
MembershipManagerEvent,
|
||||
Status,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import {
|
||||
ClientEvent,
|
||||
type MatrixClient,
|
||||
SyncState,
|
||||
type Room as MatrixRoom,
|
||||
} from "matrix-js-sdk";
|
||||
import { ClientEvent, SyncState, type Room as MatrixRoom } from "matrix-js-sdk";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -25,6 +20,7 @@ import {
|
||||
map,
|
||||
type Observable,
|
||||
of,
|
||||
scan,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap,
|
||||
@@ -33,7 +29,7 @@ import { logger } from "matrix-js-sdk/lib/logger";
|
||||
|
||||
import { type Behavior } from "../Behavior";
|
||||
import { type ConnectionManager } from "../remoteMembers/ConnectionManager";
|
||||
import { type ObservableScope } from "../ObservableScope";
|
||||
import { ObservableScope } from "../ObservableScope";
|
||||
import { Publisher } from "./Publisher";
|
||||
import { type MuteStates } from "../MuteStates";
|
||||
import { type ProcessorState } from "../../livekit/TrackProcessorContext";
|
||||
@@ -44,31 +40,10 @@ import {
|
||||
enterRTCSession,
|
||||
type EnterRTCSessionOptions,
|
||||
} from "../../rtcSessionHelpers";
|
||||
import { ElementCallError } from "../../utils/errors";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
import { ElementWidgetActions, WidgetHelpers } from "../../widget";
|
||||
|
||||
/*
|
||||
* - get well known
|
||||
* - get oldest membership
|
||||
* - get transport to use
|
||||
* - get openId + jwt token
|
||||
* - wait for createTrack() call
|
||||
* - create tracks
|
||||
* - wait for join() call
|
||||
* - Publisher.publishTracks()
|
||||
* - send join state/sticky event
|
||||
*/
|
||||
interface Props {
|
||||
scope: ObservableScope;
|
||||
mediaDevices: MediaDevices;
|
||||
muteStates: MuteStates;
|
||||
connectionManager: ConnectionManager;
|
||||
matrixRTCSession: MatrixRTCSession;
|
||||
matrixRoom: MatrixRoom;
|
||||
localTransport$: Behavior<LivekitTransport>;
|
||||
client: MatrixClient;
|
||||
roomId: string;
|
||||
e2eeLivekitOptions: E2EEOptions | undefined;
|
||||
trackerProcessorState$: Behavior<ProcessorState>;
|
||||
}
|
||||
enum LivekitState {
|
||||
UNINITIALIZED = "uninitialized",
|
||||
CONNECTING = "connecting",
|
||||
@@ -95,10 +70,35 @@ type LocalMemberMatrixState =
|
||||
| { state: MatrixState.CONNECTING }
|
||||
| { state: MatrixState.DISCONNECTED };
|
||||
|
||||
interface LocalMemberState {
|
||||
export interface LocalMemberState {
|
||||
livekit$: BehaviorSubject<LocalMemberLivekitState>;
|
||||
matrix$: BehaviorSubject<LocalMemberMatrixState>;
|
||||
}
|
||||
|
||||
/*
|
||||
* - get well known
|
||||
* - get oldest membership
|
||||
* - get transport to use
|
||||
* - get openId + jwt token
|
||||
* - wait for createTrack() call
|
||||
* - create tracks
|
||||
* - wait for join() call
|
||||
* - Publisher.publishTracks()
|
||||
* - send join state/sticky event
|
||||
*/
|
||||
interface Props {
|
||||
scope: ObservableScope;
|
||||
mediaDevices: MediaDevices;
|
||||
muteStates: MuteStates;
|
||||
connectionManager: ConnectionManager;
|
||||
matrixRTCSession: MatrixRTCSession;
|
||||
matrixRoom: MatrixRoom;
|
||||
localTransport$: Behavior<LivekitTransport | undefined>;
|
||||
e2eeLivekitOptions: E2EEOptions | undefined;
|
||||
trackProcessorState$: Behavior<ProcessorState>;
|
||||
widget: WidgetHelpers | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is responsible for managing the own membership in a room.
|
||||
* We want
|
||||
@@ -120,7 +120,8 @@ export const localMembership$ = ({
|
||||
localTransport$,
|
||||
matrixRoom,
|
||||
e2eeLivekitOptions,
|
||||
trackerProcessorState$,
|
||||
trackProcessorState$,
|
||||
widget,
|
||||
}: Props): {
|
||||
// publisher: Publisher
|
||||
requestConnect: (options: EnterRTCSessionOptions) => LocalMemberState;
|
||||
@@ -129,6 +130,8 @@ export const localMembership$ = ({
|
||||
state: LocalMemberState; // TODO this is probably superseeded by joinState$
|
||||
homeserverConnected$: Behavior<boolean>;
|
||||
connected$: Behavior<boolean>;
|
||||
reconnecting$: Behavior<boolean>;
|
||||
configError$: Behavior<ElementCallError | null>;
|
||||
} => {
|
||||
const state = {
|
||||
livekit$: new BehaviorSubject<LocalMemberLivekitState>({
|
||||
@@ -148,11 +151,12 @@ export const localMembership$ = ({
|
||||
|
||||
const connection$ = scope.behavior(
|
||||
combineLatest([connectionManager.connections$, localTransport$]).pipe(
|
||||
map(([connections, transport]) =>
|
||||
connections.find((connection) =>
|
||||
map(([connections, transport]) => {
|
||||
if (transport === undefined) return undefined;
|
||||
return connections.find((connection) =>
|
||||
areLivekitTransportsEqual(connection.transport, transport),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
/**
|
||||
@@ -214,7 +218,7 @@ export const localMembership$ = ({
|
||||
mediaDevices,
|
||||
muteStates,
|
||||
e2eeLivekitOptions,
|
||||
trackerProcessorState$,
|
||||
trackProcessorState$,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -242,31 +246,28 @@ export const localMembership$ = ({
|
||||
// /**
|
||||
// * Whether we should tell the user that we're reconnecting to the call.
|
||||
// */
|
||||
// // DISCUSSION own membership manager
|
||||
// const reconnecting$ = scope.behavior(
|
||||
// connected$.pipe(
|
||||
// // We are reconnecting if we previously had some successful initial
|
||||
// // connection but are now disconnected
|
||||
// scan(
|
||||
// ({ connectedPreviously }, connectedNow) => ({
|
||||
// connectedPreviously: connectedPreviously || connectedNow,
|
||||
// reconnecting: connectedPreviously && !connectedNow,
|
||||
// }),
|
||||
// { connectedPreviously: false, reconnecting: false },
|
||||
// ),
|
||||
// map(({ reconnecting }) => reconnecting),
|
||||
// ),
|
||||
// );
|
||||
// DISCUSSION is there a better way to do this?
|
||||
// sth that is more deriectly implied from the membership manager of the js sdk. (fromEvent(matrixRTCSession, Reconnecting)) ??? or similar
|
||||
const reconnecting$ = scope.behavior(
|
||||
connected$.pipe(
|
||||
// We are reconnecting if we previously had some successful initial
|
||||
// connection but are now disconnected
|
||||
scan(
|
||||
({ connectedPreviously }, connectedNow) => ({
|
||||
connectedPreviously: connectedPreviously || connectedNow,
|
||||
reconnecting: connectedPreviously && !connectedNow,
|
||||
}),
|
||||
{ connectedPreviously: false, reconnecting: false },
|
||||
),
|
||||
map(({ reconnecting }) => reconnecting),
|
||||
),
|
||||
);
|
||||
|
||||
const startTracks = (): Behavior<LocalTrack[]> => {
|
||||
shouldStartTracks$.next(true);
|
||||
return tracks$;
|
||||
};
|
||||
|
||||
// const joinState$ = new BehaviorSubject<LocalMemberLivekitState>({
|
||||
// state: LivekitState.UNINITIALIZED,
|
||||
// });
|
||||
|
||||
const requestConnect = (
|
||||
options: EnterRTCSessionOptions,
|
||||
): LocalMemberState => {
|
||||
@@ -288,11 +289,15 @@ export const localMembership$ = ({
|
||||
state.matrix$.next({ state: MatrixState.CONNECTING });
|
||||
localTransport$.pipe(
|
||||
tap((transport) => {
|
||||
enterRTCSession(matrixRTCSession, transport, options).catch(
|
||||
(error) => {
|
||||
logger.error(error);
|
||||
},
|
||||
);
|
||||
if (transport !== undefined) {
|
||||
enterRTCSession(matrixRTCSession, transport, options).catch(
|
||||
(error) => {
|
||||
logger.error(error);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
logger.info("Waiting for transport to enter rtc session");
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -317,6 +322,93 @@ export const localMembership$ = ({
|
||||
return state.livekit$;
|
||||
};
|
||||
|
||||
// Pause upstream of all local media tracks when we're disconnected from
|
||||
// MatrixRTC, because it can be an unpleasant surprise for the app to say
|
||||
// 'reconnecting' and yet still be transmitting your media to others.
|
||||
// We use matrixConnected$ rather than reconnecting$ because we want to
|
||||
// pause tracks during the initial joining sequence too until we're sure
|
||||
// that our own media is displayed on screen.
|
||||
combineLatest([connection$, homeserverConnected$])
|
||||
.pipe(scope.bind())
|
||||
.subscribe(([connection, connected]) => {
|
||||
if (connection?.state$.value.state !== "ConnectedToLkRoom") return;
|
||||
const publications =
|
||||
connection.livekitRoom.localParticipant.trackPublications.values();
|
||||
if (connected) {
|
||||
for (const p of publications) {
|
||||
if (p.track?.isUpstreamPaused === true) {
|
||||
const kind = p.track.kind;
|
||||
logger.log(`Resuming ${kind} track (MatrixRTC connection present)`);
|
||||
p.track
|
||||
.resumeUpstream()
|
||||
.catch((e) =>
|
||||
logger.error(
|
||||
`Failed to resume ${kind} track after MatrixRTC reconnection`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const p of publications) {
|
||||
if (p.track?.isUpstreamPaused === false) {
|
||||
const kind = p.track.kind;
|
||||
logger.log(
|
||||
`Pausing ${kind} track (uncertain MatrixRTC connection)`,
|
||||
);
|
||||
p.track
|
||||
.pauseUpstream()
|
||||
.catch((e) =>
|
||||
logger.error(
|
||||
`Failed to pause ${kind} track after entering uncertain MatrixRTC connection`,
|
||||
e,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const configError$ = new BehaviorSubject<ElementCallError | null>(null);
|
||||
// TODO I do not fully understand what this does.
|
||||
// Is it needed?
|
||||
// Is this at the right place?
|
||||
// Can this be simplified?
|
||||
// Start and stop session membership as needed
|
||||
scope.reconcile(localTransport$, async (advertised) => {
|
||||
if (advertised !== null && advertised !== undefined) {
|
||||
try {
|
||||
configError$.next(null);
|
||||
await enterRTCSession(matrixRTCSession, advertised, options);
|
||||
} catch (e) {
|
||||
logger.error("Error entering RTC session", e);
|
||||
}
|
||||
|
||||
// Update our member event when our mute state changes.
|
||||
const intentScope = new ObservableScope();
|
||||
intentScope.reconcile(muteStates.video.enabled$, async (videoEnabled) =>
|
||||
matrixRTCSession.updateCallIntent(videoEnabled ? "video" : "audio"),
|
||||
);
|
||||
|
||||
return async (): Promise<void> => {
|
||||
intentScope.end();
|
||||
// Only sends Matrix leave event. The LiveKit session will disconnect
|
||||
// as soon as either the stopConnection$ handler above gets to it or
|
||||
// the view model is destroyed.
|
||||
try {
|
||||
await matrixRTCSession.leaveRoomSession();
|
||||
} catch (e) {
|
||||
logger.error("Error leaving RTC session", e);
|
||||
}
|
||||
try {
|
||||
await widget?.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
} catch (e) {
|
||||
logger.error("Failed to send hangup action", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
startTracks,
|
||||
requestConnect,
|
||||
@@ -324,5 +416,7 @@ export const localMembership$ = ({
|
||||
state,
|
||||
homeserverConnected$,
|
||||
connected$,
|
||||
reconnecting$,
|
||||
configError$,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user