UNPKG

@p0llen/speaker-detector-client

Version:

React client for interfacing with the [speaker-detector](https://pypi.org/project/speaker-detector) Python backend. Supports real-time speaker identification, meeting transcription, and speaker enrollment.

148 lines (146 loc) 5.25 kB
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator"; /** * Mic check hook with device list + selection. * Returns: * - micStatus ("pending", "ok", "silent", "none", "blocked", "error") * - devices (array of { deviceId, label }) * - selectedDeviceId (string) * - setSelectedDeviceId (function) */ import { useEffect, useState, useRef } from "react"; // Map mic check codes to UI-friendly messages var readableMicStatus = { pending: "⏳ Checking microphone...", ok: "🎤 Mic active and working", silent: "🤫 Mic detected but silent (muted or virtual)", none: "❌ No microphone detected (unplugged or missing)", blocked: "🚫 Microphone access blocked by browser", error: "⚠️ Microphone check failed" }; export default function useMicCheck() { var [micStatus, setMicStatus] = useState("pending"); var [devices, setDevices] = useState([]); var [selectedDeviceId, setSelectedDeviceId] = useState(""); var audioContextRef = useRef(null); var streamRef = useRef(null); var timeoutRef = useRef(null); var cleanup = () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } if (audioContextRef.current) { audioContextRef.current.close(); audioContextRef.current = null; } if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; } }; var refreshDeviceList = /*#__PURE__*/function () { var _ref = _asyncToGenerator(function* () { try { var allDevices = yield navigator.mediaDevices.enumerateDevices(); var audioInputs = allDevices.filter(d => d.kind === "audioinput"); setDevices(audioInputs); // Auto-select first device if none selected if (!selectedDeviceId && audioInputs.length > 0) { setSelectedDeviceId(audioInputs[0].deviceId); } } catch (err) { console.error("❌ Failed to enumerate devices:", err); setDevices([]); } }); return function refreshDeviceList() { return _ref.apply(this, arguments); }; }(); var checkMic = /*#__PURE__*/function () { var _ref2 = _asyncToGenerator(function* () { var deviceId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : selectedDeviceId; cleanup(); setMicStatus("pending"); if (!deviceId) { setMicStatus("none"); return; } try { var stream = yield navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: deviceId } } }); streamRef.current = stream; var audioContext = new AudioContext(); audioContextRef.current = audioContext; var source = audioContext.createMediaStreamSource(stream); var analyser = audioContext.createAnalyser(); var data = new Uint8Array(analyser.frequencyBinCount); source.connect(analyser); var silenceFrames = 0; var checkVolume = () => { analyser.getByteTimeDomainData(data); var isSilent = data.every(v => Math.abs(v - 128) < 2); if (!isSilent) { console.log("🎤 Mic active and working"); clearTimeout(timeoutRef.current); setMicStatus("ok"); cleanup(); } else { silenceFrames++; if (silenceFrames > 10) { console.warn("🤫 Mic detected but silent or virtual"); clearTimeout(timeoutRef.current); setMicStatus("silent"); cleanup(); } else { requestAnimationFrame(checkVolume); } } }; checkVolume(); timeoutRef.current = setTimeout(() => { console.error("⏱️ Mic check timeout — unknown state"); setMicStatus("error"); cleanup(); }, 5000); } catch (err) { console.error("❌ Mic check failed:", err); setMicStatus(err.name === "NotAllowedError" ? "blocked" : err.name === "NotFoundError" ? "none" : "error"); } }); return function checkMic() { return _ref2.apply(this, arguments); }; }(); // Initial run + device change listener useEffect(() => { var _navigator$mediaDevic; refreshDeviceList(); checkMic(selectedDeviceId); var onChange = () => { console.log("📦 Audio device change detected"); refreshDeviceList(); checkMic(selectedDeviceId); }; (_navigator$mediaDevic = navigator.mediaDevices) === null || _navigator$mediaDevic === void 0 || _navigator$mediaDevic.addEventListener("devicechange", onChange); return () => { var _navigator$mediaDevic2; (_navigator$mediaDevic2 = navigator.mediaDevices) === null || _navigator$mediaDevic2 === void 0 || _navigator$mediaDevic2.removeEventListener("devicechange", onChange); cleanup(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDeviceId]); return { micStatus, micStatusLabel: readableMicStatus[micStatus] || "⚠️ Unknown state", devices, selectedDeviceId, setSelectedDeviceId, checkMic }; }