UNPKG

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
'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