feat(audio-rooms): announce-driven speaker discovery (T4 #17b)
Surface moq-lite's ANNOUNCE flow on NestsListener so the audio
room can render an authoritative "actively broadcasting"
indicator independent of kind-10312 presence's `publishing`
flag. Same channel nostrnests' web client uses for its live
badges (`useRoomAnnouncements` in the JS reference).
Wire-up:
* RoomAnnouncement(pubkey, active) data class — one update
per publisher transition (Active → broadcast came up,
inactive → broadcast went down).
* NestsListener.announces(): Flow<RoomAnnouncement> with a
default body that throws UnsupportedOperationException on
the IETF reference path.
* MoqLiteNestsListener implements via session.announce("")
against the room's namespace; the suffix carries the
speaker pubkey hex straight through.
* ReconnectingNestsListener routes via collectLatest so the
consumer-facing flow restarts against each new session
after a refresh / reconnect (no SubscribeHandle re-issuance
pump needed — announces is a cold per-collect stream).
* AudioRoomViewModel.announcedSpeakers: StateFlow<Set<String>>
populated by observeAnnounces. Active emissions add the
pubkey, inactive emissions remove it. Cleared on teardown.
IETF listeners leave the set empty; UI falls back to
presence's publishingNow flag.
Tests:
* NestsListenerCatalogTest — adds the announces() default-
throws-on-collect case.
Closes the audit's Tier-4 #17b gap. UI integration (e.g. a green
"live" dot driven by announcedSpeakers) is a follow-up — the data
flow is in place and downstream consumers can opt in.
This commit is contained in:
+15
@@ -53,4 +53,19 @@ class NestsListenerCatalogTest {
|
||||
listener.subscribeCatalog("speakerPubkey")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ietfDefaultListenerAnnouncesFlowThrowsOnCollect() =
|
||||
runTest {
|
||||
// The announce-prefix channel is moq-lite only as well —
|
||||
// the IETF reference path's announces() default body
|
||||
// throws UnsupportedOperationException at collect time.
|
||||
// Callers mixing the two protocols can `runCatching` the
|
||||
// collect to no-op cleanly.
|
||||
val listener = IetfStyleListener()
|
||||
val flow = listener.announces()
|
||||
assertFailsWith<UnsupportedOperationException> {
|
||||
flow.collect { /* unreachable */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user