Skip to content

Add announcement role (announcement@v1) skeleton#101

Draft
marcelveldt wants to merge 3 commits into
mainfrom
announcement-role
Draft

Add announcement role (announcement@v1) skeleton#101
marcelveldt wants to merge 3 commits into
mainfrom
announcement-role

Conversation

@marcelveldt

@marcelveldt marcelveldt commented Jul 23, 2026

Copy link
Copy Markdown

Draft implementation skeleton for the announcement@v1 role proposed in Sendspin/spec#126 — short per-client audio clips (TTS, chimes, alerts) decoded on a second concurrent pipeline next to the media stream, with the ducking policy delivered to the embedder. Opening this alongside the spec PR so the discussion has a concrete client-side shape to poke at; expect it to track whatever the spec discussion decides (including the role id, if we stage it as announcement@_draft_r1 first).

What's here

  • Wire surface: announcement@v1 role id, AnnouncementSupportObject (formats + own buffer_capacity) in client/hello, stream/start.announcement parsing (codec params + media_duck_db 0–50 / duck_ramp_ms 0–2000 / optional volume, out-of-range values dropped to defaults), client/state.announcement (state, required_lead_time_ms), binary type 24 (SENDSPIN_ROLE_ANNOUNCEMENT = 6, routed through the existing get_binary_role() helper unchanged).
  • AnnouncementRole following the documented add-a-role pattern (public header + pimpl + inbox slot + shared event ring), with an AnnouncementRoleListener the embedder implements: on_announcement_write() (PCM, task thread) plus on_announcement_start/end/clear() (main loop). on_announcement_start() carries the duck parameters — that's where e.g. ESPHome calls mixer_speaker.apply_ducking(); on_announcement_end() fires on server end, local completion, abort, and transport loss alike, so media can never stay stuck ducked.
  • AnnouncementTask: a dedicated lightweight decode task (own ring buffer + SendspinDecoder instance, one extra thread, default priority below the media sync task). Deliberately no Kalman/sync machinery: announcements are per-client, so the task starts output at the first chunk's timestamp via the time filter (or immediately without sync) — honoring that timestamp is also what aligns a coordinated multi-speaker announcement — never drops late chunks, is paced by the sink's blocking writes, and ends a stalled stream locally after 5 s.
  • Lifecycle wiring in SendspinClient: stream/end with omitted roles ends announcements too; stream/clear with omitted roles (a media seek) explicitly does not touch the announcement stream — only an explicit roles: ["announcement"] does.
  • Build gates: SENDSPIN_ENABLE_ANNOUNCEMENT (Kconfig default n while the spec is draft; depends on SENDSPIN_ENABLE_PLAYER since it reuses the ring buffer + decoder sources).
  • Tests: protocol round-trip coverage for the hello support object, stream/start defaults/validation, client/state serialization, and the binary allocation. Host build compiles clean (-Wall -Wextra -Wpedantic), full suite passes.

Deliberately not here (yet)

  • The ESPHome layer (a SendspinAnnouncementSource feeding the speaker_source announcement pipeline + duck-parameter plumbing) lives in the esphome repo and needs a released sendspin-cpp first.
  • Gain handling: per the spec draft, duck and announcement volume are applied by the embedder at the sink/mixer (where media volume already lives) — the library only transports the parameters.
  • The stream-replace (stream/clear) boundary is approximate in the task (buffered chunks racing the drain may go with the old clip); fine for silence-padded replacement clips, noted inline.
  • Announcement-capable secondary connections (spec open question 6 — e.g. HA announcing while another server streams) would live in the connection manager / admission logic, not in this role.

Marked draft until the spec PR settles.

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.

1 participant