Player: GET /api/player with Spotify now-playing info

Source, state, artist/title/album, position and duration (from cspot's
playback state, so it matches the app), volume and `can`. cspot patch 0004
adds read getters for the playback state and context.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 22:48:00 +10:00
parent c6ecbbeb25
commit 9e1ca98a5d
5 changed files with 117 additions and 4 deletions
+8
View File
@@ -42,6 +42,14 @@ void spotify_suspend(bool suspend);
void spotify_set_volume(int pct);
// A session is running (an app is connected to this device).
bool spotify_session_active(void);
typedef struct {
char artist[96], title[128], album[96]; // "" until the first track is audible
uint32_t duration_ms, position_ms; // position as the Spotify app sees it
bool paused;
} spotify_track_t;
// The current track of the running session. False without a session.
bool spotify_now_playing(spotify_track_t *t);
// Current state for status.spotify_state.
const char *spotify_state(void);
@@ -0,0 +1,14 @@
diff --git a/cspot/include/SpircHandler.h b/cspot/include/SpircHandler.h
index 7ff930b..c17caa0 100644
--- a/cspot/include/SpircHandler.h
+++ b/cspot/include/SpircHandler.h
@@ -44,6 +44,9 @@ class SpircHandler {
void subscribeToMercury();
std::shared_ptr<TrackPlayer> getTrackPlayer();
+ // Read access for the host (now-playing info: track position, play state).
+ std::shared_ptr<PlaybackState> getPlaybackState() { return playbackState; }
+ std::shared_ptr<cspot::Context> getContext() { return ctx; }
void setEventHandler(EventHandler handler);
+34
View File
@@ -12,6 +12,7 @@
#include "BellLogger.h"
#include "CSpotContext.h"
#include "LoginBlob.h"
#include "PlaybackState.h"
#include "SpircHandler.h"
#include "TrackPlayer.h"
#include "esp_log.h"
@@ -55,6 +56,9 @@ static std::deque<Boundary> s_bounds;
static std::string s_last_id; // track of the most recent data callback
static std::atomic<bool> s_depleted{false};
static std::mutex s_track_mutex;
static cspot::TrackInfo s_track; // audible track (TRACK_INFO), guarded by s_track_mutex
static void clear_boundaries(bool new_load)
{
std::lock_guard<std::mutex> lock(s_bound_mutex);
@@ -134,6 +138,30 @@ extern "C" bool spotify_session_active(void)
return s_handler != nullptr;
}
extern "C" bool spotify_now_playing(spotify_track_t *t)
{
auto h = s_handler;
if (!h) {
return false;
}
{
std::lock_guard<std::mutex> lock(s_track_mutex);
strlcpy(t->artist, s_track.artist.c_str(), sizeof(t->artist));
strlcpy(t->title, s_track.name.c_str(), sizeof(t->title));
strlcpy(t->album, s_track.album.c_str(), sizeof(t->album));
t->duration_ms = s_track.duration;
}
// Same position the app shows: last reported position, plus the time since while playing.
const auto &st = h->getPlaybackState()->innerFrame.state;
t->paused = st.status != PlayStatus_kPlayStatusPlay;
uint64_t pos = st.position_ms;
if (!t->paused) {
pos += h->getContext()->timeProvider->getSyncedTimestamp() - st.position_measured_at;
}
t->position_ms = t->duration_ms && pos > t->duration_ms ? t->duration_ms : (uint32_t)pos;
return true;
}
const char *spotify_state(void)
{
return s_state.load();
@@ -324,6 +352,8 @@ static void session(void)
case E::TRACK_INFO: {
auto &ti = std::get<cspot::TrackInfo>(ev->data);
ESP_LOGI(TAG, "track: %s - %s (%lu ms)", ti.artist.c_str(), ti.name.c_str(), (unsigned long)ti.duration);
std::lock_guard<std::mutex> lock(s_track_mutex);
s_track = ti;
return;
}
case E::PLAY_PAUSE:
@@ -370,6 +400,10 @@ static void session(void)
}
// Stops the queue and player tasks (waits for them), then closes the connection.
s_handler.reset();
{
std::lock_guard<std::mutex> lock(s_track_mutex);
s_track = {};
}
handler->disconnect();
ESP_LOGI(TAG, "session ended");
}