Matthias Nott
2026-03-25 d6cf9469aa0462d1b8313cc85907176eee1214a2
lib/services/audio_service.dart
....@@ -28,9 +28,16 @@
2828 // Autoplay suppression
2929 static bool _isBackgrounded = false;
3030
31
+ // Track last played temp file so it can be cleaned up when the track ends
32
+ static String? _lastPlaybackTempPath;
33
+
34
+ // Lifecycle observer stored so we can remove it in dispose()
35
+ static _LifecycleObserver? _lifecycleObserver;
36
+
3137 /// Initialize the audio service and set up lifecycle observer.
3238 static void init() {
33
- WidgetsBinding.instance.addObserver(_LifecycleObserver());
39
+ _lifecycleObserver = _LifecycleObserver();
40
+ WidgetsBinding.instance.addObserver(_lifecycleObserver!);
3441
3542 // Configure audio session for background playback
3643 _player.setAudioContext(AudioContext(
....@@ -52,6 +59,13 @@
5259 }
5360
5461 static void _onTrackComplete() {
62
+ // Clean up the temp file that just finished playing
63
+ final prev = _lastPlaybackTempPath;
64
+ _lastPlaybackTempPath = null;
65
+ if (prev != null) {
66
+ File(prev).delete().ignore();
67
+ }
68
+
5569 if (_queue.isNotEmpty) {
5670 _playNextInQueue();
5771 } else {
....@@ -68,6 +82,7 @@
6882 }
6983
7084 final path = _queue.removeAt(0);
85
+ _lastPlaybackTempPath = path;
7186 try {
7287 // Brief pause between tracks — iOS audio player needs time to reset
7388 await _player.stop();
....@@ -143,10 +158,12 @@
143158
144159 if (source.startsWith('/')) {
145160 await _player.play(DeviceFileSource(source));
161
+ // File path owned by caller — not tracked for deletion
146162 } else {
147163 // base64 data — write to temp file first
148164 final path = await _base64ToFile(source);
149165 if (path == null) return;
166
+ _lastPlaybackTempPath = path;
150167 await _player.play(DeviceFileSource(path));
151168 }
152169 _isPlaying = true;
....@@ -159,6 +176,7 @@
159176 final path = await _base64ToFile(base64Audio);
160177 if (path == null) return;
161178
179
+ _lastPlaybackTempPath = path;
162180 await _player.play(DeviceFileSource(path));
163181 _isPlaying = true;
164182 onPlaybackStateChanged?.call();
....@@ -177,6 +195,7 @@
177195 debugPrint('AudioService: queued (queue size: ${_queue.length})');
178196 } else {
179197 // Nothing playing — start immediately
198
+ _lastPlaybackTempPath = path;
180199 try {
181200 await _player.play(DeviceFileSource(path));
182201 _isPlaying = true;
....@@ -250,6 +269,10 @@
250269 }
251270
252271 static Future<void> dispose() async {
272
+ if (_lifecycleObserver != null) {
273
+ WidgetsBinding.instance.removeObserver(_lifecycleObserver!);
274
+ _lifecycleObserver = null;
275
+ }
253276 await cancelRecording();
254277 await stopPlayback();
255278 _recorder.dispose();