react-native-audio-api 0.13.4 → 0.13.6

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.
Files changed (65) hide show
  1. package/android/src/main/cpp/audioapi/android/OnLoad.cpp +5 -1
  2. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +26 -17
  3. package/android/src/main/cpp/audioapi/android/system/NativeRecorderControl.cpp +41 -0
  4. package/android/src/main/cpp/audioapi/android/system/NativeRecorderControl.h +23 -0
  5. package/android/src/main/java/com/swmansion/audioapi/system/AudioEvent.kt +1 -0
  6. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +136 -30
  7. package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +47 -12
  8. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +23 -0
  9. package/android/src/main/java/com/swmansion/audioapi/system/NativeRecorderControl.kt +39 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/RecorderState.kt +17 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +48 -20
  12. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +221 -227
  13. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +122 -9
  14. package/android/src/main/java/com/swmansion/audioapi/system/notification/state/RecordingNotificationState.kt +17 -11
  15. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +44 -0
  16. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +18 -11
  17. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +1 -0
  18. package/common/cpp/audioapi/HostObjects/utils/JsEnumParser.cpp +2 -0
  19. package/common/cpp/audioapi/core/inputs/ActiveRecorderHandle.cpp +117 -0
  20. package/common/cpp/audioapi/core/inputs/ActiveRecorderHandle.h +85 -0
  21. package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +8 -0
  22. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +3 -1
  23. package/common/cpp/audioapi/core/inputs/RecorderState.h +13 -0
  24. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +0 -1
  25. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +4 -4
  26. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +2 -4
  27. package/common/cpp/audioapi/events/AudioEvent.h +1 -0
  28. package/ios/audioapi/ios/AudioAPIModule.mm +30 -23
  29. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +27 -29
  30. package/ios/audioapi/ios/core/NativeAudioRecorder.h +1 -1
  31. package/ios/audioapi/ios/core/NativeAudioRecorder.m +9 -7
  32. package/ios/audioapi/ios/core/utils/FileOptions.mm +18 -5
  33. package/ios/audioapi/ios/core/utils/IOSFileWriter.h +5 -0
  34. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +50 -20
  35. package/ios/audioapi/ios/core/utils/IOSRotatingFileWriter.mm +2 -7
  36. package/ios/audioapi/ios/system/SystemNotificationManager.mm +4 -1
  37. package/lib/commonjs/core/AudioRecorder.js +12 -4
  38. package/lib/commonjs/core/AudioRecorder.js.map +1 -1
  39. package/lib/commonjs/mock/index.js +11 -1
  40. package/lib/commonjs/mock/index.js.map +1 -1
  41. package/lib/commonjs/plugin/withAudioAPI.js +4 -2
  42. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
  43. package/lib/module/core/AudioRecorder.js +12 -4
  44. package/lib/module/core/AudioRecorder.js.map +1 -1
  45. package/lib/module/mock/index.js +11 -1
  46. package/lib/module/mock/index.js.map +1 -1
  47. package/lib/module/plugin/withAudioAPI.js +4 -2
  48. package/lib/module/plugin/withAudioAPI.js.map +1 -1
  49. package/lib/typescript/core/AudioRecorder.d.ts +2 -0
  50. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
  51. package/lib/typescript/mock/index.d.ts +3 -0
  52. package/lib/typescript/mock/index.d.ts.map +1 -1
  53. package/lib/typescript/plugin/withAudioAPI.d.ts +1 -0
  54. package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -1
  55. package/lib/typescript/system/notification/types.d.ts +32 -2
  56. package/lib/typescript/system/notification/types.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/AudioAPIModule/globals.d.ts +5 -0
  59. package/src/core/AudioRecorder.ts +17 -4
  60. package/src/mock/index.ts +14 -1
  61. package/src/plugin/withAudioAPI.ts +4 -2
  62. package/src/system/notification/types.ts +32 -2
  63. package/android/src/main/res/layout/btn_round_ripple.xml +0 -9
  64. package/android/src/main/res/layout/notification_collapsed.xml +0 -45
  65. package/android/src/main/res/layout/notification_expanded.xml +0 -44
