Matthias Nott
2026-03-02 aca79f31767ae6f03f47a284f3d0e80850c5fb02
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { Audio, AVPlaybackStatus } from "expo-av";
export interface RecordingResult {
  uri: string;
  durationMs: number;
}
let currentRecording: Audio.Recording | null = null;
let currentSound: Audio.Sound | null = null;
async function requestPermissions(): Promise<boolean> {
  const { status } = await Audio.requestPermissionsAsync();
  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> {
  try {
    await stopPlayback();
    await Audio.setAudioModeAsync({
      allowsRecordingIOS: false,
      playsInSilentModeIOS: true,
    });
    const { sound } = await Audio.Sound.createAsync(
      { uri },
      { shouldPlay: true }
    );
    currentSound = sound;
    sound.setOnPlaybackStatusUpdate((status: AVPlaybackStatus) => {
      if (status.isLoaded && status.didJustFinish) {
        onFinish?.();
        sound.unloadAsync().catch(() => {});
        currentSound = null;
      }
    });
    return sound;
  } catch (error) {
    console.error("Failed to play audio:", error);
    return null;
  }
}
export async function stopPlayback(): Promise<void> {
  if (currentSound) {
    try {
      await currentSound.stopAsync();
      await currentSound.unloadAsync();
    } catch {
      // Ignore errors during cleanup
    }
    currentSound = 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);
}