Skip to content

client/state is flagged non-compliant: legacy top-level 'state', and the required player timing fields are never sent #131

Description

@mguaylam

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions