Skip to content

Update LiveKitRoom connection logic #1198

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/pink-paths-pick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@livekit/components-react': patch
---

Update LiveKitRoom connection logic to be more robust
1 change: 1 addition & 0 deletions packages/react/etc/components-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ export const LiveKitRoom: (props: React_2.PropsWithChildren<LiveKitRoomProps> &
export interface LiveKitRoomProps extends Omit<React_2.HTMLAttributes<HTMLDivElement>, 'onError'> {
audio?: AudioCaptureOptions | boolean;
connect?: boolean;
connectionSideEffect?: (room: Room) => Promise<void>;
connectOptions?: RoomConnectOptions;
// @internal (undocumented)
featureFlags?: FeatureFlags;
Expand Down
6 changes: 6 additions & 0 deletions packages/react/src/components/LiveKitRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export interface LiveKitRoomProps extends Omit<React.HTMLAttributes<HTMLDivEleme

simulateParticipants?: number | undefined;

/**
* Optional side effect to run in parallel with the connection process.
* This can be used to enable the microphone preconnect buffer / etc
*/
connectionSideEffect?: (room: Room) => Promise<void>;

Comment on lines 80 to +86
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new prop / parameter allows a user to inject in a side effect to run in parallel with the room.connect(...) call - an example of its use in agent-starter-react:

// Within a react component:
const enableMicrophonePreConnectBuffer = useCallback(async (room: Room) => {
  room.localParticipant.setMicrophoneEnabled(true, undefined, {
    preConnectBuffer: appConfig.isPreConnectBufferEnabled,
  });
}, []);

// (later on in the component)
return (
  <LiveKitRoom
    token="..."
    serverUrl="..."
    connectionSideEffect={enableMicrophonePreConnectBuffer}
  >
    {/* rest of application here */}
  </LiveKitRoom>
);

Note that I could see enableMicrophonePreConnectBuffer being extracted into the eventual agents sdk that has been discussed.

/**
* @internal
*/
Expand Down
38 changes: 30 additions & 8 deletions packages/react/src/hooks/useLiveKitRoom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { log, setupLiveKitRoom } from '@livekit/components-core';
import type { DisconnectReason } from 'livekit-client';
import { Room, MediaDeviceFailure, RoomEvent } from 'livekit-client';
import { Room, MediaDeviceFailure, RoomEvent, Mutex } from 'livekit-client';
import * as React from 'react';
import type { HTMLAttributes } from 'react';

Expand Down Expand Up @@ -47,6 +47,7 @@ export function useLiveKitRoom<T extends HTMLElement>(
onMediaDeviceFailure,
onEncryptionError,
simulateParticipants,
connectionSideEffect,
...rest
} = { ...defaultRoomProps, ...props };
if (options && passedRoom) {
Expand Down Expand Up @@ -84,7 +85,7 @@ export function useLiveKitRoom<T extends HTMLElement>(
});
};

const handleMediaDeviceError = (e: Error, kind: MediaDeviceKind) => {
const handleMediaDeviceError = (e: Error, kind?: MediaDeviceKind) => {
const mediaDeviceFailure = MediaDeviceFailure.getFailure(e);
onMediaDeviceFailure?.(mediaDeviceFailure, kind);
};
Expand Down Expand Up @@ -125,6 +126,8 @@ export function useLiveKitRoom<T extends HTMLElement>(
onDisconnected,
]);

const connectDisconnectLock = React.useMemo(() => new Mutex(), []);

React.useEffect(() => {
if (!room) return;

Expand Down Expand Up @@ -153,16 +156,31 @@ export function useLiveKitRoom<T extends HTMLElement>(
onError?.(Error('no livekit url provided'));
return;
}
room.connect(serverUrl, token, connectOptions).catch((e) => {
log.warn(e);
if (shouldConnect.current === true) {
onError?.(e as Error);

connectDisconnectLock.lock().then(async (unlock) => {
try {
const connectionPromise = room.connect(serverUrl, token, connectOptions);
if (connectionSideEffect) {
await Promise.all([connectionPromise, connectionSideEffect]);
} else {
await connectionPromise;
}
} catch (e) {
log.warn(e);
if (shouldConnect.current === true) {
onError?.(e as Error);
}
} finally {
unlock();
}
});
} else {
log.debug('disconnecting because connect is false');
shouldConnect.current = false;
room.disconnect();
connectDisconnectLock.lock().then(async (unlock) => {
await room.disconnect();
unlock();
});
}
}, [
connect,
Expand All @@ -172,13 +190,17 @@ export function useLiveKitRoom<T extends HTMLElement>(
onError,
serverUrl,
simulateParticipants,
connectionSideEffect,
]);

React.useEffect(() => {
if (!room) return;
return () => {
log.info('disconnecting on onmount');
room.disconnect();
connectDisconnectLock.lock().then(async (unlock) => {
await room.disconnect();
unlock();
});
};
}, [room]);

Expand Down