| Crates.io | tauri-plugin-audio-recorder |
| lib.rs | tauri-plugin-audio-recorder |
| version | 0.1.0 |
| created_at | 2025-12-18 20:16:58.965127+00 |
| updated_at | 2025-12-18 20:16:58.965127+00 |
| description | Cross-platform audio recording plugin for Tauri applications with pause/resume support |
| homepage | https://github.com/brenogonzaga/tauri-plugin-audio-recorder |
| repository | https://github.com/brenogonzaga/tauri-plugin-audio-recorder |
| max_upload_size | |
| id | 1993393 |
| size | 253,113 |
Cross-platform audio recording plugin for Tauri 2.x applications. Provides audio recording functionality for desktop (Windows, macOS, Linux) and mobile (iOS, Android).
Add the plugin to your Cargo.toml:
[dependencies]
tauri-plugin-audio-recorder = "0.1"
Install the JavaScript guest bindings:
npm install tauri-plugin-audio-recorder-api
# or
yarn add tauri-plugin-audio-recorder-api
# or
pnpm add tauri-plugin-audio-recorder-api
In your Tauri app setup:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_audio_recorder::init())
.run(tauri::generate_context!())
.expect("error while running application");
}
Add permissions to your capabilities/default.json:
{
"permissions": ["audio-recorder:default"]
}
For granular permissions, you can specify individual commands:
{
"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-check-permission",
"audio-recorder:allow-request-permission"
]
}
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to record audio for exercises.</string>
import {
startRecording,
stopRecording,
pauseRecording,
resumeRecording,
getStatus,
getDevices,
checkPermission,
requestPermission,
} from "tauri-plugin-audio-recorder-api";
// Check and request permission
const permission = await checkPermission();
if (!permission.granted) {
const result = await requestPermission();
if (!result.granted) {
console.error("Microphone permission denied");
return;
}
}
// Start recording
await startRecording({
outputPath: "/path/to/recording", // without extension
quality: "medium", // "low" | "medium" | "high"
maxDuration: 300, // max 5 minutes (0 = unlimited)
});
// Check status
const status = await getStatus();
console.log(`State: ${status.state}, Duration: ${status.durationMs}ms`);
// Pause/Resume
await pauseRecording();
await resumeRecording();
// Stop and get result
const result = await stopRecording();
console.log(`Recorded ${result.durationMs}ms to ${result.filePath}`);
console.log(`File size: ${result.fileSize} bytes`);
console.log(
`Sample rate: ${result.sampleRate}Hz, Channels: ${result.channels}`
);
import { getStatus } from "tauri-plugin-audio-recorder-api";
// Poll for status updates during recording
const intervalId = setInterval(async () => {
const status = await getStatus();
if (status.state === "recording") {
console.log(`Recording: ${Math.floor(status.durationMs / 1000)}s`);
updateUI(status.durationMs);
} else if (status.state === "idle") {
clearInterval(intervalId);
}
}, 100); // Update every 100ms
// Stop recording
await stopRecording();
clearInterval(intervalId);
import { getDevices, startRecording } from "tauri-plugin-audio-recorder-api";
// List available devices (Desktop only)
const { devices } = await getDevices();
console.log("Available microphones:");
devices.forEach(device => {
console.log(`- ${device.name} ${device.isDefault ? "(default)" : ""}`);
});
// Note: Currently uses system default device
// Device selection will be added in future versions
startRecording(config: RecordingConfig): Promise<void>Start audio recording.
Config:
outputPath: File path without extension (required)format: Audio format (currently only "wav", mobile outputs M4A)quality: "low" (16kHz mono) | "medium" (44.1kHz mono) | "high" (48kHz stereo)maxDuration: Max recording duration in seconds (0 = unlimited)Throws: Error if already recording or permission denied
stopRecording(): Promise<RecordingResult>Stop recording and get result.
Returns:
filePath: Full path to recorded file (with .wav or .m4a extension)durationMs: Recording duration in millisecondsfileSize: File size in bytessampleRate: Sample rate used (Hz)channels: Number of channels (1 = mono, 2 = stereo)Throws: Error if not recording
pauseRecording(): Promise<void>Pause current recording.
Note: Requires Android N+ (API 24+) on Android.
resumeRecording(): Promise<void>Resume paused recording.
getStatus(): Promise<RecordingStatus>Get current recording status.
Returns:
state: "idle" | "recording" | "paused"durationMs: Current duration in millisecondsoutputPath: Output file path (without extension) or null if idlegetDevices(): Promise<DevicesResponse>List available audio input devices (Desktop only).
Returns:
devices: Array of audio devices with id, name, and isDefaultNote: Returns empty array on mobile (uses system default device).
checkPermission(): Promise<PermissionResponse>Check microphone permission status.
Returns:
granted: Whether permission is grantedcanRequest: Whether permission can be requested (not permanently denied)requestPermission(): Promise<PermissionResponse>Request microphone permission from user.
Returns: Same as checkPermission()
Behavior:
interface RecordingConfig {
outputPath: string; // File path without extension
format?: "wav"; // Input format option (currently only "wav"; output container varies by platform)
quality?: "low" | "medium" | "high"; // Quality preset
maxDuration?: number; // Max duration in seconds (0 = unlimited)
}
interface RecordingResult {
filePath: string; // Full path to recorded file
durationMs: number; // Duration in milliseconds
fileSize: number; // File size in bytes
sampleRate: number; // Sample rate used
channels: number; // Number of channels
}
interface RecordingStatus {
state: "idle" | "recording" | "paused";
durationMs: number;
outputPath: string | null;
}
| Feature | Windows | macOS | Linux | iOS | Android |
|---|---|---|---|---|---|
| Recording | ✅ | ✅ | ✅ | ✅ | ✅ |
| Pause/Resume | ✅ | ✅ | ✅ | ✅ | ✅* |
| Status Monitoring | ✅ | ✅ | ✅ | ✅ | ✅ |
| Device Enumeration | ✅ | ✅ | ✅ | ❌ | ❌ |
| WAV Output | ✅ | ✅ | ✅ | ⚠️ | ❌ |
| M4A Output | ❌ | ❌ | ❌ | ✅ | ✅ |
| Max Duration | ✅ | ✅ | ✅ | ✅ | ✅ |
| Quality Presets | ✅ | ✅ | ✅ | ✅ | ✅ |
Legend:
| Preset | Sample Rate | Channels | Use Case |
|---|---|---|---|
low |
16 kHz | Mono | Voice/Speech |
medium |
44.1 kHz | Mono | General purpose |
high |
48 kHz | Stereo | Music/High quality |
⚠️ Important: Output format varies by platform due to native API constraints.
| Platform | Output Format | Extension | Codec | Notes |
|---|---|---|---|---|
| Desktop | WAV | .wav |
PCM | True uncompressed WAV |
| Android | M4A | .m4a |
AAC | MediaRecorder uses AAC encoder |
| iOS | M4A | .m4a |
AAC | AVAudioRecorder with kAudioFormatMPEG4AAC |
When processing recordings across platforms, check the file extension or use a media library to detect the format:
import { stopRecording } from "tauri-plugin-audio-recorder-api";
const result = await stopRecording();
const extension = result.filePath.split(".").pop();
if (extension === "wav") {
// Desktop: PCM WAV - can be processed directly
} else if (extension === "m4a") {
// Mobile: AAC in M4A container - may need transcoding for some operations
}
cpal + hound which natively produce WAVIf you need consistent format across platforms, use the media-editor plugin to convert:
import { stopRecording } from "tauri-plugin-audio-recorder-api";
import { convert } from "tauri-plugin-media-editor-api";
const result = await stopRecording();
// Convert mobile M4A to WAV if needed
if (result.filePath.endsWith(".m4a")) {
const converted = await convert({
inputPath: result.filePath,
outputPath: result.filePath.replace(".m4a", ""),
format: "wav",
audioQuality: "lossless",
});
console.log(`Converted to WAV: ${converted.outputPath}`);
}
Always request permission before recording:
const perm = await requestPermission();
if (!perm.granted) {
if (perm.canRequest) {
console.error("User denied permission");
} else {
console.error("Permission permanently denied. Enable in system settings.");
}
return;
}
iOS: Ensure NSMicrophoneUsageDescription is in Info.plist
Android: Ensure RECORD_AUDIO permission is in AndroidManifest.xml
Solution: Stop current recording before starting new one:
const status = await getStatus();
if (status.state !== "idle") {
await stopRecording();
}
await startRecording(config);
Issue: Pause/Resume requires Android N+ (API 24+, Android 7.0+)
Solution: Check Android version or handle gracefully:
try {
await pauseRecording();
} catch (error) {
if (String(error).includes("not supported")) {
console.warn("Pause not supported on this Android version");
// Fallback: stop and restart recording
}
}
Checklist:
Debug:
const { devices } = await getDevices();
if (devices.length === 0) {
console.error("No audio input devices found");
console.log("Check system audio settings");
}
Common causes:
Solution: Add minimum duration check:
const result = await stopRecording();
if (result.durationMs < 100) {
console.warn("Recording too short, may be corrupted");
}
if (result.fileSize < 1000) {
console.warn("File size too small, recording may have failed");
}
Note: maxDuration is a hard limit. Recording stops automatically when reached, but you won't receive a callback.
Recommended pattern:
await startRecording({
outputPath: "/path/to/recording",
maxDuration: 60, // 60 seconds
});
// Poll status to detect when stopped
const checkInterval = setInterval(async () => {
const status = await getStatus();
if (status.state === "idle") {
clearInterval(checkInterval);
const result = await stopRecording();
console.log("Recording completed:", result);
}
}, 1000);
Problem: Desktop produces WAV, mobile produces M4A.
Solution: Use Media Toolkit plugin for conversion:
import { stopRecording } from "tauri-plugin-audio-recorder-api";
import { convert } from "tauri-plugin-media-toolkit-api";
const result = await stopRecording();
if (result.filePath.endsWith(".m4a")) {
// Convert mobile M4A to WAV
const converted = await convert({
inputPath: result.filePath,
outputPath: result.filePath.replace(".m4a", ""),
format: "wav",
audioQuality: "lossless",
});
console.log("Converted to WAV:", converted.outputPath);
}
This is by design. The plugin automatically appends the correct extension:
// Input: outputPath = "/path/to/recording"
// Desktop output: "/path/to/recording.wav"
// Mobile output: "/path/to/recording.m4a"
// ❌ Don't include extension:
outputPath: "/path/to/recording.wav"; // Wrong
// ✅ Correct:
outputPath: "/path/to/recording"; // Correct
cpal for cross-platform audio inputhound for WAV encodingRECORD_AUDIO permissionNSMicrophoneUsageDescription in Info.plistSee the examples/audio-recorder-example directory for a complete working demo with React + Material UI, featuring:
MIT