How Meetily Detects Bluetooth Playback Devices and Warns Users: A Complete Technical Guide

Meetily monitors your active audio output device every 5 seconds using a Rust backend command and React frontend polling to detect Bluetooth playback devices and warn users about potential recording quality issues.

The open-source Meetily application protects meeting recording quality by implementing a cross-platform Bluetooth playback detection system. This article examines the complete technical workflow from the Rust-based Tauri backend to the React frontend, showing exactly how the system identifies Bluetooth devices like AirPods and alerts users before audio quality degrades.

Architecture Overview

The detection workflow spans two runtime environments. The Tauri backend handles OS-level audio device queries using platform-specific APIs, while the React frontend manages polling intervals, state persistence, and UI rendering. This separation ensures that Bluetooth detection works consistently across macOS, Windows, and Linux while maintaining a responsive user interface.

OS-Level Device Query in Rust

The detection process begins in frontend/src-tauri/src/audio/playback_monitor.rs, where the system queries the operating system for the current default output device.

On macOS, the implementation uses Core Audio. On Windows, it leverages WASAPI. Linux systems utilize PulseAudio or PipeWire. The detect_active_output() function returns device metadata including name, sample rate, and connection type.

// frontend/src-tauri/src/lib.rs
#[tauri::command]
async fn get_active_audio_output() -> Result<AudioOutputInfo, String> {
    let info = playback_monitor::detect_active_output()?; // platform-specific logic
    Ok(AudioOutputInfo {
        device_name: info.name,
        is_bluetooth: info.is_bluetooth,
        sample_rate: info.sample_rate,
        device_type: info.kind,
    })
}

The get_active_audio_output command is registered in frontend/src-tauri/src/lib.rs and exposed to the frontend via Tauri's invoke API.

Bluetooth Identification Logic

Once the OS returns the active device, the Rust code examines the device name and properties to determine if it is a Bluetooth playback device.

The system sets the is_bluetooth flag to true when:

  • The device name contains common Bluetooth keywords (e.g., "AirPods", "Bluetooth")
  • The platform API explicitly flags the device as Bluetooth

This logic resides in frontend/src-tauri/src/audio/playback_monitor.rs alongside the platform-specific detection code. The result is packaged into an AudioOutputInfo struct containing the device name, Bluetooth status, sample rate, and device type.

Frontend Polling and State Management

The React component BluetoothPlaybackWarning in frontend/src/components/BluetoothPlaybackWarning.tsx handles the continuous monitoring of audio output devices.

The component uses a useEffect hook that calls invoke('get_active_audio_output') immediately on mount, then repeats every 5 seconds (5000ms) via setInterval.

// frontend/src/components/BluetoothPlaybackWarning.tsx (simplified)
export function BluetoothPlaybackWarning({ checkInterval = 5000, enabled = true }) {
  const [isBluetoothActive, setIsBluetoothActive] = useState(false);
  const [deviceName, setDeviceName] = useState('');
  const [isDismissed, setIsDismissed] = useState(false);

  useEffect(() => {
    if (!enabled) return;

    const poll = async () => {
      const info = await invoke<AudioOutputInfo>('get_active_audio_output');
      if (info.is_bluetooth) {
        setIsBluetoothActive(true);
        setDeviceName(info.device_name);
      } else {
        setIsBluetoothActive(false);
        setIsDismissed(false);
      }
    };

    poll();                         // run immediately
    const id = setInterval(poll, checkInterval);
    return () => clearInterval(id);
  }, [checkInterval, enabled]);
  
  // ... render logic
}

When is_bluetooth returns true, the component stores the device name and activates the warning state. If the user switches to a non-Bluetooth output, the dismissal state automatically resets so future Bluetooth connections will trigger fresh warnings.

Rendering the Warning UI

When a Bluetooth device is active and the user has not dismissed the alert, the component renders a yellow Alert component explaining the risks.

The warning specifically notes that recordings may be distorted or speed up when using Bluetooth playback devices. It suggests switching to wired headphones or computer speakers for optimal recording quality. Users can dismiss the warning via a close button, which sets the isDismissed state to true within the component.

The dismissal persistence is maintained only in component state (isDismissed), meaning the warning reappears if the user switches to a non-Bluetooth device and then back to Bluetooth, or if the component remounts.

Backend Adaptations for Bluetooth Devices

Beyond warning users, Meetily adjusts its audio pipeline to accommodate Bluetooth-specific limitations. In frontend/src-tauri/src/audio/recording_manager.rs, the system implements device-specific fallbacks.

When Bluetooth devices are detected:

  • Larger buffers are applied (80-200ms) to handle Bluetooth latency variations
  • On macOS, the microphone falls back to the built-in mic to avoid variable sample-rate issues that can occur when Bluetooth headphones handle both input and output

These backend adaptations ensure that even if users ignore the warning, the recording system maintains stability despite Bluetooth's inherent latency and sample-rate quirks.

Summary

  • Cross-platform detection occurs in playback_monitor.rs using Core Audio (macOS), WASAPI (Windows), or PulseAudio/PipeWire (Linux).
  • Bluetooth identification relies on device name parsing and OS API flags to set the is_bluetooth boolean in the AudioOutputInfo struct.
  • Continuous monitoring happens every 5 seconds via the BluetoothPlaybackWarning React component using Tauri's invoke API.
  • User warnings appear as dismissible yellow alerts explaining potential distortion risks and recommending wired alternatives.
  • Recording stability is maintained through larger buffers (80-200ms) and microphone fallbacks implemented in recording_manager.rs.

Frequently Asked Questions

How often does Meetily check for Bluetooth playback devices?

Meetily polls the active audio output device every 5 seconds (5000ms) by default. This interval is configurable via the checkInterval prop in the BluetoothPlaybackWarning component. The polling occurs through a useEffect hook that calls the Tauri command get_active_audio_output immediately on mount and then repeats via setInterval until the component unmounts.

What triggers the Bluetooth detection flag in the backend?

The is_bluetooth flag in the AudioOutputInfo struct is set to true when the device name contains common Bluetooth keywords like "AirPods" or "Bluetooth", or when the platform-specific API explicitly identifies the device as Bluetooth. This logic executes in frontend/src-tauri/src/audio/playback_monitor.rs after querying the OS for the active output device.

Why does Meetily warn users about Bluetooth playback devices?

Bluetooth audio introduces variable latency and can cause sample-rate mismatches that distort recordings or cause speed-up artifacts. The warning appears in a yellow alert component when isBluetoothActive is true and the user has not dismissed the notification, advising users to switch to wired headphones or computer speakers for optimal recording fidelity.

How does Meetily handle recording when Bluetooth devices are in use?

According to the source code in frontend/src-tauri/src/audio/recording_manager.rs, Meetily applies larger audio buffers (80-200ms) to compensate for Bluetooth latency. On macOS specifically, the system falls back to the built-in microphone to avoid the variable sample-rate issues that occur when Bluetooth headphones handle both playback and recording simultaneously.

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 →