How Meetily Handles Start and Stop Recording Commands with Tauri: A Complete Technical Guide

Meetily exposes recording controls through asynchronous Tauri commands in lib.rs that delegate to a Rust audio layer, using global atomic flags to prevent concurrent sessions and guaranteeing graceful shutdown that flushes buffers and awaits transcription completion.

Meetily is an open-source meeting assistant built with the Tauri framework, combining a React frontend with Rust-powered native audio processing. Understanding how Meetily handles start and stop recording commands with Tauri reveals a sophisticated async architecture that manages device selection, transcription pipelines, and resource cleanup across the JavaScript-Rust boundary.

Tauri Command Architecture

The recording workflow is exposed to the frontend via two #[tauri::command] attributed functions declared in frontend/src-tauri/src/lib.rs: start_recording and stop_recording. These functions serve as the bridge between the TypeScript UI and the native Rust audio subsystem, handling device resolution, state management, and graceful error propagation through Tauri's invoke mechanism.

Starting a Recording Session

The start-recording flow orchestrates device initialization, model validation, and global state management through ten discrete steps.

Entry Point and Concurrency Guard

The UI initiates recording by calling invoke('start_recording', { ... }), which triggers the command handler at lines 84-102 in frontend/src-tauri/src/lib.rs. The function immediately checks the global RECORDING_FLAG (an AtomicBool) to reject requests if a session is already active, preventing race conditions and resource conflicts.

Device Resolution and Model Validation

The command delegates to audio::recording_commands::start_recording_with_devices_and_meeting, passing optional microphone, system-audio, and meeting-name arguments. Inside recording_commands.rs, the function resolves requested devices or falls back to defaults, then validates that the Whisper transcription model is ready. If model validation fails, it emits a transcription-error event to the UI and aborts before allocating audio resources.

Pipeline Initialization and Global State

A new RecordingManager is instantiated with the meeting name and an error callback. Calling manager.start_recording() launches the audio capture pipeline and returns a transcription_receiver. The manager and transcription task handle are stored in static globals (RECORDING_MANAGER and TRANSCRIPTION_TASK) while IS_RECORDING is set to true using Ordering::SeqCst, ensuring visibility across threads.

Event Registration and UI Feedback

The command registers a listener for the "transcript-update" event, converting each incoming chunk into a TranscriptSegment saved through the manager. The listener ID is stored in TRANSCRIPT_LISTENER_ID for cleanup during shutdown. Finally, the command emits a "recording-started" event, updates the system tray menu via tray.rs, and fires a native notification through notifications/commands.rs.

Stopping a Recording Session

The stop-recording flow guarantees no transcript data is lost through ordered resource deallocation and blocking awaits.

Entry and Early Exit

When the UI calls invoke('stop_recording', { save_path }), the handler at lines 44-66 in frontend/src-tauri/src/lib.rs first checks audio::recording_commands::is_recording(). If no session is active, it returns immediately without error.

Graceful Shutdown Sequence

The stop implementation in frontend/src-tauri/src/audio/recording_commands.rs (lines 482-530) executes a strict shutdown protocol:

  1. Flush Audio Streams: Calls manager.stop_streams_and_force_flush() to guarantee all buffered audio is processed and no new chunks enter the pipeline.

  2. Remove Event Listener: Unlistens the "transcript-update" handler using the stored TRANSCRIPT_LISTENER_ID, releasing the microphone resource.

  3. Await Transcription: The transcription task is awaited without timeout via task.await, ensuring every queued chunk is processed while emitting "recording-shutdown-progress" updates to the UI.

  4. Clear Globals: RECORDING_MANAGER, TRANSCRIPTION_TASK, and TRANSCRIPT_LISTENER_ID are cleared, and IS_RECORDING is atomically set to false.

Filesystem and UI Finalization

If the caller supplied a save_path, the command ensures the directory hierarchy exists, creating it recursively if necessary. After successful shutdown, it updates the tray menu, emits a "recording-stopped" event, and displays a completion notification.

Implementation Code Examples

Frontend TypeScript Calls

// Start recording with specific devices
await invoke('start_recording', {
  mic_device_name: 'Built-in Microphone',
  system_device_name: 'BlackHole 2ch',
  meeting_name: 'Team Standup'
});

// Stop and save to specific path
await invoke('stop_recording', {
  save_path: '/Users/me/Meetings/Team_Standup_2024-03-01.m4a'
});

Rust Command Signatures

// frontend/src-tauri/src/lib.rs
#[tauri::command]
async fn start_recording<R: Runtime>(
    app: AppHandle<R>,
    mic_device_name: Option<String>,
    system_device_name: Option<String>,
    meeting_name: Option<String>,
) -> Result<(), String> { /* ... */ }

#[tauri::command]
async fn stop_recording<R: Runtime>(
    app: AppHandle<R>, 
    args: RecordingArgs
) -> Result<(), String> { /* ... */ }

Core Stop Logic

// frontend/src-tauri/src/audio/recording_commands.rs
pub async fn stop_recording<R: Runtime>(
    app: AppHandle<R>, 
    _args: RecordingArgs
) -> Result<(), String> {
    if !IS_RECORDING.load(Ordering::SeqCst) { 
        return Ok(()); 
    }

    // 1. Stop audio streams and force-flush
    let manager = RECORDING_MANAGER.lock().unwrap().take();
    if let Some(mut m) = manager {
        m.stop_streams_and_force_flush().await?;
    }

    // 2. Remove transcript listener
    if let Some(id) = TRANSCRIPT_LISTENER_ID.lock().unwrap().take() {
        app.unlisten(id);
    }

    // 3. Await transcription task completion
    if let Some(task) = TRANSCRIPTION_TASK.lock().unwrap().take() {
        task.await?;
    }

    IS_RECORDING.store(false, Ordering::SeqCst);
    Ok(())
}

Key Source Files Reference

Summary

  • Meetily exposes recording controls through Tauri's #[tauri::command] attribute in lib.rs, making them callable from TypeScript via invoke().
  • Atomic flags prevent concurrent recording sessions by checking RECORDING_FLAG before initialization.
  • The start flow validates the Whisper model, initializes a RecordingManager, and stores handles in static globals for cross-command persistence.
  • The stop flow guarantees data integrity by flushing audio buffers, awaiting transcription completion, and clearing global state in a strict sequence.
  • Real-time UI updates occur through Tauri events like "recording-started", "transcript-update", and "recording-shutdown-progress".

Frequently Asked Questions

How does Meetily prevent starting multiple recording sessions simultaneously?

Meetily uses a global AtomicBool named RECORDING_FLAG that is checked atomically at the start of the start_recording command. If the flag is already true, the command returns immediately with an error, preventing resource conflicts and ensuring only one audio pipeline runs at a time.

What happens if the Whisper model isn't ready when starting a recording?

Before initializing audio capture, the start_recording_with_devices_and_meeting function validates model availability. If the model is not ready, it emits a transcription-error event to the frontend and aborts the recording initialization before any system resources are allocated.

Why does the stop_recording command use Ordering::SeqCst for atomic operations?

The IS_RECORDING flag uses Ordering::SeqCst (sequentially consistent ordering) to ensure that the state change is visible to all threads immediately. This prevents race conditions where the UI might attempt to start a new session before the previous one's cleanup is globally visible.

Where is the transcript data buffered during the shutdown process?

During shutdown, remaining audio chunks are flushed via stop_streams_and_force_flush(), and the transcription task is awaited without timeout in recording_commands.rs. This ensures all pending audio is processed through the Whisper pipeline and saved via the RecordingManager before the function returns and clears the global TRANSCRIPTION_TASK handle.

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 →