How Audio Level Monitoring Provides Real‑Time Feedback to the Meetily UI
Audio level monitoring in Meetily streams live microphone and system audio levels from a Rust‑powered Tauri backend to the React frontend via event emitters, enabling dynamic volume meter displays.
Meetily, an open‑source meeting assistant built by Zackriya‑Solutions, implements real‑time audio level monitoring to help users visualize their microphone and system audio activity during recordings. This feature bridges Rust's low‑level audio processing capabilities with a modern React UI through Tauri's event system. The implementation spans multiple files in frontend/src-tauri/src/audio/ and demonstrates how to build responsive, non‑blocking audio visualizations in a hybrid desktop application.
Backend Architecture: Commands and Monitoring Loop
Tauri Command Registration
The backend exposes three Tauri commands that the frontend invokes to control monitoring. These are registered in src/lib.rs and delegate to the helper functions in audio/simple_level_monitor.rs.
start_audio_level_monitoring(device_names: Vec<String>)— launches the monitoring loop for specified devicesstop_audio_level_monitoring()— halts the monitoring taskis_audio_level_monitoring() -> bool— queries whether monitoring is currently active
As implemented in Zackriya‑Solutions/meetily, the command handlers are thin wrappers that manage the lifecycle of an async monitoring task.
Core Monitoring Implementation in simple_level_monitor.rs
The audio/simple_level_monitor.rs file contains the central monitoring logic. It spawns an async task that runs continuously while monitoring is enabled, performing the following operations on each iteration:
- Reads audio levels from each selected device using the
cpalcrate, calculating RMS (root‑mean‑square) values - Normalizes levels to a 0‑1 range suitable for UI display
- Constructs
AudioLevelDatastructs containing device name, level, and timestamp - Emits the
"audio‑level‑update"event with anAudioLevelUpdatepayload grouping all device levels
The loop executes at a default interval of approximately 50 milliseconds, striking a balance between responsiveness and CPU usage. The use of an async task ensures the monitoring never blocks Tauri's main thread or the frontend.
Data Structures and Type Definitions
The monitoring system relies on structured types defined in audio/level_monitor.rs:
// AudioLevelData — per-device level information
pub struct AudioLevelData {
pub device_name: String,
pub level: f32, // Normalized 0.0 – 1.0
pub timestamp: u64, // Milliseconds since epoch
}
// AudioLevelUpdate — payload for event emission
pub struct AudioLevelUpdate {
pub levels: Vec<AudioLevelData>,
}
These structures enable the frontend to render separate visual meters for each audio source—typically the microphone and system audio (speaker output) channels.
Frontend Integration: Event Listeners and State Management
Subscribing to Level Updates
The React frontend consumes audio level data by listening for Tauri's "audio‑level‑update" event. A typical implementation uses the listen function from @tauri‑apps/api/event:
import { listen } from '@tauri-apps/api/event';
import { useEffect, useState } from 'react';
type AudioLevelData = {
device_name: string;
level: number;
timestamp: number;
};
type AudioLevelUpdate = {
levels: AudioLevelData[];
};
export function useAudioLevels() {
const [levels, setLevels] = useState<AudioLevelData[]>([]);
useEffect(() => {
const unlisten = listen<AudioLevelUpdate>('audio-level-update', (event) => {
setLevels(event.payload.levels);
});
// Cleanup listener on unmount
return () => {
unlisten.then((fn) => fn());
};
}, []);
return levels;
}
Rendering Volume Meters
The level data drives visual components. A minimal meter implementation maps normalized levels to CSS heights:
export function AudioMeter() {
const levels = useAudioLevels();
return (
<div className="audio-meters">
{levels.map((device) => (
<div key={device.device_name} className="meter-container">
<span className="device-label">{device.device_name}</span>
<div className="meter-bar">
<div
className="meter-fill"
style={{ height: `${device.level * 100}%` }}
/>
</div>
</div>
))}
</div>
);
}
Lifecycle Management: Starting and Stopping Monitoring
Initiating Monitoring
When the user enters a meeting or enables audio preview, the frontend invokes the start command with specific device names discovered through audio/devices/discovery.rs:
import { invoke } from '@tauri-apps/api/tauri';
async function startMonitoring(microphone: string, systemAudio: string) {
await invoke('start_audio_level_monitoring', {
deviceNames: [microphone, systemAudio],
});
}
The deviceNames parameter must match valid device identifiers returned by the discovery utilities in audio/devices/discovery.rs.
Terminating Monitoring
Proper cleanup prevents resource leaks and unnecessary CPU consumption. The frontend calls:
async function stopMonitoring() {
await invoke('stop_audio_level_monitoring');
}
This pattern is typically executed in a useEffect cleanup function or when the meeting session ends.
Complete Data Flow
The end‑to‑end audio level monitoring operates as follows:
- UI invokes
start_audio_level_monitoringwith target device names - Backend spawns an async loop in
simple_level_monitor.rs - Loop iterates every ~50ms, sampling RMS levels via
cpal - Backend emits
"audio‑level‑update"events withAudioLevelUpdatepayloads - UI receives events through Tauri's listener API
- React state updates, triggering re‑render of volume meter components
- UI invokes
stop_audio_level_monitoringto terminate the loop
This architecture decouples the audio processing from UI concerns: the Rust backend never holds references to frontend components, and the frontend remains agnostic to how levels are calculated.
Key Implementation Files
| Path | Purpose |
|---|---|
frontend/src-tauri/src/lib.rs |
Registers Tauri commands and wires handlers |
frontend/src-tauri/src/audio/simple_level_monitor.rs |
Async monitoring loop and event emission |
frontend/src-tauri/src/audio/level_monitor.rs |
Type definitions for level data structures |
frontend/src-tauri/src/audio/devices/discovery.rs |
Device enumeration for monitoring selection |
frontend/src/components/AudioMeter.tsx (example path) |
React component consuming level events |
Summary
- Rust backend in
simple_level_monitor.rscalculates real‑time RMS audio levels usingcpal - Tauri events carry
AudioLevelUpdatepayloads from backend to frontend without blocking either side - Frontend listeners receive
"audio‑level‑update"and drive React state for live meter rendering - Lifecycle commands (
start_,stop_,is_) provide explicit control over monitoring state - 50ms polling interval balances responsiveness with resource efficiency
Frequently Asked Questions
How does Meetily prevent audio level monitoring from freezing the UI?
The monitoring runs in a dedicated async task spawned by the Tauri runtime, not on the main thread. Event emission uses Tauri's non‑blocking app.emit() method, and the frontend processes updates through its standard React event loop. This design ensures smooth 60fps UI performance even during active audio processing.
Can the monitoring interval be adjusted for lower CPU usage?
The default ~50ms interval is hardcoded in simple_level_monitor.rs. To modify it, you would adjust the sleep duration in the monitoring loop. A longer interval (e.g., 100–200ms) reduces CPU load but decreases visual responsiveness. The current implementation prioritizes smooth meter animation over minimal resource consumption.
Why does the frontend need to pass device names rather than IDs?
The start_audio_level_monitoring command accepts Vec<String> device names that must match the human‑readable identifiers returned by audio/devices/discovery.rs. This approach aligns with how cpal enumerates devices and simplifies debugging. The backend performs name‑to‑device resolution internally when opening audio streams.
What happens if a monitored device disconnects during operation?
The simple_level_monitor.rs implementation handles stream errors by continuing the loop and attempting reconnections or skipping unavailable devices. The frontend receives level updates only for successfully sampled devices, so meters for disconnected hardware simply stop updating while others continue functioning normally.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →