Repository navigation
v0.9.0
What's Changed
The big change is support for the Sendspin 1.0.0-rc1 protocol via #140. Every connection is now encrypted with Noise (KKpsk2), and a server must pair with the device before it can start playback, unless the application turns on unpaired access with set_unpaired_access_enabled() (off by default). The client's identity is a persisted X25519 keypair, so client_id is now the base64url public key rather than a configured string or MAC address.
Important
rc1 is not wire-compatible with earlier protocol versions. Music Assistant needs 2.11.0b4 or newer, and older servers can no longer connect to a client built on this release.
The other major changes:
- Protocol task (#148 - #154): a library-owned task now does all protocol work: it owns every connection and receives through one shared inbound ring. Transports only receive,
loop()only drains the inbox, andconnect_to()/disconnect()can be called from any thread. Task stacks are derived from a static call graph instead of picked by hand (tools/stack_usage/). - Source role (#164 - #168, #172, #175): streams captured audio to the server (
source@v1) as PCM or Opus.examples/source_client/streams the default PortAudio input. - Opus is a build option (#126):
SENDSPIN_ENABLE_OPUS(CMake option / Kconfig entry, on by default).
Summary of breaking changes
Protocol and server compatibility (#140)
- Requires a Sendspin 1.0.0-rc1 server (Music Assistant 2.11.0b4 or newer).
SendspinClientConfig::client_idis removed. The ID comes from the persisted keypair; read it withSendspinClient::client_id()afterstart().SendspinGoodbyeReasongainsUNAUTHORIZED,PAIRING_REQUIRED,CONCURRENT_ATTEMPTandUNPAIRED.
Pairing (#140)
- New
SendspinClientConfigfields choose how a device can be paired:pairing_code_out_channels/pairing_code_formats(dynamic code),static_pairing_code,pairing_window_supported,pairing_psk(the factory Pairing PSK; generated and persisted when unset), the*_locationshints, andmax_pairing_records(default 12). - New optional
SendspinClientListenercallbacks:on_pairing_started/succeeded/failed,on_trust_changed,on_display_pairing_code/on_clear_pairing_code, andon_open_pairing_window/on_close_pairing_window. - New client methods:
confirm_pairing_window(),cancel_pairing_window(),pairing_token()/format_pairing_token(), andset_unpaired_access_enabled(). send_text()andget_current_trust()are removed (#152). Track trust throughon_trust_changed().
Persistence provider (#140, #145)
- The typed methods (
save/load_last_server_hash,save/load_static_delay) are replaced by an opaque byte store:load_blob(key),save_blob(key, data, len), andcommit(). The keys and fixed blob sizes are inpersistence_keys.h, andpersistence_codec.hexposes the encoding. A provider stores bytes and never parses them. erase_blob()is removed. A revoked record's slot is overwritten with a zeroed blob instead.commit()runs after any batch of writes that holds pairing material. A provider that writes through insave_blob()can keep the default.- Values saved by v0.8.0 are not migrated.
Client API
start_server()is removed (#147); usestart().update_state()andSendspinClientStateare removed (#140). Report availability withset_available(), and leave the group withleave().connect_to()anddisconnect()can now be called from any thread.websocket_payload_locationis replaced byinbound_ring_location(#148).- New ESP-IDF task settings:
protocol_task_psram_stack,protocol_task_priority,protocol_task_stack_size,httpd_stack_sizeandwebsocket_stack_size. Their defaults come from the derived stack budgets (#150), and smaller values are clamped up to them.
Player
- "Static delay" is renamed "output delay" to match the spec:
initial_output_delay_ms,update_output_delay(),set_output_delay_adjustable(),get_output_delay_ms(),on_output_delay_changed(). get_fixed_delay_us()is removed (#158).PlayerRoleConfigaudio formats must include FLAC or PCM; an Opus-only list is rejected (#126).
Other roles (#140)
- Artwork:
BMPis removed from the image formats (rc1 supports only JPEG and PNG). - Visualizer:
request_format()andVisualizerFormatRequestare removed. - Controller:
send_command()now returnsbool.
All changes
- Cache host CI compiles with ccache (#183) @kahrendt
- Compile host builds through ccache when it is installed (#181) @kahrendt
- Run clang-tidy on every core (#182) @kahrendt
- Stamp every inbound message when its last byte arrives (#179) @kahrendt
- Judge visualizer staleness from the delivery time, not the receive stamp (#180) @kahrendt
- Dismiss the pairing prompt when a server/activate cancels the attempt (#177) @kahrendt
- Move task stack derivation from config.h to tools/stack_usage (#176) @kahrendt
- Ride out source send stalls in the capture buffer (#175) @kahrendt
- Wait on what three lifecycle tests assert instead of a racing signal (#173) @kahrendt, @claude
- Pin CPace against the official draft-irtf-cfrg-cpace-21 test vectors (#170) @kahrendt, @claude
- Fix source capture review findings (#172) @kahrendt
- Add a source role example (#168) @kahrendt, @chrisuthe
- Add an Opus encode path to the source role (#167) @kahrendt, @chrisuthe
- Add the source role with a PCM streaming pipeline (#166) @kahrendt, @chrisuthe
- Add the outbound ring and the lent encrypted send path (#165) @kahrendt, @chrisuthe
- Add the source@v1 protocol layer (#164) @kahrendt, @chrisuthe
- Drop the connection on any send that fails after the Noise encrypt (#163) @kahrendt
- Drop a disconnected connection through drop_connection() (#162) @kahrendt
- Call the connection's one owner directly instead of through callbacks (#161) @kahrendt
- Gate player audio on the adopted client/state snapshot (#160) @kahrendt
- Keep one admission flag and one time-sync gate on the burst path (#159) @kahrendt
- Drop history wording from three playback-progress slot comments (#157) @kahrendt
- Share one hand-over and wake mechanism across the binary roles (#156) @kahrendt
- Return the sync task to idle on a decode allocation failure, close the review gaps (#154) @kahrendt
- Hand artwork parts to the decode thread as ring items (#153) @kahrendt
- Build and parse every protocol document in the JSON arena, one document at a time (#151) @kahrendt
- Derive the task stacks and ring floor, number stream starts, reap released connections, trim (#150) @kahrendt
- Run the connection lifecycle on the protocol task and finish the main-loop boundary (#149) @kahrendt
- Resolve the time filter through a dedicated slot (#146) @kahrendt
- Stamp client/time at the socket write and keep server/time off the event ring (#143) @kahrendt
- Store the liveness receive stamp as a 32-bit atomic (#144) @kahrendt
- Trim internals.md to cross-file facts and split out playback-sync.md (#142) @kahrendt
- Bind the WS server with one loop() tick instead of a 50 ms window in tests (#141) @kahrendt
- Fix stale test docs and treat library warnings as errors in CI (#137) @marcelveldt
- Add an ESP-IDF build check to CI (#138) @marcelveldt
- Store ConnectionManager's bounded connection lists inline (#136) @kahrendt
- Tweak the test standards skill and project convention (#135) @kahrendt
Breaking Changes
- Remove PlayerRole::get_fixed_delay_us() (#158) @kahrendt
- Post lifecycle requests as slots, close an admitted drop, remove the dead API and the redundant guards (#152) @kahrendt
- Run the receive path on a library-owned protocol task through one shared inbound ring (#148) @kahrendt
- Remove the deprecated start_server() alias (#147) @kahrendt
- Collapse the persistence deferral channels and add a provider commit hook (#145) @kahrendt
- Implement Sendspin 1.0.0-rc1 protocol (#140) @kahrendt, @raine-works
- Make the Opus decoder a build option and require flac or pcm in player formats (#126) @marcelveldt, @kahrendt
Dependencies
2 changes
- Bump release-drafter/release-drafter from 7.7.0 to 7.9.0 (#178) @dependabot[bot]
- Bump IXWebSocket to v12.0.1 (#174) @kahrendt
Full Changelog: v0.8.0...v0.9.0