publisher: only use highlevel participant APIs

This commit is contained in:
Valere
2025-12-12 10:31:08 +01:00
parent e4404e5bb1
commit f8310b4611
5 changed files with 608 additions and 296 deletions

View File

@@ -6,12 +6,13 @@ Please see LICENSE in the repository root for full details.
*/
import {
type LocalTrack,
type Participant,
ParticipantEvent,
type LocalParticipant,
type ScreenShareCaptureOptions,
ConnectionState,
RoomEvent,
MediaDeviceFailure,
} from "livekit-client";
import { observeParticipantEvents } from "@livekit/components-core";
import {
@@ -24,6 +25,7 @@ import {
combineLatest,
distinctUntilChanged,
from,
fromEvent,
map,
type Observable,
of,
@@ -61,9 +63,9 @@ export enum RTCBackendState {
WaitingForConnection = "waiting_for_connection",
/** Connection and transport arrived, publisher Initialized */
Initialized = "Initialized",
CreatingTracks = "creating_tracks",
// CreatingTracks = "creating_tracks",
ReadyToPublish = "ready_to_publish",
WaitingToPublish = "waiting_to_publish",
// WaitingToPublish = "waiting_to_publish",
Connected = "connected",
Disconnected = "disconnected",
Disconnecting = "disconnecting",
@@ -74,9 +76,9 @@ type LocalMemberRtcBackendState =
| { state: RTCBackendState.WaitingForTransport }
| { state: RTCBackendState.WaitingForConnection }
| { state: RTCBackendState.Initialized }
| { state: RTCBackendState.CreatingTracks }
// | { state: RTCBackendState.CreatingTracks }
| { state: RTCBackendState.ReadyToPublish }
| { state: RTCBackendState.WaitingToPublish }
// | { state: RTCBackendState.WaitingToPublish }
| { state: RTCBackendState.Connected }
| { state: RTCBackendState.Disconnected }
| { state: RTCBackendState.Disconnecting };
@@ -159,7 +161,7 @@ export const createLocalMembership$ = ({
/**
* This starts audio and video tracks. They will be reused when calling `requestConnect`.
*/
startTracks: () => Behavior<LocalTrack[]>;
startTracks: () => Behavior<void>;
/**
* This sets a inner state (shouldConnect) to true and instructs the js-sdk and livekit to keep the user
* connected to matrix and livekit.
@@ -172,7 +174,7 @@ export const createLocalMembership$ = ({
* Callback to toggle screen sharing. If null, screen sharing is not possible.
*/
toggleScreenSharing: (() => void) | null;
tracks$: Behavior<LocalTrack[]>;
// tracks$: Behavior<LocalTrack[]>;
participant$: Behavior<LocalParticipant | null>;
connection$: Behavior<Connection | null>;
homeserverConnected$: Behavior<boolean>;
@@ -224,6 +226,33 @@ export const createLocalMembership$ = ({
),
);
// Tracks error that happen when creating the local tracks.
const mediaErrors$ = localConnection$.pipe(
switchMap((connection) => {
if (!connection) {
return of(null);
} else {
return fromEvent(
connection.livekitRoom,
RoomEvent.MediaDevicesError,
(error: Error) => {
return MediaDeviceFailure.getFailure(error) ?? null;
},
);
}
}),
);
mediaErrors$.pipe(scope.bind()).subscribe((error) => {
if (error) {
logger.error(`Failed to create local tracks:`, error);
// TODO is it fatal? Do we need to create a new Specialized Error?
setMatrixError(
new UnknownCallError(new Error(`Media device error: ${error}`)),
);
}
});
const localConnectionState$ = localConnection$.pipe(
switchMap((connection) => (connection ? connection.state$ : of(null))),
);
@@ -293,16 +322,16 @@ export const createLocalMembership$ = ({
/**
* Extract the tracks from the published. Also reacts to changing publishers.
*/
const tracks$ = scope.behavior(
publisher$.pipe(switchMap((p) => (p?.tracks$ ? p.tracks$ : constant([])))),
);
const publishing$ = scope.behavior(
publisher$.pipe(switchMap((p) => p?.publishing$ ?? constant(false))),
);
// const tracks$ = scope.behavior(
// publisher$.pipe(switchMap((p) => (p?.tracks$ ? p.tracks$ : constant([])))),
// );
// const publishing$ = scope.behavior(
// publisher$.pipe(switchMap((p) => p?.publishing$ ?? constant(false))),
// );
const startTracks = (): Behavior<LocalTrack[]> => {
const startTracks = (): Behavior<void> => {
trackStartRequested.resolve();
return tracks$;
return constant(undefined);
};
const requestConnect = (): void => {
@@ -327,7 +356,7 @@ export const createLocalMembership$ = ({
}
return Promise.resolve(async (): Promise<void> => {
await publisher$?.value?.stopPublishing();
publisher$?.value?.stopTracks();
await publisher$?.value?.stopTracks();
});
});
@@ -335,13 +364,16 @@ export const createLocalMembership$ = ({
// `tracks$` will update once they are ready.
scope.reconcile(
scope.behavior(
combineLatest([publisher$, tracks$, from(trackStartRequested.promise)]),
combineLatest([
publisher$ /*, tracks$*/,
from(trackStartRequested.promise),
]),
null,
),
async (valueIfReady) => {
if (!valueIfReady) return;
const [publisher, tracks] = valueIfReady;
if (publisher && tracks.length === 0) {
const [publisher] = valueIfReady;
if (publisher) {
await publisher.createAndSetupTracks().catch((e) => logger.error(e));
}
},
@@ -349,22 +381,23 @@ export const createLocalMembership$ = ({
// Based on `connectRequested$` we start publishing tracks. (once they are there!)
scope.reconcile(
scope.behavior(combineLatest([publisher$, tracks$, connectRequested$])),
async ([publisher, tracks, shouldConnect]) => {
if (shouldConnect === publisher?.publishing$.value) return;
if (tracks.length !== 0 && shouldConnect) {
scope.behavior(combineLatest([publisher$, connectRequested$])),
async ([publisher, shouldConnect]) => {
if (shouldConnect) {
try {
await publisher?.startPublishing();
} catch (error) {
setLivekitError(error as ElementCallError);
}
} else if (tracks.length !== 0 && !shouldConnect) {
try {
await publisher?.stopPublishing();
} catch (error) {
setLivekitError(new UnknownCallError(error as Error));
}
}
// XXX Why is that?
// else {
// try {
// await publisher?.stopPublishing();
// } catch (error) {
// setLivekitError(new UnknownCallError(error as Error));
// }
// }
},
);
@@ -378,12 +411,12 @@ export const createLocalMembership$ = ({
combineLatest([
publisher$,
localTransport$,
tracks$.pipe(
tap((t) => {
logger.info("tracks$: ", t);
}),
),
publishing$,
// tracks$.pipe(
// tap((t) => {
// logger.info("tracks$: ", t);
// }),
// ),
// publishing$,
connectRequested$,
from(trackStartRequested.promise).pipe(
map(() => true),
@@ -395,8 +428,8 @@ export const createLocalMembership$ = ({
([
publisher,
localTransport,
tracks,
publishing,
// tracks,
// publishing,
shouldConnect,
shouldStartTracks,
error,
@@ -408,15 +441,15 @@ export const createLocalMembership$ = ({
// as:
// We do have <A> but not yet <B> so we are in <MyState>
if (error !== null) return { state: RTCBackendState.Error, error };
const hasTracks = tracks.length > 0;
// const hasTracks = tracks.length > 0;
if (!localTransport)
return { state: RTCBackendState.WaitingForTransport };
if (!publisher)
return { state: RTCBackendState.WaitingForConnection };
if (!shouldStartTracks) return { state: RTCBackendState.Initialized };
if (!hasTracks) return { state: RTCBackendState.CreatingTracks };
// if (!hasTracks) return { state: RTCBackendState.CreatingTracks };
if (!shouldConnect) return { state: RTCBackendState.ReadyToPublish };
if (!publishing) return { state: RTCBackendState.WaitingToPublish };
// if (!publishing) return { state: RTCBackendState.WaitingToPublish };
return { state: RTCBackendState.Connected };
},
),
@@ -588,7 +621,7 @@ export const createLocalMembership$ = ({
livekit$: livekitState$,
matrix$: matrixState$,
},
tracks$,
// tracks$,
participant$,
homeserverConnected$,
reconnecting$,