Files
podman/docs/test-audio.md
T
Kartikeya 7269098ea3 feat(frontend): shared pod-wide test audio
Make the "Test audio" connectivity check pod-wide instead of local to the
publisher. Shared on/off state is derived from the presence of the podman-beat
track across participants (self-syncing on join/leave). Any participant can
stop it: the owner unpublishes directly; non-owners send a new additive
BEAT_STOP data message that the owner honors. Drives the button label, status
line, and status waveform for everyone, and guards against rapid-double-click
double-publish and mid-start unmount leaks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 23:39:34 -07:00

3.0 KiB

Shared Test Audio — pod-wide connectivity check

Spec for the frontend/src/livekit/useBeat.ts + PodView.tsx test-audio behavior and the additive BEAT_STOP data message. Satisfies the documentation-first gate for those files.

Why

The Test audio button is PodMan's pre-flight check that the LiveKit audio path works for the whole pod — the same path the urgent Gemini-TTS voice escalation rides on. Today the beat is published correctly but its on/off state is local to the publisher: teammates can't see it's playing and can't stop it. This makes it a shared, pod-wide toggle so a judge sees the state flip on every screen at once.

Behavior

  • Any participant clicks Test audio → they publish the podman-beat audio track (Web Audio, lib/beat.ts). Everyone auto-subscribes and hears it.
  • The shared on/off state is derived from the track's presence, not a synced flag — so it self-syncs across joins/leaves and can't drift from reality. The publisher is the owner.
  • Anyone can stop it:
    • Owner clicks Stop audio → unpublishes its own track directly.
    • Non-owner clicks Stop (<owner>'s) → sends BEAT_STOP; the owner unpublishes. (LiveKit forbids unpublishing another participant's track, so a request is the only way.)
  • The Status card shows publishing / <owner> playing / ready, and the waveform animates (active) for everyone while the test is live.

State derivation (source of truth = the track)

useBeat(room) returns { on, by, mine }, recomputed from the presence of a track named podman-beat across localParticipant + remoteParticipants on these events: LocalTrackPublished/Unpublished, TrackPublished/Unpublished, TrackSubscribed/Unsubscribed, ParticipantConnected/Disconnected. Owner disconnect and late-join sync therefore need no extra messaging.

Contract (additive)

shared/src/messages.ts — one new message on the existing podman.intervention data topic:

| { type: 'BEAT_STOP' }   // any participant → owner: stop the shared beat

Additive to the DataMessage union; existing consumers ignore unknown types. No backend / API change.

Known limitation (LiveKit constraint)

A client can only unpublish its own tracks, so a non-owner's Stop is a BEAT_STOP request the owner must honor. If the owner disconnects uncleanly (crash / network drop), the SFU keeps the track published until it times the participant out — during that window the beat keeps playing and non-owners can't stop it. A clean disconnect clears it immediately via ParticipantDisconnected. Demo mitigation: have the same person who starts the test also stop it.

Files

  • shared/src/messages.tsBEAT_STOP message (additive).
  • frontend/src/livekit/useBeat.tsuseBeat(room) hook.
  • frontend/src/components/PodView.tsx — button label, status line, waveform active driven by the hook.
  • frontend/src/lib/beat.ts — unchanged (existing Web-Audio beat source).