Abelardo Ramirez bed95efb29 fix(playlists): thread the source provider id through to recent playlists
Fixes #368.

Reopening a Spotify playlist from "recent access" showed no tracks,
while the first view (right after pasting the URL) worked fine.

Root cause traced across two layers:

1. Go: ExtURLHandleResult (the parsed shape of an extension's handleUrl()
   return value) never captured a top-level `id` for the handled
   resource. Track/album/artist results carry their own id inside their
   nested metadata, but a plain playlist result has no such object, so
   its id was silently dropped everywhere from the goja parser through
   to the JSON the Dart side receives.

2. Dart: TrackState had nowhere to put that id even if it existed
   (only playlistName), so recording a "recent" playlist access stored
   the playlist's *name* as if it were its id. Reopening it later fed
   that name into PlaylistScreen's provider-guessing logic
   (legacyProviderIdFromResourceId, which only recognizes legacy
   "provider:id" prefixes), which naturally failed and fell through to
   a hardcoded Deezer metadata fetch using a Spotify playlist's name as
   the resource id - guaranteed to return nothing.

Fix, matching the "generic API, not per-provider checks" architecture
in CONTRIBUTING.md:

- go_backend/extension_provider_wrapper.go: add ExtURLHandleResult.ID.
- go_backend/extension_goja_convert.go: parse it from the handler's
  return value.
- go_backend/exports_extensions.go: surface it in the JSON response.
- lib/providers/track_provider.dart: add TrackState.playlistId,
  populated from the response's `id` field for playlist results.
- lib/screens/home_tab.dart: record the real playlist id (falling back
  to the name only if a provider never supplies one) and pass both the
  id and the already-known provider id forward to PlaylistScreen.
- lib/screens/playlist_screen.dart: PlaylistScreen gains a
  metadataProviderId param that takes priority over guessing from the
  id's shape.
- lib/screens/home_tab_recent.dart: pass the recent-access entry's
  stored providerId through when reopening a playlist.
- lib/utils/provider_resource_ids.dart: extract the
  known-id-vs-guessed-id preference into a small, directly testable
  resolvePreferredMetadataProviderId helper.

Note: this fixes the common case (an extension already reported its
own id as the source provider for the URL it handled). If a provider
never supplies an id for its handleUrl() playlist result, playlistId
stays null and behavior is unchanged from before this fix - no
regression, just not a complete fix for that narrower case, since
that would require changes in extension-side JS code outside this
repo.

Tests:
- go_backend/extension_goja_convert_url_handle_test.go: the new ID
  field round-trips from a handler's return value, and stays empty
  when the handler doesn't supply one.
- test/provider_resource_ids_test.dart: resolvePreferredMetadataProviderId
  prefers a known provider id, falls back to the legacy-prefix guess,
  treats a blank known id as unknown, and returns null for the
  unprefixed-id-with-no-known-provider case from #368 itself.

Verification: go build/vet/test and flutter analyze/test all green.
2026-08-11 17:29:41 +07:00
2026-04-04 13:27:33 +02:00
2026-04-04 13:27:33 +02:00
2026-04-04 13:27:33 +02:00
2026-04-04 13:27:33 +02:00

GitHub Release Crowdin

Telegram Channel Telegram Community

Screenshots


Extensions

Extensions let the community add new music sources and features without waiting for app updates. When a streaming service API changes or a new source becomes available, extensions can be updated independently.

Installing Extensions

  1. Open the Store tab in the app
  2. On first launch, enter an Extension Repository URL when prompted
  3. Browse and install extensions with one tap
  4. Or download a .spotiflac-ext file and install manually via Settings > Extensions
  5. Configure extension settings if needed
  6. Set provider priority under Settings > Extensions > Provider Priority

Developing Extensions

Note

Want to build your own extension? Start with the Extension Development Guide. The documentation site contains the expanded API reference.


Development

