react-native-suuqencode 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
15
15
 
16
16
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
18
- s.frameworks = "VideoToolbox", "CoreMedia", "CoreFoundation"
18
+ s.frameworks = "VideoToolbox", "CoreMedia", "CoreFoundation", "AudioToolbox", "AVFoundation"
19
19
  s.dependency "SuuqeDMABuf"
20
20
 
21
21
  install_modules_dependencies(s)
package/ios/Suuqencode.h CHANGED
@@ -1,6 +1,7 @@
1
1
  #import <React/RCTEventEmitter.h>
2
2
  #import <AVFoundation/AVFoundation.h>
3
3
  #import <VideoToolbox/VideoToolbox.h>
4
+ #import <AudioToolbox/AudioToolbox.h>
4
5
 
5
6
  @interface Suuqencode : RCTEventEmitter
6
7
 
package/ios/Suuqencode.mm CHANGED
@@ -3,10 +3,21 @@
3
3
 
4
4
  @interface Suuqencode ()
5
5
 
6
+ // Video encoding properties
6
7
  @property(nonatomic) VTCompressionSessionRef compressionSession;
7
8
  @property(nonatomic) dispatch_queue_t encodeQueue;
8
9
  @property(nonatomic) int frameCount;
9
10
 
11
+ // Audio recording + FLAC encoding properties
12
+ @property(nonatomic, strong) AVAudioEngine *audioEngine;
13
+ @property(nonatomic, strong) AVAudioConverter *pcmConverter;
14
+ @property(nonatomic, strong) AVAudioConverter *flacConverter;
15
+ @property(nonatomic, strong) AVAudioFormat *targetPCMFormat;
16
+ @property(nonatomic, strong) AVAudioFormat *flacFormat;
17
+ @property(nonatomic) dispatch_queue_t audioEncodeQueue;
18
+ @property(nonatomic) BOOL isRecordingAudio;
19
+ @property(nonatomic) double audioSampleRate;
20
+
10
21
  - (void)sendEncodedData:(NSData *)data;
11
22
 
12
23
  @end
@@ -20,6 +31,8 @@ RCT_EXPORT_MODULE()
20
31
  if (self) {
21
32
  _encodeQueue = dispatch_queue_create("com.suuqencode.encodequeue",
22
33
  DISPATCH_QUEUE_SERIAL);
34
+ _audioEncodeQueue = dispatch_queue_create("com.suuqencode.audioencodequeue",
35
+ DISPATCH_QUEUE_SERIAL);
23
36
  }
24
37
  return self;
25
38
  }
@@ -173,8 +186,297 @@ void compressionOutputCallback(void *outputCallbackRefCon,
173
186
  [self sendEventWithName:@"onEncodedData" body:base64Encoded];
174
187
  }
175
188
 
