Device from lobby to call (#1110)

* respect mute state set in lobby for call

Signed-off-by: Timo K <toger5@hotmail.de>

* move device from lobby to call

Signed-off-by: Timo K <toger5@hotmail.de>

* save device in local storage

Signed-off-by: Timo K <toger5@hotmail.de>

* local storage + fixes

Signed-off-by: Timo K <toger5@hotmail.de>

* device permissions

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo 2023-06-14 19:20:53 +02:00 committed by GitHub
parent 89768de5e0
commit 41f2728724
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 15 deletions

View file

@ -1,5 +1,10 @@
import { LocalAudioTrack, LocalVideoTrack, Room } from "livekit-client"; import {
import React from "react"; ConnectionState,
LocalAudioTrack,
LocalVideoTrack,
Room,
} from "livekit-client";
import React, { useEffect } from "react";
import { import {
useMediaDeviceSelect, useMediaDeviceSelect,
usePreviewDevice, usePreviewDevice,
@ -8,6 +13,7 @@ import {
import { MediaDevicesState, MediaDevices } from "../settings/mediaDevices"; import { MediaDevicesState, MediaDevices } from "../settings/mediaDevices";
import { LocalMediaInfo, MediaInfo } from "../room/VideoPreview"; import { LocalMediaInfo, MediaInfo } from "../room/VideoPreview";
import { roomOptions } from "./options"; import { roomOptions } from "./options";
import { useDefaultDevices } from "../settings/useSetting";
type LiveKitState = { type LiveKitState = {
// The state of the media devices (changing the devices will also change them in the room). // The state of the media devices (changing the devices will also change them in the room).
@ -19,6 +25,10 @@ type LiveKitState = {
room: Room; room: Room;
}; };
function emptyToUndef(str) {
return str === "" ? undefined : str;
}
// Returns the React state for the LiveKit's Room class. // 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 // 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`. // delayed (done after the rendering, not during the rendering), because of that this function may return `undefined`.
@ -32,21 +42,35 @@ export function useLiveKit(): LiveKitState | undefined {
// Create a React state to store the available devices and the selected device for each kind. // Create a React state to store the available devices and the selected device for each kind.
const mediaDevices = useMediaDevicesState(room); const mediaDevices = useMediaDevicesState(room);
// Create local video track. const [settingsDefaultDevices] = useDefaultDevices();
const [videoEnabled, setVideoEnabled] = React.useState<boolean>(true); const [videoEnabled, setVideoEnabled] = React.useState<boolean>(true);
const selectedVideoId = mediaDevices.state.get("videoinput")?.selectedId; const selectedVideoId = mediaDevices.state.get("videoinput")?.selectedId;
const [audioEnabled, setAudioEnabled] = React.useState<boolean>(true);
const selectedAudioId = mediaDevices.state.get("audioinput")?.selectedId;
// trigger permission popup first,
useEffect(() => {
navigator.mediaDevices.getUserMedia({
video: { deviceId: selectedVideoId ?? settingsDefaultDevices.videoinput },
audio: { deviceId: selectedAudioId ?? settingsDefaultDevices.audioinput },
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// then start the preview device (no permsssion should be triggered agian)
// Create local video track.
const video = usePreviewDevice( const video = usePreviewDevice(
videoEnabled, videoEnabled,
selectedVideoId ?? "", selectedVideoId ?? settingsDefaultDevices.videoinput,
"videoinput" "videoinput"
); );
// Create local audio track. // Create local audio track.
const [audioEnabled, setAudioEnabled] = React.useState<boolean>(true);
const selectedAudioId = mediaDevices.state.get("audioinput")?.selectedId;
const audio = usePreviewDevice( const audio = usePreviewDevice(
audioEnabled, audioEnabled,
selectedAudioId ?? "", selectedAudioId ?? settingsDefaultDevices.audioinput,
"audioinput" "audioinput"
); );
@ -71,7 +95,6 @@ export function useLiveKit(): LiveKitState | undefined {
}, },
}; };
}; };
const state: LiveKitState = { const state: LiveKitState = {
mediaDevices: mediaDevices, mediaDevices: mediaDevices,
localMedia: { localMedia: {
@ -102,19 +125,24 @@ export function useLiveKit(): LiveKitState | undefined {
return state; return state;
} }
// if a room is passed this only affects the device selection inside a call. Without room it changes what we see in the lobby
function useMediaDevicesState(room: Room): MediaDevicesState { function useMediaDevicesState(room: Room): MediaDevicesState {
let connectedRoom: Room;
if (room.state !== ConnectionState.Disconnected) {
connectedRoom = room;
}
const { const {
devices: videoDevices, devices: videoDevices,
activeDeviceId: activeVideoDevice, activeDeviceId: activeVideoDevice,
setActiveMediaDevice: setActiveVideoDevice, setActiveMediaDevice: setActiveVideoDevice,
} = useMediaDeviceSelect({ kind: "videoinput", room }); } = useMediaDeviceSelect({ kind: "videoinput", room: connectedRoom });
const { const {
devices: audioDevices, devices: audioDevices,
activeDeviceId: activeAudioDevice, activeDeviceId: activeAudioDevice,
setActiveMediaDevice: setActiveAudioDevice, setActiveMediaDevice: setActiveAudioDevice,
} = useMediaDeviceSelect({ } = useMediaDeviceSelect({
kind: "audioinput", kind: "audioinput",
room, room: connectedRoom,
}); });
const { const {
devices: audioOutputDevices, devices: audioOutputDevices,
@ -122,7 +150,7 @@ function useMediaDevicesState(room: Room): MediaDevicesState {
setActiveMediaDevice: setActiveAudioOutputDevice, setActiveMediaDevice: setActiveAudioOutputDevice,
} = useMediaDeviceSelect({ } = useMediaDeviceSelect({
kind: "audiooutput", kind: "audiooutput",
room, room: connectedRoom,
}); });
const selectActiveDevice = React.useCallback( const selectActiveDevice = React.useCallback(
@ -139,7 +167,7 @@ function useMediaDevicesState(room: Room): MediaDevicesState {
break; break;
} }
}, },
[setActiveAudioDevice, setActiveVideoDevice, setActiveAudioOutputDevice] [setActiveVideoDevice, setActiveAudioOutputDevice, setActiveAudioDevice]
); );
const [mediaDevicesState, setMediaDevicesState] = const [mediaDevicesState, setMediaDevicesState] =
@ -151,19 +179,35 @@ function useMediaDevicesState(room: Room): MediaDevicesState {
return state; return state;
}); });
const [settingsDefaultDevices, setDefaultDevices] = useDefaultDevices();
React.useEffect(() => { React.useEffect(() => {
const state = new Map<MediaDeviceKind, MediaDevices>(); const state = new Map<MediaDeviceKind, MediaDevices>();
state.set("videoinput", { state.set("videoinput", {
available: videoDevices, available: videoDevices,
selectedId: activeVideoDevice, selectedId:
emptyToUndef(activeVideoDevice) ??
emptyToUndef(settingsDefaultDevices.videoinput) ??
videoDevices[0]?.deviceId,
}); });
state.set("audioinput", { state.set("audioinput", {
available: audioDevices, available: audioDevices,
selectedId: activeAudioDevice, selectedId:
emptyToUndef(activeAudioDevice) ??
emptyToUndef(settingsDefaultDevices.audioinput) ??
audioDevices[0]?.deviceId,
}); });
state.set("audiooutput", { state.set("audiooutput", {
available: audioOutputDevices, available: audioOutputDevices,
selectedId: activeAudioOutputDevice, selectedId:
emptyToUndef(activeAudioOutputDevice) ??
emptyToUndef(settingsDefaultDevices.audiooutput) ??
audioOutputDevices[0]?.deviceId,
});
setDefaultDevices({
audioinput: state.get("audioinput").selectedId,
videoinput: state.get("videoinput").selectedId,
audiooutput: state.get("audiooutput").selectedId,
}); });
setMediaDevicesState({ setMediaDevicesState({
state, state,
@ -177,6 +221,10 @@ function useMediaDevicesState(room: Room): MediaDevicesState {
audioOutputDevices, audioOutputDevices,
activeAudioOutputDevice, activeAudioOutputDevice,
selectActiveDevice, selectActiveDevice,
setDefaultDevices,
settingsDefaultDevices.audioinput,
settingsDefaultDevices.videoinput,
settingsDefaultDevices.audiooutput,
]); ]);
return mediaDevicesState; return mediaDevicesState;

View file

@ -165,6 +165,13 @@ export function InCallView({
options options
); );
// TODO: move the room creation into the useRoom hook and out of the useLiveKit hook.
// This would than allow to not have those 4 lines
livekitRoom.options.audioCaptureDefaults.deviceId =
mediaDevices.state.get("audioinput").selectedId;
livekitRoom.options.videoCaptureDefaults.deviceId =
mediaDevices.state.get("videoinput").selectedId;
// Uses a hook to connect to the LiveKit room (on unmount the room will be left) and publish local media tracks (default).
useRoom({ useRoom({
token, token,
serverUrl: Config.get().livekit.server_url, serverUrl: Config.get().livekit.server_url,

View file

@ -100,3 +100,10 @@ export const useOptInAnalytics = (): DisableableSetting<boolean | null> => {
export const useDeveloperSettingsTab = () => export const useDeveloperSettingsTab = () =>
useSetting("developer-settings-tab", false); useSetting("developer-settings-tab", false);
export const useDefaultDevices = () =>
useSetting("defaultDevices", {
audioinput: "",
videoinput: "",
audiooutput: "",
});