Player: runtime source override and play-a-URL on /api/player
The player now owns the effective mode (config or override) and passes it down: spotify_apply(enable) and hls_set_url(url) instead of both reading source.mode from the config. /api/player/source forces spotify|hls|off (config returns to the saved mode), /api/player/url plays an m3u8 now; neither is saved. GET reports it in `forced`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+73
-8
@@ -29,6 +29,7 @@ static const char *TAG = "player";
|
||||
|
||||
typedef enum { MODE_TONE, MODE_OFF, MODE_HLS, MODE_SPOTIFY, MODE_AUTO } mode_t_;
|
||||
static const char *const SRC_NAME[] = { "tone", "off", "hls", "spotify" };
|
||||
static const char *const MODE_NAME[] = { "tone", "off", "hls", "spotify", "auto" };
|
||||
|
||||
static volatile mode_t_ s_mode = MODE_OFF;
|
||||
static volatile player_src_t s_src = PLAYER_SRC_OFF; // what is playing now
|
||||
@@ -40,6 +41,12 @@ static volatile bool s_sp_playing; // Spotify has started playback and is n
|
||||
static volatile bool s_hls_paused; // HLS paused via the API: not fetching, silence
|
||||
static audio_conv_t *s_spotify_conv;
|
||||
|
||||
// Effective mode: the config's, or a runtime override from the API (not saved). Web server task.
|
||||
static mode_t_ s_cfg_mode = MODE_OFF;
|
||||
static char s_cfg_url[HLS_URL_MAX];
|
||||
static int s_forced = -1; // mode forced via /api/player/source|url, -1 = none
|
||||
static char s_forced_url[HLS_URL_MAX]; // from /api/player/url; "" = the config's URL
|
||||
|
||||
// Failover (mode auto)
|
||||
static volatile int s_failover_delay_s = 5;
|
||||
static volatile bool s_failover_on_pause;
|
||||
@@ -173,14 +180,22 @@ static void select_source(player_src_t src)
|
||||
s_target = src; // player_read fades out and switches
|
||||
}
|
||||
|
||||
void player_apply(const cJSON *source)
|
||||
// Apply the effective mode: Spotify on/off, the HLS URL, and the source to play.
|
||||
static void apply_mode(void)
|
||||
{
|
||||
if (s_spotify_conv) { // after player_init: Spotify follows mode / name / credentials live
|
||||
spotify_apply();
|
||||
static char applied_url[HLS_URL_MAX];
|
||||
mode_t_ mode = s_forced >= 0 ? (mode_t_)s_forced : s_cfg_mode;
|
||||
if (s_spotify_conv) { // after spotify_init: follows mode / name / credentials live
|
||||
spotify_apply(mode == MODE_SPOTIFY || mode == MODE_AUTO);
|
||||
}
|
||||
const char *url = mode != MODE_HLS && mode != MODE_AUTO ? "" : s_forced_url[0] ? s_forced_url : s_cfg_url;
|
||||
if (strcmp(url, applied_url)) {
|
||||
strlcpy(applied_url, url, sizeof(applied_url));
|
||||
hls_set_url(url);
|
||||
if (s_src == PLAYER_SRC_HLS) {
|
||||
s_flush = true; // another stream: don't finish the old one's buffer
|
||||
}
|
||||
}
|
||||
s_failover_delay_s = (int)cJSON_GetObjectItemCaseSensitive(source, "failover_delay_s")->valuedouble;
|
||||
s_failover_on_pause = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(source, "failover_on_pause"));
|
||||
mode_t_ mode = mode_of(cJSON_GetObjectItemCaseSensitive(source, "mode")->valuestring);
|
||||
if (mode == s_mode) {
|
||||
return;
|
||||
}
|
||||
@@ -196,6 +211,15 @@ void player_apply(const cJSON *source)
|
||||
}
|
||||
}
|
||||
|
||||
void player_apply(const cJSON *source)
|
||||
{
|
||||
s_failover_delay_s = (int)cJSON_GetObjectItemCaseSensitive(source, "failover_delay_s")->valuedouble;
|
||||
s_failover_on_pause = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(source, "failover_on_pause"));
|
||||
s_cfg_mode = mode_of(cJSON_GetObjectItemCaseSensitive(source, "mode")->valuestring);
|
||||
strlcpy(s_cfg_url, cJSON_GetObjectItemCaseSensitive(source, "hls_url")->valuestring, sizeof(s_cfg_url));
|
||||
apply_mode();
|
||||
}
|
||||
|
||||
/* ----- source side ----- */
|
||||
|
||||
// Write converted 48 kHz frames, waiting for space at playback speed. Frames of a source that is
|
||||
@@ -369,7 +393,7 @@ static esp_err_t send_player(httpd_req_t *req)
|
||||
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");
|
||||
add_str_or_null(o, "forced", s_forced >= 0 ? MODE_NAME[s_forced] : NULL);
|
||||
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);
|
||||
@@ -438,7 +462,7 @@ static const char *transport(const char *cmd)
|
||||
// Small JSON body of a POST; NULL if missing or invalid.
|
||||
static cJSON *read_body(httpd_req_t *req)
|
||||
{
|
||||
char buf[128];
|
||||
char buf[HLS_URL_MAX + 128]; // fits {"url": ...}
|
||||
if (req->content_len == 0 || req->content_len >= sizeof(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -490,6 +514,43 @@ static esp_err_t seek_or_volume(httpd_req_t *req, bool seek)
|
||||
return send_player(req);
|
||||
}
|
||||
|
||||
// POST /api/player/source {source: spotify|hls|off|config}, /api/player/url {url}: runtime override
|
||||
static esp_err_t force_source(httpd_req_t *req, bool url)
|
||||
{
|
||||
cJSON *body = read_body(req);
|
||||
const cJSON *v = cJSON_GetObjectItemCaseSensitive(body, url ? "url" : "source");
|
||||
const char *s = cJSON_IsString(v) ? v->valuestring : "";
|
||||
const char *err = NULL;
|
||||
if (url) {
|
||||
if (strncmp(s, "http://", 7) && strncmp(s, "https://", 8)) {
|
||||
err = "expected {\"url\": \"http(s)://...m3u8\"}";
|
||||
} else if (strlen(s) >= sizeof(s_forced_url)) {
|
||||
err = "url too long";
|
||||
} else {
|
||||
s_forced = MODE_HLS;
|
||||
strlcpy(s_forced_url, s, sizeof(s_forced_url));
|
||||
if (s_hls_paused && s_src == PLAYER_SRC_HLS) {
|
||||
hls_pause(false); // a new stream starts playing
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(s, "config")) {
|
||||
s_forced = -1;
|
||||
s_forced_url[0] = '\0';
|
||||
} else if (!strcmp(s, "spotify") || !strcmp(s, "hls") || !strcmp(s, "off")) {
|
||||
s_forced = mode_of(s);
|
||||
s_forced_url[0] = '\0';
|
||||
} else {
|
||||
err = "expected {\"source\": \"spotify|hls|off|config\"}";
|
||||
}
|
||||
if (!err) {
|
||||
ESP_LOGI(TAG, "override: %s%s%s", s_forced >= 0 ? MODE_NAME[s_forced] : "none (config)",
|
||||
s_forced_url[0] ? " " : "", s_forced_url);
|
||||
apply_mode();
|
||||
}
|
||||
cJSON_Delete(body);
|
||||
return err ? httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, err) : send_player(req);
|
||||
}
|
||||
|
||||
// POST /api/player/<cmd>
|
||||
static esp_err_t player_post(httpd_req_t *req)
|
||||
{
|
||||
@@ -511,6 +572,9 @@ static esp_err_t player_post(httpd_req_t *req)
|
||||
if (!strcmp(name, "seek") || !strcmp(name, "volume")) {
|
||||
return seek_or_volume(req, name[0] == 's');
|
||||
}
|
||||
if (!strcmp(name, "source") || !strcmp(name, "url")) {
|
||||
return force_source(req, name[0] == 'u');
|
||||
}
|
||||
return httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "unknown player command");
|
||||
}
|
||||
|
||||
@@ -535,6 +599,7 @@ esp_err_t player_init(void)
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
spotify_init(spotify_pcm, spotify_event, audio_ring_written, audio_ring_read_pos);
|
||||
apply_mode(); // now with Spotify
|
||||
aes67_ota_on_update(on_ota);
|
||||
err = hls_start(decoder_feed);
|
||||
if (err != ESP_OK) {
|
||||
|
||||
Reference in New Issue
Block a user