refactor: rename Audio Room → Nest project-wide

Aligns class names, package paths, string resource keys, UI text and
intent actions with the Nests branding used by the EGG specs in
nestsClient/specs/. Mechanical rename — no behavior change.

- Folders: audiorooms/ → nests/ (5 paths across amethyst, quartz)
- 30+ class renames (AudioRoom* → Nest*, AudioRooms* → Nests*)
- String resource keys audio_room_* → nest_*
- UI strings "Audio Room"/"Audio Rooms" → "Nest"/"Nests" (incl. all locales)
- Intent extras AUDIO_ROOM_* → NEST_*
- Compose route Route.AudioRooms → Route.Nests

Spec-aligned identifiers (MeetingSpaceEvent, meetingSpaces/, the nip53*
packages) are intentionally untouched — those are NIP-53 protocol
names, not "audio room" branding.

https://claude.ai/code/session_01RDpuki4t8StSg1CZcXnV5b
This commit is contained in:
Claude
2026-04-27 14:36:50 +00:00
parent 208c90b246
commit dc3ac31ae4
74 changed files with 565 additions and 565 deletions
@@ -38,7 +38,7 @@ import java.util.concurrent.atomic.AtomicLong
* Moq-lite-backed [NestsListener]. Wraps a connected [MoqLiteSession]
* and exposes the same listener API the IETF [DefaultNestsListener]
* does, so [connectNestsListener] can swap the framing layer without
* changing the public surface that [com.vitorpamplona.amethyst.commons.viewmodels.AudioRoomViewModel]
* changing the public surface that [com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel]
* and downstream UI consume.
*
* Subscription mapping per the audio-rooms NIP draft + nests JS
@@ -21,7 +21,7 @@
package com.vitorpamplona.nestsclient
import com.vitorpamplona.nestsclient.audio.AudioCapture
import com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster
import com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster
import com.vitorpamplona.nestsclient.audio.OpusEncoder
import com.vitorpamplona.nestsclient.moq.lite.MoqLitePublisherHandle
import com.vitorpamplona.nestsclient.moq.lite.MoqLiteSession
@@ -76,7 +76,7 @@ class MoqLiteNestsSpeaker internal constructor(
throw t
}
val broadcaster =
AudioRoomMoqLiteBroadcaster(
NestMoqLiteBroadcaster(
capture = captureFactory(),
encoder = encoderFactory(),
publisher = publisher,
@@ -139,7 +139,7 @@ class MoqLiteNestsSpeaker internal constructor(
}
internal class MoqLiteBroadcastHandle(
private val broadcaster: AudioRoomMoqLiteBroadcaster,
private val broadcaster: NestMoqLiteBroadcaster,
private val publisher: MoqLitePublisherHandle,
private val parent: MoqLiteNestsSpeaker,
) : BroadcastHandle {
@@ -21,7 +21,7 @@
package com.vitorpamplona.nestsclient
import com.vitorpamplona.nestsclient.audio.AudioCapture
import com.vitorpamplona.nestsclient.audio.AudioRoomBroadcaster
import com.vitorpamplona.nestsclient.audio.NestBroadcaster
import com.vitorpamplona.nestsclient.audio.OpusEncoder
import com.vitorpamplona.nestsclient.moq.MoqProtocolException
import com.vitorpamplona.nestsclient.moq.MoqSession
@@ -133,7 +133,7 @@ sealed class NestsSpeakerState {
* unit-test suite (`NestsSpeakerTest`) and for any future IETF
* MoQ-transport target.
*
* Wraps a connected [MoqSession] and plumbs an [AudioRoomBroadcaster]
* Wraps a connected [MoqSession] and plumbs an [NestBroadcaster]
* through it on [startBroadcasting]. Construction does NOT open the
* transport.
*/
@@ -168,7 +168,7 @@ class DefaultNestsSpeaker internal constructor(
throw t
}
val broadcaster =
AudioRoomBroadcaster(
NestBroadcaster(
capture = captureFactory(),
encoder = encoderFactory(),
publisher = publisher,
@@ -236,7 +236,7 @@ class DefaultNestsSpeaker internal constructor(
}
internal class DefaultBroadcastHandle(
private val broadcaster: AudioRoomBroadcaster,
private val broadcaster: NestBroadcaster,
private val announce: MoqSession.AnnounceHandle,
private val parent: DefaultNestsSpeaker,
) : BroadcastHandle {
@@ -28,7 +28,7 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
/**
* Inverse of [AudioRoomPlayer]: pulls PCM from an [AudioCapture], runs it
* Inverse of [NestPlayer]: pulls PCM from an [AudioCapture], runs it
* through an [OpusEncoder], and pushes the resulting Opus packets into a
* [MoqSession.TrackPublisher] as MoQ OBJECT_DATAGRAMs.
*
@@ -46,7 +46,7 @@ import kotlinx.coroutines.launch
* Encode failures are reported via [onError] but do NOT tear the loop down
* one bad frame shouldn't end the broadcast.
*/
class AudioRoomBroadcaster(
class NestBroadcaster(
private val capture: AudioCapture,
private val encoder: OpusEncoder,
private val publisher: MoqSession.TrackPublisher,
@@ -66,8 +66,8 @@ class AudioRoomBroadcaster(
* surface it to the user.
*/
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
check(!stopped) { "AudioRoomBroadcaster already stopped" }
check(job == null) { "AudioRoomBroadcaster.start already called" }
check(!stopped) { "NestBroadcaster already stopped" }
check(job == null) { "NestBroadcaster.start already called" }
// Audit round-2 MoQ #7: capture.start() can throw before we have
// a job. Mark stopped + propagate so a half-started capture isn't
@@ -28,7 +28,7 @@ import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
/**
* Mirror of [AudioRoomBroadcaster] but driving a moq-lite
* Mirror of [NestBroadcaster] but driving a moq-lite
* [MoqLitePublisherHandle] instead of an IETF `MoqSession.TrackPublisher`.
* Keeps the IETF broadcaster intact for the IETF unit-test suite while
* letting the production speaker path use moq-lite.
@@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
* Lifecycle and audit comments mirror the IETF version 1:1 only the
* sink type changes.
*/
class AudioRoomMoqLiteBroadcaster(
class NestMoqLiteBroadcaster(
private val capture: AudioCapture,
private val encoder: OpusEncoder,
private val publisher: MoqLitePublisherHandle,
@@ -55,8 +55,8 @@ class AudioRoomMoqLiteBroadcaster(
* the exception propagates.
*/
fun start(onError: (AudioException) -> Unit = { /* swallow */ }) {
check(!stopped) { "AudioRoomMoqLiteBroadcaster already stopped" }
check(job == null) { "AudioRoomMoqLiteBroadcaster.start already called" }
check(!stopped) { "NestMoqLiteBroadcaster already stopped" }
check(job == null) { "NestMoqLiteBroadcaster.start already called" }
try {
capture.start()
@@ -33,7 +33,7 @@ import kotlinx.coroutines.launch
* through an [OpusDecoder] into an [AudioPlayer].
*
* Single-track. To play multiple speakers in a room, instantiate one
* [AudioRoomPlayer] per [com.vitorpamplona.nestsclient.moq.SubscribeHandle];
* [NestPlayer] per [com.vitorpamplona.nestsclient.moq.SubscribeHandle];
* each owns its own decoder (Opus state is per-track).
*
* Lifecycle:
@@ -41,7 +41,7 @@ import kotlinx.coroutines.launch
* - [stop] cancels the decode loop, stops the player, and releases the
* decoder. Idempotent.
*/
class AudioRoomPlayer(
class NestPlayer(
private val decoder: OpusDecoder,
private val player: AudioPlayer,
private val scope: CoroutineScope,
@@ -61,8 +61,8 @@ class AudioRoomPlayer(
objects: Flow<MoqObject>,
onError: (AudioException) -> Unit = { /* swallow */ },
) {
check(!stopped) { "AudioRoomPlayer already stopped" }
check(job == null) { "AudioRoomPlayer.play already called" }
check(!stopped) { "NestPlayer already stopped" }
check(job == null) { "NestPlayer.play already called" }
player.start()
job =
@@ -28,14 +28,14 @@ import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class AudioRoomBroadcasterTest {
class NestBroadcasterTest {
@Test
fun pcm_frames_flow_through_encoder_into_publisher_in_order() =
runTest {
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xAA.toByte()))
val publisher = RecordingPublisher("track")
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
broadcaster.start()
// Capture exhausts after 3 frames (readFrame returns null) so the
@@ -61,7 +61,7 @@ class AudioRoomBroadcasterTest {
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xBB.toByte()))
val publisher = RecordingPublisher("track")
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
broadcaster.setMuted(true)
broadcaster.start()
@@ -81,7 +81,7 @@ class AudioRoomBroadcasterTest {
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2)))
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xCC.toByte()), warmupSkips = 1)
val publisher = RecordingPublisher("track")
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
broadcaster.start()
capture.awaitDrained()
@@ -100,7 +100,7 @@ class AudioRoomBroadcasterTest {
val capture = ScriptedCapture(listOf(shortArrayOf(1), shortArrayOf(2), shortArrayOf(3)))
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xDD.toByte()), failOnNthCall = 1)
val publisher = RecordingPublisher("track")
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
val errors = mutableListOf<AudioException>()
broadcaster.start { errors.add(it) }
@@ -117,7 +117,7 @@ class AudioRoomBroadcasterTest {
val capture = ScriptedCapture(listOf(shortArrayOf(1)))
val encoder = ScriptedEncoder(prefix = byteArrayOf(0xEE.toByte()))
val publisher = RecordingPublisher("track")
val broadcaster = AudioRoomBroadcaster(capture, encoder, publisher, backgroundScope)
val broadcaster = NestBroadcaster(capture, encoder, publisher, backgroundScope)
broadcaster.start()
capture.awaitDrained()
@@ -31,7 +31,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class AudioRoomPlayerTest {
class NestPlayerTest {
@Test
fun every_object_payload_is_decoded_and_enqueued_in_order() =
runTest {
@@ -45,7 +45,7 @@ class AudioRoomPlayerTest {
moqObject(byteArrayOf(0x04, 0x05, 0x06)),
)
val sut = AudioRoomPlayer(decoder, player, this)
val sut = NestPlayer(decoder, player, this)
sut.play(objects)
testScheduler.advanceUntilIdle()
@@ -83,7 +83,7 @@ class AudioRoomPlayerTest {
moqObject(byteArrayOf(0x02)),
)
val sut = AudioRoomPlayer(decoder, player, this)
val sut = NestPlayer(decoder, player, this)
sut.play(objects, onError = { errors.add(it) })
testScheduler.advanceUntilIdle()
@@ -102,7 +102,7 @@ class AudioRoomPlayerTest {
runTest {
val decoder = FakeOpusDecoder { byteToShorts(it) }
val player = FakeAudioPlayer()
val sut = AudioRoomPlayer(decoder, player, this)
val sut = NestPlayer(decoder, player, this)
sut.play(flowOf())
testScheduler.advanceUntilIdle()
sut.stop()
@@ -115,7 +115,7 @@ class AudioRoomPlayerTest {
fun play_cannot_be_called_twice_on_the_same_instance() =
runTest {
val sut =
AudioRoomPlayer(
NestPlayer(
FakeOpusDecoder { byteToShorts(it) },
FakeAudioPlayer(),
this,
@@ -129,7 +129,7 @@ class AudioRoomPlayerTest {
fun play_after_stop_is_rejected() =
runTest {
val sut =
AudioRoomPlayer(
NestPlayer(
FakeOpusDecoder { byteToShorts(it) },
FakeAudioPlayer(),
this,
@@ -143,7 +143,7 @@ class AudioRoomPlayerTest {
runTest {
val decoder = FakeOpusDecoder { ShortArray(0) }
val player = FakeAudioPlayer()
val sut = AudioRoomPlayer(decoder, player, this)
val sut = NestPlayer(decoder, player, this)
sut.play(flowOf(moqObject(byteArrayOf(0x01))))
testScheduler.advanceUntilIdle()
assertEquals(0, player.queued.size)
@@ -157,7 +157,7 @@ class AudioRoomPlayerTest {
val decoder = FakeOpusDecoder { byteToShorts(it) }
val player = FakeAudioPlayer()
val sut = AudioRoomPlayer(decoder, player, this)
val sut = NestPlayer(decoder, player, this)
sut.play(channel.receiveAsFlow())
testScheduler.runCurrent()
@@ -51,7 +51,7 @@ import kotlin.test.fail
* - the broadcaster keeps the capture pump running while muted (so
* unmute is sample-accurate)
* - frames pushed during mute do NOT reach subscribers (`if (muted)
* continue` in [com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster])
* continue` in [com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster])
* - frames pushed before / after the muted window DO reach
* subscribers, with the muted ones missing entirely (no silent
* placeholder frames)
@@ -23,7 +23,7 @@ package com.vitorpamplona.nestsclient.interop
import com.vitorpamplona.nestsclient.NestsRoomConfig
import com.vitorpamplona.nestsclient.OkHttpNestsClient
import com.vitorpamplona.nestsclient.audio.AudioCapture
import com.vitorpamplona.nestsclient.audio.AudioRoomMoqLiteBroadcaster
import com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster
import com.vitorpamplona.nestsclient.audio.OpusEncoder
import com.vitorpamplona.nestsclient.connectNestsListener
import com.vitorpamplona.nestsclient.connectNestsSpeaker
@@ -57,7 +57,7 @@ import kotlin.test.fail
* (`MoqLitePublisherHandle`), the listener subscribes by speaker
* pubkey (`broadcast=pubkey`, `track="audio/data"`), the speaker
* pushes deterministic Opus-shaped payloads through the
* [AudioRoomMoqLiteBroadcaster] pipeline, and the listener collects
* [NestMoqLiteBroadcaster] pipeline, and the listener collects
* them via [SubscribeHandle.objects] (which the moq-lite adapter
* wraps over `MoqLiteFrame` so existing decoder / player code keeps
* working).