What's New
Highlights
- Audio Format Conversion: Convert between FLAC, MP3, and Opus directly from Track Metadata screen with full metadata and cover art preservation
- PC v7.0.8 Backend Merge: Adopts several Go backend improvements from SpotiFLAC PC v7.0.8 including Amazon encrypted stream support, SpotFetch metadata fallback, and Qobuz API update
- Amazon Music Re-enabled: Amazon provider back in service with new API
Added
- "Use Primary Artist Only" setting: strips featured artists from folder names (e.g. "Justin Bieber, Quavo" becomes "Justin Bieber") for cleaner folder organization
- Supports separators:
,;&feat.ft.featuringwithx - Available in Settings > Download > below "Use Album Artist for folders"
- Supports separators:
- Audio format conversion from Track Metadata screen
- Convert between FLAC, MP3, and Opus formats (any direction)
- Selectable bitrate: 128k, 192k, 256k, 320k
- Full metadata and cover art preservation during conversion
- Confirmation dialog before converting (original file deleted after)
- SAF storage support: copies to temp, converts, writes back via SAF
- Download history automatically updated with new file path
- Unified download request contract (
DownloadRequestPayload) for all providers/flows- Includes full superset fields: lyrics mode, genre/label/copyright, provider IDs, SAF params, cover/quality settings
- Added strategy flags in payload:
use_extensions,use_fallback
- New Go unified router entrypoint:
DownloadByStrategy(requestJSON)- Routing priority: YouTube service -> extension fallback -> built-in fallback -> direct service
- New Android method channel handler:
"downloadByStrategy"->Gobackend.downloadByStrategy(...) - SpotFetch metadata fallback integration for Spotify-blocked regions
- New backend client for
spotify.afkarxyz.fun/api - Automatic fallback in Spotify metadata fetch path when primary source fails
- New backend client for
- Lyrics extraction now supports MP3 (ID3v2) and Opus/OGG (Vorbis comments) in addition to FLAC
- Includes heuristic detection of lyrics stored in Comment fields
- Edit Metadata now supports manual cover selection (pick/replace cover image) and embeds it into audio tags on save
- Save Lyrics now shows an immediate in-progress snackbar (
Saving lyrics...) so users know the operation has started
Changed
- Merged several Go backend improvements from SpotiFLAC PC v7.0.8: Amazon new API with encrypted stream/decryption support, SpotFetch metadata fallback for Spotify-blocked regions, multi-format lyrics extraction (MP3/Opus/OGG), Qobuz Jumo API update.
- Download queue execution now builds one payload and uses a single bridge entrypoint (
PlatformBridge.downloadByStrategy) instead of branching into multiple bridge methods - Dart
downloadByStrategynow sends a single request to Go (downloadByStrategychannel); routing concern is centralized in Go backend - Legacy Dart bridge methods (
downloadTrack,downloadWithFallback,downloadWithExtensions,downloadFromYouTube) are now thin wrappers and marked@Deprecated - Qobuz downloader updated to latest Jumo API contract (
/getendpoint, required headers) - Amazon download flow now returns
decryption_keyfrom Go and performs decryption in Flutter (local file + SAF paths) - Amazon now uses the new
amazon.afkarxyz.funAPI flow (ASIN-based track endpoint + legacy fallback) with encrypted stream support - Amazon ASIN extraction rewritten with robust URL/query-param parsing and regex fallback
- Amazon provider re-enabled in download service picker and download settings (alongside Tidal, Qobuz, and YouTube picker flow)
- Track Metadata cover UI now refreshes from the embedded file after Edit Metadata/Re-enrich, so the displayed art matches actual file tags
- Edit Metadata cover section moved to the top of the form and now previews current embedded cover before replacement (plus selected replacement preview)
- Edit Metadata cover preview enlarged (120px to 160px) with shadow, side-by-side layout for current vs selected cover, and label repositioned below image
Fixed
- Fixed lyrics mode "External .LRC" still embedding lyrics into metadata -
lyrics_modewas not being sent to Go backend for single-service downloads and YouTube provider, causing Go to default to "embed" - Fixed
flutter_local_notificationsv20 breaking changes - migrated allinitialize(),show(), andcancel()calls from positional parameters to named parameters - Fixed SAF duplicate folder bug: concurrent batch downloads creating empty folders with
(1),(2),(3)suffixes - added synchronized lock toensureDocumentDirin Kotlin with duplicate detection and cleanup - Track Metadata lyrics section now hides "Embed Lyrics" when lyrics are already embedded in file, preventing redundant embed attempts
- Fixed lyrics embed path to support FLAC/MP3/Opus consistently (including SAF files) without forcing unsupported parser paths
- Inconsistent parameter parity across download paths
downloadWithExtensionsnow carriescopyright- YouTube path now carries
embed_max_quality_coverand metadata parity fields
- Inconsistent success response metadata between direct/fallback flows
- Added shared Go response builder for
DownloadTrackandDownloadWithFallback - Success responses now consistently include
genre,label,copyright, andlyrics_lrc
- Added shared Go response builder for
- YouTube success response now also includes extended metadata fields (
cover_url,genre,label,copyright) for parity with other providers - Fixed
Save Lyricscrash on Android (java.lang.Integer cannot be cast to java.lang.Long) by normalizingduration_mschannel argument asNumber -> Long - Fixed FLAC Re-enrich cover edge case where metadata could be written without cover when temp cover file creation failed; FLAC cover embed now uses in-memory bytes and verifies cover after write
- Fixed FLAC picture-block embed robustness by detecting image MIME via magic bytes (JPEG/PNG/GIF/WEBP) instead of relying on filename extension
- Fixed MP3/Opus metadata rewrite flows to preserve existing embedded cover when no new cover is available
- Fixed Library tab cover not updating after manual cover edit/re-embed for downloaded tracks
- Queue/Library now prefers embedded cover art extracted from local files (not just cached
coverUrl) - Added per-track extraction cache with file-modification invalidation so updated embedded art is reflected in Library
- Extraction is now on-demand for edited tracks only (not full-library reload)
- Returning from Track Metadata now refreshes cover cache only for the affected track
- Cover refresh is now skipped when file modification time is unchanged, removing unnecessary flash when simply opening/closing metadata screen
- Queue/Library now prefers embedded cover art extracted from local files (not just cached
- Fixed repeated cover preview extraction in Track Metadata screen (
track_cover_preview_*) causing visible flash when reopening- Added in-memory preview cache keyed by file path so reopening metadata reuses existing preview without re-extract
- Cache validation uses file modification time for filesystem paths; SAF paths are refreshed only after successful edit actions
- Queue/Library now also compares SAF file last-modified (
getSafFileModTimes) before refreshing embedded-cover cache - Preview cache key is now stable per track item (not volatile temp SAF path), eliminating false cache misses on SAF-backed files
- Track Metadata no longer auto-extracts cover preview on every screen open; extraction now runs only after actual edit/re-enrich changes (or when explicitly forced)
- Track metadata edits/re-enrich now sync updated tags back into
downloadHistoryProvider+ SQLite history rows- Non-Library screens that read download history (Home/album/history views) now reflect updated title/artist/album/tags without manual rescan
- Track Metadata back-navigation now returns an explicit update result after successful edits/re-enrich, enabling History-tab cover refresh fallback when SAF timestamps are unreliable
Performance
- Configured Flutter image cache limits (240 entries / 60 MiB) and added
ResizeImagewrappers for cover art precaching across all screens, reducing peak memory usage on cover-heavy pages - Added LRU eviction to Deezer cache with configurable max entries per cache type (search/album/artist/ISRC) and periodic expired-entry cleanup to prevent unbounded memory growth in long sessions
- Download progress notifications are now normalized (2-decimal progress, 1-decimal speed, 0.1 MiB byte steps) and deduplicated by track/artist/percent/queue-count, reducing notification overhead during batch downloads
- Each queue item now uses a dedicated
ConsumerWidgetwith per-item.select()instead of rebuilding the entire list on any item change; items are wrapped inRepaintBoundaryfor paint isolation - Queue/Library search indexes are now built on-demand per item instead of upfront for all items, with bounded LRU caches (max 4000 entries)
copyWithnow preserves derived lookup indexes (ISRC map, track key set) when items list is unchanged, avoiding O(n) rebuild on every scan progress update- Scan progress polling now compares values before calling
setState, skipping unnecessary widget rebuilds when nothing changed - Added in-flight flag to download progress and library scan polling to prevent concurrent timer callbacks from overlapping
- New
DownloadedEmbeddedCoverResolverservice replaces per-screen cover extraction logic with a shared bounded cache (160 entries), mod-time validation, and throttled refresh checks - Multiple embedded cover change callbacks are now coalesced into a single frame via
addPostFrameCallback, preventing redundant rebuilds - Downloaded album screen now caches filtered/sorted track lists and reuses them when the source data reference is unchanged
- Home tab recent downloads now use single-pass aggregation instead of building full per-album lists, and store only IDs instead of full item objects for the clear-all action
- Removed duplicate
_downloadedSpotifyIdsSet and_isrcSet(both now use existing map lookups), removed unused_isTypingstate in home tab - Track cache pre-warming is now capped at 80 tracks per request to avoid excessive backend calls on large playlists
- About page contributor avatars now use
memCacheWidth/memCacheHeightto decode at display size instead of full resolution - Orphaned download cleanup now checks file existence in parallel (chunk 16) instead of sequentially
- Local library
findByTrackAndArtistnow uses O(1) map lookup (_byTrackKey) instead of O(n) linear scan - Local library database load and SharedPreferences fetch now run in parallel
- Legacy mod-time backfill now uses chunked parallel
File.stat(chunk 24) with per-chunk cancel check - Downloaded album screen now caches disc grouping, sorted disc numbers, common quality, and embedded cover path with reference-identity invalidation
- Local album screen common quality is now computed once during cache rebuild instead of per-build
- Batch delete in album screens now uses O(1) map lookup (
tracksById) instead of.where().firstOrNull - Cache management page now fires all async init calls in parallel and uses chunked async directory deletion (chunk 24)
- Cover resolver preview file existence check is now throttled (2.2s interval) to reduce synchronous I/O in build path
- History and library database DELETE operations are now chunked (500 per batch) to stay within SQLite variable limits
- Library database
cleanupMissingFilesnow checks file existence in parallel (chunk 16) and deletes in batched SQL
Security
- All logs (Go and Dart) now automatically redact Bearer tokens, access/refresh tokens, client secrets, API keys, and passwords using regex-based sanitization before storage
- Extension auth URLs are now validated for HTTPS-only, no embedded credentials, and no private/local network targets before opening
- Auth URLs in logs are summarized to scheme+host+path only (query params stripped) to prevent token leakage; token exchange error bodies are truncated and sanitized
- Extension HTTP requests now block URLs with embedded credentials (
user:pass@host) - Extension storage files changed from
0644to0600(owner-only read/write) - All SAF relative directory paths are now sanitized per-segment with
./..filtering; all user-provided file names pass throughsanitizeFilename()before use - Extension ID is sanitized before building download destination path
- Log export device info now shows Build ID and Security Patch level instead of masked Device ID
Technical
- Centralized request serialization in
PlatformBridgevia shared invoke helper and unified payload model - Go strategy router normalizes incoming service casing before dispatch
- Extension runtime:
customSearchnow passes query/options via VM globals instead of string interpolation, preventing parser edge cases on certain devices - Extension runtime: JS panic handler now logs full stack trace for easier debugging
DownloadQueueLookupexpanded withbyItemIdmap anditemIdslist for O(1) queue item access from UI- Non-error/non-fatal log entries are now skipped entirely (not just hidden) when detailed logging is disabled, reducing buffer growth and Go log polling overhead
Removed
- Buy Me a Coffee references removed from donate page, FUNDING.yml, README, and all localization files (account suspended)
Downloads
Android
- arm64:
SpotiFLAC-v3.6.5-arm64.apk(recommended for modern devices) - arm32:
SpotiFLAC-v3.6.5-arm32.apk(older devices)
iOS
- iOS:
SpotiFLAC-v3.6.5-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