feat(nests): empty-stage hint, local hush, moderator/force-mute moderation
Three improvements stacked into one commit since they share files: #3 Empty-stage hint: StageGrid no longer disappears when nobody is on stage. The "Stage" label stays and a quiet "Waiting for speakers…" line keeps the strip visible so the room doesn't look broken before the first speaker arrives. #5 Local per-speaker hush: AudioPlayer gains a setVolume(Float) default-no-op method; AudioTrackPlayer composes mute and volume multiplicatively into AudioTrack.setVolume so a hushed stream stays silent regardless of mute state. NestViewModel exposes locallyHushed: ImmutableSet<String> in NestUiState plus setLocalHushed(pubkey, hushed) — applied at attach time so a re-subscribe of an already-hushed speaker stays silent. New "Hush this speaker" / "Restore this speaker" row in ParticipantHostActionsSheet, available to anyone (it affects only our own playback, nothing on the wire). #4 Host moderation gaps: wires Promote-to-Moderator using the existing RoomParticipantActions.setRole(ROLE.MODERATOR) builder — UI gap only, no protocol change. Adds a new AdminCommandEvent.Action.MUTE variant + AdminCommandEvent.forceMute(room, target) builder; the sheet emits it on "Force-mute speaker" and the AdminCommandsCollector dispatches incoming MUTE actions to a new NestViewModel.onForceMuted() that routes through the existing setMicMuted(true) path. Honor-based, same trust model as KICK — relays don't enforce signer authority, the client checks the signer is host or moderator on the active kind-30312.
This commit is contained in:
+14
-7
@@ -42,6 +42,7 @@ class AudioTrackPlayer(
|
||||
) : AudioPlayer {
|
||||
private var track: AudioTrack? = null
|
||||
private var muted: Boolean = false
|
||||
private var volume: Float = 1f
|
||||
|
||||
override fun start() {
|
||||
if (track != null) return
|
||||
@@ -105,7 +106,7 @@ class AudioTrackPlayer(
|
||||
t,
|
||||
)
|
||||
}
|
||||
applyMuteVolume(newTrack, muted)
|
||||
applyMuteVolume(newTrack)
|
||||
track = newTrack
|
||||
}
|
||||
|
||||
@@ -126,7 +127,12 @@ class AudioTrackPlayer(
|
||||
|
||||
override fun setMuted(muted: Boolean) {
|
||||
this.muted = muted
|
||||
track?.let { applyMuteVolume(it, muted) }
|
||||
track?.let { applyMuteVolume(it) }
|
||||
}
|
||||
|
||||
override fun setVolume(volume: Float) {
|
||||
this.volume = volume.coerceIn(0f, 1f)
|
||||
track?.let { applyMuteVolume(it) }
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
@@ -141,13 +147,14 @@ class AudioTrackPlayer(
|
||||
@Suppress("unused")
|
||||
val voiceCallUsage: Int get() = AudioManager.STREAM_VOICE_CALL // kept for documentation
|
||||
|
||||
private fun applyMuteVolume(
|
||||
track: AudioTrack,
|
||||
muted: Boolean,
|
||||
) {
|
||||
private fun applyMuteVolume(track: AudioTrack) {
|
||||
// setVolume is preferred over pause(): it keeps the streaming pipeline
|
||||
// running so unmute is sample-accurate and there's no AudioTrack-restart
|
||||
// glitch. AudioTrack default gain is 1.0 (RFC: AudioTrack#setVolume).
|
||||
runCatching { track.setVolume(if (muted) 0f else 1f) }
|
||||
// Mute and per-stream volume compose multiplicatively — a muted stream
|
||||
// is silent regardless of volume, and a stream at volume 0 is silent
|
||||
// regardless of mute.
|
||||
val gain = if (muted) 0f else volume
|
||||
runCatching { track.setVolume(gain) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user