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 devices
  • stop_audio_level_monitoring() — halts the monitoring task
  • is_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:

  1. Reads audio levels from each selected device using the cpal crate, calculating RMS (root‑mean‑square) values
  2. Normalizes levels to a 0‑1 range suitable for UI display
  3. Constructs AudioLevelData structs containing device name, level, and timestamp
  4. Emits the "audio‑level‑update" event with an AudioLevelUpdate payload 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:

  1. UI invokes start_audio_level_monitoring with target device names
  2. Backend spawns an async loop in simple_level_monitor.rs
  3. Loop iterates every ~50ms, sampling RMS levels via cpal
  4. Backend emits "audio‑level‑update" events with AudioLevelUpdate payloads
  5. UI receives events through Tauri's listener API
  6. React state updates, triggering re‑render of volume meter components
  7. UI invokes stop_audio_level_monitoring to 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.rs calculates real‑time RMS audio levels using cpal
  • Tauri events carry AudioLevelUpdate payloads 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →