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.
- package/Suuqencode.podspec +1 -1
- package/ios/Suuqencode.h +1 -0
- package/ios/Suuqencode.mm +303 -1
- package/lib/module/NativeSuuqencode.js.map +1 -1
- package/lib/module/index.js +40 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeSuuqencode.d.ts +2 -0
- package/lib/typescript/src/NativeSuuqencode.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeSuuqencode.ts +2 -0
- package/src/index.tsx +58 -0
package/Suuqencode.podspec
CHANGED
|
@@ -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
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;
|
|
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":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -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
|
package/lib/module/index.js.map
CHANGED
|
@@ -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
package/src/NativeSuuqencode.ts
CHANGED
|
@@ -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
|
+
}
|