react-native-suuqencode 0.1.5 → 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
  }
@@ -108,6 +121,8 @@ void compressionOutputCallback(void *outputCallbackRefCon,
108
121
  }
109
122
 
110
123
  Suuqencode *encoder = (__bridge Suuqencode *)outputCallbackRefCon;
124
+ const uint8_t startCode[] = {0x00, 0x00, 0x00, 0x01};
125
+ size_t startCodeSize = sizeof(startCode);
111
126
 
112
127
  bool isKeyFrame = !CFDictionaryContainsKey(
113
128
  (CFDictionaryRef)CFArrayGetValueAtIndex(
@@ -127,11 +142,15 @@ void compressionOutputCallback(void *outputCallbackRefCon,
127
142
  CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
128
143
  format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0);
129
144
 
130
- NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];
131
- NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];
145
+ NSMutableData *spsData = [NSMutableData dataWithBytes:startCode
146
+ length:startCodeSize];
147
+ [spsData appendBytes:sparameterSet length:sparameterSetSize];
148
+ [encoder sendEncodedData:spsData];
132
149
 
133
- [encoder sendEncodedData:sps];
134
- [encoder sendEncodedData:pps];
150
+ NSMutableData *ppsData = [NSMutableData dataWithBytes:startCode
151
+ length:startCodeSize];
152
+ [ppsData appendBytes:pparameterSet length:pparameterSetSize];
153
+ [encoder sendEncodedData:ppsData];
135
154
  }
136
155
 
137
156
  CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
@@ -140,8 +159,26 @@ void compressionOutputCallback(void *outputCallbackRefCon,
140
159
  CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength,
141
160
  &dataPointer);
142
161
 
143
- NSData *naluData = [NSData dataWithBytes:dataPointer length:length];
144
- [encoder sendEncodedData:naluData];
162
+ // Parse AVCC NAL units and convert to Annex B
163
+ size_t bufferOffset = 0;
164
+ static const int AVCCHeaderLength = 4;
165
+
166
+ while (bufferOffset < totalLength - AVCCHeaderLength) {
167
+ uint32_t NALUnitLength = 0;
168
+ memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);
169
+
170
+ // Convert big-endian length to host endianness
171
+ NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
172
+
173
+ NSMutableData *naluData = [NSMutableData dataWithBytes:startCode
174
+ length:startCodeSize];
175
+ [naluData appendBytes:(dataPointer + bufferOffset + AVCCHeaderLength)
176
+ length:NALUnitLength];
177
+
178
+ [encoder sendEncodedData:naluData];
179
+
180
+ bufferOffset += AVCCHeaderLength + NALUnitLength;
181
+ }
145
182
  }
146
183
 
147
184
  - (void)sendEncodedData:(NSData *)data {
@@ -149,8 +186,297 @@ void compressionOutputCallback(void *outputCallbackRefCon,
149
186
  [self sendEventWithName:@"onEncodedData" body:base64Encoded];
150
187
  }
151
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
+
152
478
  - (NSArray<NSString *> *)supportedEvents {
153
- return @[ @"onEncodedData" ];
479
+ return @[ @"onEncodedData", @"onAudioEncodedData", @"onAudioFormatInfo" ];
154
480
  }
155
481
 
156
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.5",
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
+ }