UNPKG

react-mic-cam-permissions

Version:

Managing camera and microphone permissions with React

211 lines (210 loc) 12.4 kB
// src/components/PermissionStatusDisplay.tsx var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import React, { useEffect, useState, useRef } from 'react'; import { useCameraPermission } from '../hooks/useCameraPermission'; import { useMicrophonePermission } from '../hooks/useMicrophonePermission'; import { PermissionStatus } from './PermissionStatus'; import { CameraPermissionButton } from './CameraPermissionButton'; import { MicrophonePermissionButton } from './MicrophonePermissionButton'; export const PermissionStatusDisplay = () => { const { permissionState: cameraPermissionState } = useCameraPermission(); const { permissionState: microphonePermissionState } = useMicrophonePermission(); const [videoDevices, setVideoDevices] = useState([]); const [audioDevices, setAudioDevices] = useState([]); const [selectedCamera, setSelectedCamera] = useState(null); const [selectedMicrophone, setSelectedMicrophone] = useState(null); const [audioLevel, setAudioLevel] = useState(0); const videoRef = useRef(null); const audioContextRef = useRef(null); const analyserRef = useRef(null); const dataArrayRef = useRef(null); const animationIdRef = useRef(null); const mediaStreamRef = useRef(null); // Fetch available devices on mount useEffect(() => { const fetchDevices = () => __awaiter(void 0, void 0, void 0, function* () { try { const devices = yield navigator.mediaDevices.enumerateDevices(); const videoInputDevices = devices.filter(device => device.kind === 'videoinput'); const audioInputDevices = devices.filter(device => device.kind === 'audioinput'); setVideoDevices(videoInputDevices); setAudioDevices(audioInputDevices); // Set default selections if (videoInputDevices.length > 0) { setSelectedCamera(videoInputDevices[0].deviceId); handleStartVideo(videoInputDevices[0].deviceId); } if (audioInputDevices.length > 0) { setSelectedMicrophone(audioInputDevices[0].deviceId); handleStartAudio(audioInputDevices[0].deviceId); } } catch (error) { console.error('Error fetching media devices:', error); } }); fetchDevices(); // Listen for device changes navigator.mediaDevices.addEventListener('devicechange', fetchDevices); return () => { navigator.mediaDevices.removeEventListener('devicechange', fetchDevices); stopAudio(); stopVideo(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Handle camera selection changes useEffect(() => { if (selectedCamera) { handleStartVideo(selectedCamera); } else { stopVideo(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedCamera]); // Handle microphone selection changes useEffect(() => { if (selectedMicrophone) { handleStartAudio(selectedMicrophone); } else { stopAudio(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedMicrophone]); // Function to start video stream const handleStartVideo = (deviceId) => __awaiter(void 0, void 0, void 0, function* () { stopVideo(); // Stop any existing video streams try { const stream = yield navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId } }, }); if (videoRef.current) { videoRef.current.srcObject = stream; } mediaStreamRef.current = stream; } catch (error) { console.error('Error starting video:', error); } }); // Function to stop video stream const stopVideo = () => { if (mediaStreamRef.current) { mediaStreamRef.current.getTracks().forEach(track => track.stop()); mediaStreamRef.current = null; } if (videoRef.current) { videoRef.current.srcObject = null; } }; // Function to start audio analysis const handleStartAudio = (deviceId) => __awaiter(void 0, void 0, void 0, function* () { stopAudio(); // Stop any existing audio streams try { const stream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: deviceId } }, }); mediaStreamRef.current = stream; audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); const source = audioContextRef.current.createMediaStreamSource(stream); analyserRef.current = audioContextRef.current.createAnalyser(); analyserRef.current.fftSize = 256; const bufferLength = analyserRef.current.frequencyBinCount; dataArrayRef.current = new Uint8Array(bufferLength); source.connect(analyserRef.current); const analyze = () => { if (analyserRef.current && dataArrayRef.current) { analyserRef.current.getByteFrequencyData(dataArrayRef.current); let sum = 0; for (let i = 0; i < dataArrayRef.current.length; i++) { sum += dataArrayRef.current[i]; } const avg = sum / dataArrayRef.current.length; setAudioLevel(avg); } animationIdRef.current = requestAnimationFrame(analyze); }; analyze(); } catch (error) { console.error('Error starting audio:', error); } }); // Function to stop audio analysis const stopAudio = () => { if (animationIdRef.current) { cancelAnimationFrame(animationIdRef.current); animationIdRef.current = null; } if (audioContextRef.current) { audioContextRef.current.close(); audioContextRef.current = null; } setAudioLevel(0); if (mediaStreamRef.current) { mediaStreamRef.current.getTracks().forEach(track => track.stop()); mediaStreamRef.current = null; } }; return (React.createElement("div", { className: "max-w-4xl mx-auto p-6 bg-white shadow-md rounded-lg border-2 border-gray-300" }, React.createElement("h3", { className: 'font-bold text-lg mb-2' }, "Pr\u00E9paration de l'entretiens"), React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6" }, React.createElement("div", { className: "p-4 border rounded-lg bg-gray-50" }, " ", React.createElement("div", { className: "mb-4" }, React.createElement("div", { className: 'mb-2' }, React.createElement("div", { className: "flex items-center space-x-4" }, React.createElement(CameraPermissionButton, null), React.createElement(PermissionStatus, { permissionType: "camera", state: cameraPermissionState, className: "text-sm text-gray-600" }))), React.createElement("video", { ref: videoRef, autoPlay: true, playsInline: true, muted: true, className: "w-full h-auto border rounded-md shadow-sm", width: "100%", height: "auto" })), React.createElement("div", { className: "space-y-4" }, React.createElement("div", null, React.createElement("label", { htmlFor: "cameraSelect", className: "block text-sm font-medium text-gray-700 mb-1" }, "Select Camera"), React.createElement("select", { id: "cameraSelect", className: "w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm", value: selectedCamera || '', onChange: e => setSelectedCamera(e.target.value) }, React.createElement("option", { value: "" }, "-- Select a camera --"), videoDevices.map(device => (React.createElement("option", { key: device.deviceId, value: device.deviceId }, device.label || `Camera ${device.deviceId}`))))))), React.createElement("div", { className: "p-4 border rounded-lg bg-gray-50" }, React.createElement("div", { className: 'mb-2' }, React.createElement("div", { className: "flex items-center space-x-4" }, React.createElement(MicrophonePermissionButton, null), React.createElement(PermissionStatus, { permissionType: "microphone", state: microphonePermissionState, className: "text-sm text-gray-600" }))), React.createElement("div", { className: "space-y-4" }, React.createElement("div", null, " ", React.createElement("label", { className: "block text-sm font-medium text-gray-700 mb-1" }, "Microphone Input Level"), React.createElement("progress", { value: audioLevel, max: 255, className: "w-full h-2 bg-gray-200 rounded", style: { appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none' } }, React.createElement("style", null, ` progress::-webkit-progress-bar { background-color: #e5e7eb; /* bg-gray-200 */ border-radius: 0.375rem; /* rounded */ } progress::-webkit-progress-value { background-color: #10b981; /* green-500 */ border-radius: 0.375rem; } progress::-moz-progress-bar { background-color: #10b981; border-radius: 0.375rem; } `))), React.createElement("label", { htmlFor: "microphoneSelect", className: "block text-sm font-medium text-gray-700 mb-1" }, "Select Microphone"), React.createElement("select", { id: "microphoneSelect", className: "w-full border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-green-500 text-sm", value: selectedMicrophone || '', onChange: e => setSelectedMicrophone(e.target.value) }, React.createElement("option", { value: "" }, "-- Select a microphone --"), audioDevices.map(device => (React.createElement("option", { key: device.deviceId, value: device.deviceId }, device.label || `Microphone ${device.deviceId}`))))), React.createElement("div", null))), React.createElement("p", { className: 'my-2 p-0 text-sm text-gray-500 font-bold' }, "Pour utiliser nos simulation d'entretiens , vous devez activer les permissions de votre navigateur. "), React.createElement("p", { className: 'my-2 p-0 text-sm text-gray-500' }, "La cam\u00E9ra n'est pas requise pour les simulations mais elle est recommand\u00E9e pour une meilleure exp\u00E9rience utilisateur."), React.createElement("p", { className: 'my-2 p-0 text-sm text-gray-500' }, "Le microphone est n\u00E9cessaire pour les simulations et est utilis\u00E9 pour enregistrer les conversations."), React.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6" }, React.createElement("button", { className: "w-full px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-blue-600 transition-colors text-sm disabled:bg-blue-300" }, "COMMENCER")))); };