mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-11 02:46:29 +00:00
* fix(android): preserve dictation across audio interruptions Playback focus loss does not mean microphone capture is unavailable. Keep dictation alive across media takeovers and wait for Android input silencing to clear, while preserving conversation interruption protection. * test(android): verify dictation clears its selected audio route
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
import { useCallback, useEffect, useRef } from "react";
|
|
import { Buffer } from "buffer";
|
|
import { useState } from "react";
|
|
|
|
import { createAudioEngine } from "@/audio";
|
|
|
|
import type {
|
|
DictationAudioSource,
|
|
DictationAudioSourceConfig,
|
|
} from "./use-dictation-audio-source.types";
|
|
|
|
export function useDictationAudioSource(config: DictationAudioSourceConfig): DictationAudioSource {
|
|
const onPcmSegmentRef = useRef(config.onPcmSegment);
|
|
const onErrorRef = useRef(config.onError);
|
|
const onInterruptionRef = useRef(config.onInterruption);
|
|
const [volume, setVolume] = useState(0);
|
|
const [isPaused, setIsPaused] = useState(false);
|
|
const engineRef = useRef<ReturnType<typeof createAudioEngine> | null>(null);
|
|
|
|
const getOrCreateEngine = useCallback(() => {
|
|
if (engineRef.current) {
|
|
return engineRef.current;
|
|
}
|
|
|
|
engineRef.current = createAudioEngine(
|
|
{
|
|
onCaptureData: (pcm) => {
|
|
onPcmSegmentRef.current(Buffer.from(pcm).toString("base64"));
|
|
},
|
|
onVolumeLevel: (level) => {
|
|
setVolume(level);
|
|
},
|
|
onError: (error) => {
|
|
onErrorRef.current?.(error);
|
|
},
|
|
onInterruption: () => {
|
|
onInterruptionRef.current?.();
|
|
},
|
|
onCaptureAvailabilityChange: (available) => setIsPaused(!available),
|
|
},
|
|
{ captureMode: "dictation" },
|
|
);
|
|
return engineRef.current;
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
onPcmSegmentRef.current = config.onPcmSegment;
|
|
onErrorRef.current = config.onError;
|
|
onInterruptionRef.current = config.onInterruption;
|
|
}, [config.onPcmSegment, config.onError, config.onInterruption]);
|
|
|
|
const start = useCallback(async () => {
|
|
const engine = getOrCreateEngine();
|
|
await engine.initialize();
|
|
await engine.startCapture();
|
|
}, [getOrCreateEngine]);
|
|
|
|
const stop = useCallback(async () => {
|
|
await engineRef.current?.stopCapture();
|
|
setVolume(0);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
const engine = engineRef.current;
|
|
engineRef.current = null;
|
|
void engine?.destroy().catch(() => undefined);
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
start,
|
|
stop,
|
|
volume,
|
|
isPaused,
|
|
};
|
|
}
|