voicebot-react-native-expo
Version:
This is a voicebot-react-native package of Kipps AI voice bot for React Native Expo
178 lines (161 loc) • 4.89 kB
text/typescript
import { log, setupLiveKitRoom } from '@livekit/components-core';
import { Room, MediaDeviceFailure, RoomEvent, ConnectionState } from 'livekit-client';
import * as React from 'react';
import type { HTMLAttributes } from 'react';
import type { LiveKitRoomProps } from '../components';
import { mergeProps } from '../mergeProps';
const defaultRoomProps: Partial<LiveKitRoomProps> = {
connect: true,
audio: false,
video: false,
};
/**
* The `useLiveKitRoom` hook is used to implement the `LiveKitRoom` or your custom implementation of it.
* It returns a `Room` instance and HTML props that should be applied to the root element of the component.
*
* @example
* ```tsx
* const { room, htmlProps } = useLiveKitRoom();
* return <div {...htmlProps}>...</div>;
* ```
* @public
*/
export function useLiveKitRoom<T extends HTMLElement>(
props: LiveKitRoomProps,
): {
room: Room | undefined;
htmlProps: HTMLAttributes<T>;
} {
const {
token,
serverUrl,
options,
room: passedRoom,
connectOptions,
connect,
audio,
video,
screen,
onConnected,
onDisconnected,
onError,
onMediaDeviceFailure,
onEncryptionError,
simulateParticipants,
...rest
} = { ...defaultRoomProps, ...props };
if (options && passedRoom) {
log.warn(
'when using a manually created room, the options object will be ignored. set the desired options directly when creating the room instead.',
);
}
const [room, setRoom] = React.useState<Room | undefined>();
React.useEffect(() => {
setRoom(passedRoom ?? new Room(options));
}, [passedRoom]);
const htmlProps = React.useMemo(() => {
const { className } = setupLiveKitRoom();
return mergeProps(rest, { className }) as HTMLAttributes<T>;
}, [rest]);
React.useEffect(() => {
if (!room) return;
const onSignalConnected = () => {
const localP = room.localParticipant;
log.debug('trying to publish local tracks');
Promise.all([
localP.setMicrophoneEnabled(!!audio, typeof audio !== 'boolean' ? audio : undefined),
localP.setCameraEnabled(!!video, typeof video !== 'boolean' ? video : undefined),
localP.setScreenShareEnabled(!!screen, typeof screen !== 'boolean' ? screen : undefined),
]).catch((e) => {
log.warn(e);
onError?.(e as Error);
});
};
const handleMediaDeviceError = (e: Error) => {
const mediaDeviceFailure = MediaDeviceFailure.getFailure(e);
onMediaDeviceFailure?.(mediaDeviceFailure);
};
const handleEncryptionError = (e: Error) => {
onEncryptionError?.(e);
};
room
.on(RoomEvent.SignalConnected, onSignalConnected)
.on(RoomEvent.MediaDevicesError, handleMediaDeviceError)
.on(RoomEvent.EncryptionError, handleEncryptionError);
return () => {
room
.off(RoomEvent.SignalConnected, onSignalConnected)
.off(RoomEvent.MediaDevicesError, handleMediaDeviceError)
.off(RoomEvent.EncryptionError, handleEncryptionError);
};
}, [room, audio, video, screen, onError, onEncryptionError, onMediaDeviceFailure]);
React.useEffect(() => {
if (!room) return;
if (simulateParticipants) {
room.simulateParticipants({
participants: {
count: simulateParticipants,
},
publish: {
audio: true,
useRealTracks: true,
},
});
return;
}
if (!token) {
log.debug('no token yet');
return;
}
if (!serverUrl) {
log.warn('no livekit url provided');
onError?.(Error('no livekit url provided'));
return;
}
if (connect) {
log.debug('connecting');
room.connect(serverUrl, token, connectOptions).catch((e) => {
log.warn(e);
onError?.(e as Error);
});
} else {
log.debug('disconnecting because connect is false');
room.disconnect();
}
}, [
connect,
token,
JSON.stringify(connectOptions),
room,
onError,
serverUrl,
simulateParticipants,
]);
React.useEffect(() => {
if (!room) return;
const connectionStateChangeListener = (state: ConnectionState) => {
switch (state) {
case ConnectionState.Disconnected:
if (onDisconnected) onDisconnected();
break;
case ConnectionState.Connected:
if (onConnected) onConnected();
break;
default:
break;
}
};
room.on(RoomEvent.ConnectionStateChanged, connectionStateChangeListener);
return () => {
room.off(RoomEvent.ConnectionStateChanged, connectionStateChangeListener);
};
}, [token, onConnected, onDisconnected, room]);
React.useEffect(() => {
if (!room) return;
return () => {
log.info('disconnecting on onmount');
room.disconnect();
};
}, [room]);
return { room, htmlProps };
}