Sammelt vier Commits:
- Fix der lokalen Wiedergabe ((0) Source error / Loading interrupted):
file://-URIs wurden als Navidrome-Song-IDs an den Server geschickt.
- Geraete-Abgleich ueber die Melo-Cloud (Download, Upload, Loeschungen,
Favoriten, Verlauf) inkl. Sicherheitsbremse gegen Massenloeschung.
- Serverseitig: hochgeladene Titel landen in der Navidrome-Bibliothek.
- UI: Album ist Kategorie, Reiter Lieder/Kategorie/Kuenstler,
YT-Download mit Kategoriewahl.
295 Tests gruen (1 uebersprungen, siehe BACKLOG.md), analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
Neu: BACKLOG.md. Haelt fest, was bewusst zurueckgestellt wurde, damit es
nicht stillschweigend liegen bleibt.
- song_detail_sheet_test.dart haengt und ist uebersprungen: inklusive der
vier bereits widerlegten Verdachtsmomente (runWithClient erreicht das
Sheet, drift unter testWidgets, einzelne Provider) und dem naechsten
Schritt. Fachlich deckt metadaten_uebernahme_test.dart den Zweck ab.
- Kein Wiederherstellen einzeln geloeschter Titel (Papierkorb am Server
waere denkbar, bewusst nicht gebaut).
Das TODO im Test verweist jetzt auf die Datei.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
Auf Wunsch von Dustin: seine beste Freundin sortiert ihre Sammlung ueber
das Album-Feld. In Melo ist der Album-Titel deshalb ab jetzt die Kategorie
— sie muss nichts neu machen.
1) Album = Kategorie
- kategorienAusTags(): Album-Titel steht VORN in der Kategorienliste (die
erste Kategorie bestimmt das Coverbild), Genres dahinter.
- Beide Scans (android_scan + scan_service) tragen ihn ein; von Hand
gepflegte Kategorien (categoriesEdited) bleiben unberuehrt.
- DB-Schema 9: einmalige Nachruestung bestehender Bibliotheken, damit das
nicht erst beim naechsten vollstaendigen Scan sichtbar wird (der auf
Android nur laeuft, wenn sich die Dateianzahl aendert).
2) Reiter: 'Songs/Kuenstler/Alben' -> 'Lieder/Kategorie/Kuenstler'
- Neu: library/category_list.dart mit groupByCategory(); Lieder ohne
Kategorie sammeln sich am Ende unter "Ohne Kategorie".
- Entfernt: library/album_list.dart, groupByAlbum(), albumArtistLabel() —
mit dem Alben-Reiter tot geworden. Das Album-FELD bleibt erhalten.
3) YouTube-Downloads ohne Original-Album
- Feld "Kategorie (optional)" im YouTube-Bereich, mit Vorschlaegen aus der
Bibliothek und freier Eingabe.
- ordneDownloadEin() verwirft nach dem Scan das Album-Tag (yt-dlp leitet es
aus Kanal/Playlist ab — als Kategorie waere das Unsinn) und setzt
stattdessen die gewaehlte Kategorie. Beides als "von Hand gesetzt"
markiert, damit der naechste Scan es nicht zurueckholt.
- MeloDb.songByPath() und MeloDb.verwirfAlbum() neu.
- CategoryService.alleNamen: Kategorienamen ohne zusaetzliche Abfrage —
ohne das flackerte die Vorschlagsliste und im Widget-Test blieb ein
Aufraeum-Timer von drift haengen.
295 Tests gruen (19 neue, 1 uebersprungen), flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
Auf Wunsch: was vom Handy hochgeladen wird, soll auch in Navidrome
auftauchen. Die Subsonic-API kennt keinen Upload — also erledigt es die
Melo-Cloud selbst.
Server (/home/dustin/scripts/melo_cloud.py, nicht versioniert — Backup als
melo_cloud.py.bak-20260821-084506):
- verknuepfe_navidrome(): Hardlink der Registry-Datei nach
/home/dustin/navidrome/music (Bind-Mount des Containers). Gleiche
Partition -> kein zusaetzlicher Speicher. Fallback: Kopie.
- rescan_navidrome(): Subsonic startScan.view ueber Navidromes
AuthProxyHeader (TrustedSources 127.0.0.1) — kein Passwort im Code.
Nur zur Beschleunigung, der Watcher findet neue Dateien ohnehin.
- upload() verknuepft neue UND bereits bekannte Titel (heilt Bestand).
- handle_delete() entfernt die Datei aus Registry + Navidrome, sobald kein
Konto sie mehr aktiv hat. Der Registry-EINTRAG bleibt stehen: handle_list
haengt die Grabsteine daran (JOIN registry) — ohne ihn erfuehren die
anderen Geraete nie von der Loeschung (Zombie-Song).
- registry_pfad(): gemeinsame Dateisuche ueber alle Audio-Endungen, mit
Rueckfall auf den Navidrome-Ordner.
Nebenbei repariert:
- Alle 325 Cloud-Dateien lagen nur noch im Navidrome-Ordner, REG war leer:
jeder Download antwortete "File missing". Per Hardlink zurueckverknuepft
(scripts/melo_cloud_migriere_registry.py).
- upload() legte jede Datei als ".mp3" ab, auch m4a/flac. Jetzt echte
Endung; _download liefert passenden Content-Type und einen Dateinamen
ohne doppelte Endung.
App:
- MeloCloudService.herunterladen() gibt die geschriebene Datei zurueck und
leitet die Endung aus dem Content-Type ab (endungFuer) — sonst landet
eine M4A als .mp3 auf dem Handy und Android ordnet sie falsch ein.
- loeschBremseGreift(): der Abgleich reicht keine Loeschwelle mehr zum
Server durch (>10 Titel UND >1/3 des Serverbestands). Ohne die Bremse
haette eine nicht eingehaengte Speicherkarte die Sammlung auf allen
Geraeten geloescht.
276 Tests gruen (1 uebersprungen), flutter analyze ohne Befund, plus ein
Durchlauf gegen den echten Server (scripts/test_melo_cloud_navidrome.py):
Upload -> Registry + Navidrome (ein Hardlink) -> Navidrome liest ein ->
Loeschen entfernt beides, Grabstein bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
BUG 1 — Lieder vom Handy waren nicht abspielbar
("Wiedergabe fehlgeschlagen: (0) Source error" / "Loading interrupted")
Wurzel-Ursache: MeloAudioHandler.loadPlaylist hat jeden Warteschlangen-
Eintrag durch NavidromeService.streamAndCacheToLocal(item.id) geschickt,
sobald Zugangsdaten existierten. item.id ist aber NIE eine Navidrome-Song-ID
— lokal ist es file:///storage/..., beim Server die fertige Stream-Adresse.
Der Server bekam also 'file:///...' als Song-ID, antwortete mit einem Fehler,
und diese Fehlerantwort wurde an just_audio weitergereicht (Source error) —
bzw. als .mp3 in den Cache geschrieben, wodurch der Titel dauerhaft kaputt
blieb.
Zweite Ursache: die Schleife lud die GANZE Warteschlange seriell vorab
herunter (30s Timeout je Titel), bevor setAudioSources lief. Bei hunderten
Titeln startete die Wiedergabe deshalb nie; ein zweiter Tipp brach den
laufenden Ladevorgang ab ("Loading interrupted").
Fix:
- Server-Titel tragen ihre ID in MediaItem.extras['navidromeId'] statt sie
aus der Abspiel-Adresse zu raten. Neue reine Funktionen navidromeIdOf,
songIdOf, nutztServerCache, quelleFuer.
- loadPlaylist baut die Quellen ohne Netzzugriff; Caching des laufenden
Titels im Hintergrund (unawaited).
- Resume/Scrobble nur noch mit der jeweils passenden ID (Server bzw. lokal).
- ladeInCache() ersetzt streamAndCacheToLocal(): .part-Datei, Pruefung des
Inhaltstyps (istAudioAntwort), stabiler Cache-Schluessel ueber die
Song-ID statt der Stream-Adresse (die trug Token+Salt und war je Sitzung
anders — der Cache war nie wiederauffindbar), Client wird geschlossen.
BUG 2 — kein Abgleich zwischen Handy und Server
Neu: services/melo_cloud_service.dart + services/sync_service.dart gegen
cloud.baka-net.de (Bearer-JWT ueber BakaAuth). Server-Titel herunterladen
(offline verfuegbar), eigene Dateien hochladen, Loeschungen in beide
Richtungen (Tombstones), Favoriten und Wiedergabe-Verlauf. Automatisch beim
App-Start und bei Rueckkehr in die App (max. alle 15 Min), plus Knopf unter
Einstellungen -> Geraete-Abgleich. Reine Planungsfunktion planeSync().
Bewusst NICHT ueber Navidrome: die Subsonic-API kennt keinen Upload-
Endpunkt. Navidrome bleibt die Streaming-Bibliothek, die Melo-Cloud ist der
gemeinsame Speicher.
DB-Schema 8: songs.cloud_id verbindet Geraet und Server.
Nebenbei behoben (blockierte Build bzw. Tests):
- database.g.dart war veraltet — das Projekt liess sich nicht uebersetzen.
- metadataEdited wurde nirgends gesetzt/beachtet: von Hand korrigierte
Metadaten wurden vom naechsten Scan ueberschrieben. Jetzt in beiden
Scans respektiert; metadatenUebernahme() setzt die Markierung.
- song_detail_sheet_test.dart haengt beim Oeffnen des Modal-Sheets und
blockierte den gesamten Testlauf — vorerst uebersprungen (TODO im Code);
der Zweck wird von metadaten_uebernahme_test.dart abgedeckt.
Enthaelt ausserdem die bis dahin nicht committete Arbeit der Vorsitzung
(Musikerkennung/ACRCloud, MusicBrainz-Metadaten, MediaStore-Datentraeger).
267 Tests gruen (1 uebersprungen), flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FpPu4nuKjKKeX1RpdDeX81
- AutoScan: beim Zurueckkehren in die App wird die Zahl der Musikdateien
mit der Bibliothek verglichen; nur bei Abweichung laeuft ein Scan, und
hoechstens alle 5 Minuten. Gleich viele Loeschungen wie Neuzugaenge
bleiben unbemerkt - dafuer gibt es den Scan von Hand.
- ServerNeuheiten: meldet neu auf Navidrome aufgetauchte Alben im
Online-Tab. Der erste Abruf meldet bewusst nichts, sonst waere die ganze
Sammlung "neu"; er nutzt die ohnehin geladenen Alben statt eines zweiten
Serveraufrufs.
- Neu: MeloDb.countSongs(), countAndroidMediaStoreSongs().
189 Tests gruen, flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Neuer YouTube-Bereich im Online-Tab: Adresse einfuegen, herunterladen, der
Titel landet in Meine Musik.
- BakaAuth: Anmeldung gegen baka-net.de/auth, Token verschluesselt auf dem
Geraet. Der Server meldet Fehler mit HTTP 200 und Text im Rumpf, deshalb
wird der Inhalt geprueft statt nur der Statuscode.
- YtDownloadService: Proxy fragen, MP3 abholen, halbe Dateien aufraeumen.
Kuenstler nur aus "Kuenstler - Lied", sonst leer statt geraten.
- MediaStoreBridge (Kotlin): legt die Datei in Music/Melo ab und meldet sie
dem MediaStore. Noetig, weil der Android-Scan sonst beim naechsten Lauf
alles wieder als verschwunden markiert.
- SubTabs nach shared/ gezogen (jetzt in Meine Musik und Online genutzt).
177 Tests gruen, flutter analyze ohne Befund, Release-APK baut.
Der Weg ueber den echten Proxy ist noch nicht auf dem Geraet erprobt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Untere Navigation folgt jetzt den Referenzbildern:
Meine Musik / Online / Suchen / Favoriten. Der bisherige "Download"-Tab
heisst "Online" und bekommt als Naechstes den YouTube-Downloader.
In "Meine Musik" gibt es Unterreiter (Songs/Kuenstler/Alben) im Pillen-Stil
der Referenz. Sie wechseln nur den Inhalt statt einen Bildschirm zu oeffnen;
die Schnellzugriff-Kacheln "Kuenstler"/"Alben" entfallen dadurch, ebenso die
damit tot gewordenen ArtistsScreen/AlbumsScreen-Huellen.
158 Tests gruen, flutter analyze ohne Befund.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
Sperrbildschirm und ReplayGain
- DB-Schema 6: lyrics + gain_db
- Songtext bevorzugt lokalen Tag, Server nur als Rueckfall; Song-ID-Bug
beim Lyrics-Aufruf behoben
- "Als Naechstes spielen" / "Zur Warteschlange hinzufuegen" ohne Eingriff
in Favoriten oder Wiedergabelisten
- songToMediaItem nimmt eine Cover-Vorgabe: Sperrbildschirm zeigt das
Kategorie-Cover
- ReplayGain: Tags werden gelesen, laute Titel abgesenkt, abschaltbar
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011snPXPafPC5i87o68H14W7
- Meine Musik als Startbildschirm ohne Topbar: Kopfzeile (Einstellungen,
Suche, Musikerkennung), Schnellzugriffe, Shuffle + Sortieren, Liederliste
- Sortierung nach Zuletzt hinzugefuegt / Name (A-Z-#) / Wie oft abgespielt,
auf- und absteigend, pro Liste gespeichert (SortStore)
- Favoriten als eigener Tab mit gleicher Shuffle-/Sortier-Leiste
- Download-Tab uebernimmt den Navidrome-Server-Browser
- DB-Schema 3: playCount, gezaehlt beim Titelstart
- Theme auf #0B0B10 / #c0392b
- Scan-Aktionen aus der entfallenen Topbar jetzt in den Einstellungen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011snPXPafPC5i87o68H14W7
Tiefe Debug-Runde bewiesen: Server, DB-Passwort und App-Token-Logik sind
korrekt (8/8-Repro gegen Live-Server) — abgelehnt wird ein falscher
Passwort-Wert vom Gerät. Damit das künftig sofort sichtbar ist:
- NavidromeService.letzterLoginFehler hält die Server-Meldung fest
- Login-Dialog zeigt sie statt generischem "Login fehlgeschlagen"
- Fehler-Log enthält die Passwort-Länge (nie das Passwort selbst)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JRj3pupcigUi683vy2Wwnz
Systematische Diagnose bewies: App-Code, Server, User 'Baka' und Passwort sind
korrekt (1:1-Nachbau des App-Requests inkl. Salt/Token -> Navidrome status:ok).
Der Fehler lag im auf dem Gerät verwendeten Credential-Wert:
- der alte Build (Ping-Fehlalarm) persistierte ungültige Zugangsdaten,
- verdeckte Tippfehler im obscureText-Passwortfeld blieben unsichtbar.
Fix:
- Passwort-Anzeigen-Umschalter (Auge-Icon) im Login-Dialog.
- URL + Passwort werden getrimmt (Autofill/Tastatur-Leerzeichen).
Ein erfolgreicher Login ueberschreibt die alten gespeicherten Credentials;
der Ping-Fix stellt sicher, dass nur gueltige gespeichert werden.
84/84 Tests gruen, analyze sauber.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tr2UwurtxyJUpWpBSeVmBm
Dokumentiert in PLAN.md den offenen Folgeschritt zu Branch
feature/log-datenschutz-scrubber: Diagnose-Versand auf Opt-in (Einwilligung)
umstellen. Kein Code-Change, nur Planung.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tr2UwurtxyJUpWpBSeVmBm
Diagnose-Logs werden zentral bereinigt, bevor sie an logs.baka-net.de gehen.
Lokaler debugPrint bleibt vollständig.
- Freitext-Scrubber (LoggerService.scrubText) für message + stackTrace:
maskiert user=/pw=/password=/token=/secret=/url= sowie Datei-Pfade,
nackte URLs, Bearer-Token, JWTs und lange Hex-IDs.
- stackTrace auf 8 Zeilen gekürzt (capFrames).
- deviceModel nur noch grobe Plattform (android/ios) statt Hersteller+Modell;
device_info_plus im Logger entfernt.
- Quelle entschärft: settings_screen loggte Navidrome-Login mit Klartext
user=/url= — jetzt nur neutraler Status inkl. pwLeer-Flag.
- Neue Tests: test/logger_service_test.dart (9). 79/79 grün, analyze sauber.
Opt-in/Einwilligung bleibt separater Folgeschritt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tr2UwurtxyJUpWpBSeVmBm
ROOT CAUSE für die fehlenden Logs — und für den fehlschlagenden
Navidrome-Login: Flutter injiziert android.permission.INTERNET nur in
debug/ und profile/AndroidManifest.xml. In main/AndroidManifest.xml
stand sie nicht, das Release-APK hatte damit gar keinen Netzzugriff.
Jede http-Anfrage schlug sofort mit SocketException fehl.
- INTERNET + ACCESS_NETWORK_STATE in main/AndroidManifest.xml ergänzt
- Login-Versuch/-Erfolg/-Fehlschlag im Settings-Dialog explizit geloggt
(URL + User + ob das Passwortfeld leer war; das Passwort selbst nicht)
- Batch-Schwelle 20 -> 5, damit INFO-Logs auch ohne vorherigen Fehler
hochgeladen werden
Nicht geaendert: der Sofort-Upload bei ERROR war bereits implementiert
(error() ruft _uploadIfError -> _uploadLogs direkt auf) und war nicht
die Ursache.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
Bisher stand im Log nur die nackte Exception-Message ("Connection timeout")
— ohne URL und ohne Fehlertyp war die Ursache nicht eingrenzbar.
Neu im Fehler-Log:
- Fehlerart klassifiziert: TIMEOUT / SOCKET / TLS-HANDSHAKE / HTTP / FORMAT
- SocketException zusätzlich mit osError-Message, errorCode, Adresse und
Adress-Typ (IPv4 vs IPv6 — zeigt ein IPv6-Problem direkt an)
- Server-URL sowie geparstes scheme/host/port (falsche URL, fehlendes
https, DNS-Auflösung erkennbar)
- e.runtimeType + Message
Bewusst NICHT geloggt: die volle Ping-URI — sie enthält Auth-Token und
Salt als Query-Parameter, die nicht auf dem Log-Server landen sollen.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
1. 🔗 AUTO-URL: Server-URL \"https://musik.baka-net.de\" vorausgefüllt (editierbar)
2. 👤 USER-BUTTONS: Baka/Tinker/Dustin als Button-Auswahl (statt Text-Input)
3. 🔐 PASSWORT-ONLY: Dialog zeigt nur Passwort-Feld + User-Auswahl (wie beim Register!)
4. 💡 GAST-MODUS: Info-Text erklärt lokale Nutzung ohne Server-Konto
→ App funktioniert auch komplett offline mit lokaler Musikbibliothek!
UX-Verbesserungen:
- Weniger zu tippen (URL + User vorgegeben)
- Schneller Login für Baka/Tinker/Dustin
- Klare Fallback-Option für lokale Nutzung
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- Größeres MiniPlayer-Widget auf allen Non-Player-Screens (80px statt 60px)
- Größeres Album-Cover (64px statt 44px) mit besserer Sichtbarkeit
- Skip-Buttons (⏮️ Vorheriges + ⏭️ Nächstes) für schnelle Navigation
- Progress-Bar zeigt Wiedergabe-Position in Echtzeit
- Besseres Styling + Schrift-Gewicht für Titel
- Funktioniert wie Home-Widget: zeigt überall was gerade spielt
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- androidShowNotificationBadge aktiviert für bessere Notification-Sichtbarkeit
- Notification bleibt aktiv auch wenn Musik pausiert (AndroidService-Konfiguration)
- Play/Pause/Skip-Controls auf Lock Screen + Notification (AudioService-Standard)
- Media-Session Integration für Android SystemUI Controls
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- getPlaylists() + getPlaylistSongs() APIs für Subsonic-Playlist-Laden
- syncPlaylistsFromServer() in PlaylistService importiert alle Server-Playlisten als lokale
- Benennung mit 🌐 Präfix um Server-Playlisten zu kennzeichnen
- Settings-Button lädt alle Playlisten mit ihren Songs vom Server
- Zeigt Anzahl importierter Playlisten mit SnackBar
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- 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
- Toggle-Buttons in Navidrome-Tab: "📀 Alben" ↔ "🎤 Künstler"
- Artist-Liste zeigt Namen pro Künstler, Tap lädt alle Songs des Künstlers
- _playArtist() Methode lädt Künstler-Songs via getArtistSongs() API
- Albums/Artists sind Lazy-Loaded (nur beim Toggle laden)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XJzQjUtnvYUtHdnTs3iCru
- Neuer 'Cache-Speicher' ListTile in Musikserver-Sektion
- Zeigt Cache-Größe in MB/KB/B
- Clear-Button: Löscht alle gecachten Lieder
- _formatBytes() für Größen-Anzeige
- _clearCache() mit SnackBar-Feedback
Nutzer kann jetzt Cache-Status sehen + leeren!
Tests: 70/70 ✅
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 ✅