Skip to content

Commit 4decdb5

Browse files
committed
v3.7.0: roll back from v4, remove internal player — v3 is already complete
Version rolled back from v4.x to v3.7.0. After extensive work on v4's internal streaming engine, smart queue, DASH pipeline, and media controls, we realized v3 was already feature-complete. Adding more big features only made maintenance increasingly difficult and the developer's life miserable. Stripped back to what works: external player only, cleaner codebase, sustainable long-term. - Remove just_audio, audio_service, audio_session and entire internal playback engine (smart queue, notification, shuffle/repeat, prefetch) - Remove PlaybackItem model, MiniPlayerBar widget, notification drawables - Remove playerMode setting (external-only now) - Migrate MainActivity from AudioServiceFragmentActivity to FlutterFragmentActivity - Migrate Qobuz to MusicDL API - Update changelog with v3.7.0 rollback explanation
1 parent 4747119 commit 4decdb5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+680
-10094
lines changed

CHANGELOG.md

Lines changed: 25 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,48 @@
11
# Changelog
22

3-
## [4.0.1] - 2026-02-26
3+
## [3.7.0] - 2026-03-04
44

5-
### Added
6-
- **Clickable Metadata Navigation**: Added reusable `ClickableArtistName` and `ClickableAlbumName`
7-
- **Love Action in Media Notification**: Added custom notification action (`toggle_love`) with new Android favorite/favorite-border status icons
8-
9-
### Changed
10-
11-
- **Track Metadata Model Expansion**: `Track` now carries `artistId` and `albumId`, propagated across search, queue, playback, CSV import, and extension mapping flows
12-
- **Full-Screen Player UX**: Top bar now supports swipe-down dismiss; artist/album text is now tappable; and in-player love toggle is available next to track metadata
13-
- **Playlist Picker Flow Refactor**: Reworked playlist picker sheet into stateful multi-select flow with explicit Done action and improved create-playlist handling
14-
- **CSV Import Interaction Flow**: Added single-flight import guard, more reliable progress dialog lifecycle, and safer local navigator usage
15-
- **Amazon API**: Amazon metadata fetch `amzn.afkarxyz.fun`
16-
- **Qobuz URL Resolution Strategy**: Removed legacy/Jumo fallback path; now uses standard API pool (deeb)
17-
- **Update Checker Asset Targeting**: Update selection now prioritizes arm64/universal assets only
18-
- **Donate Page Supporters**: Updated highlighted donor/supporter list entries
19-
20-
### Fixed
21-
22-
- **FLAC External Lyrics Output**: External `.lrc` writing now works consistently for lyrics mode `external`/`both`, with SAF conversion paths avoiding duplicate writes
23-
- **Loved-State Notification Sync**: Playback notification controls now refresh correctly when loved state changes
24-
- **Queue Selection Touch Handling**: Selection overlays/check indicators no longer block tap gestures in queue and playlist selection modes
25-
- **Vorbis-to-ID3 Tag Mapping Robustness**: FFmpeg metadata conversion now normalizes keys and handles aliases like `TRCK` and `TPOS`
26-
- **Nested Dialog Navigation Safety**: Adjusted dialog navigator scope in CSV import and track-delete flows to prevent navigator mismatch issues
27-
- **Artist/Album Routing Reliability**: Track metadata routing now reuses resolved artist/album IDs across album/artist/home/search/queue/player surfaces
28-
- **Release Workflow Go Toolchain**: Pinned CI release workflow Go version to `1.25.7` for consistent build behavior
29-
30-
---
31-
32-
## [4.0.0] - 2026-02-22
5+
Hey everyone, thank you so much for sticking with SpotiFLAC Mobile.
336

34-
> **Major update warning:** This release introduces a large streaming-focused refactor and broad cross-app behavior changes.
35-
>
36-
> **Diff scope (`cdc583678558223ecbb552176b53727d303ae218..HEAD`):** 121 files changed, 28,354 insertions(+), 4,598 deletions(-).
7+
Starting from this release, we're rolling the version back from **v4.x to v3.x**.
378

38-
### Added
9+
### Removed
3910

