import { useEffect, useRef } from "react"; import { Pressable, StyleSheet, Text, View } from "react-native"; import { Mic } from "lucide-react-native"; import { RecordingPresets, setAudioModeAsync, useAudioRecorder, useAudioRecorderState, } from "expo-audio"; import { logError } from "@/lib/errors"; const MAX_DURATION_S = 60; interface AudioRecordingOverlayProps { onComplete: (result: { uri: string; durationMs: number }) => void; onCancel: () => void; } export function AudioRecordingOverlay({ onComplete, onCancel, }: AudioRecordingOverlayProps) { const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); const state = useAudioRecorderState(recorder, 250); const finalizedRef = useRef(false); useEffect(() => { let active = true; (async () => { try { await setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true, }); await recorder.prepareToRecordAsync(); if (!active) return; recorder.record(); } catch (err) { logError(err, { scope: "compose.audio.start" }); if (active) onCancel(); } })(); return () => { active = false; if (!finalizedRef.current) { finalizedRef.current = true; recorder.stop().catch(() => {}); } void setAudioModeAsync({ allowsRecording: false, playsInSilentMode: true, }).catch((err) => logError(err, { scope: "compose.audio.exit" })); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const elapsedMs = state.durationMillis ?? 0; useEffect(() => { if (elapsedMs >= MAX_DURATION_S * 1000 && !finalizedRef.current) { void finish("commit"); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [elapsedMs]); const finish = async (kind: "commit" | "cancel") => { if (finalizedRef.current) return; finalizedRef.current = true; const durationMs = state.durationMillis ?? 0; try { await recorder.stop(); } catch (err) { logError(err, { scope: "compose.audio.stop" }); } if (kind === "cancel") { onCancel(); return; } const uri = recorder.uri; if (!uri) { onCancel(); return; } onComplete({ uri, durationMs }); }; const elapsedSec = Math.floor(elapsedMs / 1000); return ( {state.isRecording ? "Recording" : "Starting…"} {elapsedSec.toString().padStart(2, "0")}s · max {MAX_DURATION_S}s void finish("cancel")} accessibilityLabel="Cancel recording" className="rounded-full bg-white/15 px-6 py-3" > Cancel void finish("commit")} accessibilityLabel="Stop recording" className="rounded-full bg-white px-7 py-3" > Stop ); }