Skip to content

feat(source): add a source@v1 capture client (sendspin source) - #270

Draft
rudyberends wants to merge 2 commits into
Sendspin:mainfrom
rudyberends:feat/source-v1
Draft

feat(source): add a source@v1 capture client (sendspin source)#270
rudyberends wants to merge 2 commits into
Sendspin:mainfrom
rudyberends:feat/source-v1

Conversation

@rudyberends

Copy link
Copy Markdown

Adds a sendspin source command: run as a Sendspin source client that captures audio from a local input (line-in/microphone) or a synthetic sine test tone, encodes it (PCM, FLAC, or Opus via PyAV), and streams it to a server, which mixes and distributes it to players.

Implements the client side of the source@v1 role (Sendspin/spec#105).

Depends on Sendspin/aiosendspin#286 (the source@v1 protocol + client SDK). The aiosendspin version pin here must be bumped to the release that includes it before this can merge — hence draft.

Supersedes #123, which targeted the earlier input_stream/* proposal.

What's added

  • source_utils.py: SourceEncoder (PyAV; PCM passthrough / FLAC / Opus, exposing the raw codec header) + an RMS level meter
  • source_stream.py: SourceStreamer — capture loop (sounddevice line-in or sine tone), server-driven start/stop, optional line-sense reporting; stamps each frame with its capture time
  • cli.py: sendspin source subcommand and sendspin audio-devices inputs listing
  • audio_devices.py: input-device enumeration
  • settings.py: SourceSettings persistence (settings-source.json)
  • README.md: source-mode documentation

Capture is 16-bit; the server handles decode/resample/mix. The server is the sole initiator of streaming (a source stays idle until told to start). A device may run both source and player; it never plays its captured input locally.

Testing

  • ruff + mypy clean; pytest: 155 passed (13 new, incl. a cross-repo round-trip: encode with the CLI encoder then decode with the aiosendspin server decoder, for PCM/FLAC/Opus).
  • End-to-end over a real websocket: a source+player client streams a sine tone through a server and receives it back (PCM/FLAC/Opus).

Add a 'sendspin source' command that runs a source client: capture audio
from a local input (line-in/microphone) or a sine test tone, encode it
(PCM, FLAC, or Opus via PyAV), and stream it to a server, which mixes and
distributes it to players. The server controls when the source streams.

- source_utils: PyAV SourceEncoder (exposes raw codec header) + RMS level
- source_stream: SourceStreamer capture loop (sounddevice / sine), server
  start/stop handling, optional line-sense signal reporting
- cli: 'source' subcommand and 'audio-devices inputs' listing
- audio_devices: input-device enumeration
- settings: SourceSettings persistence (settings-source.json)
- README: source mode documentation
Follow aiosendspin dropping supported_formats from source@v1_support
(spec#113). A source announces its capture format per stream in
client_stream/start, so advertising it up front negotiated nothing and the
field being required made a conforming hello fail to parse.

Also spell out in the README that starting a source is a policy the server
application makes: the library no longer starts one on its own, so a source
pointed at a server that never asks for audio stays idle by design.
@jore731

jore731 commented Aug 17, 2026

Copy link
Copy Markdown

I tested this branch against Music Assistant's Sendspin server and traced the source connection path.

The source@v1 capture/streaming implementation works, but the client currently connects only over a plain WebSocket: aiosendspin.client.SendspinClient.connect() calls ws_connect() and immediately sends client/hello. The feat/source-v1 aiosendspin tree has no Noise/pairing implementation or dependency, and sendspin-cli pins aiosendspin~=6.0.1.

Observed server behavior:

  • The WebSocket and client/hello are accepted.
  • Music Assistant logs Accepting unencrypted legacy connection (transition mode).
  • The source is registered as type unknown, rather than as a secure/persisted source client.

This means the branch can exercise source streaming in legacy transition mode, but it cannot pair with a current Music Assistant server yet. Secure source support needs to land in aiosendspin first (the PR body already tracks the dependency on Sendspin/aiosendspin#286), followed by updating this CLI to use the paired/Noise connection flow and persisting the resulting trust material. The source role code itself is not the cause of the current registration issue.

@jore731

jore731 commented Aug 17, 2026

Copy link
Copy Markdown

Update to my earlier note: the current upstream path is now clear.

aiosendspin#292 merged Noise encryption/pairing and trust-store support, and aiosendspin#322 superseded #286 with a paired-client source@v1 implementation. aiosendspin/main now contains both.

This CLI PR still targets the older API (aiosendspin[server]~=6.0.1), so the next step is to rebase this branch onto the current aiosendspin API, update the dependency pin, and wire the CLI's identity/pairing store and pairing UX. The source capture code can then be retained and exercised over the encrypted connection.

No encryption implementation needs to be duplicated in this repository.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants