From f0fe53d5d1aefc9e85ef561419c5cbfabceae9d5 Mon Sep 17 00:00:00 2001 From: Ben Nicholson Date: Fri, 25 Sep 2026 20:15:29 +1000 Subject: [PATCH] Spotify: end the session for OTA; app volume starts at the player's - OTA: the player now ends the Spotify session (spotify_suspend: stop flag, waits until the session's tasks are gone, refuses new ones) instead of pausing it, and suspends HLS. Pausing was not enough (an upload still failed once with a paused session). Verified twice with a session running for minutes: "Disconnecting mercury session / session ended / suspended", installed in ~26 s, clean reboot. - Volume: createFromBlob() starts cspot at volume 0, which the app showed while we played at 100 %. The session now starts with the player's volume (spotify_set_volume); volumes coming from the app are stored exactly, without echo. Verified: slider starts at 100 %. Co-Authored-By: Claude Opus 5.5 --- components/spotify/include/spotify.h | 6 +++++ components/spotify/spotify.cpp | 37 ++++++++++++++++++++++++++++ main/player.c | 17 ++++++------- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/components/spotify/include/spotify.h b/components/spotify/include/spotify.h index 308fa31..e9dbd40 100644 --- a/components/spotify/include/spotify.h +++ b/components/spotify/include/spotify.h @@ -30,6 +30,12 @@ esp_err_t spotify_init(spotify_pcm_cb_t pcm, spotify_event_cb_t event); void spotify_apply(void); // Pause/resume the running session (the Spotify app follows). No-op without a session. void spotify_pause(bool pause); +// Suspend: end any running session (waits until its tasks are gone, max ~3 s) and refuse new ones. +// Resume: back to what the config says. Used around firmware uploads. +void spotify_suspend(bool suspend); +// The player's volume (0..100 %): reported to the Spotify app when a session starts (else the app +// shows 0 while we play at the player's level), and pushed to a running session. +void spotify_set_volume(int pct); // Current state for status.spotify_state. const char *spotify_state(void); diff --git a/components/spotify/spotify.cpp b/components/spotify/spotify.cpp index ecaa47e..f8d82c2 100644 --- a/components/spotify/spotify.cpp +++ b/components/spotify/spotify.cpp @@ -33,12 +33,44 @@ static std::string s_name; // advertised device nam static std::atomic s_enabled{false}; static std::atomic s_stop{false}; // end the running session static std::atomic s_got_blob{false}; +static std::atomic s_in_session{false}; // session() running (its tasks may exist) +static std::atomic s_volume{65535}; // player volume in Spotify units (0..65535) static std::atomic s_state{"disabled"}; static std::atomic s_pcm_bytes{0}; static std::shared_ptr s_handler; // running session, else null static spotify_pcm_cb_t s_pcm_cb; static spotify_event_cb_t s_event_cb; +extern "C" void spotify_suspend(bool suspend) +{ + if (!suspend) { + spotify_apply(); + return; + } + s_enabled = false; + s_stop = true; + s_got_blob = false; + s_state = "suspended"; + for (int i = 0; i < 30 && s_in_session; i++) { // session loop exits within 200 ms, then disconnect + vTaskDelay(pdMS_TO_TICKS(100)); + } + ESP_LOGI(TAG, "suspended%s", s_in_session ? " (session still ending)" : ""); +} + +extern "C" void spotify_set_volume(int pct) +{ + pct = pct < 0 ? 0 : pct > 100 ? 100 : pct; + int v = pct * 65535 / 100; + if (v == s_volume) { + return; + } + s_volume = v; + auto h = s_handler; + if (h) { + h->setRemoteVolume(v); + } +} + extern "C" void spotify_pause(bool pause) { auto h = s_handler; @@ -203,6 +235,8 @@ static void session(void) ctx->config.clientSecret = client_secret; ctx->config.audioFormat = bitrate >= 320 ? AudioFormat_OGG_VORBIS_320 : bitrate >= 160 ? AudioFormat_OGG_VORBIS_160 : AudioFormat_OGG_VORBIS_96; + // createFromBlob() starts at volume 0, which the app would show; report what we actually play. + ctx->config.volume = s_volume; s_state = "connecting"; ctx->session->connectWithRandomAp(); auto token = ctx->session->authenticate(login); @@ -246,6 +280,7 @@ static void session(void) } break; case E::VOLUME: + s_volume = std::get(ev->data); // exact value from the app, no echo back if (s_event_cb) { s_event_cb(SPOTIFY_EV_VOLUME, std::get(ev->data)); } @@ -278,6 +313,7 @@ static void spotify_task(void *arg) } s_got_blob = false; s_stop = false; + s_in_session = true; try { session(); } catch (const std::exception &e) { @@ -285,6 +321,7 @@ static void spotify_task(void *arg) ESP_LOGE(TAG, "session ended: %s", e.what()); } s_handler.reset(); + s_in_session = false; vTaskDelay(pdMS_TO_TICKS(2000)); } } diff --git a/main/player.c b/main/player.c index 43df7ae..d6410af 100644 --- a/main/player.c +++ b/main/player.c @@ -160,21 +160,20 @@ static void spotify_event(spotify_event_t ev, int value) } } -// Firmware upload: flash writes stall everything running from PSRAM; a Spotify session -// streaming at the same time made uploads crawl and end in a reset. Quiet the sources meanwhile. +// Firmware upload: flash writes stall tasks for > 20 ms at a time; with a Spotify session running, +// uploads crawled and sometimes ended in a reset (pausing was not enough). End the session (its +// tasks and connections are gone before the first write) and stop HLS fetching meanwhile. The +// device reboots after a good upload anyway; after a failed one the sources come back (Spotify +// waits for the app to reconnect). static void on_ota(bool starting) { - static bool paused_spotify; if (starting) { - paused_spotify = s_src == PLAYER_SRC_SPOTIFY && !s_paused; - spotify_pause(true); + spotify_suspend(true); hls_suspend(true); - ESP_LOGI(TAG, "firmware update: sources paused"); + ESP_LOGI(TAG, "firmware update: Spotify session ended, HLS suspended"); } else { hls_suspend(false); - if (paused_spotify) { - spotify_pause(false); - } + spotify_suspend(false); ESP_LOGI(TAG, "firmware update failed: sources resumed"); } }