navidrome-mcp 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/services/playback/mpv-process.d.ts.map +1 -1
- package/dist/services/playback/mpv-process.js +8 -0
- package/dist/services/playback/mpv-process.js.map +1 -1
- package/dist/services/playback/scrobble-tracker.d.ts +98 -0
- package/dist/services/playback/scrobble-tracker.d.ts.map +1 -0
- package/dist/services/playback/scrobble-tracker.js +284 -0
- package/dist/services/playback/scrobble-tracker.js.map +1 -0
- package/dist/tools/handlers/registry.d.ts.map +1 -1
- package/dist/tools/handlers/registry.js +10 -0
- package/dist/tools/handlers/registry.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Browse and search songs, albums, artists, genres, and tags with rich filtering:
|
|
|
24
24
|
|
|
25
25
|
Audio plays through your machine's speakers, no browser or Navidrome web UI needed. Search and play in a single step: *"play 5 random starred albums"*, *"queue everything I've starred from the 90s sorted by year"*, *"add 10 random rock songs to whatever's already playing, shuffled"*. Three shuffle modes for albums (keep order, randomize album order, fully interleave tracks).
|
|
26
26
|
|
|
27
|
-
The live queue is actively manipulable: move a track to the front and it starts playing, shuffle and the new top plays, remove the current track and the next one auto-advances. Saved Navidrome radio stations (Icecast, SHOUTcast, etc.) stream through mpv with ICY metadata flowing through so you can see what the station is currently playing. mpv is lazy-spawned on first use, survives MCP client restarts via a per-user socket, and works on Linux, macOS, and Windows 11.
|
|
27
|
+
The live queue is actively manipulable: move a track to the front and it starts playing, shuffle and the new top plays, remove the current track and the next one auto-advances. Saved Navidrome radio stations (Icecast, SHOUTcast, etc.) stream through mpv with ICY metadata flowing through so you can see what the station is currently playing. Plays scrobble back to Navidrome so your recently-played and play counts stay in sync with what you actually listen to through mpv. mpv is lazy-spawned on first use, survives MCP client restarts via a per-user socket, and works on Linux, macOS, and Windows 11.
|
|
28
28
|
|
|
29
29
|
This design is built for conversational control and pairs cleanly with voice transports (Whisper STT + TTS) to build a hands-free music device on a Raspberry Pi or always-on machine.
|
|
30
30
|
|
|
@@ -380,7 +380,6 @@ Audio plays through the host's speakers. mpv is lazy-spawned on first use and su
|
|
|
380
380
|
- **No audio without mpv.** When mpv isn't installed the library and saved-queue tools still work, but audio playback isn't available; use the Navidrome web UI or a Subsonic client.
|
|
381
381
|
- **Recently-played has no timestamps.** Navidrome exposes play counts and completion status, not last-played times.
|
|
382
382
|
- **Saved queue ≠ live queue.** The `*_saved_queue` tools operate on Navidrome's server-side advisory queue (web UI sync). The `*_play_queue` tools operate on the local mpv playlist. They are independent.
|
|
383
|
-
- **Scrobbling for local playback isn't wired up yet.** Listens through mpv don't currently feed back into Navidrome's play counts. Planned.
|
|
384
383
|
|
|
385
384
|
## Development
|
|
386
385
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mpv-process.d.ts","sourceRoot":"","sources":["../../../src/services/playback/mpv-process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIxE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAkB1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAyB/C;
|
|
1
|
+
{"version":3,"file":"mpv-process.d.ts","sourceRoot":"","sources":["../../../src/services/playback/mpv-process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIxE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAkB1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAyB/C;AA8CD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CA6B1E"}
|
|
@@ -116,6 +116,14 @@ function buildMpvArgs(ipcPath) {
|
|
|
116
116
|
'--load-scripts=no',
|
|
117
117
|
'--gapless-audio=weak',
|
|
118
118
|
'--prefetch-playlist=yes',
|
|
119
|
+
// Network cache. mpv defaults (--cache-secs=1.0, --demuxer-readahead-secs=1.0)
|
|
120
|
+
// are tuned for local files; on a streamed HTTP source they leave a ~1s budget
|
|
121
|
+
// that any network jitter at track-change time blows through, causing the
|
|
122
|
+
// decoder to underrun and stop after a second of playback. Audio bitrates
|
|
123
|
+
// are tiny (30s of FLAC ≈ 6MB, MP3 ≈ 1.2MB), so a generous prebuffer is free.
|
|
124
|
+
'--cache=yes',
|
|
125
|
+
'--cache-secs=30',
|
|
126
|
+
'--demuxer-readahead-secs=20',
|
|
119
127
|
`--input-ipc-server=${ipcPath}`,
|
|
120
128
|
'--volume=80',
|
|
121
129
|
'--audio-display=no',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mpv-process.js","sourceRoot":"","sources":["../../../src/services/playback/mpv-process.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mpv-process.js","sourceRoot":"","sources":["../../../src/services/playback/mpv-process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navidrome MCP Server - Scrobble Tracker
|
|
3
|
+
* Copyright (C) 2025
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
7
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import type { StateChangeEvent } from './playback-engine.js';
|
|
19
|
+
/**
|
|
20
|
+
* Subset of the playback engine the tracker depends on. Defined here so
|
|
21
|
+
* tests can pass a minimal fake without constructing the full engine.
|
|
22
|
+
*/
|
|
23
|
+
export interface ScrobbleEngine {
|
|
24
|
+
onStateChange(handler: (event: StateChangeEvent) => void): () => void;
|
|
25
|
+
getPlaylist(): Promise<Array<{
|
|
26
|
+
index: number;
|
|
27
|
+
songId: string | null;
|
|
28
|
+
duration?: number;
|
|
29
|
+
}>>;
|
|
30
|
+
getCachedProperty(name: string): unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Subset of the Navidrome client the tracker depends on.
|
|
34
|
+
*/
|
|
35
|
+
export interface ScrobbleClient {
|
|
36
|
+
subsonicRequest(endpoint: string, params?: Record<string, string>, options?: {
|
|
37
|
+
method?: 'GET' | 'POST';
|
|
38
|
+
}): Promise<unknown>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Watches the playback engine and submits Subsonic `/scrobble` calls to
|
|
42
|
+
* Navidrome, mirroring the web UI / Last.fm rules:
|
|
43
|
+
*
|
|
44
|
+
* 1. On track start → `submission=false` (now-playing notification).
|
|
45
|
+
* 2. After listening past half the duration OR 4 minutes (whichever first),
|
|
46
|
+
* once per play → `submission=true&time=<startedAt-ms>`.
|
|
47
|
+
*
|
|
48
|
+
* Tracks shorter than 30s, and radio streams (no `songId`), never scrobble.
|
|
49
|
+
*
|
|
50
|
+
* The tracker is fire-and-forget: every Subsonic call is dispatched async
|
|
51
|
+
* with errors logged at warn level. It never blocks playback or surfaces
|
|
52
|
+
* errors to tool callers.
|
|
53
|
+
*
|
|
54
|
+
* Attach semantics: mpv is intentionally configured to outlive the MCP
|
|
55
|
+
* process, so when MCP starts the engine often attaches to an mpv that's
|
|
56
|
+
* already mid-track. The engine's `installObservers` triggers mpv to emit
|
|
57
|
+
* immediate "current value" change events for every observed property,
|
|
58
|
+
* which look identical to real transitions. The first `playlist-pos`
|
|
59
|
+
* event after attach is therefore treated as initial state — it hydrates
|
|
60
|
+
* a sentinel but does NOT trigger now-playing or scrobble tracking. Only
|
|
61
|
+
* subsequent events that actually change the value are real transitions.
|
|
62
|
+
*/
|
|
63
|
+
export declare class ScrobbleTracker {
|
|
64
|
+
private readonly client;
|
|
65
|
+
private readonly engine;
|
|
66
|
+
private unsubscribe;
|
|
67
|
+
private currentSongId;
|
|
68
|
+
private currentDuration;
|
|
69
|
+
private startedAtMs;
|
|
70
|
+
private submitted;
|
|
71
|
+
private lastTimePos;
|
|
72
|
+
private lastPlaylistPos;
|
|
73
|
+
private generation;
|
|
74
|
+
constructor(client: ScrobbleClient, engine: ScrobbleEngine);
|
|
75
|
+
/**
|
|
76
|
+
* Subscribe to engine state changes. Idempotent — a second call while
|
|
77
|
+
* already attached is a no-op.
|
|
78
|
+
*/
|
|
79
|
+
attach(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Unsubscribe and reset state. Used by tests; in production the tracker's
|
|
82
|
+
* lifetime is the process lifetime.
|
|
83
|
+
*/
|
|
84
|
+
detach(): void;
|
|
85
|
+
private handleEvent;
|
|
86
|
+
private onPlaylistPos;
|
|
87
|
+
private onQueueMutation;
|
|
88
|
+
private maybeRehydrateAfterQueue;
|
|
89
|
+
private hydrateAndStart;
|
|
90
|
+
private startTrackingTrack;
|
|
91
|
+
private onDuration;
|
|
92
|
+
private onTimePos;
|
|
93
|
+
private maybeSubmit;
|
|
94
|
+
private reset;
|
|
95
|
+
private sendNowPlaying;
|
|
96
|
+
private sendSubmission;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=scrobble-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrobble-tracker.d.ts","sourceRoot":"","sources":["../../../src/services/playback/scrobble-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAQ7D;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACtE,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC3F,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GACpC,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IACxC,OAAO,CAAC,WAAW,CAA6B;IAEhD,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,SAAS,CAAS;IAM1B,OAAO,CAAC,WAAW,CAAuB;IAO1C,OAAO,CAAC,eAAe,CAAwC;IAM/D,OAAO,CAAC,UAAU,CAAK;gBAEX,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc;IAK1D;;;OAGG;IACH,MAAM,IAAI,IAAI;IAOd;;;OAGG;IACH,MAAM,IAAI,IAAI;IAWd,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,eAAe;YAaT,wBAAwB;YA2BxB,eAAe;IAgB7B,OAAO,CAAC,kBAAkB;IAoB1B,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,cAAc;CAcvB"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Navidrome MCP Server - Scrobble Tracker
|
|
3
|
+
* Copyright (C) 2025
|
|
4
|
+
*
|
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
|
6
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
7
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
* (at your option) any later version.
|
|
9
|
+
*
|
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
* GNU Affero General Public License for more details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { logger } from '../../utils/logger.js';
|
|
19
|
+
// Last.fm scrobble rules: track must be at least 30s long, and counts as
|
|
20
|
+
// played after the user has listened to half the duration OR 4 minutes,
|
|
21
|
+
// whichever comes first.
|
|
22
|
+
const MIN_DURATION_SECONDS = 30;
|
|
23
|
+
const MAX_THRESHOLD_SECONDS = 240;
|
|
24
|
+
/**
|
|
25
|
+
* Watches the playback engine and submits Subsonic `/scrobble` calls to
|
|
26
|
+
* Navidrome, mirroring the web UI / Last.fm rules:
|
|
27
|
+
*
|
|
28
|
+
* 1. On track start → `submission=false` (now-playing notification).
|
|
29
|
+
* 2. After listening past half the duration OR 4 minutes (whichever first),
|
|
30
|
+
* once per play → `submission=true&time=<startedAt-ms>`.
|
|
31
|
+
*
|
|
32
|
+
* Tracks shorter than 30s, and radio streams (no `songId`), never scrobble.
|
|
33
|
+
*
|
|
34
|
+
* The tracker is fire-and-forget: every Subsonic call is dispatched async
|
|
35
|
+
* with errors logged at warn level. It never blocks playback or surfaces
|
|
36
|
+
* errors to tool callers.
|
|
37
|
+
*
|
|
38
|
+
* Attach semantics: mpv is intentionally configured to outlive the MCP
|
|
39
|
+
* process, so when MCP starts the engine often attaches to an mpv that's
|
|
40
|
+
* already mid-track. The engine's `installObservers` triggers mpv to emit
|
|
41
|
+
* immediate "current value" change events for every observed property,
|
|
42
|
+
* which look identical to real transitions. The first `playlist-pos`
|
|
43
|
+
* event after attach is therefore treated as initial state — it hydrates
|
|
44
|
+
* a sentinel but does NOT trigger now-playing or scrobble tracking. Only
|
|
45
|
+
* subsequent events that actually change the value are real transitions.
|
|
46
|
+
*/
|
|
47
|
+
export class ScrobbleTracker {
|
|
48
|
+
client;
|
|
49
|
+
engine;
|
|
50
|
+
unsubscribe = null;
|
|
51
|
+
currentSongId = null;
|
|
52
|
+
currentDuration = null;
|
|
53
|
+
startedAtMs = null;
|
|
54
|
+
submitted = false;
|
|
55
|
+
// Latest mpv time-pos value observed for the current play, in seconds.
|
|
56
|
+
// Tracked here (rather than read from the engine cache) so a stale
|
|
57
|
+
// time-pos belonging to the previous track can't leak into the new
|
|
58
|
+
// play's threshold check during the brief window between a playlist-pos
|
|
59
|
+
// change and the first time-pos event for the new file.
|
|
60
|
+
lastTimePos = null;
|
|
61
|
+
// Sentinel 'unknown' until the first playlist-pos event after attach.
|
|
62
|
+
// That first event is mpv's observe-emitted snapshot of current state —
|
|
63
|
+
// it must NOT trigger hydration, because the engine may have just
|
|
64
|
+
// attached to an mpv already mid-track from a previous MCP session.
|
|
65
|
+
// Subsequent events that change this value are real transitions.
|
|
66
|
+
lastPlaylistPos = 'unknown';
|
|
67
|
+
// Bumped on every real transition (onPlaylistPos / onQueueMutation).
|
|
68
|
+
// hydrateAndStart captures gen at call time and bails after each await
|
|
69
|
+
// if it has advanced — guards against out-of-order playlist reads
|
|
70
|
+
// under fast skips. Persists across reset() (attach-lifetime state).
|
|
71
|
+
generation = 0;
|
|
72
|
+
constructor(client, engine) {
|
|
73
|
+
this.client = client;
|
|
74
|
+
this.engine = engine;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to engine state changes. Idempotent — a second call while
|
|
78
|
+
* already attached is a no-op.
|
|
79
|
+
*/
|
|
80
|
+
attach() {
|
|
81
|
+
if (this.unsubscribe !== null)
|
|
82
|
+
return;
|
|
83
|
+
this.unsubscribe = this.engine.onStateChange((event) => {
|
|
84
|
+
this.handleEvent(event);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Unsubscribe and reset state. Used by tests; in production the tracker's
|
|
89
|
+
* lifetime is the process lifetime.
|
|
90
|
+
*/
|
|
91
|
+
detach() {
|
|
92
|
+
if (this.unsubscribe !== null) {
|
|
93
|
+
this.unsubscribe();
|
|
94
|
+
this.unsubscribe = null;
|
|
95
|
+
}
|
|
96
|
+
this.reset();
|
|
97
|
+
// Reset attach-lifetime state too so detach-then-reattach starts clean.
|
|
98
|
+
this.lastPlaylistPos = 'unknown';
|
|
99
|
+
this.generation = 0;
|
|
100
|
+
}
|
|
101
|
+
handleEvent(event) {
|
|
102
|
+
if (event.kind === 'queue') {
|
|
103
|
+
this.onQueueMutation();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (event.kind !== 'property')
|
|
107
|
+
return;
|
|
108
|
+
switch (event.name) {
|
|
109
|
+
case 'playlist-pos':
|
|
110
|
+
this.onPlaylistPos(event.data);
|
|
111
|
+
return;
|
|
112
|
+
case 'duration':
|
|
113
|
+
this.onDuration(event.data);
|
|
114
|
+
return;
|
|
115
|
+
case 'time-pos':
|
|
116
|
+
this.onTimePos(event.data);
|
|
117
|
+
return;
|
|
118
|
+
default:
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
onPlaylistPos(data) {
|
|
123
|
+
const next = typeof data === 'number' ? data : null;
|
|
124
|
+
const prev = this.lastPlaylistPos;
|
|
125
|
+
this.lastPlaylistPos = next;
|
|
126
|
+
// First event since attach is mpv's observe-emitted current state —
|
|
127
|
+
// hydrate the sentinel but do nothing else.
|
|
128
|
+
if (prev === 'unknown')
|
|
129
|
+
return;
|
|
130
|
+
// mpv re-emit at the same value (or jumpToPlaylistEntry to current
|
|
131
|
+
// index): not a real track change. Last.fm wouldn't accept a
|
|
132
|
+
// re-scrobble within minutes anyway, so silently ignore.
|
|
133
|
+
if (prev === next)
|
|
134
|
+
return;
|
|
135
|
+
this.reset();
|
|
136
|
+
if (next === null || next < 0)
|
|
137
|
+
return;
|
|
138
|
+
void this.hydrateAndStart(next, ++this.generation);
|
|
139
|
+
}
|
|
140
|
+
onQueueMutation() {
|
|
141
|
+
// A queue-mutating engine operation just completed (enqueue / clear /
|
|
142
|
+
// shuffle / move / remove / enqueueRadio). mpv does not emit a
|
|
143
|
+
// playlist-pos change event when the index stays the same (e.g.
|
|
144
|
+
// enqueue('replace') while at index 0 — the most common case for
|
|
145
|
+
// play_songs called on an attached mpv that's already playing).
|
|
146
|
+
// Force a re-hydration; the songId comparison inside the async path
|
|
147
|
+
// makes this a no-op when the current track wasn't actually displaced
|
|
148
|
+
// (shuffle that left index 0 alone), and the generation token makes
|
|
149
|
+
// concurrent transitions safe.
|
|
150
|
+
void this.maybeRehydrateAfterQueue();
|
|
151
|
+
}
|
|
152
|
+
async maybeRehydrateAfterQueue() {
|
|
153
|
+
const cachedPos = this.engine.getCachedProperty('playlist-pos');
|
|
154
|
+
if (typeof cachedPos !== 'number' || cachedPos < 0) {
|
|
155
|
+
this.reset();
|
|
156
|
+
this.lastPlaylistPos = typeof cachedPos === 'number' ? cachedPos : null;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
let entry;
|
|
160
|
+
try {
|
|
161
|
+
const playlist = await this.engine.getPlaylist();
|
|
162
|
+
entry = playlist.find((e) => e.index === cachedPos);
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
logger.warn(`scrobble: failed to read playlist after queue mutation: ${String(err)}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (entry === undefined)
|
|
169
|
+
return;
|
|
170
|
+
// Already tracking this exact song — either a concurrent property-change
|
|
171
|
+
// handler hydrated, or the queue mutation didn't displace the current
|
|
172
|
+
// track. No-op in either case.
|
|
173
|
+
if (entry.songId !== null && entry.songId === this.currentSongId)
|
|
174
|
+
return;
|
|
175
|
+
this.reset();
|
|
176
|
+
this.lastPlaylistPos = cachedPos;
|
|
177
|
+
this.generation++;
|
|
178
|
+
if (entry.songId === null)
|
|
179
|
+
return; // radio
|
|
180
|
+
this.startTrackingTrack(entry.songId, entry.duration);
|
|
181
|
+
}
|
|
182
|
+
async hydrateAndStart(pos, gen) {
|
|
183
|
+
let entry;
|
|
184
|
+
try {
|
|
185
|
+
const playlist = await this.engine.getPlaylist();
|
|
186
|
+
if (gen !== this.generation)
|
|
187
|
+
return; // superseded by a newer transition
|
|
188
|
+
entry = playlist.find((e) => e.index === pos);
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
logger.warn(`scrobble: failed to read playlist for pos=${pos}: ${String(err)}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (entry === undefined)
|
|
195
|
+
return;
|
|
196
|
+
if (entry.songId === null)
|
|
197
|
+
return; // radio stream
|
|
198
|
+
if (gen !== this.generation)
|
|
199
|
+
return; // defensive after the find
|
|
200
|
+
this.startTrackingTrack(entry.songId, entry.duration);
|
|
201
|
+
}
|
|
202
|
+
startTrackingTrack(songId, duration) {
|
|
203
|
+
this.currentSongId = songId;
|
|
204
|
+
this.startedAtMs = Date.now();
|
|
205
|
+
this.submitted = false;
|
|
206
|
+
this.lastTimePos = null;
|
|
207
|
+
this.currentDuration = null;
|
|
208
|
+
if (typeof duration === 'number' && duration > 0) {
|
|
209
|
+
this.currentDuration = duration;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Fall back to mpv's cached duration if the playlist entry didn't
|
|
213
|
+
// carry one (e.g. post-MCP-restart attach where the metadata cache
|
|
214
|
+
// is empty).
|
|
215
|
+
const cachedDuration = this.engine.getCachedProperty('duration');
|
|
216
|
+
if (typeof cachedDuration === 'number' && cachedDuration > 0) {
|
|
217
|
+
this.currentDuration = cachedDuration;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
this.sendNowPlaying(songId);
|
|
221
|
+
}
|
|
222
|
+
onDuration(data) {
|
|
223
|
+
if (typeof data !== 'number' || data <= 0)
|
|
224
|
+
return;
|
|
225
|
+
this.currentDuration = data;
|
|
226
|
+
// Re-evaluate threshold against the last time-pos we observed for
|
|
227
|
+
// this play. Handles the (rare) case where duration arrives after a
|
|
228
|
+
// qualifying time-pos tick.
|
|
229
|
+
this.maybeSubmit(this.lastTimePos);
|
|
230
|
+
}
|
|
231
|
+
onTimePos(data) {
|
|
232
|
+
if (typeof data === 'number')
|
|
233
|
+
this.lastTimePos = data;
|
|
234
|
+
this.maybeSubmit(data);
|
|
235
|
+
}
|
|
236
|
+
maybeSubmit(timePos) {
|
|
237
|
+
if (this.submitted)
|
|
238
|
+
return;
|
|
239
|
+
if (this.currentSongId === null)
|
|
240
|
+
return;
|
|
241
|
+
if (this.startedAtMs === null)
|
|
242
|
+
return;
|
|
243
|
+
if (this.currentDuration === null || this.currentDuration < MIN_DURATION_SECONDS)
|
|
244
|
+
return;
|
|
245
|
+
if (typeof timePos !== 'number')
|
|
246
|
+
return;
|
|
247
|
+
if (timePos < this.currentDuration / 2 && timePos < MAX_THRESHOLD_SECONDS)
|
|
248
|
+
return;
|
|
249
|
+
// Set the flag BEFORE dispatching to prevent re-entry from a subsequent
|
|
250
|
+
// time-pos tick before the async call resolves.
|
|
251
|
+
this.submitted = true;
|
|
252
|
+
this.sendSubmission(this.currentSongId, this.startedAtMs);
|
|
253
|
+
}
|
|
254
|
+
reset() {
|
|
255
|
+
this.currentSongId = null;
|
|
256
|
+
this.currentDuration = null;
|
|
257
|
+
this.startedAtMs = null;
|
|
258
|
+
this.submitted = false;
|
|
259
|
+
this.lastTimePos = null;
|
|
260
|
+
// lastPlaylistPos and generation are intentionally preserved across
|
|
261
|
+
// reset() — they track attach-lifetime state, not per-play state.
|
|
262
|
+
}
|
|
263
|
+
sendNowPlaying(songId) {
|
|
264
|
+
this.client
|
|
265
|
+
.subsonicRequest('/scrobble', { id: songId, submission: 'false' }, { method: 'POST' })
|
|
266
|
+
.then(() => {
|
|
267
|
+
logger.debug(`scrobble: now-playing sent for ${songId}`);
|
|
268
|
+
})
|
|
269
|
+
.catch((err) => {
|
|
270
|
+
logger.warn(`scrobble: now-playing failed for ${songId}: ${String(err)}`);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
sendSubmission(songId, startedAtMs) {
|
|
274
|
+
this.client
|
|
275
|
+
.subsonicRequest('/scrobble', { id: songId, submission: 'true', time: String(startedAtMs) }, { method: 'POST' })
|
|
276
|
+
.then(() => {
|
|
277
|
+
logger.debug(`scrobble: submission sent for ${songId} (started ${startedAtMs})`);
|
|
278
|
+
})
|
|
279
|
+
.catch((err) => {
|
|
280
|
+
logger.warn(`scrobble: submission failed for ${songId}: ${String(err)}`);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=scrobble-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrobble-tracker.js","sourceRoot":"","sources":["../../../src/services/playback/scrobble-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/tools/handlers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAG/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAI9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwC;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IAEvC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAK5D,WAAW,IAAI,IAAI,EAAE;IAIf,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CASpE;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/tools/handlers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAG/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAI9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAID,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwC;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IAEvC,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI;IAK5D,WAAW,IAAI,IAAI,EAAE;IAIf,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CASpE;AA8BD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAqD3F"}
|
|
@@ -62,6 +62,7 @@ import { createLyricsToolCategory } from './lyrics-handlers.js';
|
|
|
62
62
|
import { createTagsToolCategory } from './tag-handlers.js';
|
|
63
63
|
import { createPlaybackToolCategory } from './playback-handlers.js';
|
|
64
64
|
import { playbackEngine } from '../../services/playback/playback-engine.js';
|
|
65
|
+
import { ScrobbleTracker } from '../../services/playback/scrobble-tracker.js';
|
|
65
66
|
// Main registration function
|
|
66
67
|
export function registerTools(server, client, config) {
|
|
67
68
|
const registry = new ToolRegistry();
|
|
@@ -89,6 +90,15 @@ export function registerTools(server, client, config) {
|
|
|
89
90
|
// Configure the singleton engine with the loaded config so tools can
|
|
90
91
|
// lazy-spawn mpv on first invocation.
|
|
91
92
|
playbackEngine.configure(config);
|
|
93
|
+
// Auto-scrobble plays to Navidrome (Last.fm rules: now-playing on start,
|
|
94
|
+
// submission past 50% of duration or 4 min, whichever first; ≥30s
|
|
95
|
+
// tracks only). The tracker is constructed once and attached for the
|
|
96
|
+
// rest of the process lifetime — there is no explicit shutdown. On
|
|
97
|
+
// SIGINT/SIGTERM the engine closes its IPC socket (mpv keeps running,
|
|
98
|
+
// detached) and the tracker is torn down with the process; any
|
|
99
|
+
// in-flight /scrobble request is abandoned, which is acceptable per
|
|
100
|
+
// Last.fm best-effort semantics.
|
|
101
|
+
new ScrobbleTracker(client, playbackEngine).attach();
|
|
92
102
|
registry.register('playback', createPlaybackToolCategory(client, config));
|
|
93
103
|
}
|
|
94
104
|
// Register MCP handlers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/tools/handlers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAInG,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAShE,mCAAmC;AACnC,MAAM,OAAO,YAAY;IACN,UAAU,GAA8B,IAAI,GAAG,EAAE,CAAC;IAClD,QAAQ,GAAW,EAAE,CAAC;IAEvC,QAAQ,CAAC,YAAoB,EAAE,QAAsB;QACnD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,WAAW;QACT,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,IAAY,EAAE,IAAa;QAC9C,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YACvD,IAAI,IAAI,EAAE,CAAC;gBACT,OAAO,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;CACF;AAED,uDAAuD;AACvD,SAAS,kBAAkB,CAAC,MAAe;IACzC,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;aACtC;SACF;KACF,CAAC;AACJ,CAAC;AAED,oCAAoC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC;AACnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,4CAA4C,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/tools/handlers/registry.ts"],"names":[],"mappings":"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"}
|