capacitor-audio-engine
Version:
High-quality audio recording Capacitor plugin with native iOS & Android support. Features pause/resume, microphone management, real-time monitoring, audio trimming, and comprehensive mobile audio recording capabilities.
1,157 lines (898 loc) • 29.7 kB
Markdown
# Capacitor Audio Engine 🎙️
Hey there! 👋 Welcome to the Native Audio plugin for Capacitor. This plugin makes it super easy to add high-quality audio recording to your mobile apps. Whether you're building a voice memo app, a podcast recorder, or just need to capture some audio, we've got you covered!
## 📑 Table of Contents
- [Capacitor Audio Engine 🎙️](#capacitor-audio-engine-️)
- [📑 Table of Contents](#-table-of-contents)
- [✨ Features](#-features)
- [📱 Platform Support](#-platform-support)
- [🚀 Installation](#-installation)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [iOS](#ios)
- [Android](#android)
- [📖 API Documentation](#-api-documentation)
- [Core Interfaces](#core-interfaces)
- [Methods](#methods)
- [Permission Management](#permission-management)
- [Recording Control](#recording-control)
- [Status & Information](#status--information)
- [Audio Processing](#audio-processing)
- [Microphone Management](#microphone-management)
- [Audio Playback](#audio-playback)
- [Event Handling](#event-handling)
- [Usage Example](#usage-example)
- [🔧 Troubleshooting](#-troubleshooting)
- [Common Issues](#common-issues)
- [🛠️ Technical Details](#️-technical-details)
- [Platform-Specific Implementations](#platform-specific-implementations)
- [Web](#web)
- [Android](#android-1)
- [iOS](#ios-1)
- [📚 Additional Documentation](#-additional-documentation)
- [🤝 Contributing](#-contributing)
- [📄 License](#-license)
- [📞 Need Help?](#-need-help)
## ✨ Features
### 🎙️ Audio Recording
- 🎯 Record high-quality audio on Android and iOS
- ⏯️ Pause and resume your recordings
- 📊 Monitor recording status in real-time
- 🔒 Handle permissions automatically
- ✂️ Trim your audio files
- 📝 Get detailed recording metadata
- 🎙️ **Microphone management** - Detect and switch between available microphones
- 🔍 **Microphone status** - Check if microphone is busy/in use by other apps
### 🎵 Audio Playback
- 📂 **Playlist support** - Initialize and manage playlists of multiple audio tracks
- ▶️ **Full playback controls** - Play, pause, resume, stop with seamless track transitions
- ⏭️ **Navigation** - Skip to next/previous track or jump to specific track index
- 🎯 **Seeking** - Seek to any position within the current track
- 📊 **Real-time info** - Get current track, position, duration, and playback status
- 🔔 **Event notifications** - Track changes, playback state, and error handling
- 🔄 **Auto-advance** - Automatically play next track when current track ends
- 📱 **Lock screen controls** - Native media controls on iOS and Android
- 🔊 **Background playback** - Continue playing when app is backgrounded
- 🎨 **Metadata support** - Display track title, artist, and artwork
- ⚡ **Audio preloading** - Preload audio files for faster playback start times
- 🎼 **Multi-audio resume** - Resume any of multiple audio files with custom settings
- 📋 **Audio information** - Get detailed metadata from local and remote audio files
- 📡 **Real-time monitoring** - Track playback progress and status changes
- 🌐 Cross-platform support (Web coming soon!)
- 🎚️ Consistent audio quality:
- Sample Rate: 44.1kHz
- Channels: 1 (mono)
- Bitrate: 128kbps
## 📱 Platform Support
| Feature | Android | iOS | Web |
| -------------------- | ------- | --- | --- |
| Recording | ✅ | ✅ | 🔜 |
| Pause/Resume | ✅ | ✅ | 🔜 |
| Permission Handling | ✅ | ✅ | 🔜 |
| Status Monitoring | ✅ | ✅ | 🔜 |
| Audio Trimming | ✅ | ✅ | 🔜 |
| Microphone Detection | ✅ | ✅ | 🔜 |
| Microphone Switching | ✅ | ✅ | 🔜 |
| Audio Playback | ✅ | ✅ | 🔜 |
| Playback Controls | ✅ | ✅ | 🔜 |
| Audio Preloading | ✅ | ✅ | ❌ |
| Multi-Audio Resume | ✅ | ✅ | ❌ |
| Audio Information | ✅ | ✅ | 🔜 |
> 💡 **Note:** Android and iOS are fully supported! Web support is coming soon - we're working on it! 🚧
## 🚀 Installation
### Prerequisites
- Node.js 14+ and npm
- Capacitor 5.0.0+
- iOS 13+ for iOS development
- Android 10+ (API level 29) for Android development
### Setup
1. Install the plugin:
NPM:
```bash
npm i capacitor-audio-engine
```
PNPM:
```bash
pnpm add capacitor-audio-engine
```
YARN
```bash
yarn add capacitor-audio-engine
```
2. Sync your project:
```bash
npx cap sync
```
3. Add required permissions:
#### iOS
Add these to your `Info.plist`:
```xml
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone to record audio</string>
```
#### Android
Add this to your `AndroidManifest.xml`:
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
## 📖 API Documentation
### Core Interfaces
#### `AudioFileInfo`
```typescript
export interface AudioFileInfo {
path: string;
webPath: string;
uri: string;
mimeType: string;
size: number;
duration: number;
sampleRate: number;
channels: number;
bitrate: number;
createdAt: number;
filename: string;
/**
* Base64-encoded audio data with MIME prefix (Data URI format)
* Format: "data:audio/m4a;base64,<base64-data>"
*/
base64?: string;
}
```
#### `RecordingOptions`
```typescript
export interface RecordingOptions {
/**
* Audio sample rate (Hz). Default: 44100
*/
sampleRate?: number;
/**
* Number of audio channels. Default: 1 (mono)
*/
channels?: number;
/**
* Audio bitrate (bps). Default: 128000
*/
bitrate?: number;
/**
* Note: The audio format is always .m4a (MPEG-4/AAC) on all platforms.
*/
}
```
#### `RecordingStatus`
```typescript
type RecordingStatus = 'idle' | 'recording' | 'paused';
```
#### `AudioRecordingEventName`
```typescript
type AudioRecordingEventName = 'durationChange' | 'error';
```
#### `DurationChangeData`
```typescript
export interface DurationChangeData {
duration: number;
}
```
#### `ErrorEventData`
```typescript
export interface ErrorEventData {
message: string;
code?: string | number;
details?: any;
}
```
#### `MicrophoneInfo`
```typescript
export interface MicrophoneInfo {
id: number;
name: string;
type: 'internal' | 'external' | 'unknown';
description?: string;
uid?: string; // iOS only
isConnected?: boolean; // Android only
}
```
#### `MicrophoneStatusResult`
```typescript
export interface MicrophoneStatusResult {
busy: boolean;
reason?: string;
}
```
#### `AvailableMicrophonesResult`
```typescript
export interface AvailableMicrophonesResult {
microphones: MicrophoneInfo[];
}
```
#### `SwitchMicrophoneOptions`
```typescript
export interface SwitchMicrophoneOptions {
microphoneId: number;
}
```
#### `SwitchMicrophoneResult`
```typescript
export interface SwitchMicrophoneResult {
success: boolean;
microphoneId: number;
}
```
#### `PlaybackStatus`
```typescript
type PlaybackStatus = 'idle' | 'loaded' | 'playing' | 'paused' | 'stopped' | 'completed' | 'error';
```
#### `PlaybackOptions`
```typescript
export interface PlaybackOptions {
/**
* Playback speed (0.5 - 2.0). Default: 1.0
*/
speed?: number;
/**
* Start time in seconds. Default: 0
*/
startTime?: number;
/**
* Whether to loop the audio. Default: false
*/
loop?: boolean;
/**
* Volume level (0.0 - 1.0). Default: 1.0
*/
volume?: number;
}
```
#### `ResumePlaybackOptions`
```typescript
export interface ResumePlaybackOptions {
/**
* URI of the audio file to resume
* If not provided, resumes the currently paused playback
*/
uri?: string;
/**
* Playback speed (0.5 - 2.0). Default: 1.0
*/
speed?: number;
/**
* Volume level (0.0 - 1.0). Default: 1.0
*/
volume?: number;
/**
* Whether to loop the audio. Default: false
*/
loop?: boolean;
}
```
#### `PreloadOptions`
```typescript
export interface PreloadOptions {
/**
* URI of the audio file to preload
*/
uri: string;
/**
* Whether to prepare for playback immediately. Default: true
*/
prepare?: boolean;
}
```
#### `AudioPlayerInfo`
```typescript
export interface AudioPlayerInfo {
status: PlaybackStatus;
currentTime: number;
duration: number;
speed?: number;
volume?: number;
isLooping?: boolean;
uri?: string;
}
```
#### `AudioPlaybackEventName`
```typescript
type AudioPlaybackEventName = 'playbackStatusChange' | 'playbackProgress' | 'playbackCompleted' | 'playbackError';
```
#### `PlaybackProgressData`
```typescript
export interface PlaybackProgressData {
currentTime: number;
duration: number;
position: number; // Playback position as percentage (0-100)
}
```
#### `PlaybackStatusData`
```typescript
export interface PlaybackStatusData {
status: PlaybackStatus;
currentTime?: number;
duration?: number;
}
```
#### `PlaybackErrorData`
```typescript
export interface PlaybackErrorData {
message: string;
code?: string | number;
details?: any;
}
```
#### `PlaybackCompletedData`
```typescript
export interface PlaybackCompletedData {
duration: number;
}
```
#### `GetAudioInfoOptions`
```typescript
export interface GetAudioInfoOptions {
/**
* URI of the audio file to analyze
* Supports:
* - Local file URIs (from stopRecording)
* - Remote CDN URLs (HTTP/HTTPS)
*/
uri: string;
}
```
### Methods
#### Permission Management
##### `checkPermission()`
Check if your app has permission to use the microphone.
```typescript
checkPermission(): Promise<{ granted: boolean; audioPermission?: boolean; notificationPermission?: boolean }>;
```
##### `requestPermission()`
Ask the user for microphone permission.
```typescript
requestPermission(): Promise<{ granted: boolean; audioPermission?: boolean; notificationPermission?: boolean }>;
```
#### Recording Control
##### `startRecording()`
Start recording audio from the device's microphone.
```typescript
startRecording(options?: RecordingOptions): Promise<void>;
```
##### `pauseRecording()`
Pause the current recording.
```typescript
pauseRecording(): Promise<void>;
```
##### `resumeRecording()`
Resume the current recording if it was previously paused.
```typescript
resumeRecording(): Promise<void>;
```
##### `stopRecording()`
Stop the current recording and get the recorded file information.
```typescript
stopRecording(): Promise<AudioFileInfo>;
```
#### Status & Information
##### `getDuration()`
Get the current recording duration.
```typescript
getDuration(): Promise<{ duration: number }>;
```
##### `getStatus()`
Check the current recording status.
```typescript
getStatus(): Promise<{ status: RecordingStatus; isRecording: boolean }>;
```
#### Audio Processing
##### `trimAudio()`
Trim an audio file to a specific duration.
```typescript
trimAudio(options: { uri: string; start: number; end: number }): Promise<AudioFileInfo>;
```
##### `getAudioInfo()`
Get detailed information about an audio file (local or remote).
```typescript
getAudioInfo(options: GetAudioInfoOptions): Promise<AudioFileInfo>;
```
**Example:**
```typescript
// Get info for a local recording
const localInfo = await CapacitorAudioEngine.getAudioInfo({
uri: 'file:///path/to/recording.m4a',
});
// Get info for a remote CDN file
const remoteInfo = await CapacitorAudioEngine.getAudioInfo({
uri: 'https://example.com/audio/sample.mp3',
});
console.log('Duration:', localInfo.duration, 'seconds');
console.log('File size:', localInfo.size, 'bytes');
console.log('Sample rate:', localInfo.sampleRate, 'Hz');
```
**Platform Notes:**
- **Android**: Uses MediaMetadataRetriever to extract metadata from local and remote files
- **iOS**: Uses AVAsset to extract metadata from local and remote files
- **Web**: Not supported
#### Microphone Management
##### `isMicrophoneBusy()`
Check if the microphone is currently being used by another application.
```typescript
isMicrophoneBusy(): Promise<MicrophoneStatusResult>;
```
**Example:**
```typescript
const status = await CapacitorAudioEngine.isMicrophoneBusy();
if (status.busy) {
console.log('Microphone is busy:', status.reason);
} else {
console.log('Microphone is available');
}
```
##### `getAvailableMicrophones()`
Get a list of available microphones (internal and external).
```typescript
getAvailableMicrophones(): Promise<AvailableMicrophonesResult>;
```
**Example:**
```typescript
const result = await CapacitorAudioEngine.getAvailableMicrophones();
result.microphones.forEach((mic) => {
console.log(`${mic.name} (${mic.type}): ${mic.isConnected ? 'Connected' : 'Disconnected'}`);
});
```
##### `switchMicrophone()`
Switch to a different microphone while keeping recording active.
```typescript
switchMicrophone(options: SwitchMicrophoneOptions): Promise<SwitchMicrophoneResult>;
```
**Example:**
```typescript
// Get available microphones
const result = await CapacitorAudioEngine.getAvailableMicrophones();
const externalMic = result.microphones.find((mic) => mic.type === 'external');
if (externalMic) {
try {
const switchResult = await CapacitorAudioEngine.switchMicrophone({
microphoneId: externalMic.id,
});
console.log('Switched to:', switchResult.message);
} catch (error) {
console.error('Failed to switch microphone:', error);
}
}
```
**Platform Notes:**
- **Android**: Shows primary built-in microphone + all external devices (headsets, USB, Bluetooth)
- **iOS**: Shows all available audio inputs from AVAudioSession
- **Web**: Not supported (returns empty array)
#### Audio Playback
The Audio Engine now supports comprehensive playlist-based audio playback with full control over multiple tracks.
##### `preloadTracks()`
Preload audio tracks from URLs and initialize playlist for optimal performance.
```typescript
preloadTracks(options: PreloadTracksOptions): Promise<void>;
interface PreloadTracksOptions {
tracks: string[]; // Array of audio URLs
preloadNext?: boolean; // Default: true
}
```
**Example:**
```typescript
const trackUrls = [
'https://example.com/song1.mp3',
'file:///path/to/local/song2.m4a',
'https://example.com/song3.mp3'
];
await CapacitorAudioEngine.preloadTracks({
tracks: trackUrls,
preloadNext: true,
});
```
##### `playAudio()`
Start playback of the current track in the playlist.
```typescript
playAudio(): Promise<void>;
```
**Example:**
```typescript
await CapacitorAudioEngine.playAudio();
```
##### `pauseAudio()`
Pause the current audio playback.
```typescript
pauseAudio(): Promise<void>;
```
##### `resumeAudio()`
Resume paused audio playback.
```typescript
resumeAudio(): Promise<void>;
```
##### `stopAudio()`
Stop audio playback and reset to the beginning of the current track.
```typescript
stopAudio(): Promise<void>;
```
##### `seekAudio()`
Seek to a specific position within the current track.
```typescript
seekAudio(options: SeekOptions): Promise<void>;
interface SeekOptions {
seconds: number;
}
```
**Example:**
```typescript
// Seek to 30 seconds
await CapacitorAudioEngine.seekAudio({ seconds: 30 });
```
##### `skipToNext()`
Skip to the next track in the playlist.
```typescript
skipToNext(): Promise<void>;
```
##### `skipToPrevious()`
Skip to the previous track in the playlist.
```typescript
skipToPrevious(): Promise<void>;
```
##### `skipToIndex()`
Jump to a specific track in the playlist by index.
```typescript
skipToIndex(options: SkipToIndexOptions): Promise<void>;
interface SkipToIndexOptions {
index: number; // Zero-based track index
}
```
**Example:**
```typescript
// Jump to the third track (index 2)
await CapacitorAudioEngine.skipToIndex({ index: 2 });
```
##### `getPlaybackInfo()`
Get comprehensive information about the current playback state.
```typescript
getPlaybackInfo(): Promise<PlaybackInfo>;
interface PlaybackInfo {
currentTrack: AudioTrack | null;
currentIndex: number;
currentPosition: number; // in seconds
duration: number; // in seconds
isPlaying: boolean;
status: PlaybackStatus; // 'idle' | 'loading' | 'playing' | 'paused' | 'stopped'
}
```
**Example:**
```typescript
const info = await CapacitorAudioEngine.getPlaybackInfo();
console.log('Current track:', info.currentTrack?.title);
console.log('Position:', `${info.currentPosition}s / ${info.duration}s`);
console.log('Playing:', info.isPlaying);
console.log('Track index:', info.currentIndex);
```
#### Event Handling
##### `addListener()`
Add a listener for recording or playback events.
```typescript
addListener<T extends AudioEventName>(
eventName: T,
callback: (event: AudioEventMap[T]) => void,
): Promise<PluginListenerHandle>;
```
**Recording Event Examples:**
```typescript
// Listen for recording duration changes
await CapacitorAudioEngine.addListener('durationChange', (event) => {
console.log('Recording duration:', event.duration, 'seconds');
});
// Listen for recording errors
await CapacitorAudioEngine.addListener('error', (event) => {
console.error('Recording error:', event.message);
});
```
**Playback Event Examples:**
```typescript
// Listen for track changes
await CapacitorAudioEngine.addListener('trackChanged', (event) => {
console.log('Track changed:', event.track.title, 'at index', event.index);
// Update UI to show new track info
});
// Listen for track completion
await CapacitorAudioEngine.addListener('trackEnded', (event) => {
console.log('Track ended:', event.track.title);
// Track will auto-advance to next if available
});
// Listen for playback start
await CapacitorAudioEngine.addListener('playbackStarted', (event) => {
console.log('Playback started:', event.track.title);
// Update play/pause button state
});
// Listen for playback pause
await CapacitorAudioEngine.addListener('playbackPaused', (event) => {
console.log('Playback paused:', event.track.title, 'at', event.position, 'seconds');
// Update play/pause button state
});
// Listen for playback errors
await CapacitorAudioEngine.addListener('playbackError', (event) => {
console.error('Playback error:', event.message);
// Show error message to user
});
await CapacitorAudioEngine.addListener('playbackCompleted', (data) => {
console.log('Playback completed, duration:', data.duration);
});
// Listen for playback errors
await CapacitorAudioEngine.addListener('playbackError', (data) => {
console.error('Playback error:', data.message);
});
```
##### `removeAllListeners()`
Remove all listeners for recording events.
```typescript
removeAllListeners(): Promise<void>;
```
> **Note:** The audio format is always `.m4a` (MPEG-4/AAC) on all platforms.
#### Usage Example
Here's a complete example of how to use the plugin with microphone management:
```typescript
import { CapacitorAudioEngine } from 'capacitor-audio-engine';
class AudioRecorder {
private isRecording = false;
private availableMicrophones: MicrophoneInfo[] = [];
private selectedMicrophoneId: number | null = null;
async initialize() {
// Check and request permission
const permission = await CapacitorAudioEngine.checkPermission();
if (!permission.granted) {
const result = await CapacitorAudioEngine.requestPermission();
if (!result.granted) {
throw new Error('Microphone permission denied');
}
}
// Load available microphones
await this.loadMicrophones();
// Set up event listeners
await this.setupEventListeners();
}
async loadMicrophones() {
try {
const result = await CapacitorAudioEngine.getAvailableMicrophones();
this.availableMicrophones = result.microphones;
// Select internal microphone by default
const internalMic = result.microphones.find((mic) => mic.type === 'internal');
if (internalMic) {
this.selectedMicrophoneId = internalMic.id;
}
console.log('Available microphones:', result.microphones);
} catch (error) {
console.error('Failed to load microphones:', error);
}
}
async startRecording() {
try {
// Check if microphone is busy
const status = await CapacitorAudioEngine.isMicrophoneBusy();
if (status.busy) {
throw new Error(`Microphone is busy: ${status.reason}`);
}
// Switch to selected microphone if available
if (this.selectedMicrophoneId) {
await CapacitorAudioEngine.switchMicrophone({
microphoneId: this.selectedMicrophoneId,
});
}
// Start recording
await CapacitorAudioEngine.startRecording({
sampleRate: 44100,
channels: 1,
bitrate: 128000,
});
this.isRecording = true;
console.log('Recording started');
} catch (error) {
console.error('Failed to start recording:', error);
}
}
async stopRecording() {
try {
const result = await CapacitorAudioEngine.stopRecording();
this.isRecording = false;
console.log('Recording saved:', result);
return result;
} catch (error) {
console.error('Failed to stop recording:', error);
}
}
async playRecording(audioFile: AudioFileInfo) {
try {
// Preload the audio file for better performance
await CapacitorAudioEngine.preload({
uri: audioFile.uri,
prepare: true,
});
// Start playback with custom options
await CapacitorAudioEngine.startPlayback({
uri: audioFile.uri,
speed: 1.0, // Normal speed
volume: 1.0, // Full volume
loop: false, // Don't loop
startTime: 0, // Start from beginning
});
console.log('Playback started');
} catch (error) {
console.error('Failed to start playback:', error);
}
}
async pausePlayback() {
try {
await CapacitorAudioEngine.pausePlayback();
console.log('Playback paused');
} catch (error) {
console.error('Failed to pause playback:', error);
}
}
async resumePlayback() {
try {
// Resume current playback (existing behavior)
await CapacitorAudioEngine.resumePlayback();
console.log('Playback resumed');
} catch (error) {
console.error('Failed to resume playback:', error);
}
}
async resumePlaybackWithOptions(uri?: string, speed?: number, volume?: number, loop?: boolean) {
try {
// Resume with custom options - can switch to different audio files
await CapacitorAudioEngine.resumePlayback({
uri, // Optional: switch to different audio file
speed, // Optional: custom playback speed
volume, // Optional: custom volume level
loop, // Optional: enable/disable looping
});
console.log('Playback resumed with custom options');
} catch (error) {
console.error('Failed to resume playback with options:', error);
}
}
async stopPlayback() {
try {
await CapacitorAudioEngine.stopPlayback();
console.log('Playback stopped');
} catch (error) {
console.error('Failed to stop playback:', error);
}
}
async seekTo(time: number) {
try {
await CapacitorAudioEngine.seekTo({ time });
console.log(`Seeked to ${time} seconds`);
} catch (error) {
console.error('Failed to seek:', error);
}
}
async getPlaybackStatus() {
try {
const status = await CapacitorAudioEngine.getPlaybackStatus();
console.log('Playback status:', status);
return status;
} catch (error) {
console.error('Failed to get playback status:', error);
}
}
async switchMicrophone(microphoneId: number) {
try {
const result = await CapacitorAudioEngine.switchMicrophone({ microphoneId });
this.selectedMicrophoneId = result.microphoneId;
console.log('Switched microphone:', result.message);
} catch (error) {
console.error('Failed to switch microphone:', error);
}
}
private async setupEventListeners() {
// Recording event listeners
await CapacitorAudioEngine.addListener('durationChange', (data) => {
console.log('Recording duration:', data.duration);
});
await CapacitorAudioEngine.addListener('error', (data) => {
console.error('Recording error:', data.message);
});
// Playback event listeners
await CapacitorAudioEngine.addListener('playbackProgress', (data) => {
console.log(`Playback progress: ${data.currentTime}s / ${data.duration}s`);
});
await CapacitorAudioEngine.addListener('playbackStatusChange', (data) => {
console.log('Playback status changed to:', data.status);
});
await CapacitorAudioEngine.addListener('playbackCompleted', (data) => {
console.log('Playback completed, duration:', data.duration);
});
await CapacitorAudioEngine.addListener('playbackError', (data) => {
console.error('Playback error:', data.message);
});
}
async cleanup() {
await CapacitorAudioEngine.removeAllListeners();
}
}
// Usage
const recorder = new AudioRecorder();
await recorder.initialize();
await recorder.startRecording();
// ... record audio ...
const audioFile = await recorder.stopRecording();
```
## 🔧 Troubleshooting
### Common Issues
1. **Permission Denied**
- Ensure you've added the required permissions in your platform-specific files
- Check if the user has granted permission in their device settings
- Try requesting permission again
2. **Recording Not Starting**
- Verify that you're not already recording
- Check if the microphone is being used by another app
- Ensure you have sufficient storage space
3. **Audio Quality Issues**
- Check if the device's microphone is working properly
- Verify that no other apps are using the microphone
- Ensure you're not in a noisy environment
4. **File Access Issues**
- Check if the app has proper storage permissions
- Verify that the storage path is accessible
- Ensure there's enough free space
5. **Microphone Issues**
- Use `isMicrophoneBusy()` to check if another app is using the microphone
- Try refreshing available microphones with `getAvailableMicrophones()`
- Ensure external microphones (headsets, USB) are properly connected
- On Android: Built-in microphone should always be available
- On iOS: Check if microphone access is enabled in device settings
6. **Microphone Switching Issues**
- Verify the microphone ID exists in the available microphones list
- External microphones may disconnect during recording
- Some devices may not support seamless microphone switching during recording
## 🛠️ Technical Details
### Platform-Specific Implementations
#### Web
- Uses MediaRecorder API
- Format: WebM container with Opus codec
- MIME Type: 'audio/webm;codecs=opus'
- Permission: Uses navigator.permissions.query API
- Audio trimming: Not supported (logs console message)
- **Microphone Management**: Not supported (returns empty arrays and placeholder responses)
#### Android
- Uses MediaRecorder
- Format: M4A container with AAC codec (MPEG-4/AAC, always .m4a)
- MIME Type: 'audio/m4a' or 'audio/m4a'
- Audio Source: MIC
- Storage: App's external files directory under "Recordings" folder
- Filename Format: "recording\_[timestamp].m4a"
- **Background Recording**: Full support via foreground service with microphone type
- **Required Permission**: `android.permission.RECORD_AUDIO`
- **Background Permissions**: `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_MICROPHONE`, `POST_NOTIFICATIONS`
- **Microphone Management**:
- Uses AudioManager.getDevices() to enumerate input devices (API 23+)
- Shows primary built-in microphone + all external devices
- Supports headset, USB, and Bluetooth microphones
- Uses AudioRecord for microphone busy detection
- Microphone switching uses MediaRecorder.setPreferredDevice() (API 28+)
#### iOS
- Uses AVAudioRecorder
- Format: M4A container with AAC codec (MPEG-4/AAC, always .m4a)
- MIME Type: 'audio/m4a'
- Quality: High
- Uses AVAssetExportSession for audio trimming
- **Background Recording**: Supports continuous recording when app is backgrounded (requires 'audio' background mode)
- **Required Permission**: NSMicrophoneUsageDescription in Info.plist
- **Background Mode**: UIBackgroundModes with 'audio' capability
- **Microphone Management**:
- Uses AVAudioSession.availableInputs to list audio inputs
- Supports built-in, wired headset, and Bluetooth microphones
- Uses AVAudioSession.setPreferredInput() for microphone switching
- Real-time microphone busy detection via AVAudioSession
## 📚 Additional Documentation
For more detailed examples and advanced usage patterns, check out:
- **[Microphone Management Guide](docs/MICROPHONE_USAGE.md)** - Comprehensive guide for microphone detection, switching, and troubleshooting
## 🤝 Contributing
We love contributions! Whether it's fixing bugs, adding features, or improving docs, your help makes this plugin better for everyone. Here's how to help:
1. Fork the repo
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 📞 Need Help?
Found a bug? Have a feature request? Just want to chat? [Open an issue](https://github.com/abdelfattah-ashour/capacitor-native-audio/issues) on GitHub and we'll help you out!
---
Made with ❤️ by [Abdelfattah Ashour](https://github.com/abdelfattah-ashour)