Combined permission request with newer livekit sdk version (#1200)
--------- Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
@@ -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 {
|
||||
@@ -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({
|
||||
<SettingsModal
|
||||
client={client}
|
||||
roomId={groupCall.room.roomId}
|
||||
mediaDevices={roomMediaDevices}
|
||||
mediaDevicesSwitcher={roomMediaSwitcher}
|
||||
{...settingsModalProps}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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<boolean>(true);
|
||||
const [audioEnabled, setAudioEnabled] = useState<boolean>(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 (
|
||||
<div className={styles.preview} ref={previewRef}>
|
||||
<video ref={mediaElement} muted playsInline disablePictureInPicture />
|
||||
<video ref={videoEl} muted playsInline disablePictureInPicture />
|
||||
<>
|
||||
{(video ? !videoEnabled : true) && (
|
||||
{(videoTrack ? !videoEnabled : true) && (
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
size={(previewBounds.height - 66) / 2}
|
||||
@@ -149,25 +172,21 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.previewButtons}>
|
||||
{audio.localTrack && (
|
||||
<MicButton
|
||||
muted={!audioEnabled}
|
||||
onPress={() => setAudioEnabled(!audioEnabled)}
|
||||
/>
|
||||
)}
|
||||
{video.localTrack && (
|
||||
<VideoButton
|
||||
muted={!videoEnabled}
|
||||
onPress={() => setVideoEnabled(!videoEnabled)}
|
||||
/>
|
||||
)}
|
||||
<MicButton
|
||||
muted={!audioEnabled}
|
||||
onPress={() => setAudioEnabled(!audioEnabled)}
|
||||
/>
|
||||
<VideoButton
|
||||
muted={!videoEnabled}
|
||||
onPress={() => setVideoEnabled(!videoEnabled)}
|
||||
/>
|
||||
<SettingsButton onPress={openSettings} />
|
||||
</div>
|
||||
</>
|
||||
{settingsModalState.isOpen && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
mediaDevices={mediaDevices}
|
||||
mediaDevicesSwitcher={mediaSwitcher}
|
||||
{...settingsModalProps}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -42,10 +42,13 @@ import { Body, Caption } from "../typography/Typography";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { ProfileSettingsTab } from "./ProfileSettingsTab";
|
||||
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
||||
import { MediaDevices, MediaDevicesState } from "../livekit/useMediaDevices";
|
||||
import {
|
||||
MediaDevices,
|
||||
MediaDevicesState,
|
||||
} from "../livekit/useMediaDevicesSwitcher";
|
||||
|
||||
interface Props {
|
||||
mediaDevices?: MediaDevicesState;
|
||||
mediaDevicesSwitcher?: MediaDevicesState;
|
||||
isOpen: boolean;
|
||||
client: MatrixClient;
|
||||
roomId?: string;
|
||||
@@ -106,7 +109,7 @@ export const SettingsModal = (props: Props) => {
|
||||
</Caption>
|
||||
);
|
||||
|
||||
const devices = props.mediaDevices;
|
||||
const devices = props.mediaDevicesSwitcher;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user