feat(voice): real-time mic input level metering at 30 Hz (#25)

* feat(voice): add real-time mic input level metering at 30 Hz

Expose input RMS from the audio engine through the bridge as a
dedicated Rust→Dart Stream<double>, replacing the binary on/off
indicator with a proportional dBFS level meter.

Rust side:
- chanora_audio: add set_input_dbfs/input_dbfs accessors to
  SharedAudioProcessingStats; restructure CaptureState::ingest()
  to compute dBFS from mono buffer before the PTT guard so the
  meter shows mic activity even when not transmitting.
- chanora_core: widen audio_stats() return to include f32 input
  level.
- chanora_bridge: add input_level: f32 to BridgeAudioStats and
  new input_level_stream(sink: StreamSink<f32>) that pushes at
  ~30 Hz via tokio interval task.
- Update frb_generated.rs serialization for the new field.

Flutter side:
- VoiceLevelMeter: accept optional double level (dBFS), map
  -60..0 dBFS to 0..1 fill fraction, animate with
  TweenAnimationBuilder for smooth transitions.
- voice_compact.dart: subscribe to inputLevelStream in the voice
  details sheet for 30 Hz meter updates, keeping 250 ms poll for
  TX/RX counters.
- voice_bar.dart: accept optional inputLevel from the stream.
- main.dart: subscribe to inputLevelStream, pass to VoiceBar.

* chore: sync Flutter build config and dependency updates

- Add Flutter migrator flags to gradle.properties (builtInKotlin, newDsl)
- Add FlutterGeneratedPluginSwiftPackage to iOS/macOS Xcode projects
- Update meta 1.17→1.18, test_api 0.7.10→0.7.11
- Rebuild chanora_bridge framework for macOS
- Update Podfile.lock for iOS and macOS

* fix(voice): correct meter animation, pre-gain dBFS, stream lifecycle, and protocol warnings

B1: Convert VoiceLevelMeter to StatefulWidget tracking previous fill
     as Tween begin so the meter animates smoothly instead of resetting
     to zero on every frame.

B2: Compute dBFS from pre-gain mono samples in CaptureState::ingest()
     so the level meter reflects raw mic input, matching mobile paths.

B4: End input_level_stream after 10 consecutive session errors instead
     of emitting -120 dBFS forever when the session is gone.

Also fixes all 13 clippy warnings in chanora_protocol: collapsed
nested if-let patterns, replaced .ok() + Some matching with Ok, used
? operator, and introduced EventChannels struct to reduce the four
helper functions below the 7-argument threshold.

* fix(voice): use MissedTickBehavior::Skip for level meter stream and align dBFS doc

Set MissedTickBehavior::Skip on the input_level_stream tokio interval
so slow audio_stats() calls skip missed ticks instead of bursting,
preventing CPU spikes on the UI meter thread.

Align VoiceLevelMeter class doc: the mapping floors at -60 dBFS
(via dbfsToFraction), not the full -120 range.
This commit is contained in:
Edison Jwa
2026-06-05 20:57:16 +09:00
committed by GitHub
parent 2c7b68e21e
commit 82441f3d97
25 changed files with 519 additions and 258 deletions
@@ -404,6 +404,19 @@ impl Default for SharedAudioProcessingStats {
}
impl SharedAudioProcessingStats {
/// Store the raw input dBFS level (desktop capture path).
/// Mobile platforms use [`Self::update_capture`] instead, which
/// also records VAD state; this lighter method is for the cpal
/// capture path that has no VAD pipeline.
pub fn set_input_dbfs(&self, dbfs: f32) {
self.input_dbfs.store(dbfs.to_bits(), Ordering::Relaxed);
}
/// Read the current input dBFS level.
pub fn input_dbfs(&self) -> f32 {
f32::from_bits(self.input_dbfs.load(Ordering::Relaxed))
}
/// Store capture levels and VAD state.
pub fn update_capture(
&self,
+31 -17
View File
@@ -838,6 +838,7 @@ impl AudioEngine {
transmit_flag_for_capture,
frames_sent.clone(),
cfg.mic_gain,
audio_processing_stats.clone(),
);
let (input_stream, capture_active) = match capture_result {
Ok(s) => (Some(s), true),
@@ -1597,6 +1598,11 @@ impl AudioEngine {
self.frames_received.load(Ordering::Relaxed)
}
/// Current microphone input level in dBFS (-120.0 = silence, 0.0 = clipping).
pub fn input_level(&self) -> f32 {
self.audio_processing_stats.input_dbfs()
}
/// Current audio-processing config snapshot.
pub fn audio_processing_config_snapshot(&self) -> crate::AudioProcessingConfig {
self.audio_processing_config.lock().unwrap().clone()
@@ -1717,6 +1723,7 @@ fn try_open_capture(
transmit_active: Arc<AtomicBool>,
frames_sent: Arc<AtomicU32>,
mic_gain: f32,
audio_processing_stats: Arc<crate::SharedAudioProcessingStats>,
) -> Result<cpal::Stream, AudioError> {
let in_cfg = in_dev
.default_input_config()
@@ -1752,6 +1759,7 @@ fn try_open_capture(
voice_out_tx,
transmit_active,
frames_sent,
audio_processing_stats,
)));
let stream = match in_format {
@@ -1803,6 +1811,7 @@ struct CaptureState {
/// capacity so the drain-into-frame path skips the allocator
/// after warmup. Same precedent as `mono_scratch` above.
frame_scratch: Vec<f32>,
audio_processing_stats: Arc<crate::SharedAudioProcessingStats>,
}
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))]
@@ -1815,6 +1824,7 @@ impl CaptureState {
voice_out_tx: mpsc::Sender<OutPacket>,
transmit_active: Arc<AtomicBool>,
frames_sent: Arc<AtomicU32>,
audio_processing_stats: Arc<crate::SharedAudioProcessingStats>,
) -> Self {
Self {
encoder,
@@ -1828,12 +1838,9 @@ impl CaptureState {
voice_out_tx,
transmit_active,
frames_sent,
// Generous upper bound for typical cpal periods
// (commonly 256..1024 frames); `clear()` retains the
// backing allocation across callbacks. See struct doc.
mono_scratch: Vec::with_capacity(4096),
// Exact upper bound: drain pulls FRAME_SAMPLES at a time.
frame_scratch: Vec::with_capacity(FRAME_SAMPLES),
audio_processing_stats,
}
}
@@ -1841,17 +1848,8 @@ impl CaptureState {
/// 48 kHz mono frames; encode and send when `transmit_active`
/// is true (PTT engaged).
fn ingest<T: ToF32 + Copy>(&mut self, buf: &[T]) {
if !self.transmit_active.load(Ordering::Relaxed) {
// Drain accumulator while muted so we don't pop on PTT release.
self.pcm_accum.clear();
return;
}
// 1. Down-mix to mono + gain.
// Reuse `self.mono_scratch` to avoid a per-callback Vec
// allocation on the realtime audio thread; see struct
// doc and the engine.rs:1389-1397 precedent for why this
// matters for user-perceptible audio popping.
// 1. Down-mix to mono (pre-gain). Always performed so the level
// meter reflects real mic input even when PTT is released.
let in_channels = self.in_channels;
let mic_gain = self.mic_gain;
self.mono_scratch.clear();
@@ -1859,8 +1857,23 @@ impl CaptureState {
self.mono_scratch.reserve(frame_count);
for frame in buf.chunks(in_channels) {
let sum: f32 = frame.iter().map(|s| s.to_f32_sample()).sum();
self.mono_scratch
.push((sum / frame.len() as f32) * mic_gain);
self.mono_scratch.push(sum / frame.len() as f32);
}
// Measure dBFS from pre-gain samples so the level meter
// reflects the raw mic input, not the amplified signal.
self.audio_processing_stats
.set_input_dbfs(crate::frame::dbfs(&self.mono_scratch));
if mic_gain != 1.0 {
for s in &mut self.mono_scratch {
*s *= mic_gain;
}
}
if !self.transmit_active.load(Ordering::Relaxed) {
self.pcm_accum.clear();
return;
}
// 2. Resample to 48 kHz if needed. We re-borrow
@@ -2488,6 +2501,7 @@ pub mod bench_seam {
tx,
transmit_active.clone(),
frames_sent,
Arc::new(crate::SharedAudioProcessingStats::default()),
);
Self {
state,
+36 -1
View File
@@ -1038,6 +1038,8 @@ pub struct BridgeAudioStats {
pub frames_received: u32,
/// Current push-to-talk state.
pub ptt_active: bool,
/// Current microphone input level in dBFS (-120.0 = silence, 0.0 = clipping).
pub input_level: f32,
}
/// Bridge route class for P1 audio-processing policy.
@@ -2096,7 +2098,7 @@ pub fn events_stream(sink: StreamSink<BridgeEvent>) -> Result<(), BridgeError> {
/// Read audio statistics. Errors if no connection or audio not started.
pub async fn audio_stats() -> Result<BridgeAudioStats, BridgeError> {
let (s, r, p) = runtime()
let (s, r, p, lvl) = runtime()
.spawn(async { session().audio_stats().await })
.await
.map_err(|e| task_join_error("audio_stats", e))??;
@@ -2104,9 +2106,42 @@ pub async fn audio_stats() -> Result<BridgeAudioStats, BridgeError> {
frames_sent: s,
frames_received: r,
ptt_active: p,
input_level: lvl,
})
}
/// Subscribe to real-time microphone input level at ~30 Hz.
/// Values are dBFS (-120 = silence, 0 = clipping). The stream ends
/// when the Dart subscriber cancels, the session is dropped, or
/// the session becomes persistently unavailable.
pub fn input_level_stream(sink: StreamSink<f32>) -> Result<(), BridgeError> {
runtime().spawn(async move {
let mut interval = tokio::time::interval(Duration::from_millis(33));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
let mut consecutive_errors = 0u32;
loop {
interval.tick().await;
let level = match session().audio_stats().await {
Ok((_, _, _, lvl)) => {
consecutive_errors = 0;
lvl
}
Err(_) => {
consecutive_errors += 1;
if consecutive_errors >= 10 {
return;
}
-120.0
}
};
if sink.add(level).is_err() {
return;
}
}
});
Ok(())
}
/// Apply the P1 audio-processing config.
pub async fn set_audio_processing_config(
config: BridgeAudioProcessingConfig,
+86 -30
View File
@@ -38,7 +38,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
default_rust_auto_opaque = RustAutoOpaqueMoi,
);
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 281698435;
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -20394775;
// Section: executor
@@ -738,6 +738,42 @@ fn wire__crate__api__init_storage_impl(
},
)
}
fn wire__crate__api__input_level_stream_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
rust_vec_len_: i32,
data_len_: i32,
) {
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
flutter_rust_bridge::for_generated::TaskInfo {
debug_name: "input_level_stream",
port: Some(port_),
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
},
move || {
let message = unsafe {
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
ptr_,
rust_vec_len_,
data_len_,
)
};
let mut deserializer =
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_sink =
<StreamSink<f32, flutter_rust_bridge::for_generated::SseCodec>>::sse_decode(
&mut deserializer,
);
deserializer.end();
move |context| {
transform_result_sse::<_, crate::BridgeError>((move || {
let output_ok = crate::api::input_level_stream(api_sink)?;
Ok(output_ok)
})())
}
},
)
}
fn wire__crate__api__is_connected_impl(
port_: flutter_rust_bridge::for_generated::MessagePort,
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
@@ -1790,6 +1826,14 @@ impl SseDecode
}
}
impl SseDecode for StreamSink<f32, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut inner = <String>::sse_decode(deserializer);
return StreamSink::deserialize(inner);
}
}
impl SseDecode for String {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@@ -1962,10 +2006,12 @@ impl SseDecode for crate::api::BridgeAudioStats {
let mut var_framesSent = <u32>::sse_decode(deserializer);
let mut var_framesReceived = <u32>::sse_decode(deserializer);
let mut var_pttActive = <bool>::sse_decode(deserializer);
let mut var_inputLevel = <f32>::sse_decode(deserializer);
return crate::api::BridgeAudioStats {
frames_sent: var_framesSent,
frames_received: var_framesReceived,
ptt_active: var_pttActive,
input_level: var_inputLevel,
};
}
}
@@ -2755,33 +2801,34 @@ fn pde_ffi_dispatcher_primary_impl(
14 => wire__crate__api__get_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
15 => wire__crate__api__get_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
20 => wire__crate__api__init_storage_impl(port, ptr, rust_vec_len, data_len),
21 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
22 => wire__crate__api__list_audio_devices_impl(port, ptr, rust_vec_len, data_len),
23 => wire__crate__api__list_bookmarks_impl(port, ptr, rust_vec_len, data_len),
25 => wire__crate__api__move_to_channel_impl(port, ptr, rust_vec_len, data_len),
26 => wire__crate__api__prefetch_server_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__api__ptt_descriptor_impl(port, ptr, rust_vec_len, data_len),
29 => wire__crate__api__send_chat_message_impl(port, ptr, rust_vec_len, data_len),
31 => wire__crate__api__set_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__api__set_client_volume_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__api__set_hard_mute_impl(port, ptr, rust_vec_len, data_len),
34 => wire__crate__api__set_input_device_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__api__set_input_muted_impl(port, ptr, rust_vec_len, data_len),
36 => {
21 => wire__crate__api__input_level_stream_impl(port, ptr, rust_vec_len, data_len),
22 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
23 => wire__crate__api__list_audio_devices_impl(port, ptr, rust_vec_len, data_len),
24 => wire__crate__api__list_bookmarks_impl(port, ptr, rust_vec_len, data_len),
26 => wire__crate__api__move_to_channel_impl(port, ptr, rust_vec_len, data_len),
27 => wire__crate__api__prefetch_server_impl(port, ptr, rust_vec_len, data_len),
28 => wire__crate__api__ptt_descriptor_impl(port, ptr, rust_vec_len, data_len),
30 => wire__crate__api__send_chat_message_impl(port, ptr, rust_vec_len, data_len),
32 => wire__crate__api__set_audio_processing_config_impl(port, ptr, rust_vec_len, data_len),
33 => wire__crate__api__set_client_volume_impl(port, ptr, rust_vec_len, data_len),
34 => wire__crate__api__set_hard_mute_impl(port, ptr, rust_vec_len, data_len),
35 => wire__crate__api__set_input_device_impl(port, ptr, rust_vec_len, data_len),
36 => wire__crate__api__set_input_muted_impl(port, ptr, rust_vec_len, data_len),
37 => {
wire__crate__api__set_ios_voice_processing_mode_impl(port, ptr, rust_vec_len, data_len)
}
38 => wire__crate__api__set_output_device_impl(port, ptr, rust_vec_len, data_len),
39 => wire__crate__api__set_output_gain_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__api__set_output_muted_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
42 => wire__crate__api__set_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
43 => wire__crate__api__set_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__api__set_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__api__set_vad_model_path_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
47 => wire__crate__api__update_bookmark_impl(port, ptr, rust_vec_len, data_len),
48 => wire__crate__api__voice_join_impl(port, ptr, rust_vec_len, data_len),
49 => wire__crate__api__voice_leave_impl(port, ptr, rust_vec_len, data_len),
39 => wire__crate__api__set_output_device_impl(port, ptr, rust_vec_len, data_len),
40 => wire__crate__api__set_output_gain_impl(port, ptr, rust_vec_len, data_len),
41 => wire__crate__api__set_output_muted_impl(port, ptr, rust_vec_len, data_len),
42 => wire__crate__api__set_ptt_impl(port, ptr, rust_vec_len, data_len),
43 => wire__crate__api__set_ptt_binding_impl(port, ptr, rust_vec_len, data_len),
44 => wire__crate__api__set_release_tail_ms_impl(port, ptr, rust_vec_len, data_len),
45 => wire__crate__api__set_transmit_mode_impl(port, ptr, rust_vec_len, data_len),
46 => wire__crate__api__set_vad_model_path_impl(port, ptr, rust_vec_len, data_len),
47 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
48 => wire__crate__api__update_bookmark_impl(port, ptr, rust_vec_len, data_len),
49 => wire__crate__api__voice_join_impl(port, ptr, rust_vec_len, data_len),
50 => wire__crate__api__voice_leave_impl(port, ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@@ -2803,10 +2850,10 @@ fn pde_ffi_dispatcher_sync_impl(
data_len,
),
19 => wire__crate__api__handle_route_change_impl(ptr, rust_vec_len, data_len),
24 => wire__crate__api__log_file_path_str_impl(ptr, rust_vec_len, data_len),
28 => wire__crate__api__record_lifecycle_event_impl(ptr, rust_vec_len, data_len),
30 => wire__crate__api__set_audio_output_route_impl(ptr, rust_vec_len, data_len),
37 => wire__crate__api__set_network_state_impl(ptr, rust_vec_len, data_len),
25 => wire__crate__api__log_file_path_str_impl(ptr, rust_vec_len, data_len),
29 => wire__crate__api__record_lifecycle_event_impl(ptr, rust_vec_len, data_len),
31 => wire__crate__api__set_audio_output_route_impl(ptr, rust_vec_len, data_len),
38 => wire__crate__api__set_network_state_impl(ptr, rust_vec_len, data_len),
_ => unreachable!(),
}
}
@@ -2984,6 +3031,7 @@ impl flutter_rust_bridge::IntoDart for crate::api::BridgeAudioStats {
self.frames_sent.into_into_dart().into_dart(),
self.frames_received.into_into_dart().into_dart(),
self.ptt_active.into_into_dart().into_dart(),
self.input_level.into_into_dart().into_dart(),
]
.into_dart()
}
@@ -3652,6 +3700,13 @@ impl SseEncode
}
}
impl SseEncode for StreamSink<f32, flutter_rust_bridge::for_generated::SseCodec> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
unimplemented!("")
}
}
impl SseEncode for String {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@@ -3781,6 +3836,7 @@ impl SseEncode for crate::api::BridgeAudioStats {
<u32>::sse_encode(self.frames_sent, serializer);
<u32>::sse_encode(self.frames_received, serializer);
<bool>::sse_encode(self.ptt_active, serializer);
<f32>::sse_encode(self.input_level, serializer);
}
}
+49 -81
View File
@@ -56,6 +56,13 @@ type PendingMoves = HashMap<
),
>;
struct EventChannels {
voice_in: mpsc::Sender<InboundVoice>,
chat: mpsc::Sender<ChatMessage>,
activity: mpsc::Sender<ServerActivity>,
delta: mpsc::Sender<ProtocolDelta>,
}
#[derive(Debug, PartialEq, Eq)]
enum SendTimeoutError<T> {
Timeout(T),
@@ -278,10 +285,12 @@ impl ProtocolClient {
cfg.clone(),
rx,
voice_out_rx,
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
EventChannels {
voice_in: voice_in_tx,
chat: chat_tx,
activity: activity_tx,
delta: delta_tx,
},
ready_tx,
lost_tx,
));
@@ -502,10 +511,7 @@ async fn connection_task(
cfg: ConnectConfig,
mut rx: mpsc::Receiver<Request>,
mut voice_out_rx: mpsc::Receiver<OutPacket>,
voice_in_tx: mpsc::Sender<InboundVoice>,
chat_tx: mpsc::Sender<ChatMessage>,
activity_tx: mpsc::Sender<ServerActivity>,
delta_tx: mpsc::Sender<ProtocolDelta>,
channels: EventChannels,
ready_tx: oneshot::Sender<Result<(), ProtocolError>>,
lost_tx: oneshot::Sender<DisconnectReason>,
) {
@@ -686,14 +692,14 @@ async fn connection_task(
Ok(Some(Ok(item))) => {
match item {
StreamItem::Audio(buf) => {
handle_audio_stream_item(&voice_in_tx, &mut voice_activity, buf).await;
handle_audio_stream_item(&channels.voice_in, &mut voice_activity, buf).await;
}
other => handle_non_audio_stream_item(
&con,
other,
&chat_tx,
&activity_tx,
&delta_tx,
&channels.chat,
&channels.activity,
&channels.delta,
&mut pending_moves,
),
}
@@ -730,10 +736,8 @@ async fn connection_task(
})
.collect();
for handle in expired {
if let Some((_target_channel, reply, _)) = pending_moves.remove(&handle) {
if let Some(reply) = reply {
let _ = reply.send(Ok(()));
}
if let Some((_target_channel, Some(reply), _)) = pending_moves.remove(&handle) {
let _ = reply.send(Ok(()));
}
}
}
@@ -794,10 +798,7 @@ async fn connection_task(
let r = fetch_client_profile(
&mut con,
client_id,
&voice_in_tx,
&chat_tx,
&activity_tx,
&delta_tx,
&channels,
&mut pending_moves,
&mut voice_activity,
)
@@ -872,7 +873,7 @@ fn handle_non_audio_stream_item(
} = &ev
{
let own_client = con.get_state().ok().map(|state| state.own_client);
if let Some(state) = con.get_state().ok() {
if let Ok(state) = con.get_state() {
if let Some(client) = state.clients.get(client_id) {
let _ = delta_tx.try_send(ProtocolDelta::ClientMoved {
client_id: client_id.0 as u64,
@@ -1118,10 +1119,7 @@ fn send_text_to_mode(
async fn fetch_client_profile(
con: &mut Connection,
client_id: u64,
voice_in_tx: &mpsc::Sender<InboundVoice>,
chat_tx: &mpsc::Sender<ChatMessage>,
activity_tx: &mpsc::Sender<ServerActivity>,
delta_tx: &mpsc::Sender<ProtocolDelta>,
channels: &EventChannels,
pending_moves: &mut PendingMoves,
voice_activity: &mut HashMap<u64, Instant>,
) -> Result<ClientProfile, ProtocolError> {
@@ -1158,10 +1156,7 @@ async fn fetch_client_profile(
let _ = request_messages(
con,
build_command("servergrouplist", &[], &[]),
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1171,10 +1166,7 @@ async fn fetch_client_profile(
let _ = request_messages(
con,
build_command("channelgrouplist", &[], &[]),
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1188,10 +1180,7 @@ async fn fetch_client_profile(
&[("clid", client_id.to_string())],
&[],
),
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1209,10 +1198,7 @@ async fn fetch_client_profile(
if let Err(e) = request_messages(
con,
build_command("getconnectioninfo", &[("clid", client_id.to_string())], &[]),
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1231,10 +1217,7 @@ async fn fetch_client_profile(
request_client_db_info(
con,
database_id,
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1392,10 +1375,7 @@ fn client_profile_refresh_plan(
async fn request_messages(
con: &mut Connection,
command: OutCommand,
voice_in_tx: &mpsc::Sender<InboundVoice>,
chat_tx: &mpsc::Sender<ChatMessage>,
activity_tx: &mpsc::Sender<ServerActivity>,
delta_tx: &mpsc::Sender<ProtocolDelta>,
channels: &EventChannels,
pending_moves: &mut PendingMoves,
voice_activity: &mut HashMap<u64, Instant>,
) -> Result<Vec<InMessage>, ProtocolError> {
@@ -1431,14 +1411,14 @@ async fn request_messages(
return Ok(messages);
}
StreamItem::Audio(buf) => {
handle_audio_stream_item(voice_in_tx, voice_activity, buf).await;
handle_audio_stream_item(&channels.voice_in, voice_activity, buf).await;
}
other => handle_non_audio_stream_item(
con,
other,
chat_tx,
activity_tx,
delta_tx,
&channels.chat,
&channels.activity,
&channels.delta,
pending_moves,
),
}
@@ -1448,20 +1428,14 @@ async fn request_messages(
async fn request_client_db_info(
con: &mut Connection,
dbid: tsclientlib::ClientDbId,
voice_in_tx: &mpsc::Sender<InboundVoice>,
chat_tx: &mpsc::Sender<ChatMessage>,
activity_tx: &mpsc::Sender<ServerActivity>,
delta_tx: &mpsc::Sender<ProtocolDelta>,
channels: &EventChannels,
pending_moves: &mut PendingMoves,
voice_activity: &mut HashMap<u64, Instant>,
) -> Result<InClientDbInfoPart, ProtocolError> {
let messages = request_messages(
con,
build_command("clientdbinfo", &[("cldbid", dbid.0.to_string())], &[]),
voice_in_tx,
chat_tx,
activity_tx,
delta_tx,
channels,
pending_moves,
voice_activity,
)
@@ -1784,9 +1758,7 @@ fn format_server_activity(con: &Connection, ev: &tsclientlib::events::Event) ->
extra,
..
} => {
if extra.reason.is_none() {
return None;
}
extra.reason?;
let client = activity_client(con, *client_id)?;
let channel = activity_channel_name(con, client.channel)?;
Some(format!(
@@ -2164,7 +2136,7 @@ fn forward_delta(
id: PropertyId::Client(client_id),
..
} => {
if let Some(state) = con.get_state().ok() {
if let Ok(state) = con.get_state() {
if let Some(client) = state.clients.get(client_id) {
let _ = delta_tx.try_send(ProtocolDelta::ClientJoined {
client_id: client_id.0 as u64,
@@ -2181,15 +2153,13 @@ fn forward_delta(
}
Event::PropertyRemoved {
id: PropertyId::Client(_),
old,
old: PropertyValue::Client(client),
..
} => {
if let PropertyValue::Client(client) = old {
let _ = delta_tx.try_send(ProtocolDelta::ClientLeft {
client_id: client.id.0 as u64,
name: client.name.clone(),
});
}
let _ = delta_tx.try_send(ProtocolDelta::ClientLeft {
client_id: client.id.0 as u64,
name: client.name.clone(),
});
}
Event::PropertyChanged {
id: PropertyId::ClientChannel(_),
@@ -2199,7 +2169,7 @@ fn forward_delta(
id: PropertyId::Client(client_id),
..
} => {
if let Some(state) = con.get_state().ok() {
if let Ok(state) = con.get_state() {
if let Some(client) = state.clients.get(client_id) {
let _ = delta_tx.try_send(ProtocolDelta::ClientUpdated {
client_id: client_id.0 as u64,
@@ -2216,7 +2186,7 @@ fn forward_delta(
id: PropertyId::Channel(channel_id),
..
} => {
if let Some(state) = con.get_state().ok() {
if let Ok(state) = con.get_state() {
if let Some(channel) = state.channels.get(channel_id) {
let _ = delta_tx.try_send(ProtocolDelta::ChannelAdded {
id: channel_id.0,
@@ -2231,20 +2201,18 @@ fn forward_delta(
}
Event::PropertyRemoved {
id: PropertyId::Channel(_),
old,
old: PropertyValue::Channel(channel),
..
} => {
if let PropertyValue::Channel(channel) = old {
let _ = delta_tx.try_send(ProtocolDelta::ChannelRemoved {
id: channel.id.0,
});
}
let _ = delta_tx.try_send(ProtocolDelta::ChannelRemoved {
id: channel.id.0,
});
}
Event::PropertyChanged {
id: PropertyId::Channel(channel_id),
..
} => {
if let Some(state) = con.get_state().ok() {
if let Ok(state) = con.get_state() {
if let Some(channel) = state.channels.get(channel_id) {
let _ = delta_tx.try_send(ProtocolDelta::ChannelUpdated {
id: channel_id.0,