Skip to content

Repository files navigation

Tauri Plugin Audio Recorder

Cross-platform audio recording for Tauri 2.x. Desktop captures to WAV (PCM via cpal + hound); mobile uses the native encoder so output is M4A/AAC.

Platform Matrix

Platform Engine Output
macOS CPAL WAV
Windows CPAL WAV
Linux CPAL WAV
iOS AVAudioRecorder M4A
Android MediaRecorder M4A

Installation

Rust

[dependencies]
tauri-plugin-audio-recorder = "0.1"

TypeScript

npm install tauri-plugin-audio-recorder-api

Setup

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_audio_recorder::init())
        .run(tauri::generate_context!())
        .unwrap();
}

Permissions

{ "permissions": ["audio-recorder:default"] }

Granular:

{
  "permissions": [
    "audio-recorder:allow-start-recording",
    "audio-recorder:allow-stop-recording",
    "audio-recorder:allow-pause-recording",
    "audio-recorder:allow-resume-recording",
    "audio-recorder:allow-get-status",
    "audio-recorder:allow-get-devices",
    "audio-recorder:allow-get-channels",
    "audio-recorder:allow-check-permission",
    "audio-recorder:allow-request-permission"
  ]
}

Platform Setup

AndroidAndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

iOSInfo.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Microphone access required for recording.</string>

Usage

import {
  startRecording,
  stopRecording,
  pauseRecording,
  resumeRecording,
  getStatus,
  getDevices,
  getChannels,
  requestPermission,
} from "tauri-plugin-audio-recorder-api";

const { granted } = await requestPermission();
if (!granted) return;

await startRecording({
  outputPath: "/path/to/recording", // extension is appended automatically
  quality: "medium",
  maxDuration: 300,
});

const status = await getStatus(); // { state, durationMs, outputPath }
await pauseRecording();
await resumeRecording();

const result = await stopRecording();
// result.filePath ends with ".wav" on desktop, ".m4a" on mobile
console.log(`${result.durationMs}ms → ${result.filePath} (${result.fileSize} bytes)`);

Handling the format difference

Desktop produces WAV; mobile produces M4A. Check the extension when processing across platforms:

const result = await stopRecording();
if (result.filePath.endsWith(".m4a")) {
  // Convert with tauri-plugin-media-toolkit if WAV is needed
}

Device enumeration and selection (desktop only)

const { devices } = await getDevices(); // returns [] on mobile
devices.forEach(d => console.log(d.name, d.isDefault ? "(default)" : ""));

// Record from a specific device instead of the system default
await startRecording({
  outputPath: "/path/to/recording",
  deviceId: devices[0].id,
});

If the requested device is no longer available when recording starts (e.g. unplugged), the recorder logs a warning and falls back to the system default device.

Channel selection (desktop only)

By default every channel the device provides is recorded. To capture a single channel of a multi-channel input, pick one from getChannels():

const { channels } = await getChannels(devices[0].id); // omit the id for the default device
channels.forEach(c => console.log(c.id, c.name)); // "0" "Channel 1", "1" "Channel 2", …

await startRecording({
  outputPath: "/path/to/recording",
  deviceId: devices[0].id,
  channelId: channels[1].id, // mono recording of Channel 2
});

Unlike deviceId, an invalid channelId does not fall back — startRecording() throws Invalid channel: … and nothing is recorded.

Max-duration detection

maxDuration stops recording automatically with no callback. Poll to detect completion — and do not call stopRecording() afterward, since the recorder is already idle:

const outputPath = "/path/to/recording";
await startRecording({ outputPath, maxDuration: 60 });

const poll = setInterval(async () => {
  const { state } = await getStatus();
  if (state === "idle") {
    clearInterval(poll);
    // File is already saved at outputPath + ".wav" (desktop) or ".m4a" (mobile)
  }
}, 1000);

API Reference

  • startRecording(config) — starts capture; throws if already recording
  • stopRecording()RecordingResult — finalises and returns file metadata
  • pauseRecording() — Android requires API 24+ (Android 7.0+)
  • resumeRecording()
  • getStatus(){ state, durationMs, outputPath }
  • getDevices(){ devices } — desktop only, empty on mobile
  • getChannels(deviceId?){ channels } — desktop only, empty on mobile
  • checkPermission() / requestPermission(){ granted, canRequest }

RecordingConfig

interface RecordingConfig {
  outputPath: string;                   // without extension
  quality?: "low" | "medium" | "high"; // 16kHz mono | 44.1kHz mono | 48kHz stereo
  maxDuration?: number;                 // seconds, 0 = unlimited
  deviceId?: string;                    // id from getDevices(); desktop only, default = system default
  channelId?: string;                   // id from getChannels(); desktop only, default = all channels
}

RecordingResult

interface RecordingResult {
  filePath: string;   // full path with extension
  durationMs: number;
  fileSize: number;
  sampleRate: number;
  channels: number;
}

Quality Presets

Preset Sample Rate Channels
low 16 kHz Mono
medium 44.1 kHz Mono
high 48 kHz Stereo

Troubleshooting

Permission denied — iOS: verify NSMicrophoneUsageDescription in Info.plist. Android: verify RECORD_AUDIO in AndroidManifest. Always call requestPermission() before startRecording().

Pause/Resume on Android — requires Android N (API 24+). Catch the error and fall back to stop/restart on older devices.

Empty or tiny output file — the output path's parent directory doesn't exist, or recording was stopped immediately. Check that result.durationMs > 100 and result.fileSize > 1000.

Used By

See USED_BY.md for projects using this plugin in production.

License

MIT

About

Cross-platform audio recording plugin for Tauri 2.x applications. Provides audio recording functionality for desktop (Windows, macOS, Linux) and mobile (iOS, Android).

Resources

Stars

19 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages