@kortexa-ai/react-multimodal
Version:
A set of react components and hooks to help with multimodal input
1,005 lines (998 loc) • 34.6 kB
JavaScript
var de = Object.defineProperty;
var le = (l, n, r) => n in l ? de(l, n, { enumerable: !0, configurable: !0, writable: !0, value: r }) : l[n] = r;
var oe = (l, n, r) => le(l, typeof n != "symbol" ? n + "" : n, r);
import { createContext as q, useContext as ee, useState as F, useRef as R, useEffect as x, useCallback as d, useMemo as j } from "react";
import { jsx as J } from "react/jsx-runtime";
import { Hands as ue } from "@mediapipe/hands";
const re = q(
void 0
);
re.displayName = "kortexa.ai:composite-media";
const Re = () => {
const l = ee(re);
if (!l)
throw new Error(
"useCompositeMedia must be used within a CompositeMediaProvider"
);
return l;
}, ne = q(
void 0
);
ne.displayName = "kortexa.ai:microphone";
const ge = 16e3, ie = "kortexa-audio-processor", fe = `
class AudioProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.bufferSize = 512;
this.buffer = new Float32Array(this.bufferSize);
this.offset = 0;
}
process(inputs, outputs) {
const input = inputs[0]?.[0];
if (!input?.length) return true;
for (let i = 0; i < input.length; i++) {
this.buffer[this.offset++] = input[i];
if (this.offset === this.bufferSize) {
this.port.postMessage(this.buffer);
this.offset = 0;
}
}
return true;
}
}
registerProcessor('${ie}', AudioProcessor);
`;
function he() {
const l = new Blob([fe], {
type: "application/javascript; charset=utf-8"
});
return URL.createObjectURL(l);
}
function me({
sampleRate: l = ge,
onData: n,
onError: r
}) {
const [e, t] = F(!1), h = R(!1), o = R(void 0), m = R(void 0), y = R(void 0), H = R(void 0), L = R(n), g = R(r);
x(() => {
L.current = n, g.current = r;
}, [n, r]);
const C = d(() => {
var D, A, a;
if (!h.current) {
h.current = !0;
try {
m.current && (m.current.getTracks().forEach((S) => S.stop()), m.current = void 0), H.current && (H.current.disconnect(), H.current = void 0), y.current && (y.current.port.onmessage = null, y.current.disconnect(), y.current = void 0), ((D = o.current) == null ? void 0 : D.state) !== "closed" && ((A = o.current) == null || A.close(), o.current = void 0);
} catch (u) {
console.error("Error closing audio context:", u), (a = g.current) == null || a.call(g, "Failed to close audio context");
} finally {
t(!1), h.current = !1;
}
}
}, []), E = d(async () => {
var D;
e && C();
try {
(!o.current || o.current.state === "closed") && (o.current = new AudioContext({ sampleRate: l })), o.current.state === "suspended" && await o.current.resume(), await o.current.audioWorklet.addModule(
// `data:application/javascript;base64,${btoa(WORKLET_CODE)}`
he()
), m.current = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: !0,
noiseSuppression: !0
}
}), H.current = o.current.createMediaStreamSource(
m.current
), y.current = new AudioWorkletNode(
o.current,
ie
), y.current.port.onmessage = (A) => {
var a;
(a = L.current) == null || a.call(L, new Float32Array(A.data));
}, H.current.connect(y.current), y.current.connect(o.current.destination), t(!0);
} catch (A) {
console.error("Error starting audio:", A), (D = g.current) == null || D.call(g, "Failed to access microphone"), C();
}
}, [l, C, e]);
return x(() => C, [C]), {
isRecording: e,
start: E,
stop: C
};
}
class ce {
constructor() {
oe(this, "listeners", /* @__PURE__ */ new Map());
}
addListener(n, r) {
this.listeners.has(n) || this.listeners.set(n, /* @__PURE__ */ new Map()), this.listeners.get(n).set(r.id, r);
}
removeListener(n, r) {
const e = this.listeners.get(n);
e && e.delete(r);
}
async dispatch(n, r) {
const e = this.listeners.get(n);
if (!e) return;
const t = [];
e.forEach((h) => {
try {
const m = h.listener(r);
m instanceof Promise && t.push(m);
} catch (o) {
console.error(`Error in ${String(n)} listener ${h.id}:`, o);
}
}), t.length > 0 && await Promise.all(t);
}
clear(n) {
n ? this.listeners.delete(n) : this.listeners.clear();
}
getListenerCount(n) {
var r;
return ((r = this.listeners.get(n)) == null ? void 0 : r.size) ?? 0;
}
}
const P = new ce();
function ve({ children: l, ...n }) {
const {
isRecording: r,
start: e,
stop: t
} = me({
...n,
onData: (a) => {
var u;
(u = n.onData) == null || u.call(n, a), P.dispatch("data", a);
},
onError: (a) => {
var u;
(u = n.onError) == null || u.call(n, a), P.dispatch("error", a);
}
}), h = d(async () => {
try {
r || (await e(), await P.dispatch("start"));
} catch (a) {
let u = "Failed to start microphone";
throw a instanceof Error ? u += ": " + a.message : u += ": " + String(a), P.dispatch("error", u), a;
}
}, [r, e]), o = d(() => {
try {
r && (P.dispatch("stop"), t());
} catch {
P.dispatch("error", "Failed to stop microphone");
}
}, [r, t]), m = d((a) => {
const u = `kortexa-microphone-data-${Date.now()}`;
return P.addListener("data", { id: u, listener: a }), u;
}, []), y = d((a) => {
P.removeListener("data", a);
}, []), H = d((a) => {
const u = `kortexa-microphone-start-${Date.now()}`;
return P.addListener("start", { id: u, listener: a }), u;
}, []), L = d((a) => {
P.removeListener("start", a);
}, []), g = d((a) => {
const u = `kortexa-microphone-stop-${Date.now()}`;
return P.addListener("stop", { id: u, listener: a }), u;
}, []), C = d((a) => {
P.removeListener("stop", a);
}, []), E = d((a) => {
const u = `kortexa-microphone-error-${Date.now()}`;
return P.addListener("error", { id: u, listener: a }), u;
}, []), D = d((a) => {
P.removeListener("error", a);
}, []);
x(() => () => {
P.clear();
}, []);
const A = j(() => ({
isRecording: r,
start: h,
stop: o,
addDataListener: m,
removeDataListener: y,
addStartListener: H,
removeStartListener: L,
addStopListener: g,
removeStopListener: C,
addErrorListener: E,
removeErrorListener: D
}), [
r,
h,
o,
m,
y,
H,
L,
g,
C,
E,
D
]);
return /* @__PURE__ */ J(ne.Provider, { value: A, children: l });
}
function Me() {
const l = ee(ne);
if (!l) throw new Error("useMicrophone must be used within MicrophoneProvider");
return l;
}
const se = q(
void 0
);
se.displayName = "kortexa.ai:camera";
const pe = ({
defaultFacingMode: l = "user",
defaultDeviceId: n,
requestedWidth: r,
requestedHeight: e,
requestedAspectRatio: t,
onStream: h,
onError: o
} = {}) => {
const [m, y] = F(!1), [H, L] = F(null), [g, C] = F(l), [E, D] = F(
[]
), [A, a] = F(
n
), u = d(async () => {
try {
const f = (await navigator.mediaDevices.enumerateDevices()).filter(
(M) => M.kind === "videoinput"
);
if (D(f), f.length > 0 && !A) {
const M = f.find((T) => (T.label.toLowerCase().includes("front") ? "user" : T.label.toLowerCase().includes("back") ? "environment" : null) === g);
a(
(M == null ? void 0 : M.deviceId) || f[0].deviceId
);
}
} catch (O) {
console.error("Error enumerating camera devices:", O), o == null || o("Error enumerating camera devices.");
}
}, [A, g, o]);
x(() => {
u();
}, []), x(() => {
typeof window < "u" && "ontouchstart" in window && l === "user" && !n && C("environment");
}, [l, n]);
const S = d(
async (O) => {
const f = O || A;
if (!f && E.length === 0 && (await u(), E.length === 0 || !f)) {
console.error("No camera devices available or selected."), o == null || o("No camera devices available or selected.");
return;
}
try {
const M = {
deviceId: f ? { exact: f } : void 0,
facingMode: f ? void 0 : g
// Only use facingMode if no specific deviceId
};
r && (M.width = { ideal: r }), e && (M.height = { ideal: e }), t && (M.aspectRatio = {
ideal: t
});
const T = {
video: Object.keys(M).length > 0 ? M : !0
}, s = await navigator.mediaDevices.getUserMedia(
T
);
L(s), y(!0), h == null || h(s), f && a(f);
} catch (M) {
if (console.error("Error accessing camera:", M), o == null || o(`Error accessing camera: ${M.message}`), f && E.length > 1) {
const T = E.find(
(s) => s.deviceId !== f
);
T && (console.log(
`Attempting to start with device: ${T.label || T.deviceId}`
), await S(T.deviceId));
}
}
},
[
A,
g,
h,
o,
E,
u,
r,
e,
t
]
), k = d(() => {
H && (H.getTracks().forEach((O) => O.stop()), L(null), y(!1), h == null || h());
}, [H, h]), b = d(() => {
m ? k() : S();
}, [m, k, S]), I = d(async () => {
if (!m) return;
const O = g === "user" ? "environment" : "user";
C(O), k();
const f = E.find((M) => {
const T = M.label.toLowerCase();
return O === "user" ? T.includes("front") || !T.includes("back") : O === "environment" ? T.includes("back") : !1;
});
f ? (a(f.deviceId), await S(f.deviceId)) : E.length > 0 ? (a(void 0), await S()) : o == null || o("No alternative camera found for flipping.");
}, [m, g, k, S, E, o]), _ = d(
async (O) => {
O === A && m || (k(), a(O), await S(O));
},
[A, m, k, S]
);
return x(() => () => {
H && H.getTracks().forEach((O) => O.stop());
}, [H]), {
stream: H,
isRecording: m,
facingMode: g,
availableDevices: E,
currentDeviceId: A,
start: S,
stop: k,
toggle: b,
flip: I,
setDevice: _,
getDevices: u
};
}, $ = new ce();
function He({ children: l, ...n }) {
const {
stream: r,
isRecording: e,
facingMode: t,
availableDevices: h,
currentDeviceId: o,
start: m,
stop: y,
flip: H,
setDevice: L,
getDevices: g
} = pe({
...n,
onStream: (s) => {
var v;
(v = n.onStream) == null || v.call(n, s), $.dispatch("streamChanged", s);
},
onError: (s) => {
var v;
(v = n.onError) == null || v.call(n, s), $.dispatch("error", s);
}
}), C = d(async () => {
try {
await m(), $.dispatch("started");
} catch (s) {
let v = "Failed to start camera";
throw s instanceof Error ? v += ": " + s.message : v += ": " + String(s), $.dispatch("error", v), s;
}
}, [m]), E = d(() => {
try {
y(), $.dispatch("stopped");
} catch (s) {
const v = `Failed to stop camera: ${s.message}`;
$.dispatch("error", v);
}
}, [y]), D = d(async () => {
try {
await H();
} catch (s) {
const v = `Failed to flip camera: ${s.message}`;
$.dispatch("error", v);
}
}, [H]), A = d(async (s) => {
try {
await L(s);
} catch (v) {
const V = `Failed to set device: ${v.message}`;
$.dispatch("error", V);
}
}, [L]);
x(() => {
$.dispatch("facingMode", t);
}, [t]);
const a = d((s) => {
const v = `kortexa-camera-stream-${Date.now()}`, V = (N) => {
s(N);
};
return $.addListener("stream", { id: v, listener: V }), v;
}, []), u = d((s) => {
$.removeListener("stream", s);
}, []), S = d((s) => {
const v = `kortexa-camera-started-${Date.now()}`;
return $.addListener("started", { id: v, listener: s }), v;
}, []), k = d((s) => {
$.removeListener("started", s);
}, []), b = d((s) => {
const v = `kortexa-camera-stopped-${Date.now()}`;
return $.addListener("stopped", { id: v, listener: s }), v;
}, []), I = d((s) => {
$.removeListener("stopped", s);
}, []), _ = d((s) => {
const v = `kortexa-camera-facingMode-${Date.now()}`, V = (N) => {
N !== void 0 && s(N);
};
return $.addListener("facingMode", { id: v, listener: V }), v;
}, []), O = d((s) => {
$.removeListener("facingMode", s);
}, []), f = d((s) => {
const v = `kortexa-camera-error-${Date.now()}`, V = (N) => {
N !== void 0 && s(N);
};
return $.addListener("error", { id: v, listener: V }), v;
}, []), M = d((s) => {
$.removeListener("error", s);
}, []);
x(() => () => {
$.clear();
}, []);
const T = j(() => ({
isRecording: e,
stream: r,
facingMode: t,
availableDevices: h,
currentDeviceId: o,
start: C,
stop: E,
toggle: () => e ? E() : C(),
flip: D,
setDevice: A,
getDevices: g,
addStreamListener: a,
removeStreamListener: u,
addStartListener: S,
removeStartListener: k,
addStopListener: b,
removeStopListener: I,
addFacingModeListener: _,
removeFacingModeListener: O,
addErrorListener: f,
removeErrorListener: M
}), [
e,
r,
t,
h,
o,
C,
E,
D,
A,
g,
a,
u,
S,
k,
b,
I,
_,
O,
f,
M
]);
return /* @__PURE__ */ J(se.Provider, { value: T, children: l });
}
const Ee = () => {
const l = ee(se);
if (!l) throw new Error("useCamera must be used within a CameraProvider");
return l;
}, ke = {
staticImageMode: !1,
maxNumHands: 2,
modelComplexity: 1,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5,
selfieMode: !0
// Flip handedness for selfie view
};
function we({
options: l,
handsVersion: n = "0.4.1675469240",
onInitialLoad: r,
onHandsData: e,
onError: t,
onTrackingStarted: h,
onTrackingStopped: o,
onResults: m
} = {}) {
const y = R(null), H = R(null), L = R(null), g = R(!1), C = R(!1), [E, D] = F(!1), [A, a] = F(null), [u, S] = F(null), k = j(
() => ({ ...ke, ...l }),
[l]
), b = R(e);
x(() => {
b.current = e;
}, [e]);
const I = R(t);
x(() => {
I.current = t;
}, [t]);
const _ = R(h);
x(() => {
_.current = h;
}, [h]);
const O = R(o);
x(() => {
O.current = o;
}, [o]);
const f = R(m);
x(() => {
f.current = m;
}, [m]);
const M = R(r);
x(() => {
M.current = r;
}, [r]);
const T = R(/* @__PURE__ */ new Map()), s = R(
/* @__PURE__ */ new Map()
), v = R(/* @__PURE__ */ new Map()), V = R(/* @__PURE__ */ new Map()), N = d(
(c) => {
const p = [];
c.multiHandLandmarks && c.multiHandedness && c.multiHandLandmarks.forEach(
(z, K) => {
var i;
const X = c.multiHandedness[K];
z && X && p.push({
landmarks: z,
worldLandmarks: (i = c.multiHandWorldLandmarks) == null ? void 0 : i[K],
handedness: [X]
});
}
);
const U = {
detectedHands: p,
imageDimensions: c.image ? {
width: c.image.width,
height: c.image.height
} : void 0
};
if (a(U), b.current && b.current(U), T.current.forEach(
(z, K) => z(U)
), f.current)
try {
f.current(p, c.image);
} catch (z) {
console.error(
"[useHandsControl] Error in onResults callback:",
z
);
const K = z instanceof Error ? z.message : String(z);
S(K), I.current && I.current(K), s.current.forEach(
(X, i) => X(K)
);
}
!C.current && p.length > 0 && (C.current = !0, console.log(
"[useHandsControl] processResults: Initial hand detection successful."
), M.current && M.current());
},
[
a,
S,
b,
f,
M,
I,
T,
C
]
);
x(() => {
console.log(
"[useHandsControl] useEffect[options, handsVersion]: Initializing MediaPipe Hands..."
);
const c = new ue({
locateFile: (p) => (console.log(
`[useHandsControl] locateFile: Attempting to locate ${p} using version ${n}`
), `https://cdn.jsdelivr.net/npm/@mediapipe/hands@${n}/${p}`)
});
return c.setOptions(k), c.onResults(N), y.current = c, console.log(
"[useHandsControl] MediaPipe Hands instance initialized with options:",
k
), () => {
y.current && (y.current.close().catch(
(p) => console.error(
"[useHandsControl] Error closing MediaPipe Hands:",
p
)
), y.current = null, console.log(
"[useHandsControl] MediaPipe Hands instance closed."
));
};
}, [k, n, N]);
const Q = d(async () => {
if (!H.current || !y.current) {
console.warn(
"[useHandsControl] sendFrame: Video element or Hands instance not available. Cannot send frame."
);
return;
}
if (H.current.paused || H.current.ended || H.current.readyState < HTMLMediaElement.HAVE_METADATA) {
g.current && (L.current = requestAnimationFrame(Q));
return;
}
try {
await y.current.send({ image: H.current });
} catch (c) {
console.error(
"[useHandsControl] sendFrame: Error sending frame to MediaPipe Hands:",
c
);
const p = c instanceof Error ? c.message : String(c);
S(p), I.current && I.current(p), s.current.forEach(
(U, z) => U(p)
);
}
g.current ? L.current = requestAnimationFrame(Q) : console.log(
"[useHandsControl] sendFrame: Loop terminated because isTrackingRef.current is false."
);
}, [
y,
H,
S,
I,
s
]), Z = d(
async (c) => {
if (!y.current) {
console.error(
"[useHandsControl] startTracking: Hands instance not initialized."
), S("Hands instance not initialized."), I.current && I.current("Hands instance not initialized."), s.current.forEach(
(p, U) => p("Hands instance not initialized.")
);
return;
}
if (g.current) {
console.warn(
"[useHandsControl] startTracking: Already tracking."
);
return;
}
console.log(
"[useHandsControl] startTracking: Video element set:",
c
), H.current = c, C.current = !1, g.current = !0, console.log(
"[useHandsControl] startTracking: Before setIsTracking(true). isTrackingRef.current:",
g.current
), D(!0), L.current && cancelAnimationFrame(L.current), L.current = requestAnimationFrame(Q), console.log(
"[useHandsControl] startTracking: Loop started. animationFrameIdRef:",
L.current
), _.current && _.current(), v.current.forEach(
(p, U) => p()
), S(null);
},
[
Q,
_,
v,
I,
s,
S
]
), B = d(() => {
console.log(
"[useHandsControl] stopTracking: Called. Current isTracking (state):",
E,
"isTrackingRef.current:",
g.current
), !(!g.current && !E) && (g.current = !1, D(!1), console.log(
"[useHandsControl] stopTracking: After updates. isTracking (state should be false soon):",
E,
"isTrackingRef.current:",
g.current,
"animationFrameIdRef:",
L.current
), L.current && (cancelAnimationFrame(L.current), console.log(
"[useHandsControl] stopTracking: Cleared animation frame",
L.current
), L.current = null), O.current && O.current(), V.current.forEach(
(c, p) => c()
), console.log("[useHandsControl] stopTracking: Completed."));
}, [E, O, V]);
return {
isTracking: E,
handsData: A,
error: u,
startTracking: Z,
stopTracking: B,
getHandsInstance: () => y.current,
addHandsDataListener: (c) => {
const p = Date.now().toString() + Math.random().toString();
return T.current.set(p, c), p;
},
removeHandsDataListener: (c) => {
T.current.delete(c);
},
addErrorListener: (c) => {
const p = Date.now().toString() + Math.random().toString();
return s.current.set(p, c), p;
},
removeErrorListener: (c) => {
s.current.delete(c);
},
addStartListener: (c) => {
const p = Date.now().toString() + Math.random().toString();
return v.current.set(p, c), p;
},
removeStartListener: (c) => {
v.current.delete(c);
},
addStopListener: (c) => {
const p = Date.now().toString() + Math.random().toString();
return V.current.set(p, c), p;
},
removeStopListener: (c) => {
V.current.delete(c);
}
};
}
const ae = q(
{}
);
ae.displayName = "kortexa.ai:hands-tracking";
function ye({ children: l, ...n }) {
const r = we(n);
return /* @__PURE__ */ J(ae.Provider, { value: r, children: l });
}
const Le = () => {
const l = ee(ae);
if (l === null) throw new Error("useHands must be used within a HandsProvider");
return l;
}, Ce = "proceed";
function Oe({
children: l,
startBehavior: n
}) {
const r = Me(), e = Ee(), t = Le(), h = R(null), [o, m] = F(!1), [y, H] = F(!1), [L, g] = F(void 0), [C, E] = F(!1), [D, A] = F(void 0), [a, u] = F(void 0), [S, k] = F(void 0), [b, I] = F(!1), [_, O] = F(!1);
x(() => {
console.log("[MediaOrchestrator] Attaching mic error listener.");
const i = r.addErrorListener((w) => {
console.log("[MediaOrchestrator] Mic error received:", w), A(w);
});
return () => {
console.log("[MediaOrchestrator] Removing mic error listener."), r.removeErrorListener(i);
};
}, [r]), x(() => {
console.log("[MediaOrchestrator] Attaching cam error listener.");
const i = e.addErrorListener((w) => {
console.log("[MediaOrchestrator] Cam error received:", w), u(w);
});
return () => {
console.log("[MediaOrchestrator] Removing cam error listener."), e.removeErrorListener(i);
};
}, [e]), x(() => {
if (t) {
console.log("[MediaOrchestrator] Attaching hands error listener.");
const i = t.addErrorListener((w) => {
console.log("[MediaOrchestrator] Hands error received:", w), k(w), I(!1);
});
return () => {
console.log("[MediaOrchestrator] Removing hands error listener."), t.removeErrorListener(i);
};
}
return () => {
};
}, [t]);
const f = j(() => r.isRecording, [r]), M = j(() => e.isRecording, [e]), T = j(() => (t == null ? void 0 : t.isTracking) ?? !1, [t]), s = j(() => f && M, [f, M]), v = j(() => {
}, []), V = j(() => e.stream ?? void 0, [e]), N = j(() => e.facingMode, [e]), Q = j(() => (t == null ? void 0 : t.handsData) ?? void 0, [t]);
console.log("[MediaProvider] InternalOrchestrator render. States: isAudioActive:", f, "isVideoActive:", M, "isHandTrackingActive:", T, "isStarting:", C, "isStartingHandsInternal:", b, "attemptedHandsStartInCycle:", o, "isVideoElementForHandsSet:", _);
const Z = d((i) => {
console.log("[MediaOrchestrator] setVideoElementForHands called with element:", i ? "VideoElement" : "null"), h.current = i, i ? O(!0) : (console.log("[MediaOrchestrator] Video element removed, resetting attemptedHandsStartInCycle."), m(!1), O(!1));
}, []), B = d(async () => {
if (console.log(`[MediaOrchestrator] startHandsAsyncInternal: Entered. Pre-conditions: hands=${!!t}, videoElement=${!!h.current}, cam.isRecording=${e == null ? void 0 : e.isRecording}, stream=${!!(e != null && e.stream)}, !hands.isTracking=${!(t != null && t.isTracking)}`), !t || !h.current || !(e != null && e.isRecording) || !(e != null && e.stream)) {
const w = "Pre-conditions not met for starting hand tracking (hands, videoElement, camera state).";
return console.warn("[MediaOrchestrator] startHandsAsyncInternal: Pre-conditions not met.", { hands: !!t, videoElement: !!h.current, camIsRecording: e == null ? void 0 : e.isRecording, stream: !!(e != null && e.stream) }), k(w), m(!0), Promise.reject(new Error(w));
}
if (t.isTracking)
return console.log("[MediaOrchestrator] startHandsAsyncInternal: Already tracking. Exiting."), Promise.resolve();
const i = h.current;
console.log("[MediaOrchestrator] startHandsAsyncInternal: Setting attemptedHandsStartInCycle to true."), m(!0), k(void 0);
try {
console.log(`[MediaOrchestrator] startHandsAsyncInternal: Video element readyState: ${i.readyState}. Waiting for HAVE_METADATA if necessary.`), i.readyState < HTMLMediaElement.HAVE_METADATA && await new Promise((w, W) => {
const G = () => {
console.log("[MediaOrchestrator] startHandsAsyncInternal: videoEl loadedmetadata event triggered."), i.removeEventListener("loadedmetadata", G), i.removeEventListener("error", Y), w();
}, Y = (te) => {
console.error("[MediaOrchestrator] startHandsAsyncInternal: videoEl error event triggered.", te), i.removeEventListener("loadedmetadata", G), i.removeEventListener("error", Y), W(new Error("Video element error during metadata load for hand tracking."));
};
i.addEventListener("loadedmetadata", G), i.addEventListener("error", Y);
}), console.log("[MediaOrchestrator] startHandsAsyncInternal: Video ready. Calling hands.startTracking()."), await t.startTracking(i), console.log("[MediaOrchestrator] startHandsAsyncInternal: hands.startTracking() call completed.");
} catch (w) {
const W = w instanceof Error ? w.message : String(w);
throw console.error("[MediaOrchestrator] startHandsAsyncInternal: Error during hand tracking start:", W), k(W), t != null && t.isTracking && console.warn("[MediaOrchestrator] startHandsAsyncInternal: hands.isTracking is true after a startTracking error. This might be unexpected."), w;
}
}, [t, e, k, m]), c = d(async () => {
if (console.log(`[MediaOrchestrator] startMedia: Entered. isStarting: ${C}, isAudioActive: ${f}, isVideoActive: ${M}, hands.isTracking: ${t == null ? void 0 : t.isTracking}, videoElementSet: ${!!h.current}`), C) {
console.log("[MediaOrchestrator] startMedia: Already starting. Exiting.");
return;
}
E(!0), console.log("[MediaOrchestrator] startMedia: Set isStarting to true."), g(void 0), m(!1), H(!1), console.log("[MediaOrchestrator] startMedia: Reset attemptedHandsStartInCycle and userExplicitlyStoppedHands to false."), A(void 0), u(void 0), k(void 0);
let i = f, w = M;
if (r && !f) {
console.log("[MediaOrchestrator] startMedia: Attempting to start Mic...");
try {
await r.start(), i = !0, console.log("[MediaOrchestrator] startMedia: Mic start successful.");
} catch {
i = !1, console.error("[MediaOrchestrator] startMedia: Mic start failed.");
}
}
if (e && !M) {
console.log("[MediaOrchestrator] startMedia: Attempting to start Camera...");
try {
await e.start(), w = !0, console.log("[MediaOrchestrator] startMedia: Camera start successful.");
} catch {
w = !1, console.error("[MediaOrchestrator] startMedia: Camera start failed.");
}
}
if (n === "halt") {
if (!i || !w) {
const G = D || (i ? "" : "Failed to start"), Y = a || (w ? "" : "Failed to start"), te = `Media start halted: Mic ${i ? "OK" : `FAIL (${G})`}. Cam ${w ? "OK" : `FAIL (${Y})`}.`;
console.warn("[MediaOrchestrator] startMedia: Halting due to mic/cam failure.", { localMicAttemptOk: i, localCamAttemptOk: w, micErrMsg: G, camErrMsg: Y }), g(te), i && r && r.stop(), w && e && e.stop(), E(!1), console.log("[MediaOrchestrator] startMedia: Set isStarting to false (halted).");
return;
}
} else {
const W = [];
if (i || W.push(`Mic: ${D || "Unknown"}`), w || W.push(`Cam: ${a || "Unknown"}`), W.length > 0) {
const G = `Failed to start: ${W.join("; ")}. Others proceeded if successful.`;
console.warn("[MediaOrchestrator] startMedia: Proceeding with errors.", { errorParts: W }), g(G);
}
}
if (w && (e != null && e.isRecording) && e.stream && t && !t.isTracking && h.current && !o) {
console.log("[MediaOrchestrator] startMedia: Conditions met to attempt starting hands via startHandsAsyncInternal.");
try {
await B(), console.log("[MediaOrchestrator] startMedia: startHandsAsyncInternal completed successfully.");
} catch {
console.error("[MediaOrchestrator] startMedia: startHandsAsyncInternal failed. Error should be in currentHandsError."), n === "halt" && (g(`Failed to start hand tracking. Halting media start. Error: ${S || "Unknown hands error"}`), console.warn("[MediaOrchestrator] startMedia: Hand tracking failed with startBehavior=halt. Media (mic/cam) might still be running."));
}
}
E(!1), console.log("[MediaOrchestrator] startMedia: Set isStarting to false (completed). Exiting startMedia.");
}, [
r,
e,
t,
n,
f,
M,
C,
h,
o,
B,
D,
a,
S,
A,
u,
k,
g,
E,
m,
H
// Added setters for completeness though some might be stable
]);
x(() => {
e != null && e.isRecording && e.stream && t && !t.isTracking && h.current && !o && !y && (console.log("[MediaOrchestrator] useEffect[...]: Conditions met for auto-starting hands. Calling startHandsAsyncInternal."), B().catch(() => {
console.error("[MediaOrchestrator] useEffect[...]: Error during automatic startHandsAsyncInternal. Error should be in currentHandsError.");
}));
}, [e == null ? void 0 : e.isRecording, e == null ? void 0 : e.stream, t, h, o, y, B]), x(() => {
e != null && e.isRecording || (console.log("[MediaOrchestrator] useEffect[cam.isOn]: Camera is off, resetting attemptedHandsStartInCycle to false."), m(!1));
}, [e == null ? void 0 : e.isRecording, m]);
const p = d(async () => {
if (console.log(`[MediaOrchestrator] stopHands: Entered. hands.isTracking: ${t == null ? void 0 : t.isTracking}`), t && t.isTracking)
try {
await t.stopTracking(), console.log("[MediaOrchestrator] stopHands: hands.stopTracking() call completed."), H(!0);
} catch (i) {
const w = i instanceof Error ? i.message : String(i);
console.error("[MediaOrchestrator] stopHands: Error during hand tracking stop:", w), k(w);
}
console.log("[MediaOrchestrator] stopHands: Resetting attemptedHandsStartInCycle to false."), m(!1), I(!1);
}, [t, k, m]);
x(() => {
e && !e.isRecording && (t != null && t.isTracking && (console.log("[MediaOrchestrator] useEffect[...]: Camera turned off. Calling stopHands."), p()), console.log("[MediaOrchestrator] useEffect[...]: Camera off, resetting attemptedHandsStartInCycle."), m(!1));
}, [e, e == null ? void 0 : e.isRecording, t, p]);
const U = d(() => {
console.log(`[MediaOrchestrator] stopMedia: Entered. mic.isRecording: ${r == null ? void 0 : r.isRecording}, cam.isRecording: ${e == null ? void 0 : e.isRecording}`), r != null && r.isRecording && (console.log("[MediaOrchestrator] stopMedia: Stopping Mic."), r.stop()), e != null && e.isRecording && (console.log("[MediaOrchestrator] stopMedia: Stopping Camera. This should trigger useEffect to stop hands."), e.stop()), g(void 0), A(void 0), u(void 0), k(void 0), E(!1), console.log("[MediaOrchestrator] stopMedia: Exiting.");
}, [r, e, g, A, u, k, E]), z = d(async () => {
console.log(`[MediaOrchestrator] toggleMedia: Entered. isAudioActive: ${f}, isVideoActive: ${M}`), f && M ? (console.log("[MediaOrchestrator] toggleMedia: Media is active, calling stopMedia."), U()) : (console.log("[MediaOrchestrator] toggleMedia: Media is inactive, calling startMedia."), await c()), console.log("[MediaOrchestrator] toggleMedia: Exiting.");
}, [f, M, c, U]), K = d(async () => {
if (console.log(`[MediaOrchestrator] startHands: Entered. isStartingHandsInternal: ${b}, hands.isTracking: ${t == null ? void 0 : t.isTracking}`), b || t != null && t.isTracking) {
console.log("[MediaOrchestrator] startHands: Already starting or tracking. Exiting.");
return;
}
if (H(!1), console.log("[MediaOrchestrator] startHands: Setting isStartingHandsInternal to true."), I(!0), k(void 0), !t) {
const i = "Hand tracking service not available.";
console.warn("[MediaOrchestrator] startHands:", i), k(i), I(!1);
return;
}
if (!(e != null && e.isRecording)) {
const i = "Camera is not active. Cannot start hand tracking.";
console.warn("[MediaOrchestrator] startHands:", i), k(i), I(!1);
return;
}
if (!h.current) {
const i = "Video element not set for hand tracking.";
console.warn("[MediaOrchestrator] startHands:", i), k(i), I(!1), O(!1);
return;
}
try {
console.log("[MediaOrchestrator] startHands: Calling startHandsAsyncInternal."), await B(), console.log("[MediaOrchestrator] startHands: startHandsAsyncInternal completed successfully.");
} catch {
console.error("[MediaOrchestrator] startHands: startHandsAsyncInternal failed. Error should be in currentHandsError.");
} finally {
console.log("[MediaOrchestrator] startHands: Setting isStartingHandsInternal to false (completed attempt)."), I(!1);
}
console.log("[MediaOrchestrator] startHands: Exiting.");
}, [t, e == null ? void 0 : e.isRecording, h, b, B, I, k]), X = j(() => ({
isAudioActive: f,
isVideoActive: M,
isHandTrackingActive: T,
isMediaActive: s,
audioStream: v,
videoStream: V,
videoFacingMode: N,
currentHandsData: Q,
audioError: D,
videoError: a,
handsError: S,
mediaError: L,
startMedia: c,
stopMedia: U,
toggleMedia: z,
cam: e,
mic: r,
hands: t,
setVideoElementForHands: Z,
startHands: K,
stopHands: p,
isStartingMedia: C,
isStartingHands: b,
isVideoElementForHandsSet: _
}), [
f,
M,
T,
s,
v,
V,
N,
Q,
D,
a,
S,
L,
c,
U,
z,
e,
r,
t,
Z,
K,
p,
C,
b,
_
]);
return /* @__PURE__ */ J(re.Provider, { value: X, children: l });
}
function De({
children: l,
microphoneProps: n,
cameraProps: r,
handsProps: e,
startBehavior: t = Ce
}) {
return /* @__PURE__ */ J(ve, { ...n, children: /* @__PURE__ */ J(He, { ...r, children: /* @__PURE__ */ J(ye, { ...e, children: /* @__PURE__ */ J(
Oe,
{
startBehavior: t,
children: l
}
) }) }) });
}
export {
He as CameraProvider,
De as CompositeMediaProvider,
ye as HandsProvider,
ve as MicrophoneProvider,
Ee as useCamera,
pe as useCameraDevice,
Re as useCompositeMedia,
Le as useHands,
we as useHandsControl,
Me as useMicrophone,
me as useMicrophoneDevice
};
//# sourceMappingURL=react-multimodal.js.map