Add synced listening history

This commit is contained in:
Ultradesu
2026-07-27 23:16:03 +01:00
parent 845df4e031
commit 232c171aac
13 changed files with 498 additions and 236 deletions
+6 -1
View File
@@ -821,7 +821,12 @@
</template>
</button>
<div class="track-info">
<div class="track-title" x-text="item.track?.title || item.track_title"></div>
<div class="track-title">
<span x-text="item.track?.title || item.track_title"></span>
<span class="history-device-badge"
:title="item.device_id"
x-text="item.device_name"></span>
</div>
<div class="track-artists-inline">
<template x-for="(artist, artistIdx) in $store.library.trackArtistLinks(item.track)" :key="artist.label + '-' + artist.id + '-' + artistIdx">
<span>
+9 -3
View File
@@ -726,6 +726,7 @@ document.addEventListener('alpine:init', () => {
_historyRecorded: false,
_nowPlayingSent: false,
_playbackStartedAt: null,
_listenId: null,
_listenedSeconds: 0,
_lastAudioTime: 0,
_localSourceTrackId: null,
@@ -754,7 +755,7 @@ document.addEventListener('alpine:init', () => {
audio.addEventListener('ended', () => {
this._trackListenedDelta();
this._recordHistory(true);
this._recordHistory('finished');
if (Alpine.store('devices')?.shouldPlayJamLocally()) {
this.isPlaying = false;
return;
@@ -1437,21 +1438,24 @@ document.addEventListener('alpine:init', () => {
} catch {}
},
_recordHistory(completed) {
_recordHistory(endedReason) {
if (this._historyRecorded || !this.currentTrack) return;
if (!Number.isInteger(Number(this.currentTrack.id)) || this.currentTrack.federated_cache) {
return;
}
this._historyRecorded = true;
const completed = endedReason === 'finished';
const listenedSeconds = this._historyListenedSeconds(completed);
fetch('/api/player/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
track_id: this.currentTrack.id,
listen_id: this._listenId,
started_at: this._playbackStartedAt,
duration_listened: listenedSeconds,
completed: completed,
ended_reason: endedReason,
}),
}).catch(() => {});
},
@@ -1464,13 +1468,15 @@ document.addEventListener('alpine:init', () => {
const listened = Math.floor(Number(this._listenedSeconds || 0));
const threshold = Math.ceil(duration / 2);
if (threshold <= 0 || listened < threshold) return false;
this._recordHistory(true);
this._recordHistory('unknown');
return true;
},
_resetPlaybackTracking() {
this._nowPlayingSent = false;
this._playbackStartedAt = null;
this._listenId = (globalThis.crypto?.randomUUID?.()
|| `${Date.now()}-${Math.random().toString(16).slice(2)}`);
this._listenedSeconds = 0;
this._lastAudioTime = 0;
},
+12
View File
@@ -3701,6 +3701,18 @@ button.user-stat:hover {
}
.history-row:last-child { border-bottom: 0; }
.history-device-badge {
display: inline-block;
margin-left: .45rem;
padding: .08rem .38rem;
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
border-radius: 999px;
color: var(--text-muted);
font-size: .68rem;
font-weight: 500;
line-height: 1.2;
vertical-align: .12rem;
}
.history-cover {
width: 40px;