1+ #include < android/log.h>
12#include < audioapi/android/core/AndroidAudioRecorder.h>
23#include < audioapi/android/core/utils/AndroidFileWriterBackend.h>
34#include < audioapi/android/core/utils/AndroidRecorderCallback.h>
67#include < audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h>
78#endif // RN_AUDIO_API_FFMPEG_DISABLED
89
10+ #include < audioapi/android/core/utils/AndroidRotatingFileWriter.h>
911#include < audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h>
1012#include < audioapi/core/sources/RecorderAdapterNode.h>
1113#include < audioapi/core/utils/Constants.h>
1921#include < string>
2022#include < unordered_map>
2123#include < utility>
24+ #include < vector>
2225
2326namespace audioapi {
2427
@@ -102,37 +105,34 @@ Result<NoneType, std::string> AndroidAudioRecorder::openAudioStream() {
102105// / RN side requires their "file://" prefix, but sometimes it returned raw path.
103106// / Most likely this was due to alpha version mistakes, but in case of problems leaving this here. (ㆆ _ ㆆ)
104107// / @returns On success, returns the file URI where the recording is being saved (if file output is enabled).
105- Result<std::string , std::string> AndroidAudioRecorder::start (const std::string &fileNameOverride) {
108+ Result<NoneType , std::string> AndroidAudioRecorder::start (const std::string &fileNameOverride) {
106109 std::scoped_lock startLock (callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
107110
108111 if (!isIdle ()) {
109- return Result<std::string , std::string>::Err (" Recorder is already recording" );
112+ return Result<NoneType , std::string>::Err (" Recorder is already recording" );
110113 }
111114
112115 auto streamResult = openAudioStream ();
113116
114117 if (!streamResult.is_ok ()) {
115- return Result<std::string , std::string>::Err (streamResult.unwrap_err ());
118+ return Result<NoneType , std::string>::Err (streamResult.unwrap_err ());
116119 }
117120
118121 if (mStream_ == nullptr ) {
119- return Result<std::string , std::string>::Err (" Audio stream is not initialized." );
122+ return Result<NoneType , std::string>::Err (" Audio stream is not initialized." );
120123 }
121124
122125 if (usesFileOutput ()) {
123- auto fileResult = std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
124- ->openFile (
125- streamSampleRate_,
126- streamChannelCount_,
127- streamMaxBufferSizeInFrames_,
128- fileNameOverride);
129-
130- if (!fileResult.is_ok ()) {
131- return Result<std::string, std::string>::Err (
132- " Failed to open file for writing: " + fileResult.unwrap_err ());
126+ recordingSegmentPaths_.clear ();
127+ auto writerResult = setupFileWriter (fileProperties_, fileNameOverride);
128+ if (!writerResult.is_ok ()) {
129+ return writerResult;
133130 }
134-
135- filePath_ = fileResult.unwrap ();
131+ __android_log_print (
132+ ANDROID_LOG_INFO,
133+ " AndroidAudioRecorder" ,
134+ " File created successfully at path: %s" ,
135+ filePath_.c_str ());
136136 }
137137
138138 if (usesCallback ()) {
@@ -149,32 +149,32 @@ Result<std::string, std::string> AndroidAudioRecorder::start(const std::string &
149149 auto result = mStream_ ->requestStart ();
150150
151151 if (result != oboe::Result::OK) {
152- return Result<std::string , std::string>::Err (
152+ return Result<NoneType , std::string>::Err (
153153 " Failed to start stream: " + std::string (oboe::convertToText (result)));
154154 }
155155
156156 state_.store (RecorderState::Recording, std::memory_order_release);
157- return Result<std::string , std::string>::Ok (std::format ( " file://{} " , filePath_) );
157+ return Result<NoneType , std::string>::Ok (None );
158158}
159159
160160// / @brief Stops the audio stream and finalizes any output (file writing, callback, adapter node).
161161// / This method should be called from the JS thread only.
162162// / @returns On success, returns the file URI, size in MB and duration in seconds of the recorded file (if file output is enabled).
163163// / NOTE: due to the file access nature on Android, the size might sometimes be zeroed (really long files).
164- Result<std::tuple<std::string, double , double >, std::string> AndroidAudioRecorder::stop () {
164+ Result<std::tuple<std::vector<std::string>, double , double >, std::string>
165+ AndroidAudioRecorder::stop () {
165166 std::scoped_lock stopLock (callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
166167
167- std::string filePath = std::format (" file://{}" , filePath_);
168168 double outputFileSize = 0.0 ;
169169 double outputDuration = 0.0 ;
170170
171171 if (isIdle ()) {
172- return Result<std::tuple<std::string, double , double >, std::string>::Err (
172+ return Result<std::tuple<std::vector<std:: string> , double , double >, std::string>::Err (
173173 " Recorder is not in recording state." );
174174 }
175175
176176 if (mStream_ == nullptr ) {
177- return Result<std::tuple<std::string, double , double >, std::string>::Err (
177+ return Result<std::tuple<std::vector<std:: string> , double , double >, std::string>::Err (
178178 " Audio stream is not initialized." );
179179 }
180180
@@ -185,7 +185,7 @@ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorde
185185 auto fileResult = fileWriter_->closeFile ();
186186
187187 if (!fileResult.is_ok ()) {
188- return Result<std::tuple<std::string, double , double >, std::string>::Err (
188+ return Result<std::tuple<std::vector<std:: string> , double , double >, std::string>::Err (
189189 " Failed to close file: " + fileResult.unwrap_err ());
190190 }
191191
@@ -201,9 +201,20 @@ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorde
201201 adapterNode_->adapterCleanup ();
202202 }
203203
204+ std::vector<std::string> outputPaths;
205+ for (const auto &raw : recordingSegmentPaths_) {
206+ if (!raw.empty ()) {
207+ outputPaths.push_back (std::format (" file://{}" , raw));
208+ }
209+ }
210+ if (usesFileOutput () && outputPaths.empty () && !filePath_.empty ()) {
211+ outputPaths.push_back (std::format (" file://{}" , filePath_));
212+ }
213+
214+ recordingSegmentPaths_.clear ();
204215 filePath_ = " " ;
205- return Result<std::tuple<std::string, double , double >, std::string>::Ok (
206- {filePath , outputFileSize, outputDuration} );
216+ return Result<std::tuple<std::vector<std:: string> , double , double >, std::string>::Ok (
217+ std::make_tuple ( std::move (outputPaths) , outputFileSize, outputDuration) );
207218}
208219
209220// / @brief Enables file output for the recorder with the specified properties.
@@ -213,37 +224,85 @@ Result<std::tuple<std::string, double, double>, std::string> AndroidAudioRecorde
213224// / This method should be called from the JS thread only.
214225// / @param properties Properties defining the audio file format and encoding options.
215226// / @returns On success, returns the file URI where the recording is being saved, otherwise returns an error message.
216- Result<std::string , std::string> AndroidAudioRecorder::enableFileOutput (
227+ Result<NoneType , std::string> AndroidAudioRecorder::enableFileOutput (
217228 std::shared_ptr<AudioFileProperties> properties) {
218229 std::scoped_lock fileWriterLock (fileWriterMutex_);
230+ fileProperties_ = properties;
219231
220- if (properties->format == AudioFileProperties::Format::WAV) {
221- fileWriter_ = std::make_shared<MiniAudioFileWriter>(audioEventHandlerRegistry_, properties);
222- } else {
232+ if (!isIdle ()) {
233+ auto writerResult = setupFileWriter (properties);
234+ if (!writerResult.is_ok ()) {
235+ return writerResult;
236+ }
237+ }
238+
239+ fileOutputEnabled_.store (true , std::memory_order_release);
240+ return Result<NoneType, std::string>::Ok (None);
241+ }
242+
243+ std::shared_ptr<AudioFileWriter> AndroidAudioRecorder::createFileWriter (
244+ const std::shared_ptr<AudioFileProperties> &props) {
245+ if (props->format == AudioFileProperties::Format::WAV) {
246+ return std::make_shared<MiniAudioFileWriter>(
247+ audioEventHandlerRegistry_,
248+ props,
249+ streamSampleRate_,
250+ streamChannelCount_,
251+ streamMaxBufferSizeInFrames_);
252+ }
223253#if !RN_AUDIO_API_FFMPEG_DISABLED
224- fileWriter_ = std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
225- audioEventHandlerRegistry_, properties);
254+ return std::make_shared<android::ffmpeg::FFmpegAudioFileWriter>(
255+ audioEventHandlerRegistry_,
256+ props,
257+ streamSampleRate_,
258+ streamChannelCount_,
259+ streamMaxBufferSizeInFrames_);
226260#else
261+ return nullptr ;
262+ #endif
263+ }
264+
265+ Result<NoneType, std::string> AndroidAudioRecorder::setupFileWriter (
266+ const std::shared_ptr<AudioFileProperties> &properties,
267+ const std::string &fileNameOverride) {
268+ #if RN_AUDIO_API_FFMPEG_DISABLED
269+ if (properties->format != AudioFileProperties::Format::WAV) {
227270 return Result<std::string, std::string>::Err (
228271 " FFmpeg backend is disabled. Cannot create file writer for the requested format. Use WAV format instead." );
272+ }
229273#endif
274+
275+ if (properties->rotateIntervalBytes > 0 ) {
276+ fileWriter_ = std::make_shared<AndroidRotatingFileWriter>(
277+ audioEventHandlerRegistry_,
278+ properties,
279+ properties->rotateIntervalBytes ,
280+ [this ](const std::shared_ptr<AudioFileProperties> &p) { return createFileWriter (p); },
281+ [this ](const std::string &path) {
282+ if (!path.empty ()) {
283+ recordingSegmentPaths_.push_back (path);
284+ }
285+ });
286+ } else {
287+ fileWriter_ = createFileWriter (properties);
230288 }
231289
232- if (!isIdle ()) {
233- auto fileResult =
234- std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
235- ->openFile (streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_, " " );
290+ fileWriter_->setOnErrorCallback (errorCallbackId_.load (std::memory_order_acquire));
236291
237- if (!fileResult.is_ok ()) {
238- return Result<std::string, std::string>::Err (
239- " Failed to open file for writing: " + fileResult.unwrap_err ());
240- }
292+ auto backend = std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_);
293+ auto fileResult = backend->openFile (
294+ streamSampleRate_, streamChannelCount_, streamMaxBufferSizeInFrames_, fileNameOverride);
241295
242- filePath_ = fileResult.unwrap ();
296+ if (!fileResult.is_ok ()) {
297+ return Result<NoneType, std::string>::Err (
298+ " Failed to open file for writing: " + fileResult.unwrap_err ());
243299 }
244300
245- fileOutputEnabled_.store (true , std::memory_order_release);
246- return Result<std::string, std::string>::Ok (filePath_);
301+ filePath_ = fileResult.unwrap ();
302+ if (properties->rotateIntervalBytes == 0 ) {
303+ recordingSegmentPaths_.push_back (filePath_);
304+ }
305+ return Result<NoneType, std::string>::Ok (None);
247306}
248307
249308// / @brief Disables file output for the recorder.
@@ -360,8 +419,7 @@ oboe::DataCallbackResult AndroidAudioRecorder::onAudioReady(
360419
361420 if (usesFileOutput ()) {
362421 if (auto fileWriterLock = Locker::tryLock (fileWriterMutex_)) {
363- std::static_pointer_cast<AndroidFileWriterBackend>(fileWriter_)
364- ->writeAudioData (audioData, numFrames);
422+ fileWriter_->writeAudioData (audioData, numFrames);
365423 }
366424 }
367425
0 commit comments