Player: seek and volume on /api/player
seek {ms} runs on the Spotify session task like a seek from the app (buffer
flushed, app notified). volume {value}|{delta} sets the player volume and
pushes it to the session, so the app's slider follows; setRemoteVolume now
also runs on the session task.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+60
-6
@@ -423,7 +423,7 @@ static const char *transport(const char *cmd)
|
||||
if (src == PLAYER_SRC_SPOTIFY) {
|
||||
spotify_cmd_t c = pause ? SPOTIFY_CMD_PAUSE : play ? SPOTIFY_CMD_PLAY :
|
||||
next ? SPOTIFY_CMD_NEXT : SPOTIFY_CMD_PREV;
|
||||
return spotify_command(c) ? NULL : "no Spotify session";
|
||||
return spotify_command(c, 0) ? NULL : "no Spotify session";
|
||||
}
|
||||
if (src == PLAYER_SRC_HLS) {
|
||||
if (next || prev) {
|
||||
@@ -435,6 +435,61 @@ static const char *transport(const char *cmd)
|
||||
return "no player source active";
|
||||
}
|
||||
|
||||
// Small JSON body of a POST; NULL if missing or invalid.
|
||||
static cJSON *read_body(httpd_req_t *req)
|
||||
{
|
||||
char buf[128];
|
||||
if (req->content_len == 0 || req->content_len >= sizeof(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
size_t got = 0;
|
||||
while (got < req->content_len) {
|
||||
int r = httpd_req_recv(req, buf + got, req->content_len - got);
|
||||
if (r == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
if (r <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
got += r;
|
||||
}
|
||||
buf[got] = '\0';
|
||||
return cJSON_Parse(buf);
|
||||
}
|
||||
|
||||
static esp_err_t send_409(httpd_req_t *req, const char *msg)
|
||||
{
|
||||
httpd_resp_set_status(req, "409 Conflict");
|
||||
return httpd_resp_sendstr(req, msg);
|
||||
}
|
||||
|
||||
// POST /api/player/seek {ms}, /api/player/volume {value} or {delta}
|
||||
static esp_err_t seek_or_volume(httpd_req_t *req, bool seek)
|
||||
{
|
||||
cJSON *body = read_body(req);
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(body, seek ? "ms" : "value");
|
||||
const cJSON *d = seek ? NULL : cJSON_GetObjectItemCaseSensitive(body, "delta");
|
||||
double val = cJSON_IsNumber(v) ? v->valuedouble : cJSON_IsNumber(d) ? s_volume_pct + d->valuedouble : -1;
|
||||
cJSON_Delete(body);
|
||||
if (val < 0 && !(cJSON_IsNumber(d) && !seek)) {
|
||||
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, seek ? "expected {\"ms\": n}" :
|
||||
"expected {\"value\": 0-100} or {\"delta\": n}");
|
||||
}
|
||||
if (seek) {
|
||||
if (s_src != PLAYER_SRC_SPOTIFY) {
|
||||
return send_409(req, "source can't seek");
|
||||
}
|
||||
if (!spotify_command(SPOTIFY_CMD_SEEK, (uint32_t)val)) {
|
||||
return send_409(req, "no Spotify session");
|
||||
}
|
||||
} else {
|
||||
int pct = val < 0 ? 0 : val > 100 ? 100 : (int)(val + 0.5);
|
||||
set_volume(pct);
|
||||
spotify_set_volume(pct); // the app follows (if a session runs)
|
||||
}
|
||||
return send_player(req);
|
||||
}
|
||||
|
||||
// POST /api/player/<cmd>
|
||||
static esp_err_t player_post(httpd_req_t *req)
|
||||
{
|
||||
@@ -450,13 +505,12 @@ static esp_err_t player_post(httpd_req_t *req)
|
||||
for (int i = 0; i < sizeof(TRANSPORT) / sizeof(TRANSPORT[0]); i++) {
|
||||
if (!strcmp(name, TRANSPORT[i])) {
|
||||
const char *err = transport(name);
|
||||
if (err) {
|
||||
httpd_resp_set_status(req, "409 Conflict");
|
||||
return httpd_resp_sendstr(req, err);
|
||||
}
|
||||
return send_player(req);
|
||||
return err ? send_409(req, err) : send_player(req);
|
||||
}
|
||||
}
|
||||
if (!strcmp(name, "seek") || !strcmp(name, "volume")) {
|
||||
return seek_or_volume(req, name[0] == 's');
|
||||
}
|
||||
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "unknown player command");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user