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:
+23
@@ -71,6 +71,16 @@ class AdminCommandEvent(
|
||||
val code: String,
|
||||
) {
|
||||
KICK("kick"),
|
||||
|
||||
/**
|
||||
* Force the target speaker's mic to muted. Honor-based: the
|
||||
* target's client receives the command and flips its own
|
||||
* presence-side mic-mute. A non-cooperating client could
|
||||
* ignore the event and keep broadcasting — same trust model
|
||||
* as KICK, which a misbehaving client could also ignore by
|
||||
* not disconnecting.
|
||||
*/
|
||||
MUTE("mute"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
@@ -95,5 +105,18 @@ class AdminCommandEvent(
|
||||
aTag(room)
|
||||
add(PTag(target).toTagArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a force-mute command. Same envelope as [kick] —
|
||||
* tagged for [room] + [target], action verb in content.
|
||||
*/
|
||||
fun forceMute(
|
||||
room: ATag,
|
||||
target: HexKey,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
) = eventTemplate<AdminCommandEvent>(KIND, Action.MUTE.code, createdAt) {
|
||||
aTag(room)
|
||||
add(PTag(target).toTagArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user