From 6062c3585c29b2c0911d4d16bde7092ab05d2568 Mon Sep 17 00:00:00 2001 From: arielpetit Date: Tue, 28 Jul 2026 13:25:40 +0100 Subject: [PATCH] feat(): Critical Stability & Reliability Fixes --- src/library/library.ts | 15 ++++++++++----- src/player/history.ts | 6 ++++-- src/player/mpv.ts | 5 +++++ src/sources/spotify/token.ts | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/library/library.ts b/src/library/library.ts index b21d9af..1bea43d 100644 --- a/src/library/library.ts +++ b/src/library/library.ts @@ -89,7 +89,8 @@ export class Library { async upsert(track: Track): Promise { this.index.tracks[track.id] = track; - this.notify(); + this.version++; + this.sorted = null; this.schedulePersist(); } @@ -97,13 +98,15 @@ export class Library { async upsertMany(tracks: Track[]): Promise { if (tracks.length === 0) return; for (const track of tracks) this.index.tracks[track.id] = track; - this.notify(); + this.version++; + this.sorted = null; this.schedulePersist(); } async remove(id: string): Promise { delete this.index.tracks[id]; - this.notify(); + this.version++; + this.sorted = null; this.schedulePersist(); } @@ -111,13 +114,15 @@ export class Library { async removeMany(ids: string[]): Promise { if (ids.length === 0) return; for (const id of ids) delete this.index.tracks[id]; - this.notify(); + this.version++; + this.sorted = null; this.schedulePersist(); } async clear(): Promise { this.index.tracks = {}; - this.notify(); + this.version++; + this.sorted = null; this.schedulePersist(); } diff --git a/src/player/history.ts b/src/player/history.ts index 16ed365..8ae51ec 100644 --- a/src/player/history.ts +++ b/src/player/history.ts @@ -79,7 +79,8 @@ export class PlayHistory { { id, at: new Date().toISOString() }, ...this.index.entries.filter((e) => e.id !== id), ].slice(0, CAP); - this.notify(); + this.version++; + for (const fn of this.listeners) fn(); void this.persist(); } @@ -88,7 +89,8 @@ export class PlayHistory { const kept = this.index.entries.filter((e) => existing(e.id)); if (kept.length === this.index.entries.length) return; this.index.entries = kept; - this.notify(); + this.version++; + for (const fn of this.listeners) fn(); void this.persist(); } diff --git a/src/player/mpv.ts b/src/player/mpv.ts index 9161f81..5233afa 100644 --- a/src/player/mpv.ts +++ b/src/player/mpv.ts @@ -44,6 +44,8 @@ export class MpvPlayer extends EventEmitter { * after the last track, after stop), so transport commands are gated on this. */ private loaded = false; + /** Lock to prevent connection reset while ensureStarted() is in progress. */ + private resetting = false; constructor(mpvPath: string) { super(); @@ -73,6 +75,8 @@ export class MpvPlayer extends EventEmitter { * Used both on an unexpected exit (auto-respawn) and during teardown. */ private resetConnection(): void { + if (this.resetting) return; + this.resetting = true; this.loaded = false; if (this.sock) { try { @@ -91,6 +95,7 @@ export class MpvPlayer extends EventEmitter { this.unlinkSocket(); // Each respawn gets a brand-new endpoint to avoid colliding with a stale one. this.ipcPath = MpvPlayer.makeIpcPath(); + this.resetting = false; } /** Best-effort removal of the unix socket file (no-op on Windows pipes). */ diff --git a/src/sources/spotify/token.ts b/src/sources/spotify/token.ts index d0f8c01..59b7e62 100644 --- a/src/sources/spotify/token.ts +++ b/src/sources/spotify/token.ts @@ -84,7 +84,7 @@ export function clearSpotifyTokenCache(): void { /** Fetch (or reuse) an anonymous web-player bearer token. */ export async function getWebPlayerToken(): Promise { const hit = tokenCache; - if (hit && hit.expiresAtMs - Date.now() > TOKEN_SKEW_MS) { + if (hit && hit.expiresAtMs > Date.now()) { return hit; }