tchvu3/capacitor-voice-recorder
Capacitor plugin for simple voice recording
| Maintainer | GitHub |
|---|---|
| Avihu Harush | tchvu3 |
npm install --save capacitor-voice-recorder
npx cap sync
Add the following to your AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />Add the following to your Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone to record audio.</string>| Name | Android | iOS | Web |
|---|---|---|---|
| canDeviceVoiceRecord | ✅ | ✅ | ✅ |
| requestAudioRecordingPermission | ✅ | ✅ | ✅ |
| hasAudioRecordingPermission | ✅ | ✅ | ✅ |
| startRecording | ✅ | ✅ | ✅ |
| stopRecording | ✅ | ✅ | ✅ |
| pauseRecording | ✅ | ✅ | ✅ |
| resumeRecording | ✅ | ✅ | ✅ |
| getCurrentStatus | ✅ | ✅ | ✅ |
The capacitor-voice-recorder plugin allows you to record audio on Android, iOS, and Web platforms.
Below is a summary
of the key methods and how to use them.
Check if the device/browser can record audio.
VoiceRecorder.canDeviceVoiceRecord().then((result: GenericResponse) => console.log(result.value));| Return Value | Description |
|---|---|
{ value: true } |
The device/browser can record audio. |
{ value: false } |
The browser cannot record audio. Note: On mobile, it always returns { value: true }. |
Request audio recording permission from the user.
VoiceRecorder.requestAudioRecordingPermission().then((result: GenericResponse) => console.log(result.value));| Return Value | Description |
|---|---|
{ value: true } |
Permission granted. |
{ value: false } |
Permission denied. |
Check if the audio recording permission has been granted.
VoiceRecorder.hasAudioRecordingPermission().then((result: GenericResponse) => console.log(result.value));| Return Value | Description |
|---|---|
{ value: true } |
Permission granted. |
{ value: false } |
Permission denied. |
| Error Code | Description |
|---|---|
COULD_NOT_QUERY_PERMISSION_STATUS |
Failed to query permission status. |
Start the audio recording.
Optional options can be used with this method to save the file in the device's filesystem and return a path to that file instead of a base64 string. This greatly increases performance for large files.
VoiceRecorder.startRecording(options?: RecordingOptions)
.then((result: GenericResponse) => console.log(result.value))
.catch(error => console.log(error));| Option | Description |
|---|---|
| directory | Specifies a Capacitor Filesystem Directory |
| subDirectory | Specifies a custom sub-directory (optional) |
| Return Value | Description |
|---|---|
{ value: true } |
Recording started successfully. |
| Error Code | Description |
|---|---|
MISSING_PERMISSION |
Required permission is missing. |
DEVICE_CANNOT_VOICE_RECORD |
Device/browser cannot record audio. |
ALREADY_RECORDING |
A recording is already in progress. |
MICROPHONE_BEING_USED |
Microphone is being used by another app. |
FAILED_TO_RECORD |
Unknown error occurred during recording. |
Stops the audio recording and returns the recording data.
When a directory option has been passed to the VoiceRecorder.startRecording method the data will include a path instead of a recordDataBase64
VoiceRecorder.stopRecording()
.then((result: RecordingData) => console.log(result.value))
.catch(error => console.log(error));| Return Value | Description |
|---|---|
recordDataBase64 |
The recorded audio data in Base64 format. |
msDuration |
The duration of the recording in milliseconds. |
mimeType |
The MIME type of the recorded audio. |
path |
The path to the audio file |
| Error Code | Description |
|---|---|
RECORDING_HAS_NOT_STARTED |
No recording in progress. |
EMPTY_RECORDING |
Recording stopped immediately after starting. |
FAILED_TO_FETCH_RECORDING |
Unknown error occurred while fetching the recording. |
FAILED_TO_MERGE_RECORDING |
Failed to merge audio segments after interruption (iOS only). |
Pause the ongoing audio recording.
VoiceRecorder.pauseRecording()
.then((result: GenericResponse) => console.log(result.value))
.catch(error => console.log(error));| Return Value | Description |
|---|---|
{ value: true } |
Recording paused successfully. |
{ value: false } |
Recording is already paused. |
| Error Code | Description |
|---|---|
RECORDING_HAS_NOT_STARTED |
No recording in progress. |
NOT_SUPPORTED_OS_VERSION |
Operation not supported on the current OS version. |
Resumes a paused or interrupted audio recording.
VoiceRecorder.resumeRecording()
.then((result: GenericResponse) => console.log(result.value))
.catch(error => console.log(error));| Return Value | Description |
|---|---|
{ value: true } |
Recording resumed successfully. |
{ value: false } |
Recording is already running. |
Note: This method works with both PAUSED (user-initiated) and INTERRUPTED (system-initiated) states.
| Error Code | Description |
|---|---|
RECORDING_HAS_NOT_STARTED |
No recording in progress. |
NOT_SUPPORTED_OS_VERSION |
Operation not supported on the current OS version. |
Retrieves the current status of the recorder.
VoiceRecorder.getCurrentStatus()
.then((result: CurrentRecordingStatus) => console.log(result.status))
.catch(error => console.log(error));| Status Code | Description |
|---|---|
NONE |
Plugin is idle and waiting to start a new recording. |
RECORDING |
Plugin is currently recording. |
PAUSED |
Recording is paused by user. |
INTERRUPTED |
Recording was paused due to system interruption. |
The plugin automatically handles audio interruptions on iOS and Android (such as phone calls, other apps using the microphone, or system notifications). When an interruption occurs, the recording is automatically paused and the state changes to INTERRUPTED.
-
Interruption Begins: When a phone call comes in or another app takes audio focus, the plugin automatically pauses the recording and emits a
voiceRecordingInterruptedevent. -
Interruption Ends: When the interruption ends (e.g., phone call finishes), the plugin emits a
voiceRecordingInterruptionEndedevent, but keeps the state asINTERRUPTED. -
User Decision: The app can then decide whether to resume recording (using
resumeRecording()) or stop it (usingstopRecording()).
import { VoiceRecorder } from 'capacitor-voice-recorder';
// Listen for interruption events (iOS & Android only)
VoiceRecorder.addListener('voiceRecordingInterrupted', () => {
console.log('Recording was interrupted (e.g., phone call)');
// Update UI to show interrupted state
});
VoiceRecorder.addListener('voiceRecordingInterruptionEnded', () => {
console.log('Interruption ended - recording is still paused');
// Optionally prompt user to resume or stop recording
// VoiceRecorder.resumeRecording() or VoiceRecorder.stopRecording()
});| Platform | Interruption Handling |
|---|---|
| iOS | ✅ Full support (AVAudioSession interruption notifications) |
| Android | ✅ Full support (AudioManager audio focus) |
| Web | ❌ Not supported (maintains existing behavior) |
Note: The INTERRUPTED state is distinct from PAUSED. PAUSED is user-initiated, while INTERRUPTED is system-initiated. Both states can be resumed using resumeRecording().
On iOS, the AVAudioRecorder.record() method internally calls prepareToRecord(), which overwrites the existing audio file. To preserve audio across interruptions, the plugin implements segmented recording:
- Initial Recording: Creates the base recording file (e.g.,
recording-1234567890.aac) - After Interruption: When resuming from
INTERRUPTEDstate, a new segment file is created (e.g.,recording-1234567891-segment-1.aac) - Multiple Interruptions: Each subsequent interruption creates additional numbered segments
- Merging: When
stopRecording()is called, all segments are automatically merged into a single audio file usingAVMutableCompositionandAVAssetExportSession - Cleanup: Temporary segment files are deleted after successful merge
Note: Regular pause/resume (user-initiated) continues to use a single file without segmentation. Only system interruptions trigger segmented recording.
Format Change: When audio interruptions occur and segments are merged, the final output file will be in M4A format (MIME type: audio/mp4) instead of AAC (MIME type: audio/aac). This is because AVAssetExportSession exports to M4A container format. Recordings without interruptions remain in AAC format.
The plugin will return the recording in one of several possible formats.
The format is dependent on the os / web browser that the user uses.
On android and ios the mime type will be audio/aac, while on chrome and firefox it
will be audio/webm;codecs=opus and on safari it will be audio/mp4.
Note that these three browsers have been tested on.
The plugin should still work on other browsers,
as there is a list of mime types that the plugin checks against the user's browser.
Note that this fact might cause unexpected behavior in case you'll try to play recordings between several devices or browsers—as they do not all support the same set of audio formats. It is recommended to convert the recordings to a format that all your target devices support. As this plugin focuses on the recording aspect, it does not provide any conversion between formats.
To play the recorded file, you can use plain JavaScript:
const base64Sound = '...' // from plugin
const mimeType = '...' // from plugin
const audioRef = new Audio(`data:${mimeType};base64,${base64Sound}`)
audioRef.oncanplaythrough = () => audioRef.play()
audioRef.load()import { Capacitor } from '@capacitor/core'
import { Directory, Filesystem } from '@capacitor/filesystem'
const PATH = '...' // from plugin
/** Generate a URL to the blob file with @capacitor/core and @capacitor/filesystem */
const getBlobURL = async (path: string) => {
const directory = Directory.Data // Same Directory as the one you used with VoiceRecorder.startRecording
if (config.public.platform === 'web') {
const { data } = await Filesystem.readFile({ directory, path })
return URL.createObjectURL(data)
}
const { uri } = await Filesystem.getUri({ directory, path })
return Capacitor.convertFileSrc(uri)
}
/** Read the audio file */
const play = async () => {
const url = await getBlobURL(PATH)
const audioRef = new Audio(url)
audioRef.onended = () => { URL.revokeObjectUrl(url) }
audioRef.play()
}
/** Load the audio file (ie: to send to a Cloud Storage service) */
const load = async () => {
const url = await getBlobURL(PATH)
const response = await fetch(url)
return response.blob()
}Versioning follows Capacitor versioning. Major versions of the plugin are compatible with major versions of Capacitor. You can find each version in its own dedicated branch.
| Plugin Version | Capacitor Version |
|---|---|
| 5.* | 5 |
| 6.* | 6 |
| 7.* | 7 |
If you enjoy my work and find it useful, feel free to invite me to a cup of coffee :)
This project is licensed under the MIT License - see the LICENSE file for details.
Thanks to independo-gmbh for the readme update.