189
+ #pragma mark - Audio Recording & FLAC Encoding
190
+
191
+ RCT_EXPORT_METHOD(startAudioEncode:(double)sampleRate
192
+ resolve:(RCTPromiseResolveBlock)resolve
193
+ reject:(RCTPromiseRejectBlock)reject) {
194
+ if (self.isRecordingAudio) {
195
+ reject(@"ALREADY_RECORDING", @"Audio recording is already in progress", nil);
196
+ return;
197
+ }
198
+
199
+ self.audioSampleRate = sampleRate > 0 ? sampleRate : 16000.0;
200
+
201
+ AVAudioSession *session = [AVAudioSession sharedInstance];
202
+ [session requestRecordPermission:^(BOOL granted) {
203
+ if (!granted) {
204
+ reject(@"PERMISSION_DENIED", @"Microphone permission was denied", nil);
205
+ return;
206
+ }
207
+
208
+ dispatch_async(dispatch_get_main_queue(), ^{
209
+ NSError *setupError = nil;
210
+ BOOL success = [self setupAudioCaptureWithError:&setupError];
211
+ if (!success) {
212
+ reject(@"SETUP_FAILED",
213
+ setupError ? setupError.localizedDescription : @"Failed to set up audio capture",
214
+ setupError);
215
+ return;
216
+ }
217
+
218
+ // Emit format info so the receiver knows the audio parameters
219
+ NSDictionary *formatInfo = @{
220
+ @"sampleRate": @(self.audioSampleRate),
221
+ @"channels": @(1),
222
+ @"bitsPerSample": @(16),
223
+ @"codec": @"flac"
224
+ };
225
+ [self sendEventWithName:@"onAudioFormatInfo" body:formatInfo];
226
+ resolve(@(YES));
227
+ });
228
+ }];
229
+ }
230
+
231
+ RCT_EXPORT_METHOD(stopAudioEncode) {
232
+ if (!self.isRecordingAudio) {
233
+ return;
234
+ }
235
+
236
+ self.isRecordingAudio = NO;
237
+
238
+ if (self.audioEngine) {
239
+ [self.audioEngine.inputNode removeTapOnBus:0];
240
+ [self.audioEngine stop];
241
+ self.audioEngine = nil;
242
+ }
243
+
244
+ self.pcmConverter = nil;
245
+ self.flacConverter = nil;
246
+ self.targetPCMFormat = nil;
247
+ self.flacFormat = nil;
248
+ }
249
+
250
+ - (BOOL)setupAudioCaptureWithError:(NSError **)outError {
251
+ AVAudioSession *session = [AVAudioSession sharedInstance];
252
+ NSError *error = nil;
253
+
254
+ [session setCategory:AVAudioSessionCategoryPlayAndRecord
255
+ withOptions:(AVAudioSessionCategoryOptionDefaultToSpeaker |
256
+ AVAudioSessionCategoryOptionAllowBluetooth)
257
+ error:&error];
258
+ if (error) {
259
+ if (outError) *outError = error;
260
+ return NO;
261
+ }
262
+
263
+ [session setActive:YES error:&error];
264
+ if (error) {
265
+ if (outError) *outError = error;
266
+ return NO;
267
+ }
268
+
269
+ // Initialize audio engine
270
+ self.audioEngine = [[AVAudioEngine alloc] init];
271
+ AVAudioInputNode *inputNode = self.audioEngine.inputNode;
272
+ AVAudioFormat *hardwareFormat = [inputNode outputFormatForBus:0];
273
+
274
+ if (!hardwareFormat || hardwareFormat.sampleRate == 0) {
275
+ if (outError) {
276
+ *outError = [NSError errorWithDomain:@"SuuqencodeAudio"
277
+ code:-1
278
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not get hardware audio format"}];
279
+ }
280
+ return NO;
281
+ }
282
+
283
+ // Target PCM format: 16-bit signed integer, mono, at requested sample rate
284
+ AudioStreamBasicDescription pcmASBD = {};
285
+ pcmASBD.mFormatID = kAudioFormatLinearPCM;
286
+ pcmASBD.mSampleRate = self.audioSampleRate;
287
+ pcmASBD.mChannelsPerFrame = 1;
288
+ pcmASBD.mBitsPerChannel = 16;
289
+ pcmASBD.mBytesPerFrame = 2;
290
+ pcmASBD.mBytesPerPacket = 2;
291
+ pcmASBD.mFramesPerPacket = 1;
292
+ pcmASBD.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
293
+
294
+ self.targetPCMFormat = [[AVAudioFormat alloc] initWithStreamDescription:&pcmASBD];
295
+ if (!self.targetPCMFormat) {
296
+ if (outError) {
297
+ *outError = [NSError errorWithDomain:@"SuuqencodeAudio"
298
+ code:-2
299
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not create target PCM format"}];
300
+ }
301
+ return NO;
302
+ }
303
+
304
+ // FLAC output format
305
+ AudioStreamBasicDescription flacASBD = {};
306
+ flacASBD.mFormatID = kAudioFormatFLAC;
307
+ flacASBD.mSampleRate = self.audioSampleRate;
308
+ flacASBD.mChannelsPerFrame = 1;
309
+ flacASBD.mBitsPerChannel = 16;
310
+ flacASBD.mFramesPerPacket = 0;
311
+ flacASBD.mBytesPerPacket = 0;
312
+ flacASBD.mBytesPerFrame = 0;
313
+ flacASBD.mFormatFlags = 0;
314
+
315
+ self.flacFormat = [[AVAudioFormat alloc] initWithStreamDescription:&flacASBD];
316
+ if (!self.flacFormat) {
317
+ if (outError) {
318
+ *outError = [NSError errorWithDomain:@"SuuqencodeAudio"
319
+ code:-3
320
+ userInfo:@{NSLocalizedDescriptionKey: @"FLAC audio format not available on this device"}];
321
+ }
322
+ return NO;
323
+ }
324
+
325
+ // PCM resampler: hardware format → target PCM
326
+ self.pcmConverter = [[AVAudioConverter alloc] initFromFormat:hardwareFormat
327
+ toFormat:self.targetPCMFormat];
328
+ if (!self.pcmConverter) {
329
+ if (outError) {
330
+ *outError = [NSError errorWithDomain:@"SuuqencodeAudio"
331
+ code:-4
332
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not create PCM resampler"}];
333
+ }
334
+ return NO;
335
+ }
336
+
337
+ // FLAC encoder: target PCM → FLAC
338
+ self.flacConverter = [[AVAudioConverter alloc] initFromFormat:self.targetPCMFormat
339
+ toFormat:self.flacFormat];
340
+ if (!self.flacConverter) {
341
+ if (outError) {
342
+ *outError = [NSError errorWithDomain:@"SuuqencodeAudio"
343
+ code:-5
344
+ userInfo:@{NSLocalizedDescriptionKey: @"Could not create FLAC encoder — FLAC encoding may not be supported on this OS version"}];
345
+ }
346
+ return NO;
347
+ }
348
+
349
+ // Install tap on the input node using the hardware's native format
350
+ __weak Suuqencode *weakSelf = self;
351
+ [inputNode installTapOnBus:0
352
+ bufferSize:4096
353
+ format:hardwareFormat
354
+ block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
355
+ __strong Suuqencode *strongSelf = weakSelf;
356
+ if (!strongSelf || !strongSelf.isRecordingAudio) return;
357
+
358
+ // Copy the buffer so we can safely dispatch off the realtime audio thread
359
+ AVAudioPCMBuffer *bufferCopy = [[AVAudioPCMBuffer alloc] initWithPCMFormat:buffer.format
360
+ frameCapacity:buffer.frameLength];
361
+ bufferCopy.frameLength = buffer.frameLength;
362
+
363
+ if (hardwareFormat.commonFormat == AVAudioPCMFormatFloat32) {
364
+ for (AVAudioChannelCount ch = 0; ch < hardwareFormat.channelCount; ch++) {
365
+ memcpy(bufferCopy.floatChannelData[ch],
366
+ buffer.floatChannelData[ch],
367
+ buffer.frameLength * sizeof(float));
368
+ }
369
+ } else if (hardwareFormat.commonFormat == AVAudioPCMFormatInt16) {
370
+ for (AVAudioChannelCount ch = 0; ch < hardwareFormat.channelCount; ch++) {
371
+ memcpy(bufferCopy.int16ChannelData[ch],
372
+ buffer.int16ChannelData[ch],
373
+ buffer.frameLength * sizeof(int16_t));
374
+ }
375
+ } else if (hardwareFormat.commonFormat == AVAudioPCMFormatInt32) {
376
+ for (AVAudioChannelCount ch = 0; ch < hardwareFormat.channelCount; ch++) {
377
+ memcpy(bufferCopy.int32ChannelData[ch],
378
+ buffer.int32ChannelData[ch],
379
+ buffer.frameLength * sizeof(int32_t));
380
+ }
381
+ } else {
382
+ // Float64 or other — fallback to raw bytes
383
+ size_t byteCount = buffer.frameLength * hardwareFormat.streamDescription->mBytesPerFrame;
384
+ for (AVAudioChannelCount ch = 0; ch < hardwareFormat.channelCount; ch++) {
385
+ memcpy(bufferCopy.floatChannelData[ch],
386
+ buffer.floatChannelData[ch],
387
+ byteCount);
388
+ }
389
+ }
390
+
391
+ dispatch_async(strongSelf.audioEncodeQueue, ^{
392
+ [strongSelf processAudioBuffer:bufferCopy];
393
+ });
394
+ }];
395
+
396
+ // Start the engine
397
+ [self.audioEngine startAndReturnError:&error];
398
+ if (error) {
399
+ if (outError) *outError = error;
400
+ [inputNode removeTapOnBus:0];
401
+ self.audioEngine = nil;
402
+ return NO;
403
+ }
404
+
405
+ self.isRecordingAudio = YES;
406
+ return YES;
407
+ }
408
+
409
+ - (void)processAudioBuffer:(AVAudioPCMBuffer *)inputBuffer {
410
+ if (!self.isRecordingAudio) return;
411
+
412
+ // Step 1: Resample hardware PCM → target PCM (16kHz mono int16)
413
+ double sampleRateRatio = self.targetPCMFormat.sampleRate / inputBuffer.format.sampleRate;
414
+ AVAudioFrameCount outputFrameCapacity = (AVAudioFrameCount)ceil(inputBuffer.frameLength * sampleRateRatio) + 16;
415
+
416
+ AVAudioPCMBuffer *pcmBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:self.targetPCMFormat
417
+ frameCapacity:outputFrameCapacity];
418
+ NSError *error = nil;
419
+ __block BOOL pcmInputProvided = NO;
420
+
421
+ AVAudioConverterOutputStatus pcmStatus = [self.pcmConverter
422
+ convertToBuffer:pcmBuffer
423
+ error:&error
424
+ withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets,
425
+ AVAudioConverterInputStatus *outStatus) {
426
+ if (pcmInputProvided) {
427
+ *outStatus = AVAudioConverterInputStatus_NoDataNow;
428
+ return nil;
429
+ }
430
+ pcmInputProvided = YES;
431
+ *outStatus = AVAudioConverterInputStatus_HaveData;
432
+ return inputBuffer;
433
+ }];
434
+
435
+ if (error) {
436
+ NSLog(@"[Suuqencode] PCM resampling error: %@", error);
437
+ return;
438
+ }
439
+ if (pcmBuffer.frameLength == 0) {
440
+ return;
441
+ }
442
+
443
+ // Step 2: Encode PCM → FLAC
444
+ AVAudioCompressedBuffer *flacBuffer =
445
+ [[AVAudioCompressedBuffer alloc] initWithFormat:self.flacFormat
446
+ packetCapacity:8
447
+ maximumPacketSize:65536];
448
+
449
+ __block BOOL flacInputProvided = NO;
450
+ AVAudioConverterOutputStatus flacStatus = [self.flacConverter
451
+ convertToBuffer:flacBuffer
452
+ error:&error
453
+ withInputFromBlock:^AVAudioBuffer * _Nullable(AVAudioPacketCount inNumberOfPackets,
454
+ AVAudioConverterInputStatus *outStatus) {
455
+ if (flacInputProvided) {
456
+ *outStatus = AVAudioConverterInputStatus_NoDataNow;
457
+ return nil;
458
+ }
459
+ flacInputProvided = YES;
460
+ *outStatus = AVAudioConverterInputStatus_HaveData;
461
+ return pcmBuffer;
462
+ }];
463
+
464
+ if (error) {
465
+ NSLog(@"[Suuqencode] FLAC encoding error: %@", error);
466
+ return;
467
+ }
468
+
469
+ if ((flacStatus == AVAudioConverterOutputStatus_HaveData ||
470
+ flacStatus == AVAudioConverterOutputStatus_InputRanDry) &&
471
+ flacBuffer.byteLength > 0) {
472
+ NSData *flacData = [NSData dataWithBytes:flacBuffer.data length:flacBuffer.byteLength];
473
+ NSString *base64 = [flacData base64EncodedStringWithOptions:0];
474
+ [self sendEventWithName:@"onAudioEncodedData" body:base64];
475
+ }
476
+ }
477
+
176
478
  - (NSArray<NSString *> *)supportedEvents {
177
- return @[ @"onEncodedData" ];
479
+ return @[ @"onEncodedData", @"onAudioEncodedData", @"onAudioFormatInfo" ];
178
480
  }