40-
- **End-to-End Streaming Mode**: Full streaming playback flow with full-screen player, synced lyrics, media controls, and queue-aware tap behavior across album, artist, playlist, home, and search screens
41-
- **Smart Queue System**: ML-based queue auto-curation with related artist discovery, plus a dedicated playback queue view
42-
- **DASH Streaming Pipeline**: Native DASH manifest playback support with local proxy integration and FFmpeg tunnel fallback for unsupported paths
43-
- **Playback State Persistence**: Player state and queue continuity restored across app restarts
44-
- **Adaptive Playback Engine**: EventChannel-driven playback/progress updates (replacing polling) and adaptive prefetch behavior
45-
- **Queue Reliability Controls**: New auto-skip unavailable tracks option during queue playback
46-
- **Player Quick Action**: New download button in full-screen player top bar
47-
- **Metadata Control**: New global master switch for embed metadata behavior
48-
- **Setup Flow Update**: Initial setup now prioritizes mode selection instead of Spotify API setup
49-
- **Library Workflow Expansion**: Playlist-first library redesign, drag-and-drop categorization, folder multi-select, and batch playlist picker flows
50-
- **SongLink Region Setting**: Region selection support for metadata/linking behavior
51-
- **Track Interaction UX**: Long-press context menus for track actions across major collection screens
52-
- **Batch Tools**: Multi-select share, batch convert, and batch re-enrich improvements for downloaded/local/queue workflows
11+
- **Internal Audio Player** — Removed `just_audio`, `audio_service`, and `audio_session` dependencies entirely. The internal playback engine (smart queue, media notification, shuffle/repeat, lyrics sync, prefetch, playback state persistence) has been completely removed. Playback now delegates to the system's external player.
12+
- **PlaybackItem Model** — No longer needed without internal playback.
13+
- **MiniPlayerBar Widget** — Removed the in-app mini player UI.
14+
- **Media Notification Controls** — Removed notification drawables (`ic_stat_favorite`, `ic_stat_favorite_border`) and the `keep.xml` resource file.
15+
- **Player Mode Setting** — The `playerMode` setting has been removed since external player is now the only mode.
16+
- **Online Playback Feature** — Online streaming mode, DASH pipeline, and related components introduced in v4.0.0 are gone from the main branch.
5317

5418
### Changed
5519

56-
- **Global Mode-Driven Actions**: Interaction mode now drives behavior app-wide (download-oriented vs streaming-oriented actions)
57-
- **UI Redesign and Responsiveness**: Full-screen cover/parallax rollout and responsive fixes for filter sheets and full-screen player in small screens/landscape
58-
- **Performance Optimizations**: Granular Riverpod consumers, selective provider watching, computation caching, debounced extension storage writes, and lifecycle cleanups
59-
- **Lyrics Loading Strategy**: Lyrics are now lazy-loaded only when the lyrics view is visible
60-
- **Persistence Backend Refactor**: Core persistence paths migrated to SQLite-backed stores for app state and library collections
61-
- **Shared Code Refactor**: Duplicated logic extracted into shared Dart/Go utilities for cleaner boundaries and maintainability
20+
- **MainActivity** now extends `FlutterFragmentActivity` directly (previously `AudioServiceFragmentActivity`).
21+
- **PlaybackController** simplified from ~1200 lines to ~87 lines — now only resolves local file paths and opens them via external player.
22+
- **ProGuard rules** cleaned up — removed audio_service/just_audio/audio_session rules.
23+
- **Qobuz** migrated to MusicDL API.
6224

63-
### Fixed
25+
### Note
6426

65-
- **iOS Build Compatibility**: Resolved `RepeatMode` naming collision with Flutter SDK symbols
66-
- **Playback Completion Handling**: Fixed track completion restart issues and queue-end completion synchronization
67-
- **Streaming Stability**: Added guards for playback race conditions during queue/stream state transitions
68-
- **Provider I/O Safety**: Improved Android/Go file descriptor handling for SAF-based outputs
69-
- **Metadata Matching Robustness**: Improved title matching with strict emoji handling and name+artist fallback lookup behavior
70-
- **Navigation Behavior**: Back button now exits app correctly instead of unexpectedly returning to home
27+
The v4 source code will remain available on the **experimental branch** as an **archived reference**, but it will no longer receive any updates or development from our side. On **March 6, 2026**, the experimental branch will be officially **deprecated and removed**.
7128

72-
---
29+
There are three main reasons behind this decision:
7330

74-
## [4.0.0] - 2026-02-22
31+
1. **Growing maintenance complexity** — The online playback feature involves a lot of tightly coupled components (DASH manifests, local proxy, FFmpeg tunnel fallback, EventChannel-driven updates, playback state persistence, and more). As the app continues to grow, keeping this feature stable has become increasingly difficult and time-consuming — time that could be better spent improving the core features you all rely on every day.
7532

76-
### Added
33+
2. **Respecting the API providers** — After giving it some thought, we realized that the streaming feature was indirectly hurting the API providers who have been generous enough to make their services available. They already offer streaming directly on their own websites, and it only feels right to direct streaming usage back to their platforms.
7734

78-
- **Interaction Mode Setting**: New "Interaction Mode" toggle in Options settings to switch between Downloader Mode (tap to queue downloads) and Streaming Mode (tap to play instantly)
79-
- Affects album, artist discography, playlist, home explore, and search screens
80-
- All action buttons (Download All, Download Selected, Download Discography) dynamically switch to Play equivalents when in Streaming Mode
81-
- **Streaming Playback Integration**: Tapping tracks in Streaming Mode plays them via `playTrackStreamAndSetQueue` with full queue support across all collection screens (album, artist, playlist, home, search)
82-
- **Long-Press Track Context Menus**: Added `onLongPress` handler on track items across album, artist, home, playlist, and search screens to open the track options bottom sheet via `TrackCollectionQuickActions.showTrackOptionsSheet`
83-
- **USDT TRC20 Crypto Donation**: Added USDT (TRC20) wallet address to Donate page with tap-to-copy-to-clipboard functionality and snackbar confirmation
84-
- **Localization**: Added interaction mode and streaming playback strings across all 14 supported locales (`optionsInteractionMode`, `modeDownloader`, `modeDownloaderSubtitle`, `modeStreaming`, `modeStreamingSubtitle`, `playAllCount`, `discographyPlay`, `discographyPlayAll`, `discographyPlaySelected`)
85-
- **Indonesian (ID) Localization**: Full translations for all new streaming mode strings
35+
3. **Long-term sustainability** — We want SpotiFLAC to be around for as long as possible. Keeping certain features in the app could attract unwanted attention and put the project's continued existence at risk. Removing them is a proactive step to keep things running smoothly for everyone.
8636

87-
### Changed
88-
89-
- **Mini Player Bar Layout**: Media section (cover art / lyrics) now uses fixed-height `SizedBox` (50% screen height, clamped 300–560px) instead of `Expanded` for more consistent layout
90-
- **Lyrics Font Size Increase**: Synced lyrics current line 22→24px, non-current 18→19px; word-by-word highlight 22→24px; unsynced 18→19px
91-
- **Playback Media Controls**: Removed stop button from notification media controls for cleaner transport bar
92-
- **Playback Queue Exhaustion**: Player now properly syncs `ProcessingState.completed` state when queue is exhausted instead of silently stopping
93-
- **`TrackCollectionQuickActions.showTrackOptionsSheet` Made Static**: Extracted to a public static method so all screens can invoke it directly for long-press handling
94-
- **Bottom Spacing in Mini Player**: Reduced from 16px to 4px for tighter layout
95-
96-
### Fixed
37+
**Still want online playback? Check out these services:**
38+
- [DabMusic](https://dabmusic.xyz)
39+
- [SquidWTF](https://tidal.squid.wtf)
9740

98-
- **Playback State Not Updating on Queue End**: Fixed playback notification staying in "playing" state when all tracks in queue have finished
41+
Thank you for your understanding and continued support. This decision was made to ensure the long-term sustainability of the app and to respect the ecosystem that has been supporting SpotiFLAC all along. You guys are the best, and we truly appreciate each and every one of you!
9942

10043
---
10144

102-
## [3.7.0] - 2026-02-19
45+
## [3.6.0] - 2026-02-19
10346

10447
### Added
10548

android/app/proguard-rules.pro

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,6 @@
8080
-keep class io.flutter.plugins.pathprovider.** { *; }
8181
-keep class dev.flutter.pigeon.** { *; }
8282

83-
# Audio Service (media playback notification) - CRITICAL for release builds
84-
-keep class com.ryanheise.audioservice.** { *; }
85-
-keep class com.ryanheise.audio_session.** { *; }
86-
-keep class com.ryanheise.just_audio.** { *; }
87-
88-
# AndroidX Media / MediaSession (used by audio_service)
89-
-keep class androidx.media.** { *; }
90-
-keep class android.support.v4.media.** { *; }
91-
-dontwarn android.support.v4.media.**
92-
9383
# Local Notifications
9484
-keep class com.dexterous.** { *; }
9585
-keep class com.dexterous.flutterlocalnotifications.** { *; }

android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import android.os.Build
77
import androidx.activity.OnBackPressedCallback
88
import androidx.activity.result.contract.ActivityResultContracts
99
import androidx.documentfile.provider.DocumentFile
10-
import com.ryanheise.audioservice.AudioServiceFragmentActivity
10+
import io.flutter.embedding.android.FlutterFragmentActivity
1111
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode
1212
import io.flutter.embedding.android.FlutterFragment
1313
import io.flutter.embedding.android.RenderMode
@@ -32,7 +32,7 @@ import java.io.FileInputStream
3232
import java.io.FileOutputStream
3333
import java.util.Locale
3434

35-
class MainActivity: AudioServiceFragmentActivity() {
35+
class MainActivity: FlutterFragmentActivity() {
3636
private val CHANNEL = "com.zarz.spotiflac/backend"
3737
private val DOWNLOAD_PROGRESS_STREAM_CHANNEL =
3838
"com.zarz.spotiflac/download_progress_stream"
@@ -50,6 +50,7 @@ class MainActivity: AudioServiceFragmentActivity() {
5050
private var libraryScanProgressStreamJob: Job? = null
5151
private var libraryScanProgressEventSink: EventChannel.EventSink? = null
5252
private var lastLibraryScanProgressPayload: String? = null
53+
private var flutterBackCallback: OnBackPressedCallback? = null
5354
@Volatile private var safScanCancel = false
5455
@Volatile private var safScanActive = false
5556
private val safTreeLauncher = registerForActivityResult(
@@ -1370,11 +1371,12 @@ class MainActivity: AudioServiceFragmentActivity() {
13701371
// which disables Flutter's own OnBackPressedCallback and causes the
13711372
// system default (finish activity) to run. This callback guarantees
13721373
// popRoute is always forwarded to Flutter, where PopScope handles it.
1373-
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
1374+
flutterBackCallback = object : OnBackPressedCallback(true) {
13741375
override fun handleOnBackPressed() {
13751376
flutterEngine.navigationChannel.popRoute()
13761377
}
1377-
})
1378+
}
1379+
onBackPressedDispatcher.addCallback(this, flutterBackCallback!!)
13781380

13791381
val messenger = flutterEngine.dartExecutor.binaryMessenger
13801382

@@ -1416,6 +1418,15 @@ class MainActivity: AudioServiceFragmentActivity() {
14161418
scope.launch {
14171419
try {
14181420
when (call.method) {
1421+
"exitApp" -> {
1422+
flutterBackCallback?.isEnabled = false
1423+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
1424+
finishAndRemoveTask()
1425+
} else {
1426+
finish()
1427+
}
1428+
result.success(null)
1429+
}
14191430
"parseSpotifyUrl" -> {
14201431
val url = call.argument<String>("url") ?: ""
14211432
val response = withContext(Dispatchers.IO) {

android/app/src/main/res/drawable/ic_stat_favorite.xml

Lines changed: 0 additions & 9 deletions
This file was deleted.

android/app/src/main/res/drawable/ic_stat_favorite_border.xml

Lines changed: 0 additions & 9 deletions
This file was deleted.

android/app/src/main/res/raw/keep.xml

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)