Start using LiveKit SDK for media devices

This version is not supposed to properly work, this is a work in
progress.

Main changes:
* Completely removed the PTT logic (for simplicity, it could be
  introduced later).
* Abstracted away the work with the media devices.
* Defined confined interfaces of the affected components so that they
  only get the data that they need without importing Matris JS SDK or
  LiveKit SDK, so that we can exchange their "backend" at any time.
* Started using JS/TS SDK from LiveKit as well as their React SDK to
  define the state of the local media devices and local streams.
This commit is contained in:
Daniel Abramov
2023-05-26 20:41:32 +02:00
parent e4f279fa63
commit f4f5c1ed31
22 changed files with 579 additions and 1670 deletions

145
src/room/useLiveKit.ts Normal file
View File

@@ -0,0 +1,145 @@
import { EventEmitter } from "events";
import { Room, RoomEvent, Track } from "livekit-client";
import React from "react";
import { useLocalParticipant } from "@livekit/components-react";
import {
MediaDeviceHandlerCallbacks,
MediaDeviceHandlerEvents,
MediaDevicesManager,
} from "./devices/mediaDevices";
import { MediaDevicesState, useMediaDevices } from "./devices/useMediaDevices";
import { LocalMediaInfo, MediaInfo } from "./VideoPreview";
import type TypedEmitter from "typed-emitter";
type LiveKitState = {
mediaDevices: MediaDevicesState;
localMedia: LocalMediaInfo;
enterRoom: () => Promise<void>;
leaveRoom: () => Promise<void>;
};
// Returns the React state for the LiveKit's Room class.
// The actual return type should be `LiveKitState`, but since this is a React hook, the initialisation is
// delayed (done after the rendering, not during the rendering), because of that this function may return `undefined`.
// But soon this state is changed to the actual `LiveKitState` value.
export function useLiveKit(
url: string,
token: string
): LiveKitState | undefined {
// TODO: Pass the proper paramters to configure the room (supported codecs, simulcast, adaptive streaming, etc).
const [room] = React.useState<Room>(() => {
return new Room();
});
const [mediaDevicesManager] = React.useState<MediaDevicesManager>(() => {
return new LkMediaDevicesManager(room);
});
const { state: mediaDevicesState, selectActiveDevice: selectDeviceFn } =
useMediaDevices(mediaDevicesManager);
React.useEffect(() => {
console.log("media devices changed, mediaDevices:", mediaDevicesState);
}, [mediaDevicesState]);
const {
microphoneTrack,
isMicrophoneEnabled,
cameraTrack,
isCameraEnabled,
localParticipant,
} = useLocalParticipant({ room });
const [state, setState] = React.useState<LiveKitState | undefined>(undefined);
React.useEffect(() => {
// Helper to create local media without the
const createLocalMedia = (
enabled: boolean,
track: Track | undefined,
setEnabled
): MediaInfo | undefined => {
if (!track) {
return undefined;
}
return {
track,
muted: !enabled,
setMuted: async (newState: boolean) => {
if (enabled != newState) {
await setEnabled(newState);
}
},
};
};
const state: LiveKitState = {
mediaDevices: {
state: mediaDevicesState,
selectActiveDevice: selectDeviceFn,
},
localMedia: {
audio: createLocalMedia(
isMicrophoneEnabled,
microphoneTrack?.track,
localParticipant.setMicrophoneEnabled
),
video: createLocalMedia(
isCameraEnabled,
cameraTrack?.track,
localParticipant.setCameraEnabled
),
},
enterRoom: async () => {
// TODO: Pass connection parameters (autosubscribe, etc.).
await room.connect(url, token);
},
leaveRoom: async () => {
await room.disconnect();
},
};
setState(state);
}, [
url,
token,
room,
mediaDevicesState,
selectDeviceFn,
localParticipant,
microphoneTrack,
cameraTrack,
isMicrophoneEnabled,
isCameraEnabled,
]);
return state;
}
// Implement the MediaDevicesHandler interface for the LiveKit's Room class by wrapping it, so that
// we can pass the confined version of the `Room` to the `MediaDevicesHandler` consumers.
export class LkMediaDevicesManager
extends (EventEmitter as new () => TypedEmitter<MediaDeviceHandlerCallbacks>)
implements MediaDevicesManager
{
private room: Room;
constructor(room: Room) {
super();
this.room = room;
this.room.on(RoomEvent.MediaDevicesChanged, () => {
this.emit(MediaDeviceHandlerEvents.DevicesChanged);
});
}
async getDevices(kind: MediaDeviceKind) {
return await Room.getLocalDevices(kind);
}
async setActiveDevice(kind: MediaDeviceKind, deviceId: string) {
await this.room.switchActiveDevice(kind, deviceId);
}
}