From a0f39302919fbacf7a0d407f01b1a50413ea6f70 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Mon, 02 Mar 2026 23:15:13 +0100
Subject: [PATCH] feat: on-device speech recognition, navigation screen, session picker
---
services/audio.ts | 105 ++++++++++++++--------------------------------------
1 files changed, 29 insertions(+), 76 deletions(-)
diff --git a/services/audio.ts b/services/audio.ts
index 31c7dd8..769d299 100644
--- a/services/audio.ts
+++ b/services/audio.ts
@@ -1,112 +1,65 @@
-import { Audio, AVPlaybackStatus } from "expo-av";
+import {
+ createAudioPlayer,
+ requestRecordingPermissionsAsync,
+ setAudioModeAsync,
+} from "expo-audio";
export interface RecordingResult {
uri: string;
durationMs: number;
}
-let currentRecording: Audio.Recording | null = null;
-let currentSound: Audio.Sound | null = null;
+let currentPlayer: ReturnType<typeof createAudioPlayer> | null = null;
-async function requestPermissions(): Promise<boolean> {
- const { status } = await Audio.requestPermissionsAsync();
+export async function requestPermissions(): Promise<boolean> {
+ const { status } = await requestRecordingPermissionsAsync();
return status === "granted";
-}
-
-export async function startRecording(): Promise<Audio.Recording | null> {
- const granted = await requestPermissions();
- if (!granted) return null;
-
- try {
- await Audio.setAudioModeAsync({
- allowsRecordingIOS: true,
- playsInSilentModeIOS: true,
- });
-
- const { recording } = await Audio.Recording.createAsync(
- Audio.RecordingOptionsPresets.HIGH_QUALITY
- );
-
- currentRecording = recording;
- return recording;
- } catch (error) {
- console.error("Failed to start recording:", error);
- return null;
- }
-}
-
-export async function stopRecording(): Promise<RecordingResult | null> {
- if (!currentRecording) return null;
-
- try {
- await currentRecording.stopAndUnloadAsync();
- const status = await currentRecording.getStatusAsync();
- const uri = currentRecording.getURI();
- currentRecording = null;
-
- await Audio.setAudioModeAsync({
- allowsRecordingIOS: false,
- });
-
- if (!uri) return null;
-
- const durationMs = (status as { durationMillis?: number }).durationMillis ?? 0;
- return { uri, durationMs };
- } catch (error) {
- console.error("Failed to stop recording:", error);
- currentRecording = null;
- return null;
- }
}
export async function playAudio(
uri: string,
onFinish?: () => void
-): Promise<Audio.Sound | null> {
+): Promise<void> {
try {
await stopPlayback();
- await Audio.setAudioModeAsync({
- allowsRecordingIOS: false,
- playsInSilentModeIOS: true,
+ await setAudioModeAsync({
+ playsInSilentMode: true,
});
- const { sound } = await Audio.Sound.createAsync(
- { uri },
- { shouldPlay: true }
- );
+ const player = createAudioPlayer(uri);
+ currentPlayer = player;
- currentSound = sound;
-
- sound.setOnPlaybackStatusUpdate((status: AVPlaybackStatus) => {
- if (status.isLoaded && status.didJustFinish) {
+ player.addListener("playbackStatusUpdate", (status) => {
+ if (!status.playing && status.currentTime >= status.duration && status.duration > 0) {
onFinish?.();
- sound.unloadAsync().catch(() => {});
- currentSound = null;
+ player.remove();
+ if (currentPlayer === player) currentPlayer = null;
}
});
- return sound;
+ player.play();
} catch (error) {
console.error("Failed to play audio:", error);
- return null;
}
}
export async function stopPlayback(): Promise<void> {
- if (currentSound) {
+ if (currentPlayer) {
try {
- await currentSound.stopAsync();
- await currentSound.unloadAsync();
+ currentPlayer.pause();
+ currentPlayer.remove();
} catch {
- // Ignore errors during cleanup
+ // Ignore cleanup errors
}
- currentSound = null;
+ currentPlayer = null;
}
}
-export function encodeAudioToBase64(uri: string): Promise<string> {
- // In React Native, we'd use FileSystem from expo-file-system
- // For now, return the URI as-is since we may not have expo-file-system
- return Promise.resolve(uri);
+export async function encodeAudioToBase64(uri: string): Promise<string> {
+ const FileSystem = await import("expo-file-system");
+ const result = await FileSystem.readAsStringAsync(uri, {
+ encoding: FileSystem.EncodingType.Base64,
+ });
+ return result;
}
--
Gitblit v1.3.1