github spotiflacapp/SpotiFLAC-Mobile v3.6.5
SpotiFLAC v3.6.5

latest releases: v4.8.0, v4.7.1, v4.7.0...
5 months ago

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. featuring with x
    • Available in Settings > Download > below "Use Album Artist for folders"
  • 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
  • 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 downloadByStrategy now sends a single request to Go (downloadByStrategy channel); 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 (/get endpoint, required headers)
  • Amazon download flow now returns decryption_key from Go and performs decryption in Flutter (local file + SAF paths)
  • Amazon now uses the new amazon.afkarxyz.fun API 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_mode was not being sent to Go backend for single-service downloads and YouTube provider, causing Go to default to "embed"
  • Fixed flutter_local_notifications v20 breaking changes - migrated all initialize(), show(), and cancel() 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 to ensureDocumentDir in 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
    • downloadWithExtensions now carries copyright
    • YouTube path now carries embed_max_quality_cover and metadata parity fields
  • Inconsistent success response metadata between direct/fallback flows
    • Added shared Go response builder for DownloadTrack and DownloadWithFallback
    • Success responses now consistently include genre, label, copyright, and lyrics_lrc
  • YouTube success response now also includes extended metadata fields (cover_url, genre, label, copyright) for parity with other providers
  • Fixed Save Lyrics crash on Android (java.lang.Integer cannot be cast to java.lang.Long) by normalizing duration_ms channel argument as Number -> 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
  • 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 ResizeImage wrappers 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 ConsumerWidget with per-item .select() instead of rebuilding the entire list on any item change; items are wrapped in RepaintBoundary for 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)
  • copyWith now 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 DownloadedEmbeddedCoverResolver service 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 _downloadedSpotifyIds Set and _isrcSet (both now use existing map lookups), removed unused _isTyping state 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/memCacheHeight to decode at display size instead of full resolution
  • Orphaned download cleanup now checks file existence in parallel (chunk 16) instead of sequentially
  • Local library findByTrackAndArtist now 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 cleanupMissingFiles now 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 0644 to 0600 (owner-only read/write)
  • All SAF relative directory paths are now sanitized per-segment with ./.. filtering; all user-provided file names pass through sanitizeFilename() 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 PlatformBridge via shared invoke helper and unified payload model
  • Go strategy router normalizes incoming service casing before dispatch
  • Extension runtime: customSearch now 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
  • DownloadQueueLookup expanded with byItemId map and itemIds list 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

arm64 arm32 iOS

Don't miss a new SpotiFLAC-Mobile release

NewReleases is sending notifications on new releases.