fix(android): clarify channel and voice status icons
This commit is contained in:
@@ -368,6 +368,12 @@ pub struct BridgeClient {
|
||||
pub channel: u64,
|
||||
/// Nickname.
|
||||
pub name: String,
|
||||
/// True when this client has muted microphone/input capture.
|
||||
pub input_muted: bool,
|
||||
/// True when this client has muted speaker/output audio.
|
||||
pub output_muted: bool,
|
||||
/// True when recent inbound voice activity was observed for this client.
|
||||
pub is_speaking: bool,
|
||||
/// True for TeamSpeak ServerQuery clients.
|
||||
pub is_server_query: bool,
|
||||
}
|
||||
@@ -418,6 +424,9 @@ impl From<chanora_protocol::ServerSnapshot> for BridgeSnapshot {
|
||||
id: c.id.0,
|
||||
channel: c.channel.0,
|
||||
name: c.name,
|
||||
input_muted: c.input_muted,
|
||||
output_muted: c.output_muted,
|
||||
is_speaking: c.is_speaking,
|
||||
is_server_query: c.is_server_query,
|
||||
})
|
||||
.collect(),
|
||||
|
||||
@@ -1292,11 +1292,17 @@ impl SseDecode for crate::api::BridgeClient {
|
||||
let mut var_id = <u64>::sse_decode(deserializer);
|
||||
let mut var_channel = <u64>::sse_decode(deserializer);
|
||||
let mut var_name = <String>::sse_decode(deserializer);
|
||||
let mut var_input_muted = <bool>::sse_decode(deserializer);
|
||||
let mut var_output_muted = <bool>::sse_decode(deserializer);
|
||||
let mut var_is_speaking = <bool>::sse_decode(deserializer);
|
||||
let mut var_isServerQuery = <bool>::sse_decode(deserializer);
|
||||
return crate::api::BridgeClient {
|
||||
id: var_id,
|
||||
channel: var_channel,
|
||||
name: var_name,
|
||||
input_muted: var_input_muted,
|
||||
output_muted: var_output_muted,
|
||||
is_speaking: var_is_speaking,
|
||||
is_server_query: var_isServerQuery,
|
||||
};
|
||||
}
|
||||
@@ -1820,6 +1826,9 @@ impl flutter_rust_bridge::IntoDart for crate::api::BridgeClient {
|
||||
self.id.into_into_dart().into_dart(),
|
||||
self.channel.into_into_dart().into_dart(),
|
||||
self.name.into_into_dart().into_dart(),
|
||||
self.input_muted.into_into_dart().into_dart(),
|
||||
self.output_muted.into_into_dart().into_dart(),
|
||||
self.is_speaking.into_into_dart().into_dart(),
|
||||
self.is_server_query.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
@@ -2193,6 +2202,9 @@ impl SseEncode for crate::api::BridgeClient {
|
||||
<u64>::sse_encode(self.id, serializer);
|
||||
<u64>::sse_encode(self.channel, serializer);
|
||||
<String>::sse_encode(self.name, serializer);
|
||||
<bool>::sse_encode(self.input_muted, serializer);
|
||||
<bool>::sse_encode(self.output_muted, serializer);
|
||||
<bool>::sse_encode(self.is_speaking, serializer);
|
||||
<bool>::sse_encode(self.is_server_query, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! * Disconnect is requested via a `oneshot`; the task drains
|
||||
//! `tsclientlib`'s outbound events and exits.
|
||||
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
@@ -36,6 +36,8 @@ use tsproto_types::ClientType;
|
||||
use crate::dto::{ChannelId, ChannelInfo, ClientId, ClientInfo, ServerSnapshot};
|
||||
use crate::ProtocolError;
|
||||
|
||||
const SPEAKING_ACTIVITY_WINDOW: Duration = Duration::from_millis(750);
|
||||
|
||||
/// Pick the TeamSpeak `client_version`/platform/signature triple
|
||||
/// (sourced from `ReSpeak/tsdeclarations/Versions.csv`, baked into
|
||||
/// `tsproto-types` at vendor-time) that best matches the *runtime*
|
||||
@@ -509,6 +511,7 @@ async fn connection_task(
|
||||
std::time::Instant,
|
||||
),
|
||||
> = HashMap::new();
|
||||
let mut voice_activity: HashMap<u64, Instant> = HashMap::new();
|
||||
|
||||
// Main loop: pump events, service requests, forward voice.
|
||||
loop {
|
||||
@@ -530,6 +533,7 @@ async fn connection_task(
|
||||
StreamItem::Audio(buf) => {
|
||||
let from = packet_sender_id(&buf);
|
||||
if let Some(from) = from {
|
||||
voice_activity.insert(from, Instant::now());
|
||||
if voice_in_tx
|
||||
.try_send(InboundVoice {
|
||||
from_client: from,
|
||||
@@ -612,7 +616,7 @@ async fn connection_task(
|
||||
// 3. Service at most one control request (non-blocking).
|
||||
match rx.try_recv() {
|
||||
Ok(Request::Snapshot(reply)) => {
|
||||
let snap = build_snapshot(&con);
|
||||
let snap = build_snapshot(&con, &voice_activity);
|
||||
let _ = reply.send(snap);
|
||||
}
|
||||
Ok(Request::MoveToChannel {
|
||||
@@ -826,7 +830,10 @@ fn emit_subtree<'a, T>(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_snapshot(con: &Connection) -> Result<ServerSnapshot, ProtocolError> {
|
||||
fn build_snapshot(
|
||||
con: &Connection,
|
||||
voice_activity: &HashMap<u64, Instant>,
|
||||
) -> Result<ServerSnapshot, ProtocolError> {
|
||||
let state: &data::Connection = con
|
||||
.get_state()
|
||||
.map_err(|e| ProtocolError::Backend(format!("get_state: {e}")))?;
|
||||
@@ -874,6 +881,11 @@ fn build_snapshot(con: &Connection) -> Result<ServerSnapshot, ProtocolError> {
|
||||
id: ClientId(c.id.0 as u64),
|
||||
channel: ChannelId(c.channel.0),
|
||||
name: sanitize(&c.name),
|
||||
input_muted: c.input_muted,
|
||||
output_muted: c.output_muted || c.output_only_muted,
|
||||
is_speaking: voice_activity
|
||||
.get(&(c.id.0 as u64))
|
||||
.is_some_and(|last| last.elapsed() <= SPEAKING_ACTIVITY_WINDOW),
|
||||
is_server_query: is_server_query_client_type(&c.client_type),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -36,6 +36,12 @@ pub struct ClientInfo {
|
||||
pub channel: ChannelId,
|
||||
/// Nickname, preserved verbatim per ADR-008.
|
||||
pub name: String,
|
||||
/// True when this client has muted microphone/input capture.
|
||||
pub input_muted: bool,
|
||||
/// True when this client has muted speaker/output audio.
|
||||
pub output_muted: bool,
|
||||
/// True when recent inbound voice activity was observed for this client.
|
||||
pub is_speaking: bool,
|
||||
/// True for TeamSpeak ServerQuery clients.
|
||||
pub is_server_query: bool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user