10 KiB
Updates and Features
These are ranked in order of importance and should be tackled one element at a time before moving onto the next one. This stands as the top level orchestration for improving the application.
Completion Checklist
- 1. Drag n Drop Queue Reorder
- 2. Filtering and Sorting Options
- 3. Network Connectivity
- 4. Simoultaneous Download Setting
- 5. Playlist Sharing
- 6. In-App Purchase
Details
-
Drag n Drop Queue Reorder Currently the queue only allows the user to add utilizing the dedicated buttons throughout the app or remove elements using the 'X' option in the queue itself. Once an item has been added to the queue there is no method of moving it to another point. For example, if I decided I wwanted to shift the next song dowwn a handful of places in the queue it is currently not possible. We should add a drag and drop functionality to reorder elements in the queue.
-
Filtering and Sorting Options Within the Albums and Tracks section of the app library there should be more options for sorting and filtering. I would like the ability to filter based on genre, release year, and when it wwas added to the server. That wwway it can be easier to find what I am looking for without have to directly search for anything. All of those elements should be available in the metadata.
-
Network Connectivity This is probably the largest task of this group. I wwwant there to be connectivity through instances of the application. For example I have the app on my ipad as ell as my phone nd I would like the ability to control the device that is currently playing with the other. Basically a shared state in the app that allows for wireless control. This is a common feature of all music apps and should be included in ours as well.
-
Simoultaneous Download Setting Currently the app will only dowwnload 3 songs concurrently. There should be an option in settings to specify the number of concurrent tracks that can be dowwnloaded at a time to help speed up the process. In conjunction with this there should be an option on tracks to download all tracks.
-
Playlist Sharing I have multiple users on my server that I have active in the application and I want to be able to share playlists amongst us all. Given the fact we are sharing the same server this should be possible. This wwwill require thinking to figure out the guardrails and application of sharing but I wwant this feature.
-
In-App Purchase I want a "Buy the dev a coffee" type payment availbale in the app. Android this is not too much of a concern for review etc but for Apple IOS review it wmight be more of a hassle. Basically I want an optional button/toggle in settings that would let a user pay 5$ for the app voluntarily to help with its development.
Execution Plan
Stack: Flutter/Dart · Riverpod · dio · just_audio · Subsonic API (Navidrome). Hard constraint: client-only — no server changes; every feature must ride existing Subsonic endpoints or live entirely in-app.
Build order: 1 → 4 → 2 → 5 → 6 → (3 as its own project). (Reordered from #4 before #2 — #4 is the smallest win and warms up the settings plumbing #2 reuses.)
1. Drag-n-Drop Queue Reorder — Small, pure client
- Add
reorderQueue(oldIndex, newIndex)toPlaybackController(lib/playback/playback_engine.dart): move item inqueue, adjustcurrentIndexso the now-playing track keeps playing, mirror the move onto just_audio'sConcatenatingAudioSource(reuse the existing_applyOrderpath). - Swap queue UI to
ReorderableListView. - Resolved semantics: reorder mutates the currently-active order. Reorder-then-shuffle → shuffle re-randomizes upcoming tail as normal. Shuffle-then-reorder → the manual move sticks, rest stays shuffled. (Open edge case: whether a reorder made while shuffled should also rewrite
_unshuffledOrderso it survives un-shuffle — deferred.)
4. Simultaneous Download Setting + Download-All — Small, pure client
- Replace
static const int _maxConcurrent = 3(lib/downloads/download_manager.dart:141) with a settings-backed value; ensure the pump loop re-reads it so changes apply live. Sane cap ~1–10. - Add stepper setting in
lib/screens/settings_screen.dart+lib/settings/settings_store.dart. - Add "Download all" bulk-enqueue actions on album / playlist / tracks contexts.
2. Filtering & Sorting (genre / year / date-added) — Medium, client-only feasible
- Prereq: parse
created(date-added) into Album/Song models inlib/subsonic/models.dart— not present today, required for "when added." - Albums: extend
getAlbumList2(lib/subsonic/subsonic_client.dart) to passgenre,fromYear/toYear; usetype=newestfor date-added; addgetGenresfor the genre picker. - Tracks: sort/filter client-side over cached
lib/library/library_index.dart(currently alphabetical-only). - Add filter/sort control bar to
lib/screens/browser_screen.dartpanels; persist last choice insettings_store.dart. - Risks: genre is free-text (casing / multi-genre inconsistency); server-side album lists paginate at 500.
5. Playlist Sharing (same-server users) — Medium, client-only feasible
Playlistmodel already exposesowner+public(lib/subsonic/models.dart:253-263).- Add
updatePlaylist(public: true)support to client; add a "Share / make public" toggle in playlist UI. Surface a "Shared" section for playlists whereowner != currentUser. - Resolved: public (server-wide) sharing is acceptable. A non-owner saves a copy — clone the public playlist into their own via
createPlaylist+addTracksToPlaylist(avoids edit-permission issues; owner-only edit/delete on originals). - Risks: server-dependent — confirm Navidrome honors
public. No targeted per-user sharing possible client-only (all-or-public only).
6. In-App "Buy the Dev a Coffee" ($5) — Medium, client + store config
- Resolved: route through IAP (review-safe). Flutter
in_app_purchase, modeled as a consumable $5 product configured in App Store Connect + Google Play Console. Apple requires digital tips to go through IAP (no external payment link on iOS). Optional button inlib/screens/settings_screen.dart.
3. Network Connectivity / Cross-Device Control — Large, LAN-only
Scope (locked): LAN-only · remote-control topology (one device plays audio, others act as remotes) · full remote (transport + live queue mirror + browse/enqueue/reorder). Both devices must be signed into the same Subsonic server — that constraint is what makes full remote cheap: the remote sends a track by id / Song JSON and the host resolves its own stream URI locally via the existing streamUriFor. No server changes; discovery + transport are pure LAN.
Why not cross-network: Subsonic/Navidrome exposes no realtime channel, pub/sub, or shared scratch space, so two instances can't coordinate over the internet without a relay (breaks client-only) or a hacky high-latency polling abuse of playlists. LAN-only is the robust client-only answer.
Architecture
- Transport: host runs an in-process WebSocket server (
dart:ioHttpServer+WebSocketTransformer.upgrade— no new dep); remote is aWebSocket.connectclient. - Discovery: mDNS/Bonjour via a new dep —
nsd(register + browse;bonsoiris the alt). Service type_timbre._tcp; TXT record carriesserverKey, protocol version, device name. - Auth (no manual pairing): both devices already know the server password → derive an HMAC key from it; host sends a nonce, remote returns
HMAC-SHA256(nonce). Proves same-user without a PIN (cryptoalready a dep). Control gated to matchingserverKeyonly. - Playback facade (load-bearing refactor):
activePlaybackProvider(state the UI renders) +playbackCommandsProvider(aPlaybackCommandsinterface the UI drives).PlaybackController(unchanged local engine) and a newRemotePlaybackProxyboth satisfy the seam. Local mode delegates to the controller; remote mode renders the last WS snapshot and turns commands into outgoing messages (optimistic, reconciled on next snapshot). - New modules:
lib/remote/messages.dart(DTOs + codec + auth),host_server.dart,discovery.dart,remote_session.dart,remote_playback.dart,lib/state/remote_providers.dart, plus a "Devices" picker sheet + a "Playing on " status chip.
Build order
- Protocol + codec + auth — pure Dart, fully unit-testable (
Command/StateSnapshot/HelloDTOs, versioning, HMAC challenge-response). - Facade indirection — add
activePlaybackProvider+playbackCommandsProvider, migrate the ~11 UI files offplaybackProvider. Ships as a no-op local-only refactor; de-risks everything downstream. ← land first. - Host server — WS server broadcasts snapshots on
PlaybackControllerchange (reuse the throttle pattern), applies inbound commands. - Discovery — advertise when playable / browse for peers; platform config.
- Remote session + routing — connect, mirror state, send commands, position interpolation, disconnect → fall back to local.
- UI — device sheet, status chip, route browse/enqueue actions to the host.
- Auth + polish + tests — loopback (host + client in one process) integration test.
Risks / gotchas
- iOS 14+:
NSLocalNetworkUsageDescription+NSBonjourServices=_timbre._tcpin Info.plist or discovery silently fails. Android NSD needs a multicast lock (nsd handles). - iOS backgrounding: host stays alive while playing (audio background mode via
just_audio_background); a paused+backgrounded host may suspend its socket — remote reconnects on resume. - Position sync: interpolate on the remote between periodic snapshots; reconcile on seek.
- Offline host: host resolves its own URIs; a track it can't stream reuses the existing
PlaybackError.offlinepath. - Multi-remote works free: host broadcasts to all clients.
Estimate: ~2–3× any other item on this list; step 2 (facade) is the reversible foundation to land first.