📊 Feature: History-Sync vom Navidrome-Server (Phase 2, Part 5/X)

- 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
This commit is contained in:
Hermes (Server)
2026-08-19 19:26:41 +02:00
co-authored by Claude Haiku 4.5
parent 5202b130c5
commit f93b2d35da
2 changed files with 70 additions and 6 deletions
+27 -6
View File
@@ -38,10 +38,15 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
_player.playbackEventStream.map(_transformEvent).pipe(playbackState);
// Wiedergabeposition alle ~5s persistieren, solange aktiv abgespielt wird.
// Synche auch zum Server wenn verbunden.
_positionRecordTimer = Timer.periodic(const Duration(seconds: 5), (_) {
final songId = mediaItem.value?.extras?['songId'] as String?;
if (_player.playing && songId != null) {
db.recordPlayback(songId, _player.position.inMilliseconds);
final posMs = _player.position.inMilliseconds;
db.recordPlayback(songId, posMs);
if (_nav.istVerbunden) {
_nav.scrobble(songId, (posMs ~/ 1000).toInt());
}
}
});
@@ -86,12 +91,28 @@ class MeloAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
await _player.setAudioSources(sources, initialIndex: startIndex);
// Bei bekannter letzter Position an dieser Stelle fortsetzen,
// statt immer von vorne zu beginnen.
final songId = items[startIndex].extras?['songId'] as String?;
// statt immer von vorne zu beginnen. Priorisiert Server-Position über lokal.
final item = items[startIndex];
final songId = item.extras?['songId'] as String?;
if (songId != null) {
final lastMs = await db.lastPosition(songId);
if (lastMs != null && shouldResumeAt(lastMs, items[startIndex].duration)) {
await _player.seek(Duration(milliseconds: lastMs), index: startIndex);
int? resumeMs;
if (_nav.istVerbunden) {
final serverMs = await _nav.getBookmark(songId);
if (serverMs != null && shouldResumeAt(serverMs, item.duration)) {
resumeMs = serverMs;
}
}
if (resumeMs == null) {
final localMs = await db.lastPosition(songId);
if (localMs != null && shouldResumeAt(localMs, item.duration)) {
resumeMs = localMs;
}
}
if (resumeMs != null) {
await _player.seek(Duration(milliseconds: resumeMs), index: startIndex);
}
}