|
@@ -28,6 +28,10 @@ const DEFAULT_STATE = {
|
|
|
albumArtUrl: null,
|
|
albumArtUrl: null,
|
|
|
trackUrl: null,
|
|
trackUrl: null,
|
|
|
durationMs: null,
|
|
durationMs: null,
|
|
|
|
|
+ // For animating progress while actively playing.
|
|
|
|
|
+ isPlaying: null,
|
|
|
|
|
+ progressMs: null,
|
|
|
|
|
+ startedAtMs: null,
|
|
|
stoppedAtMs: null,
|
|
stoppedAtMs: null,
|
|
|
stoppedProgressMs: null,
|
|
stoppedProgressMs: null,
|
|
|
lastUpdatedAtMs: null,
|
|
lastUpdatedAtMs: null,
|
|
@@ -42,6 +46,9 @@ function coerceState(v) {
|
|
|
albumArtUrl: typeof v.albumArtUrl === "string" ? v.albumArtUrl : null,
|
|
albumArtUrl: typeof v.albumArtUrl === "string" ? v.albumArtUrl : null,
|
|
|
trackUrl: typeof v.trackUrl === "string" ? v.trackUrl : null,
|
|
trackUrl: typeof v.trackUrl === "string" ? v.trackUrl : null,
|
|
|
durationMs: typeof v.durationMs === "number" ? v.durationMs : 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,
|
|
stoppedAtMs: typeof v.stoppedAtMs === "number" ? v.stoppedAtMs : null,
|
|
|
stoppedProgressMs:
|
|
stoppedProgressMs:
|
|
|
typeof v.stoppedProgressMs === "number" ? v.stoppedProgressMs : null,
|
|
typeof v.stoppedProgressMs === "number" ? v.stoppedProgressMs : null,
|
|
@@ -121,8 +128,39 @@ async function pollOnce() {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
if (res.status === 204) {
|
|
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;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -158,13 +196,21 @@ async function pollOnce() {
|
|
|
albumArtUrl,
|
|
albumArtUrl,
|
|
|
trackUrl,
|
|
trackUrl,
|
|
|
durationMs,
|
|
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
|
|
? progressMs
|
|
|
- : prev.stoppedProgressMs
|
|
|
|
|
- : null,
|
|
|
|
|
- lastUpdatedAtMs: isPlaying ? nowMs : prev.lastUpdatedAtMs,
|
|
|
|
|
|
|
+ : prev.stoppedProgressMs ?? 0,
|
|
|
|
|
+ lastUpdatedAtMs: nowMs,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
await writeState(next);
|
|
await writeState(next);
|