What's New
Highlights
- SAF Storage (Android 10+): Proper Storage Access Framework support for download destination (content URIs)
- Select download folder via SAF tree picker
- Downloads now write to SAF file descriptors (
/proc/self/fd/*) instead of raw filesystem paths - Works around Android 10+ scoped storage permission errors
Added
- Home feed disk caching via SharedPreferences for instant restore on app startup
- SAF display path resolver in native Android layer (converts tree URIs to readable paths)
- New settings fields for storage mode + SAF tree URI
- SAF platform bridge methods: pick tree, stat/exists/delete, open content URI, copy to temp, write back to SAF
- SAF library scan mode (DocumentFile traversal + metadata read)
- Library UI toggle to show SAF-repaired history items
- Scan cancelled banner + retry action for library scans
- Android DocumentFile dependency for SAF operations
- Post-processing API v2 (SAF-aware, ready to replace v1)
Changed
- Download pipeline supports
output_path+output_extfor Go backend - Tidal/Qobuz/Amazon/Extension downloads use SAF-aware output when enabled
- Post-processing hooks run for SAF content URIs (via temp file bridge)
- File operations in Library/Queue/Track screens now SAF-aware (
open,exists,delete,stat) - Android build tooling upgraded to Gradle 9.3.1 (wrapper)
- Android build path validated with Java 25 (Gradle/Kotlin/assemble debug)
- SAF tree picker flow in
MainActivitymigrated to Activity Result API (registerForActivityResult) MainActivityhost migrated toFlutterFragmentActivityfor SAF picker compatibility- Legacy
startActivityForResult/onActivityResultSAF picker path removed
Fixed
- Android 10+
permission deniedwhen writing to/storage/emulated/0(now handled via SAF) - SAF history repair: auto-resolve missing content URIs using tree + filename
- SAF download fallback: retry in app-private storage when SAF write fails
- Tidal DASH manifest writing when output path is a file descriptor (no invalid
.m4apath) - External LRC output in SAF mode
- Restored old-device renderer fallback while using
FlutterFragmentActivityby injecting shell args from a customFlutterFragment(--enable-impeller=falseon problematic devices) - Preserved Flutter fragment creation behavior (cached engine, engine group, new engine) while adding Impeller fallback support
- SAF tree picker result now consistently returns
tree_uripayload with persisted URI permission handling
Downloads
Android
- arm64:
SpotiFLAC-v3.5.0-beta.1-arm64.apk(recommended for modern devices) - arm32:
SpotiFLAC-v3.5.0-beta.1-arm32.apk(older devices)
iOS
- iOS:
SpotiFLAC-v3.5.0-beta.1-ios-unsigned.ipa(sideload required)
Installation
Android: Enable "Install from unknown sources" and install the APK
iOS: Use AltStore, Sideloadly, or similar tools to sideload the IPA