simo 3 днів тому
батько
коміт
df9872644d
2 змінених файлів з 55 додано та 9 видалено
  1. 1 1
      README.md
  2. 54 8
      src/index.js

+ 1 - 1
README.md

@@ -29,7 +29,7 @@ npm start
 
 Endpoints:
 
-- `GET /last-known` -> persisted state JSON
+- `GET /last-known` -> persisted state JSON, including `isPlaying`, `progressMs`, and `startedAtMs` while active so static clients can animate playback progress
 - `GET /healthz` -> `{ ok: true }`
 
 ## Notes

+ 54 - 8
src/index.js

@@ -28,6 +28,10 @@ const DEFAULT_STATE = {
   albumArtUrl: null,
   trackUrl: null,
   durationMs: null,
+  // For animating progress while actively playing.
+  isPlaying: null,
+  progressMs: null,
+  startedAtMs: null,
   stoppedAtMs: null,
   stoppedProgressMs: null,
   lastUpdatedAtMs: null,
@@ -42,6 +46,9 @@ function coerceState(v) {
     albumArtUrl: typeof v.albumArtUrl === "string" ? v.albumArtUrl : null,
     trackUrl: typeof v.trackUrl === "string" ? v.trackUrl : null,
     durationMs: typeof v.durationMs === "number" ? v.durationMs : null,
+    isPlaying: typeof v.isPlaying === "boolean" ? v.isPlaying : null,
+    progressMs: typeof v.progressMs === "number" ? v.progressMs : null,
+    startedAtMs: typeof v.startedAtMs === "number" ? v.startedAtMs : null,
     stoppedAtMs: typeof v.stoppedAtMs === "number" ? v.stoppedAtMs : null,
     stoppedProgressMs:
       typeof v.stoppedProgressMs === "number" ? v.stoppedProgressMs : null,
@@ -121,8 +128,39 @@ async function pollOnce() {
   });
 
   if (res.status === 204) {
-    if (prev.title && !prev.stoppedAtMs) {
-      await writeState({ ...prev, stoppedAtMs: nowMs });
+    if (prev.title) {
+      const estimatedProgressMs =
+        typeof prev.progressMs === "number"
+          ? prev.progressMs +
+            Math.max(
+              0,
+              nowMs -
+                (typeof prev.lastUpdatedAtMs === "number"
+                  ? prev.lastUpdatedAtMs
+                  : nowMs),
+            )
+          : null;
+      const stoppedProgressMs =
+        typeof estimatedProgressMs === "number"
+          ? Math.max(
+              0,
+              Math.min(
+                typeof prev.durationMs === "number"
+                  ? prev.durationMs
+                  : estimatedProgressMs,
+                estimatedProgressMs,
+              ),
+            )
+          : prev.stoppedProgressMs;
+
+      await writeState({
+        ...prev,
+        isPlaying: false,
+        progressMs: null,
+        startedAtMs: null,
+        stoppedAtMs: prev.stoppedAtMs ?? nowMs,
+        stoppedProgressMs,
+      });
     }
     return;
   }
@@ -158,13 +196,21 @@ async function pollOnce() {
     albumArtUrl,
     trackUrl,
     durationMs,
-    stoppedAtMs: isPlaying ? null : prev.stoppedAtMs ?? nowMs,
-    stoppedProgressMs: !isPlaying
-      ? typeof progressMs === "number"
+    isPlaying,
+    // Snapshot progress for the current track (even while playing).
+    progressMs,
+    // For clients: while playing, you can animate with `Date.now() - startedAtMs`.
+    startedAtMs:
+      isPlaying && typeof progressMs === "number" ? nowMs - progressMs : null,
+    // If Spotify reports not playing, always emit a concrete stop timestamp and progress.
+    // This avoids returning nulls for stoppedAtMs/stoppedProgressMs.
+    stoppedAtMs: isPlaying ? null : nowMs,
+    stoppedProgressMs: isPlaying
+      ? null
+      : typeof progressMs === "number"
         ? progressMs
-        : prev.stoppedProgressMs
-      : null,
-    lastUpdatedAtMs: isPlaying ? nowMs : prev.lastUpdatedAtMs,
+        : prev.stoppedProgressMs ?? 0,
+    lastUpdatedAtMs: nowMs,
   };
 
   await writeState(next);