@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
JavaScript
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
};
}