@@ -29,5 +29,6 @@ enum class AudioEvent : uint8_t {
29
29
  BUFFER_ENDED,
30
30
  RECORDER_ERROR,
31
31
  BUFFERING_STATE_CHANGE,
32
+ RECORDING_NOTIFICATION_STOP,
32
33
  };
33
34
  } // namespace audioapi
@@ -20,6 +20,7 @@
20
20
  #import <audioapi/ios/system/SystemNotificationManager.h>
21
21
  #import <audioapi/ios/system/notification/NotificationRegistry.h>
22
22
 
23
+ #import <audioapi/core/inputs/ActiveRecorderHandle.h>
23
24
  #import <audioapi/events/AudioEventHandlerRegistry.h>
24
25
 
25
26
  using namespace audioapi;
@@ -145,17 +146,23 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(resolveAndroidReleaseAsset : (NSString *)
145
146
  }
146
147
 
147
148
  RCT_EXPORT_METHOD(
148
- readAndroidReleaseAssetBytesAsBase64 : (NSString *)assetPath resolve : (RCTPromiseResolveBlock)
149
- resolve reject : (RCTPromiseRejectBlock)reject)
149
+ readAndroidReleaseAssetBytesAsBase64 : (NSString *)assetPath resolve : (
150
+ RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
150
151
  {
151
152
  reject(@"E_PLATFORM", @"readAndroidReleaseAssetBytesAsBase64 is only available on Android", nil);
152
153
  }
153
154
 
154
155
  RCT_EXPORT_METHOD(
155
- setAudioSessionActivity : (BOOL)enabled resolve : (RCTPromiseResolveBlock)
156
- resolve reject : (RCTPromiseRejectBlock)reject)
156
+ setAudioSessionActivity : (BOOL)enabled resolve : (RCTPromiseResolveBlock)resolve reject : (
157
+ RCTPromiseRejectBlock)reject)
157
158
  {
158
159
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
160
+ // Deactivating the session underneath a live recording would corrupt its output, so
161
+ // the recording is finalized first
162
+ if (!enabled) {
163
+ ActiveRecorderHandle::global().stopAndReturnInfo();
164
+ }
165
+
159
166
  NSError *error = nil;
160
167
  auto success = [self.audioSessionManager setActive:enabled error:&error];
161
168
 
@@ -191,9 +198,9 @@ RCT_EXPORT_METHOD(
191
198
  }
192
199
 
193
200
  RCT_EXPORT_METHOD(
194
- setAudioSessionOptions : (NSString *)category mode : (NSString *)mode options : (NSArray *)
195
- options allowHaptics : (BOOL)allowHaptics notifyOthersOnDeactivation : (BOOL)
196
- notifyOthersOnDeactivation)
201
+ setAudioSessionOptions : (NSString *)category mode : (NSString *)mode options : (
202
+ NSArray *)options allowHaptics : (BOOL)allowHaptics notifyOthersOnDeactivation : (BOOL)
203
+ notifyOthersOnDeactivation)
197
204
  {
198
205
  if (!self.audioSessionManager.shouldManageSession) {
199
206
  [self.audioSessionManager setShouldManageSession:true];
@@ -222,8 +229,8 @@ RCT_EXPORT_METHOD(observeVolumeChanges : (BOOL)enabled)
222
229
  }
223
230
 
224
231
  RCT_EXPORT_METHOD(
225
- requestRecordingPermissions : (nonnull RCTPromiseResolveBlock)
226
- resolve reject : (nonnull RCTPromiseRejectBlock)reject)
232
+ requestRecordingPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (
233
+ nonnull RCTPromiseRejectBlock)reject)
227
234
  {
228
235
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
229
236
  [self.audioSessionManager requestRecordingPermissions:resolve reject:reject];
@@ -231,8 +238,8 @@ RCT_EXPORT_METHOD(
231
238
  }
232
239
 
233
240
  RCT_EXPORT_METHOD(
234
- checkRecordingPermissions : (nonnull RCTPromiseResolveBlock)
235
- resolve reject : (nonnull RCTPromiseRejectBlock)reject)
241
+ checkRecordingPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (
242
+ nonnull RCTPromiseRejectBlock)reject)
236
243
  {
237
244
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
238
245
  [self.audioSessionManager checkRecordingPermissions:resolve reject:reject];
@@ -240,8 +247,8 @@ RCT_EXPORT_METHOD(
240
247
  }
241
248
 
242
249
  RCT_EXPORT_METHOD(
243
- requestNotificationPermissions : (nonnull RCTPromiseResolveBlock)
244
- resolve reject : (nonnull RCTPromiseRejectBlock)reject)
250
+ requestNotificationPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (
251
+ nonnull RCTPromiseRejectBlock)reject)
245
252
  {
246
253
  // iOS doesn't require explicit notification permissions for media controls
247
254
  // MPNowPlayingInfoCenter and MPRemoteCommandCenter work without permissions
@@ -250,8 +257,8 @@ RCT_EXPORT_METHOD(
250
257
  }
251
258
 
252
259
  RCT_EXPORT_METHOD(
253
- checkNotificationPermissions : (nonnull RCTPromiseResolveBlock)
254
- resolve reject : (nonnull RCTPromiseRejectBlock)reject)
260
+ checkNotificationPermissions : (nonnull RCTPromiseResolveBlock)resolve reject : (
261
+ nonnull RCTPromiseRejectBlock)reject)
255
262
  {
256
263
  // iOS doesn't require explicit notification permissions for media controls
257
264
  // Return 'Granted' to match the spec interface
@@ -259,8 +266,8 @@ RCT_EXPORT_METHOD(
259
266
  }
260
267
 
261
268
  RCT_EXPORT_METHOD(
262
- getDevicesInfo : (nonnull RCTPromiseResolveBlock)
263
- resolve reject : (nonnull RCTPromiseRejectBlock)reject)
269
+ getDevicesInfo : (nonnull RCTPromiseResolveBlock)resolve reject : (
270
+ nonnull RCTPromiseRejectBlock)reject)
264
271
  {
265
272
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
266
273
  [self.audioSessionManager getDevicesInfo:resolve reject:reject];
@@ -268,8 +275,8 @@ RCT_EXPORT_METHOD(
268
275
  }
269
276
 
270
277
  RCT_EXPORT_METHOD(
271
- setInputDevice : (NSString *)deviceId resolve : (RCTPromiseResolveBlock)
272
- resolve reject : (RCTPromiseRejectBlock)reject)
278
+ setInputDevice : (NSString *)deviceId resolve : (RCTPromiseResolveBlock)resolve reject : (
279
+ RCTPromiseRejectBlock)reject)
273
280
  {
274
281
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
275
282
  [self.audioSessionManager setInputDevice:deviceId resolve:resolve reject:reject];
@@ -300,8 +307,8 @@ RCT_EXPORT_METHOD(
300
307
  }
301
308
 
302
309
  RCT_EXPORT_METHOD(
303
- hideNotification : (NSString *)key resolve : (RCTPromiseResolveBlock)
304
- resolve reject : (RCTPromiseRejectBlock)reject)
310
+ hideNotification : (NSString *)key resolve : (RCTPromiseResolveBlock)resolve reject : (
311
+ RCTPromiseRejectBlock)reject)
305
312
  {
306
313
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
307
314
  BOOL success = [self.notificationRegistry hideNotificationWithKey:key];
@@ -315,8 +322,8 @@ RCT_EXPORT_METHOD(
315
322
  }
316
323
 
317
324
  RCT_EXPORT_METHOD(
318
- isNotificationActive : (NSString *)key resolve : (RCTPromiseResolveBlock)
319
- resolve reject : (RCTPromiseRejectBlock)reject)
325
+ isNotificationActive : (NSString *)key resolve : (RCTPromiseResolveBlock)resolve reject : (
326
+ RCTPromiseRejectBlock)reject)
320
327
  {
321
328
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
322
329
  BOOL isActive = [self.notificationRegistry isNotificationActiveWithKey:key];
@@ -2,6 +2,7 @@
2
2
  #import <AudioEngine.h>
3
3
  #import <AudioSessionManager.h>
4
4
  #import <Foundation/Foundation.h>
5
+ #include <algorithm>
5
6
  #include <mutex>
6
7
  #include <unordered_map>
7
8
  #include <vector>
@@ -93,7 +94,9 @@ void IOSAudioRecorder::runSideEffects(const AudioBufferList *inputBuffer, int nu
93
94
 
94
95
  if (isConnected()) {
95
96
  if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
96
- for (size_t channel = 0; channel < adapterNode_->getChannelCount(); ++channel) {
97
+ const size_t channelCount = std::min(
98
+ adapterNode_->getChannelCount(), static_cast<size_t>(inputBuffer->mNumberBuffers));
99
+ for (size_t channel = 0; channel < channelCount; ++channel) {
97
100
  auto *data = static_cast<float *>(inputBuffer->mBuffers[channel].mData);
98
101
  adapterNode_->buff_[channel]->write(data, numFrames);
99
102
  }
@@ -204,7 +207,6 @@ Result<NoneType, std::string> IOSAudioRecorder::reprepareFileWriter(
204
207
  }
205
208
 
206
209
  filePath_ = result.unwrap();
207
- recordingSegmentPaths_.push_back(filePath_);
208
210
  fileOutputConfigured_.store(true, std::memory_order_release);
209
211
  return Result<NoneType, std::string>::Ok(None);
210
212
  }
@@ -237,6 +239,8 @@ void IOSAudioRecorder::reprepareAdapter(AVAudioFormat *inputFormat, int maxInput
237
239
  return;
238
240
  }
239
241
 
242
+ // init() is a no-op on an initialized adapter, so the old format has to be torn down first.
243
+ adapterNode_->adapterCleanup();
240
244
  adapterNode_->init(
241
245
  static_cast<size_t>(maxInputBufferLength),
242
246
  recorderFormatChannelCount(inputFormat),
@@ -407,16 +411,16 @@ AudioRecorder::StopResult IOSAudioRecorder::stop()
407
411
  std::shared_ptr<RecorderAdapterNode> adapterNode;
408
412
  std::vector<std::string> outputPaths;
409
413
 
410
- {
411
- std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
414
+ if (isIdle()) {
415
+ return StopResult::Err("Recorder is not in recording state.");
416
+ }
412
417
 
413
- if (isIdle()) {
414
- return StopResult::Err("Recorder is not in recording state.");
415
- }
418
+ state_.store(RecorderState::Idle, std::memory_order_release);
419
+ [nativeRecorder_ setInputArmed:false];
420
+ [nativeRecorder_ stop];
416
421
 
417
- [nativeRecorder_ setInputArmed:false];
418
- state_.store(RecorderState::Idle, std::memory_order_release);
419
- [nativeRecorder_ stop];
422
+ {
423
+ std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
420
424
 
421
425
  if (usesFileOutput()) {
422
426
  fileOutputConfigured_.store(false, std::memory_order_release);
@@ -443,34 +447,25 @@ AudioRecorder::StopResult IOSAudioRecorder::stop()
443
447
  }
444
448
 
445
449
  /// @brief Enables file output for the recorder with specified properties.
446
- /// If the recorder is already active, it will open the file for writing immediately.
450
+ /// The file itself is created by the next start(). An active (recording or paused) session keeps
451
+ /// the output it started with, so calling this during a session fails and changes nothing.
447
452
  /// This method should be called from the JS thread only.
448
453
  /// @param properties Shared pointer to AudioFileProperties defining the output file format.
449
- /// @returns Result containing the output file path if enabled successfully, or an error message.
454
+ /// @returns Ok when the properties were stored, otherwise an error message.
450
455
  Result<NoneType, std::string> IOSAudioRecorder::enableFileOutput(
451
456
  std::shared_ptr<AudioFileProperties> properties)
452
457
  {
453
458
  std::scoped_lock lock(fileWriterMutex_, errorCallbackMutex_);
454
- fileProperties_ = properties;
455
- fileOutputEnabled_.store(true, std::memory_order_release);
456
- fileOutputConfigured_.store(false, std::memory_order_release);
457
459
 
458
460
  if (!isIdle()) {
459
- AVAudioFormat *resolvedInputFormat = [nativeRecorder_ getResolvedInputFormat];
460
- int resolvedBufferSize = [nativeRecorder_ getResolvedBufferSize];
461
-
462
- if (!hasUsableRecorderFormat(resolvedInputFormat) || resolvedBufferSize <= 0) {
463
- return Result<NoneType, std::string>::Err(
464
- "Failed to open file for writing: recorder input format is unavailable");
465
- }
466
-
467
- auto writerResult = setupFileWriter(properties);
468
- if (writerResult.is_err()) {
469
- fileOutputEnabled_.store(false, std::memory_order_release);
470
- return Result<NoneType, std::string>::Err(writerResult.unwrap_err());
471
- }
461
+ return Result<NoneType, std::string>::Err(
462
+ "File output cannot be changed while a recording session is active");
472
463
  }
473
464
 
465
+ fileProperties_ = properties;
466
+ fileOutputEnabled_.store(true, std::memory_order_release);
467
+ fileOutputConfigured_.store(false, std::memory_order_release);
468
+
474
469
  return Result<NoneType, std::string>::Ok(None);
475
470
  }
476
471
 
@@ -565,8 +560,11 @@ void IOSAudioRecorder::resume()
565
560
  return;
566
561
  }
567
562
 
568
- [nativeRecorder_ resume];
569
563
  state_.store(RecorderState::Recording, std::memory_order_release);
564
+
565
+ if (![nativeRecorder_ resume]) {
566
+ state_.store(RecorderState::Paused, std::memory_order_release);
567
+ }
570
568
  }
571
569
 
572
570
  /// @brief Checks if the recorder is currently recording.
@@ -26,7 +26,7 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
26
26
 
27
27
  - (void)pause;
28
28
 
29
- - (void)resume;
29
+ - (BOOL)resume;
30
30
 
31
31
  - (void)cleanup;
32
32
 
@@ -164,18 +164,20 @@ static inline uint32_t nextPowerOfTwo(uint32_t x)
164
164
  [audioEngine pauseIfNecessary];
165
165
  }
166
166
 
167
- - (void)resume
167
+ - (BOOL)resume
168
168
  {
169
169
  AudioEngine *audioEngine = [AudioEngine sharedInstance];
170
170
  assert(audioEngine != nil);
171
171
 
172
- if ([audioEngine startIfNecessary]) {
173
- if (self.onInputConfigurationChange != nil) {
174
- self.onInputConfigurationChange();
175
- } else {
176
- self.inputArmed = YES;
177
- }
172
+ if (![audioEngine startIfNecessary]) {
173
+ return NO;
178
174
  }
175
+
176
+ if (self.onInputConfigurationChange != nil) {
177
+ self.onInputConfigurationChange();
178
+ }
179
+
180
+ return YES;
179
181
  }
180
182
 
181
183
  - (void)cleanup
@@ -166,13 +166,26 @@ NSURL *getFileURL(
166
166
  NSString *timestamp = getTimestampString();
167
167
  NSString *fileExtension = getFileExtension(properties);
168
168
 
169
- NSString *fileName = fileNameOverride.length() > 0 && properties->rotateIntervalBytes == 0
170
- ? [NSString stringWithFormat:@"%@.%@",
169
+ if (fileNameOverride.length() > 0 && properties->rotateIntervalBytes == 0) {
170
+ NSString *fileName =
171
+ [NSString stringWithFormat:@"%@.%@",
171
172
  [NSString stringWithUTF8String:fileNameOverride.c_str()],
172
- fileExtension]
173
- : [NSString stringWithFormat:@"%@_%@.%@", fileNamePrefix, timestamp, fileExtension];
173
+ fileExtension];
174
+ return [directoryURL URLByAppendingPathComponent:fileName];
175
+ }
176
+
177
+ NSString *fileName =
178
+ [NSString stringWithFormat:@"%@_%@.%@", fileNamePrefix, timestamp, fileExtension];
179
+ NSURL *fileURL = [directoryURL URLByAppendingPathComponent:fileName];
180
+
181
+ for (NSUInteger suffix = 1; [[NSFileManager defaultManager] fileExistsAtPath:fileURL.path];
182
+ ++suffix) {
183
+ fileName = [NSString
184
+ stringWithFormat:@"%@_%@_%lu.%@", fileNamePrefix, timestamp, (unsigned long)suffix, fileExtension];
185
+ fileURL = [directoryURL URLByAppendingPathComponent:fileName];
186
+ }
174
187
 
175
- return [directoryURL URLByAppendingPathComponent:fileName];
188
+ return fileURL;
176
189
  }
177
190
 
178
191
  NSSearchPathDirectory getDirectory(const std::shared_ptr<AudioFileProperties> &properties)
@@ -67,6 +67,11 @@ class IOSFileWriter : public AudioFileWriter {
67
67
  private:
68
68
  using FreeList = slots::SlotFreeList<FILE_WRITER_POOL_SIZE>;
69
69
 
70
+ Result<NoneType, std::string> prepareConversionPipeline(
71
+ AVAudioFormat *bufferFormat,
72
+ size_t maxInputBufferLength);
73
+ void releaseConversionPipeline();
74
+
70
75
  std::vector<ios::OwnedAudioBufferListPtr> inputBufferPool_;
71
76
  size_t inputBufferBytesPerBuffer_{0};
72
77
  std::unique_ptr<FreeList> freeSlots_;
@@ -83,6 +83,24 @@ OpenFileResult IOSFileWriter::openFile(
83
83
  [[error debugDescription] UTF8String]);
84
84
  }
85
85
 
86
+ auto pipelineResult = prepareConversionPipeline(bufferFormat, maxInputBufferLength);
87
+
88
+ if (pipelineResult.is_err()) {
89
+ rollbackFailedOpen();
90
+ return OpenFileResult::Err(pipelineResult.unwrap_err());
91
+ }
92
+
93
+ isFileOpen_.store(true, std::memory_order_release);
94
+ return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
95
+ }
96
+ }
97
+
98
+ /// @brief Builds the converter, conversion buffers and offloader for the given input format.
99
+ Result<NoneType, std::string> IOSFileWriter::prepareConversionPipeline(
100
+ AVAudioFormat *bufferFormat,
101
+ size_t maxInputBufferLength)
102
+ {
103
+ @autoreleasepool {
86
104
  converter_ = [[AVAudioConverter alloc] initFromFormat:bufferFormat
87
105
  toFormat:[audioFile_ processingFormat]];
88
106
  converter_.sampleRateConverterAlgorithm = AVSampleRateConverterAlgorithm_Normal;
@@ -103,8 +121,7 @@ OpenFileResult IOSFileWriter::openFile(
103
121
 
104
122
  if (converterInputBuffer_ == nil || converterOutputBuffer_ == nil || audioFile_ == nil ||
105
123
  converter_ == nil) {
106
- rollbackFailedOpen();
107
- return OpenFileResult::Err("Error creating converter buffers");
124
+ return Result<NoneType, std::string>::Err("Error creating converter buffers");
108
125
  }
109
126
 
110
127
  auto offloaderLambda = [this](WriterData data) {
@@ -126,8 +143,7 @@ OpenFileResult IOSFileWriter::openFile(
126
143
  for (size_t i = 0; i < FILE_WRITER_POOL_SIZE; ++i) {
127
144
  OwnedAudioBufferListPtr buffer(allocateOwnedAudioBufferList(bufferCount, bytesPerBuffer));
128
145
  if (buffer == nullptr) {
129
- rollbackFailedOpen();
130
- return OpenFileResult::Err("Failed to preallocate iOS file writer buffers");
146
+ return Result<NoneType, std::string>::Err("Failed to preallocate iOS file writer buffers");
131
147
  }
132
148
  inputBufferPool_.push_back(std::move(buffer));
133
149
  }
@@ -135,39 +151,53 @@ OpenFileResult IOSFileWriter::openFile(
135
151
  freeSlots_ = std::make_unique<FreeList>();
136
152
  freeSlots_->seed();
137
153
 
138
- isFileOpen_.store(true, std::memory_order_release);
139
- return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
154
+ return Result<NoneType, std::string>::Ok(None);
140
155
  }
141
156
  }
142
157
 
158
+ /// @brief Tears down the converter, conversion buffers and offloader, draining pending writes.
159
+ void IOSFileWriter::releaseConversionPipeline()
160
+ {
161
+ offloader_.reset();
162
+ freeSlots_.reset();
163
+ inputBufferPool_.clear();
164
+ inputBufferBytesPerBuffer_ = 0;
165
+
166
+ converter_ = nil;
167
+ converterInputBuffer_ = nil;
168
+ converterOutputBuffer_ = nil;
169
+ }
170
+
143
171
  OpenFileResult IOSFileWriter::reopenForInputFormatChange(
144
172
  AVAudioFormat *bufferFormat,
145
173
  size_t maxInputBufferLength)
146
174
  {
147
175
  @autoreleasepool {
148
- if (isFileOpen()) {
149
- auto closeResult = closeFile();
150
- if (closeResult.is_err()) {
151
- return OpenFileResult::Err(
152
- "Failed to finalize recording segment: " + closeResult.unwrap_err());
153
- }
176
+ if (!isFileOpen() || audioFile_ == nil) {
177
+ return openFile(bufferFormat, maxInputBufferLength, "");
154
178
  }
155
179
 
156
- return openFile(bufferFormat, maxInputBufferLength, "");
180
+ // The file settings come from the recorder's file properties, not from the input, so the file
181
+ // stays open across a configuration change and the recording is not split. Only the conversion
182
+ // pipeline, which is built for the input format, is rebuilt.
183
+ releaseConversionPipeline();
184
+ bufferFormat_ = bufferFormat;
185
+
186
+ auto pipelineResult = prepareConversionPipeline(bufferFormat, maxInputBufferLength);
187
+
188
+ if (pipelineResult.is_err()) {
189
+ return OpenFileResult::Err(pipelineResult.unwrap_err());
190
+ }
191
+
192
+ return OpenFileResult::Ok([[fileURL_ path] UTF8String]);
157
193
  }
158
194
  }
159
195
 
160
196
  void IOSFileWriter::rollbackFailedOpen()
161
197
  {
162
- offloader_.reset();
163
- freeSlots_.reset();
164
- inputBufferPool_.clear();
165
- inputBufferBytesPerBuffer_ = 0;
198
+ releaseConversionPipeline();
166
199
 
167
200
  audioFile_ = nil;
168
- converter_ = nil;
169
- converterInputBuffer_ = nil;
170
- converterOutputBuffer_ = nil;
171
201
  bufferFormat_ = nil;
172
202
 
173
203
  if (fileURL_ != nil) {
@@ -58,13 +58,8 @@ OpenFileResult IOSRotatingFileWriter::reprepareStreamFormat(
58
58
  return openInnerWriter();
59
59
  }
60
60
 
61
- rotateFiles();
62
-
63
- if (currentWriter_ == nullptr) {
64
- return OpenFileResult::Err("Failed to reopen file for writing after input format change");
65
- }
66
-
67
- return OpenFileResult::Ok(currentWriter_->getFilePath());
61
+ auto inner = std::static_pointer_cast<IOSFileWriter>(currentWriter_);
62
+ return inner->reopenForInputFormatChange(streamFormat_, streamMaxBufferSizeInFrames_);
68
63
  }
69
64
 
70
65
  void IOSRotatingFileWriter::writeAudioData(AudioDataType data, int numFrames)
@@ -23,6 +23,7 @@ static NSString *NotificationManagerContext = @"SystemNotificationManagerContext
23
23
 
24
24
  - (void)cleanup
25
25
  {
26
+ [self observeVolumeChanges:NO];
26
27
  self.notificationCenter = nil;
27
28
  }
28
29
 
@@ -64,7 +65,9 @@ static NSString *NotificationManagerContext = @"SystemNotificationManagerContext
64
65
  options:NSKeyValueObservingOptionNew
65
66
  context:(void *)&NotificationManagerContext];
66
67
  } else {
67
- [[AVAudioSession sharedInstance] removeObserver:self forKeyPath:@"outputVolume" context:nil];
68
+ [[AVAudioSession sharedInstance] removeObserver:self
69
+ forKeyPath:@"outputVolume"
70
+ context:(void *)&NotificationManagerContext];
68
71
  }
69
72
 
70
73
  self.volumeChangesObserved = enabled;
@@ -34,11 +34,19 @@ class AudioRecorder {
34
34
  constructor() {
35
35
  this.recorder = globalThis.createAudioRecorder();
36
36
  }
37
+ static isRecordingOngoing() {
38
+ return globalThis.isRecordingOngoing?.() ?? false;
39
+ }
40
+ static consumeLastRecordingResult() {
41
+ return globalThis.consumeLastRecordingResult?.() ?? null;
42
+ }
37
43
  enableFileOutput(options) {
38
- this.options_ = options || {};
39
- const parsedOptions = withDefaultOptions(this.options_);
40
- const result = this.recorder.enableFileOutput(parsedOptions);
41
- this.isFileOutputEnabled = true;
44
+ const requestedOptions = options || {};
45
+ const result = this.recorder.enableFileOutput(withDefaultOptions(requestedOptions));
46
+ if (result.status === 'success') {
47
+ this.options_ = requestedOptions;
48
+ this.isFileOutputEnabled = true;
49
+ }
42
50
  return result;
43
51
  }
44
52
  get options() {
@@ -1 +1 @@
1
- {"version":3,"names":["_errors","require","_events","_types","_filePresets","_interopRequireDefault","_AudioBuffer","e","__esModule","default","withDefaultOptions","inOptions","directory","FileDirectory","Cache","subDirectory","fileNamePrefix","channelCount","format","FileFormat","M4A","preset","FilePreset","High","androidFlushIntervalMs","rotateIntervalBytes","AudioRecorder","onAudioReadySubscription","onErrorSubscription","options_","isFileOutputEnabled","audioEventEmitter","AudioEventEmitter","globalThis","constructor","recorder","createAudioRecorder","enableFileOutput","options","parsedOptions","result","disableFileOutput","start","fileNameOverride","stop","pause","resume","connect","node","wasConnected","AudioApiError","getNode","disconnect","onAudioReady","callback","clearOnAudioReady","remove","addAudioEventListener","event","audioBuffer","AudioBuffer","buffer","setOnAudioReady","sampleRate","bufferLength","callbackId","subscriptionId","isRecording","isPaused","getCurrentDuration","onError","clearOnError","setOnError","exports"],"sourceRoot":"../../../src","sources":["core/AudioRecorder.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_errors","require","_events","_types","_filePresets","_interopRequireDefault","_AudioBuffer","e","__esModule","default","withDefaultOptions","inOptions","directory","FileDirectory","Cache","subDirectory","fileNamePrefix","channelCount","format","FileFormat","M4A","preset","FilePreset","High","androidFlushIntervalMs","rotateIntervalBytes","AudioRecorder","onAudioReadySubscription","onErrorSubscription","options_","isFileOutputEnabled","audioEventEmitter","AudioEventEmitter","globalThis","constructor","recorder","createAudioRecorder","isRecordingOngoing","consumeLastRecordingResult","enableFileOutput","options","requestedOptions","result","status","disableFileOutput","start","fileNameOverride","stop","pause","resume","connect","node","wasConnected","AudioApiError","getNode","disconnect","onAudioReady","callback","clearOnAudioReady","remove","addAudioEventListener","event","audioBuffer","AudioBuffer","buffer","setOnAudioReady","sampleRate","bufferLength","callbackId","subscriptionId","isRecording","isPaused","getCurrentDuration","onError","clearOnError","setOnError","exports"],"sourceRoot":"../../../src","sources":["core/AudioRecorder.ts"],"mappings":"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","ignoreList":[]}
@@ -652,6 +652,7 @@ class OfflineAudioContextMock extends BaseAudioContextMock {
652
652
  }
653
653
  }
654
654
  class AudioRecorderMock {
655
+ static lastCreated = null;
655
656
  _isRecording = false;
656
657
  _isPaused = false;
657
658
  _currentDuration = 0;
@@ -660,7 +661,16 @@ class AudioRecorderMock {
660
661
  eventEmitter = new MockAudioEventEmitter();
661
662
  onAudioReadySubscription = null;
662
663
  onErrorSubscription = null;
663
- constructor() {}
664
+ constructor() {
665
+ AudioRecorderMock.lastCreated = this;
666
+ }
667
+ static isRecordingOngoing() {
668
+ const recorder = AudioRecorderMock.lastCreated;
669
+ return recorder != null && (recorder._isRecording || recorder._isPaused);
670
+ }
671
+ static consumeLastRecordingResult() {
672
+ return null;
673
+ }
664
674
  enableFileOutput(options) {
665
675
  this._options = options || {};
666
676
  this.isFileOutputEnabled = true;