A server running aiosendspin flags every connection from a v0.8.0 client as non-compliant, twice:
WARNING [aiosendspin.server.client.c4:e9:0a:4f:35:70] non-compliant client: initial client/state omitted required player timing fields
WARNING [aiosendspin.server.client.c4:e9:0a:4f:35:70] non-compliant client: client/state used the legacy top-level 'state' field
Seen with Music Assistant 2.10.3 against sendspin-cli 0.3.0 (pinning sendspin-cpp v0.8.0), on every connect and reconnect. Playback itself works — the server accepts the client and plays fine — so this is about conformance rather than a functional break.
Where it comes from
format_client_state_message() in src/protocol.cpp:894 writes:
root["type"] = "client/state";
root["payload"]["state"] = to_cstr(msg->state);
if (msg->player.has_value()) {
const ClientPlayerStateObject& player_state = msg->player.value();
root["payload"]["player"]["volume"] = player_state.volume;
root["payload"]["player"]["muted"] = player_state.muted;
root["payload"]["player"]["static_delay_ms"] = player_state.static_delay_ms;
...
}
The legacy field. The payload carries a top-level state, where the server expects available. aiosendspin's ClientStatePayload.__pre_deserialize__ (models/core.py:329) normalizes it — available = state != "external_source" — and records legacy_state_used, which the server then flags. So it is accepted, but explicitly as a legacy shape.
The timing fields. initial_state_deviations() (server/roles/player/v1.py:643) requires all three of static_delay_ms, required_lead_time_ms and min_buffer_ms in the first client/state:
if (
player.static_delay_ms is None
or player.required_lead_time_ms is None
or player.min_buffer_ms is None
):
reasons.append("omitted required player timing fields")
v0.8.0 sends the first and not the other two. They are not merely unserialized — they are not modelled at all:
struct ClientPlayerStateObject {
uint8_t volume{};
bool muted{};
uint16_t static_delay_ms{};
std::vector<SendspinPlayerCommand> supported_commands{};
};
Grepping src/ for required_lead_time_ms, min_buffer_ms or "available" returns nothing, so neither the fields nor the newer key exist anywhere in the library.
Why it seems worth fixing
The values look like they are already known to a host player — sendspin-cli takes a --buffer-ms and computes its ALSA ring and period from it — so this may be plumbing rather than new machinery. And the legacy state field is normalized by a compatibility path that, judging by its name and the explicit flagging, is not meant to be load-bearing forever.
Happy to test a branch: I run this on a MIPS OpenWrt router against Music Assistant daily, and the warnings are reproducible on every connect.
Environment
|
|
| Library |
sendspin-cpp v0.8.0, as pinned by sendspin-cli 0.3.0 |
| Client |
sendspin-cli 0.3.0, packaged for OpenWrt, ALSA sink |
| Host |
D-Link DIR-3040, OpenWrt 25.12.5, ramips/mt7621 (MIPS32 little-endian, musl) |
| Server |
Music Assistant 2.10.3 / aiosendspin |
A server running
aiosendspinflags every connection from a v0.8.0 client as non-compliant, twice:Seen with Music Assistant 2.10.3 against sendspin-cli 0.3.0 (pinning sendspin-cpp
v0.8.0), on every connect and reconnect. Playback itself works — the server accepts the client and plays fine — so this is about conformance rather than a functional break.Where it comes from
format_client_state_message()insrc/protocol.cpp:894writes:The legacy field. The payload carries a top-level
state, where the server expectsavailable.aiosendspin'sClientStatePayload.__pre_deserialize__(models/core.py:329) normalizes it —available = state != "external_source"— and recordslegacy_state_used, which the server then flags. So it is accepted, but explicitly as a legacy shape.The timing fields.
initial_state_deviations()(server/roles/player/v1.py:643) requires all three ofstatic_delay_ms,required_lead_time_msandmin_buffer_msin the firstclient/state:v0.8.0 sends the first and not the other two. They are not merely unserialized — they are not modelled at all:
Grepping
src/forrequired_lead_time_ms,min_buffer_msor"available"returns nothing, so neither the fields nor the newer key exist anywhere in the library.Why it seems worth fixing
The values look like they are already known to a host player — sendspin-cli takes a
--buffer-msand computes its ALSA ring and period from it — so this may be plumbing rather than new machinery. And the legacystatefield is normalized by a compatibility path that, judging by its name and the explicit flagging, is not meant to be load-bearing forever.Happy to test a branch: I run this on a MIPS OpenWrt router against Music Assistant daily, and the warnings are reproducible on every connect.
Environment
v0.8.0, as pinned by sendspin-cli 0.3.0ramips/mt7621(MIPS32 little-endian, musl)aiosendspin