feat(audio-rooms): emit publishing + onstage tags on presence heartbeat
Threads the new kind-10312 presence dimensions through the VM and the
heartbeat:
* AudioRoomUiState.onStageNow: explicit Boolean field, defaults to
true. Tier-2's "leave the stage" tap will flip it to false via
AudioRoomViewModel.setOnStage(...). Drives the
`["onstage", "0|1"]` tag.
* AudioRoomUiState.publishingNow: derived property, true only when
broadcast is Broadcasting AND not muted. Drives the
`["publishing", "0|1"]` tag. Matches the wire-tag semantics
"actually pushing audio packets" (vs holding a slot but silenced).
The heartbeat in AudioRoomActivityContent now reads both values and
passes them to MeetingRoomPresenceEvent.build. onstageTag IS a
LaunchedEffect key (so leaving the stage triggers an immediate
publish), publishingTag is NOT (mute toggles already publish via the
debounced effect).
The leave / dispose path now publishes publishing=false + onstage=false
explicitly, so aggregating peers can drop us from the grid immediately
instead of waiting out the staleness window.
Tests:
* AudioRoomViewModelTest::onStageNowDefaultsTrueAndSetOnStageFlipsIt
* AudioRoomViewModelTest::publishingNowDerivesFromBroadcastStateAndMute
(covers all four BroadcastUiState states + the muted-broadcasting
edge case where publishingNow must be false despite holding a slot)
This commit is contained in:
+35
@@ -136,6 +136,41 @@ class AudioRoomViewModelTest {
|
||||
assertFalse(vm.uiState.value.isMuted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onStageNowDefaultsTrueAndSetOnStageFlipsIt() =
|
||||
runTest {
|
||||
val vm = newViewModel { FakeNestsListener() }
|
||||
|
||||
// Defaults to true so a freshly-joined speaker advertises
|
||||
// onstage=1 on the first heartbeat (the heartbeat in
|
||||
// AudioRoomActivityContent reads this as the tag value).
|
||||
assertTrue(vm.uiState.value.onStageNow)
|
||||
vm.setOnStage(false)
|
||||
assertFalse(vm.uiState.value.onStageNow)
|
||||
vm.setOnStage(true)
|
||||
assertTrue(vm.uiState.value.onStageNow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun publishingNowDerivesFromBroadcastStateAndMute() {
|
||||
// Idle / connecting / failed: never publishing.
|
||||
assertFalse(AudioRoomUiState().publishingNow)
|
||||
assertFalse(AudioRoomUiState(broadcast = BroadcastUiState.Connecting).publishingNow)
|
||||
assertFalse(AudioRoomUiState(broadcast = BroadcastUiState.Failed("boom")).publishingNow)
|
||||
|
||||
// Broadcasting unmuted: publishing.
|
||||
assertTrue(
|
||||
AudioRoomUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = false)).publishingNow,
|
||||
)
|
||||
|
||||
// Broadcasting muted: NOT publishing — matches the wire-tag
|
||||
// semantics ("publishing=1" means actually pushing packets,
|
||||
// not "hold a slot but silenced").
|
||||
assertFalse(
|
||||
AudioRoomUiState(broadcast = BroadcastUiState.Broadcasting(isMuted = true)).publishingNow,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connectIsIdempotentWhileConnecting() =
|
||||
runTest {
|
||||
|
||||
Reference in New Issue
Block a user