cordova-plugin-audioinput
Version:
Audio input capture plugin for Cordova and Capacitor - real-time microphone access with streaming and file recording support
283 lines (210 loc) • 8.26 kB
Markdown
# cordova-plugin-audioinput
[](https://www.npmjs.com/package/cordova-plugin-audioinput)
[](https://www.npmjs.com/package/cordova-plugin-audioinput)
[](LICENSE)
[](https://github.com/edimuj/cordova-plugin-audioinput)
Real-time microphone capture for **Cordova** and **Capacitor** with a single package.
Use this plugin when you need low-latency PCM chunks in JavaScript (streaming, VAD, waveform analysis, custom DSP) and when `MediaRecorder` is too high-level or too delayed.
## Why This Plugin Exists
Mobile apps often need raw, continuous microphone frames, not just encoded audio blobs.
This plugin gives you:
- low-latency PCM chunk streaming to JS
- file recording support (WAV)
- one package for Cordova + Capacitor
- Android, iOS, and web implementations
## Features
- Real-time PCM streaming (`audioData` / `audioinput` events)
- Cordova native bridge now streams binary PCM payloads (ArrayBuffer) for lower bridge overhead
- Optional normalization (`-1.0 .. 1.0`) for easier JS DSP
- Optional WAV recording via `fileUrl`
- Microphone permission helpers
- TypeScript definitions for Capacitor
- Cordova Web Audio integration (`streamToWebAudio`, `connect`, `disconnect`)
## Platform Support
| Platform | Cordova | Capacitor |
| --- | --- | --- |
| Android | ✅ | ✅ |
| iOS | ✅ | ✅ |
| Browser | ✅ | ✅ |
Notes:
- Capacitor Android build config defaults to `minSdkVersion 24`.
- Capacitor iOS podspec uses deployment target `14.0`.
- Web support is intended for development/lightweight browser use-cases.
## Installation
### Cordova
```bash
cordova plugin add cordova-plugin-audioinput
```
### Capacitor
```bash
npm install cordova-plugin-audioinput
npx cap sync
```
## iOS Permission String
Ensure `NSMicrophoneUsageDescription` exists in your app `Info.plist`.
Example:
```xml
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access to capture audio.</string>
```
## Quick Start (Capacitor)
```ts
import { AudioInput } from 'cordova-plugin-audioinput';
await AudioInput.initialize({
sampleRate: 44100,
bufferSize: 16384,
channels: 1,
format: 'PCM_16BIT',
normalize: true,
});
const permission = await AudioInput.checkMicrophonePermission();
if (!permission.granted) {
const requested = await AudioInput.getMicrophonePermission();
if (!requested.granted) throw new Error('Microphone permission denied');
}
const audioDataHandle = await AudioInput.addListener('audioData', event => {
// event.data is number[]
console.log('samples:', event.data.length);
});
const errorHandle = await AudioInput.addListener('audioError', event => {
console.error('audio error:', event.message);
});
await AudioInput.start();
// ... later
await AudioInput.stop();
await audioDataHandle.remove();
await errorHandle.remove();
```
### Capacitor File Recording
```ts
import { AudioInput } from 'cordova-plugin-audioinput';
await AudioInput.addListener('audioInputFinished', event => {
console.log('WAV file:', event.fileUrl);
});
await AudioInput.start({
sampleRate: 16000,
channels: 1,
format: 'PCM_16BIT',
fileUrl: 'file:///path/to/recording.wav',
});
// stop() resolves when capture stops.
// fileUrl is delivered via audioInputFinished event.
await AudioInput.stop();
```
## Quick Start (Cordova)
```js
function onAudioInput(event) {
console.log('samples:', event.data.length);
}
function onAudioInputError(event) {
console.error('audio error:', event.message);
}
window.addEventListener('audioinput', onAudioInput, false);
window.addEventListener('audioinputerror', onAudioInputError, false);
audioinput.checkMicrophonePermission(function (hasPermission) {
if (hasPermission) {
startCapture();
return;
}
audioinput.getMicrophonePermission(function (granted) {
if (granted) startCapture();
});
});
function startCapture() {
audioinput.start({
sampleRate: 44100,
bufferSize: 16384,
channels: 1,
format: audioinput.FORMAT.PCM_16BIT,
normalize: true,
});
}
function stopCapture() {
audioinput.stop(function (fileUrl) {
if (fileUrl) console.log('Saved file:', fileUrl);
});
}
```
## API (Capacitor)
### Methods
- `initialize(options: AudioInputOptions): Promise<void>`
- `checkMicrophonePermission(): Promise<{ granted: boolean }>`
- `getMicrophonePermission(): Promise<{ granted: boolean }>`
- `start(options?: AudioInputOptions): Promise<void>`
- `stop(): Promise<{ fileUrl?: string }>`
- `isCapturing(): Promise<{ capturing: boolean }>`
- `getCfg(): Promise<AudioInputOptions>`
- `removeAllListeners(): Promise<void>`
### Events
- `audioData` → `{ data: number[], sampleRate?, channels?, format?, timestamp? }`
- `audioError` → `{ message: string, code?: string }`
- `audioInputFinished` → `{ fileUrl: string, timestamp?: number }`
- `stateChange` → `{ state: 'idle' | 'capturing' | 'stopped' | 'error', message?, timestamp? }`
## API (Cordova)
### Methods
- `audioinput.initialize(captureCfg, onComplete)`
- `audioinput.checkMicrophonePermission(callback)`
- `audioinput.getMicrophonePermission(callback)`
- `audioinput.start(captureCfg)`
- `audioinput.stop(onStopped)`
- `audioinput.isCapturing()`
- `audioinput.getCfg()`
- `audioinput.connect(audioNode)`
- `audioinput.disconnect()`
- `audioinput.getAudioContext()`
### Events
- `audioinput` → `{ data, sampleRate?, channels?, format?, timestamp? }`
- `audioinputerror` → `{ message }`
- `audioinputfinished` → `{ file, timestamp? }`
- `audioinputstatechange` → `{ state, message?, timestamp? }`
## Configuration (`AudioInputOptions` / `captureCfg`)
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| `sampleRate` | `number` | `44100` | Common values: `8000`, `16000`, `22050`, `44100`, `48000` |
| `bufferSize` | `number` | `16384` | Power-of-two is recommended |
| `channels` | `1 \| 2` | `1` | Mono or stereo |
| `format` | `'PCM_16BIT' \| 'PCM_8BIT'` | `'PCM_16BIT'` | `PCM_16BIT` recommended |
| `normalize` | `boolean` | `true` | Normalize to float range `-1..1` |
| `normalizationFactor` | `number` | `32767.0` | Used when `normalize=true` |
| `audioSourceType` | `number` | `0` | See source constants |
| `fileUrl` | `string` | `undefined` | Record to WAV file instead of streaming events |
Cordova-only additions:
| Option | Type | Default | Notes |
| --- | --- | --- | --- |
| `streamToWebAudio` | `boolean` | `false` | Pipe captured audio through Web Audio API |
| `audioContext` | `AudioContext` | auto | Provide your own context |
| `concatenateMaxChunks` | `number` | `10` | Queue merge tuning |
## Constants
### Capacitor
```ts
import { SampleRate, AudioSourceType } from 'cordova-plugin-audioinput';
```
### Cordova
```js
audioinput.SAMPLERATE
audioinput.CHANNELS
audioinput.FORMAT
audioinput.AUDIOSOURCE_TYPE
```
## Performance Tips
- Prefer `PCM_16BIT` unless you have a hard requirement for `PCM_8BIT`.
- Start with mono (`channels: 1`) and `sampleRate: 16000` for speech workloads.
- Use larger `bufferSize` for lower CPU usage and smaller `bufferSize` for lower latency.
- If you only need files, set `fileUrl` and skip streaming processing.
- Run `npm run bench:js` for a quick synthetic JS hot-path benchmark.
## Known Limitations
- Device support for sample-rate/channel combinations varies.
- Bluetooth microphone routing behavior varies by OS/device.
- `streamToWebAudio` is a Cordova API surface (via `window.audioinput`).
- Web implementation does not persist `fileUrl` recordings (it emits an `audioError` warning and continues streaming).
## Demo / Test Apps
- [app-audioinput-demo](https://github.com/edimuj/app-audioinput-demo)
- Local harnesses in [`test-apps`](test-apps):
- `test-apps/cordova-test-app`
- `test-apps/capacitor-test-app`
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## Contributing
PRs are welcome. Please keep backward compatibility for existing Cordova integrations.
## License
MIT — see [LICENSE](LICENSE).