SpotiFLAC Mobile combines a Flutter/Dart UI, a Go backend compiled with gomobile, and thin Android/iOS platform bridges. Toolchain versions are pinned in .fvmrc, go_backend/go.mod, and the GitHub Actions workflows.

Start with the Contributing Guide for the development setup, project boundaries, validation commands, and pull request checklist.


SpotiFLAC (Desktop)

Download music in true lossless FLAC from extension-provided sources on Windows, macOS & Linux.

SpotiFLAC (Python Module)

Python library for SpotiFLAC integration, maintained by @ShuShuzinhuu.


FAQ

Why does the Store tab ask me to enter a URL?

Starting from version 3.8.0, SpotiFLAC Mobile uses a decentralized extension repository system. Extensions are hosted independently rather than on a built-in server, so anyone can create and host a compatible repository. Enter a repository URL in the Store tab to browse and install extensions.

Why is my download failing with "Song not found"?

The track may not be available from your enabled providers. Try enabling more providers under Settings > Extensions > Provider Priority, or install additional download extensions from the Store.

Why are some tracks downloading in lower quality?

Quality depends on what's available from the source and the installed download extension. Check each extension's quality options and service notes in the app.

Can I download playlists?

Yes! Just paste the playlist URL in the search bar. The app will fetch all tracks and queue them for download.

Why do I need to grant storage permission?

The app needs a writable destination for downloaded files. On Android, choose a folder with the system folder picker (SAF), or use the app-specific folder. If Android revokes a saved folder grant, select the folder again in Settings.

Is this app safe?

SpotiFLAC Mobile is open source, so its code and build workflows can be inspected directly in this repository.

Why is downloading not working in my country?

Some countries have restricted access to certain streaming service APIs. If downloads are failing, try using a VPN to connect through a different region.

Can I add SpotiFLAC Mobile to AltStore or SideStore?

Yes! Add the official source to receive updates directly within the app. Copy this link:

https://raw.githubusercontent.com/spotiflacapp/SpotiFLAC-Mobile/refs/heads/main/apps.json

In AltStore/SideStore, go to Browse > Sources, tap +, and paste the link.

Note

If SpotiFLAC Mobile is useful to you, consider supporting development:

Ko-fi


Contributors

Thanks to everyone who has contributed to SpotiFLAC Mobile!

We also appreciate everyone who helped with translations on Crowdin, reported bugs, suggested features, and spread the word.

Interested in contributing? Check out the Contributing Guide to get started!


API Credits

MusicDL LRCLib Paxsenix Cobalt Song.link
IDHS

Disclaimer

This repository and its contents are provided strictly for educational and research purposes. The software is provided "as-is" without warranty of any kind, express or implied, as stated in the MIT License.

  • No copyrighted content is hosted, stored, mirrored, or distributed by this repository.
  • Users must ensure that their use of this software is properly authorized and complies with all applicable laws, regulations, and third-party terms of service.
  • This software is provided free of charge by the maintainer. If you paid a third party for access to this software in its original form from this repository, you may have been misled or scammed. Any redistribution or commercial use by third parties must comply with the terms of the repository license. No affiliation, endorsement, or support by the maintainer is implied unless explicitly stated in writing.
  • SpotiFLAC Mobile is an independent project. It is not affiliated with, endorsed by, or connected to any other project or version on other platforms that may share a similar name. The maintainer of this repository has no control over or responsibility for third-party projects.
  • The author(s) disclaim all liability for any direct, indirect, incidental, or consequential damages arising from the use or misuse of this software. Users assume all risk associated with its use.
  • If you are a copyright holder or authorized representative and believe this repository infringes upon your rights, please contact the maintainer with sufficient detail (including relevant URLs and proof of ownership). The matter will be promptly investigated and appropriate action will be taken, which may include removal of the referenced material.

Tip

Star the repo to get notified about all new releases directly from GitHub.

Languages
Dart 72.6%
Go 20.1%
Kotlin 6.4%
Swift 0.8%