react-native-audio-api 0.11.0-nightly-95f9c99-20251215 → 0.11.0-nightly-dd83923-20251216

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 (138) hide show
  1. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +382 -39
  2. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +45 -18
  3. package/android/src/main/cpp/audioapi/android/core/NativeAudioRecorder.hpp +9 -9
  4. package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +33 -0
  5. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +170 -0
  6. package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +46 -0
  7. package/android/src/main/cpp/audioapi/android/core/utils/AudioDecoder.cpp +0 -1
  8. package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.cpp +83 -0
  9. package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.h +22 -0
  10. package/android/src/main/cpp/audioapi/android/core/utils/MiniaudioImplementation.cpp +8 -0
  11. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +493 -0
  12. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +70 -0
  13. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +56 -0
  14. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +114 -0
  15. package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.h +34 -0
  16. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +296 -0
  17. package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +40 -0
  18. package/android/src/main/cpp/audioapi/android/system/NativeFileInfo.hpp +32 -0
  19. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +2 -0
  20. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +7 -3
  21. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +1 -0
  22. package/android/src/main/java/com/swmansion/audioapi/system/NativeFileInfo.kt +18 -0
  23. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +1 -0
  24. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +2 -0
  25. package/android/src/oldarch/NativeAudioAPIModuleSpec.java +100 -80
  26. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +3 -11
  27. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +145 -16
  28. package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +21 -6
  29. package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +43 -60
  30. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +53 -33
  31. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +42 -14
  32. package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +12 -9
  33. package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +41 -0
  34. package/common/cpp/audioapi/core/utils/AudioFileWriter.h +44 -0
  35. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +101 -0
  36. package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +52 -0
  37. package/common/cpp/audioapi/utils/AudioFileProperties.cpp +92 -0
  38. package/common/cpp/audioapi/utils/AudioFileProperties.h +76 -0
  39. package/common/cpp/audioapi/utils/Result.hpp +323 -0
  40. package/common/cpp/audioapi/utils/UnitConversion.h +9 -0
  41. package/ios/audioapi/ios/AudioAPIModule.mm +9 -14
  42. package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -1
  43. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +7 -6
  44. package/ios/audioapi/ios/core/IOSAudioRecorder.h +39 -13
  45. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +302 -26
  46. package/ios/audioapi/ios/core/NativeAudioPlayer.m +7 -11
  47. package/ios/audioapi/ios/core/NativeAudioRecorder.h +8 -9
  48. package/ios/audioapi/ios/core/NativeAudioRecorder.m +70 -76
  49. package/ios/audioapi/ios/core/utils/AudioDecoder.mm +1 -0
  50. package/ios/audioapi/ios/core/utils/FileOptions.h +33 -0
  51. package/ios/audioapi/ios/core/utils/FileOptions.mm +195 -0
  52. package/ios/audioapi/ios/core/utils/IOSFileWriter.h +53 -0
  53. package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +239 -0
  54. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.h +47 -0
  55. package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +185 -0
  56. package/ios/audioapi/ios/system/AudioEngine.h +21 -16
  57. package/ios/audioapi/ios/system/AudioEngine.mm +138 -130
  58. package/ios/audioapi/ios/system/AudioSessionManager.h +19 -9
  59. package/ios/audioapi/ios/system/AudioSessionManager.mm +250 -215
  60. package/ios/audioapi/ios/system/NotificationManager.mm +24 -42
  61. package/lib/commonjs/api.js +82 -109
  62. package/lib/commonjs/api.js.map +1 -1
  63. package/lib/commonjs/core/AudioRecorder.js +159 -13
  64. package/lib/commonjs/core/AudioRecorder.js.map +1 -1
  65. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  66. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +17 -14
  67. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -1
  68. package/lib/commonjs/system/notification/RecordingNotificationManager.js +22 -19
  69. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -1
  70. package/lib/commonjs/system/notification/SimpleNotificationManager.js +16 -13
  71. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -1
  72. package/lib/commonjs/types.js +39 -0
  73. package/lib/commonjs/types.js.map +1 -1
  74. package/lib/commonjs/utils/filePresets.js +43 -0
  75. package/lib/commonjs/utils/filePresets.js.map +1 -0
  76. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +6 -3
  77. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -1
  78. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +6 -3
  79. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -1
  80. package/lib/module/api.js +5 -4
  81. package/lib/module/api.js.map +1 -1
  82. package/lib/module/core/AudioRecorder.js +159 -13
  83. package/lib/module/core/AudioRecorder.js.map +1 -1
  84. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  85. package/lib/module/system/notification/PlaybackNotificationManager.js +17 -14
  86. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -1
  87. package/lib/module/system/notification/RecordingNotificationManager.js +22 -19
  88. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -1
  89. package/lib/module/system/notification/SimpleNotificationManager.js +16 -13
  90. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -1
  91. package/lib/module/types.js +38 -1
  92. package/lib/module/types.js.map +1 -1
  93. package/lib/module/utils/filePresets.js +39 -0
  94. package/lib/module/utils/filePresets.js.map +1 -0
  95. package/lib/module/web-system/notification/PlaybackNotificationManager.js +6 -3
  96. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -1
  97. package/lib/module/web-system/notification/RecordingNotificationManager.js +6 -3
  98. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -1
  99. package/lib/typescript/api.d.ts +5 -4
  100. package/lib/typescript/api.d.ts.map +1 -1
  101. package/lib/typescript/core/AudioRecorder.d.ts +69 -7
  102. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
  103. package/lib/typescript/events/types.d.ts +36 -2
  104. package/lib/typescript/events/types.d.ts.map +1 -1
  105. package/lib/typescript/interfaces.d.ts +24 -4
  106. package/lib/typescript/interfaces.d.ts.map +1 -1
  107. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -1
  108. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  109. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +4 -3
  110. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -1
  111. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +4 -3
  112. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -1
  113. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +3 -2
  114. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -1
  115. package/lib/typescript/system/notification/types.d.ts.map +1 -1
  116. package/lib/typescript/types.d.ts +79 -3
  117. package/lib/typescript/types.d.ts.map +1 -1
  118. package/lib/typescript/utils/filePresets.d.ts +9 -0
  119. package/lib/typescript/utils/filePresets.d.ts.map +1 -0
  120. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +4 -3
  121. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -1
  122. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +4 -3
  123. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -1
  124. package/package.json +4 -4
  125. package/src/AudioAPIModule/globals.d.ts +1 -2
  126. package/src/api.ts +8 -29
  127. package/src/core/AudioRecorder.ts +195 -23
  128. package/src/events/types.ts +40 -2
  129. package/src/interfaces.ts +34 -5
  130. package/src/specs/NativeAudioAPIModule.ts +2 -2
  131. package/src/system/notification/PlaybackNotificationManager.ts +20 -16
  132. package/src/system/notification/RecordingNotificationManager.ts +26 -21
  133. package/src/system/notification/SimpleNotificationManager.ts +18 -13
  134. package/src/system/notification/types.ts +1 -0
  135. package/src/types.ts +89 -3
  136. package/src/utils/filePresets.ts +47 -0
  137. package/src/web-system/notification/PlaybackNotificationManager.ts +9 -5
  138. package/src/web-system/notification/RecordingNotificationManager.ts +9 -5
@@ -1,37 +1,61 @@
1
+ #include <android/log.h>
1
2
  #include <audioapi/android/core/AndroidAudioRecorder.h>
3
+ #include <audioapi/android/core/utils/AndroidFileWriterBackend.h>
4
+ #include <audioapi/android/core/utils/AndroidRecorderCallback.h>
5
+
6
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
7
+ #include <audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h>
8
+ #endif // RN_AUDIO_API_FFMPEG_DISABLED
9
+
10
+ #include <audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h>
2
11
  #include <audioapi/core/sources/RecorderAdapterNode.h>
3
12
  #include <audioapi/core/utils/Constants.h>
13
+ #include <audioapi/core/utils/Locker.h>
4
14
  #include <audioapi/events/AudioEventHandlerRegistry.h>
5
15
  #include <audioapi/utils/AudioArray.h>
6
16
  #include <audioapi/utils/AudioBus.h>
17
+ #include <audioapi/utils/AudioFileProperties.h>
7
18
  #include <audioapi/utils/CircularAudioArray.h>
8
19
  #include <audioapi/utils/CircularOverflowableAudioArray.h>
9
20
 
10
21
  #include <memory>
22
+ #include <string>
11
23
 
12
24
  namespace audioapi {
13
25
 
14
26
  AndroidAudioRecorder::AndroidAudioRecorder(
15
- float sampleRate,
16
- int bufferLength,
17
27
  const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry)
18
- : AudioRecorder(sampleRate, bufferLength, audioEventHandlerRegistry) {
19
- AudioStreamBuilder builder;
20
- builder.setSharingMode(SharingMode::Exclusive)
21
- ->setDirection(Direction::Input)
22
- ->setFormat(AudioFormat::Float)
23
- ->setFormatConversionAllowed(true)
24
- ->setPerformanceMode(PerformanceMode::None)
25
- ->setChannelCount(1)
26
- ->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
27
- ->setDataCallback(this)
28
- ->setSampleRate(static_cast<int>(sampleRate))
29
- ->openStream(mStream_);
30
-
28
+ : AudioRecorder(audioEventHandlerRegistry),
29
+ streamSampleRate_(0.0),
30
+ streamChannelCount_(0),
31
+ streamMaxBufferSizeInFrames_(0) {
31
32
  nativeAudioRecorder_ = jni::make_global(NativeAudioRecorder::create());
32
33
  }
33
34
 
35
+ /// @brief Destructor ensures that the audio stream and each output type are closed and flushed up remaining data.
36
+ /// TODO: Possibly locks here are not necessary, but we might have an issue with oboe having raw pointer to the
37
+ /// recorder (and player) instances, thus creating race conditions during destruction.
38
+ /// callable from the JS thread only (i hope).
34
39
  AndroidAudioRecorder::~AndroidAudioRecorder() {
40
+ {
41
+ std::scoped_lock dtorLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
42
+
43
+ if (usesFileOutput()) {
44
+ fileOutputEnabled_.store(false, std::memory_order_release);
45
+ fileWriter_->closeFile();
46
+ }
47
+
48
+ if (usesCallback()) {
49
+ callbackOutputEnabled_.store(false, std::memory_order_release);
50
+ dataCallback_->cleanup();
51
+ }
52
+
53
+ if (isConnected()) {
54
+ isConnected_.store(false, std::memory_order_release);
55
+ adapterNode_->cleanup();
56
+ }
57
+ }
58
+
35
59
  nativeAudioRecorder_.release();
36
60
 
37
61
  if (mStream_) {
@@ -41,53 +65,372 @@ AndroidAudioRecorder::~AndroidAudioRecorder() {
41
65
  }
42
66
  }
43
67
 
44
- void AndroidAudioRecorder::start() {
45
- if (isRunning_.load()) {
46
- return;
68
+ /// @brief Creates and opens the Oboe audio input stream for recording.
69
+ /// calculates the "native" or hardware stream parameters for other interfaces
70
+ /// to use.
71
+ /// Callable from the JS thread only.
72
+ /// @returns Success status or Error status with message.
73
+ Result<NoneType, std::string> AndroidAudioRecorder::openAudioStream() {
74
+ if (mStream_) {
75
+ return Result<NoneType, std::string>::Ok(None);
47
76
  }
48
77
 
49
- if (mStream_) {
50
- jni::ThreadScope::WithClassLoader([this]() { nativeAudioRecorder_->start(); });
51
- mStream_->requestStart();
78
+ oboe::AudioStreamBuilder builder;
79
+ builder.setSharingMode(oboe::SharingMode::Exclusive)
80
+ ->setDirection(oboe::Direction::Input)
81
+ ->setFormat(oboe::AudioFormat::Float)
82
+ ->setFormatConversionAllowed(true)
83
+ ->setPerformanceMode(oboe::PerformanceMode::None)
84
+ ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
85
+ ->setDataCallback(this)
86
+ ->setErrorCallback(this);
87
+
88
+ auto result = builder.openStream(mStream_);
89
+
90
+ if (result != oboe::Result::OK || mStream_ == nullptr) {
91
+ return Result<NoneType, std::string>::Err(
92
+ "Failed to open audio stream: " + std::string(oboe::convertToText(result)));
52
93
  }
53
94
 
54
- isRunning_.store(true);
95
+ streamSampleRate_ = static_cast<float>(mStream_->getSampleRate());
96
+ streamChannelCount_ = mStream_->getChannelCount();
97
+ streamMaxBufferSizeInFrames_ = mStream_->getBufferSizeInFrames();
98
+
99
+ return Result<NoneType, std::string>::Ok(None);
55
100
  }
56
101
 
57
- void AndroidAudioRecorder::stop() {
58
- if (!isRunning_.load()) {
102
+ /// @brief prepares and starts the audio recording process.
103
+ /// If audio stream is opened correctly, it will set up any output configured
104
+ /// (file writing, callback, adapter node) and start the stream.
105
+ /// This method should be called from the JS thread only.
106
+ /// NOTE: I've noticed some possibly invalid file paths being returned on Android,
107
+ /// RN side requires their "file://" prefix, but sometimes it returned raw path.
108
+ /// Most likely this was due to alpha version mistakes, but in case of problems leaving this here. (ㆆ _ ㆆ)
109
+ /// @returns On success, returns the file URI where the recording is being saved (if file output is enabled).
110
+ Result<std::string, std::string> AndroidAudioRecorder::start() {
111
+ std::scoped_lock startLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
112
+
113
+ if (isRecording()) {
114
+ return Result<std::string, std::string>::Ok(std::format("file://{}", filePath_));
115
+ }
116
+
117
+ auto streamResult = openAudioStream();
118
+
119
+ if (!streamResult.is_ok()) {
120
+ return Result<std::string, std::string>::Err(streamResult.unwrap_err());
121
+ }
122
+
123
+ if (!mStream_ || !nativeAudioRecorder_) {
124
+ return Result<std::string, std::string>::Err("Audio stream is not initialized.");
125
+ }
126
+
127
+ if (usesFileOutput()) {
128
+ auto fileResult =
129
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
130
+ ->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
131
+
132
+ if (!fileResult.is_ok()) {
133
+ return Result<std::string, std::string>::Err(
134
+ "Failed to open file for writing: " + fileResult.unwrap_err());
135
+ }
136
+
137
+ filePath_ = fileResult.unwrap();
138
+ }
139
+
140
+ if (usesCallback()) {
141
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
142
+ ->prepare(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
143
+ }
144
+
145
+ if (isConnected()) {
146
+ adapterNode_->init(streamMaxBufferSizeInFrames_, streamChannelCount_);
147
+ }
148
+
149
+ auto result = mStream_->requestStart();
150
+
151
+ if (result != oboe::Result::OK) {
152
+ return Result<std::string, std::string>::Err(
153
+ "Failed to start stream: " + std::string(oboe::convertToText(result)));
154
+ }
155
+
156
+ jni::ThreadScope::WithClassLoader([this]() { nativeAudioRecorder_->start(); });
157
+
158
+ state_.store(RecorderState::Recording, std::memory_order_release);
159
+ return Result<std::string, std::string>::Ok(std::format("file://{}", filePath_));
160
+ }
161
+
162
+ /// @brief Stops the audio stream and finalizes any output (file writing, callback, adapter node).
163
+ /// This method should be called from the JS thread only.
164
+ /// @returns On success, returns the file URI, size in MB and duration in seconds of the recorded file (if file output is enabled).
165
+ /// NOTE: due to the file access nature on Android, the size might sometimes be zeroed (really long files).
166
+ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorder::stop() {
167
+ std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
168
+
169
+ std::string filePath = std::format("file://{}", filePath_);
170
+ double outputFileSize = 0.0;
171
+ double outputDuration = 0.0;
172
+
173
+ if (!isRecording()) {
174
+ return Result<std::tuple<std::string, double, double>, std::string>::Err(
175
+ "Recorder is not in recording state.");
176
+ }
177
+
178
+ if (!mStream_ || !nativeAudioRecorder_) {
179
+ return Result<std::tuple<std::string, double, double>, std::string>::Err(
180
+ "Audio stream is not initialized.");
181
+ }
182
+
183
+ state_.store(RecorderState::Idle, std::memory_order_release);
184
+ jni::ThreadScope::WithClassLoader([this]() { nativeAudioRecorder_->stop(); });
185
+ mStream_->requestStop();
186
+
187
+ if (usesFileOutput()) {
188
+ auto fileResult = fileWriter_->closeFile();
189
+
190
+ if (!fileResult.is_ok()) {
191
+ return Result<std::tuple<std::string, double, double>, std::string>::Err(
192
+ "Failed to close file: " + fileResult.unwrap_err());
193
+ }
194
+
195
+ outputFileSize = std::get<0>(fileResult.unwrap());
196
+ outputDuration = std::get<1>(fileResult.unwrap());
197
+ }
198
+
199
+ if (usesCallback()) {
200
+ dataCallback_->cleanup();
201
+ }
202
+
203
+ if (isConnected()) {
204
+ adapterNode_->cleanup();
205
+ }
206
+
207
+ filePath_ = "";
208
+ return Result<std::tuple<std::string, double, double>, std::string>::Ok(
209
+ {filePath, outputFileSize, outputDuration});
210
+ }
211
+
212
+ /// @brief Enables file output for the recorder with the specified properties.
213
+ /// If the recorder is already active, it will prepare and open the file for writing immediately.
214
+ /// Due to the nature of RN this might be called multiple times during recording session (especially during development),
215
+ /// thus the requirement of handling the "already active" case.
216
+ /// This method should be called from the JS thread only.
217
+ /// @param properties Properties defining the audio file format and encoding options.
218
+ /// @returns On success, returns the file URI where the recording is being saved, otherwise returns an error message.
219
+ Result<std::string, std::string> AndroidAudioRecorder::enableFileOutput(
220
+ std::shared_ptr<AudioFileProperties> properties) {
221
+ std::scoped_lock fileWriterLock(fileWriterMutex_);
222
+
223
+ if (properties->format == AudioFileProperties::Format::WAV) {
224
+ fileWriter_ = std::make_shared<MiniAudioFileWriter>(audioEventHandlerRegistry_, properties);
225
+ } else {
226
+ #if !RN_AUDIO_API_FFMPEG_DISABLED
227
+ fileWriter_ = std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
228
+ audioEventHandlerRegistry_, properties);
229
+ #else
230
+ return Result<std::string, std::string>::Err(
231
+ "FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead.");
232
+ #endif
233
+ }
234
+
235
+ if (!isIdle()) {
236
+ auto fileResult =
237
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
238
+ ->openFile(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
239
+
240
+ if (!fileResult.is_ok()) {
241
+ return Result<std::string, std::string>::Err(
242
+ "Failed to open file for writing: " + fileResult.unwrap_err());
243
+ }
244
+
245
+ filePath_ = fileResult.unwrap();
246
+ }
247
+
248
+ fileOutputEnabled_.store(true, std::memory_order_release);
249
+ return Result<std::string, std::string>::Ok(filePath_);
250
+ }
251
+
252
+ /// @brief Disables file output for the recorder.
253
+ /// If the recorder is currently active, it will finalize and close the file immediately.
254
+ /// This method should be called from the JS thread only.
255
+ void AndroidAudioRecorder::disableFileOutput() {
256
+ std::scoped_lock fileWriterLock(fileWriterMutex_);
257
+ fileOutputEnabled_.store(false, std::memory_order_release);
258
+ fileWriter_ = nullptr;
259
+ }
260
+
261
+ /// @brief Pauses the audio recording stream.
262
+ /// For session without active file output, this method acts same as stop().
263
+ /// This method should be called from the JS thread only.
264
+ void AndroidAudioRecorder::pause() {
265
+ if (!isRecording()) {
266
+ return;
267
+ }
268
+
269
+ mStream_->pause(0);
270
+ state_.store(RecorderState::Paused, std::memory_order_release);
271
+ }
272
+
273
+ /// @brief Resumes the audio recording stream if it was previously paused.
274
+ /// This method should be called from the JS thread only.
275
+ void AndroidAudioRecorder::resume() {
276
+ if (!isPaused()) {
59
277
  return;
60
278
  }
61
279
 
62
- isRunning_.store(false);
280
+ mStream_->start(0);
281
+ state_.store(RecorderState::Recording, std::memory_order_release);
282
+ }
63
283
 
64
- if (mStream_) {
65
- jni::ThreadScope::WithClassLoader([this]() { nativeAudioRecorder_->stop(); });
66
- mStream_->requestStop();
284
+ /// @brief Sets the callback to be invoked when audio data is ready.
285
+ /// If the recorder is already active, it will prepare the callback for receiving audio data immediately.
286
+ /// This method should be called from the JS thread only.
287
+ /// @param sampleRate Desired sample rate for the callback audio data.
288
+ /// @param bufferLength Desired buffer length in frames for the callback audio data.
289
+ /// @param channelCount Number of channels for the callback audio data.
290
+ /// @param callbackId Identifier for the JS callback to be invoked.
291
+ /// @returns Success status or Error status with message.
292
+ Result<NoneType, std::string> AndroidAudioRecorder::setOnAudioReadyCallback(
293
+ float sampleRate,
294
+ size_t bufferLength,
295
+ int channelCount,
296
+ uint64_t callbackId) {
297
+ std::scoped_lock callbackLock(callbackMutex_);
298
+ dataCallback_ = std::make_shared<AndroidRecorderCallback>(
299
+ audioEventHandlerRegistry_, sampleRate, bufferLength, channelCount, callbackId);
300
+
301
+ if (!isIdle()) {
302
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
303
+ ->prepare(streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_);
304
+ }
305
+
306
+ callbackOutputEnabled_.store(true, std::memory_order_release);
307
+
308
+ return Result<NoneType, std::string>::Ok(None);
309
+ }
310
+
311
+ /// @brief Clears the audio data callback.
312
+ /// If the recorder is currently active, it will stop invoking the callback immediately.
313
+ /// This method should be called from the JS thread only.
314
+ void AndroidAudioRecorder::clearOnAudioReadyCallback() {
315
+ std::scoped_lock callbackLock(callbackMutex_);
316
+ callbackOutputEnabled_.store(false, std::memory_order_release);
317
+ dataCallback_ = nullptr;
318
+ }
319
+
320
+ /// @brief Connects a RecorderAdapterNode to the recorder for audio data routing.
321
+ /// If the recorder is already active, it will initialize the adapter node immediately.
322
+ /// This method should be called from the JS thread only.
323
+ /// @param node Shared pointer to the RecorderAdapterNode to connect.
324
+ void AndroidAudioRecorder::connect(const std::shared_ptr<RecorderAdapterNode> &node) {
325
+ std::scoped_lock adapterLock(adapterNodeMutex_);
326
+ adapterNode_ = node;
327
+ deinterleavingBuffer_ = std::make_shared<AudioArray>(streamMaxBufferSizeInFrames_);
328
+
329
+ if (!isIdle()) {
330
+ adapterNode_->init(streamMaxBufferSizeInFrames_, streamChannelCount_);
67
331
  }
68
332
 
69
- sendRemainingData();
333
+ isConnected_.store(true, std::memory_order_release);
70
334
  }
71
335
 
72
- DataCallbackResult AndroidAudioRecorder::onAudioReady(
336
+ /// @brief Disconnects the currently connected RecorderAdapterNode from the recorder.
337
+ /// If the recorder is currently active, it will stop routing audio data immediately.
338
+ /// This method should be called from the JS thread only.
339
+ void AndroidAudioRecorder::disconnect() {
340
+ std::scoped_lock adapterLock(adapterNodeMutex_);
341
+ isConnected_.store(false, std::memory_order_release);
342
+ deinterleavingBuffer_ = nullptr;
343
+ adapterNode_ = nullptr;
344
+ }
345
+
346
+ /// @brief onAudioReady callback that is invoked by the Oboe stream when new audio data is available.
347
+ /// This method runs on the audio thread.
348
+ /// It routes the audio data to the enabled outputs: file writer, callback, and adapter node.
349
+ /// For safety measures (check note about RN of enableFileOutput), each output is protected by a lock
350
+ /// additionally to the enabled checks.
351
+ /// @param oboeStream Pointer to the Oboe audio stream.
352
+ /// @param audioData Pointer to the audio data buffer (interleaved float samples).
353
+ /// @param numFrames Number of audio frames in the data buffer.
354
+ /// @returns DataCallbackResult indicating whether to continue or stop the stream.
355
+ oboe::DataCallbackResult AndroidAudioRecorder::onAudioReady(
73
356
  oboe::AudioStream *oboeStream,
74
357
  void *audioData,
75
358
  int32_t numFrames) {
76
- if (isRunning_.load()) {
77
- auto *inputChannel = static_cast<float *>(audioData);
78
- writeToBuffers(inputChannel, numFrames);
359
+ if (isPaused()) {
360
+ return oboe::DataCallbackResult::Continue;
361
+ }
362
+
363
+ if (usesFileOutput()) {
364
+ if (auto fileWriterLock = Locker::tryLock(fileWriterMutex_)) {
365
+ std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
366
+ ->writeAudioData(audioData, numFrames);
367
+ }
79
368
  }
80
369
 
81
- while (circularBuffer_->getNumberOfAvailableFrames() >= bufferLength_) {
82
- auto bus = std::make_shared<AudioBus>(bufferLength_, 1, sampleRate_);
83
- auto *outputChannel = bus->getChannel(0)->getData();
370
+ if (usesCallback()) {
371
+ if (auto callbackLock = Locker::tryLock(callbackMutex_)) {
372
+ std::static_pointer_cast<AndroidRecorderCallback>(dataCallback_)
373
+ ->receiveAudioData(audioData, numFrames);
374
+ }
375
+ }
376
+
377
+ if (isConnected()) {
378
+ if (auto adapterLock = Locker::tryLock(adapterNodeMutex_)) {
379
+ for (int channel = 0; channel < streamChannelCount_; ++channel) {
380
+ for (int frame = 0; frame < numFrames; ++frame) {
381
+ deinterleavingBuffer_->getData()[frame] =
382
+ static_cast<float *>(audioData)[frame * streamChannelCount_ + channel];
383
+ }
384
+
385
+ adapterNode_->buff_[channel]->write(deinterleavingBuffer_->getData(), numFrames);
386
+ }
387
+ }
388
+ }
389
+
390
+ return oboe::DataCallbackResult::Continue;
391
+ }
392
+
393
+ bool AndroidAudioRecorder::isRecording() const {
394
+ return state_.load(std::memory_order_acquire) == RecorderState::Recording &&
395
+ mStream_->getState() == oboe::StreamState::Started;
396
+ }
397
+
398
+ bool AndroidAudioRecorder::isPaused() const {
399
+ return state_.load(std::memory_order_acquire) == RecorderState::Paused;
400
+ }
84
401
 
85
- circularBuffer_->pop_front(outputChannel, bufferLength_);
402
+ bool AndroidAudioRecorder::isIdle() const {
403
+ return state_.load(std::memory_order_acquire) == RecorderState::Idle;
404
+ }
86
405
 
87
- invokeOnAudioReadyCallback(bus, bufferLength_);
406
+ void AndroidAudioRecorder::cleanup() {
407
+ state_.store(RecorderState::Idle, std::memory_order_release);
408
+
409
+ if (mStream_) {
410
+ mStream_->close();
411
+ mStream_.reset();
88
412
  }
413
+ }
414
+
415
+ /// @brief onError callback that is invoked by the Oboe stream when an error occurs.
416
+ /// This method runs on the audio thread.
417
+ /// If the error is a disconnection, it attempts to reopen the stream and resume recording.
418
+ /// @param oboeStream Pointer to the Oboe audio stream.
419
+ /// @param error The oboe::Result error code.
420
+ void AndroidAudioRecorder::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result error) {
421
+ if (error == oboe::Result::ErrorDisconnected) {
422
+ cleanup();
423
+
424
+ auto streamResult = openAudioStream();
89
425
 
90
- return DataCallbackResult::Continue;
426
+ if (!streamResult.is_ok()) {
427
+ // TODO: call error callback
428
+ return;
429
+ }
430
+
431
+ mStream_->requestStart();
432
+ state_.store(RecorderState::Recording, std::memory_order_release);
433
+ }
91
434
  }
92
435
 
93
436
  } // namespace audioapi
@@ -1,40 +1,67 @@
1
1
  #pragma once
2
2
 
3
3
  #include <audioapi/core/inputs/AudioRecorder.h>
4
-
5
4
  #include <oboe/Oboe.h>
6
5
  #include <functional>
7
6
  #include <memory>
8
-
7
+ #include <string>
8
+ #include <tuple>
9
+ #include <mutex>
10
+ #include <audioapi/utils/Result.hpp>
9
11
  #include <audioapi/android/core/NativeAudioRecorder.hpp>
10
12
 
11
13
  namespace audioapi {
12
14
 
13
- using namespace oboe;
14
-
15
15
  class AudioBus;
16
+ class AudioArray;
17
+ class CircularAudioArray;
18
+ class AudioFileProperties;
19
+ class AndroidRecorderCallback;
20
+ class AndroidFileWriterBackend;
21
+ class AudioEventHandlerRegistry;
16
22
 
17
- class AndroidAudioRecorder : public AudioStreamDataCallback, public AudioRecorder {
23
+ class AndroidAudioRecorder : public oboe::AudioStreamCallback, public AudioRecorder {
18
24
  public:
19
- AndroidAudioRecorder(float sampleRate,
20
- int bufferLength,
21
- const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry
22
- );
25
+ explicit AndroidAudioRecorder(const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
26
+ ~AndroidAudioRecorder() override;
27
+ void cleanup();
28
+
29
+ Result<std::string, std::string> start() override;
30
+ Result<std::tuple<std::string, double, double>, std::string> stop() override;
23
31
 
24
- ~AndroidAudioRecorder() override;
32
+ Result<std::string, std::string> enableFileOutput(std::shared_ptr<AudioFileProperties> properties) override;
33
+ void disableFileOutput() override;
25
34
 
26
- void start() override;
27
- void stop() override;
35
+ void pause() override;
36
+ void resume() override;
37
+ bool isRecording() const override;
38
+ bool isPaused() const override;
39
+ bool isIdle() const override;
28
40
 
29
- DataCallbackResult onAudioReady(
30
- AudioStream *oboeStream,
31
- void *audioData,
32
- int32_t numFrames) override;
41
+ Result<NoneType, std::string> setOnAudioReadyCallback(float sampleRate, size_t bufferLength, int channelCount, uint64_t callbackId)
42
+ override;
43
+ void clearOnAudioReadyCallback() override;
44
+
45
+ void connect(const std::shared_ptr<RecorderAdapterNode> &node) override;
46
+ void disconnect() override;
47
+
48
+ oboe::DataCallbackResult onAudioReady(
49
+ oboe::AudioStream *oboeStream,
50
+ void *audioData,
51
+ int32_t numFrames) override;
52
+ void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error) override;
33
53
 
34
54
  private:
35
- std::shared_ptr<AudioStream> mStream_;
55
+ std::shared_ptr<AudioArray> deinterleavingBuffer_;
56
+
57
+ float streamSampleRate_;
58
+ int32_t streamChannelCount_;
59
+ int32_t streamMaxBufferSizeInFrames_;
60
+
61
+ facebook::jni::global_ref<NativeAudioRecorder> nativeAudioRecorder_;
36
62
 
37
- facebook::jni::global_ref<NativeAudioRecorder> nativeAudioRecorder_;
63
+ std::shared_ptr<oboe::AudioStream> mStream_;
64
+ Result<NoneType, std::string> openAudioStream();
38
65
  };
39
66
 
40
67
  } // namespace audioapi
@@ -22,15 +22,15 @@ class NativeAudioRecorder : public jni::JavaClass<NativeAudioRecorder> {
22
22
  return newInstance();
23
23
  }
24
24
 
25
- void start() {
26
- static const auto method = javaClassStatic()->getMethod<void()>("start");
27
- method(self());
28
- }
29
-
30
- void stop() {
31
- static const auto method = javaClassStatic()->getMethod<void()>("stop");
32
- method(self());
33
- }
25
+ void start() {
26
+ static const auto method = javaClassStatic()->getMethod<void()>("start");
27
+ method(self());
28
+ }
29
+
30
+ void stop() {
31
+ static const auto method = javaClassStatic()->getMethod<void()>("stop");
32
+ method(self());
33
+ }
34
34
  };
35
35
 
36
36
  } // namespace audioapi
@@ -0,0 +1,33 @@
1
+ #pragma once
2
+
3
+ #include <audioapi/core/utils/AudioFileWriter.h>
4
+ #include <tuple>
5
+ #include <string>
6
+ #include <memory>
7
+ #include <audioapi/utils/Result.hpp>
8
+
9
+ namespace audioapi {
10
+
11
+ class AudioFileProperties;
12
+
13
+ class AndroidFileWriterBackend : public AudioFileWriter {
14
+ public:
15
+ explicit AndroidFileWriterBackend(
16
+ const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry,
17
+ const std::shared_ptr<AudioFileProperties> &fileProperties)
18
+ : AudioFileWriter(audioEventHandlerRegistry, fileProperties) {}
19
+
20
+ virtual OpenFileResult openFile(float streamSampleRate, int32_t streamChannelCount, int32_t streamMaxBufferSize) = 0;
21
+ virtual bool writeAudioData(void *data, int numFrames) = 0;
22
+
23
+ std::string getFilePath() const override { return filePath_; }
24
+ double getCurrentDuration() const override { return static_cast<double>(framesWritten_.load(std::memory_order_acquire)) / streamSampleRate_; }
25
+
26
+ protected:
27
+ float streamSampleRate_{0};
28
+ int32_t streamChannelCount_{0};
29
+ int32_t streamMaxBufferSize_{0};
30
+ std::string filePath_{""};
31
+ };
32
+
33
+ } // namespace audioapi