Commit Graph

15 Commits

Author SHA1 Message Date
bsncubed 6b30b2c43b spotify: Tremor (Vorbis) allocations in PSRAM (bell patch 0003)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:49:42 +10:00
bsncubed e5c3c0ad8a 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>
2026-09-25 23:15:38 +10:00
bsncubed a55a3ce14a 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>
2026-09-25 23:04:11 +10:00
bsncubed 8485ecb5b7 Player: transport commands on /api/player
play/pause/toggle/stop/next/prev. Spotify commands run on the session task
(same thread as cspot's frame handling) and the POST waits until they ran,
so it answers the new state. HLS pause stops fetching and resume rejoins at
the live edge; next/prev on HLS and anything on tone/off return 409.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:59:14 +10:00
bsncubed 9e1ca98a5d 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>
2026-09-25 22:48:00 +10:00
bsncubed c6ecbbeb25 Player: auto mode fails over between Spotify and HLS
No Spotify session (or paused, with failover_on_pause) for failover_delay_s
switches to HLS; Spotify playing switches back within ~1 s. Switches fade
out/in over 30 ms and flush the ring. HLS is suspended while Spotify plays.
cspot is held back instead of drained while it isn't Spotify's turn: it keeps
decoding on pause, so dropping its frames let it race through the queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:36:11 +10:00
bsncubed b8702e6faa Spotify: tell cspot when a new track becomes audible
The Mac dropped the device soon after the first track change: we never
called SpircHandler::notifyAudioReachedPlayback(), so cspot's queue did
not advance and the state sent to Spotify stalled.
- The data callback records a boundary (output write position, track
  id) when the track id changes; the session loop (<= 200 ms) calls
  notifyAudioReachedPlayback(id) once playback (ring read position)
  passes it, and notifyAudioEnded() after DEPLETED once the buffer is
  empty. PLAYBACK_START clears boundaries (next data is a new one),
  seek/flush drop pending ones.
- audio_ring exposes its write/read frame counters (wrap-safe compare).
- Verified: 10 min on the Mac without a disconnect, every track change
  notified ("track audible, Spotify notified"), app shows the playing
  track (switches up to ~3-5 s early), 0 underruns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:47:28 +10:00
bsncubed 5ce4588c80 cspot: delayed Pong (like librespot) stops the 6-minute connection resets
The Spotify AP reset the connection (recv -1, errno 104 ECONNRESET) on
the third Ping, every 6 minutes, like clockwork. cspot answered each
Ping at once; librespot (core/src/session.rs) waits 60 s:
Ping -> 60 s -> Pong -> PongAck -> 60 s -> Ping.
- 0003-delayed-pong: record the Ping, send the Pong 60 s later from
  triggerTimeout() (called on every 3 s receive timeout), through the
  PR #3 connection snapshot; dropped on reconnect.
- 0002-diag-log-recv-errors: log recv's return value/errno before
  "Error in read" (error path only); this is what showed the RST.
Verified: over 8+ minutes Ping/delayed Pong/PongAck every 2 min, no
reset at the 6-minute mark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:47:27 +10:00
bsncubed 8008aa175f cspot: patch in philippe44/cspot PR #3 (crash when a request races a reconnect)
MercurySession::reconnect() nulls conn/shanConn (for 5 s per failed
retry) while other tasks keep sending through them: a NULL dereference,
not a catchable exception. Our Spotify sessions are dropped by the
server and reconnect every ~6 minutes, so this race is hit regularly;
likely behind the sporadic resets during long sessions/OTA.
Patch applies cleanly to the pinned 3010349; builds, boots, confirmed.
Drop it once the fix is in the pinned cspot commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:17:01 +10:00
bsncubed f0fe53d5d1 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 <noreply@anthropic.com>
2026-09-25 20:15:29 +10:00
bsncubed 0aac7bc339 Spotify follows source mode, name and credentials live
- spotify_init() sets up the logger, zeroconf routes and session task
  once; spotify_apply() (at boot and on every source save) enables or
  disables: enabled = mode spotify/auto + credentials. Disable removes
  the mDNS entry, zeroconf answers 404 and a running session ends
  (loop exits within 200 ms, SpircHandler::disconnect stops the queue
  and player tasks). A new device name ends the session and
  re-advertises. The login blob is swapped under a mutex.
- Verified: hls/spotify/rename switch the mDNS entry and /spotify_info
  (404/200) live; switching to hls during Spotify playback ended the
  session in < 2 s, HLS played after ~5 s, internal heap 221 -> 365 KB
  (cspot frees everything).
- CLAUDE.md: OTA-with-session findings (intermittent, flash-write stalls
  seen as TX resyncs during uploads), serial-port reset caveat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:43:16 +10:00
bsncubed 8ba4de5eda OTA: quiet the audio sources during a firmware upload
An upload during an active Spotify session crawled (~10 kB/s, 197 s)
and ended in a reset (seen twice). Workaround:
- aes67_ota_on_update(cb): cb(true) right before an accepted upload
  writes flash, cb(false) if it then fails.
- The player pauses Spotify (spotify_pause -> SpircHandler::setPause, the
  app follows) and suspends HLS fetching (hls_suspend); resumed if the
  upload fails. AES67 TX keeps running (silence).
- Verified: upload during Spotify playback paused the session and
  installed in 25.8 s (normal for 2 MB), clean reboot and self-test.
  Root cause still open (noted in CLAUDE.md with the other cspot items).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:02:03 +10:00
bsncubed 4a38254660 Step 7.5b3b: Spotify audio on AES67
- spotify_init(pcm_cb, event_cb): cspot's data callback hands 44.1 kHz
  stereo s16 PCM to the player and returns what was taken; the player's
  blocking ring write paces decoding to playback speed.
- Events: FLUSH/SEEK/PLAYBACK_START empty the ring (skip/seek sound at
  once); PLAY_PAUSE pauses output (silence, buffer kept, no underrun);
  VOLUME logged (applied in b4).
- player: Spotify via its own audio_out converter (44.1 -> 48 kHz);
  source_state playing / paused / buffering.
- Verified from a Mac: music on the AES67 stream (10 s: 0 gaps, no
  silent packets, RMS -9.6 dBFS, peak 0.0 dBFS), buffer 4.0 s full,
  0 underruns, pause/play/skip/seek events.
  Seen: first 3 connects "Can't connect to spotify servers", 4th worked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:41:49 +10:00
bsncubed c240ac9a3c Step 7.5b2: Spotify Connect zeroconf and login work (PCM counted only)
- Zeroconf on our httpd (GET/POST /spotify_info, form body URL-decoded)
  and _spotify-connect._tcp via our mDNS (VERSION, CPath, Stack TXT).
- Session task: waits for the app's login blob, connects with the
  user's client ID/secret and the configured bitrate (96/160/320 ->
  OGG_VORBIS_*), SpircHandler, events logged; cspot exceptions caught.
  Starts when source.mode is spotify/auto and credentials are set.
