refactor(nests): per-stream channel count + AudioBroadcastConfig (I4 prep)

Split the previously global `AudioFormat.CHANNELS = 1` into a
`DEFAULT_CHANNELS` constant + per-call-site `channelCount` parameters
so a single broadcast can advertise stereo Opus without forcing every
mono call site to grow a new argument. Generalises the catalog factory
to `MoqLiteHangCatalog.opus48k(name, channels)` with memoised JSON
bytes per shape, threads a new `AudioBroadcastConfig(channelCount)`
through `connectNestsSpeaker` / `connectReconnectingNestsSpeaker` /
`MoqLiteNestsSpeaker`, and adds a `channelCount` parameter to
`MediaCodecOpusEncoder`. Production behaviour is unchanged for
mono callers (the new config defaults to mono); the listener side
already discovers the channel count from the catalog via
`NestViewModel.awaitAudioPipelineConfig`. No test or wire changes.

Phase 1 of `nestsClient/plans/2026-05-06-i4-stereo-cross-stack-scenario.md`.
The hang-interop test scaffolding (`HangInteropTest`, `runSpeakerToHangListen`,
Rust `hang-listen` / `hang-publish`, `JvmOpusEncoder`) doesn't exist on
this branch yet, so the I4 forward + reverse scenarios are deferred
until the parent T16 plan lands.

https://claude.ai/code/session_01EqJEADzH9yjSuoP5L9js8i
This commit is contained in:
Claude
2026-05-06 22:57:21 +00:00
parent 32e578dcc8
commit 23b8bfd34a
13 changed files with 254 additions and 59 deletions
@@ -188,6 +188,13 @@ suspend fun connectNestsSpeaker(
framesPerGroup: Int =
com.vitorpamplona.nestsclient.audio
.NestMoqLiteBroadcaster.DEFAULT_FRAMES_PER_GROUP,
/**
* Per-broadcast audio shape. Defaults to mono so existing call sites
* keep the prior behaviour. Caller is responsible for matching the
* channel count to the encoder + capture they pass — see
* [AudioBroadcastConfig].
*/
broadcastConfig: AudioBroadcastConfig = AudioBroadcastConfig(),
): NestsSpeaker {
val state =
MutableStateFlow<NestsSpeakerState>(
@@ -252,6 +259,7 @@ suspend fun connectNestsSpeaker(
scope = scope,
mutableState = state,
framesPerGroup = framesPerGroup,
broadcastConfig = broadcastConfig,
)
}