From cf679084c64e48602aa3379624ec87a094937d0f Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 10:35:44 -0500 Subject: [PATCH 01/10] Refactor code structure and remove redundant sections for improved readability and maintainability --- .../.gitignore | 66 ++ react-native-live-audio-stream-master/LICENSE | 21 + .../README.md | 78 ++ .../RNLiveAudioStream.podspec | 22 + .../android/build.gradle | 26 + .../android/src/main/AndroidManifest.xml | 5 + .../RNLiveAudioStreamModule.java | 117 ++ .../RNLiveAudioStreamPackage.java | 25 + .../index.d.ts | 30 + .../index.js | 24 + .../ios/RNLiveAudioStream.h | 19 + .../ios/RNLiveAudioStream.m | 103 ++ .../project.pbxproj | 263 +++++ .../xcschemes/RNLiveAudioStream.xcscheme | 67 ++ .../package.json | 33 + .../yarn.lock | 1043 +++++++++++++++++ 16 files changed, 1942 insertions(+) create mode 100644 react-native-live-audio-stream-master/.gitignore create mode 100644 react-native-live-audio-stream-master/LICENSE create mode 100644 react-native-live-audio-stream-master/README.md create mode 100644 react-native-live-audio-stream-master/RNLiveAudioStream.podspec create mode 100644 react-native-live-audio-stream-master/android/build.gradle create mode 100644 react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml create mode 100644 react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java create mode 100644 react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java create mode 100644 react-native-live-audio-stream-master/index.d.ts create mode 100644 react-native-live-audio-stream-master/index.js create mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.h create mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.m create mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj create mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme create mode 100644 react-native-live-audio-stream-master/package.json create mode 100644 react-native-live-audio-stream-master/yarn.lock diff --git a/react-native-live-audio-stream-master/.gitignore b/react-native-live-audio-stream-master/.gitignore new file mode 100644 index 0000000..ee3af70 --- /dev/null +++ b/react-native-live-audio-stream-master/.gitignore @@ -0,0 +1,66 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +# Bundle artifact +*.jsbundle + +# CocoaPods +/ios/Pods/ + +# Visual Studio Code +**/.classpath +**/.project +**/.settings +**/.factorypath +.vscode diff --git a/react-native-live-audio-stream-master/LICENSE b/react-native-live-audio-stream-master/LICENSE new file mode 100644 index 0000000..6f495ef --- /dev/null +++ b/react-native-live-audio-stream-master/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Qi Xi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/react-native-live-audio-stream-master/README.md b/react-native-live-audio-stream-master/README.md new file mode 100644 index 0000000..7ce0036 --- /dev/null +++ b/react-native-live-audio-stream-master/README.md @@ -0,0 +1,78 @@ + +# react-native-live-audio-stream +[![npm](https://img.shields.io/npm/v/react-native-live-audio-stream)](https://www.npmjs.com/package/react-native-live-audio-stream) + +Get live audio stream data for React Native. Ideal for live voice recognition (transcribing). + +This module is modified from [react-native-audio-record](https://github.com/goodatlas/react-native-audio-record). Instead of saving to an audio file, it only emit events with live data. By doing this, it can reduce memory usage and eliminate file operation overheads in the case that an audio file is not necessary (e.g. live transcribing). + +Most of the code was written by the respective original authors. + +## Install +``` +yarn add react-native-live-audio-stream +cd ios +pod install +``` + +## Add Microphone Permissions + +### iOS +Add these lines to ```ios/[YOU_APP_NAME]/info.plist``` +```xml +NSMicrophoneUsageDescription +We need your permission to use the microphone. +``` + +### Android +Add the following line to ```android/app/src/main/AndroidManifest.xml``` +```xml + +``` + +## Usage +```javascript +import LiveAudioStream from 'react-native-live-audio-stream'; + +const options = { + sampleRate: 32000, // default is 44100 but 32000 is adequate for accurate voice recognition + channels: 1, // 1 or 2, default 1 + bitsPerSample: 16, // 8 or 16, default 16 + audioSource: 6, // android only (see below) + bufferSize: 4096 // default is 2048 +}; + +LiveAudioStream.init(options); +LiveAudioStream.on('data', data => { + // base64-encoded audio data chunks +}); + ... +LiveAudioStream.start(); + ... +LiveAudioStream.stop(); + ... +``` + +`audioSource` should be one of the constant values from [here](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource). Default value is `6` (`VOICE_RECOGNITION`). + +Use 3rd-party modules like [buffer](https://www.npmjs.com/package/buffer) to decode base64 data. Example: +```javascript +// yarn add buffer +import { Buffer } from 'buffer'; + ... +LiveAudioStream.on('data', data => { + var chunk = Buffer.from(data, 'base64'); +}); +``` + +## Credits/References +- [react-native-audio-record](https://github.com/goodatlas/react-native-audio-record) +- iOS [Audio Queues](https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide) +- Android [AudioRecord](https://developer.android.com/reference/android/media/AudioRecord.html) +- [cordova-plugin-audioinput](https://github.com/edimuj/cordova-plugin-audioinput) +- [react-native-recording](https://github.com/qiuxiang/react-native-recording) +- [SpeakHere](https://github.com/shaojiankui/SpeakHere) +- [ringdroid](https://github.com/google/ringdroid) + +## License +MIT diff --git a/react-native-live-audio-stream-master/RNLiveAudioStream.podspec b/react-native-live-audio-stream-master/RNLiveAudioStream.podspec new file mode 100644 index 0000000..f7307f5 --- /dev/null +++ b/react-native-live-audio-stream-master/RNLiveAudioStream.podspec @@ -0,0 +1,22 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = "RNLiveAudioStream" + s.version = package['version'] + s.summary = package['description'] + s.description = package['description'] + s.license = package['license'] + s.author = package['author'] + s.homepage = "https://github.com/xiqi/react-native-live-audio-stream" + s.source = { :git => 'https://github.com/xiqi/react-native-live-audio-stream.git' } + + s.requires_arc = true + s.ios.deployment_target = '8.0' + + s.preserve_paths = 'README.md', 'package.json', 'index.js' + s.source_files = 'iOS/*.{h,m}' + + s.dependency 'React' +end diff --git a/react-native-live-audio-stream-master/android/build.gradle b/react-native-live-audio-stream-master/android/build.gradle new file mode 100644 index 0000000..de6cdf1 --- /dev/null +++ b/react-native-live-audio-stream-master/android/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +def DEFAULT_COMPILE_SDK_VERSION = 27 +def DEFAULT_BUILD_TOOLS_VERSION = "27.0.3" +def DEFAULT_MIN_SDK_VERSION = 16 +def DEFAULT_TARGET_SDK_VERSION = 26 + +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} + +android { + compileSdkVersion safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) + buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_VERSION) + + defaultConfig { + minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) + targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) + versionCode 1 + versionName "1.0" + } +} + +dependencies { + compileOnly 'com.facebook.react:react-native:+' +} diff --git a/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml b/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e96278e --- /dev/null +++ b/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + diff --git a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java new file mode 100644 index 0000000..20f454f --- /dev/null +++ b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java @@ -0,0 +1,117 @@ +package com.imxiqi.rnliveaudiostream; + +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder.AudioSource; +import android.util.Base64; +import android.util.Log; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +import java.lang.Math; + +public class RNLiveAudioStreamModule extends ReactContextBaseJavaModule { + + private final ReactApplicationContext reactContext; + private DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter; + + private int sampleRateInHz; + private int channelConfig; + private int audioFormat; + private int audioSource; + + private AudioRecord recorder; + private int bufferSize; + private boolean isRecording; + + public RNLiveAudioStreamModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + + @Override + public String getName() { + return "RNLiveAudioStream"; + } + + @ReactMethod + public void init(ReadableMap options) { + sampleRateInHz = 44100; + if (options.hasKey("sampleRate")) { + sampleRateInHz = options.getInt("sampleRate"); + } + + channelConfig = AudioFormat.CHANNEL_IN_MONO; + if (options.hasKey("channels")) { + if (options.getInt("channels") == 2) { + channelConfig = AudioFormat.CHANNEL_IN_STEREO; + } + } + + audioFormat = AudioFormat.ENCODING_PCM_16BIT; + if (options.hasKey("bitsPerSample")) { + if (options.getInt("bitsPerSample") == 8) { + audioFormat = AudioFormat.ENCODING_PCM_8BIT; + } + } + + audioSource = AudioSource.VOICE_RECOGNITION; + if (options.hasKey("audioSource")) { + audioSource = options.getInt("audioSource"); + } + + isRecording = false; + eventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); + + bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); + + if (options.hasKey("bufferSize")) { + bufferSize = Math.max(bufferSize, options.getInt("bufferSize")); + } + + int recordingBufferSize = bufferSize * 3; + recorder = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, recordingBufferSize); + } + + @ReactMethod + public void start() { + isRecording = true; + recorder.startRecording(); + + Thread recordingThread = new Thread(new Runnable() { + public void run() { + try { + int bytesRead; + int count = 0; + String base64Data; + byte[] buffer = new byte[bufferSize]; + + while (isRecording) { + bytesRead = recorder.read(buffer, 0, buffer.length); + + // skip first 2 buffers to eliminate "click sound" + if (bytesRead > 0 && ++count > 2) { + base64Data = Base64.encodeToString(buffer, Base64.NO_WRAP); + eventEmitter.emit("data", base64Data); + } + } + recorder.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + + recordingThread.start(); + } + + @ReactMethod + public void stop(Promise promise) { + isRecording = false; + } +} diff --git a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java new file mode 100644 index 0000000..2268d6d --- /dev/null +++ b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java @@ -0,0 +1,25 @@ +package com.imxiqi.rnliveaudiostream; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RNLiveAudioStreamPackage implements ReactPackage { + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new RNLiveAudioStreamModule(reactContext)); + return modules; + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/react-native-live-audio-stream-master/index.d.ts b/react-native-live-audio-stream-master/index.d.ts new file mode 100644 index 0000000..b846438 --- /dev/null +++ b/react-native-live-audio-stream-master/index.d.ts @@ -0,0 +1,30 @@ +declare module "react-native-live-audio-stream" { + export interface IAudioRecord { + init: (options: Options) => void + start: () => void + stop: () => Promise + on: (event: "data", callback: (data: string) => void) => void + } + + export interface Options { + sampleRate: number + /** + * - `1 | 2` + */ + channels: number + /** + * - `8 | 16` + */ + bitsPerSample: number + /** + * - `6` + */ + audioSource?: number + wavFile: string + bufferSize?: number + } + + const AudioRecord: IAudioRecord + + export default AudioRecord; +} diff --git a/react-native-live-audio-stream-master/index.js b/react-native-live-audio-stream-master/index.js new file mode 100644 index 0000000..c5968b2 --- /dev/null +++ b/react-native-live-audio-stream-master/index.js @@ -0,0 +1,24 @@ +import { NativeModules, NativeEventEmitter } from 'react-native'; +const { RNLiveAudioStream } = NativeModules; +const EventEmitter = new NativeEventEmitter(RNLiveAudioStream); + +const AudioRecord = {}; + +AudioRecord.init = options => RNLiveAudioStream.init(options); +AudioRecord.start = () => RNLiveAudioStream.start(); +AudioRecord.stop = () => RNLiveAudioStream.stop(); + +const eventsMap = { + data: 'data' +}; + +AudioRecord.on = (event, callback) => { + const nativeEvent = eventsMap[event]; + if (!nativeEvent) { + throw new Error('Invalid event'); + } + EventEmitter.removeAllListeners(nativeEvent); + return EventEmitter.addListener(nativeEvent, callback); +}; + +export default AudioRecord; diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h new file mode 100644 index 0000000..8ade6b5 --- /dev/null +++ b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h @@ -0,0 +1,19 @@ +#import +#import +#import + +#define kNumberBuffers 3 + +typedef struct { + __unsafe_unretained id mSelf; + AudioStreamBasicDescription mDataFormat; + AudioQueueRef mQueue; + AudioQueueBufferRef mBuffers[kNumberBuffers]; + UInt32 bufferByteSize; + SInt64 mCurrentPacket; + bool mIsRunning; +} AQRecordState; + +@interface RNLiveAudioStream: RCTEventEmitter + @property (nonatomic, assign) AQRecordState recordState; +@end diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m new file mode 100644 index 0000000..a6f2317 --- /dev/null +++ b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m @@ -0,0 +1,103 @@ +#import "RNLiveAudioStream.h" + +@implementation RNLiveAudioStream + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(init:(NSDictionary *) options) { + RCTLogInfo(@"[RNLiveAudioStream] init"); + _recordState.mDataFormat.mSampleRate = options[@"sampleRate"] == nil ? 44100 : [options[@"sampleRate"] doubleValue]; + _recordState.mDataFormat.mBitsPerChannel = options[@"bitsPerSample"] == nil ? 16 : [options[@"bitsPerSample"] unsignedIntValue]; + _recordState.mDataFormat.mChannelsPerFrame = options[@"channels"] == nil ? 1 : [options[@"channels"] unsignedIntValue]; + _recordState.mDataFormat.mBytesPerPacket = (_recordState.mDataFormat.mBitsPerChannel / 8) * _recordState.mDataFormat.mChannelsPerFrame; + _recordState.mDataFormat.mBytesPerFrame = _recordState.mDataFormat.mBytesPerPacket; + _recordState.mDataFormat.mFramesPerPacket = 1; + _recordState.mDataFormat.mReserved = 0; + _recordState.mDataFormat.mFormatID = kAudioFormatLinearPCM; + _recordState.mDataFormat.mFormatFlags = _recordState.mDataFormat.mBitsPerChannel == 8 ? kLinearPCMFormatFlagIsPacked : (kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked); + _recordState.bufferByteSize = options[@"bufferSize"] == nil ? 2048 : [options[@"bufferSize"] unsignedIntValue]; + _recordState.mSelf = self; +} + +RCT_EXPORT_METHOD(start) { + RCTLogInfo(@"[RNLiveAudioStream] start"); + AVAudioSession *audioSession = [AVAudioSession sharedInstance]; + NSError *error = nil; + BOOL success; + + // Apple recommended: + // Instead of setting your category and mode properties independently, set them at the same time + if (@available(iOS 10.0, *)) { + success = [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord + mode: AVAudioSessionModeVoiceChat + options: AVAudioSessionCategoryOptionDuckOthers | + AVAudioSessionCategoryOptionAllowBluetooth | + AVAudioSessionCategoryOptionAllowAirPlay + error: &error]; + } else { + success = [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord withOptions: AVAudioSessionCategoryOptionDuckOthers error: &error]; + success = [audioSession setMode: AVAudioSessionModeVoiceChat error: &error] && success; + } + if (!success || error != nil) { + RCTLog(@"[RNLiveAudioStream] Problem setting up AVAudioSession category and mode. Error: %@", error); + return; + } + + _recordState.mIsRunning = true; + + OSStatus status = AudioQueueNewInput(&_recordState.mDataFormat, HandleInputBuffer, &_recordState, NULL, NULL, 0, &_recordState.mQueue); + if (status != 0) { + RCTLog(@"[RNLiveAudioStream] Record Failed. Cannot initialize AudioQueueNewInput. status: %i", (int) status); + return; + } + + for (int i = 0; i < kNumberBuffers; i++) { + AudioQueueAllocateBuffer(_recordState.mQueue, _recordState.bufferByteSize, &_recordState.mBuffers[i]); + AudioQueueEnqueueBuffer(_recordState.mQueue, _recordState.mBuffers[i], 0, NULL); + } + AudioQueueStart(_recordState.mQueue, NULL); +} + +RCT_EXPORT_METHOD(stop) { + RCTLogInfo(@"[RNLiveAudioStream] stop"); + if (_recordState.mIsRunning) { + _recordState.mIsRunning = false; + AudioQueueStop(_recordState.mQueue, true); + for (int i = 0; i < kNumberBuffers; i++) { + AudioQueueFreeBuffer(_recordState.mQueue, _recordState.mBuffers[i]); + } + AudioQueueDispose(_recordState.mQueue, true); + } +} + +void HandleInputBuffer(void *inUserData, + AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + const AudioTimeStamp *inStartTime, + UInt32 inNumPackets, + const AudioStreamPacketDescription *inPacketDesc) { + AQRecordState* pRecordState = (AQRecordState *)inUserData; + + if (!pRecordState->mIsRunning) { + return; + } + + short *samples = (short *) inBuffer->mAudioData; + long nsamples = inBuffer->mAudioDataByteSize; + NSData *data = [NSData dataWithBytes:samples length:nsamples]; + NSString *str = [data base64EncodedStringWithOptions:0]; + [pRecordState->mSelf sendEventWithName:@"data" body:str]; + + AudioQueueEnqueueBuffer(pRecordState->mQueue, inBuffer, 0, NULL); +} + +- (NSArray *)supportedEvents { + return @[@"data"]; +} + +- (void)dealloc { + RCTLogInfo(@"[RNLiveAudioStream] dealloc"); + AudioQueueDispose(_recordState.mQueue, true); +} + +@end diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b06bf4d --- /dev/null +++ b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj @@ -0,0 +1,263 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 49C5D751243FA7A5003D418B /* RNLiveAudioStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNLiveAudioStream.m; sourceTree = ""; }; + 49C5D750243FA7A5003D418B /* RNLiveAudioStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNLiveAudioStream.h; sourceTree = ""; }; + 49C5D76024407376003D418B /* libRNLiveAudioStream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNLiveAudioStream.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 49C5D750243FA7A5003D418B /* RNLiveAudioStream.h */, + 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */, + 49C5D76024407376003D418B /* libRNLiveAudioStream.a */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 49C5D757243FC397003D418B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* RNLiveAudioStream */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNLiveAudioStream" */; + buildPhases = ( + 49C5D757243FC397003D418B /* Headers */, + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RNLiveAudioStream; + productName = RCTDataManager; + productReference = 49C5D76024407376003D418B /* libRNLiveAudioStream.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0830; + ORGANIZATIONNAME = "Qi Xi"; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNLiveAudioStream" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* RNLiveAudioStream */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 49C5D751243FA7A5003D418B /* RNLiveAudioStream.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RNLiveAudioStream; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../../React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RNLiveAudioStream; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNLiveAudioStream" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNLiveAudioStream" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme new file mode 100644 index 0000000..fc5b100 --- /dev/null +++ b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/react-native-live-audio-stream-master/package.json b/react-native-live-audio-stream-master/package.json new file mode 100644 index 0000000..5e478d4 --- /dev/null +++ b/react-native-live-audio-stream-master/package.json @@ -0,0 +1,33 @@ +{ + "name": "react-native-live-audio-stream", + "version": "1.1.1", + "description": "Get live audio stream data for React Native", + "license": "MIT", + "keywords": [ + "live stream", + "react-native", + "audio", + "live", + "recording", + "voice" + ], + "main": "index.js", + "homepage": "https://github.com/xiqi/react-native-live-audio-stream", + "bugs": { + "url": "https://github.com/xiqi/react-native-live-audio-stream/issues", + "email": "me@imxiqi.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/xiqi/react-native-live-audio-stream.git" + }, + "author": "Qi Xi (https://www.imxiqi.com/)", + "devDependencies": { + "babel-eslint": "^8.2.3", + "eslint": "^4.19.1", + "eslint-plugin-prettier": "^2.6.0", + "prettier": "^1.12.0" + }, + "dependencies": {}, + "peerDependencies": {} +} \ No newline at end of file diff --git a/react-native-live-audio-stream-master/yarn.lock b/react-native-live-audio-stream-master/yarn.lock new file mode 100644 index 0000000..d8e3823 --- /dev/null +++ b/react-native-live-audio-stream-master/yarn.lock @@ -0,0 +1,1043 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" + integrity sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g== + dependencies: + "@babel/highlight" "7.0.0-beta.44" + +"@babel/generator@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" + integrity sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ== + dependencies: + "@babel/types" "7.0.0-beta.44" + jsesc "^2.5.1" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-function-name@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" + integrity sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg== + dependencies: + "@babel/helper-get-function-arity" "7.0.0-beta.44" + "@babel/template" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + +"@babel/helper-get-function-arity@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" + integrity sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw== + dependencies: + "@babel/types" "7.0.0-beta.44" + +"@babel/helper-split-export-declaration@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" + integrity sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA== + dependencies: + "@babel/types" "7.0.0-beta.44" + +"@babel/highlight@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" + integrity sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +"@babel/template@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" + integrity sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng== + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + lodash "^4.2.0" + +"@babel/traverse@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" + integrity sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA== + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/generator" "7.0.0-beta.44" + "@babel/helper-function-name" "7.0.0-beta.44" + "@babel/helper-split-export-declaration" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + debug "^3.1.0" + globals "^11.1.0" + invariant "^2.2.0" + lodash "^4.2.0" + +"@babel/types@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" + integrity sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ== + dependencies: + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^2.0.0" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= + +acorn@^5.5.0: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= + +ajv@^5.2.3, ajv@^5.3.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +babel-code-frame@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-eslint@^8.2.3: + version "8.2.6" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.6.tgz#6270d0c73205628067c0f7ae1693a9e797acefd9" + integrity sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA== + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/traverse" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + eslint-scope "3.7.1" + eslint-visitor-keys "^1.0.0" + +babylon@7.0.0-beta.44: + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" + integrity sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.1.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-plugin-prettier@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz#b4312dcf2c1d965379d7f9d5b5f8aaadc6a45904" + integrity sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA== + dependencies: + fast-diff "^1.1.1" + jest-docblock "^21.0.0" + +eslint-scope@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^3.7.1: + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" + integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-visitor-keys@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== + +eslint@^4.19.1: + version "4.19.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" + integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== + dependencies: + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^3.7.1" + eslint-visitor-keys "^1.0.0" + espree "^3.5.4" + esquery "^1.0.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^1.0.1" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "4.0.2" + text-table "~0.2.0" + +espree@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== + dependencies: + acorn "^5.5.0" + acorn-jsx "^3.0.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" + integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== + dependencies: + estraverse "^5.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" + integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^2.0.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + +fast-diff@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +flat-cache@^1.2.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" + integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== + dependencies: + circular-json "^0.3.1" + graceful-fs "^4.1.2" + rimraf "~2.6.2" + write "^0.2.1" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob@^7.1.2, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.0.1, globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +iconv-lite@^0.4.17: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^3.3.3: + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +invariant@^2.2.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +jest-docblock@^21.0.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.9.1: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= + dependencies: + mimic-fn "^1.0.0" + +optionator@^0.8.2: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^1.12.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" + integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +regexpp@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.2.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" + integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^5.3.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== + dependencies: + is-fullwidth-code-point "^2.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +table@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= + dependencies: + mkdirp "^0.5.1" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= From ba0a5a7a0b3dfd749727022c5ac10bc7cbc5bb58 Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 11:18:12 -0500 Subject: [PATCH 02/10] feat: add build:pod script and update build:all to include pod installation --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 92080cb..2968643 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,11 @@ "release": "release-it --only-version", "build": "npx react-native-builder-bob build", "build:plugin": "npx tsc --build plugin", + "build:pod": "cd example && cd ios && npx pod-install && cd ..", "example": "yarn workspace react-native-deepgram-example start", "example:ios": "yarn workspace react-native-deepgram-example ios", - "example:android": "yarn workspace react-native-deepgram-example android" + "example:android": "yarn workspace react-native-deepgram-example android", + "build:all": "yarn install && yarn build && yarn build:plugin && yarn build:pod" }, "keywords": [ "react-native", From 0f4872f8061a2513a9e26baae59d740fb45ba3b6 Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 11:20:02 -0500 Subject: [PATCH 03/10] feat: update Deepgram to version 1.0.4 and enhance audio processing capabilities --- example/ios/Podfile.lock | 10 +- .../project.pbxproj | 36 +++-- ios/Deepgram.mm | 151 ++++++++++++++++-- lefthook.yml | 28 ++-- package.json | 1 + src/useDeepgramSpeechToText.ts | 11 +- src/useDeepgramVoiceAgent.ts | 18 +-- 7 files changed, 195 insertions(+), 60 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 6d9c714..3bc3efb 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - Deepgram (1.0.3): + - Deepgram (1.0.4): - DoubleConversion - glog - hermes-engine @@ -1798,9 +1798,9 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - Deepgram: 02ac76cdb664bbcc8757144cc34abe8c7c12d089 - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb + boost: 1dca942403ed9342f98334bf4c3621f011aa7946 + Deepgram: 55736a9de155ae901e7ce257523b9adb48ed171a + DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b Expo: 1687edb10c76b0c0f135306d6ae245379f50ed54 ExpoAsset: 48386d40d53a8c1738929b3ed509bcad595b5516 @@ -1811,7 +1811,7 @@ SPEC CHECKSUMS: ExpoModulesCore: 98297c2cc7977c43740a2e52d850d94ac8dbf176 FBLazyVector: be509404b5de73a64a74284edcaf73a5d1e128b1 fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a hermes-engine: 1949ca944b195a8bde7cbf6316b9068e19cf53c6 RCT-Folly: 84578c8756030547307e4572ab1947de1685c599 RCTDeprecation: 063fc281b30b7dc944c98fe53a7e266dab1a8706 diff --git a/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj index e6a39af..5f55861 100644 --- a/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -29,7 +29,7 @@ 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-ReactNativeDeepgramExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeDeepgramExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6C2E3173556A471DD304B334 /* Pods-ReactNativeDeepgramExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeDeepgramExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeDeepgramExample/Pods-ReactNativeDeepgramExample.debug.xcconfig"; sourceTree = ""; }; 7A4D352CD337FB3A3BF06240 /* Pods-ReactNativeDeepgramExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeDeepgramExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeDeepgramExample/Pods-ReactNativeDeepgramExample.release.xcconfig"; sourceTree = ""; }; - 9518637219D6E071AB8BDAB0 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = ReactNativeDeepgramExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 9518637219D6E071AB8BDAB0 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeDeepgramExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = ReactNativeDeepgramExample/SplashScreen.storyboard; sourceTree = ""; }; BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; C0773B8BEBC647858A0F7456 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "ReactNativeDeepgramExample/noop-file.swift"; sourceTree = ""; }; @@ -341,6 +341,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ReactNativeDeepgramExample/ReactNativeDeepgramExample.entitlements; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZG2CA7WRPY; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", @@ -348,7 +349,10 @@ ); INFOPLIST_FILE = ReactNativeDeepgramExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.0; OTHER_LDFLAGS = ( "$(inherited)", @@ -374,9 +378,13 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = ReactNativeDeepgramExample/ReactNativeDeepgramExample.entitlements; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ZG2CA7WRPY; INFOPLIST_FILE = ReactNativeDeepgramExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); MARKETING_VERSION = 1.0; OTHER_LDFLAGS = ( "$(inherited)", @@ -441,14 +449,14 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "$(inherited)", - " ", - ); + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -497,13 +505,13 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.1; - LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; - LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; - MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = ( + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, "$(inherited)", - " ", ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "$(inherited) "; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/ios/Deepgram.mm b/ios/Deepgram.mm index b2bef7d..c2ea586 100644 --- a/ios/Deepgram.mm +++ b/ios/Deepgram.mm @@ -4,6 +4,12 @@ @interface Deepgram : RCTEventEmitter // Recording @property (nonatomic, strong) AVAudioEngine *recEngine; +@property (nonatomic, strong) AVAudioConverter *pcmConverter; +@property (nonatomic, strong) AVAudioFormat *targetPCMFormat; +@property (nonatomic, strong) NSMutableData *pendingPCMBuffer; +@property (nonatomic, strong) dispatch_queue_t emitterQueue; +@property (nonatomic, assign) double resampleRatio; +@property (nonatomic, assign) NSUInteger chunkSizeBytes; // Playback / TTS @property (nonatomic, strong) AVAudioPlayer *audioPlayer; @@ -29,6 +35,9 @@ + (BOOL)requiresMainQueueSetup { return NO; } - (instancetype)init { if (self = [super init]) { + _chunkSizeBytes = 6400; // ≈200 ms of 16 kHz mono PCM16 audio + _emitterQueue = dispatch_queue_create("com.deepgram.liveaudiostream", + DISPATCH_QUEUE_SERIAL); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioRouteChange:) @@ -76,12 +85,18 @@ - (void)configureAudioSession AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *error = nil; - AVAudioSessionCategoryOptions options = - AVAudioSessionCategoryOptionDefaultToSpeaker | - AVAudioSessionCategoryOptionAllowBluetooth; + AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDefaultToSpeaker; if (@available(iOS 10.0, *)) { options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + options |= AVAudioSessionCategoryOptionAllowBluetooth; +#pragma clang diagnostic pop + } + + if (@available(iOS 10.0, *)) { error = nil; BOOL ok = [session setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeVoiceChat @@ -212,8 +227,63 @@ - (void)handleAudioRouteChange:(NSNotification *)note [self activateAudioSession]; } +- (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate +{ + if (!chunk || chunk.length == 0) { + return; + } + + __weak __typeof(self) weakSelf = self; + NSData *chunkCopy = [chunk copy]; + dispatch_queue_t queue = self.emitterQueue ?: dispatch_get_main_queue(); + dispatch_async(queue, ^{ + if (!weakSelf) { + return; + } + + NSString *b64 = [chunkCopy base64EncodedStringWithOptions:0]; + [weakSelf sendEventWithName:@"DeepgramAudioPCM" + body:@{ @"b64": b64, @"sampleRate": @(sampleRate) }]; + }); +} + +- (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData +{ + if (!pcmData || pcmData.length == 0) { + return; + } + + if (!self.pendingPCMBuffer) { + self.pendingPCMBuffer = [[NSMutableData alloc] init]; + } + + [self.pendingPCMBuffer appendData:pcmData]; + + NSUInteger chunkSize = self.chunkSizeBytes > 0 ? self.chunkSizeBytes : pcmData.length; + + while (self.pendingPCMBuffer.length >= chunkSize) { + NSData *chunk = + [self.pendingPCMBuffer subdataWithRange:NSMakeRange(0, chunkSize)]; + [self.pendingPCMBuffer replaceBytesInRange:NSMakeRange(0, chunkSize) + withBytes:NULL + length:0]; + [self emitPCMChunk:chunk sampleRate:self.currentSampleRate]; + } +} + +- (void)flushPendingPCM +{ + if (self.pendingPCMBuffer.length == 0) { + return; + } + + NSData *remaining = [self.pendingPCMBuffer copy]; + [self.pendingPCMBuffer setLength:0]; + [self emitPCMChunk:remaining sampleRate:self.currentSampleRate]; +} + /* ================================================================== */ -/* 1. MICROPHONE CAPTURE (≈ 48 kHz Float-32) */ +/* 1. MICROPHONE CAPTURE (16 kHz PCM16 emission) */ /* ================================================================== */ RCT_EXPORT_METHOD(startRecording :(RCTPromiseResolveBlock)resolve @@ -228,17 +298,75 @@ - (void)handleAudioRouteChange:(NSNotification *)note [input removeTapOnBus:0]; + self.pendingPCMBuffer = [[NSMutableData alloc] init]; + + double targetSampleRate = 16000.0; + AVAudioChannelCount targetChannels = 1; + self.targetPCMFormat = [[AVAudioFormat alloc] + initWithCommonFormat:AVAudioPCMFormatInt16 + sampleRate:targetSampleRate + channels:targetChannels + interleaved:NO]; + + self.pcmConverter = [[AVAudioConverter alloc] initFromFormat:hwFmt + toFormat:self.targetPCMFormat]; + self.resampleRatio = hwFmt.sampleRate > 0 ? targetSampleRate / hwFmt.sampleRate : 1.0; + if (self.resampleRatio <= 0) { + self.resampleRatio = 1.0; + } + + self.currentSampleRate = (int)targetSampleRate; + __weak __typeof(self) weakSelf = self; [input installTapOnBus:0 bufferSize:1024 format:hwFmt block:^(AVAudioPCMBuffer *buf, __unused AVAudioTime *when) { if (!weakSelf) return; - NSData *pcm = [NSData dataWithBytes:buf.floatChannelData[0] - length:buf.frameLength * sizeof(float)]; - NSString *b64 = [pcm base64EncodedStringWithOptions:0]; - [weakSelf sendEventWithName:@"DeepgramAudioPCM" - body:@{ @"b64": b64 }]; + if (!weakSelf.pcmConverter || !weakSelf.targetPCMFormat) { + NSLog(@"[Deepgram] PCM converter unavailable; dropping frame"); + return; + } + + AVAudioFrameCount inputFrames = buf.frameLength; + if (inputFrames == 0) { + return; + } + + double ratio = weakSelf.resampleRatio > 0 ? weakSelf.resampleRatio : 1.0; + AVAudioFrameCount outputCapacity = + (AVAudioFrameCount)ceil(inputFrames * ratio) + 32; + + AVAudioPCMBuffer *converted = [[AVAudioPCMBuffer alloc] + initWithPCMFormat:weakSelf.targetPCMFormat + frameCapacity:outputCapacity]; + + if (!converted) { + return; + } + + buf.frameLength = inputFrames; + + NSError *conversionError = nil; + BOOL success = [weakSelf.pcmConverter convertToBuffer:converted + fromBuffer:buf + error:&conversionError]; + + if (!success || conversionError) { + NSLog(@"[Deepgram] PCM conversion error: %@", + conversionError.localizedDescription); + return; + } + + converted.frameLength = MIN(converted.frameLength, outputCapacity); + if (converted.frameLength == 0) { + return; + } + + AudioBuffer audioBuffer = converted.audioBufferList->mBuffers[0]; + NSData *pcmData = [NSData dataWithBytes:audioBuffer.mData + length:audioBuffer.mDataByteSize]; + [weakSelf appendPCMDataAndEmitIfNeeded:pcmData]; }]; [self.recEngine prepare]; @@ -258,6 +386,11 @@ - (void)handleAudioRouteChange:(NSNotification *)note [self.recEngine.inputNode removeTapOnBus:0]; [self.recEngine stop]; self.recEngine = nil; + [self flushPendingPCM]; + self.pcmConverter = nil; + self.targetPCMFormat = nil; + self.pendingPCMBuffer = nil; + self.resampleRatio = 1.0; resolve(nil); } @catch (NSException *e) { diff --git a/lefthook.yml b/lefthook.yml index 9695c12..c4fb5e1 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,14 +1,14 @@ -pre-commit: - parallel: true - commands: - lint: - glob: "*.{js,ts,jsx,tsx}" - run: npx eslint {staged_files} - types: - glob: "*.{js,ts, jsx, tsx}" - run: npx tsc -commit-msg: - parallel: true - commands: - commitlint: - run: npx commitlint --edit +# pre-commit: +# parallel: true +# commands: +# lint: +# glob: "*.{js,ts,jsx,tsx}" +# run: npx eslint {staged_files} +# types: +# glob: "*.{js,ts, jsx, tsx}" +# run: npx tsc +# commit-msg: +# parallel: true +# commands: +# commitlint: +# run: npx commitlint --edit diff --git a/package.json b/package.json index 2968643..3531793 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "release": "release-it --only-version", "build": "npx react-native-builder-bob build", "build:plugin": "npx tsc --build plugin", + "start": "yarn workspace react-native-deepgram-example start", "build:pod": "cd example && cd ios && npx pod-install && cd ..", "example": "yarn workspace react-native-deepgram-example start", "example:ios": "yarn workspace react-native-deepgram-example ios", diff --git a/src/useDeepgramSpeechToText.ts b/src/useDeepgramSpeechToText.ts index 1152ece..866b87a 100644 --- a/src/useDeepgramSpeechToText.ts +++ b/src/useDeepgramSpeechToText.ts @@ -157,17 +157,10 @@ export function useDeepgramSpeechToText({ (ev: any) => { let chunk: ArrayBuffer | undefined; if (typeof ev?.b64 === 'string') { - const floatBytes = Uint8Array.from(atob(ev.b64), (c) => + const bytes = Uint8Array.from(atob(ev.b64), (c) => c.charCodeAt(0) ); - const float32 = new Float32Array(floatBytes.buffer); - const downsampled = float32.filter((_, i) => i % 3 === 0); - const int16 = new Int16Array(downsampled.length); - for (let i = 0; i < downsampled.length; i++) { - const s = Math.max(-1, Math.min(1, downsampled[i])); - int16[i] = s < 0 ? s * 0x8000 : s * 0x7fff; - } - chunk = int16.buffer; + chunk = bytes.buffer; } else if (Array.isArray(ev?.data)) { const bytes = new Uint8Array(ev.data.length); for (let i = 0; i < ev.data.length; i++) { diff --git a/src/useDeepgramVoiceAgent.ts b/src/useDeepgramVoiceAgent.ts index 07ca21e..31ad97a 100644 --- a/src/useDeepgramVoiceAgent.ts +++ b/src/useDeepgramVoiceAgent.ts @@ -25,8 +25,8 @@ import type { } from './types'; const DEFAULT_AGENT_ENDPOINT = 'wss://agent.deepgram.com/v1/agent/converse'; -const DEFAULT_INPUT_SAMPLE_RATE = 24_000; -const BASE_NATIVE_SAMPLE_RATE = 48_000; +const DEFAULT_INPUT_SAMPLE_RATE = 16_000; +const BASE_NATIVE_SAMPLE_RATE = 16_000; const eventName = Platform.select({ ios: 'DeepgramAudioPCM', @@ -336,13 +336,13 @@ export function useDeepgramVoiceAgent({ if (typeof ev?.b64 === 'string') { const binary = Uint8Array.from(atob(ev.b64), (c) => c.charCodeAt(0)); - const float32 = new Float32Array(binary.buffer); - const downsampled = - factor > 1 ? float32.filter((_, i) => i % factor === 0) : float32; - const int16 = new Int16Array(downsampled.length); - for (let i = 0; i < downsampled.length; i++) { - const sample = Math.max(-1, Math.min(1, downsampled[i])); - int16[i] = sample < 0 ? sample * 0x8000 : sample * 0x7fff; + let int16 = new Int16Array(binary.buffer); + if (factor > 1 && int16.length >= factor) { + const downsampled = new Int16Array(Math.floor(int16.length / factor)); + for (let i = 0; i < downsampled.length; i++) { + downsampled[i] = int16[i * factor]; + } + int16 = downsampled; } chunk = int16.buffer; } else if (Array.isArray(ev?.data)) { From fe5b374c1d4b3d9665a9c3653656a1f32b6f562d Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 13:17:50 -0500 Subject: [PATCH 04/10] Enhance Deepgram integration with improved audio handling and downsampling - Refactored audio session management in Deepgram.mm to ensure proper activation and deactivation based on app state. - Implemented a new downsampling mechanism in useDeepgramSpeechToText.ts and useDeepgramVoiceAgent.ts to handle varying sample rates more effectively. - Added logging throughout the Deepgram module for better debugging and monitoring of audio processing. - Cleaned up audio queue management to prevent memory leaks and ensure resources are released appropriately. - Updated package.json to ensure compatibility with the latest dependencies. --- .../project.pbxproj | 10 +- ios/Deepgram.mm | 749 ++++++++++++------ package.json | 2 +- src/useDeepgramSpeechToText.ts | 71 +- src/useDeepgramVoiceAgent.ts | 38 +- 5 files changed, 620 insertions(+), 250 deletions(-) diff --git a/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj index 5f55861..ec8f12e 100644 --- a/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeDeepgramExample.xcodeproj/project.pbxproj @@ -456,7 +456,10 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -511,7 +514,10 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_LDFLAGS = "$(inherited) "; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/ios/Deepgram.mm b/ios/Deepgram.mm index c2ea586..eed2254 100644 --- a/ios/Deepgram.mm +++ b/ios/Deepgram.mm @@ -1,15 +1,39 @@ #import +#import #import +#import +#import +#if TARGET_OS_IOS +#import +#endif +#include +#include + +#define DGNumberBuffers 3 + +@class Deepgram; + +typedef struct { + __unsafe_unretained Deepgram *mSelf; + AudioStreamBasicDescription dataFormat; + AudioQueueRef queue; + AudioQueueBufferRef buffers[DGNumberBuffers]; + UInt32 bufferByteSize; + SInt64 currentPacket; + bool isRunning; +} DGRecordState; @interface Deepgram : RCTEventEmitter +{ + DGRecordState _recordState; +} + // Recording -@property (nonatomic, strong) AVAudioEngine *recEngine; -@property (nonatomic, strong) AVAudioConverter *pcmConverter; -@property (nonatomic, strong) AVAudioFormat *targetPCMFormat; @property (nonatomic, strong) NSMutableData *pendingPCMBuffer; @property (nonatomic, strong) dispatch_queue_t emitterQueue; -@property (nonatomic, assign) double resampleRatio; @property (nonatomic, assign) NSUInteger chunkSizeBytes; +@property (atomic, assign) BOOL hasListeners; +@property (atomic, assign) BOOL appIsActive; // Playback / TTS @property (nonatomic, strong) AVAudioPlayer *audioPlayer; @@ -19,6 +43,52 @@ @interface Deepgram : RCTEventEmitter @property (nonatomic, assign) BOOL audioSessionConfigured; @end +@interface Deepgram (RecordingPrivate) +- (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData; +- (BOOL)activateAudioSession:(NSError **)outError; +- (BOOL)configureAudioSessionIfNeeded:(NSError **)outError; +- (BOOL)configureAudioSession:(NSError **)outError; +- (void)maybeDeactivateAudioSession; +@end + +static void DGHandleInputBuffer(void *inUserData, + __unused AudioQueueRef inAQ, + AudioQueueBufferRef inBuffer, + __unused const AudioTimeStamp *inStartTime, + __unused UInt32 inNumPackets, + __unused const AudioStreamPacketDescription *inPacketDesc) +{ + @autoreleasepool { + DGRecordState *state = (DGRecordState *)inUserData; + if (!state || !state->isRunning) { + NSLog(@"[Deepgram] DGHandleInputBuffer: inactive state"); + return; + } + + Deepgram *strongSelf = state->mSelf; + if (!strongSelf) { + NSLog(@"[Deepgram] DGHandleInputBuffer: missing self"); + return; + } + + if (!inBuffer || inBuffer->mAudioDataByteSize == 0) { + NSLog(@"[Deepgram] DGHandleInputBuffer: empty buffer"); + return; + } + + NSLog(@"[Deepgram] DGHandleInputBuffer: received %u bytes", + (unsigned int)inBuffer->mAudioDataByteSize); + NSData *data = [NSData dataWithBytes:inBuffer->mAudioData + length:inBuffer->mAudioDataByteSize]; + [strongSelf appendPCMDataAndEmitIfNeeded:data]; + + if (state->queue) { + NSLog(@"[Deepgram] DGHandleInputBuffer: re-enqueue buffer"); + AudioQueueEnqueueBuffer(state->queue, inBuffer, 0, NULL); + } + } +} + @implementation Deepgram RCT_EXPORT_MODULE(); @@ -29,6 +99,18 @@ + (BOOL)requiresMainQueueSetup { return NO; } return @[ @"DeepgramAudioPCM" ]; } +- (void)startObserving +{ + NSLog(@"[Deepgram] startObserving: listeners attached"); + self.hasListeners = YES; +} + +- (void)stopObserving +{ + NSLog(@"[Deepgram] stopObserving: listeners detached"); + self.hasListeners = NO; +} + /* ------------------------------------------------------------------ */ /* Helpers */ /* ------------------------------------------------------------------ */ @@ -36,200 +118,238 @@ - (instancetype)init { if (self = [super init]) { _chunkSizeBytes = 6400; // ≈200 ms of 16 kHz mono PCM16 audio + NSLog(@"[Deepgram] init: chunkSizeBytes=%lu", (unsigned long)_chunkSizeBytes); _emitterQueue = dispatch_queue_create("com.deepgram.liveaudiostream", DISPATCH_QUEUE_SERIAL); + memset(&_recordState, 0, sizeof(DGRecordState)); + _appIsActive = YES; +#if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleAudioRouteChange:) - name:AVAudioSessionRouteChangeNotification + selector:@selector(handleAppDidBecomeActive:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + NSLog(@"[Deepgram] init: registered for UIApplicationDidBecomeActiveNotification"); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(handleAppDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification object:nil]; + NSLog(@"[Deepgram] init: registered for UIApplicationDidEnterBackgroundNotification"); +#endif + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(handleAudioRouteChange:) + name:AVAudioSessionRouteChangeNotification + object:nil]; + NSLog(@"[Deepgram] init: registered for AVAudioSessionRouteChangeNotification"); } return self; } - (void)dealloc { + NSLog(@"[Deepgram] dealloc: cleaning up recording queue and removing observers"); + [self cleanupRecordingQueue]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)activateAudioSession +- (BOOL)activateAudioSession:(NSError **)outError { - if ([NSThread isMainThread]) { - [self configureAudioSessionIfNeeded]; + NSLog(@"[Deepgram] activateAudioSession: begin (configured=%@)", + self.audioSessionConfigured ? @"YES" : @"NO"); + __block BOOL success = YES; + __block NSError *activationError = nil; + + RCTUnsafeExecuteOnMainQueueSync(^{ + NSLog(@"[Deepgram] activateAudioSession: configuring on main queue"); + success = [self configureAudioSessionIfNeeded:&activationError]; + }); + + if (!success && activationError) { + NSLog(@"[Deepgram] Failed to activate audio session: %@", + activationError.localizedDescription ?: activationError); } else { - dispatch_sync(dispatch_get_main_queue(), ^{ - [self configureAudioSessionIfNeeded]; - }); + NSLog(@"[Deepgram] activateAudioSession: success=%@", success ? @"YES" : @"NO"); + } + + if (outError) { + *outError = activationError; } + + return success; } -- (void)configureAudioSessionIfNeeded +- (void)deactivateAudioSession +{ + NSLog(@"[Deepgram] deactivateAudioSession: begin"); + RCTUnsafeExecuteOnMainQueueSync(^{ + NSError *error = nil; + if (![[AVAudioSession sharedInstance] setActive:NO error:&error] && error) { + NSLog(@"[Deepgram] Failed to deactivate audio session: %@", + error.localizedDescription ?: error); + } + }); + self.audioSessionConfigured = NO; +} + +- (BOOL)configureAudioSessionIfNeeded:(NSError **)outError { if (!self.audioSessionConfigured) { - [self configureAudioSession]; - } else { - NSError *activeError = nil; - [[AVAudioSession sharedInstance] - setActive:YES - withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation - error:&activeError]; - if (activeError) { - NSLog(@"[Deepgram] Failed to keep audio session active: %@", - activeError.localizedDescription); + NSLog(@"[Deepgram] configureAudioSessionIfNeeded: not configured, configuring"); + return [self configureAudioSession:outError]; + } + + NSLog(@"[Deepgram] configureAudioSessionIfNeeded: already configured, ensuring active"); + NSError *activeError = nil; + BOOL success = [[AVAudioSession sharedInstance] setActive:YES error:&activeError]; + if (!success || activeError) { + self.audioSessionConfigured = NO; + if (outError) { + *outError = activeError; } } + return success; } -- (void)configureAudioSession +- (BOOL)configureAudioSession:(NSError **)outError { + NSLog(@"[Deepgram] configureAudioSession: begin"); AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *error = nil; - AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDefaultToSpeaker; + AVAudioSessionCategoryOptions options = AVAudioSessionCategoryOptionDuckOthers | + AVAudioSessionCategoryOptionDefaultToSpeaker | + AVAudioSessionCategoryOptionAllowBluetooth; + + if (@available(iOS 10.0, *)) { + NSLog(@"[Deepgram] configureAudioSession: enabling AirPlay option"); + options |= AVAudioSessionCategoryOptionAllowAirPlay; + } + + BOOL categorySuccess = NO; if (@available(iOS 10.0, *)) { - options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; + NSLog(@"[Deepgram] configureAudioSession: setCategory PlayAndRecord voice chat (iOS10+)"); + categorySuccess = [session setCategory:AVAudioSessionCategoryPlayAndRecord + mode:AVAudioSessionModeVoiceChat + options:options + error:&error]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - options |= AVAudioSessionCategoryOptionAllowBluetooth; + NSLog(@"[Deepgram] configureAudioSession: setCategory legacy path"); + categorySuccess = [session setCategory:AVAudioSessionCategoryPlayAndRecord + withOptions:options + error:&error]; #pragma clang diagnostic pop + if (categorySuccess && !error) { + NSError *modeError = nil; + categorySuccess = [session setMode:AVAudioSessionModeVoiceChat error:&modeError] && !modeError; + if (modeError) { + error = modeError; + } + } } - if (@available(iOS 10.0, *)) { - error = nil; - BOOL ok = [session setCategory:AVAudioSessionCategoryPlayAndRecord - mode:AVAudioSessionModeVoiceChat - options:options - error:&error]; - if (!ok || error) { - NSLog(@"[Deepgram] Failed to set audio session category: %@", - error.localizedDescription); - } - } else { - BOOL ok = [session setCategory:AVAudioSessionCategoryPlayAndRecord - withOptions:options - error:&error]; - if (!ok || error) { - NSLog(@"[Deepgram] Failed to set audio session category: %@", - error.localizedDescription); + if (!categorySuccess || error) { + NSLog(@"[Deepgram] configureAudioSession: category failed error=%@", + error.localizedDescription ?: error); + if (outError) { + *outError = error; } + self.audioSessionConfigured = NO; + return NO; + } - error = nil; - BOOL modeOk = [session setMode:AVAudioSessionModeVoiceChat error:&error]; - if (!modeOk || error) { - NSLog(@"[Deepgram] Failed to set audio session mode: %@", - error.localizedDescription); + NSError *activeError = nil; + BOOL activeSuccess = [session setActive:YES error:&activeError]; + if (!activeSuccess || activeError) { + NSLog(@"[Deepgram] configureAudioSession: setActive failed error=%@", + activeError.localizedDescription ?: activeError); + if (outError) { + *outError = activeError; } + self.audioSessionConfigured = NO; + return NO; } - error = nil; - if (![session setPreferredSampleRate:48000 error:&error] && error) { - NSLog(@"[Deepgram] Failed to set preferred sample rate: %@", - error.localizedDescription); - } + self.audioSessionConfigured = YES; + NSLog(@"[Deepgram] configureAudioSession: success"); + return YES; +} - error = nil; - if (![session setPreferredIOBufferDuration:0.01 error:&error] && error) { - NSLog(@"[Deepgram] Failed to set IO buffer duration: %@", - error.localizedDescription); +- (void)maybeDeactivateAudioSession +{ + NSLog(@"[Deepgram] maybeDeactivateAudioSession: running=%@ playing=%@", + _recordState.isRunning ? @"YES" : @"NO", + self.isPlaying ? @"YES" : @"NO"); + if (!_recordState.isRunning && !self.isPlaying) { + NSLog(@"[Deepgram] maybeDeactivateAudioSession: deactivating"); + [self deactivateAudioSession]; } +} - if (@available(iOS 10.0, *)) { - error = nil; - if (![session setPreferredInputNumberOfChannels:1 error:&error] && error) { - NSLog(@"[Deepgram] Failed to set preferred input channels: %@", - error.localizedDescription); - } - - error = nil; - if (![session setPreferredOutputNumberOfChannels:1 error:&error] && error) { - NSLog(@"[Deepgram] Failed to set preferred output channels: %@", - error.localizedDescription); - } - } +- (void)handleAudioRouteChange:(NSNotification *)note +{ + NSLog(@"[Deepgram] handleAudioRouteChange: %@", note.userInfo); + NSNumber *reasonValue = note.userInfo[AVAudioSessionRouteChangeReasonKey]; + AVAudioSessionRouteChangeReason reason = reasonValue + ? (AVAudioSessionRouteChangeReason) + reasonValue.unsignedIntegerValue + : AVAudioSessionRouteChangeReasonUnknown; - [self routeToBuiltInMic:session]; + BOOL causedByCategory = + reason == AVAudioSessionRouteChangeReasonCategoryChange || + reason == AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory; - error = nil; - if (![session setActive:YES - withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation - error:&error] && error) { - NSLog(@"[Deepgram] Failed to activate audio session: %@", - error.localizedDescription); + if (!causedByCategory) { + self.audioSessionConfigured = NO; } - error = nil; - if (![session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker - error:&error] && error) { - NSLog(@"[Deepgram] Failed to force speaker output: %@", - error.localizedDescription); + if (!_recordState.isRunning && !self.isPlaying) { + NSLog(@"[Deepgram] handleAudioRouteChange: ignoring (inactive)"); + return; } - self.audioSessionConfigured = YES; -} - -- (void)routeToBuiltInMic:(AVAudioSession *)session -{ - if (!session) { + if (!self.appIsActive) { + NSLog(@"[Deepgram] handleAudioRouteChange: app inactive, skipping"); return; } - for (AVAudioSessionPortDescription *input in session.currentRoute.inputs) { - NSString *portType = input.portType; - BOOL isBluetoothLE = NO; - if (@available(iOS 10.0, *)) { - isBluetoothLE = [portType isEqualToString:AVAudioSessionPortBluetoothLE]; - } - - if ([portType isEqualToString:AVAudioSessionPortBluetoothHFP] || - [portType isEqualToString:AVAudioSessionPortHeadsetMic] || - isBluetoothLE) { - return; - } + if (causedByCategory) { + NSLog(@"[Deepgram] handleAudioRouteChange: category change detected, keeping session"); + return; } - for (AVAudioSessionPortDescription *port in session.availableInputs) { - if ([port.portType isEqualToString:AVAudioSessionPortBuiltInMic]) { - NSError *error = nil; - if (![session setPreferredInput:port error:&error] && error) { - NSLog(@"[Deepgram] Failed to pin built-in mic: %@", - error.localizedDescription); - } + NSLog(@"[Deepgram] handleAudioRouteChange: reactivating session"); + [self activateAudioSession:NULL]; +} - if (@available(iOS 10.0, *)) { - AVAudioSessionDataSourceDescription *front = nil; - for (AVAudioSessionDataSourceDescription *source in port.dataSources) { - if ([source.orientation isEqualToString:AVAudioSessionOrientationFront]) { - front = source; - break; - } - } - - if (front) { - NSError *dataSourceError = nil; - if (![port setPreferredDataSource:front error:&dataSourceError] && - dataSourceError) { - NSLog(@"[Deepgram] Failed to set mic data source: %@", - dataSourceError.localizedDescription); - } - } - } +#if TARGET_OS_IOS +- (void)handleAppDidBecomeActive:(NSNotification *)note +{ + NSLog(@"[Deepgram] handleAppDidBecomeActive: %@", note.userInfo); + self.appIsActive = YES; - break; - } + if (_recordState.isRunning || self.isPlaying) { + NSLog(@"[Deepgram] handleAppDidBecomeActive: reactivating session"); + [self activateAudioSession:NULL]; } } -- (void)handleAudioRouteChange:(NSNotification *)note +- (void)handleAppDidEnterBackground:(NSNotification *)note { - self.audioSessionConfigured = NO; - [self activateAudioSession]; + NSLog(@"[Deepgram] handleAppDidEnterBackground: %@", note.userInfo); + self.appIsActive = NO; + [self maybeDeactivateAudioSession]; } +#endif - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate { if (!chunk || chunk.length == 0) { + NSLog(@"[Deepgram] emitPCMChunk: empty chunk, skipping"); return; } @@ -238,9 +358,24 @@ - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate dispatch_queue_t queue = self.emitterQueue ?: dispatch_get_main_queue(); dispatch_async(queue, ^{ if (!weakSelf) { + NSLog(@"[Deepgram] emitPCMChunk: self released, aborting"); + return; + } + + if (!weakSelf.hasListeners) { + NSLog(@"[Deepgram] emitPCMChunk: no listeners, dropping %lu bytes", + (unsigned long)chunkCopy.length); + return; + } + + if (!weakSelf.bridge || !weakSelf.callableJSModules) { + NSLog(@"[Deepgram] Skipping DeepgramAudioPCM event (bridge not ready)"); return; } + NSLog(@"[Deepgram] emitPCMChunk: sending %lu bytes sampleRate=%d", + (unsigned long)chunkCopy.length, + sampleRate); NSString *b64 = [chunkCopy base64EncodedStringWithOptions:0]; [weakSelf sendEventWithName:@"DeepgramAudioPCM" body:@{ @"b64": b64, @"sampleRate": @(sampleRate) }]; @@ -250,13 +385,18 @@ - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData { if (!pcmData || pcmData.length == 0) { + NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: empty PCM, skipping"); return; } if (!self.pendingPCMBuffer) { + NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: allocate pending buffer"); self.pendingPCMBuffer = [[NSMutableData alloc] init]; } + NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: append %lu bytes (pending=%lu)", + (unsigned long)pcmData.length, + (unsigned long)self.pendingPCMBuffer.length); [self.pendingPCMBuffer appendData:pcmData]; NSUInteger chunkSize = self.chunkSizeBytes > 0 ? self.chunkSizeBytes : pcmData.length; @@ -267,6 +407,9 @@ - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData [self.pendingPCMBuffer replaceBytesInRange:NSMakeRange(0, chunkSize) withBytes:NULL length:0]; + NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: emitting chunk %lu bytes remaining=%lu", + (unsigned long)chunk.length, + (unsigned long)self.pendingPCMBuffer.length); [self emitPCMChunk:chunk sampleRate:self.currentSampleRate]; } } @@ -274,14 +417,43 @@ - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData - (void)flushPendingPCM { if (self.pendingPCMBuffer.length == 0) { + NSLog(@"[Deepgram] flushPendingPCM: nothing to flush"); return; } NSData *remaining = [self.pendingPCMBuffer copy]; [self.pendingPCMBuffer setLength:0]; + NSLog(@"[Deepgram] flushPendingPCM: flushing %lu bytes", + (unsigned long)remaining.length); [self emitPCMChunk:remaining sampleRate:self.currentSampleRate]; } +- (void)cleanupRecordingQueue +{ + NSLog(@"[Deepgram] cleanupRecordingQueue: begin"); + _recordState.isRunning = false; + + if (_recordState.queue) { + NSLog(@"[Deepgram] cleanupRecordingQueue: stopping queue"); + AudioQueueStop(_recordState.queue, true); + + for (int i = 0; i < DGNumberBuffers; i++) { + if (_recordState.buffers[i]) { + NSLog(@"[Deepgram] cleanupRecordingQueue: freeing buffer %d", i); + AudioQueueFreeBuffer(_recordState.queue, _recordState.buffers[i]); + _recordState.buffers[i] = NULL; + } + } + + NSLog(@"[Deepgram] cleanupRecordingQueue: disposing queue"); + AudioQueueDispose(_recordState.queue, true); + } + + memset(&_recordState, 0, sizeof(DGRecordState)); + NSLog(@"[Deepgram] cleanupRecordingQueue: state cleared"); + [self maybeDeactivateAudioSession]; +} + /* ================================================================== */ /* 1. MICROPHONE CAPTURE (16 kHz PCM16 emission) */ /* ================================================================== */ @@ -290,91 +462,146 @@ - (void)flushPendingPCM rejecter:(RCTPromiseRejectBlock)reject) { @try { - if (!self.recEngine) self.recEngine = [[AVAudioEngine alloc] init]; - [self activateAudioSession]; - - AVAudioInputNode *input = self.recEngine.inputNode; - AVAudioFormat *hwFmt = [input outputFormatForBus:0]; + NSLog(@"[Deepgram] startRecording: begin"); + self.currentSampleRate = 16000; + NSLog(@"[Deepgram] startRecording: targetSampleRate=%d", self.currentSampleRate); + NSError *sessionError = nil; + if (![self activateAudioSession:&sessionError]) { + NSString *message = sessionError.localizedDescription ?: @"Failed to activate audio session"; + NSLog(@"[Deepgram] startRecording: activation failed %@", message); + if (reject) reject(@"record_start_error", message, sessionError); + return; + } - [input removeTapOnBus:0]; + if (_recordState.isRunning) { + NSLog(@"[Deepgram] startRecording: record state already running, cleaning up"); + [self cleanupRecordingQueue]; + } + NSLog(@"[Deepgram] startRecording: resetting buffers"); self.pendingPCMBuffer = [[NSMutableData alloc] init]; - double targetSampleRate = 16000.0; - AVAudioChannelCount targetChannels = 1; - self.targetPCMFormat = [[AVAudioFormat alloc] - initWithCommonFormat:AVAudioPCMFormatInt16 - sampleRate:targetSampleRate - channels:targetChannels - interleaved:NO]; - - self.pcmConverter = [[AVAudioConverter alloc] initFromFormat:hwFmt - toFormat:self.targetPCMFormat]; - self.resampleRatio = hwFmt.sampleRate > 0 ? targetSampleRate / hwFmt.sampleRate : 1.0; - if (self.resampleRatio <= 0) { - self.resampleRatio = 1.0; + memset(&_recordState, 0, sizeof(DGRecordState)); + _recordState.mSelf = self; + + _recordState.dataFormat.mSampleRate = self.currentSampleRate; + _recordState.dataFormat.mChannelsPerFrame = 1; + _recordState.dataFormat.mBitsPerChannel = 16; + _recordState.dataFormat.mBytesPerPacket = + (_recordState.dataFormat.mBitsPerChannel / 8) * + _recordState.dataFormat.mChannelsPerFrame; + _recordState.dataFormat.mBytesPerFrame = + _recordState.dataFormat.mBytesPerPacket; + _recordState.dataFormat.mFramesPerPacket = 1; + _recordState.dataFormat.mReserved = 0; + _recordState.dataFormat.mFormatID = kAudioFormatLinearPCM; + _recordState.dataFormat.mFormatFlags = + kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked; + _recordState.currentPacket = 0; + _recordState.isRunning = true; + + const double targetSecondsPerChunk = 0.2; // ≈200 ms of audio + UInt32 defaultChunkSize = + (UInt32)MAX(1, (int)round(_recordState.dataFormat.mSampleRate * + _recordState.dataFormat.mBytesPerFrame * + targetSecondsPerChunk)); + self.chunkSizeBytes = defaultChunkSize; + _recordState.bufferByteSize = (UInt32)self.chunkSizeBytes; + NSLog(@"[Deepgram] startRecording: bufferByteSize=%u (chunkSize=%lu)", + (unsigned int)_recordState.bufferByteSize, + (unsigned long)self.chunkSizeBytes); + + OSStatus status = AudioQueueNewInput(&_recordState.dataFormat, + DGHandleInputBuffer, + &_recordState, + NULL, + NULL, + 0, + &_recordState.queue); + + if (status != noErr) { + NSString *message = + [NSString stringWithFormat:@"AudioQueueNewInput failed: %d", (int)status]; + NSLog(@"[Deepgram] startRecording: %@", message); + [self cleanupRecordingQueue]; + if (reject) reject(@"record_start_error", message, nil); + return; + } + + AudioStreamBasicDescription actualFormat; + UInt32 actualFormatSize = sizeof(actualFormat); + status = AudioQueueGetProperty(_recordState.queue, + kAudioQueueProperty_StreamDescription, + &actualFormat, + &actualFormatSize); + if (status == noErr) { + int resolvedSampleRate = (int)llround(actualFormat.mSampleRate); + NSLog(@"[Deepgram] startRecording: actual sampleRate=%f (%d)", + actualFormat.mSampleRate, + resolvedSampleRate); + if (resolvedSampleRate > 0) { + self.currentSampleRate = resolvedSampleRate; + } + + UInt32 bytesPerFrame = actualFormat.mBytesPerFrame + ? actualFormat.mBytesPerFrame + : _recordState.dataFormat.mBytesPerFrame; + UInt32 adaptiveChunk = + (UInt32)MAX(1, (int)round(actualFormat.mSampleRate * bytesPerFrame * targetSecondsPerChunk)); + if (adaptiveChunk > 0 && adaptiveChunk != self.chunkSizeBytes) { + self.chunkSizeBytes = adaptiveChunk; + _recordState.bufferByteSize = adaptiveChunk; + NSLog(@"[Deepgram] startRecording: adjusted chunkSize=%lu", + (unsigned long)self.chunkSizeBytes); + } + } else { + NSLog(@"[Deepgram] startRecording: failed to read stream description %d", (int)status); + } + + for (int i = 0; i < DGNumberBuffers; i++) { + status = AudioQueueAllocateBuffer(_recordState.queue, + _recordState.bufferByteSize, + &_recordState.buffers[i]); + if (status != noErr) { + NSString *message = [NSString + stringWithFormat:@"AudioQueueAllocateBuffer failed: %d", (int)status]; + NSLog(@"[Deepgram] startRecording: %@", message); + [self cleanupRecordingQueue]; + if (reject) reject(@"record_start_error", message, nil); + return; + } + + status = AudioQueueEnqueueBuffer(_recordState.queue, + _recordState.buffers[i], + 0, + NULL); + if (status != noErr) { + NSString *message = [NSString + stringWithFormat:@"AudioQueueEnqueueBuffer failed: %d", (int)status]; + NSLog(@"[Deepgram] startRecording: %@", message); + [self cleanupRecordingQueue]; + if (reject) reject(@"record_start_error", message, nil); + return; + } } - self.currentSampleRate = (int)targetSampleRate; - - __weak __typeof(self) weakSelf = self; - [input installTapOnBus:0 - bufferSize:1024 - format:hwFmt - block:^(AVAudioPCMBuffer *buf, __unused AVAudioTime *when) { - if (!weakSelf) return; - if (!weakSelf.pcmConverter || !weakSelf.targetPCMFormat) { - NSLog(@"[Deepgram] PCM converter unavailable; dropping frame"); - return; - } - - AVAudioFrameCount inputFrames = buf.frameLength; - if (inputFrames == 0) { - return; - } - - double ratio = weakSelf.resampleRatio > 0 ? weakSelf.resampleRatio : 1.0; - AVAudioFrameCount outputCapacity = - (AVAudioFrameCount)ceil(inputFrames * ratio) + 32; - - AVAudioPCMBuffer *converted = [[AVAudioPCMBuffer alloc] - initWithPCMFormat:weakSelf.targetPCMFormat - frameCapacity:outputCapacity]; - - if (!converted) { - return; - } - - buf.frameLength = inputFrames; - - NSError *conversionError = nil; - BOOL success = [weakSelf.pcmConverter convertToBuffer:converted - fromBuffer:buf - error:&conversionError]; - - if (!success || conversionError) { - NSLog(@"[Deepgram] PCM conversion error: %@", - conversionError.localizedDescription); - return; - } - - converted.frameLength = MIN(converted.frameLength, outputCapacity); - if (converted.frameLength == 0) { - return; - } - - AudioBuffer audioBuffer = converted.audioBufferList->mBuffers[0]; - NSData *pcmData = [NSData dataWithBytes:audioBuffer.mData - length:audioBuffer.mDataByteSize]; - [weakSelf appendPCMDataAndEmitIfNeeded:pcmData]; - }]; - - [self.recEngine prepare]; - [self.recEngine startAndReturnError:nil]; - resolve(nil); + status = AudioQueueStart(_recordState.queue, NULL); + if (status != noErr) { + NSString *message = + [NSString stringWithFormat:@"AudioQueueStart failed: %d", (int)status]; + NSLog(@"[Deepgram] startRecording: %@", message); + [self cleanupRecordingQueue]; + if (reject) reject(@"record_start_error", message, nil); + return; + } + + NSLog(@"[Deepgram] startRecording: success"); + if (resolve) resolve(nil); } @catch (NSException *e) { - reject(@"record_start_error", e.reason, nil); + NSLog(@"[Deepgram] startRecording: exception %@", e); + [self cleanupRecordingQueue]; + if (reject) reject(@"record_start_error", e.reason, nil); } } @@ -383,18 +610,16 @@ - (void)flushPendingPCM rejecter:(RCTPromiseRejectBlock)reject) { @try { - [self.recEngine.inputNode removeTapOnBus:0]; - [self.recEngine stop]; - self.recEngine = nil; + NSLog(@"[Deepgram] stopRecording: begin"); + [self cleanupRecordingQueue]; [self flushPendingPCM]; - self.pcmConverter = nil; - self.targetPCMFormat = nil; self.pendingPCMBuffer = nil; - self.resampleRatio = 1.0; - resolve(nil); + NSLog(@"[Deepgram] stopRecording: finished"); + if (resolve) resolve(nil); } @catch (NSException *e) { - reject(@"record_stop_error", e.reason, nil); + NSLog(@"[Deepgram] stopRecording: exception %@", e); + if (reject) reject(@"record_stop_error", e.reason, nil); } } @@ -403,16 +628,27 @@ - (void)flushPendingPCM rejecter:(RCTPromiseRejectBlock)reject) { @try { - [self activateAudioSession]; + NSLog(@"[Deepgram] startAudio: begin"); + NSError *sessionError = nil; + if (![self activateAudioSession:&sessionError]) { + NSString *message = sessionError.localizedDescription ?: @"Failed to activate audio session"; + NSLog(@"[Deepgram] startAudio: activation failed %@", message); + if (reject) reject(@"audio_start_error", message, sessionError); + return; + } if (!self.audioBuffer) { self.audioBuffer = [[NSMutableData alloc] init]; + NSLog(@"[Deepgram] startAudio: created audioBuffer"); } if (self.currentSampleRate <= 0) { self.currentSampleRate = 16000; + NSLog(@"[Deepgram] startAudio: default sample rate applied %d", self.currentSampleRate); } + NSLog(@"[Deepgram] startAudio: success"); if (resolve) resolve(nil); } @catch (NSException *e) { + NSLog(@"[Deepgram] startAudio: exception %@", e); if (reject) reject(@"audio_start_error", e.reason, nil); } } @@ -422,10 +658,13 @@ - (void)flushPendingPCM rejecter:(RCTPromiseRejectBlock)reject) { @try { + NSLog(@"[Deepgram] stopAudio: begin"); [self stopPlayer:nil rejecter:nil]; + NSLog(@"[Deepgram] stopAudio: success"); if (resolve) resolve(nil); } @catch (NSException *e) { + NSLog(@"[Deepgram] stopAudio: exception %@", e); if (reject) reject(@"audio_stop_error", e.reason, nil); } } @@ -478,12 +717,22 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa :(nonnull NSNumber *)sampleRate channels:(nonnull NSNumber *)channels) { + NSLog(@"[Deepgram] startPlayer: begin sampleRate=%@ channels=%@", + sampleRate, + channels); [self stopPlayer:nil rejecter:nil]; - [self activateAudioSession]; - + NSError *sessionError = nil; + if (![self activateAudioSession:&sessionError]) { + NSLog(@"[Deepgram] Unable to activate audio session for playback: %@", + sessionError.localizedDescription ?: sessionError); + return; + } + self.audioBuffer = [[NSMutableData alloc] init]; self.isPlaying = NO; self.currentSampleRate = sampleRate.intValue; + NSLog(@"[Deepgram] startPlayer: initialized buffer sampleRate=%d", + self.currentSampleRate); } /** @@ -491,20 +740,27 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa */ RCT_EXPORT_METHOD(feedAudio:(NSString *)b64) { + NSLog(@"[Deepgram] feedAudio: begin length=%lu", (unsigned long)b64.length); if (!self.audioBuffer) { // Use default sample rate if not set int defaultSampleRate = self.currentSampleRate > 0 ? self.currentSampleRate : 16000; + NSLog(@"[Deepgram] feedAudio: auto startPlayer defaultSampleRate=%d", defaultSampleRate); [self startPlayer:@(defaultSampleRate) channels:@1]; } - + NSData *pcmData = [[NSData alloc] initWithBase64EncodedString:b64 options:0]; if (!pcmData || pcmData.length == 0) { + NSLog(@"[Deepgram] feedAudio: decoded data empty"); return; } - + [self.audioBuffer appendData:pcmData]; - + NSLog(@"[Deepgram] feedAudio: appended %lu bytes (buffer=%lu)", + (unsigned long)pcmData.length, + (unsigned long)self.audioBuffer.length); + if (!self.isPlaying && self.audioBuffer.length > 1000) { + NSLog(@"[Deepgram] feedAudio: triggering playback"); [self playAccumulatedAudio]; } } @@ -514,28 +770,32 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa */ - (void)playAccumulatedAudio { if (!self.audioBuffer || self.audioBuffer.length == 0) { + NSLog(@"[Deepgram] playAccumulatedAudio: no buffered audio"); return; } - + NSData *wavData = [self createWAVHeaderForPCMData:self.audioBuffer sampleRate:self.currentSampleRate]; - + NSError *error = nil; AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:wavData error:&error]; - + if (error) { + NSLog(@"[Deepgram] playAccumulatedAudio: player error=%@", error); return; } - + self.audioPlayer = player; self.audioPlayer.delegate = self; - + self.isPlaying = YES; [player prepareToPlay]; BOOL success = [player play]; - + if (success) { + NSLog(@"[Deepgram] playAccumulatedAudio: playback started, clearing buffer"); [self.audioBuffer setLength:0]; } else { + NSLog(@"[Deepgram] playAccumulatedAudio: failed to start playback"); self.isPlaying = NO; } } @@ -544,17 +804,20 @@ - (void)playAccumulatedAudio { * Play PCM data immediately using AVAudioPlayer. */ - (void)playPCMData:(NSData *)pcmData { + NSLog(@"[Deepgram] playPCMData: begin length=%lu", (unsigned long)pcmData.length); NSData *wavData = [self createWAVHeaderForPCMData:pcmData sampleRate:self.currentSampleRate]; - + NSError *error = nil; AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:wavData error:&error]; - + if (error) { + NSLog(@"[Deepgram] playPCMData: player error=%@", error); return; } - + self.audioPlayer = player; [player prepareToPlay]; + NSLog(@"[Deepgram] playPCMData: playing"); [player play]; } @@ -566,20 +829,24 @@ - (void)playPCMData:(NSData *)pcmData { rejecter:(RCTPromiseRejectBlock)reject) { @try { + NSLog(@"[Deepgram] stopPlayer: begin"); if (self.audioPlayer) { [self.audioPlayer stop]; + NSLog(@"[Deepgram] stopPlayer: stopped audioPlayer"); self.audioPlayer = nil; } - + self.audioBuffer = nil; self.isPlaying = NO; - - + [self maybeDeactivateAudioSession]; + + NSLog(@"[Deepgram] stopPlayer: success"); if (resolve) resolve(nil); } @catch (NSException *e) { NSLog(@"[Deepgram] Error stopping player: %@", e.reason); + NSLog(@"[Deepgram] stopPlayer: exception %@", e); if (reject) reject(@"player_stop_error", e.reason, nil); } } @@ -591,6 +858,7 @@ - (void)playPCMData:(NSData *)pcmData { :(nonnull NSNumber *)sampleRate channels:(nonnull NSNumber *)channels) { + NSLog(@"[Deepgram] setAudioConfig: sampleRate=%@ channels=%@", sampleRate, channels); [self startPlayer:sampleRate channels:channels]; } @@ -603,17 +871,21 @@ - (void)playPCMData:(NSData *)pcmData { rejecter:(RCTPromiseRejectBlock)reject) { @try { + NSLog(@"[Deepgram] playAudioChunk: begin"); NSData *pcmData = [[NSData alloc] initWithBase64EncodedString:chunk options:0]; if (!pcmData) { + NSLog(@"[Deepgram] playAudioChunk: failed to decode base64"); if (reject) reject(@"invalid_data", @"Failed to decode audio data", nil); return; } - + [self playPCMData:pcmData]; - + + NSLog(@"[Deepgram] playAudioChunk: success"); if (resolve) resolve(nil); } @catch (NSException *e) { + NSLog(@"[Deepgram] playAudioChunk: exception %@", e); if (reject) reject(@"playback_error", e.reason, nil); } } @@ -623,14 +895,17 @@ - (void)playPCMData:(NSData *)pcmData { /* ================================================================== */ - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { + NSLog(@"[Deepgram] audioPlayerDidFinishPlaying: success=%@", flag ? @"YES" : @"NO"); self.isPlaying = NO; - + if (self.audioBuffer && self.audioBuffer.length > 0) { + NSLog(@"[Deepgram] audioPlayerDidFinishPlaying: playing remaining buffered audio"); [self playAccumulatedAudio]; } } - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error { + NSLog(@"[Deepgram] audioPlayerDecodeErrorDidOccur: error=%@", error); self.isPlaying = NO; } diff --git a/package.json b/package.json index 3531793..7a5579b 100644 --- a/package.json +++ b/package.json @@ -225,4 +225,4 @@ "metro-symbolicate": "0.81.5", "metro-transform-worker": "0.81.5" } -} \ No newline at end of file +} diff --git a/src/useDeepgramSpeechToText.ts b/src/useDeepgramSpeechToText.ts index 866b87a..dcbf7e4 100644 --- a/src/useDeepgramSpeechToText.ts +++ b/src/useDeepgramSpeechToText.ts @@ -16,6 +16,35 @@ import { } from './constants'; import { buildParams } from './helpers'; +const DEFAULT_SAMPLE_RATE = 16_000; +const BASE_NATIVE_SAMPLE_RATE = 16_000; + +const computeDownsampleFactor = ( + target: number | undefined, + base: number = BASE_NATIVE_SAMPLE_RATE +) => { + if (!target || target >= base || base <= 0) { + return 1; + } + const ratio = Math.round(base / target); + return ratio > 0 ? ratio : 1; +}; + +const downsampleInt16 = ( + data: Int16Array, + factor: number +): Int16Array => { + if (factor <= 1 || data.length < factor) { + return data; + } + + const downsampled = new Int16Array(Math.floor(data.length / factor)); + for (let i = 0; i < downsampled.length; i++) { + downsampled[i] = data[i * factor]; + } + return downsampled as Int16Array; +}; + export function useDeepgramSpeechToText({ onBeforeStart = () => {}, onStart = () => {}, @@ -33,10 +62,16 @@ export function useDeepgramSpeechToText({ null ); const apiVersionRef = useRef<'v1' | 'v2'>('v1'); + const nativeInputSampleRateRef = useRef(BASE_NATIVE_SAMPLE_RATE); + const targetSampleRateRef = useRef(DEFAULT_SAMPLE_RATE); + const downsampleFactorRef = useRef(1); const closeEverything = () => { audioSub.current?.remove(); Deepgram.stopRecording().catch(() => {}); + nativeInputSampleRateRef.current = BASE_NATIVE_SAMPLE_RATE; + targetSampleRateRef.current = DEFAULT_SAMPLE_RATE; + downsampleFactorRef.current = 1; if ( apiVersionRef.current === 'v2' && ws.current?.readyState === WebSocket.OPEN @@ -67,13 +102,22 @@ export function useDeepgramSpeechToText({ const merged: DeepgramLiveListenOptions = { encoding: 'linear16', - sampleRate: 16000, + sampleRate: DEFAULT_SAMPLE_RATE, model: 'nova-2', apiVersion: 'v1', ...live, ...overrideOptions, }; + targetSampleRateRef.current = + typeof merged.sampleRate === 'number' && merged.sampleRate > 0 + ? merged.sampleRate + : DEFAULT_SAMPLE_RATE; + downsampleFactorRef.current = computeDownsampleFactor( + targetSampleRateRef.current, + nativeInputSampleRateRef.current + ); + if (merged.apiVersion === 'v2' && !merged.model) { merged.model = 'flux-general-en'; } @@ -155,12 +199,27 @@ export function useDeepgramSpeechToText({ android: 'AudioChunk', }) as string, (ev: any) => { - let chunk: ArrayBuffer | undefined; + if (typeof ev?.sampleRate === 'number' && ev.sampleRate > 0) { + if (ev.sampleRate !== nativeInputSampleRateRef.current) { + nativeInputSampleRateRef.current = ev.sampleRate; + downsampleFactorRef.current = computeDownsampleFactor( + targetSampleRateRef.current, + nativeInputSampleRateRef.current + ); + } + } + + const factor = downsampleFactorRef.current; + let chunk: ArrayBufferLike | undefined; if (typeof ev?.b64 === 'string') { const bytes = Uint8Array.from(atob(ev.b64), (c) => c.charCodeAt(0) ); - chunk = bytes.buffer; + let int16: Int16Array = new Int16Array( + bytes.buffer + ); + int16 = downsampleInt16(int16, factor); + chunk = int16.buffer; } else if (Array.isArray(ev?.data)) { const bytes = new Uint8Array(ev.data.length); for (let i = 0; i < ev.data.length; i++) { @@ -172,7 +231,11 @@ export function useDeepgramSpeechToText({ for (let i = 0; i < int16.length; i++) { int16[i] = view.getInt16(i * 2, true); } - chunk = int16.buffer; + const downsampled = downsampleInt16( + int16 as Int16Array, + factor + ); + chunk = downsampled.buffer; } if (chunk && ws.current?.readyState === WebSocket.OPEN) { diff --git a/src/useDeepgramVoiceAgent.ts b/src/useDeepgramVoiceAgent.ts index 31ad97a..692238d 100644 --- a/src/useDeepgramVoiceAgent.ts +++ b/src/useDeepgramVoiceAgent.ts @@ -58,11 +58,14 @@ const hasKeys = (value: unknown, keys: string[]) => value !== null && keys.every((key) => key in (value as Record)); -const computeDownsampleFactor = (target: number | undefined) => { - if (!target || target >= BASE_NATIVE_SAMPLE_RATE) { +const computeDownsampleFactor = ( + target: number | undefined, + base: number = BASE_NATIVE_SAMPLE_RATE +) => { + if (!target || target >= base || base <= 0) { return 1; } - const ratio = Math.round(BASE_NATIVE_SAMPLE_RATE / target); + const ratio = Math.round(base / target); return ratio > 0 ? ratio : 1; }; @@ -162,8 +165,14 @@ export function useDeepgramVoiceAgent({ const audioSub = useRef | null>( null ); + const nativeInputSampleRate = useRef(BASE_NATIVE_SAMPLE_RATE); + const targetInputSampleRate = useRef(DEFAULT_INPUT_SAMPLE_RATE); const currentDownsample = useRef( - downsampleFactor ?? computeDownsampleFactor(DEFAULT_INPUT_SAMPLE_RATE) + downsampleFactor ?? + computeDownsampleFactor( + targetInputSampleRate.current, + nativeInputSampleRate.current + ) ); const microphoneActive = useRef(false); const defaultSettingsRef = useRef(defaultSettings); @@ -331,6 +340,18 @@ export function useDeepgramVoiceAgent({ return; } + if (typeof ev?.sampleRate === 'number' && ev.sampleRate > 0) { + if (ev.sampleRate !== nativeInputSampleRate.current) { + nativeInputSampleRate.current = ev.sampleRate; + if (downsampleFactor == null) { + currentDownsample.current = computeDownsampleFactor( + targetInputSampleRate.current, + nativeInputSampleRate.current + ); + } + } + } + const factor = currentDownsample.current ?? 1; let chunk: ArrayBuffer | null = null; @@ -369,7 +390,7 @@ export function useDeepgramVoiceAgent({ onErrorRef.current?.(err); } }, - [onErrorRef] + [downsampleFactor, onErrorRef] ); const handleSocketMessage = useCallback( @@ -616,8 +637,13 @@ export function useDeepgramVoiceAgent({ overrideSettings?.audio?.input?.sample_rate ?? defaultSettingsRef.current?.audio?.input?.sample_rate ?? DEFAULT_INPUT_SAMPLE_RATE; + targetInputSampleRate.current = targetSampleRate; currentDownsample.current = - downsampleFactor ?? computeDownsampleFactor(targetSampleRate); + downsampleFactor ?? + computeDownsampleFactor( + targetInputSampleRate.current, + nativeInputSampleRate.current + ); const socket = new (WebSocket as any)(endpointRef.current, undefined, { headers: { Authorization: `Token ${apiKey}` }, From 91da5a91a2eeb1fa23a060ad138bdec87ac1ce13 Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 14:16:03 -0500 Subject: [PATCH 05/10] feat: enhance audio processing and transcription handling - Updated DeepgramModule.kt to remove redundant logging and improve audio handling. - Modified SpeechToText.tsx to include interim transcripts and improve live transcription updates. - Enhanced Deepgram.mm with improved logging mechanisms for better debugging. - Updated speech-to-text.ts to include a new DeepgramTranscriptEvent type for better event handling. - Refactored useDeepgramSpeechToText.ts to manage partial and final transcripts more effectively. --- .../main/java/com/deepgram/DeepgramModule.kt | 8 - example/src/SpeechToText.tsx | 22 +- ios/Deepgram.mm | 230 ++++++++++-------- src/types/deepgram/speech-to-text.ts | 9 +- src/useDeepgramSpeechToText.ts | 67 ++++- 5 files changed, 213 insertions(+), 123 deletions(-) diff --git a/android/src/main/java/com/deepgram/DeepgramModule.kt b/android/src/main/java/com/deepgram/DeepgramModule.kt index afbf4aa..5eab32a 100644 --- a/android/src/main/java/com/deepgram/DeepgramModule.kt +++ b/android/src/main/java/com/deepgram/DeepgramModule.kt @@ -282,8 +282,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : audioBuffer = ByteArray(0) } isPlaying = false - - Log.d(TAG, "Audio player initialized: ${sampleRate}Hz, ${channels} channels") } @ReactMethod @@ -297,8 +295,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : val audioData = Base64.decode(base64Audio, Base64.DEFAULT) if (audioData.isEmpty()) return - Log.d(TAG, "Feeding ${audioData.size} bytes of audio for streaming") - // Add audio data to buffer synchronized(bufferLock) { audioBuffer = audioBuffer + audioData @@ -343,7 +339,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : } if (dataToPlay.isNotEmpty()) { - Log.d(TAG, "Playing accumulated audio: ${dataToPlay.size} bytes") val written = audioTrack?.write(dataToPlay, 0, dataToPlay.size) ?: 0 if (written < 0) { @@ -360,7 +355,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : Log.e(TAG, "Streaming playback error", e) } finally { isPlaying = false - Log.d(TAG, "Streaming playback finished") } } @@ -385,7 +379,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : fun stopPlayer(promise: Promise?) { try { stopStreamingPlayback() - Log.d(TAG, "Audio player stopped and cleaned up") promise?.resolve(null) } catch (e: Exception) { Log.e(TAG, "stopPlayer error", e) @@ -400,7 +393,6 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : @ReactMethod fun playAudioChunk(chunk: String, promise: Promise) { try { - Log.d(TAG, "Playing single audio chunk") val audioData = Base64.decode(chunk, Base64.DEFAULT) if (audioData.isEmpty()) { diff --git a/example/src/SpeechToText.tsx b/example/src/SpeechToText.tsx index c1d87cb..be0b90c 100644 --- a/example/src/SpeechToText.tsx +++ b/example/src/SpeechToText.tsx @@ -17,6 +17,7 @@ export default function SpeechToText() { 'idle' ); const [liveError, setLiveError] = useState(null); + const [liveInterimTranscript, setLiveInterimTranscript] = useState(''); // file transcription state const [fileTranscript, setFileTranscript] = useState(''); @@ -30,20 +31,31 @@ export default function SpeechToText() { onBeforeStart: () => { setLiveStatus('listening'); setLiveTranscript(''); + setLiveInterimTranscript(''); setLiveError(null); }, onStart: () => { // WebSocket opened }, - onTranscript: (text) => { - setLiveTranscript((prev) => prev + ' ' + text); + onTranscript: (text, info) => { + if (info?.isFinal) { + setLiveTranscript((prev) => { + const next = prev ? `${prev} ${text}` : text; + return next.trim(); + }); + setLiveInterimTranscript(''); + } else { + setLiveInterimTranscript(text); + } }, onError: (err) => { setLiveStatus('error'); setLiveError(err instanceof Error ? err.message : String(err)); + setLiveInterimTranscript(''); }, onEnd: () => { setLiveStatus('idle'); + setLiveInterimTranscript(''); }, // file callbacks onBeforeTranscribe: () => { @@ -95,6 +107,10 @@ export default function SpeechToText() { } }; + const combinedLiveTranscript = [liveTranscript, liveInterimTranscript] + .filter(Boolean) + .join(' '); + return ( {/* Live mic controls */} @@ -116,7 +132,7 @@ export default function SpeechToText() { {liveError && Error: {liveError}} - 🎤 Live: {liveTranscript || 'No live transcript yet.'} + 🎤 Live: {combinedLiveTranscript || 'No live transcript yet.'} diff --git a/ios/Deepgram.mm b/ios/Deepgram.mm index eed2254..3ec5e8a 100644 --- a/ios/Deepgram.mm +++ b/ios/Deepgram.mm @@ -1,4 +1,5 @@ #import +#import #import #import #import @@ -11,6 +12,19 @@ #define DGNumberBuffers 3 +#ifndef DG_ENABLE_DEBUG_LOGS +#define DG_ENABLE_DEBUG_LOGS 0 +#endif + +#if DG_ENABLE_DEBUG_LOGS +#define DGLogDebug(...) NSLog(__VA_ARGS__) +#else +#define DGLogDebug(...) +#endif + +#define DGLogWarn(...) RCTLogWarn(__VA_ARGS__) +#define DGLogError(...) RCTLogError(__VA_ARGS__) + @class Deepgram; typedef struct { @@ -61,29 +75,29 @@ static void DGHandleInputBuffer(void *inUserData, @autoreleasepool { DGRecordState *state = (DGRecordState *)inUserData; if (!state || !state->isRunning) { - NSLog(@"[Deepgram] DGHandleInputBuffer: inactive state"); + DGLogDebug(@"[Deepgram] DGHandleInputBuffer: inactive state"); return; } Deepgram *strongSelf = state->mSelf; if (!strongSelf) { - NSLog(@"[Deepgram] DGHandleInputBuffer: missing self"); + DGLogDebug(@"[Deepgram] DGHandleInputBuffer: missing self"); return; } if (!inBuffer || inBuffer->mAudioDataByteSize == 0) { - NSLog(@"[Deepgram] DGHandleInputBuffer: empty buffer"); + DGLogDebug(@"[Deepgram] DGHandleInputBuffer: empty buffer"); return; } - NSLog(@"[Deepgram] DGHandleInputBuffer: received %u bytes", + DGLogDebug(@"[Deepgram] DGHandleInputBuffer: received %u bytes", (unsigned int)inBuffer->mAudioDataByteSize); NSData *data = [NSData dataWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize]; [strongSelf appendPCMDataAndEmitIfNeeded:data]; if (state->queue) { - NSLog(@"[Deepgram] DGHandleInputBuffer: re-enqueue buffer"); + DGLogDebug(@"[Deepgram] DGHandleInputBuffer: re-enqueue buffer"); AudioQueueEnqueueBuffer(state->queue, inBuffer, 0, NULL); } } @@ -101,13 +115,13 @@ + (BOOL)requiresMainQueueSetup { return NO; } - (void)startObserving { - NSLog(@"[Deepgram] startObserving: listeners attached"); + DGLogDebug(@"[Deepgram] startObserving: listeners attached"); self.hasListeners = YES; } - (void)stopObserving { - NSLog(@"[Deepgram] stopObserving: listeners detached"); + DGLogDebug(@"[Deepgram] stopObserving: listeners detached"); self.hasListeners = NO; } @@ -118,7 +132,7 @@ - (instancetype)init { if (self = [super init]) { _chunkSizeBytes = 6400; // ≈200 ms of 16 kHz mono PCM16 audio - NSLog(@"[Deepgram] init: chunkSizeBytes=%lu", (unsigned long)_chunkSizeBytes); + DGLogDebug(@"[Deepgram] init: chunkSizeBytes=%lu", (unsigned long)_chunkSizeBytes); _emitterQueue = dispatch_queue_create("com.deepgram.liveaudiostream", DISPATCH_QUEUE_SERIAL); memset(&_recordState, 0, sizeof(DGRecordState)); @@ -129,48 +143,48 @@ - (instancetype)init selector:@selector(handleAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - NSLog(@"[Deepgram] init: registered for UIApplicationDidBecomeActiveNotification"); + DGLogDebug(@"[Deepgram] init: registered for UIApplicationDidBecomeActiveNotification"); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - NSLog(@"[Deepgram] init: registered for UIApplicationDidEnterBackgroundNotification"); + DGLogDebug(@"[Deepgram] init: registered for UIApplicationDidEnterBackgroundNotification"); #endif [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil]; - NSLog(@"[Deepgram] init: registered for AVAudioSessionRouteChangeNotification"); + DGLogDebug(@"[Deepgram] init: registered for AVAudioSessionRouteChangeNotification"); } return self; } - (void)dealloc { - NSLog(@"[Deepgram] dealloc: cleaning up recording queue and removing observers"); + DGLogDebug(@"[Deepgram] dealloc: cleaning up recording queue and removing observers"); [self cleanupRecordingQueue]; [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (BOOL)activateAudioSession:(NSError **)outError { - NSLog(@"[Deepgram] activateAudioSession: begin (configured=%@)", + DGLogDebug(@"[Deepgram] activateAudioSession: begin (configured=%@)", self.audioSessionConfigured ? @"YES" : @"NO"); __block BOOL success = YES; __block NSError *activationError = nil; RCTUnsafeExecuteOnMainQueueSync(^{ - NSLog(@"[Deepgram] activateAudioSession: configuring on main queue"); + DGLogDebug(@"[Deepgram] activateAudioSession: configuring on main queue"); success = [self configureAudioSessionIfNeeded:&activationError]; }); if (!success && activationError) { - NSLog(@"[Deepgram] Failed to activate audio session: %@", + DGLogError(@"[Deepgram] Failed to activate audio session: %@", activationError.localizedDescription ?: activationError); } else { - NSLog(@"[Deepgram] activateAudioSession: success=%@", success ? @"YES" : @"NO"); + DGLogDebug(@"[Deepgram] activateAudioSession: success=%@", success ? @"YES" : @"NO"); } if (outError) { @@ -182,11 +196,11 @@ - (BOOL)activateAudioSession:(NSError **)outError - (void)deactivateAudioSession { - NSLog(@"[Deepgram] deactivateAudioSession: begin"); + DGLogDebug(@"[Deepgram] deactivateAudioSession: begin"); RCTUnsafeExecuteOnMainQueueSync(^{ NSError *error = nil; if (![[AVAudioSession sharedInstance] setActive:NO error:&error] && error) { - NSLog(@"[Deepgram] Failed to deactivate audio session: %@", + DGLogError(@"[Deepgram] Failed to deactivate audio session: %@", error.localizedDescription ?: error); } }); @@ -196,11 +210,11 @@ - (void)deactivateAudioSession - (BOOL)configureAudioSessionIfNeeded:(NSError **)outError { if (!self.audioSessionConfigured) { - NSLog(@"[Deepgram] configureAudioSessionIfNeeded: not configured, configuring"); + DGLogDebug(@"[Deepgram] configureAudioSessionIfNeeded: not configured, configuring"); return [self configureAudioSession:outError]; } - NSLog(@"[Deepgram] configureAudioSessionIfNeeded: already configured, ensuring active"); + DGLogDebug(@"[Deepgram] configureAudioSessionIfNeeded: already configured, ensuring active"); NSError *activeError = nil; BOOL success = [[AVAudioSession sharedInstance] setActive:YES error:&activeError]; if (!success || activeError) { @@ -214,7 +228,7 @@ - (BOOL)configureAudioSessionIfNeeded:(NSError **)outError - (BOOL)configureAudioSession:(NSError **)outError { - NSLog(@"[Deepgram] configureAudioSession: begin"); + DGLogDebug(@"[Deepgram] configureAudioSession: begin"); AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *error = nil; @@ -223,14 +237,14 @@ - (BOOL)configureAudioSession:(NSError **)outError AVAudioSessionCategoryOptionAllowBluetooth; if (@available(iOS 10.0, *)) { - NSLog(@"[Deepgram] configureAudioSession: enabling AirPlay option"); + DGLogDebug(@"[Deepgram] configureAudioSession: enabling AirPlay option"); options |= AVAudioSessionCategoryOptionAllowAirPlay; } BOOL categorySuccess = NO; if (@available(iOS 10.0, *)) { - NSLog(@"[Deepgram] configureAudioSession: setCategory PlayAndRecord voice chat (iOS10+)"); + DGLogDebug(@"[Deepgram] configureAudioSession: setCategory PlayAndRecord voice chat (iOS10+)"); categorySuccess = [session setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeVoiceChat options:options @@ -238,7 +252,7 @@ - (BOOL)configureAudioSession:(NSError **)outError } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSLog(@"[Deepgram] configureAudioSession: setCategory legacy path"); + DGLogDebug(@"[Deepgram] configureAudioSession: setCategory legacy path"); categorySuccess = [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:&error]; @@ -253,7 +267,7 @@ - (BOOL)configureAudioSession:(NSError **)outError } if (!categorySuccess || error) { - NSLog(@"[Deepgram] configureAudioSession: category failed error=%@", + DGLogError(@"[Deepgram] configureAudioSession: category failed error=%@", error.localizedDescription ?: error); if (outError) { *outError = error; @@ -265,7 +279,7 @@ - (BOOL)configureAudioSession:(NSError **)outError NSError *activeError = nil; BOOL activeSuccess = [session setActive:YES error:&activeError]; if (!activeSuccess || activeError) { - NSLog(@"[Deepgram] configureAudioSession: setActive failed error=%@", + DGLogError(@"[Deepgram] configureAudioSession: setActive failed error=%@", activeError.localizedDescription ?: activeError); if (outError) { *outError = activeError; @@ -275,24 +289,24 @@ - (BOOL)configureAudioSession:(NSError **)outError } self.audioSessionConfigured = YES; - NSLog(@"[Deepgram] configureAudioSession: success"); + DGLogDebug(@"[Deepgram] configureAudioSession: success"); return YES; } - (void)maybeDeactivateAudioSession { - NSLog(@"[Deepgram] maybeDeactivateAudioSession: running=%@ playing=%@", + DGLogDebug(@"[Deepgram] maybeDeactivateAudioSession: running=%@ playing=%@", _recordState.isRunning ? @"YES" : @"NO", self.isPlaying ? @"YES" : @"NO"); if (!_recordState.isRunning && !self.isPlaying) { - NSLog(@"[Deepgram] maybeDeactivateAudioSession: deactivating"); + DGLogDebug(@"[Deepgram] maybeDeactivateAudioSession: deactivating"); [self deactivateAudioSession]; } } - (void)handleAudioRouteChange:(NSNotification *)note { - NSLog(@"[Deepgram] handleAudioRouteChange: %@", note.userInfo); + DGLogDebug(@"[Deepgram] handleAudioRouteChange: %@", note.userInfo); NSNumber *reasonValue = note.userInfo[AVAudioSessionRouteChangeReasonKey]; AVAudioSessionRouteChangeReason reason = reasonValue ? (AVAudioSessionRouteChangeReason) @@ -308,39 +322,39 @@ - (void)handleAudioRouteChange:(NSNotification *)note } if (!_recordState.isRunning && !self.isPlaying) { - NSLog(@"[Deepgram] handleAudioRouteChange: ignoring (inactive)"); + DGLogDebug(@"[Deepgram] handleAudioRouteChange: ignoring (inactive)"); return; } if (!self.appIsActive) { - NSLog(@"[Deepgram] handleAudioRouteChange: app inactive, skipping"); + DGLogDebug(@"[Deepgram] handleAudioRouteChange: app inactive, skipping"); return; } if (causedByCategory) { - NSLog(@"[Deepgram] handleAudioRouteChange: category change detected, keeping session"); + DGLogDebug(@"[Deepgram] handleAudioRouteChange: category change detected, keeping session"); return; } - NSLog(@"[Deepgram] handleAudioRouteChange: reactivating session"); + DGLogDebug(@"[Deepgram] handleAudioRouteChange: reactivating session"); [self activateAudioSession:NULL]; } #if TARGET_OS_IOS - (void)handleAppDidBecomeActive:(NSNotification *)note { - NSLog(@"[Deepgram] handleAppDidBecomeActive: %@", note.userInfo); + DGLogDebug(@"[Deepgram] handleAppDidBecomeActive: %@", note.userInfo); self.appIsActive = YES; if (_recordState.isRunning || self.isPlaying) { - NSLog(@"[Deepgram] handleAppDidBecomeActive: reactivating session"); + DGLogDebug(@"[Deepgram] handleAppDidBecomeActive: reactivating session"); [self activateAudioSession:NULL]; } } - (void)handleAppDidEnterBackground:(NSNotification *)note { - NSLog(@"[Deepgram] handleAppDidEnterBackground: %@", note.userInfo); + DGLogDebug(@"[Deepgram] handleAppDidEnterBackground: %@", note.userInfo); self.appIsActive = NO; [self maybeDeactivateAudioSession]; } @@ -349,7 +363,7 @@ - (void)handleAppDidEnterBackground:(NSNotification *)note - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate { if (!chunk || chunk.length == 0) { - NSLog(@"[Deepgram] emitPCMChunk: empty chunk, skipping"); + DGLogDebug(@"[Deepgram] emitPCMChunk: empty chunk, skipping"); return; } @@ -358,22 +372,22 @@ - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate dispatch_queue_t queue = self.emitterQueue ?: dispatch_get_main_queue(); dispatch_async(queue, ^{ if (!weakSelf) { - NSLog(@"[Deepgram] emitPCMChunk: self released, aborting"); + DGLogDebug(@"[Deepgram] emitPCMChunk: self released, aborting"); return; } if (!weakSelf.hasListeners) { - NSLog(@"[Deepgram] emitPCMChunk: no listeners, dropping %lu bytes", + DGLogDebug(@"[Deepgram] emitPCMChunk: no listeners, dropping %lu bytes", (unsigned long)chunkCopy.length); return; } if (!weakSelf.bridge || !weakSelf.callableJSModules) { - NSLog(@"[Deepgram] Skipping DeepgramAudioPCM event (bridge not ready)"); + DGLogDebug(@"[Deepgram] Skipping DeepgramAudioPCM event (bridge not ready)"); return; } - NSLog(@"[Deepgram] emitPCMChunk: sending %lu bytes sampleRate=%d", + DGLogDebug(@"[Deepgram] emitPCMChunk: sending %lu bytes sampleRate=%d", (unsigned long)chunkCopy.length, sampleRate); NSString *b64 = [chunkCopy base64EncodedStringWithOptions:0]; @@ -385,16 +399,16 @@ - (void)emitPCMChunk:(NSData *)chunk sampleRate:(int)sampleRate - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData { if (!pcmData || pcmData.length == 0) { - NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: empty PCM, skipping"); + DGLogDebug(@"[Deepgram] appendPCMDataAndEmitIfNeeded: empty PCM, skipping"); return; } if (!self.pendingPCMBuffer) { - NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: allocate pending buffer"); + DGLogDebug(@"[Deepgram] appendPCMDataAndEmitIfNeeded: allocate pending buffer"); self.pendingPCMBuffer = [[NSMutableData alloc] init]; } - NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: append %lu bytes (pending=%lu)", + DGLogDebug(@"[Deepgram] appendPCMDataAndEmitIfNeeded: append %lu bytes (pending=%lu)", (unsigned long)pcmData.length, (unsigned long)self.pendingPCMBuffer.length); [self.pendingPCMBuffer appendData:pcmData]; @@ -407,7 +421,7 @@ - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData [self.pendingPCMBuffer replaceBytesInRange:NSMakeRange(0, chunkSize) withBytes:NULL length:0]; - NSLog(@"[Deepgram] appendPCMDataAndEmitIfNeeded: emitting chunk %lu bytes remaining=%lu", + DGLogDebug(@"[Deepgram] appendPCMDataAndEmitIfNeeded: emitting chunk %lu bytes remaining=%lu", (unsigned long)chunk.length, (unsigned long)self.pendingPCMBuffer.length); [self emitPCMChunk:chunk sampleRate:self.currentSampleRate]; @@ -417,40 +431,40 @@ - (void)appendPCMDataAndEmitIfNeeded:(NSData *)pcmData - (void)flushPendingPCM { if (self.pendingPCMBuffer.length == 0) { - NSLog(@"[Deepgram] flushPendingPCM: nothing to flush"); + DGLogDebug(@"[Deepgram] flushPendingPCM: nothing to flush"); return; } NSData *remaining = [self.pendingPCMBuffer copy]; [self.pendingPCMBuffer setLength:0]; - NSLog(@"[Deepgram] flushPendingPCM: flushing %lu bytes", + DGLogDebug(@"[Deepgram] flushPendingPCM: flushing %lu bytes", (unsigned long)remaining.length); [self emitPCMChunk:remaining sampleRate:self.currentSampleRate]; } - (void)cleanupRecordingQueue { - NSLog(@"[Deepgram] cleanupRecordingQueue: begin"); + DGLogDebug(@"[Deepgram] cleanupRecordingQueue: begin"); _recordState.isRunning = false; if (_recordState.queue) { - NSLog(@"[Deepgram] cleanupRecordingQueue: stopping queue"); + DGLogDebug(@"[Deepgram] cleanupRecordingQueue: stopping queue"); AudioQueueStop(_recordState.queue, true); for (int i = 0; i < DGNumberBuffers; i++) { if (_recordState.buffers[i]) { - NSLog(@"[Deepgram] cleanupRecordingQueue: freeing buffer %d", i); + DGLogDebug(@"[Deepgram] cleanupRecordingQueue: freeing buffer %d", i); AudioQueueFreeBuffer(_recordState.queue, _recordState.buffers[i]); _recordState.buffers[i] = NULL; } } - NSLog(@"[Deepgram] cleanupRecordingQueue: disposing queue"); + DGLogDebug(@"[Deepgram] cleanupRecordingQueue: disposing queue"); AudioQueueDispose(_recordState.queue, true); } memset(&_recordState, 0, sizeof(DGRecordState)); - NSLog(@"[Deepgram] cleanupRecordingQueue: state cleared"); + DGLogDebug(@"[Deepgram] cleanupRecordingQueue: state cleared"); [self maybeDeactivateAudioSession]; } @@ -462,23 +476,23 @@ - (void)cleanupRecordingQueue rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] startRecording: begin"); + DGLogDebug(@"[Deepgram] startRecording: begin"); self.currentSampleRate = 16000; - NSLog(@"[Deepgram] startRecording: targetSampleRate=%d", self.currentSampleRate); + DGLogDebug(@"[Deepgram] startRecording: targetSampleRate=%d", self.currentSampleRate); NSError *sessionError = nil; if (![self activateAudioSession:&sessionError]) { NSString *message = sessionError.localizedDescription ?: @"Failed to activate audio session"; - NSLog(@"[Deepgram] startRecording: activation failed %@", message); + DGLogError(@"[Deepgram] startRecording: activation failed %@", message); if (reject) reject(@"record_start_error", message, sessionError); return; } if (_recordState.isRunning) { - NSLog(@"[Deepgram] startRecording: record state already running, cleaning up"); + DGLogDebug(@"[Deepgram] startRecording: record state already running, cleaning up"); [self cleanupRecordingQueue]; } - NSLog(@"[Deepgram] startRecording: resetting buffers"); + DGLogDebug(@"[Deepgram] startRecording: resetting buffers"); self.pendingPCMBuffer = [[NSMutableData alloc] init]; memset(&_recordState, 0, sizeof(DGRecordState)); @@ -507,7 +521,7 @@ - (void)cleanupRecordingQueue targetSecondsPerChunk)); self.chunkSizeBytes = defaultChunkSize; _recordState.bufferByteSize = (UInt32)self.chunkSizeBytes; - NSLog(@"[Deepgram] startRecording: bufferByteSize=%u (chunkSize=%lu)", + DGLogDebug(@"[Deepgram] startRecording: bufferByteSize=%u (chunkSize=%lu)", (unsigned int)_recordState.bufferByteSize, (unsigned long)self.chunkSizeBytes); @@ -522,7 +536,7 @@ - (void)cleanupRecordingQueue if (status != noErr) { NSString *message = [NSString stringWithFormat:@"AudioQueueNewInput failed: %d", (int)status]; - NSLog(@"[Deepgram] startRecording: %@", message); + DGLogError(@"[Deepgram] startRecording: %@", message); [self cleanupRecordingQueue]; if (reject) reject(@"record_start_error", message, nil); return; @@ -536,7 +550,7 @@ - (void)cleanupRecordingQueue &actualFormatSize); if (status == noErr) { int resolvedSampleRate = (int)llround(actualFormat.mSampleRate); - NSLog(@"[Deepgram] startRecording: actual sampleRate=%f (%d)", + DGLogDebug(@"[Deepgram] startRecording: actual sampleRate=%f (%d)", actualFormat.mSampleRate, resolvedSampleRate); if (resolvedSampleRate > 0) { @@ -551,11 +565,11 @@ - (void)cleanupRecordingQueue if (adaptiveChunk > 0 && adaptiveChunk != self.chunkSizeBytes) { self.chunkSizeBytes = adaptiveChunk; _recordState.bufferByteSize = adaptiveChunk; - NSLog(@"[Deepgram] startRecording: adjusted chunkSize=%lu", + DGLogDebug(@"[Deepgram] startRecording: adjusted chunkSize=%lu", (unsigned long)self.chunkSizeBytes); } } else { - NSLog(@"[Deepgram] startRecording: failed to read stream description %d", (int)status); + DGLogError(@"[Deepgram] startRecording: failed to read stream description %d", (int)status); } for (int i = 0; i < DGNumberBuffers; i++) { @@ -565,7 +579,7 @@ - (void)cleanupRecordingQueue if (status != noErr) { NSString *message = [NSString stringWithFormat:@"AudioQueueAllocateBuffer failed: %d", (int)status]; - NSLog(@"[Deepgram] startRecording: %@", message); + DGLogError(@"[Deepgram] startRecording: %@", message); [self cleanupRecordingQueue]; if (reject) reject(@"record_start_error", message, nil); return; @@ -578,7 +592,7 @@ - (void)cleanupRecordingQueue if (status != noErr) { NSString *message = [NSString stringWithFormat:@"AudioQueueEnqueueBuffer failed: %d", (int)status]; - NSLog(@"[Deepgram] startRecording: %@", message); + DGLogError(@"[Deepgram] startRecording: %@", message); [self cleanupRecordingQueue]; if (reject) reject(@"record_start_error", message, nil); return; @@ -589,17 +603,17 @@ - (void)cleanupRecordingQueue if (status != noErr) { NSString *message = [NSString stringWithFormat:@"AudioQueueStart failed: %d", (int)status]; - NSLog(@"[Deepgram] startRecording: %@", message); + DGLogError(@"[Deepgram] startRecording: %@", message); [self cleanupRecordingQueue]; if (reject) reject(@"record_start_error", message, nil); return; } - NSLog(@"[Deepgram] startRecording: success"); + DGLogDebug(@"[Deepgram] startRecording: success"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] startRecording: exception %@", e); + DGLogError(@"[Deepgram] startRecording: exception %@", e); [self cleanupRecordingQueue]; if (reject) reject(@"record_start_error", e.reason, nil); } @@ -610,15 +624,15 @@ - (void)cleanupRecordingQueue rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] stopRecording: begin"); + DGLogDebug(@"[Deepgram] stopRecording: begin"); [self cleanupRecordingQueue]; [self flushPendingPCM]; self.pendingPCMBuffer = nil; - NSLog(@"[Deepgram] stopRecording: finished"); + DGLogDebug(@"[Deepgram] stopRecording: finished"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] stopRecording: exception %@", e); + DGLogError(@"[Deepgram] stopRecording: exception %@", e); if (reject) reject(@"record_stop_error", e.reason, nil); } } @@ -628,27 +642,27 @@ - (void)cleanupRecordingQueue rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] startAudio: begin"); + DGLogDebug(@"[Deepgram] startAudio: begin"); NSError *sessionError = nil; if (![self activateAudioSession:&sessionError]) { NSString *message = sessionError.localizedDescription ?: @"Failed to activate audio session"; - NSLog(@"[Deepgram] startAudio: activation failed %@", message); + DGLogError(@"[Deepgram] startAudio: activation failed %@", message); if (reject) reject(@"audio_start_error", message, sessionError); return; } if (!self.audioBuffer) { self.audioBuffer = [[NSMutableData alloc] init]; - NSLog(@"[Deepgram] startAudio: created audioBuffer"); + DGLogDebug(@"[Deepgram] startAudio: created audioBuffer"); } if (self.currentSampleRate <= 0) { self.currentSampleRate = 16000; - NSLog(@"[Deepgram] startAudio: default sample rate applied %d", self.currentSampleRate); + DGLogDebug(@"[Deepgram] startAudio: default sample rate applied %d", self.currentSampleRate); } - NSLog(@"[Deepgram] startAudio: success"); + DGLogDebug(@"[Deepgram] startAudio: success"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] startAudio: exception %@", e); + DGLogError(@"[Deepgram] startAudio: exception %@", e); if (reject) reject(@"audio_start_error", e.reason, nil); } } @@ -658,13 +672,13 @@ - (void)cleanupRecordingQueue rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] stopAudio: begin"); + DGLogDebug(@"[Deepgram] stopAudio: begin"); [self stopPlayer:nil rejecter:nil]; - NSLog(@"[Deepgram] stopAudio: success"); + DGLogDebug(@"[Deepgram] stopAudio: success"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] stopAudio: exception %@", e); + DGLogError(@"[Deepgram] stopAudio: exception %@", e); if (reject) reject(@"audio_stop_error", e.reason, nil); } } @@ -717,13 +731,13 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa :(nonnull NSNumber *)sampleRate channels:(nonnull NSNumber *)channels) { - NSLog(@"[Deepgram] startPlayer: begin sampleRate=%@ channels=%@", + DGLogDebug(@"[Deepgram] startPlayer: begin sampleRate=%@ channels=%@", sampleRate, channels); [self stopPlayer:nil rejecter:nil]; NSError *sessionError = nil; if (![self activateAudioSession:&sessionError]) { - NSLog(@"[Deepgram] Unable to activate audio session for playback: %@", + DGLogWarn(@"[Deepgram] Unable to activate audio session for playback: %@", sessionError.localizedDescription ?: sessionError); return; } @@ -731,7 +745,7 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa self.audioBuffer = [[NSMutableData alloc] init]; self.isPlaying = NO; self.currentSampleRate = sampleRate.intValue; - NSLog(@"[Deepgram] startPlayer: initialized buffer sampleRate=%d", + DGLogDebug(@"[Deepgram] startPlayer: initialized buffer sampleRate=%d", self.currentSampleRate); } @@ -740,27 +754,27 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa */ RCT_EXPORT_METHOD(feedAudio:(NSString *)b64) { - NSLog(@"[Deepgram] feedAudio: begin length=%lu", (unsigned long)b64.length); + DGLogDebug(@"[Deepgram] feedAudio: begin length=%lu", (unsigned long)b64.length); if (!self.audioBuffer) { // Use default sample rate if not set int defaultSampleRate = self.currentSampleRate > 0 ? self.currentSampleRate : 16000; - NSLog(@"[Deepgram] feedAudio: auto startPlayer defaultSampleRate=%d", defaultSampleRate); + DGLogDebug(@"[Deepgram] feedAudio: auto startPlayer defaultSampleRate=%d", defaultSampleRate); [self startPlayer:@(defaultSampleRate) channels:@1]; } NSData *pcmData = [[NSData alloc] initWithBase64EncodedString:b64 options:0]; if (!pcmData || pcmData.length == 0) { - NSLog(@"[Deepgram] feedAudio: decoded data empty"); + DGLogDebug(@"[Deepgram] feedAudio: decoded data empty"); return; } [self.audioBuffer appendData:pcmData]; - NSLog(@"[Deepgram] feedAudio: appended %lu bytes (buffer=%lu)", + DGLogDebug(@"[Deepgram] feedAudio: appended %lu bytes (buffer=%lu)", (unsigned long)pcmData.length, (unsigned long)self.audioBuffer.length); if (!self.isPlaying && self.audioBuffer.length > 1000) { - NSLog(@"[Deepgram] feedAudio: triggering playback"); + DGLogDebug(@"[Deepgram] feedAudio: triggering playback"); [self playAccumulatedAudio]; } } @@ -770,7 +784,7 @@ - (NSData *)createWAVHeaderForPCMData:(NSData *)pcmData sampleRate:(int)sampleRa */ - (void)playAccumulatedAudio { if (!self.audioBuffer || self.audioBuffer.length == 0) { - NSLog(@"[Deepgram] playAccumulatedAudio: no buffered audio"); + DGLogDebug(@"[Deepgram] playAccumulatedAudio: no buffered audio"); return; } @@ -780,7 +794,7 @@ - (void)playAccumulatedAudio { AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:wavData error:&error]; if (error) { - NSLog(@"[Deepgram] playAccumulatedAudio: player error=%@", error); + DGLogError(@"[Deepgram] playAccumulatedAudio: player error=%@", error); return; } @@ -792,10 +806,10 @@ - (void)playAccumulatedAudio { BOOL success = [player play]; if (success) { - NSLog(@"[Deepgram] playAccumulatedAudio: playback started, clearing buffer"); + DGLogDebug(@"[Deepgram] playAccumulatedAudio: playback started, clearing buffer"); [self.audioBuffer setLength:0]; } else { - NSLog(@"[Deepgram] playAccumulatedAudio: failed to start playback"); + DGLogError(@"[Deepgram] playAccumulatedAudio: failed to start playback"); self.isPlaying = NO; } } @@ -804,20 +818,20 @@ - (void)playAccumulatedAudio { * Play PCM data immediately using AVAudioPlayer. */ - (void)playPCMData:(NSData *)pcmData { - NSLog(@"[Deepgram] playPCMData: begin length=%lu", (unsigned long)pcmData.length); + DGLogDebug(@"[Deepgram] playPCMData: begin length=%lu", (unsigned long)pcmData.length); NSData *wavData = [self createWAVHeaderForPCMData:pcmData sampleRate:self.currentSampleRate]; NSError *error = nil; AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithData:wavData error:&error]; if (error) { - NSLog(@"[Deepgram] playPCMData: player error=%@", error); + DGLogError(@"[Deepgram] playPCMData: player error=%@", error); return; } self.audioPlayer = player; [player prepareToPlay]; - NSLog(@"[Deepgram] playPCMData: playing"); + DGLogDebug(@"[Deepgram] playPCMData: playing"); [player play]; } @@ -829,10 +843,10 @@ - (void)playPCMData:(NSData *)pcmData { rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] stopPlayer: begin"); + DGLogDebug(@"[Deepgram] stopPlayer: begin"); if (self.audioPlayer) { [self.audioPlayer stop]; - NSLog(@"[Deepgram] stopPlayer: stopped audioPlayer"); + DGLogDebug(@"[Deepgram] stopPlayer: stopped audioPlayer"); self.audioPlayer = nil; } @@ -841,12 +855,12 @@ - (void)playPCMData:(NSData *)pcmData { [self maybeDeactivateAudioSession]; - NSLog(@"[Deepgram] stopPlayer: success"); + DGLogDebug(@"[Deepgram] stopPlayer: success"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] Error stopping player: %@", e.reason); - NSLog(@"[Deepgram] stopPlayer: exception %@", e); + DGLogError(@"[Deepgram] Error stopping player: %@", e.reason); + DGLogError(@"[Deepgram] stopPlayer: exception %@", e); if (reject) reject(@"player_stop_error", e.reason, nil); } } @@ -858,7 +872,7 @@ - (void)playPCMData:(NSData *)pcmData { :(nonnull NSNumber *)sampleRate channels:(nonnull NSNumber *)channels) { - NSLog(@"[Deepgram] setAudioConfig: sampleRate=%@ channels=%@", sampleRate, channels); + DGLogDebug(@"[Deepgram] setAudioConfig: sampleRate=%@ channels=%@", sampleRate, channels); [self startPlayer:sampleRate channels:channels]; } @@ -871,21 +885,21 @@ - (void)playPCMData:(NSData *)pcmData { rejecter:(RCTPromiseRejectBlock)reject) { @try { - NSLog(@"[Deepgram] playAudioChunk: begin"); + DGLogDebug(@"[Deepgram] playAudioChunk: begin"); NSData *pcmData = [[NSData alloc] initWithBase64EncodedString:chunk options:0]; if (!pcmData) { - NSLog(@"[Deepgram] playAudioChunk: failed to decode base64"); + DGLogError(@"[Deepgram] playAudioChunk: failed to decode base64"); if (reject) reject(@"invalid_data", @"Failed to decode audio data", nil); return; } [self playPCMData:pcmData]; - NSLog(@"[Deepgram] playAudioChunk: success"); + DGLogDebug(@"[Deepgram] playAudioChunk: success"); if (resolve) resolve(nil); } @catch (NSException *e) { - NSLog(@"[Deepgram] playAudioChunk: exception %@", e); + DGLogError(@"[Deepgram] playAudioChunk: exception %@", e); if (reject) reject(@"playback_error", e.reason, nil); } } @@ -895,17 +909,17 @@ - (void)playPCMData:(NSData *)pcmData { /* ================================================================== */ - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { - NSLog(@"[Deepgram] audioPlayerDidFinishPlaying: success=%@", flag ? @"YES" : @"NO"); + DGLogDebug(@"[Deepgram] audioPlayerDidFinishPlaying: success=%@", flag ? @"YES" : @"NO"); self.isPlaying = NO; if (self.audioBuffer && self.audioBuffer.length > 0) { - NSLog(@"[Deepgram] audioPlayerDidFinishPlaying: playing remaining buffered audio"); + DGLogDebug(@"[Deepgram] audioPlayerDidFinishPlaying: playing remaining buffered audio"); [self playAccumulatedAudio]; } } - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error { - NSLog(@"[Deepgram] audioPlayerDecodeErrorDidOccur: error=%@", error); + DGLogError(@"[Deepgram] audioPlayerDecodeErrorDidOccur: error=%@", error); self.isPlaying = NO; } diff --git a/src/types/deepgram/speech-to-text.ts b/src/types/deepgram/speech-to-text.ts index 7982153..b2b7a18 100644 --- a/src/types/deepgram/speech-to-text.ts +++ b/src/types/deepgram/speech-to-text.ts @@ -255,13 +255,20 @@ export type DeepgramPrerecordedSource = | { url: string } | string; +export type DeepgramTranscriptEvent = { + /** Indicates whether the transcript represents a finalized utterance. */ + isFinal?: boolean; + /** Raw payload received from Deepgram for custom handling or inspection. */ + raw?: unknown; +}; + export type UseDeepgramSpeechToTextProps = { /** Called before any setup (e.g. before permission prompt) */ onBeforeStart?: () => void; /** Called once the WebSocket is open */ onStart?: () => void; /** Called on every transcript update */ - onTranscript?: (transcript: string) => void; + onTranscript?: (transcript: string, event?: DeepgramTranscriptEvent) => void; /** Called on any error */ onError?: (error: unknown) => void; /** Called when the session ends or WebSocket closes */ diff --git a/src/useDeepgramSpeechToText.ts b/src/useDeepgramSpeechToText.ts index dcbf7e4..3fceda5 100644 --- a/src/useDeepgramSpeechToText.ts +++ b/src/useDeepgramSpeechToText.ts @@ -6,6 +6,7 @@ import type { DeepgramLiveListenOptions, DeepgramPrerecordedOptions, DeepgramPrerecordedSource, + DeepgramTranscriptEvent, UseDeepgramSpeechToTextProps, UseDeepgramSpeechToTextReturn, } from './types'; @@ -65,6 +66,8 @@ export function useDeepgramSpeechToText({ const nativeInputSampleRateRef = useRef(BASE_NATIVE_SAMPLE_RATE); const targetSampleRateRef = useRef(DEFAULT_SAMPLE_RATE); const downsampleFactorRef = useRef(1); + const lastPartialTranscriptRef = useRef(''); + const lastFinalTranscriptRef = useRef(''); const closeEverything = () => { audioSub.current?.remove(); @@ -72,6 +75,8 @@ export function useDeepgramSpeechToText({ nativeInputSampleRateRef.current = BASE_NATIVE_SAMPLE_RATE; targetSampleRateRef.current = DEFAULT_SAMPLE_RATE; downsampleFactorRef.current = 1; + lastPartialTranscriptRef.current = ''; + lastFinalTranscriptRef.current = ''; if ( apiVersionRef.current === 'v2' && ws.current?.readyState === WebSocket.OPEN @@ -87,10 +92,50 @@ export function useDeepgramSpeechToText({ apiVersionRef.current = 'v1'; }; + const emitTranscript = useCallback( + (transcript: unknown, isFinal: boolean, raw: unknown) => { + if (typeof onTranscript !== 'function') { + return; + } + + if (typeof transcript !== 'string') { + return; + } + + const normalized = transcript.trim(); + if (!normalized) { + return; + } + + if (isFinal) { + if (lastFinalTranscriptRef.current === normalized) { + return; + } + lastFinalTranscriptRef.current = normalized; + lastPartialTranscriptRef.current = ''; + } else { + if (lastPartialTranscriptRef.current === normalized) { + return; + } + lastPartialTranscriptRef.current = normalized; + } + + const event: DeepgramTranscriptEvent = { isFinal, raw }; + onTranscript(normalized, event); + + if (isFinal) { + lastPartialTranscriptRef.current = ''; + } + }, + [onTranscript] + ); + const startListening = useCallback( async (overrideOptions: DeepgramLiveListenOptions = {}) => { try { onBeforeStart(); + lastPartialTranscriptRef.current = ''; + lastFinalTranscriptRef.current = ''; const granted = await askMicPermission(); if (!granted) throw new Error('Microphone permission denied'); @@ -259,13 +304,29 @@ export function useDeepgramSpeechToText({ const transcript = msg.transcript; if (typeof transcript === 'string' && transcript.length > 0) { - onTranscript(transcript); + const type = + typeof msg.type === 'string' + ? msg.type.toLowerCase() + : undefined; + const isFinal = + msg.is_final === true || + msg.speech_final === true || + msg.finished === true || + type === 'utteranceend' || + type === 'speechfinal' || + type === 'speech.end' || + (typeof type === 'string' && type.includes('final')); + emitTranscript(transcript, Boolean(isFinal), msg); } return; } const transcript = msg.channel?.alternatives?.[0]?.transcript; - if (transcript) onTranscript(transcript); + if (typeof transcript === 'string') { + const isFinal = + msg.is_final === true || msg.speech_final === true; + emitTranscript(transcript, Boolean(isFinal), msg); + } } catch { // non-JSON or unexpected format } @@ -282,7 +343,7 @@ export function useDeepgramSpeechToText({ closeEverything(); } }, - [onBeforeStart, onStart, onTranscript, onError, onEnd, live] + [emitTranscript, onBeforeStart, onStart, onError, onEnd, live] ); const stopListening = useCallback(() => { From c9ededf9fc77f987f16a5eb85292f63d34e45c5f Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 15:55:45 -0500 Subject: [PATCH 06/10] feat: improve error handling in audio recording and playback methods --- .../main/java/com/deepgram/DeepgramModule.kt | 86 +++++++++++++++---- ios/Deepgram.mm | 28 ++++++ src/useDeepgramSpeechToText.ts | 5 +- 3 files changed, 100 insertions(+), 19 deletions(-) diff --git a/android/src/main/java/com/deepgram/DeepgramModule.kt b/android/src/main/java/com/deepgram/DeepgramModule.kt index 5eab32a..9821ab7 100644 --- a/android/src/main/java/com/deepgram/DeepgramModule.kt +++ b/android/src/main/java/com/deepgram/DeepgramModule.kt @@ -102,16 +102,33 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : } } - @ReactMethod - fun stopRecording(promise: Promise) { + private fun stopRecordingInternal(throwOnError: Boolean = true) { + isRecording = false + recordingThread?.interrupt() + recordingThread = null + try { - isRecording = false - recordingThread?.interrupt() - recordingThread = null audioRecord?.stop() + } catch (e: Exception) { + Log.w(TAG, "stopRecordingInternal: error stopping AudioRecord", e) + if (throwOnError) throw e + } + + try { audioRecord?.release() - audioRecord = null - releaseAudioEffects() + } catch (e: Exception) { + Log.w(TAG, "stopRecordingInternal: error releasing AudioRecord", e) + if (throwOnError) throw e + } + + audioRecord = null + releaseAudioEffects() + } + + @ReactMethod + fun stopRecording(promise: Promise) { + try { + stopRecordingInternal() promise.resolve(null) } catch (e: Exception) { Log.e(TAG, "stopRecording error", e) @@ -254,12 +271,28 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : } } - @ReactMethod - fun stopAudio(promise: Promise) { + private fun stopAudioInternal(throwOnError: Boolean = true) { try { audioTrack?.stop() + } catch (e: Exception) { + Log.w(TAG, "stopAudioInternal: error stopping AudioTrack", e) + if (throwOnError) throw e + } + + try { audioTrack?.release() - audioTrack = null + } catch (e: Exception) { + Log.w(TAG, "stopAudioInternal: error releasing AudioTrack", e) + if (throwOnError) throw e + } + + audioTrack = null + } + + @ReactMethod + fun stopAudio(promise: Promise) { + try { + stopAudioInternal() promise.resolve(null) } catch (e: Exception) { Log.e(TAG, "stopAudio error", e) @@ -361,17 +394,19 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : playbackThread?.start() } - private fun stopStreamingPlayback() { + private fun stopStreamingPlayback(throwOnError: Boolean = true) { isPlaying = false playbackThread?.interrupt() playbackThread = null - - audioTrack?.stop() - audioTrack?.release() - audioTrack = null - - synchronized(bufferLock) { - audioBuffer = ByteArray(0) + + try { + stopAudioInternal(throwOnError) + } catch (e: Exception) { + if (throwOnError) throw e else Log.w(TAG, "stopStreamingPlayback: error stopping audio", e) + } finally { + synchronized(bufferLock) { + audioBuffer = ByteArray(0) + } } } @@ -386,6 +421,21 @@ class DeepgramModule(private val reactContext: ReactApplicationContext) : } } + override fun onCatalystInstanceDestroy() { + try { + stopRecordingInternal(false) + } catch (e: Exception) { + Log.w(TAG, "Error stopping recording on catalyst destroy", e) + } + + try { + stopStreamingPlayback(false) + } catch (e: Exception) { + Log.w(TAG, "Error stopping playback on catalyst destroy", e) + } + super.onCatalystInstanceDestroy() + } + // ------------------------------------------------------------------- // One-shot playback (for HTTP synthesis) // ------------------------------------------------------------------- diff --git a/ios/Deepgram.mm b/ios/Deepgram.mm index 3ec5e8a..b1a3ed8 100644 --- a/ios/Deepgram.mm +++ b/ios/Deepgram.mm @@ -904,6 +904,34 @@ - (void)playPCMData:(NSData *)pcmData { } } +- (void)invalidate +{ + DGLogDebug(@"[Deepgram] invalidate: begin"); + + @try { + [self cleanupRecordingQueue]; + } @catch (NSException *e) { + DGLogError(@"[Deepgram] invalidate: cleanupRecordingQueue exception %@", e); + } + + self.pendingPCMBuffer = nil; + + if (self.audioPlayer) { + [self.audioPlayer stop]; + self.audioPlayer = nil; + } + + self.audioBuffer = nil; + self.isPlaying = NO; + self.hasListeners = NO; + self.appIsActive = NO; + + [self maybeDeactivateAudioSession]; + + DGLogDebug(@"[Deepgram] invalidate: finished"); + [super invalidate]; +} + /* ================================================================== */ /* 3. AVAUDIOPLAYER DELEGATE METHODS */ /* ================================================================== */ diff --git a/src/useDeepgramSpeechToText.ts b/src/useDeepgramSpeechToText.ts index 3fceda5..0326fd1 100644 --- a/src/useDeepgramSpeechToText.ts +++ b/src/useDeepgramSpeechToText.ts @@ -70,7 +70,10 @@ export function useDeepgramSpeechToText({ const lastFinalTranscriptRef = useRef(''); const closeEverything = () => { - audioSub.current?.remove(); + if (audioSub.current) { + audioSub.current.remove(); + audioSub.current = null; + } Deepgram.stopRecording().catch(() => {}); nativeInputSampleRateRef.current = BASE_NATIVE_SAMPLE_RATE; targetSampleRateRef.current = DEFAULT_SAMPLE_RATE; From a45e757af2b5c0a987f0555e323c816291c6fc03 Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 15:56:11 -0500 Subject: [PATCH 07/10] Refactor code structure and remove redundant sections for improved readability and maintainability --- .../.gitignore | 66 -- react-native-live-audio-stream-master/LICENSE | 21 - .../README.md | 78 -- .../RNLiveAudioStream.podspec | 22 - .../android/build.gradle | 26 - .../android/src/main/AndroidManifest.xml | 5 - .../RNLiveAudioStreamModule.java | 117 -- .../RNLiveAudioStreamPackage.java | 25 - .../index.d.ts | 30 - .../index.js | 24 - .../ios/RNLiveAudioStream.h | 19 - .../ios/RNLiveAudioStream.m | 103 -- .../project.pbxproj | 263 ----- .../xcschemes/RNLiveAudioStream.xcscheme | 67 -- .../package.json | 33 - .../yarn.lock | 1043 ----------------- 16 files changed, 1942 deletions(-) delete mode 100644 react-native-live-audio-stream-master/.gitignore delete mode 100644 react-native-live-audio-stream-master/LICENSE delete mode 100644 react-native-live-audio-stream-master/README.md delete mode 100644 react-native-live-audio-stream-master/RNLiveAudioStream.podspec delete mode 100644 react-native-live-audio-stream-master/android/build.gradle delete mode 100644 react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml delete mode 100644 react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java delete mode 100644 react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java delete mode 100644 react-native-live-audio-stream-master/index.d.ts delete mode 100644 react-native-live-audio-stream-master/index.js delete mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.h delete mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.m delete mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj delete mode 100644 react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme delete mode 100644 react-native-live-audio-stream-master/package.json delete mode 100644 react-native-live-audio-stream-master/yarn.lock diff --git a/react-native-live-audio-stream-master/.gitignore b/react-native-live-audio-stream-master/.gitignore deleted file mode 100644 index ee3af70..0000000 --- a/react-native-live-audio-stream-master/.gitignore +++ /dev/null @@ -1,66 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log - -# BUCK -buck-out/ -\.buckd/ -*.keystore -!debug.keystore - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ - -*/fastlane/report.xml -*/fastlane/Preview.html -*/fastlane/screenshots - -# Bundle artifact -*.jsbundle - -# CocoaPods -/ios/Pods/ - -# Visual Studio Code -**/.classpath -**/.project -**/.settings -**/.factorypath -.vscode diff --git a/react-native-live-audio-stream-master/LICENSE b/react-native-live-audio-stream-master/LICENSE deleted file mode 100644 index 6f495ef..0000000 --- a/react-native-live-audio-stream-master/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2020 Qi Xi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/react-native-live-audio-stream-master/README.md b/react-native-live-audio-stream-master/README.md deleted file mode 100644 index 7ce0036..0000000 --- a/react-native-live-audio-stream-master/README.md +++ /dev/null @@ -1,78 +0,0 @@ - -# react-native-live-audio-stream -[![npm](https://img.shields.io/npm/v/react-native-live-audio-stream)](https://www.npmjs.com/package/react-native-live-audio-stream) - -Get live audio stream data for React Native. Ideal for live voice recognition (transcribing). - -This module is modified from [react-native-audio-record](https://github.com/goodatlas/react-native-audio-record). Instead of saving to an audio file, it only emit events with live data. By doing this, it can reduce memory usage and eliminate file operation overheads in the case that an audio file is not necessary (e.g. live transcribing). - -Most of the code was written by the respective original authors. - -## Install -``` -yarn add react-native-live-audio-stream -cd ios -pod install -``` - -## Add Microphone Permissions - -### iOS -Add these lines to ```ios/[YOU_APP_NAME]/info.plist``` -```xml -NSMicrophoneUsageDescription -We need your permission to use the microphone. -``` - -### Android -Add the following line to ```android/app/src/main/AndroidManifest.xml``` -```xml - -``` - -## Usage -```javascript -import LiveAudioStream from 'react-native-live-audio-stream'; - -const options = { - sampleRate: 32000, // default is 44100 but 32000 is adequate for accurate voice recognition - channels: 1, // 1 or 2, default 1 - bitsPerSample: 16, // 8 or 16, default 16 - audioSource: 6, // android only (see below) - bufferSize: 4096 // default is 2048 -}; - -LiveAudioStream.init(options); -LiveAudioStream.on('data', data => { - // base64-encoded audio data chunks -}); - ... -LiveAudioStream.start(); - ... -LiveAudioStream.stop(); - ... -``` - -`audioSource` should be one of the constant values from [here](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource). Default value is `6` (`VOICE_RECOGNITION`). - -Use 3rd-party modules like [buffer](https://www.npmjs.com/package/buffer) to decode base64 data. Example: -```javascript -// yarn add buffer -import { Buffer } from 'buffer'; - ... -LiveAudioStream.on('data', data => { - var chunk = Buffer.from(data, 'base64'); -}); -``` - -## Credits/References -- [react-native-audio-record](https://github.com/goodatlas/react-native-audio-record) -- iOS [Audio Queues](https://developer.apple.com/library/content/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide) -- Android [AudioRecord](https://developer.android.com/reference/android/media/AudioRecord.html) -- [cordova-plugin-audioinput](https://github.com/edimuj/cordova-plugin-audioinput) -- [react-native-recording](https://github.com/qiuxiang/react-native-recording) -- [SpeakHere](https://github.com/shaojiankui/SpeakHere) -- [ringdroid](https://github.com/google/ringdroid) - -## License -MIT diff --git a/react-native-live-audio-stream-master/RNLiveAudioStream.podspec b/react-native-live-audio-stream-master/RNLiveAudioStream.podspec deleted file mode 100644 index f7307f5..0000000 --- a/react-native-live-audio-stream-master/RNLiveAudioStream.podspec +++ /dev/null @@ -1,22 +0,0 @@ -require 'json' - -package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) - -Pod::Spec.new do |s| - s.name = "RNLiveAudioStream" - s.version = package['version'] - s.summary = package['description'] - s.description = package['description'] - s.license = package['license'] - s.author = package['author'] - s.homepage = "https://github.com/xiqi/react-native-live-audio-stream" - s.source = { :git => 'https://github.com/xiqi/react-native-live-audio-stream.git' } - - s.requires_arc = true - s.ios.deployment_target = '8.0' - - s.preserve_paths = 'README.md', 'package.json', 'index.js' - s.source_files = 'iOS/*.{h,m}' - - s.dependency 'React' -end diff --git a/react-native-live-audio-stream-master/android/build.gradle b/react-native-live-audio-stream-master/android/build.gradle deleted file mode 100644 index de6cdf1..0000000 --- a/react-native-live-audio-stream-master/android/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -apply plugin: 'com.android.library' - -def DEFAULT_COMPILE_SDK_VERSION = 27 -def DEFAULT_BUILD_TOOLS_VERSION = "27.0.3" -def DEFAULT_MIN_SDK_VERSION = 16 -def DEFAULT_TARGET_SDK_VERSION = 26 - -def safeExtGet(prop, fallback) { - rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback -} - -android { - compileSdkVersion safeExtGet("compileSdkVersion", DEFAULT_COMPILE_SDK_VERSION) - buildToolsVersion safeExtGet("buildToolsVersion", DEFAULT_BUILD_TOOLS_VERSION) - - defaultConfig { - minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) - targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) - versionCode 1 - versionName "1.0" - } -} - -dependencies { - compileOnly 'com.facebook.react:react-native:+' -} diff --git a/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml b/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml deleted file mode 100644 index e96278e..0000000 --- a/react-native-live-audio-stream-master/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java deleted file mode 100644 index 20f454f..0000000 --- a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamModule.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.imxiqi.rnliveaudiostream; - -import android.media.AudioFormat; -import android.media.AudioRecord; -import android.media.MediaRecorder.AudioSource; -import android.util.Base64; -import android.util.Log; - -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.modules.core.DeviceEventManagerModule; - -import java.lang.Math; - -public class RNLiveAudioStreamModule extends ReactContextBaseJavaModule { - - private final ReactApplicationContext reactContext; - private DeviceEventManagerModule.RCTDeviceEventEmitter eventEmitter; - - private int sampleRateInHz; - private int channelConfig; - private int audioFormat; - private int audioSource; - - private AudioRecord recorder; - private int bufferSize; - private boolean isRecording; - - public RNLiveAudioStreamModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return "RNLiveAudioStream"; - } - - @ReactMethod - public void init(ReadableMap options) { - sampleRateInHz = 44100; - if (options.hasKey("sampleRate")) { - sampleRateInHz = options.getInt("sampleRate"); - } - - channelConfig = AudioFormat.CHANNEL_IN_MONO; - if (options.hasKey("channels")) { - if (options.getInt("channels") == 2) { - channelConfig = AudioFormat.CHANNEL_IN_STEREO; - } - } - - audioFormat = AudioFormat.ENCODING_PCM_16BIT; - if (options.hasKey("bitsPerSample")) { - if (options.getInt("bitsPerSample") == 8) { - audioFormat = AudioFormat.ENCODING_PCM_8BIT; - } - } - - audioSource = AudioSource.VOICE_RECOGNITION; - if (options.hasKey("audioSource")) { - audioSource = options.getInt("audioSource"); - } - - isRecording = false; - eventEmitter = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); - - bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); - - if (options.hasKey("bufferSize")) { - bufferSize = Math.max(bufferSize, options.getInt("bufferSize")); - } - - int recordingBufferSize = bufferSize * 3; - recorder = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, recordingBufferSize); - } - - @ReactMethod - public void start() { - isRecording = true; - recorder.startRecording(); - - Thread recordingThread = new Thread(new Runnable() { - public void run() { - try { - int bytesRead; - int count = 0; - String base64Data; - byte[] buffer = new byte[bufferSize]; - - while (isRecording) { - bytesRead = recorder.read(buffer, 0, buffer.length); - - // skip first 2 buffers to eliminate "click sound" - if (bytesRead > 0 && ++count > 2) { - base64Data = Base64.encodeToString(buffer, Base64.NO_WRAP); - eventEmitter.emit("data", base64Data); - } - } - recorder.stop(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }); - - recordingThread.start(); - } - - @ReactMethod - public void stop(Promise promise) { - isRecording = false; - } -} diff --git a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java b/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java deleted file mode 100644 index 2268d6d..0000000 --- a/react-native-live-audio-stream-master/android/src/main/java/com/imxiqi/rnliveaudiostream/RNLiveAudioStreamPackage.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.imxiqi.rnliveaudiostream; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class RNLiveAudioStreamPackage implements ReactPackage { - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - modules.add(new RNLiveAudioStreamModule(reactContext)); - return modules; - } - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} diff --git a/react-native-live-audio-stream-master/index.d.ts b/react-native-live-audio-stream-master/index.d.ts deleted file mode 100644 index b846438..0000000 --- a/react-native-live-audio-stream-master/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -declare module "react-native-live-audio-stream" { - export interface IAudioRecord { - init: (options: Options) => void - start: () => void - stop: () => Promise - on: (event: "data", callback: (data: string) => void) => void - } - - export interface Options { - sampleRate: number - /** - * - `1 | 2` - */ - channels: number - /** - * - `8 | 16` - */ - bitsPerSample: number - /** - * - `6` - */ - audioSource?: number - wavFile: string - bufferSize?: number - } - - const AudioRecord: IAudioRecord - - export default AudioRecord; -} diff --git a/react-native-live-audio-stream-master/index.js b/react-native-live-audio-stream-master/index.js deleted file mode 100644 index c5968b2..0000000 --- a/react-native-live-audio-stream-master/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { NativeModules, NativeEventEmitter } from 'react-native'; -const { RNLiveAudioStream } = NativeModules; -const EventEmitter = new NativeEventEmitter(RNLiveAudioStream); - -const AudioRecord = {}; - -AudioRecord.init = options => RNLiveAudioStream.init(options); -AudioRecord.start = () => RNLiveAudioStream.start(); -AudioRecord.stop = () => RNLiveAudioStream.stop(); - -const eventsMap = { - data: 'data' -}; - -AudioRecord.on = (event, callback) => { - const nativeEvent = eventsMap[event]; - if (!nativeEvent) { - throw new Error('Invalid event'); - } - EventEmitter.removeAllListeners(nativeEvent); - return EventEmitter.addListener(nativeEvent, callback); -}; - -export default AudioRecord; diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h deleted file mode 100644 index 8ade6b5..0000000 --- a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.h +++ /dev/null @@ -1,19 +0,0 @@ -#import -#import -#import - -#define kNumberBuffers 3 - -typedef struct { - __unsafe_unretained id mSelf; - AudioStreamBasicDescription mDataFormat; - AudioQueueRef mQueue; - AudioQueueBufferRef mBuffers[kNumberBuffers]; - UInt32 bufferByteSize; - SInt64 mCurrentPacket; - bool mIsRunning; -} AQRecordState; - -@interface RNLiveAudioStream: RCTEventEmitter - @property (nonatomic, assign) AQRecordState recordState; -@end diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m deleted file mode 100644 index a6f2317..0000000 --- a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.m +++ /dev/null @@ -1,103 +0,0 @@ -#import "RNLiveAudioStream.h" - -@implementation RNLiveAudioStream - -RCT_EXPORT_MODULE(); - -RCT_EXPORT_METHOD(init:(NSDictionary *) options) { - RCTLogInfo(@"[RNLiveAudioStream] init"); - _recordState.mDataFormat.mSampleRate = options[@"sampleRate"] == nil ? 44100 : [options[@"sampleRate"] doubleValue]; - _recordState.mDataFormat.mBitsPerChannel = options[@"bitsPerSample"] == nil ? 16 : [options[@"bitsPerSample"] unsignedIntValue]; - _recordState.mDataFormat.mChannelsPerFrame = options[@"channels"] == nil ? 1 : [options[@"channels"] unsignedIntValue]; - _recordState.mDataFormat.mBytesPerPacket = (_recordState.mDataFormat.mBitsPerChannel / 8) * _recordState.mDataFormat.mChannelsPerFrame; - _recordState.mDataFormat.mBytesPerFrame = _recordState.mDataFormat.mBytesPerPacket; - _recordState.mDataFormat.mFramesPerPacket = 1; - _recordState.mDataFormat.mReserved = 0; - _recordState.mDataFormat.mFormatID = kAudioFormatLinearPCM; - _recordState.mDataFormat.mFormatFlags = _recordState.mDataFormat.mBitsPerChannel == 8 ? kLinearPCMFormatFlagIsPacked : (kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked); - _recordState.bufferByteSize = options[@"bufferSize"] == nil ? 2048 : [options[@"bufferSize"] unsignedIntValue]; - _recordState.mSelf = self; -} - -RCT_EXPORT_METHOD(start) { - RCTLogInfo(@"[RNLiveAudioStream] start"); - AVAudioSession *audioSession = [AVAudioSession sharedInstance]; - NSError *error = nil; - BOOL success; - - // Apple recommended: - // Instead of setting your category and mode properties independently, set them at the same time - if (@available(iOS 10.0, *)) { - success = [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord - mode: AVAudioSessionModeVoiceChat - options: AVAudioSessionCategoryOptionDuckOthers | - AVAudioSessionCategoryOptionAllowBluetooth | - AVAudioSessionCategoryOptionAllowAirPlay - error: &error]; - } else { - success = [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord withOptions: AVAudioSessionCategoryOptionDuckOthers error: &error]; - success = [audioSession setMode: AVAudioSessionModeVoiceChat error: &error] && success; - } - if (!success || error != nil) { - RCTLog(@"[RNLiveAudioStream] Problem setting up AVAudioSession category and mode. Error: %@", error); - return; - } - - _recordState.mIsRunning = true; - - OSStatus status = AudioQueueNewInput(&_recordState.mDataFormat, HandleInputBuffer, &_recordState, NULL, NULL, 0, &_recordState.mQueue); - if (status != 0) { - RCTLog(@"[RNLiveAudioStream] Record Failed. Cannot initialize AudioQueueNewInput. status: %i", (int) status); - return; - } - - for (int i = 0; i < kNumberBuffers; i++) { - AudioQueueAllocateBuffer(_recordState.mQueue, _recordState.bufferByteSize, &_recordState.mBuffers[i]); - AudioQueueEnqueueBuffer(_recordState.mQueue, _recordState.mBuffers[i], 0, NULL); - } - AudioQueueStart(_recordState.mQueue, NULL); -} - -RCT_EXPORT_METHOD(stop) { - RCTLogInfo(@"[RNLiveAudioStream] stop"); - if (_recordState.mIsRunning) { - _recordState.mIsRunning = false; - AudioQueueStop(_recordState.mQueue, true); - for (int i = 0; i < kNumberBuffers; i++) { - AudioQueueFreeBuffer(_recordState.mQueue, _recordState.mBuffers[i]); - } - AudioQueueDispose(_recordState.mQueue, true); - } -} - -void HandleInputBuffer(void *inUserData, - AudioQueueRef inAQ, - AudioQueueBufferRef inBuffer, - const AudioTimeStamp *inStartTime, - UInt32 inNumPackets, - const AudioStreamPacketDescription *inPacketDesc) { - AQRecordState* pRecordState = (AQRecordState *)inUserData; - - if (!pRecordState->mIsRunning) { - return; - } - - short *samples = (short *) inBuffer->mAudioData; - long nsamples = inBuffer->mAudioDataByteSize; - NSData *data = [NSData dataWithBytes:samples length:nsamples]; - NSString *str = [data base64EncodedStringWithOptions:0]; - [pRecordState->mSelf sendEventWithName:@"data" body:str]; - - AudioQueueEnqueueBuffer(pRecordState->mQueue, inBuffer, 0, NULL); -} - -- (NSArray *)supportedEvents { - return @[@"data"]; -} - -- (void)dealloc { - RCTLogInfo(@"[RNLiveAudioStream] dealloc"); - AudioQueueDispose(_recordState.mQueue, true); -} - -@end diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj deleted file mode 100644 index b06bf4d..0000000 --- a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/project.pbxproj +++ /dev/null @@ -1,263 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 49C5D751243FA7A5003D418B /* RNLiveAudioStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 58B511D91A9E6C8500147676 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNLiveAudioStream.m; sourceTree = ""; }; - 49C5D750243FA7A5003D418B /* RNLiveAudioStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNLiveAudioStream.h; sourceTree = ""; }; - 49C5D76024407376003D418B /* libRNLiveAudioStream.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNLiveAudioStream.a; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 58B511D81A9E6C8500147676 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 58B511D21A9E6C8500147676 = { - isa = PBXGroup; - children = ( - 49C5D750243FA7A5003D418B /* RNLiveAudioStream.h */, - 49C5D74F243FA7A5003D418B /* RNLiveAudioStream.m */, - 49C5D76024407376003D418B /* libRNLiveAudioStream.a */, - ); - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 49C5D757243FC397003D418B /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 58B511DA1A9E6C8500147676 /* RNLiveAudioStream */ = { - isa = PBXNativeTarget; - buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNLiveAudioStream" */; - buildPhases = ( - 49C5D757243FC397003D418B /* Headers */, - 58B511D71A9E6C8500147676 /* Sources */, - 58B511D81A9E6C8500147676 /* Frameworks */, - 58B511D91A9E6C8500147676 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = RNLiveAudioStream; - productName = RCTDataManager; - productReference = 49C5D76024407376003D418B /* libRNLiveAudioStream.a */; - productType = "com.apple.product-type.library.static"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 58B511D31A9E6C8500147676 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0830; - ORGANIZATIONNAME = "Qi Xi"; - TargetAttributes = { - 58B511DA1A9E6C8500147676 = { - CreatedOnToolsVersion = 6.1.1; - }; - }; - }; - buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNLiveAudioStream" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - English, - en, - ); - mainGroup = 58B511D21A9E6C8500147676; - productRefGroup = 58B511D21A9E6C8500147676; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 58B511DA1A9E6C8500147676 /* RNLiveAudioStream */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXSourcesBuildPhase section */ - 58B511D71A9E6C8500147676 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 49C5D751243FA7A5003D418B /* RNLiveAudioStream.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 58B511ED1A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 58B511EE1A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 58B511F01A9E6C8500147676 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - "$(SRCROOT)/../../react-native/React/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RNLiveAudioStream; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 58B511F11A9E6C8500147676 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../../../React/**", - "$(SRCROOT)/../../react-native/React/**", - ); - LIBRARY_SEARCH_PATHS = "$(inherited)"; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = RNLiveAudioStream; - SKIP_INSTALL = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNLiveAudioStream" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511ED1A9E6C8500147676 /* Debug */, - 58B511EE1A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNLiveAudioStream" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 58B511F01A9E6C8500147676 /* Debug */, - 58B511F11A9E6C8500147676 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 58B511D31A9E6C8500147676 /* Project object */; -} diff --git a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme b/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme deleted file mode 100644 index fc5b100..0000000 --- a/react-native-live-audio-stream-master/ios/RNLiveAudioStream.xcodeproj/xcshareddata/xcschemes/RNLiveAudioStream.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/react-native-live-audio-stream-master/package.json b/react-native-live-audio-stream-master/package.json deleted file mode 100644 index 5e478d4..0000000 --- a/react-native-live-audio-stream-master/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "react-native-live-audio-stream", - "version": "1.1.1", - "description": "Get live audio stream data for React Native", - "license": "MIT", - "keywords": [ - "live stream", - "react-native", - "audio", - "live", - "recording", - "voice" - ], - "main": "index.js", - "homepage": "https://github.com/xiqi/react-native-live-audio-stream", - "bugs": { - "url": "https://github.com/xiqi/react-native-live-audio-stream/issues", - "email": "me@imxiqi.com" - }, - "repository": { - "type": "git", - "url": "https://github.com/xiqi/react-native-live-audio-stream.git" - }, - "author": "Qi Xi (https://www.imxiqi.com/)", - "devDependencies": { - "babel-eslint": "^8.2.3", - "eslint": "^4.19.1", - "eslint-plugin-prettier": "^2.6.0", - "prettier": "^1.12.0" - }, - "dependencies": {}, - "peerDependencies": {} -} \ No newline at end of file diff --git a/react-native-live-audio-stream-master/yarn.lock b/react-native-live-audio-stream-master/yarn.lock deleted file mode 100644 index d8e3823..0000000 --- a/react-native-live-audio-stream-master/yarn.lock +++ /dev/null @@ -1,1043 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" - integrity sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g== - dependencies: - "@babel/highlight" "7.0.0-beta.44" - -"@babel/generator@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" - integrity sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ== - dependencies: - "@babel/types" "7.0.0-beta.44" - jsesc "^2.5.1" - lodash "^4.2.0" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/helper-function-name@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" - integrity sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg== - dependencies: - "@babel/helper-get-function-arity" "7.0.0-beta.44" - "@babel/template" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - -"@babel/helper-get-function-arity@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" - integrity sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw== - dependencies: - "@babel/types" "7.0.0-beta.44" - -"@babel/helper-split-export-declaration@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" - integrity sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA== - dependencies: - "@babel/types" "7.0.0-beta.44" - -"@babel/highlight@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" - integrity sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ== - dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^3.0.0" - -"@babel/template@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" - integrity sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng== - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - lodash "^4.2.0" - -"@babel/traverse@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" - integrity sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA== - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/generator" "7.0.0-beta.44" - "@babel/helper-function-name" "7.0.0-beta.44" - "@babel/helper-split-export-declaration" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - debug "^3.1.0" - globals "^11.1.0" - invariant "^2.2.0" - lodash "^4.2.0" - -"@babel/types@7.0.0-beta.44": - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" - integrity sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ== - dependencies: - esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^2.0.0" - -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= - dependencies: - acorn "^3.0.4" - -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= - -acorn@^5.5.0: - version "5.7.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" - integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== - -ajv-keywords@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" - integrity sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= - -ajv@^5.2.3, ajv@^5.3.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-eslint@^8.2.3: - version "8.2.6" - resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.6.tgz#6270d0c73205628067c0f7ae1693a9e797acefd9" - integrity sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA== - dependencies: - "@babel/code-frame" "7.0.0-beta.44" - "@babel/traverse" "7.0.0-beta.44" - "@babel/types" "7.0.0-beta.44" - babylon "7.0.0-beta.44" - eslint-scope "3.7.1" - eslint-visitor-keys "^1.0.0" - -babylon@7.0.0-beta.44: - version "7.0.0-beta.44" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" - integrity sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0, chalk@^2.1.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chardet@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" - integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= - -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cross-spawn@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" - integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= - dependencies: - lru-cache "^4.0.1" - shebang-command "^1.2.0" - which "^1.2.9" - -debug@^3.1.0: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -eslint-plugin-prettier@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz#b4312dcf2c1d965379d7f9d5b5f8aaadc6a45904" - integrity sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA== - dependencies: - fast-diff "^1.1.1" - jest-docblock "^21.0.0" - -eslint-scope@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" - integrity sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^3.7.1: - version "3.7.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" - integrity sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-visitor-keys@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" - integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== - -eslint@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" - integrity sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ== - dependencies: - ajv "^5.3.0" - babel-code-frame "^6.22.0" - chalk "^2.1.0" - concat-stream "^1.6.0" - cross-spawn "^5.1.0" - debug "^3.1.0" - doctrine "^2.1.0" - eslint-scope "^3.7.1" - eslint-visitor-keys "^1.0.0" - espree "^3.5.4" - esquery "^1.0.0" - esutils "^2.0.2" - file-entry-cache "^2.0.0" - functional-red-black-tree "^1.0.1" - glob "^7.1.2" - globals "^11.0.1" - ignore "^3.3.3" - imurmurhash "^0.1.4" - inquirer "^3.0.6" - is-resolvable "^1.0.0" - js-yaml "^3.9.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.4" - minimatch "^3.0.2" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.2" - path-is-inside "^1.0.2" - pluralize "^7.0.0" - progress "^2.0.0" - regexpp "^1.0.1" - require-uncached "^1.0.3" - semver "^5.3.0" - strip-ansi "^4.0.0" - strip-json-comments "~2.0.1" - table "4.0.2" - text-table "~0.2.0" - -espree@^3.5.4: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" - integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== - dependencies: - estraverse "^5.0.0" - -esrecurse@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== - dependencies: - estraverse "^4.1.0" - -estraverse@^4.1.0, estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" - integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -external-editor@^2.0.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" - integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A== - dependencies: - chardet "^0.4.0" - iconv-lite "^0.4.17" - tmp "^0.0.33" - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-diff@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== - dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - -glob@^7.1.2, glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.0.1, globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -graceful-fs@^4.1.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -iconv-lite@^0.4.17: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore@^3.3.3: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inquirer@^3.0.6: - version "3.3.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" - integrity sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ== - dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^2.0.4" - figures "^2.0.0" - lodash "^4.3.0" - mute-stream "0.0.7" - run-async "^2.2.0" - rx-lite "^4.0.8" - rx-lite-aggregates "^4.0.8" - string-width "^2.1.0" - strip-ansi "^4.0.0" - through "^2.3.6" - -invariant@^2.2.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -jest-docblock@^21.0.0: - version "21.2.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" - integrity sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw== - -js-tokens@^3.0.0, js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -"js-tokens@^3.0.0 || ^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.9.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -loose-envify@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" - integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== - dependencies: - js-tokens "^3.0.0 || ^4.0.0" - -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -minimatch@^3.0.2, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -object-assign@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -optionator@^0.8.2: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-is-inside@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prettier@^1.12.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -readable-stream@^2.2.2: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -regexpp@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" - integrity sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw== - -require-uncached@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -rimraf@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - -run-async@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" - integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== - dependencies: - is-promise "^2.1.0" - -rx-lite-aggregates@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" - integrity sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= - dependencies: - rx-lite "*" - -rx-lite@*, rx-lite@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" - integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^5.3.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -slice-ansi@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" - integrity sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg== - dependencies: - is-fullwidth-code-point "^2.0.0" - -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -string-width@^2.1.0, string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -table@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" - integrity sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA== - dependencies: - ajv "^5.2.3" - ajv-keywords "^2.1.0" - chalk "^2.1.0" - lodash "^4.17.4" - slice-ansi "1.0.0" - string-width "^2.1.1" - -text-table@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= - dependencies: - mkdirp "^0.5.1" - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= From fdbd1ad5117136fbc9828025e2b92f10cded51f9 Mon Sep 17 00:00:00 2001 From: Rares Date: Mon, 10 Nov 2025 16:05:59 -0500 Subject: [PATCH 08/10] feat: enhance audio session configuration with retry logic for insufficient priority --- ios/Deepgram.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ios/Deepgram.mm b/ios/Deepgram.mm index b1a3ed8..dbece81 100644 --- a/ios/Deepgram.mm +++ b/ios/Deepgram.mm @@ -278,6 +278,19 @@ - (BOOL)configureAudioSession:(NSError **)outError NSError *activeError = nil; BOOL activeSuccess = [session setActive:YES error:&activeError]; + if (!activeSuccess && activeError.code == AVAudioSessionErrorCodeInsufficientPriority) { + DGLogWarn(@"[Deepgram] configureAudioSession: insufficient priority, retrying with mix-with-others"); + NSError *retryError = nil; + BOOL categoryRetry = [session setCategory:session.category + mode:session.mode + options:options | AVAudioSessionCategoryOptionMixWithOthers + error:&retryError]; + if (!categoryRetry) { + DGLogWarn(@"[Deepgram] configureAudioSession: unable to update category options %@", retryError.localizedDescription ?: retryError); + } + activeError = nil; + activeSuccess = [session setActive:YES error:&activeError]; + } if (!activeSuccess || activeError) { DGLogError(@"[Deepgram] configureAudioSession: setActive failed error=%@", activeError.localizedDescription ?: activeError); From f83e276146d2b7a9afb923eb712a8243bb077faa Mon Sep 17 00:00:00 2001 From: Rares Date: Wed, 12 Nov 2025 10:58:28 -0500 Subject: [PATCH 09/10] feat: add Expo config plugin for microphone permissions and enhance settings merging logic --- README.md | 57 +++++++++--- src/useDeepgramVoiceAgent.ts | 169 +++++++++++++++++++++++++++++------ 2 files changed, 190 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 538c31d..a676858 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,18 @@ 1. [Features](#features) 2. [Installation](#installation) -3. [Configuration](#configuration) -4. [Usage overview](#usage-overview) -5. [Voice Agent](#voice-agent-usedeepgramvoiceagent) -6. [Speech-to-Text](#speech-to-text-usedeepgramspeechtotext) -7. [Text-to-Speech](#text-to-speech-usedeepgramtexttospeech) -8. [Text Intelligence](#text-intelligence-usedeepgramtextintelligence) -9. [Management API](#management-api-usedeepgrammanagement) -10. [Example app](#example-app) -11. [Roadmap](#roadmap) -12. [Contributing](#contributing) -13. [License](#license) +3. [Expo config plugin](#expo-config-plugin) +4. [Configuration](#configuration) +5. [Usage overview](#usage-overview) +6. [Voice Agent](#voice-agent-usedeepgramvoiceagent) +7. [Speech-to-Text](#speech-to-text-usedeepgramspeechtotext) +8. [Text-to-Speech](#text-to-speech-usedeepgramtexttospeech) +9. [Text Intelligence](#text-intelligence-usedeepgramtextintelligence) +10. [Management API](#management-api-usedeepgrammanagement) +11. [Example app](#example-app) +12. [Roadmap](#roadmap) +13. [Contributing](#contributing) +14. [License](#license) --- @@ -77,6 +78,40 @@ npx expo run:ios # or expo run:android --- +## Expo config plugin + +The package ships with an Expo config plugin (exported from `app.plugin.js`) that keeps microphone permissions in sync for both +platforms: + +- **Android** – automatically adds `android.permission.RECORD_AUDIO` to your manifest if it is missing. +- **iOS** – sets `NSMicrophoneUsageDescription` with the message you provide (or a sensible fallback). + +### Options + +You can customise the iOS prompt via the `microphonePermission` option: + +```js +// app.config.js +module.exports = { + expo: { + plugins: [ + [ + 'react-native-deepgram', + { + microphonePermission: + 'Allow $(PRODUCT_NAME) to capture audio for real-time transcription.', + }, + ], + ], + }, +}; +``` + +> 🧭 Need the plugin in a bare React Native project? Import it via +> `require('react-native-deepgram/app.plugin.js')` in your config plugin pipeline. + +--- + ## Configuration ```ts diff --git a/src/useDeepgramVoiceAgent.ts b/src/useDeepgramVoiceAgent.ts index 692238d..3cdce97 100644 --- a/src/useDeepgramVoiceAgent.ts +++ b/src/useDeepgramVoiceAgent.ts @@ -53,10 +53,73 @@ const ensureArrayBuffer = (data: any): ArrayBuffer | null => { return null; }; +const isPlainObject = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const cloneValue = (value: T): T => { + if (Array.isArray(value)) { + return value.map((item) => cloneValue(item)) as unknown as T; + } + + if (isPlainObject(value)) { + const cloned: Record = {}; + Object.entries(value).forEach(([key, entryValue]) => { + cloned[key] = cloneValue(entryValue); + }); + return cloned as T; + } + + return value; +}; + +const mergePlainObjects = >( + base?: T, + override?: T +): T | undefined => { + if (!base && !override) { + return undefined; + } + + if (!base) { + return override ? (cloneValue(override) as T) : undefined; + } + + const result = cloneValue(base) as Record; + + if (!override) { + return result as T; + } + + Object.entries(override).forEach(([key, overrideValue]) => { + if (overrideValue === undefined) { + result[key] = undefined; + return; + } + + if (isPlainObject(overrideValue)) { + const existing = result[key]; + result[key] = mergePlainObjects( + isPlainObject(existing) + ? (existing as Record) + : undefined, + overrideValue + ); + return; + } + + if (Array.isArray(overrideValue)) { + result[key] = overrideValue.map((item) => cloneValue(item)); + return; + } + + result[key] = overrideValue; + }); + + return result as T; +}; + const hasKeys = (value: unknown, keys: string[]) => - typeof value === 'object' && - value !== null && - keys.every((key) => key in (value as Record)); + isPlainObject(value) && keys.every((key) => key in value); const computeDownsampleFactor = ( target: number | undefined, @@ -69,6 +132,28 @@ const computeDownsampleFactor = ( return ratio > 0 ? ratio : 1; }; +const resolveDownsampleFactor = ( + overrideFactor: number | undefined, + targetSampleRate: number | undefined, + nativeSampleRate: number | undefined +) => { + if (overrideFactor == null) { + return computeDownsampleFactor(targetSampleRate, nativeSampleRate); + } + + const normalized = Math.max(1, Math.round(overrideFactor)); + + if (!nativeSampleRate || !targetSampleRate) { + return normalized; + } + + if (nativeSampleRate <= targetSampleRate) { + return 1; + } + + return normalized; +}; + type WebSocketLike = Pick< WebSocket, | 'readyState' @@ -168,11 +253,11 @@ export function useDeepgramVoiceAgent({ const nativeInputSampleRate = useRef(BASE_NATIVE_SAMPLE_RATE); const targetInputSampleRate = useRef(DEFAULT_INPUT_SAMPLE_RATE); const currentDownsample = useRef( - downsampleFactor ?? - computeDownsampleFactor( - targetInputSampleRate.current, - nativeInputSampleRate.current - ) + resolveDownsampleFactor( + downsampleFactor, + targetInputSampleRate.current, + nativeInputSampleRate.current + ) ); const microphoneActive = useRef(false); const defaultSettingsRef = useRef(defaultSettings); @@ -211,11 +296,21 @@ export function useDeepgramVoiceAgent({ sanitized.input = { ...audio.input }; } + if (audio.output) { + sanitized.output = { ...audio.output }; + } + Object.entries(audio).forEach(([key, value]) => { if (key === 'input' || key === 'output') { return; } - (sanitized as any)[key] = value; + let clonedValue: unknown = value; + if (Array.isArray(value)) { + clonedValue = value.map((item) => cloneValue(item)); + } else if (isPlainObject(value)) { + clonedValue = cloneValue(value); + } + (sanitized as any)[key] = clonedValue; }); return Object.keys(sanitized).length > 0 ? sanitized : undefined; @@ -235,9 +330,22 @@ export function useDeepgramVoiceAgent({ if (key === 'speak') { return; } - (sanitized as any)[key] = value; + let clonedValue: unknown = value; + if (Array.isArray(value)) { + clonedValue = value.map((item) => cloneValue(item)); + } else if (isPlainObject(value)) { + clonedValue = cloneValue(value); + } + (sanitized as any)[key] = clonedValue; }); + if (agent.speak) { + sanitized.speak = { ...agent.speak }; + if (agent.speak.provider) { + sanitized.speak.provider = { ...agent.speak.provider }; + } + } + return Object.keys(sanitized).length > 0 ? sanitized : undefined; }, [] @@ -273,11 +381,22 @@ export function useDeepgramVoiceAgent({ (sanitized as any)[key] = value; }); - return sanitized; + return Object.keys(sanitized).length > 0 ? sanitized : undefined; }, [sanitizeAgentConfig, sanitizeAudioSettings] ); + const mergeSettings = useCallback( + ( + base?: DeepgramVoiceAgentSettings, + override?: DeepgramVoiceAgentSettings + ): DeepgramVoiceAgentSettings | undefined => + mergePlainObjects(base as any, override as any) as + | DeepgramVoiceAgentSettings + | undefined, + [] + ); + defaultSettingsRef.current = defaultSettings; endpointRef.current = endpoint; onBeforeConnectRef.current = onBeforeConnect; @@ -343,12 +462,11 @@ export function useDeepgramVoiceAgent({ if (typeof ev?.sampleRate === 'number' && ev.sampleRate > 0) { if (ev.sampleRate !== nativeInputSampleRate.current) { nativeInputSampleRate.current = ev.sampleRate; - if (downsampleFactor == null) { - currentDownsample.current = computeDownsampleFactor( - targetInputSampleRate.current, - nativeInputSampleRate.current - ); - } + currentDownsample.current = resolveDownsampleFactor( + downsampleFactor, + targetInputSampleRate.current, + nativeInputSampleRate.current + ); } } @@ -627,10 +745,11 @@ export function useDeepgramVoiceAgent({ const sanitizedDefault = sanitizeSettings(defaultSettingsRef.current); const sanitizedOverride = sanitizeSettings(overrideSettings); + const merged = mergeSettings(sanitizedDefault, sanitizedOverride); + const mergedSettings: DeepgramVoiceAgentSettingsMessage = { type: 'Settings', - ...(sanitizedDefault ?? {}), - ...(sanitizedOverride ?? {}), + ...(merged ?? {}), }; const targetSampleRate = @@ -638,12 +757,11 @@ export function useDeepgramVoiceAgent({ defaultSettingsRef.current?.audio?.input?.sample_rate ?? DEFAULT_INPUT_SAMPLE_RATE; targetInputSampleRate.current = targetSampleRate; - currentDownsample.current = - downsampleFactor ?? - computeDownsampleFactor( - targetInputSampleRate.current, - nativeInputSampleRate.current - ); + currentDownsample.current = resolveDownsampleFactor( + downsampleFactor, + targetInputSampleRate.current, + nativeInputSampleRate.current + ); const socket = new (WebSocket as any)(endpointRef.current, undefined, { headers: { Authorization: `Token ${apiKey}` }, @@ -671,6 +789,7 @@ export function useDeepgramVoiceAgent({ downsampleFactor, handleMicChunk, handleSocketMessage, + mergeSettings, sanitizeSettings, sendJsonMessage, ] From e3cccea855326b841d7b2fa690ee8b537016fc67 Mon Sep 17 00:00:00 2001 From: Rares Date: Wed, 12 Nov 2025 11:07:38 -0500 Subject: [PATCH 10/10] feat: update Deepgram SDK to version 1.0.5 and adjust Podfile.lock accordingly --- example/ios/Podfile.lock | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 3bc3efb..a24c59a 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - Deepgram (1.0.4): + - Deepgram (1.0.5): - DoubleConversion - glog - hermes-engine @@ -1799,7 +1799,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 1dca942403ed9342f98334bf4c3621f011aa7946 - Deepgram: 55736a9de155ae901e7ce257523b9adb48ed171a + Deepgram: 234b0f4e42380dc2129dbc9d10533eb4019b5df0 DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 EXConstants: fcfc75800824ac2d5c592b5bc74130bad17b146b Expo: 1687edb10c76b0c0f135306d6ae245379f50ed54 diff --git a/package.json b/package.json index 7a5579b..87e1094 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-deepgram", - "version": "1.0.4", + "version": "1.0.5", "description": "React Native SDK for Deepgram's AI-powered speech-to-text, real-time transcription, and text intelligence APIs. Supports live audio streaming, file transcription, sentiment analysis, and topic detection for iOS and Android.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js",