- bell::bellGlobalLogger was NULL: every CSPOT_LOG crashed the board
  right after addUser (Load access fault in Session.cpp:67 /
  LoginBlob.cpp:58). cspot/bell logs now go to esp_log (UART + syslog);
  signed CDN URL tokens are cut from the log.
- esp_audio_codec's own Vorbis decoder clashed with bell's Tremor
  symbols: CONFIG_AUDIO_DECODER_VORBIS_SUPPORT / SIMPLE_DEC_OGG off.
- status.spotify_state from the session (waiting / connecting /
  connected / login failed / error / disabled / no client credentials).
- Verified from a Mac: device appears, "connected as <user>", access
  token fetched with the user's client credentials, track info, audio
  key, CDN URL (the step broken upstream), PCM decoded. Without
  backpressure a track decodes in ~26 s (b3 feeds the ring).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:26:36 +10:00
bsncubed 039ec75473 Step 7.5b1: cspot (philippe44 fork) builds and links on the P4
- external/cspot: git submodule, pinned to philippe44/cspot 3010349
  (bell ed2d6e9); GPL-3.0.
- components/spotify: project component wrapping cspot. bell trimmed to
  what cspot needs (Tremor Vorbis; no codec wrapper, sinks, MQTT, web
  server, fmt, regex; cJSON from IDF). Xtensa biquad assembly filtered
  out (P4 is RISC-V). CMAKE_POLICY_VERSION_MINIMUM 3.5 for CMake 4.
- Patches in components/spotify/patches, applied at configure time:
  nanopb generator works with protobuf >= 5 (MakeClass removed);
  URLParser.cpp missing <cstdio>/<cstring> for GCC 14.
- CONFIG_COMPILER_CXX_EXCEPTIONS=y (cspot needs exceptions).
- spotify_init() only builds a LoginBlob (link check).
- CLAUDE.md: submodule init and IDF-venv Python packages for nanopb.
- Verified on board: "cspot linked: device "P4 AES67", zeroconf info 588
  bytes"; HLS still playing, PTP locked; image 1.83 MB (70% free).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:25:10 +10:00