@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.
139 lines (138 loc) • 5.15 kB
JavaScript
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
// speaker-detector-client/src/hooks/useSpeakerDetection.js
import { useEffect, useRef, useState } from "react";
import { API_BASE } from "../lib/constants";
import useListeningMode from "./useListeningMode";
export function useSpeakerDetection() {
var {
endpoint = "/api/active-speaker",
log = true
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var {
threshold,
intervalMs
} = useListeningMode("off"); // ← SSOT values
var minConfidence = threshold;
var interval = intervalMs;
var [speaker, setSpeaker] = useState(null);
var [confidence, setConfidence] = useState(null);
var [isSpeaking, setIsSpeaking] = useState(false);
var [status, setStatus] = useState("pending");
var [error, setError] = useState(null);
var [backendOnline, setBackendOnline] = useState(null);
var lastAnnouncedNameRef = useRef(null);
useEffect(() => {
var id;
var check = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(function* () {
try {
var r = yield fetch("".concat(API_BASE, "/api/version"), {
cache: "no-store"
});
if (!r.ok) throw new Error();
yield r.json();
setBackendOnline(true);
} catch (_unused) {
setBackendOnline(false);
}
});
return function check() {
return _ref.apply(this, arguments);
};
}();
check();
id = setInterval(check, 5000);
return () => clearInterval(id);
}, []);
useEffect(() => {
var id,
cancelled = false;
var poll = /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(function* () {
try {
var _d$speaker;
var r = yield fetch("".concat(API_BASE).concat(endpoint), {
cache: "no-store"
});
if (!r.ok) throw new Error("Active speaker API failed");
var d = yield r.json();
if (cancelled) return;
var rawSpeaker = (_d$speaker = d.speaker) !== null && _d$speaker !== void 0 ? _d$speaker : null;
var rawConfidence = typeof d.confidence === "number" ? d.confidence : null;
var backendIsSpeaking = typeof d.is_speaking === "boolean" ? d.is_speaking : null;
var stateStatus = (d.status || "pending").trim();
setStatus(stateStatus);
setSpeaker(rawSpeaker);
setConfidence(rawConfidence);
var knownEnough = rawSpeaker && rawSpeaker !== "unknown" && (rawConfidence !== null && rawConfidence !== void 0 ? rawConfidence : 0) >= (minConfidence !== null && minConfidence !== void 0 ? minConfidence : 0.5);
var computedIsSpeaking = backendIsSpeaking !== null ? backendIsSpeaking : knownEnough;
setIsSpeaking(computedIsSpeaking);
// events
window.dispatchEvent(new CustomEvent("speaker:update", {
detail: {
name: rawSpeaker,
confidence: rawConfidence,
isSpeaking: computedIsSpeaking,
status: stateStatus,
backendOnline: backendOnline !== false,
ts: Date.now()
}
}));
if (knownEnough) {
if (lastAnnouncedNameRef.current !== rawSpeaker) {
window.dispatchEvent(new CustomEvent("speaker:identified", {
detail: {
name: rawSpeaker,
confidence: rawConfidence,
status: stateStatus,
ts: Date.now()
}
}));
lastAnnouncedNameRef.current = rawSpeaker;
}
} else if (lastAnnouncedNameRef.current) {
window.dispatchEvent(new CustomEvent("speaker:cleared", {
detail: {
previous: lastAnnouncedNameRef.current,
to: rawSpeaker || "unknown",
status: stateStatus,
confidence: rawConfidence !== null && rawConfidence !== void 0 ? rawConfidence : 0,
ts: Date.now()
}
}));
lastAnnouncedNameRef.current = null;
}
if (log) {
var pct = Math.round((rawConfidence || 0) * 100);
console.log("\uD83C\uDF99\uFE0F Detected: ".concat(rawSpeaker, " (").concat(pct, "%) \u2014 ").concat(stateStatus, " | isSpeaking=").concat(computedIsSpeaking));
}
setError(null);
} catch (e) {
if (backendOnline === false) setError("Backend unreachable");
setStatus("error");
setSpeaker(null);
setConfidence(null);
setIsSpeaking(false);
}
});
return function poll() {
return _ref2.apply(this, arguments);
};
}();
poll();
id = setInterval(poll, Math.max(300, interval || 3000));
return () => {
cancelled = true;
clearInterval(id);
};
}, [endpoint, backendOnline, log, interval, minConfidence]);
return {
speaker,
confidence,
isSpeaking,
error,
status,
backendOnline
};
}
export default useSpeakerDetection;