179
481
 
180
482
  + (BOOL)requiresMainQueueSetup {
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSuuqencode.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAQpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,YAAY,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeSuuqencode.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAUpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,YAAY,CAAC","ignoreList":[]}
@@ -16,4 +16,44 @@ export function addEncodedDataListener(callback) {
16
16
  subscription.remove();
17
17
  };
18
18
  }
19
+ /**
20
+ * Start recording from the microphone and encoding audio to FLAC in real time.
21
+ * Each encoded FLAC packet is base64-encoded and emitted via the onAudioEncodedData event.
22
+ * @param sampleRate Target sample rate in Hz (default: 16000)
23
+ * @returns Promise that resolves when recording has started successfully
24
+ */
25
+ export function startAudioEncode(sampleRate = 16000) {
26
+ return Suuqencode.startAudioEncode(sampleRate);
27
+ }
28
+
29
+ /**
30
+ * Stop audio recording and FLAC encoding.
31
+ */
32
+ export function stopAudioEncode() {
33
+ Suuqencode.stopAudioEncode();
34
+ }
35
+
36
+ /**
37
+ * Listen for base64-encoded FLAC audio packets.
38
+ */
39
+ export function addAudioDataListener(callback) {
40
+ const subscription = eventEmitter.addListener('onAudioEncodedData', data => {
41
+ callback(data);
42
+ });
43
+ return () => {
44
+ subscription.remove();
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Listen for audio format info emitted once at the start of recording.
50
+ */
51
+ export function addAudioFormatInfoListener(callback) {
52
+ const subscription = eventEmitter.addListener('onAudioFormatInfo', info => {
53
+ callback(info);
54
+ });
55
+ return () => {
56
+ subscription.remove();
57
+ };
58
+ }
19
59
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["NativeModules","NativeEventEmitter","Suuqencode","eventEmitter","startEncode","addEncodedDataListener","callback","subscription","addListener","data","remove"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,kBAAkB,QAAQ,cAAc;AAEhE,MAAM;EAAEC;AAAW,CAAC,GAAGF,aAAa;AAEpC,MAAMG,YAAY,GAAG,IAAIF,kBAAkB,CAACC,UAAU,CAAC;AAEvD,OAAO,SAASE,WAAWA,CAAA,EAAS;EAClCF,UAAU,CAACE,WAAW,CAAC,CAAC;AAC1B;AAEA,OAAO,SAASC,sBAAsBA,CACpCC,QAAgC,EACpB;EACZ,MAAMC,YAAY,GAAGJ,YAAY,CAACK,WAAW,CAC3C,eAAe,EACdC,IAAS,IAAK;IACbH,QAAQ,CAACG,IAAc,CAAC;EAC1B,CACF,CAAC;EACD,OAAO,MAAM;IACXF,YAAY,CAACG,MAAM,CAAC,CAAC;EACvB,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["NativeModules","NativeEventEmitter","Suuqencode","eventEmitter","startEncode","addEncodedDataListener","callback","subscription","addListener","data","remove","startAudioEncode","sampleRate","stopAudioEncode","addAudioDataListener","addAudioFormatInfoListener","info"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,aAAa,EAAEC,kBAAkB,QAAQ,cAAc;AAEhE,MAAM;EAAEC;AAAW,CAAC,GAAGF,aAAa;AAEpC,MAAMG,YAAY,GAAG,IAAIF,kBAAkB,CAACC,UAAU,CAAC;AAEvD,OAAO,SAASE,WAAWA,CAAA,EAAS;EAClCF,UAAU,CAACE,WAAW,CAAC,CAAC;AAC1B;AAEA,OAAO,SAASC,sBAAsBA,CACpCC,QAAgC,EACpB;EACZ,MAAMC,YAAY,GAAGJ,YAAY,CAACK,WAAW,CAC3C,eAAe,EACdC,IAAS,IAAK;IACbH,QAAQ,CAACG,IAAc,CAAC;EAC1B,CACF,CAAC;EACD,OAAO,MAAM;IACXF,YAAY,CAACG,MAAM,CAAC,CAAC;EACvB,CAAC;AACH;AASA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAACC,UAAkB,GAAG,KAAK,EAAoB;EAC7E,OAAOV,UAAU,CAACS,gBAAgB,CAACC,UAAU,CAAC;AAChD;;AAEA;AACA;AACA;AACA,OAAO,SAASC,eAAeA,CAAA,EAAS;EACtCX,UAAU,CAACW,eAAe,CAAC,CAAC;AAC9B;;AAEA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAClCR,QAAgC,EACpB;EACZ,MAAMC,YAAY,GAAGJ,YAAY,CAACK,WAAW,CAC3C,oBAAoB,EACnBC,IAAS,IAAK;IACbH,QAAQ,CAACG,IAAc,CAAC;EAC1B,CACF,CAAC;EACD,OAAO,MAAM;IACXF,YAAY,CAACG,MAAM,CAAC,CAAC;EACvB,CAAC;AACH;;AAEA;AACA;AACA;AACA,OAAO,SAASK,0BAA0BA,CACxCT,QAAyC,EAC7B;EACZ,MAAMC,YAAY,GAAGJ,YAAY,CAACK,WAAW,CAC3C,mBAAmB,EAClBQ,IAAS,IAAK;IACbV,QAAQ,CAACU,IAAuB,CAAC;EACnC,CACF,CAAC;EACD,OAAO,MAAM;IACXT,YAAY,CAACG,MAAM,CAAC,CAAC;EACvB,CAAC;AACH","ignoreList":[]}
@@ -1,6 +1,8 @@
1
1
  import { type TurboModule } from 'react-native';
2
2
  export interface Spec extends TurboModule {
3
3
  startEncode(): void;
4
+ startAudioEncode(sampleRate: number): Promise<boolean>;
5
+ stopAudioEncode(): void;
4
6
  addListener(eventName: string): void;
5
7
  removeListeners(count: number): void;
6
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NativeSuuqencode.d.ts","sourceRoot":"","sources":["../../../src/NativeSuuqencode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,WAAW,IAAI,IAAI,CAAC;IACpB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;;AAED,wBAAoE"}
1
+ {"version":3,"file":"NativeSuuqencode.d.ts","sourceRoot":"","sources":["../../../src/NativeSuuqencode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;;AAED,wBAAoE"}
@@ -1,3 +1,28 @@
1
1
  export declare function startEncode(): void;
2
2
  export declare function addEncodedDataListener(callback: (data: string) => void): () => void;
3
+ export interface AudioFormatInfo {
4
+ sampleRate: number;
5
+ channels: number;
6
+ bitsPerSample: number;
7
+ codec: string;
8
+ }
9
+ /**
10
+ * Start recording from the microphone and encoding audio to FLAC in real time.
11
+ * Each encoded FLAC packet is base64-encoded and emitted via the onAudioEncodedData event.
12
+ * @param sampleRate Target sample rate in Hz (default: 16000)
13
+ * @returns Promise that resolves when recording has started successfully
14
+ */
15
+ export declare function startAudioEncode(sampleRate?: number): Promise<boolean>;
16
+ /**
17
+ * Stop audio recording and FLAC encoding.
18
+ */
19
+ export declare function stopAudioEncode(): void;
20
+ /**
21
+ * Listen for base64-encoded FLAC audio packets.
22
+ */
23
+ export declare function addAudioDataListener(callback: (data: string) => void): () => void;
24
+ /**
25
+ * Listen for audio format info emitted once at the start of recording.
26
+ */
27
+ export declare function addAudioFormatInfoListener(callback: (info: AudioFormatInfo) => void): () => void;
3
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC/B,MAAM,IAAI,CAUZ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC/B,MAAM,IAAI,CAUZ;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,GAAE,MAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAE7E;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC/B,MAAM,IAAI,CAUZ;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GACxC,MAAM,IAAI,CAUZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-suuqencode",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "h264 hardware encode support for iOS, developed by Suuqe Llc.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -2,6 +2,8 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
2
 
3
3
  export interface Spec extends TurboModule {
4
4
  startEncode(): void;
5
+ startAudioEncode(sampleRate: number): Promise<boolean>;
6
+ stopAudioEncode(): void;
5
7
  addListener(eventName: string): void;
6
8
  removeListeners(count: number): void;
7
9
  }
package/src/index.tsx CHANGED
@@ -21,3 +21,61 @@ export function addEncodedDataListener(
21
21
  subscription.remove();
22
22
  };
23
23
  }
24
+
25
+ export interface AudioFormatInfo {
26
+ sampleRate: number;
27
+ channels: number;
28
+ bitsPerSample: number;
29
+ codec: string;
30
+ }
31
+
32
+ /**
33
+ * Start recording from the microphone and encoding audio to FLAC in real time.
34
+ * Each encoded FLAC packet is base64-encoded and emitted via the onAudioEncodedData event.
35
+ * @param sampleRate Target sample rate in Hz (default: 16000)
36
+ * @returns Promise that resolves when recording has started successfully
37
+ */
38
+ export function startAudioEncode(sampleRate: number = 16000): Promise<boolean> {
39
+ return Suuqencode.startAudioEncode(sampleRate);
40
+ }
41
+
42
+ /**
43
+ * Stop audio recording and FLAC encoding.
44
+ */
45
+ export function stopAudioEncode(): void {
46
+ Suuqencode.stopAudioEncode();
47
+ }
48
+
49
+ /**
50
+ * Listen for base64-encoded FLAC audio packets.
51
+ */
52
+ export function addAudioDataListener(
53
+ callback: (data: string) => void
54
+ ): () => void {
55
+ const subscription = eventEmitter.addListener(
56
+ 'onAudioEncodedData',
57
+ (data: any) => {
58
+ callback(data as string);
59
+ }
60
+ );
61
+ return () => {
62
+ subscription.remove();
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Listen for audio format info emitted once at the start of recording.
68
+ */
69
+ export function addAudioFormatInfoListener(
70
+ callback: (info: AudioFormatInfo) => void
71
+ ): () => void {
72
+ const subscription = eventEmitter.addListener(
73
+ 'onAudioFormatInfo',
74
+ (info: any) => {
75
+ callback(info as AudioFormatInfo);
76
+ }
77
+ );
78
+ return () => {
79
+ subscription.remove();
80
+ };
81
+ }