- Neue NavidromeService APIs: scrobble() + getBookmark()
- Lädt Wiedergabe-Position vom Server beim Song-Start (Subsonic-API)
- Sendet Position alle 5s zum Server wenn verbunden
- Priorisiert Server-Position über lokale Position
- Full Sync: lokal speichern + Server synchen parallel
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
IMPLEMENTATION:
- NavidromeService.streamAndCacheToLocal(): Stream + gleichzeitig cachen
- Cache-First-Logik: Wenn Cache existiert → lokal spielen
- AudioHandler erweitert: loadPlaylist() nutzt Auto-Caching
- CacheManager Integration in AudioHandler
FLOW:
1. User spielt Album ab → streamt vom Server
2. Während des Abspielens → lokal in Cache speichern
3. Nächstes Mal: Lokal spielen (schneller, Offline möglich)
Phase 2 P0 COMPLETE! ✅
Tests: 70/70 ✅
LYRICS:
- Lyrics-Button im Now-Playing Screen (📝 Icon)
- Bottom-Sheet mit Lyrics vom Server (getLyrics() API)
- Fallback: 'Keine Lyrics verfügbar'
- Lyrics-Klasse für Datenmodell
SHUFFLE/REPEAT:
- Shuffle-Button (schon implementiert!) zeigt aktiven Status rot
- Repeat-Button (Off → All → One → Off cycle)
- Beide via AudioHandler.setShuffleMode() / setRepeatMode()
- Integration in _Controls (Player-UI)
Phase 1 P1-Features jetzt funktionsfähig!
Tests: 70/70 ✅
- MeloAudioHandler bekommt MeloDb per Konstruktor injiziert
- Positions-Timer (alle 5s) schreibt db.recordPlayback(songId, positionMs)
während aktiv abgespielt wird (songId aus mediaItem.extras['songId'])
- loadPlaylist liest db.lastPosition() für den Start-Song und seekt vor
dem play() an die gespeicherte Position, statt immer von vorne zu starten
- shouldResumeAt() als reine, testbare Funktion extrahiert: kein Resume bei
Position 0 oder innerhalb der letzten 3s einer bekannten Track-Dauer
- dispose() zum gefahrlosen Abbrechen des Positions-Timers in Tests ohne
vollen App-Lifecycle (super.stop() lässt sich in Widget-Tests wegen der
aktiven playbackState-Pipe nicht gefahrlos aufrufen)
- library_screen_test.dart: MeloAudioHandler-Konstruktion angepasst (db
jetzt Pflichtparameter) + handler.dispose() im Teardown
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Neuer QueueScreen: zeigt handler.queue, hebt aktuell spielenden Titel
hervor (fett/rot), Antippen springt per skipToQueueItem
- Entfernen und Umsortieren ohne Änderung an MeloAudioHandler: Queue wird
clientseitig neu berechnet und per loadPlaylist neu geladen; Position
wird nur wiederhergestellt, wenn der abgespielte Titel unverändert blieb
- Reine Queue-Logik (removeFromQueue/reorderQueue/newPlayingIndex) als
eigenständige, ohne AudioHandler testbare Funktionen extrahiert
- Warteschlangen-Button in der Now-Playing-AppBar verlinkt den neuen Screen
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- songToMediaItem trägt jetzt die Song-UUID in extras['songId'], damit
Downstream-Code (Favoriten-Button, später Playback-History) den echten
Song wiederfinden kann statt nur die file-URI zu haben
- NowPlayingScreen: Schließen-Button entfernt (AppBar-leading), da der
Screen jetzt als eigener Tab läuft statt gepusht zu werden
- FavoriteButton für den aktuellen Song in die AppBar-Actions eingebaut,
reaktiv über einen verschachtelten StreamBuilder<MediaItem?>; ohne
gültige songId (z.B. nichts spielt) wird kein Button gerendert
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Timer stops playback (pause) after 5/10/15/30/60 min. Wall-clock
deadline is self-correcting against OS throttling; 1Hz tick drives
the countdown UI. Cancelled in stop() so no timer resurrects a paused
player after teardown.
- sleep_timer.dart: isolated time logic, injectable clock for testing
- audio_handler.dart: sleepTimer field + cancel() in stop()
- now_playing_screen.dart: AppBar button + bottom sheet
- 10 unit tests via mocked clock
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Critical Blockers Fixed
- Remove playbackState.addError() which broke audio_service permanently
- Move error handling from audio_handler to song_list where UI context exists
- Display error in SnackBar instead of destroying playback stream
- Add escapeChar parameter to LIKE queries for correct wildcard handling
- Fixes broken search for titles with _ or % characters
- Add dismissScanError() method to allow closing error banner
- Previously banner couldn't be dismissed (missing notifyListeners())
## Improvements
- Remove dead livePaths list, use companions.isNotEmpty instead
- Add initial progress update to show total files when scan starts
- Add tooltip to now_playing_screen Zurück button
- Add proper error handling in song_list with async/await
## Testing
- Add regression test for CRITICAL-1: empty scan doesn't delete songs
- Add regression test for CRITICAL-2: tombstoned files restore on re-scan
- All 11 tests pass
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
## Critical Fixes
- Fix CRITICAL-1: Empty scans no longer delete entire library
- Use timestamp-based tombstone logic instead of path-based
- Only mark missing if livePaths is not empty
- Fix CRITICAL-2: Re-scanned files now return from deleted state
- Add deleted=false to SongsCompanion.insert in both scanners
## High Priority Fixes
- HIGH-1: Fix SQLite variable limit crash on large libraries
- Replace isNotIn() with timestamp comparison (O(1) not O(n))
- HIGH-2: Fix UI freezing during scan
- Throttle progress updates (every 50 files or end of scan)
- HIGH-3: Add error handling for scan failures
- Wrap scanFolders/scanAndroidMediaStore in try/catch
- Display scanError banner in LibraryScreen
- HIGH-4: Add error handling for playback failures
- Wrap loadPlaylist in try/catch
## Medium Priority Fixes
- MEDIUM-1: Replace ! with ?? to handle unknown ProcessingState
- MEDIUM-2: Escape LIKE wildcards in search queries
- MEDIUM-4: Display StreamBuilder errors instead of treating as empty
- MEDIUM-8: Add tooltips to all IconButtons for accessibility
- Add doc comments to public database APIs
## Testing
- Add regression tests for CRITICAL-1 and CRITICAL-2
- All 9 existing tests pass
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>