diff --git a/packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts b/packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts index 15d1d72a26..72bcaae7e1 100644 --- a/packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts +++ b/packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts @@ -9,14 +9,20 @@ import { useModuleFactory } from '../useModuleFactory'; * @returns Ready to use VAD model. */ export const useVAD = ({ model, preventLoad = false }: VADProps): VADType => { - const { error, isReady, isGenerating, downloadProgress, runForward } = - useModuleFactory({ - factory: (config, onProgress) => - VADModule.fromModelName(config, onProgress), - config: model, - deps: [model.modelName, model.modelSource], - preventLoad, - }); + const { + error, + isReady, + isGenerating, + downloadProgress, + runForward, + runSideChannel, + } = useModuleFactory({ + factory: (config, onProgress) => + VADModule.fromModelName(config, onProgress), + config: model, + deps: [model.modelName, model.modelSource], + preventLoad, + }); const forward = (waveform: Float32Array) => runForward((inst) => inst.forward(waveform)); @@ -25,16 +31,9 @@ export const useVAD = ({ model, preventLoad = false }: VADProps): VADType => { runForward((inst) => inst.stream(input)); const streamInsert = (waveform: Float32Array) => - runForward((inst) => { - inst.streamInsert(waveform); - return Promise.resolve(); - }); + runSideChannel((inst) => inst.streamInsert(waveform)); - const streamStop = () => - runForward((inst) => { - inst.streamStop(); - return Promise.resolve(); - }); + const streamStop = () => runSideChannel((inst) => inst.streamStop()); return { error, diff --git a/packages/react-native-executorch/src/hooks/useModuleFactory.ts b/packages/react-native-executorch/src/hooks/useModuleFactory.ts index f816d61e2d..82f8c7a49e 100644 --- a/packages/react-native-executorch/src/hooks/useModuleFactory.ts +++ b/packages/react-native-executorch/src/hooks/useModuleFactory.ts @@ -14,7 +14,7 @@ type RunOnFrame = M extends { runOnFrame: infer R } ? R : never; * not-loaded / already-generating guards so individual hooks only need to * define their typed `forward` wrapper. * @param props - Options object containing the factory function, config, deps array, and optional preventLoad flag. - * @returns An object with error, isReady, isGenerating, downloadProgress, runForward, instance, and runOnFrame. + * @returns An object with error, isReady, isGenerating, downloadProgress, runForward, runSideChannel, instance, and runOnFrame. * @internal */ export function useModuleFactory({ @@ -89,6 +89,16 @@ export function useModuleFactory({ } }; + // Non-gating call path for streaming modules: only checks `isReady`, never + // `isGenerating`. Lets side-channel methods (e.g. `streamInsert` buffer push, + // `streamStop` interrupt signal) run while `stream`/`forward` is in flight. + const runSideChannel = (fn: (instance: M) => R): R => { + if (!isReady || !instance) { + throw new RnExecutorchError(RnExecutorchErrorCode.ModuleNotLoaded); + } + return fn(instance); + }; + const runOnFrame = useMemo( () => instance && 'runOnFrame' in instance @@ -103,6 +113,7 @@ export function useModuleFactory({ isGenerating, downloadProgress, runForward, + runSideChannel, instance, runOnFrame, };