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
+60 -3
View File
@@ -323,18 +323,70 @@ static void on_ota(bool starting)
}
}
static const char *source_state(player_src_t src)
{
return src == PLAYER_SRC_TONE ? "playing" : src == PLAYER_SRC_OFF ? "idle" :
src == PLAYER_SRC_SPOTIFY && !spotify_session_active() ? "idle" :
src == PLAYER_SRC_SPOTIFY && s_paused ? "paused" : s_playing ? "playing" : "buffering";
}
static void player_status(cJSON *st)
{
player_src_t src = s_src;
const char *state = src == PLAYER_SRC_TONE ? "playing" : src == PLAYER_SRC_OFF ? "idle" :
src == PLAYER_SRC_SPOTIFY && !spotify_session_active() ? "idle" :
src == PLAYER_SRC_SPOTIFY && s_paused ? "paused" : s_playing ? "playing" : "buffering";
const char *state = source_state(src);
cJSON_AddStringToObject(st, "active_source", SRC_NAME[src]);
cJSON_AddStringToObject(st, "source_state", state);
cJSON_AddStringToObject(st, "spotify_state", spotify_state());
cJSON_AddNumberToObject(st, "buffer_ms", (double)(audio_ring_level() * 1000 / RATE));
}
/* ----- Player API ----- */
static void add_str_or_null(cJSON *o, const char *key, const char *s)
{
if (s && s[0]) {
cJSON_AddStringToObject(o, key, s);
} else {
cJSON_AddNullToObject(o, key);
}
}
// Same JSON for GET /api/player and every POST.
static esp_err_t send_player(httpd_req_t *req)
{
player_src_t src = s_src;
spotify_track_t t;
bool sp = src == PLAYER_SRC_SPOTIFY && spotify_now_playing(&t);
cJSON *o = cJSON_CreateObject();
cJSON_AddStringToObject(o, "source", SRC_NAME[src]);
cJSON_AddNullToObject(o, "forced");
cJSON_AddStringToObject(o, "state", src == PLAYER_SRC_OFF ? "stopped" : source_state(src));
add_str_or_null(o, "artist", sp ? t.artist : NULL);
add_str_or_null(o, "title", sp ? t.title : NULL);
add_str_or_null(o, "album", sp ? t.album : NULL);
if (sp && t.duration_ms) {
cJSON_AddNumberToObject(o, "position_ms", t.position_ms);
cJSON_AddNumberToObject(o, "duration_ms", t.duration_ms);
} else {
cJSON_AddNullToObject(o, "position_ms");
cJSON_AddNullToObject(o, "duration_ms");
}
cJSON_AddNumberToObject(o, "volume", s_volume_pct);
cJSON *can = cJSON_AddObjectToObject(o, "can");
cJSON_AddBoolToObject(can, "pause", sp);
cJSON_AddBoolToObject(can, "next", sp);
cJSON_AddBoolToObject(can, "prev", sp);
cJSON_AddBoolToObject(can, "seek", sp);
esp_err_t err = web_send_json(req, o);
cJSON_Delete(o);
return err;
}
static esp_err_t player_get(httpd_req_t *req)
{
return send_player(req);
}
esp_err_t player_init(void)
{
esp_err_t err = audio_ring_init(RING_FRAMES, CHANNELS);
@@ -364,5 +416,10 @@ esp_err_t player_init(void)
if (xTaskCreate(control_task, "player_ctl", 3072, NULL, 4, NULL) != pdPASS) {
return ESP_ERR_NO_MEM;
}
static const httpd_uri_t get = { .uri = "/api/player", .method = HTTP_GET, .handler = player_get };
err = web_register_uri(&get);
if (err != ESP_OK) {
return err;
}
return status_register(player_status);
}