@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.
106 lines (103 loc) • 3.89 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
// hooks/useSpeakerDetection.js
import { useState, useEffect } from "react";
import useListeningMode from "./useListeningMode";
var DEFAULT_API_URL = import.meta.env.VITE_SPEAKER_API_URL || "http://localhost:9000";
export function useSpeakerDetection() {
var {
interval = 3000,
minConfidence = 0.5,
endpoint = "/api/active-speaker",
log = true
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var [speaker, setSpeaker] = useState(null);
var [confidence, setConfidence] = useState(null);
var [isSpeaking, setIsSpeaking] = useState(false);
var [error, setError] = useState(null);
var [status, setStatus] = useState("pending");
var [backendOnline, setBackendOnline] = useState(null);
var {
mode: listeningMode
} = useListeningMode("off");
// Heartbeat: backend reachability
useEffect(() => {
var id;
var check = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(function* () {
try {
var res = yield fetch("".concat(DEFAULT_API_URL, "/api/version"), {
cache: "no-store"
});
if (!res.ok) throw new Error("Version API failed");
yield res.json();
setBackendOnline(true);
} catch (_unused) {
setBackendOnline(false);
}
});
return function check() {
return _ref.apply(this, arguments);
};
}();
check();
id = setInterval(check, 5000); // snappier
return () => clearInterval(id);
}, []);
// Active speaker polling — ALWAYS poll so UI can show disabled status too
useEffect(() => {
var id;
var cancelled = false;
var poll = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(function* () {
try {
var _data$status, _data$speaker;
var res = yield fetch("".concat(DEFAULT_API_URL).concat(endpoint), {
cache: "no-store"
});
if (!res.ok) throw new Error("Active speaker API failed");
var data = yield res.json();
if (cancelled) return;
// data from backend is already { speaker, confidence, status, is_speaking }
var s = (_data$status = data.status) !== null && _data$status !== void 0 ? _data$status : "pending";
var spk = (_data$speaker = data.speaker) !== null && _data$speaker !== void 0 ? _data$speaker : null;
var conf = typeof data.confidence === "number" ? data.confidence : null;
var speaking = typeof data.is_speaking === "boolean" ? data.is_speaking : (conf !== null && conf !== void 0 ? conf : 0) >= minConfidence && spk && spk !== "unknown";
setStatus(s);
setSpeaker(spk);
setConfidence(conf);
setIsSpeaking(speaking);
setError(null);
if (log) {
console.log("\uD83C\uDF99\uFE0F Detected: ".concat(spk !== null && spk !== void 0 ? spk : "—", " (").concat(Math.round((conf !== null && conf !== void 0 ? conf : 0) * 100), "%) | ").concat(s));
}
} catch (err) {
console.error("❌ Failed to poll active speaker:", err);
if (backendOnline === false) setError("Backend unreachable");
setStatus("error");
setSpeaker(null);
setConfidence(null);
setIsSpeaking(false);
}
});
return function poll() {
return _ref2.apply(this, arguments);
};
}();
poll(); // immediate
id = setInterval(poll, interval);
return () => {
cancelled = true;
clearInterval(id);
};
}, [interval, minConfidence, endpoint, log, backendOnline /* note: not gated by listeningMode */]);
return {
speaker,
confidence,
isSpeaking,
error,
status,
backendOnline,
listeningMode
};
}
export default useSpeakerDetection;