react-mic-cam-permissions
Version:
Managing camera and microphone permissions with React
211 lines (210 loc) • 12.4 kB
JavaScript
// 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"))));
};