Files
amethyst/commons/src/commonMain
Claude 0a45d1094f feat(audio-rooms): M8 polish + M3/M9 foreground service
M8a — presence event reflects mic-mute state:
- AudioRoomStage's `publishPresence` now passes the broadcaster's
  current mic state into the kind 10312 `muted` tag: `null` when not
  broadcasting (no mic to be muted on), explicit `true` / `false` while
  the speaker path is `Broadcasting`. Other clients can now render a
  mute indicator on our avatar.

M8b — auto-reconnect with capped exponential backoff:
- AudioRoomViewModel detects `NestsListenerState.Failed` and schedules a
  retry after 1s, 2s, 4s, ..., capped at 16s. Up to 3 attempts before
  the UI stays in Failed for a manual retry.
- User-initiated `connect()` and `disconnect()` reset the retry counter
  so manual recovery starts fresh.

M8c — iOS:
- Skipped: neither `:nestsClient` nor `:quic` declares an iOS target
  yet, so there's no iosMain source set to populate. When iOS lands,
  the audio capture/playback + transport actuals will need iOS impls
  (and the speaker UI will need an iOS shell).

M3 + M9 — foreground service:
- New `AudioRoomForegroundService` (foregroundServiceType
  `mediaPlayback|microphone`) anchors the process so audio keeps
  playing with the screen off. Holds a partial wake-lock + media-style
  notification; the notification's "Stop" action stops the service.
- Lifecycle wired in AudioRoomStage via:
  * LaunchedEffect(isConnected, isBroadcasting) on the listener +
    broadcast UI state — promotes to mediaPlayback+microphone type
    when broadcasting starts (Android 14+ split foreground-type
    permission requirement), falls back to mediaPlayback when only
    listening, stops entirely when listener drops.
  * DisposableEffect(Unit) for screen-exit cleanup.
- The service does NOT own the MoQ session / decoder / player — those
  remain in the VM. Screen-off works; "navigate away keeps audio" would
  require moving the audio stack into the service, which is a bigger
  refactor outside the audio-rooms completion plan's scope.
- Strings: 6 new `audio_room_notification_*` keys.
- Manifest: declares the service with `mediaPlayback|microphone`
  foreground type. RECORD_AUDIO + FOREGROUND_SERVICE_MICROPHONE were
  already declared.

Verified: `./gradlew spotlessApply :commons:jvmTest :nestsClient:jvmTest
:quic:jvmTest :amethyst:compilePlayDebugKotlin` all green.
2026-04-26 03:34:28 +00:00
..