From 9be925012440a17c1a6e5ab1025d637181906986 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:41:29 +0200 Subject: [PATCH] Combined permission request with newer livekit sdk version (#1200) --------- Signed-off-by: Timo K --- package.json | 4 +- ...aDevices.ts => useMediaDevicesSwitcher.ts} | 17 +- src/room/InCallView.tsx | 6 +- src/room/VideoPreview.tsx | 149 ++++++++++-------- src/settings/SettingsModal.tsx | 9 +- yarn.lock | 59 ++++--- 6 files changed, 137 insertions(+), 107 deletions(-) rename src/livekit/{useMediaDevices.ts => useMediaDevicesSwitcher.ts} (84%) diff --git a/package.json b/package.json index e236b18a..09713cc6 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@juggle/resize-observer": "^3.3.1", - "@livekit/components-react": "^1.0.3", + "@livekit/components-react": "^1.0.7", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz", "@opentelemetry/api": "^1.4.0", "@opentelemetry/context-zone": "^1.9.1", @@ -55,7 +55,7 @@ "i18next": "^21.10.0", "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", - "livekit-client": "^1.9.7", + "livekit-client": "^1.11.4", "lodash": "^4.17.21", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#4b3406daa95c8f969f386341b8b632ba4a60501a", "matrix-widget-api": "^1.3.1", diff --git a/src/livekit/useMediaDevices.ts b/src/livekit/useMediaDevicesSwitcher.ts similarity index 84% rename from src/livekit/useMediaDevices.ts rename to src/livekit/useMediaDevicesSwitcher.ts index e5b0a151..303d73cd 100644 --- a/src/livekit/useMediaDevices.ts +++ b/src/livekit/useMediaDevicesSwitcher.ts @@ -1,5 +1,5 @@ import { useMediaDeviceSelect } from "@livekit/components-react"; -import { Room } from "livekit-client"; +import { LocalAudioTrack, LocalVideoTrack, Room } from "livekit-client"; import { useEffect } from "react"; import { useDefaultDevices } from "../settings/useSetting"; @@ -17,12 +17,21 @@ export type MediaDevicesState = { }; // if a room is passed this only affects the device selection inside a call. Without room it changes what we see in the lobby -export function useMediaDevices(room?: Room): MediaDevicesState { +export function useMediaDevicesSwitcher( + room?: Room, + tracks?: { videoTrack?: LocalVideoTrack; audioTrack?: LocalAudioTrack }, + requestPermissions = true +): MediaDevicesState { const { devices: videoDevices, activeDeviceId: activeVideoDevice, setActiveMediaDevice: setActiveVideoDevice, - } = useMediaDeviceSelect({ kind: "videoinput", room }); + } = useMediaDeviceSelect({ + kind: "videoinput", + room, + track: tracks?.videoTrack, + requestPermissions, + }); const { devices: audioDevices, @@ -31,6 +40,8 @@ export function useMediaDevices(room?: Room): MediaDevicesState { } = useMediaDeviceSelect({ kind: "audioinput", room, + track: tracks?.audioTrack, + requestPermissions, }); const { diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index b82b1513..8262f34d 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -80,7 +80,7 @@ import { useRageshakeRequestModal } from "../settings/submit-rageshake"; import { RageshakeRequestModal } from "./RageshakeRequestModal"; import { VideoTile } from "../video-grid/VideoTile"; import { UserChoices, useLiveKit } from "../livekit/useLiveKit"; -import { useMediaDevices } from "../livekit/useMediaDevices"; +import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher"; import { useFullscreen } from "./useFullscreen"; import { useLayoutStates } from "../video-grid/Layout"; import { useSFUConfig } from "../livekit/OpenIDLoader"; @@ -148,7 +148,7 @@ export function InCallView({ ); // Managed media devices state coupled with an active room. - const roomMediaDevices = useMediaDevices(livekitRoom); + const roomMediaSwitcher = useMediaDevicesSwitcher(livekitRoom); const screenSharingTracks = useTracks( [{ source: Track.Source.ScreenShare, withPlaceholder: false }], @@ -427,7 +427,7 @@ export function InCallView({ )} diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index 9f6c6276..5f401c9b 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -14,11 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useState, useEffect, useRef, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useRef } from "react"; import useMeasure from "react-use-measure"; import { ResizeObserver } from "@juggle/resize-observer"; import { OverlayTriggerState } from "@react-stately/overlays"; -import { usePreviewDevice } from "@livekit/components-react"; +import { usePreviewTracks } from "@livekit/components-react"; +import { LocalAudioTrack, LocalVideoTrack, Track } from "livekit-client"; import { MicButton, SettingsButton, VideoButton } from "../button"; import { Avatar } from "../Avatar"; @@ -26,7 +27,7 @@ import styles from "./VideoPreview.module.css"; import { useModalTriggerState } from "../Modal"; import { SettingsModal } from "../settings/SettingsModal"; import { useClient } from "../ClientContext"; -import { useMediaDevices } from "../livekit/useMediaDevices"; +import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher"; import { DeviceChoices, UserChoices } from "../livekit/useLiveKit"; import { useDefaultDevices } from "../settings/useSetting"; @@ -61,85 +62,107 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) { settingsModalState.open(); }, [settingsModalState]); - // Fetch user media devices. - const mediaDevices = useMediaDevices(); - // Create local media tracks. const [videoEnabled, setVideoEnabled] = useState(true); const [audioEnabled, setAudioEnabled] = useState(true); - const [videoId, audioId] = [ - mediaDevices.videoIn.selectedId, - mediaDevices.audioIn.selectedId, - ]; - const [defaultDevices] = useDefaultDevices(); - const video = usePreviewDevice( - videoEnabled, - videoId != "" ? videoId : defaultDevices.videoinput, - "videoinput" - ); - const audio = usePreviewDevice( - audioEnabled, - audioId != "" ? audioId : defaultDevices.audioinput, - "audioinput" - ); - const activeVideoId = video?.selectedDevice?.deviceId; - const activeAudioId = audio?.selectedDevice?.deviceId; + // The settings are updated as soon as the device changes. We wrap the settings value in a ref to store their initial value. + // Not changing the device options prohibits the usePreviewTracks hook to recreate the tracks. + const initialDefaultDevices = useRef(useDefaultDevices()[0]); + + const tracks = usePreviewTracks( + { + audio: { deviceId: initialDefaultDevices.current.audioinput }, + video: { deviceId: initialDefaultDevices.current.videoinput }, + }, + (error) => { + console.error("Error while creating preview Tracks:", error); + } + ); + const videoTrack = React.useMemo( + () => + tracks?.filter((t) => t.kind === Track.Kind.Video)[0] as LocalVideoTrack, + [tracks] + ); + const audioTrack = React.useMemo( + () => + tracks?.filter((t) => t.kind === Track.Kind.Audio)[0] as LocalAudioTrack, + [tracks] + ); + // Only let the MediaDeviceSwitcher request permissions if a video track is already available. + // Otherwise we would end up asking for permissions in usePreviewTracks and in useMediaDevicesSwitcher. + const requestPermissions = !!videoTrack; + const mediaSwitcher = useMediaDevicesSwitcher( + undefined, + { + videoTrack, + audioTrack, + }, + requestPermissions + ); + const { videoIn, audioIn } = mediaSwitcher; + + const videoEl = React.useRef(null); + useEffect(() => { + // Effect to update the settings const createChoices = ( enabled: boolean, deviceId?: string ): DeviceChoices | undefined => { - if (deviceId === undefined) { - return undefined; - } - - return { - selectedId: deviceId, - enabled, - }; + return deviceId + ? { + selectedId: deviceId, + enabled, + } + : undefined; }; - onUserChoicesChanged({ - video: createChoices(videoEnabled, activeVideoId), - audio: createChoices(audioEnabled, activeAudioId), + video: createChoices(videoEnabled, videoIn.selectedId), + audio: createChoices(audioEnabled, audioIn.selectedId), }); }, [ onUserChoicesChanged, - activeVideoId, + videoIn.selectedId, videoEnabled, - activeAudioId, + audioIn.selectedId, audioEnabled, ]); - const [selectVideo, selectAudio] = [ - mediaDevices.videoIn.setSelected, - mediaDevices.audioIn.setSelected, - ]; useEffect(() => { - if (activeVideoId && activeVideoId !== "") { - selectVideo(activeVideoId); + // Effect to update the initial device selection for the ui elements based on the current preview track. + if (!videoIn.selectedId || videoIn.selectedId == "") { + videoTrack?.getDeviceId().then((videoId) => { + if (videoId) { + videoIn.setSelected(videoId); + } + }); } - if (activeAudioId && activeAudioId !== "") { - selectAudio(activeAudioId); + if (!audioIn.selectedId || audioIn.selectedId == "") { + audioTrack?.getDeviceId().then((audioId) => { + if (audioId) { + audioIn.setSelected(audioId); + } + }); } - }, [selectVideo, selectAudio, activeVideoId, activeAudioId]); + }, [videoIn, audioIn, videoTrack, audioTrack]); - const mediaElement = useRef(null); useEffect(() => { - if (mediaElement.current) { - video?.localTrack?.attach(mediaElement.current); + // Effect to connect the videoTrack with the video element. + if (videoEl.current) { + videoTrack?.unmute(); + videoTrack?.attach(videoEl.current); } return () => { - video?.localTrack?.detach(); + videoTrack?.detach(); }; - }, [video?.localTrack, mediaElement]); + }, [videoTrack]); return (
-