cordova-plugin-audioinput
Version:
Audio input capture plugin for Cordova and Capacitor - real-time microphone access with streaming and file recording support
277 lines (269 loc) • 9.82 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var core = require('@capacitor/core');
/**
* Audio input plugin for Capacitor
* Provides real-time audio capture with support for streaming and file recording
*/
/**
* Audio source types enum for convenience
*/
exports.AudioSourceType = void 0;
(function (AudioSourceType) {
AudioSourceType[AudioSourceType["DEFAULT"] = 0] = "DEFAULT";
AudioSourceType[AudioSourceType["MIC"] = 1] = "MIC";
AudioSourceType[AudioSourceType["CAMCORDER"] = 5] = "CAMCORDER";
AudioSourceType[AudioSourceType["VOICE_RECOGNITION"] = 6] = "VOICE_RECOGNITION";
AudioSourceType[AudioSourceType["VOICE_COMMUNICATION"] = 7] = "VOICE_COMMUNICATION";
AudioSourceType[AudioSourceType["UNPROCESSED"] = 9] = "UNPROCESSED";
})(exports.AudioSourceType || (exports.AudioSourceType = {}));
/**
* Sample rates enum for convenience
*/
exports.SampleRate = void 0;
(function (SampleRate) {
SampleRate[SampleRate["TELEPHONE_8000Hz"] = 8000] = "TELEPHONE_8000Hz";
SampleRate[SampleRate["CD_QUARTER_11025Hz"] = 11025] = "CD_QUARTER_11025Hz";
SampleRate[SampleRate["VOIP_16000Hz"] = 16000] = "VOIP_16000Hz";
SampleRate[SampleRate["CD_HALF_22050Hz"] = 22050] = "CD_HALF_22050Hz";
SampleRate[SampleRate["MINI_DV_32000Hz"] = 32000] = "MINI_DV_32000Hz";
SampleRate[SampleRate["CD_XA_37800Hz"] = 37800] = "CD_XA_37800Hz";
SampleRate[SampleRate["NTSC_44056Hz"] = 44056] = "NTSC_44056Hz";
SampleRate[SampleRate["CD_AUDIO_44100Hz"] = 44100] = "CD_AUDIO_44100Hz";
SampleRate[SampleRate["DVD_AUDIO_48000Hz"] = 48000] = "DVD_AUDIO_48000Hz";
})(exports.SampleRate || (exports.SampleRate = {}));
/**
* Audio Input Plugin
*
* Provides real-time audio capture from the device microphone.
* Supports both Cordova and Capacitor platforms.
*
* @example
* ```typescript
* import { AudioInput } from 'cordova-plugin-audioinput';
*
* // Initialize
* await AudioInput.initialize({
* sampleRate: 44100,
* bufferSize: 16384,
* channels: 1,
* format: 'PCM_16BIT',
* normalize: true
* });
*
* // Check permission
* const { granted } = await AudioInput.checkMicrophonePermission();
*
* // Request permission if needed
* if (!granted) {
* await AudioInput.getMicrophonePermission();
* }
*
* // Listen for audio data
* AudioInput.addListener('audioData', (event) => {
* console.log('Audio samples:', event.data);
* });
*
* // Start recording
* await AudioInput.start({
* sampleRate: 44100,
* bufferSize: 16384
* });
*
* // Stop recording
* await AudioInput.stop();
* ```
*/
const AudioInput = core.registerPlugin('AudioInput', {
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.AudioInputWeb()),
});
/**
* Web implementation of AudioInput plugin
* Uses Web Audio API for browser-based audio capture
*/
class AudioInputWeb extends core.WebPlugin {
constructor() {
super(...arguments);
this.audioContext = null;
this.mediaStream = null;
this.scriptProcessor = null;
this.micGainNode = null;
this.capturing = false;
this.hasMicrophonePermission = false;
this.options = {};
}
async initialize(options) {
this.options = Object.assign(Object.assign({}, this.options), options);
this.emitStateChange('idle');
return Promise.resolve();
}
async checkMicrophonePermission() {
var _a;
if (this.mediaStream !== null || this.hasMicrophonePermission) {
return { granted: true };
}
if ((_a = navigator.permissions) === null || _a === void 0 ? void 0 : _a.query) {
try {
const status = await navigator.permissions.query({
name: 'microphone',
});
const granted = status.state === 'granted';
if (granted) {
this.hasMicrophonePermission = true;
}
return { granted };
}
catch (_b) {
// Fall through to cached permission state.
}
}
return { granted: this.hasMicrophonePermission };
}
async getMicrophonePermission() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// We got permission, but we'll close this stream for now
// It will be reopened in start()
stream.getTracks().forEach(track => track.stop());
this.hasMicrophonePermission = true;
return { granted: true };
}
catch (error) {
console.error('Microphone permission denied:', error);
return { granted: false };
}
}
async start(options) {
if (this.capturing) {
throw new Error('Already capturing audio');
}
if (options) {
this.options = Object.assign(Object.assign({}, this.options), options);
}
this.warnUnsupportedOptions();
try {
// Request microphone access
this.mediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
autoGainControl: false,
noiseSuppression: false,
},
});
this.hasMicrophonePermission = true;
// Create audio context
const AudioContext = window.AudioContext || window.webkitAudioContext;
this.audioContext = new AudioContext();
// Create nodes
const source = this.audioContext.createMediaStreamSource(this.mediaStream);
this.micGainNode = this.audioContext.createGain();
// Create script processor for audio data
const bufferSize = this.options.bufferSize || 16384;
this.scriptProcessor = this.audioContext.createScriptProcessor(bufferSize, 1, 1);
this.scriptProcessor.onaudioprocess = event => {
if (!this.capturing)
return;
const inputData = event.inputBuffer.getChannelData(0);
const samples = this.processSamples(inputData);
const format = this.options.format || 'PCM_16BIT';
const sampleRate = this.audioContext ? this.audioContext.sampleRate : 0;
this.notifyListeners('audioData', {
data: samples,
sampleRate,
channels: 1,
format,
timestamp: Date.now(),
});
};
// Connect the audio graph
source.connect(this.micGainNode);
this.micGainNode.connect(this.scriptProcessor);
this.scriptProcessor.connect(this.audioContext.destination);
this.capturing = true;
this.emitStateChange('capturing');
}
catch (error) {
this.emitError(error.message || 'Failed to start audio capture');
this.emitStateChange('error', error.message || 'Failed to start audio capture');
throw error;
}
}
async stop() {
this.capturing = false;
if (this.scriptProcessor) {
this.scriptProcessor.disconnect();
this.scriptProcessor = null;
}
if (this.micGainNode) {
this.micGainNode.disconnect();
this.micGainNode = null;
}
if (this.mediaStream) {
this.mediaStream.getTracks().forEach(track => track.stop());
this.mediaStream = null;
}
if (this.audioContext) {
await this.audioContext.close();
this.audioContext = null;
}
this.emitStateChange('stopped');
return {};
}
async isCapturing() {
return { capturing: this.capturing };
}
async getCfg() {
return Object.assign({}, this.options);
}
/**
* Process audio samples according to options
*/
processSamples(inputData) {
const normalize = this.options.normalize !== false;
const normalizationFactor = this.options.normalizationFactor || 32767.0;
if (normalize) {
// Return as Float32Array (already normalized -1 to 1)
return Array.from(inputData);
}
else {
// Convert to Int16Array
const output = new Array(inputData.length);
for (let i = 0; i < inputData.length; i++) {
const sample = Math.max(-1, Math.min(1, inputData[i]));
output[i] = Math.floor(sample * normalizationFactor);
}
return output;
}
}
emitStateChange(state, message) {
this.notifyListeners('stateChange', {
state,
message,
timestamp: Date.now(),
});
}
emitError(message, code) {
this.notifyListeners('audioError', {
message,
code,
});
}
warnUnsupportedOptions() {
if (this.options.fileUrl) {
const message = 'Web implementation does not support fileUrl recording; continuing in stream mode.';
console.warn(message);
this.emitError(message, 'WEB_FILE_RECORDING_UNSUPPORTED');
}
if ((this.options.channels || 1) !== 1) {
const message = 'Web implementation currently captures mono only; requested channels value is ignored.';
console.warn(message);
this.emitError(message, 'WEB_CHANNELS_UNSUPPORTED');
}
}
}
var web = /*#__PURE__*/Object.freeze({
__proto__: null,
AudioInputWeb: AudioInputWeb
});
exports.AudioInput = AudioInput;
//# sourceMappingURL=plugin.cjs.js.map