UNPKG

@varunmhajan/hom-i-voice-ai

Version:

Voice AI utilities for home loan assistance with India-specific formatting

1,555 lines 55.6 kB
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
	typeof define === 'function' && define.amd ? define(['exports'], factory) :
	(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.HomiVoiceAI = {}));
})(this, (function (exports) { 'use strict';

	function getDefaultExportFromCjs (x) {
		return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
	}

	var eventemitter3 = {exports: {}};

	(function (module) {

		var has = Object.prototype.hasOwnProperty
		  , prefix = '~';

		/**
		 * Constructor to create a storage for our `EE` objects.
		 * An `Events` instance is a plain object whose properties are event names.
		 *
		 * @constructor
		 * @private
		 */
		function Events() {}

		//
		// We try to not inherit from `Object.prototype`. In some engines creating an
		// instance in this way is faster than calling `Object.create(null)` directly.
		// If `Object.create(null)` is not supported we prefix the event names with a
		// character to make sure that the built-in object properties are not
		// overridden or used as an attack vector.
		//
		if (Object.create) {
		  Events.prototype = Object.create(null);

		  //
		  // This hack is needed because the `__proto__` property is still inherited in
		  // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
		  //
		  if (!new Events().__proto__) prefix = false;
		}

		/**
		 * Representation of a single event listener.
		 *
		 * @param {Function} fn The listener function.
		 * @param {*} context The context to invoke the listener with.
		 * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
		 * @constructor
		 * @private
		 */
		function EE(fn, context, once) {
		  this.fn = fn;
		  this.context = context;
		  this.once = once || false;
		}

		/**
		 * Add a listener for a given event.
		 *
		 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
		 * @param {(String|Symbol)} event The event name.
		 * @param {Function} fn The listener function.
		 * @param {*} context The context to invoke the listener with.
		 * @param {Boolean} once Specify if the listener is a one-time listener.
		 * @returns {EventEmitter}
		 * @private
		 */
		function addListener(emitter, event, fn, context, once) {
		  if (typeof fn !== 'function') {
		    throw new TypeError('The listener must be a function');
		  }

		  var listener = new EE(fn, context || emitter, once)
		    , evt = prefix ? prefix + event : event;

		  if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
		  else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
		  else emitter._events[evt] = [emitter._events[evt], listener];

		  return emitter;
		}

		/**
		 * Clear event by name.
		 *
		 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
		 * @param {(String|Symbol)} evt The Event name.
		 * @private
		 */
		function clearEvent(emitter, evt) {
		  if (--emitter._eventsCount === 0) emitter._events = new Events();
		  else delete emitter._events[evt];
		}

		/**
		 * Minimal `EventEmitter` interface that is molded against the Node.js
		 * `EventEmitter` interface.
		 *
		 * @constructor
		 * @public
		 */
		function EventEmitter() {
		  this._events = new Events();
		  this._eventsCount = 0;
		}

		/**
		 * Return an array listing the events for which the emitter has registered
		 * listeners.
		 *
		 * @returns {Array}
		 * @public
		 */
		EventEmitter.prototype.eventNames = function eventNames() {
		  var names = []
		    , events
		    , name;

		  if (this._eventsCount === 0) return names;

		  for (name in (events = this._events)) {
		    if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
		  }

		  if (Object.getOwnPropertySymbols) {
		    return names.concat(Object.getOwnPropertySymbols(events));
		  }

		  return names;
		};

		/**
		 * Return the listeners registered for a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @returns {Array} The registered listeners.
		 * @public
		 */
		EventEmitter.prototype.listeners = function listeners(event) {
		  var evt = prefix ? prefix + event : event
		    , handlers = this._events[evt];

		  if (!handlers) return [];
		  if (handlers.fn) return [handlers.fn];

		  for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
		    ee[i] = handlers[i].fn;
		  }

		  return ee;
		};

		/**
		 * Return the number of listeners listening to a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @returns {Number} The number of listeners.
		 * @public
		 */
		EventEmitter.prototype.listenerCount = function listenerCount(event) {
		  var evt = prefix ? prefix + event : event
		    , listeners = this._events[evt];

		  if (!listeners) return 0;
		  if (listeners.fn) return 1;
		  return listeners.length;
		};

		/**
		 * Calls each of the listeners registered for a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @returns {Boolean} `true` if the event had listeners, else `false`.
		 * @public
		 */
		EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
		  var evt = prefix ? prefix + event : event;

		  if (!this._events[evt]) return false;

		  var listeners = this._events[evt]
		    , len = arguments.length
		    , args
		    , i;

		  if (listeners.fn) {
		    if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);

		    switch (len) {
		      case 1: return listeners.fn.call(listeners.context), true;
		      case 2: return listeners.fn.call(listeners.context, a1), true;
		      case 3: return listeners.fn.call(listeners.context, a1, a2), true;
		      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
		      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
		      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
		    }

		    for (i = 1, args = new Array(len -1); i < len; i++) {
		      args[i - 1] = arguments[i];
		    }

		    listeners.fn.apply(listeners.context, args);
		  } else {
		    var length = listeners.length
		      , j;

		    for (i = 0; i < length; i++) {
		      if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);

		      switch (len) {
		        case 1: listeners[i].fn.call(listeners[i].context); break;
		        case 2: listeners[i].fn.call(listeners[i].context, a1); break;
		        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
		        case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
		        default:
		          if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
		            args[j - 1] = arguments[j];
		          }

		          listeners[i].fn.apply(listeners[i].context, args);
		      }
		    }
		  }

		  return true;
		};

		/**
		 * Add a listener for a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @param {Function} fn The listener function.
		 * @param {*} [context=this] The context to invoke the listener with.
		 * @returns {EventEmitter} `this`.
		 * @public
		 */
		EventEmitter.prototype.on = function on(event, fn, context) {
		  return addListener(this, event, fn, context, false);
		};

		/**
		 * Add a one-time listener for a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @param {Function} fn The listener function.
		 * @param {*} [context=this] The context to invoke the listener with.
		 * @returns {EventEmitter} `this`.
		 * @public
		 */
		EventEmitter.prototype.once = function once(event, fn, context) {
		  return addListener(this, event, fn, context, true);
		};

		/**
		 * Remove the listeners of a given event.
		 *
		 * @param {(String|Symbol)} event The event name.
		 * @param {Function} fn Only remove the listeners that match this function.
		 * @param {*} context Only remove the listeners that have this context.
		 * @param {Boolean} once Only remove one-time listeners.
		 * @returns {EventEmitter} `this`.
		 * @public
		 */
		EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
		  var evt = prefix ? prefix + event : event;

		  if (!this._events[evt]) return this;
		  if (!fn) {
		    clearEvent(this, evt);
		    return this;
		  }

		  var listeners = this._events[evt];

		  if (listeners.fn) {
		    if (
		      listeners.fn === fn &&
		      (!once || listeners.once) &&
		      (!context || listeners.context === context)
		    ) {
		      clearEvent(this, evt);
		    }
		  } else {
		    for (var i = 0, events = [], length = listeners.length; i < length; i++) {
		      if (
		        listeners[i].fn !== fn ||
		        (once && !listeners[i].once) ||
		        (context && listeners[i].context !== context)
		      ) {
		        events.push(listeners[i]);
		      }
		    }

		    //
		    // Reset the array, or remove it completely if we have no more listeners.
		    //
		    if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
		    else clearEvent(this, evt);
		  }

		  return this;
		};

		/**
		 * Remove all listeners, or those of the specified event.
		 *
		 * @param {(String|Symbol)} [event] The event name.
		 * @returns {EventEmitter} `this`.
		 * @public
		 */
		EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
		  var evt;

		  if (event) {
		    evt = prefix ? prefix + event : event;
		    if (this._events[evt]) clearEvent(this, evt);
		  } else {
		    this._events = new Events();
		    this._eventsCount = 0;
		  }

		  return this;
		};

		//
		// Alias methods names because people roll like that.
		//
		EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
		EventEmitter.prototype.addListener = EventEmitter.prototype.on;

		//
		// Expose the prefix.
		//
		EventEmitter.prefixed = prefix;

		//
		// Allow `EventEmitter` to be imported as module namespace.
		//
		EventEmitter.EventEmitter = EventEmitter;

		//
		// Expose the module.
		//
		{
		  module.exports = EventEmitter;
		} 
	} (eventemitter3));

	var eventemitter3Exports = eventemitter3.exports;
	var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);

	/**
	 * Ultra-fast Voice AI Client with minimal latency optimizations
	 */
	class VoiceClient extends EventEmitter {
	    constructor(config) {
	        super();
	        this.conversationHistory = [];
	        this.audioCache = new Map();
	        this.isProcessing = false;
	        this.requestQueue = [];
	        this.processingQueue = false;
	        // Performance tracking
	        this.performanceMetrics = {
	            averageLatency: 0,
	            requestCount: 0,
	            cacheHitRate: 0,
	            errorRate: 0
	        };
	        // Enhanced API key validation
	        if (!config.apiKey) {
	            throw new Error('API key is required for @hom-i/voice-ai package');
	        }
	        if (typeof config.apiKey !== 'string' || config.apiKey.trim().length === 0) {
	            throw new Error('API key must be a valid non-empty string');
	        }
	        // Basic format validation - API keys should be at least 16 characters
	        if (config.apiKey.length < 16) {
	            throw new Error('Invalid API key format. Please ensure you have a valid HOM-i Voice AI API key');
	        }
	        // Check for placeholder/example keys
	        const invalidKeys = ['your-api-key', 'api-key', 'test-key', 'demo-key', 'placeholder'];
	        if (invalidKeys.some(invalid => config.apiKey.toLowerCase().includes(invalid))) {
	            throw new Error('Please replace the placeholder API key with your actual HOM-i Voice AI API key');
	        }
	        this.config = {
	            apiKey: config.apiKey,
	            baseUrl: config.baseUrl || 'https://chat.orbit.basichomeloan.com',
	            language: config.language || 'en',
	            voiceId: config.voiceId || '2bNrEsM0omyhLiEyOwqY', // Updated to Monika voice
	            audioFormat: config.audioFormat || 'mp3',
	            priority: config.priority || 'speed',
	            mode: config.mode || 'full-voice',
	            enableSTT: config.enableSTT ?? true,
	            enableTTS: config.enableTTS ?? true,
	            autoPlay: config.autoPlay ?? true,
	            enableCompression: config.enableCompression ?? true,
	            enableCaching: config.enableCaching ?? true,
	            chunkSize: config.chunkSize || 4096,
	            maxCacheSize: config.maxCacheSize || 50,
	            timeout: config.timeout || 10000,
	            retryAttempts: config.retryAttempts || 2,
	            ultraFastMode: config.ultraFastMode ?? true,
	            preloadVoices: config.preloadVoices ?? true,
	            enableWebWorkers: config.enableWebWorkers ?? false, // Disabled by default for compatibility
	        };
	        this.initializeOptimizations();
	    }
	    async initializeOptimizations() {
	        // Verify API key with backend
	        await this.verifyApiKey();
	        // Preload voice models if enabled
	        if (this.config.preloadVoices && this.config.ultraFastMode) {
	            this.preloadVoiceModels();
	        }
	        // Initialize Web Workers if enabled
	        if (this.config.enableWebWorkers && typeof Worker !== 'undefined') {
	            this.initializeWebWorkers();
	        }
	        this.emit('ready');
	    }
	    async verifyApiKey() {
	        try {
	            const response = await fetch(`${this.config.baseUrl}/api/voice/verify-key`, {
	                method: 'POST',
	                headers: { 'Content-Type': 'application/json' },
	                body: JSON.stringify({ apiKey: this.config.apiKey }),
	                signal: AbortSignal.timeout(5000) // 5 second timeout
	            });
	            if (!response.ok) {
	                if (response.status === 401 || response.status === 403) {
	                    throw new Error('Invalid API key. Please check your HOM-i Voice AI API credentials');
	                }
	                throw new Error(`API key verification failed: ${response.statusText}`);
	            }
	            const data = await response.json();
	            if (!data.success) {
	                throw new Error(data.error || 'API key verification failed');
	            }
	            this.emit('apiKeyVerified', { valid: true, key: this.config.apiKey.slice(0, 4) + '...' });
	        }
	        catch (error) {
	            // If verification endpoint doesn't exist, log warning but continue
	            if (error instanceof Error && error.message.includes('404')) {
	                console.warn('@hom-i/voice-ai: API key verification endpoint not available, proceeding without verification');
	                return;
	            }
	            // For other errors, throw to prevent initialization
	            this.emit('apiKeyVerified', { valid: false, error: error instanceof Error ? error.message : 'Unknown error' });
	            throw new Error(`API key verification failed: ${error instanceof Error ? error.message : 'Please check your API key'}`);
	        }
	    }
	    async preloadVoiceModels() {
	        try {
	            // Pre-warm the voice API with a minimal request
	            await this.textToSpeech('', {
	                autoPlay: false,
	                enableCaching: false
	            });
	        }
	        catch {
	            // Ignore preload errors
	        }
	    }
	    initializeWebWorkers() {
	        // TODO: Implement Web Worker for audio processing
	        // This would move audio encoding/decoding off the main thread
	    }
	    /**
	     * Send voice message with ultra-fast processing
	     */
	    async sendVoiceMessage(input, options = {}) {
	        const startTime = performance.now();
	        if (this.isProcessing && this.config.ultraFastMode) {
	            // In ultra-fast mode, cancel previous request
	            this.abortController?.abort();
	        }
	        this.abortController = new AbortController();
	        this.isProcessing = true;
	        this.emit('processingStarted', { type: 'voice_message' });
	        try {
	            const mergedConfig = { ...this.config, ...options };
	            // Check cache first for text inputs
	            if (typeof input === 'string' && this.config.enableCaching) {
	                const cachedResponse = this.getCachedResponse(input, mergedConfig);
	                if (cachedResponse) {
	                    this.updatePerformanceMetrics(startTime, true);
	                    this.emit('messageReceived', cachedResponse);
	                    return cachedResponse;
	                }
	            }
	            const requestBody = await this.prepareRequestBody(input, mergedConfig);
	            const response = await this.makeRequest('/api/voice/simple-voice', requestBody);
	            if (response.success) {
	                this.conversationHistory = response.conversationHistory || [];
	                // Cache the response
	                if (typeof input === 'string' && this.config.enableCaching) {
	                    this.cacheResponse(input, mergedConfig, response);
	                }
	                // Auto-play if enabled
	                if (response.audio && mergedConfig.autoPlay) {
	                    this.playAudio(response.audio);
	                }
	                this.updatePerformanceMetrics(startTime, false);
	                this.emit('messageReceived', response);
	                return response;
	            }
	            else {
	                throw new Error(response.error || 'API request failed');
	            }
	        }
	        catch (error) {
	            this.handleError(error);
	            throw error;
	        }
	        finally {
	            this.isProcessing = false;
	            this.emit('processingEnded', { type: 'voice_message' });
	        }
	    }
	    /**
	     * Send text message optimized for speed
	     */
	    async sendTextMessage(text, options = {}) {
	        return this.sendVoiceMessage(text, { ...options, enableSTT: false });
	    }
	    /**
	     * Transcribe audio only (no TTS response)
	     */
	    async transcribeAudio(audioInput, options = {}) {
	        return this.sendVoiceMessage(audioInput, {
	            ...options,
	            enableTTS: false,
	            mode: 'stt-only'
	        });
	    }
	    /**
	     * Convert text to speech with caching
	     */
	    async textToSpeech(text, options = {}) {
	        const mergedConfig = { ...this.config, ...options };
	        const cacheKey = this.generateCacheKey(text, mergedConfig);
	        // Check cache first
	        if (this.config.enableCaching && this.audioCache.has(cacheKey)) {
	            const cachedAudio = this.audioCache.get(cacheKey);
	            const response = {
	                success: true,
	                audio: cachedAudio,
	                metadata: {
	                    processingTime: 0,
	                    endpoint: 'cache',
	                    cached: true
	                }
	            };
	            if (mergedConfig.autoPlay) {
	                this.playAudio(cachedAudio);
	            }
	            this.emit('audioFromCache', { text, audio: cachedAudio });
	            return response;
	        }
	        const response = await this.sendVoiceMessage(text, {
	            ...options,
	            mode: 'tts-only'
	        });
	        // Cache the result
	        if (response.audio && this.config.enableCaching) {
	            this.audioCache.set(cacheKey, response.audio);
	            this.manageCacheSize();
	        }
	        return response;
	    }
	    /**
	     * Play audio with optimized loading
	     */
	    async playAudio(audioData) {
	        return new Promise((resolve, reject) => {
	            try {
	                const audioSrc = audioData.startsWith('data:')
	                    ? audioData
	                    : `data:audio/${this.config.audioFormat};base64,${audioData}`;
	                const audio = new Audio(audioSrc);
	                // Optimize audio loading
	                audio.preload = 'auto';
	                audio.crossOrigin = 'anonymous';
	                audio.onloadstart = () => this.emit('audioLoadStart', audioData);
	                audio.oncanplay = () => this.emit('audioCanPlay', audioData);
	                audio.onplay = () => this.emit('audioPlayStart', audioData);
	                audio.onended = () => {
	                    this.emit('audioPlayEnd', audioData);
	                    resolve();
	                };
	                audio.onerror = (error) => {
	                    this.emit('audioError', error);
	                    reject(error);
	                };
	                audio.play().catch(reject);
	            }
	            catch (error) {
	                this.emit('audioError', error);
	                reject(error);
	            }
	        });
	    }
	    // Request preparation and optimization methods
	    async prepareRequestBody(input, config) {
	        const requestBody = {
	            apiKey: config.apiKey,
	            voiceConfig: {
	                language: config.language,
	                voiceId: config.voiceId,
	                audioFormat: config.audioFormat,
	                priority: config.priority,
	                mode: config.mode,
	                enableSTT: config.enableSTT,
	                enableTTS: config.enableTTS,
	            },
	            conversationHistory: this.conversationHistory
	        };
	        // Handle different input types
	        if (input instanceof Blob || input instanceof File) {
	            const base64Audio = await this.blobToBase64(input);
	            requestBody.audioData = {
	                audio: base64Audio,
	                mimeType: input.type
	            };
	        }
	        else if (input?.audio && input?.mimeType) {
	            requestBody.audioData = input;
	        }
	        else if (typeof input === 'string') {
	            requestBody.message = input;
	        }
	        return requestBody;
	    }
	    async makeRequest(endpoint, body) {
	        const url = `${this.config.baseUrl}${endpoint}`;
	        const response = await fetch(url, {
	            method: 'POST',
	            headers: { 'Content-Type': 'application/json' },
	            body: JSON.stringify(body),
	            signal: this.abortController?.signal,
	            // Optimization headers
	            keepalive: true,
	        });
	        if (!response.ok) {
	            throw new Error(`HTTP ${response.status}: ${response.statusText}`);
	        }
	        return response.json();
	    }
	    // Caching and performance methods
	    generateCacheKey(text, config) {
	        return `${text}_${config.voiceId}_${config.language}_${config.audioFormat}`;
	    }
	    getCachedResponse(text, config) {
	        const cacheKey = this.generateCacheKey(text, config);
	        const cachedAudio = this.audioCache.get(cacheKey);
	        if (cachedAudio) {
	            return {
	                success: true,
	                message: text,
	                audio: cachedAudio,
	                metadata: {
	                    processingTime: 0,
	                    endpoint: 'cache',
	                    cached: true
	                }
	            };
	        }
	        return null;
	    }
	    cacheResponse(text, config, response) {
	        if (response.audio) {
	            const cacheKey = this.generateCacheKey(text, config);
	            this.audioCache.set(cacheKey, response.audio);
	            this.manageCacheSize();
	        }
	    }
	    manageCacheSize() {
	        if (this.audioCache.size > this.config.maxCacheSize) {
	            // Remove oldest entries (simple LRU)
	            const entriesToRemove = this.audioCache.size - this.config.maxCacheSize;
	            const entries = Array.from(this.audioCache.entries());
	            for (let i = 0; i < entriesToRemove; i++) {
	                this.audioCache.delete(entries[i][0]);
	            }
	        }
	    }
	    updatePerformanceMetrics(startTime, fromCache) {
	        const processingTime = performance.now() - startTime;
	        this.performanceMetrics.requestCount++;
	        if (fromCache) {
	            this.performanceMetrics.cacheHitRate =
	                (this.performanceMetrics.cacheHitRate * (this.performanceMetrics.requestCount - 1) + 1) /
	                    this.performanceMetrics.requestCount;
	        }
	        else {
	            this.performanceMetrics.averageLatency =
	                (this.performanceMetrics.averageLatency * (this.performanceMetrics.requestCount - 1) + processingTime) /
	                    this.performanceMetrics.requestCount;
	        }
	    }
	    handleError(error) {
	        this.performanceMetrics.errorRate =
	            (this.performanceMetrics.errorRate * this.performanceMetrics.requestCount + 1) /
	                (this.performanceMetrics.requestCount + 1);
	        this.emit('error', error);
	    }
	    // Utility methods
	    async blobToBase64(blob) {
	        return new Promise((resolve) => {
	            const reader = new FileReader();
	            reader.onloadend = () => {
	                const base64 = reader.result.split(',')[1];
	                resolve(base64);
	            };
	            reader.readAsDataURL(blob);
	        });
	    }
	    // Public API methods
	    getConversationHistory() {
	        return [...this.conversationHistory];
	    }
	    clearConversationHistory() {
	        this.conversationHistory = [];
	        this.emit('conversationCleared');
	    }
	    clearCache() {
	        this.audioCache.clear();
	        this.emit('cacheCleared');
	    }
	    getPerformanceMetrics() {
	        return { ...this.performanceMetrics };
	    }
	    getStatus() {
	        return {
	            isProcessing: this.isProcessing,
	            cacheSize: this.audioCache.size,
	            conversationLength: this.conversationHistory.length,
	            config: { ...this.config },
	            performance: this.getPerformanceMetrics()
	        };
	    }
	    updateConfig(updates) {
	        this.config = { ...this.config, ...updates };
	        this.emit('configUpdated', this.config);
	    }
	    destroy() {
	        this.abortController?.abort();
	        this.clearCache();
	        this.removeAllListeners();
	    }
	}

	/**
	 * Ultra-fast Voice Recorder with advanced VAD and low-latency processing
	 */
	class VoiceRecorder extends EventEmitter {
	    constructor(config = {}) {
	        super();
	        this.mediaRecorder = null;
	        this.mediaStream = null;
	        this.audioContext = null;
	        this.analyser = null;
	        this.scriptProcessor = null;
	        this.recordingState = {
	            isRecording: false,
	            isProcessing: false,
	            voiceActivityDetected: false,
	            audioLevel: 0,
	            recordingDuration: 0,
	            error: null
	        };
	        this.audioChunks = [];
	        this.recordingStartTime = 0;
	        this.silenceTimer = null;
	        this.vadTimer = null;
	        this.animationFrame = null;
	        // Performance monitoring
	        this.performanceMetrics = {
	            totalRecordings: 0,
	            averageRecordingDuration: 0,
	            vadAccuracy: 0,
	            processingLatency: 0
	        };
	        this.config = {
	            // Audio settings
	            sampleRate: config.sampleRate || 44100,
	            channels: config.channels || 1,
	            audioBitsPerSecond: config.audioBitsPerSecond || 128000,
	            mimeType: config.mimeType || 'audio/webm;codecs=opus',
	            // Voice Activity Detection
	            enableVAD: config.enableVAD ?? true,
	            vadThreshold: config.vadThreshold || 0.01,
	            silenceTimeout: config.silenceTimeout || 1500,
	            minRecordingTime: config.minRecordingTime || 500,
	            maxRecordingTime: config.maxRecordingTime || 30000,
	            // Performance optimizations
	            chunkDuration: config.chunkDuration || 100,
	            enableNoiseSuppression: config.enableNoiseSuppression ?? true,
	            enableEchoCancellation: config.enableEchoCancellation ?? true,
	            enableAutoGainControl: config.enableAutoGainControl ?? true,
	            // Ultra-fast mode
	            ultraFastMode: config.ultraFastMode ?? true,
	            enableRealTimeProcessing: config.enableRealTimeProcessing ?? false,
	            bufferSize: config.bufferSize || 4096,
	        };
	        this.initializeAudioContext();
	    }
	    async initializeAudioContext() {
	        try {
	            // Create optimized audio context
	            const AudioContextClass = window.AudioContext || window.webkitAudioContext;
	            this.audioContext = new AudioContextClass({
	                sampleRate: this.config.sampleRate,
	                latencyHint: this.config.ultraFastMode ? 'interactive' : 'balanced',
	            });
	            // Resume if suspended
	            if (this.audioContext.state === 'suspended') {
	                await this.audioContext.resume();
	            }
	            this.emit('initialized');
	        }
	        catch (error) {
	            this.handleError('Failed to initialize audio context', error);
	        }
	    }
	    /**
	     * Start recording with optimized settings
	     */
	    async startRecording() {
	        if (this.recordingState.isRecording) {
	            throw new Error('Already recording');
	        }
	        const startTime = performance.now();
	        try {
	            // Get optimized media stream
	            this.mediaStream = await navigator.mediaDevices.getUserMedia({
	                audio: {
	                    sampleRate: this.config.sampleRate,
	                    channelCount: this.config.channels,
	                    echoCancellation: this.config.enableEchoCancellation,
	                    noiseSuppression: this.config.enableNoiseSuppression,
	                    autoGainControl: this.config.enableAutoGainControl,
	                    // Ultra-fast mode optimizations
	                    ...(this.config.ultraFastMode && {
	                        latency: 0.01, // 10ms latency
	                        sampleSize: 16,
	                    })
	                }
	            });
	            // Setup audio analysis
	            await this.setupAudioAnalysis();
	            // Setup media recorder with optimized settings
	            this.setupMediaRecorder();
	            // Start recording
	            this.audioChunks = [];
	            this.recordingStartTime = Date.now();
	            this.mediaRecorder.start(this.config.chunkDuration);
	            // Update state
	            this.recordingState = {
	                ...this.recordingState,
	                isRecording: true,
	                error: null,
	                recordingDuration: 0
	            };
	            // Start VAD if enabled
	            if (this.config.enableVAD) {
	                this.startVoiceActivityDetection();
	            }
	            // Start duration tracking
	            this.startDurationTracking();
	            // Track performance
	            const initLatency = performance.now() - startTime;
	            this.updatePerformanceMetrics('initLatency', initLatency);
	            this.emit('recordingStarted', {
	                timestamp: this.recordingStartTime,
	                config: this.config
	            });
	        }
	        catch (error) {
	            this.handleError('Failed to start recording', error);
	            throw error;
	        }
	    }
	    /**
	     * Stop recording and return audio data
	     */
	    async stopRecording() {
	        if (!this.recordingState.isRecording) {
	            throw new Error('Not currently recording');
	        }
	        const stopTime = performance.now();
	        return new Promise((resolve, reject) => {
	            if (!this.mediaRecorder) {
	                reject(new Error('Media recorder not initialized'));
	                return;
	            }
	            // Setup completion handler
	            const handleStop = () => {
	                const recordingDuration = Date.now() - this.recordingStartTime;
	                // Combine all audio chunks
	                const audioBlob = new Blob(this.audioChunks, {
	                    type: this.config.mimeType
	                });
	                // Update performance metrics
	                this.performanceMetrics.totalRecordings++;
	                this.performanceMetrics.averageRecordingDuration =
	                    (this.performanceMetrics.averageRecordingDuration * (this.performanceMetrics.totalRecordings - 1) + recordingDuration) /
	                        this.performanceMetrics.totalRecordings;
	                // Cleanup
	                this.cleanup();
	                // Update state
	                this.recordingState = {
	                    ...this.recordingState,
	                    isRecording: false,
	                    recordingDuration
	                };
	                const processingTime = performance.now() - stopTime;
	                this.emit('recordingStopped', {
	                    audioBlob,
	                    duration: recordingDuration,
	                    processingTime,
	                    chunks: this.audioChunks.length
	                });
	                resolve(audioBlob);
	            };
	            // Handle data available
	            this.mediaRecorder.ondataavailable = (event) => {
	                if (event.data.size > 0) {
	                    this.audioChunks.push(event.data);
	                    // Emit chunk for real-time processing if enabled
	                    if (this.config.enableRealTimeProcessing) {
	                        this.emit('audioChunk', {
	                            data: event.data,
	                            timestamp: Date.now(),
	                            duration: this.config.chunkDuration,
	                            isLast: false
	                        });
	                    }
	                }
	            };
	            this.mediaRecorder.onstop = handleStop;
	            this.mediaRecorder.onerror = (event) => {
	                this.handleError('Media recorder error', event);
	                reject(new Error('Recording failed'));
	            };
	            // Stop recording
	            this.mediaRecorder.stop();
	            // Stop timers
	            this.stopVoiceActivityDetection();
	            this.stopDurationTracking();
	        });
	    }
	    /**
	     * Setup audio analysis for VAD and level monitoring
	     */
	    async setupAudioAnalysis() {
	        if (!this.audioContext || !this.mediaStream) {
	            throw new Error('Audio context or media stream not available');
	        }
	        try {
	            // Create analyser for real-time audio analysis
	            this.analyser = this.audioContext.createAnalyser();
	            this.analyser.fftSize = this.config.bufferSize;
	            this.analyser.smoothingTimeConstant = 0.1; // Faster response
	            this.analyser.minDecibels = -100;
	            this.analyser.maxDecibels = -10;
	            // Connect media stream to analyser
	            const source = this.audioContext.createMediaStreamSource(this.mediaStream);
	            source.connect(this.analyser);
	            // Setup script processor for real-time processing (if enabled)
	            if (this.config.enableRealTimeProcessing) {
	                this.scriptProcessor = this.audioContext.createScriptProcessor(this.config.bufferSize, this.config.channels, this.config.channels);
	                this.scriptProcessor.onaudioprocess = (event) => {
	                    const inputBuffer = event.inputBuffer.getChannelData(0);
	                    this.processAudioBuffer(inputBuffer);
	                };
	                source.connect(this.scriptProcessor);
	                this.scriptProcessor.connect(this.audioContext.destination);
	            }
	        }
	        catch (error) {
	            throw new Error(`Failed to setup audio analysis: ${error}`);
	        }
	    }
	    /**
	     * Setup optimized media recorder
	     */
	    setupMediaRecorder() {
	        if (!this.mediaStream) {
	            throw new Error('Media stream not available');
	        }
	        try {
	            // Find the best supported MIME type
	            const mimeType = this.findBestMimeType();
	            this.mediaRecorder = new MediaRecorder(this.mediaStream, {
	                mimeType,
	                audioBitsPerSecond: this.config.audioBitsPerSecond,
	            });
	            // Update config with actual MIME type
	            this.config.mimeType = mimeType;
	        }
	        catch (error) {
	            throw new Error(`Failed to create media recorder: ${error}`);
	        }
	    }
	    /**
	     * Find the best supported MIME type for recording
	     */
	    findBestMimeType() {
	        const mimeTypes = [
	            'audio/webm;codecs=opus',
	            'audio/webm',
	            'audio/mp4',
	            'audio/ogg;codecs=opus',
	            'audio/ogg',
	            'audio/wav'
	        ];
	        for (const mimeType of mimeTypes) {
	            if (MediaRecorder.isTypeSupported(mimeType)) {
	                return mimeType;
	            }
	        }
	        // Fallback to any supported type
	        return this.config.mimeType;
	    }
	    /**
	     * Voice Activity Detection with optimized algorithms
	     */
	    startVoiceActivityDetection() {
	        if (!this.analyser)
	            return;
	        const detectVoiceActivity = () => {
	            if (!this.analyser || !this.recordingState.isRecording)
	                return;
	            const dataArray = new Uint8Array(this.analyser.frequencyBinCount);
	            this.analyser.getByteFrequencyData(dataArray);
	            // Calculate RMS (Root Mean Square) for better VAD
	            const sum = dataArray.reduce((acc, value) => acc + value * value, 0);
	            const rms = Math.sqrt(sum / dataArray.length);
	            const normalizedLevel = rms / 255;
	            // Update audio level
	            this.recordingState.audioLevel = normalizedLevel;
	            // Voice activity detection
	            const isVoiceDetected = normalizedLevel > this.config.vadThreshold;
	            const previousVAD = this.recordingState.voiceActivityDetected;
	            this.recordingState.voiceActivityDetected = isVoiceDetected;
	            // Emit level updates
	            this.emit('audioLevel', {
	                level: normalizedLevel,
	                isVoiceDetected,
	                timestamp: Date.now()
	            });
	            // Handle voice activity changes
	            if (isVoiceDetected && !previousVAD) {
	                this.emit('voiceStart', { timestamp: Date.now() });
	                this.clearSilenceTimer();
	            }
	            else if (!isVoiceDetected && previousVAD) {
	                this.emit('voiceEnd', { timestamp: Date.now() });
	                this.startSilenceTimer();
	            }
	            // Continue monitoring
	            this.animationFrame = requestAnimationFrame(detectVoiceActivity);
	        };
	        detectVoiceActivity();
	    }
	    /**
	     * Stop voice activity detection
	     */
	    stopVoiceActivityDetection() {
	        if (this.animationFrame) {
	            cancelAnimationFrame(this.animationFrame);
	            this.animationFrame = null;
	        }
	        this.clearSilenceTimer();
	    }
	    /**
	     * Start silence timer for auto-stop
	     */
	    startSilenceTimer() {
	        this.clearSilenceTimer();
	        this.silenceTimer = setTimeout(async () => {
	            const recordingDuration = Date.now() - this.recordingStartTime;
	            // Only auto-stop if recording is long enough
	            if (recordingDuration >= this.config.minRecordingTime) {
	                this.emit('silenceDetected', {
	                    duration: recordingDuration,
	                    autoStopping: true
	                });
	                try {
	                    await this.stopRecording();
	                }
	                catch (error) {
	                    this.handleError('Auto-stop failed', error);
	                }
	            }
	        }, this.config.silenceTimeout);
	    }
	    /**
	     * Clear silence timer
	     */
	    clearSilenceTimer() {
	        if (this.silenceTimer) {
	            clearTimeout(this.silenceTimer);
	            this.silenceTimer = null;
	        }
	    }
	    /**
	     * Start duration tracking with auto-stop
	     */
	    startDurationTracking() {
	        const updateDuration = () => {
	            if (!this.recordingState.isRecording)
	                return;
	            const duration = Date.now() - this.recordingStartTime;
	            this.recordingState.recordingDuration = duration;
	            // Auto-stop if max duration reached
	            if (duration >= this.config.maxRecordingTime) {
	                this.emit('maxDurationReached', { duration });
	                this.stopRecording().catch(error => {
	                    this.handleError('Auto-stop on max duration failed', error);
	                });
	                return;
	            }
	            // Continue tracking
	            setTimeout(updateDuration, 100);
	        };
	        updateDuration();
	    }
	    /**
	     * Stop duration tracking
	     */
	    stopDurationTracking() {
	        // Duration tracking stops automatically when recording stops
	    }
	    /**
	     * Process audio buffer for real-time analysis
	     */
	    processAudioBuffer(buffer) {
	        // Calculate additional metrics
	        const peak = Math.max(...buffer.map(Math.abs));
	        const energy = buffer.reduce((sum, sample) => sum + sample * sample, 0) / buffer.length;
	        this.emit('audioBuffer', {
	            buffer: buffer.slice(), // Copy for safety
	            peak,
	            energy,
	            timestamp: Date.now()
	        });
	    }
	    /**
	     * Update performance metrics
	     */
	    updatePerformanceMetrics(metric, value) {
	        if (metric === 'initLatency') {
	            this.performanceMetrics.processingLatency =
	                (this.performanceMetrics.processingLatency + value) / 2;
	        }
	    }
	    /**
	     * Handle errors consistently
	     */
	    handleError(message, error) {
	        const errorMessage = error instanceof Error ? error.message : String(error);
	        this.recordingState.error = `${message}: ${errorMessage}`;
	        console.error(`VoiceRecorder Error - ${message}:`, error);
	        this.emit('error', {
	            message,
	            error: errorMessage,
	            timestamp: Date.now()
	        });
	    }
	    /**
	     * Cleanup resources
	     */
	    cleanup() {
	        // Stop tracks
	        if (this.mediaStream) {
	            this.mediaStream.getTracks().forEach(track => track.stop());
	            this.mediaStream = null;
	        }
	        // Cleanup audio nodes
	        if (this.scriptProcessor) {
	            this.scriptProcessor.disconnect();
	            this.scriptProcessor = null;
	        }
	        if (this.analyser) {
	            this.analyser.disconnect();
	            this.analyser = null;
	        }
	        // Clear timers
	        this.stopVoiceActivityDetection();
	        this.clearSilenceTimer();
	    }
	    // Public API methods
	    getState() {
	        return { ...this.recordingState };
	    }
	    getConfig() {
	        return { ...this.config };
	    }
	    getPerformanceMetrics() {
	        return { ...this.performanceMetrics };
	    }
	    updateConfig(updates) {
	        this.config = { ...this.config, ...updates };
	        this.emit('configUpdated', this.config);
	    }
	    /**
	     * Check if browser supports recording
	     */
	    static isSupported() {
	        return !!(typeof navigator !== 'undefined' &&
	            navigator.mediaDevices &&
	            typeof navigator.mediaDevices.getUserMedia === 'function' &&
	            typeof MediaRecorder !== 'undefined' &&
	            (window.location.protocol === 'https:' || window.location.hostname === 'localhost'));
	    }
	    /**
	     * Get supported MIME types
	     */
	    static getSupportedMimeTypes() {
	        const mimeTypes = [
	            'audio/webm;codecs=opus',
	            'audio/webm',
	            'audio/mp4',
	            'audio/ogg;codecs=opus',
	            'audio/ogg',
	            'audio/wav'
	        ];
	        return mimeTypes.filter(type => MediaRecorder.isTypeSupported(type));
	    }
	    /**
	     * Destroy recorder and cleanup all resources
	     */
	    destroy() {
	        if (this.recordingState.isRecording) {
	            this.stopRecording().catch(() => {
	                // Ignore stop errors during destroy
	            });
	        }
	        this.cleanup();
	        if (this.audioContext && this.audioContext.state !== 'closed') {
	            this.audioContext.close();
	        }
	        this.removeAllListeners();
	    }
	}

	// Core exports
	// Version info
	const VERSION = '1.6.0';
	// Utilities
	const utils = {
	    /**
	     * Check if voice features are supported in the current environment
	     */
	    isVoiceSupported() {
	        return !!(typeof navigator !== 'undefined' &&
	            navigator.mediaDevices &&
	            typeof navigator.mediaDevices.getUserMedia === 'function' &&
	            (window.location.protocol === 'https:' || window.location.hostname === 'localhost'));
	    },
	    /**
	     * Check if recording is supported
	     */
	    isRecordingSupported() {
	        return !!(typeof MediaRecorder !== 'undefined' &&
	            this.isVoiceSupported());
	    },
	    /**
	     * Get optimal voice configuration for different scenarios
	     */
	    getOptimalConfig(scenario) {
	        switch (scenario) {
	            case 'ultra-fast':
	                return {
	                    priority: 'speed',
	                    ultraFastMode: true,
	                    enableCaching: true,
	                    enableCompression: true,
	                    audioFormat: 'mp3',
	                    timeout: 5000,
	                    maxCacheSize: 100
	                };
	            case 'high-quality':
	                return {
	                    priority: 'quality',
	                    ultraFastMode: false,
	                    enableCaching: false,
	                    enableCompression: false,
	                    audioFormat: 'wav',
	                    timeout: 30000,
	                    maxCacheSize: 10
	                };
	            case 'balanced':
	            default:
	                return {
	                    priority: 'balanced',
	                    ultraFastMode: true,
	                    enableCaching: true,
	                    enableCompression: true,
	                    audioFormat: 'mp3',
	                    timeout: 15000,
	                    maxCacheSize: 50
	                };
	        }
	    },
	    /**
	     * Create a VoiceClient with optimal settings for quick setup
	     */
	    createQuickClient(apiKey, scenario = 'balanced') {
	        const config = {
	            apiKey,
	            ...this.getOptimalConfig(scenario)
	        };
	        return new VoiceClient(config);
	    },
	    /**
	     * Get performance recommendations based on metrics
	     */
	    getPerformanceRecommendations(metrics) {
	        const recommendations = [];
	        if (metrics.averageLatency > 2000) {
	            recommendations.push('Consider enabling ultra-fast mode for better latency');
	            recommendations.push('Check your network connection');
	        }
	        if (metrics.cacheHitRate < 0.3) {
	            recommendations.push('Enable caching to improve performance');
	            recommendations.push('Consider increasing cache size');
	        }
	        if (metrics.errorRate > 0.1) {
	            recommendations.push('Check API key and network connectivity');
	            recommendations.push('Consider implementing retry logic');
	        }
	        if (recommendations.length === 0) {
	            recommendations.push('Performance looks good! 🎉');
	        }
	        return recommendations;
	    },
	    /**
	     * Format text for optimal voice interaction (short and conversational)
	     */
	    formatForVoice(text, maxWords = 6) {
	        if (!text || typeof text !== 'string')
	            return '';
	        // Remove common filler words and phrases for voice
	        const fillerWords = ['actually', 'basically', 'essentially', 'definitely', 'certainly', 'absolutely', 'of course', 'you know', 'I think', 'I believe', 'let me', 'well,', 'so,', 'now,'];
	        let words = text.split(/\s+/).filter(w => w.trim());
	        // Remove filler words
	        words = words.filter(word => !fillerWords.some(filler => word.toLowerCase().includes(filler.toLowerCase())));
	        // Try to extract key phrases first
	        const keyPhrases = [
	            'yes', 'no', 'sure', 'okay', 'great', 'good', 'got it', 'checking', 'done',
	            'right', 'correct', 'exactly', 'perfect', 'sounds good', 'what city',
	            'your budget', 'tell me', 'which', 'how much', 'when', 'where'
	        ];
	        const lowerText = text.toLowerCase();
	        const foundKeyPhrase = keyPhrases.find(phrase => lowerText.includes(phrase));
	        let result = '';
	        if (foundKeyPhrase && foundKeyPhrase.split(' ').length <= 3) {
	            // Use the key phrase if it's short enough
	            result = foundKeyPhrase.charAt(0).toUpperCase() + foundKeyPhrase.slice(1);
	        }
	        else if (words.length <= maxWords) {
	            // Use cleaned words if short enough
	            result = words.join(' ');
	        }
	        else {
	            // Take first maxWords maximum
	            result = words.slice(0, maxWords).join(' ');
	        }
	        // Clean up and add appropriate punctuation
	        result = result.trim();
	        // Remove unnecessary punctuation
	        result = result.replace(/[.!?]+$/, '');
	        // Add appropriate punctuation
	        if (result.toLowerCase().match(/^(yes|sure|great|good|okay|right|correct|exactly|perfect|sounds good)$/)) {
	            result += '!';
	        }
	        else if (result.toLowerCase().includes('?') || result.toLowerCase().match(/^(what|which|how|when|where|why)/)) {
	            if (!result.includes('?')) {
	                result += '?';
	            }
	        }
	        else {
	            result += '.';
	        }
	        return result;
	    },
	    /**
	     * Create voice-optimized configuration for ultra-short responses
	     */
	    getVoiceOptimizedConfig(apiKey) {
	        return {
	            apiKey,
	            priority: 'speed',
	            ultraFastMode: true,
	            enableCaching: true,
	            enableCompression: true,
	            audioFormat: 'mp3',
	            timeout: 5000,
	            maxCacheSize: 100,
	            // Voice-specific optimizations
	            language: 'en',
	            voiceId: 'N2al4jd45e882svx17SU', // Optimized voice for speed
	            autoPlay: true,
	            enableSTT: true,
	            enableTTS: true
	        };
	    }
	};

	// Core exports (re-export for vanilla usage)
	// Vanilla JavaScript helpers for easier integration
	const VanillaVoiceAI = {
	    /**
	     * Quick setup for vanilla JS projects
	     */
	    createClient(apiKey, options = {}) {
	        const { VoiceClient, utils } = require('../index');
	        const config = {
	            apiKey,
	            ...utils.getOptimalConfig(options.mode || 'balanced'),
	            ...options
	        };
	        return new VoiceClient(config);
	    },
	    /**
	     * Quick setup for recorder
	     */
	    createRecorder(options = {}) {
	        const { VoiceRecorder } = require('../index');
	        return new VoiceRecorder(options);
	    },
	    /**
	     * Check browser support
	     */
	    checkSupport() {
	        const { utils, VoiceRecorder } = require('../index');
	        return {
	            voiceSupported: utils.isVoiceSupported(),
	            recordingSupported: utils.isRecordingSupported(),
	            supportedMimeTypes: VoiceRecorder.getSupportedMimeTypes(),
	            isHttps: window.location.protocol === 'https:' || window.location.hostname === 'localhost'
	        };
	    },
	    /**
	     * Simple text-to-speech function
	     */
	    async speak(text, apiKey, options = {}) {
	        const client = this.createClient(apiKey, {
	            ...options,
	            mode: 'tts-only',
	            autoPlay: true
	        });
	        try {
	            const response = await client.textToSpeech(text);
	            return response;
	        }
	        finally {
	            client.destroy();
	        }
	    },
	    /**
	     * Simple speech-to-text function
	     */
	    async transcribe(audioBlob, apiKey, options = {}) {
	        const client = this.createClient(apiKey, {
	            ...options,
	            mode: 'stt-only'
	        });
	        try {
	            const response = await client.transcribeAudio(audioBlob);
	            return response;
	        }
	        finally {
	            client.destroy();
	        }
	    },
	    /**
	     * Record and transcribe in one function
	     */
	    async recordAndTranscribe(apiKey, recorderOptions = {}, clientOptions = {}) {
	        const recorder = this.createRecorder({
	            enableVAD: true,
	            silenceTimeout: 1500,
	            ...recorderOptions
	        });
	        const client = this.createClient(apiKey, {
	            mode: 'stt-only',
	            ...clientOptions
	        });
	        try {
	            // Start recording
	            await recorder.startRecording();
	            // Wait for recording to complete (via VAD or manual stop)
	            return new Promise((resolve, reject) => {
	                recorder.on('recordingStopped', async ({ audioBlob }) => {
	                    try {
	                        const response = await client.transcribeAudio(audioBlob);
	                        resolve(response);
	                    }
	                    catch (error) {
	                        reject(error);
	                    }
	                });
	                recorder.on('error', reject);
	            });
	        }
	        finally {
	            recorder.destroy();
	            client.destroy();
	        }
	    },
	    /**
	     * Full voice chat function
	     */
	    async voiceChat(input, apiKey, options = {}) {
	        const client = this.createClient(apiKey, {
	            mode: 'full-voice',
	            autoPlay: true,
	            ...options
	        });
	        try {
	            const response = await client.sendVoiceMessage(input);
	            return response;
	        }
	        finally {
	            client.destroy();
	        }
	    }
	};
	// Make available globally for script tag usage
	if (typeof window !== 'undefined') {
	    window.HomiVoiceAI = {
	        VoiceClient,
	        VoiceRecorder,
	        utils,
	        VERSION,
	        ...VanillaVoiceAI
	    };
	}

	exports.VERSION = VERSION;
	exports.VanillaVoiceAI = VanillaVoiceAI;
	exports.VoiceClient = VoiceClient;
	exports.VoiceRecorder = VoiceRecorder;
	exports.utils = utils;

}));
//# sourceMappingURL=index.umd.js.map