feat(flutter): register stream-cached files in the audio cache index

Closes the gap where LockCachingAudioSource wrote files to disk but
never told AudioCacheManager about them — meaning evict() couldn't
reclaim stream-cached files when usage exceeded the cap, only
explicitly-pinned downloads.

Wire just_audio's bufferedPositionStream as the "download complete"
signal: when bufferedPosition reaches duration (with 200ms slack for
header bytes), look up the on-disk file at the LockCaching path,
read its size, and insert an audio_cache_index row via the new
AudioCacheManager.registerStreamCache(). Source defaults to
incidental so stream-cached tracks are first to be evicted under
pressure.

Dedupe via _streamCacheRegistered Set so we don't hit drift on every
~200ms buffered-position emit. Cache the application cache dir path
on first use for the same reason.

Eviction now sees the full set of files on disk; usageBytes() (which
already walks the dir) and evict() (which reads the index) are
finally consistent for stream-cached tracks. Pinned tracks keep
their existing manual-download flow unchanged.
This commit is contained in:
2026-05-11 17:46:32 -04:00
parent c08f4ace80
commit 9cac664679
2 changed files with 70 additions and 0 deletions
+21
View File
@@ -83,6 +83,27 @@ class AudioCacheManager {
return path;
}
/// Registers a file already on disk in the cache index. Intended for
/// the streaming path (LockCachingAudioSource) which writes the file
/// itself; we need an index row so eviction can find and delete it
/// when usage exceeds the cap. `source` defaults to incidental so
/// stream-cached tracks are first to be evicted.
Future<void> registerStreamCache(
String trackId,
String path,
int sizeBytes, {
CacheSource source = CacheSource.incidental,
}) async {
await _db.into(_db.audioCacheIndex).insertOnConflictUpdate(
AudioCacheIndexCompanion.insert(
trackId: trackId,
path: path,
sizeBytes: sizeBytes,
source: source,
),
);
}
/// Removes a track from the index AND deletes the file.
Future<void> unpin(String trackId) async {
final row = await (_db.select(_db.audioCacheIndex)