react-native-audio-api 0.11.0-nightly-95f9c99-20251215 → 0.11.0-nightly-52d0b79-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.
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +382 -39
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +45 -18
- package/android/src/main/cpp/audioapi/android/core/NativeAudioRecorder.hpp +9 -9
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidFileWriterBackend.h +33 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +170 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +46 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AudioDecoder.cpp +0 -1
- package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.cpp +83 -0
- package/android/src/main/cpp/audioapi/android/core/utils/FileOptions.h +22 -0
- package/android/src/main/cpp/audioapi/android/core/utils/MiniaudioImplementation.cpp +8 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.cpp +493 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/FFmpegFileWriter.h +70 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/ptrs.hpp +56 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.cpp +114 -0
- package/android/src/main/cpp/audioapi/android/core/utils/ffmpegBackend/utils.h +34 -0
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.cpp +296 -0
- package/android/src/main/cpp/audioapi/android/core/utils/miniaudioBackend/MiniAudioFileWriter.h +40 -0
- package/android/src/main/cpp/audioapi/android/system/NativeFileInfo.hpp +32 -0
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +2 -0
- package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +7 -3
- package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +1 -0
- package/android/src/main/java/com/swmansion/audioapi/system/NativeFileInfo.kt +18 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +1 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +2 -0
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +3 -11
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.cpp +145 -16
- package/common/cpp/audioapi/HostObjects/inputs/AudioRecorderHostObject.h +21 -6
- package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +43 -60
- package/common/cpp/audioapi/core/inputs/AudioRecorder.h +53 -33
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.cpp +42 -14
- package/common/cpp/audioapi/core/sources/RecorderAdapterNode.h +12 -9
- package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +41 -0
- package/common/cpp/audioapi/core/utils/AudioFileWriter.h +44 -0
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +101 -0
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +52 -0
- package/common/cpp/audioapi/utils/AudioFileProperties.cpp +92 -0
- package/common/cpp/audioapi/utils/AudioFileProperties.h +76 -0
- package/common/cpp/audioapi/utils/Result.hpp +323 -0
- package/common/cpp/audioapi/utils/UnitConversion.h +9 -0
- package/ios/audioapi/ios/AudioAPIModule.mm +9 -14
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -1
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +7 -6
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +39 -13
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +302 -26
- package/ios/audioapi/ios/core/NativeAudioPlayer.m +7 -11
- package/ios/audioapi/ios/core/NativeAudioRecorder.h +8 -9
- package/ios/audioapi/ios/core/NativeAudioRecorder.m +70 -76
- package/ios/audioapi/ios/core/utils/AudioDecoder.mm +1 -0
- package/ios/audioapi/ios/core/utils/FileOptions.h +31 -0
- package/ios/audioapi/ios/core/utils/FileOptions.mm +195 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.h +53 -0
- package/ios/audioapi/ios/core/utils/IOSFileWriter.mm +239 -0
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.h +47 -0
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +185 -0
- package/ios/audioapi/ios/system/AudioEngine.h +21 -16
- package/ios/audioapi/ios/system/AudioEngine.mm +138 -130
- package/ios/audioapi/ios/system/AudioSessionManager.h +19 -9
- package/ios/audioapi/ios/system/AudioSessionManager.mm +250 -215
- package/ios/audioapi/ios/system/NotificationManager.mm +24 -42
- package/lib/commonjs/api.js +82 -109
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/core/AudioRecorder.js +159 -13
- package/lib/commonjs/core/AudioRecorder.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js +17 -14
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/RecordingNotificationManager.js +22 -19
- package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/commonjs/system/notification/SimpleNotificationManager.js +16 -13
- package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -1
- package/lib/commonjs/types.js +39 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/utils/filePresets.js +43 -0
- package/lib/commonjs/utils/filePresets.js.map +1 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +6 -3
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +6 -3
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/api.js +5 -4
- package/lib/module/api.js.map +1 -1
- package/lib/module/core/AudioRecorder.js +159 -13
- package/lib/module/core/AudioRecorder.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/system/notification/PlaybackNotificationManager.js +17 -14
- package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/system/notification/RecordingNotificationManager.js +22 -19
- package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/module/system/notification/SimpleNotificationManager.js +16 -13
- package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -1
- package/lib/module/types.js +38 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils/filePresets.js +39 -0
- package/lib/module/utils/filePresets.js.map +1 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js +6 -3
- package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -1
- package/lib/module/web-system/notification/RecordingNotificationManager.js +6 -3
- package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -1
- package/lib/typescript/api.d.ts +5 -4
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/core/AudioRecorder.d.ts +69 -7
- package/lib/typescript/core/AudioRecorder.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +36 -2
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +24 -4
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +4 -3
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +4 -3
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +3 -2
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -1
- package/lib/typescript/system/notification/types.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +79 -3
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/utils/filePresets.d.ts +9 -0
- package/lib/typescript/utils/filePresets.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +4 -3
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -1
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +4 -3
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/AudioAPIModule/globals.d.ts +1 -2
- package/src/api.ts +8 -29
- package/src/core/AudioRecorder.ts +195 -23
- package/src/events/types.ts +40 -2
- package/src/interfaces.ts +34 -5
- package/src/specs/NativeAudioAPIModule.ts +2 -2
- package/src/system/notification/PlaybackNotificationManager.ts +20 -16
- package/src/system/notification/RecordingNotificationManager.ts +26 -21
- package/src/system/notification/SimpleNotificationManager.ts +18 -13
- package/src/system/notification/types.ts +1 -0
- package/src/types.ts +89 -3
- package/src/utils/filePresets.ts +47 -0
- package/src/web-system/notification/PlaybackNotificationManager.ts +9 -5
- package/src/web-system/notification/RecordingNotificationManager.ts +9 -5
|
@@ -57,10 +57,15 @@ RCT_EXPORT_MODULE(AudioAPIModule);
|
|
|
57
57
|
[super invalidate];
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
- (dispatch_queue_t)methodQueue
|
|
61
|
+
{
|
|
62
|
+
return dispatch_queue_create("com.swmansion.audioapi.MainModuleQueue", DISPATCH_QUEUE_SERIAL);
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
|
|
61
66
|
{
|
|
62
67
|
self.audioSessionManager = [[AudioSessionManager alloc] init];
|
|
63
|
-
self.audioEngine = [[AudioEngine alloc]
|
|
68
|
+
self.audioEngine = [[AudioEngine alloc] init];
|
|
64
69
|
self.notificationManager = [[NotificationManager alloc] initWithAudioAPIModule:self];
|
|
65
70
|
self.notificationRegistry = [[NotificationRegistry alloc] initWithAudioAPIModule:self];
|
|
66
71
|
|
|
@@ -118,14 +123,9 @@ RCT_EXPORT_METHOD(
|
|
|
118
123
|
resolve reject : (RCTPromiseRejectBlock)reject)
|
|
119
124
|
{
|
|
120
125
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if ([self.audioSessionManager setActive:enabled]) {
|
|
125
|
-
resolve(@"true");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
resolve(@"false");
|
|
126
|
+
auto success = [self.audioSessionManager setActive:enabled];
|
|
127
|
+
|
|
128
|
+
resolve(@(success));
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -334,9 +334,4 @@ RCT_EXPORT_METHOD(
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
- (dispatch_queue_t)methodQueue
|
|
338
|
-
{
|
|
339
|
-
return dispatch_queue_create("swmansion.audioapi.Queue", DISPATCH_QUEUE_SERIAL);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
337
|
@end
|
|
@@ -21,7 +21,7 @@ IOSAudioPlayer::IOSAudioPlayer(
|
|
|
21
21
|
while (processedFrames < numFrames) {
|
|
22
22
|
int framesToProcess = std::min(numFrames - processedFrames, RENDER_QUANTUM_SIZE);
|
|
23
23
|
|
|
24
|
-
if (isRunning_.load()) {
|
|
24
|
+
if (isRunning_.load(std::memory_order_acquire)) {
|
|
25
25
|
renderAudio_(audioBus_, framesToProcess);
|
|
26
26
|
} else {
|
|
27
27
|
audioBus_->zero();
|
|
@@ -57,13 +57,13 @@ bool IOSAudioPlayer::start()
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
bool success = [audioPlayer_ start];
|
|
60
|
-
isRunning_.store(success);
|
|
60
|
+
isRunning_.store(success, std::memory_order_release);
|
|
61
61
|
return success;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
void IOSAudioPlayer::stop()
|
|
65
65
|
{
|
|
66
|
-
isRunning_.store(false);
|
|
66
|
+
isRunning_.store(false, std::memory_order_release);
|
|
67
67
|
[audioPlayer_ stop];
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -74,13 +74,13 @@ bool IOSAudioPlayer::resume()
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
bool success = [audioPlayer_ resume];
|
|
77
|
-
isRunning_.store(success);
|
|
77
|
+
isRunning_.store(success, std::memory_order_release);
|
|
78
78
|
return success;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
void IOSAudioPlayer::suspend()
|
|
82
82
|
{
|
|
83
|
-
isRunning_.store(false);
|
|
83
|
+
isRunning_.store(false, std::memory_order_release);
|
|
84
84
|
[audioPlayer_ suspend];
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -88,7 +88,8 @@ bool IOSAudioPlayer::isRunning() const
|
|
|
88
88
|
{
|
|
89
89
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
90
90
|
|
|
91
|
-
return isRunning_.load() &&
|
|
91
|
+
return isRunning_.load(std::memory_order_acquire) &&
|
|
92
|
+
[audioEngine getState] == AudioEngineState::AudioEngineStateRunning;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
void IOSAudioPlayer::cleanup()
|
|
@@ -1,32 +1,58 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#ifdef __OBJC__ // when compiled as Objective-C
|
|
3
|
+
#ifdef __OBJC__ // when compiled as Objective-C
|
|
4
4
|
#import <NativeAudioRecorder.h>
|
|
5
|
-
#else
|
|
5
|
+
#else
|
|
6
|
+
typedef struct objc_object NSURL;
|
|
7
|
+
typedef struct objc_object AVAudioFile;
|
|
8
|
+
typedef struct objc_object AudioBufferList;
|
|
6
9
|
typedef struct objc_object NativeAudioRecorder;
|
|
7
10
|
#endif // __OBJC__
|
|
8
11
|
|
|
9
12
|
#include <audioapi/core/inputs/AudioRecorder.h>
|
|
13
|
+
#include <audioapi/utils/Result.hpp>
|
|
14
|
+
|
|
15
|
+
#include <mutex>
|
|
10
16
|
|
|
11
17
|
namespace audioapi {
|
|
12
18
|
|
|
13
|
-
class
|
|
14
|
-
class
|
|
19
|
+
class FileWriter;
|
|
20
|
+
class RecorderCallback;
|
|
21
|
+
class RecorderAdapterNode;
|
|
22
|
+
class AudioFileProperties;
|
|
23
|
+
class AudioEventHandlerRegistry;
|
|
15
24
|
|
|
16
25
|
class IOSAudioRecorder : public AudioRecorder {
|
|
17
26
|
public:
|
|
18
|
-
IOSAudioRecorder(
|
|
19
|
-
float sampleRate,
|
|
20
|
-
int bufferLength,
|
|
21
|
-
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
|
|
22
|
-
|
|
27
|
+
IOSAudioRecorder(const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
|
|
23
28
|
~IOSAudioRecorder() override;
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
Result<std::string, std::string> start() override;
|
|
31
|
+
Result<std::tuple<std::string, double, double>, std::string> stop() override;
|
|
32
|
+
|
|
33
|
+
Result<std::string, std::string> enableFileOutput(
|
|
34
|
+
std::shared_ptr<AudioFileProperties> properties) override;
|
|
35
|
+
void disableFileOutput() override;
|
|
36
|
+
|
|
37
|
+
void connect(const std::shared_ptr<RecorderAdapterNode> &node) override;
|
|
38
|
+
void disconnect() override;
|
|
39
|
+
|
|
40
|
+
void pause() override;
|
|
41
|
+
void resume() override;
|
|
42
|
+
|
|
43
|
+
bool isRecording() const override;
|
|
44
|
+
bool isPaused() const override;
|
|
45
|
+
bool isIdle() const override;
|
|
46
|
+
|
|
47
|
+
Result<NoneType, std::string> setOnAudioReadyCallback(
|
|
48
|
+
float sampleRate,
|
|
49
|
+
size_t bufferLength,
|
|
50
|
+
int channelCount,
|
|
51
|
+
uint64_t callbackId) override;
|
|
52
|
+
void clearOnAudioReadyCallback() override;
|
|
27
53
|
|
|
28
|
-
|
|
29
|
-
NativeAudioRecorder *
|
|
54
|
+
protected:
|
|
55
|
+
NativeAudioRecorder *nativeRecorder_;
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
} // namespace audioapi
|
|
@@ -1,70 +1,346 @@
|
|
|
1
1
|
#import <AVFoundation/AVFoundation.h>
|
|
2
|
+
#import <AudioEngine.h>
|
|
3
|
+
#import <AudioSessionManager.h>
|
|
4
|
+
#import <Foundation/Foundation.h>
|
|
2
5
|
|
|
6
|
+
#include <unordered_map>
|
|
7
|
+
|
|
8
|
+
#include <audioapi/core/sources/RecorderAdapterNode.h>
|
|
9
|
+
#include <audioapi/core/utils/AudioFileWriter.h>
|
|
3
10
|
#include <audioapi/core/utils/Constants.h>
|
|
11
|
+
#include <audioapi/core/utils/Locker.h>
|
|
4
12
|
#include <audioapi/dsp/VectorMath.h>
|
|
5
13
|
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
6
14
|
#include <audioapi/ios/core/IOSAudioRecorder.h>
|
|
15
|
+
#include <audioapi/ios/core/utils/IOSFileWriter.h>
|
|
16
|
+
#include <audioapi/ios/core/utils/IOSRecorderCallback.h>
|
|
17
|
+
#include <audioapi/ios/system/AudioEngine.h>
|
|
7
18
|
#include <audioapi/utils/AudioArray.h>
|
|
8
19
|
#include <audioapi/utils/AudioBus.h>
|
|
20
|
+
#include <audioapi/utils/AudioFileProperties.h>
|
|
9
21
|
#include <audioapi/utils/CircularAudioArray.h>
|
|
10
22
|
#include <audioapi/utils/CircularOverflowableAudioArray.h>
|
|
11
|
-
#include <
|
|
23
|
+
#include <audioapi/utils/Result.hpp>
|
|
12
24
|
|
|
13
25
|
namespace audioapi {
|
|
14
26
|
|
|
27
|
+
/// @brief Constructs an IOSAudioRecorder instance.
|
|
28
|
+
/// This constructor initializes the receiver block and native side recorder wrapper (AVAudioSinkNode).
|
|
29
|
+
/// All other necessary fields (like buffers) are initialized in start() method.
|
|
30
|
+
/// This "method" should be called from the JS thread only.
|
|
31
|
+
/// @param audioEventHandlerRegistry Shared pointer to the AudioEventHandlerRegistry for event handling.
|
|
15
32
|
IOSAudioRecorder::IOSAudioRecorder(
|
|
16
|
-
float sampleRate,
|
|
17
|
-
int bufferLength,
|
|
18
33
|
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry)
|
|
19
|
-
: AudioRecorder(
|
|
34
|
+
: AudioRecorder(audioEventHandlerRegistry)
|
|
20
35
|
{
|
|
21
|
-
AudioReceiverBlock
|
|
22
|
-
if (
|
|
23
|
-
auto
|
|
24
|
-
|
|
36
|
+
AudioReceiverBlock receiverBlock = ^(const AudioBufferList *inputBuffer, int numFrames) {
|
|
37
|
+
if (usesFileOutput()) {
|
|
38
|
+
if (auto lock = Locker::tryLock(fileWriterMutex_)) {
|
|
39
|
+
std::static_pointer_cast<IOSFileWriter>(fileWriter_)
|
|
40
|
+
->writeAudioData(inputBuffer, numFrames);
|
|
41
|
+
}
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
|
|
28
|
-
auto
|
|
29
|
-
|
|
44
|
+
if (usesCallback()) {
|
|
45
|
+
if (auto lock = Locker::tryLock(callbackMutex_)) {
|
|
46
|
+
std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
|
|
47
|
+
->receiveAudioData(inputBuffer, numFrames);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
30
50
|
|
|
31
|
-
|
|
51
|
+
if (isConnected()) {
|
|
52
|
+
if (auto lock = Locker::tryLock(adapterNodeMutex_)) {
|
|
53
|
+
for (size_t channel = 0; channel < adapterNode_->channelCount_; ++channel) {
|
|
54
|
+
float *channelData = (float *)inputBuffer->mBuffers[channel].mData;
|
|
32
55
|
|
|
33
|
-
|
|
56
|
+
adapterNode_->buff_[channel]->write(channelData, numFrames);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
34
59
|
}
|
|
35
60
|
};
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
bufferLength:bufferLength
|
|
39
|
-
sampleRate:sampleRate];
|
|
62
|
+
nativeRecorder_ = [[NativeAudioRecorder alloc] initWithReceiverBlock:receiverBlock];
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
IOSAudioRecorder::~IOSAudioRecorder()
|
|
43
66
|
{
|
|
44
67
|
stop();
|
|
45
|
-
[
|
|
68
|
+
[nativeRecorder_ cleanup];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// @brief Starts the audio recording process and prepares necessary resources.
|
|
72
|
+
/// This method should be called from the JS thread only.
|
|
73
|
+
/// @returns Result containing the file path if recording started successfully, or an error message.
|
|
74
|
+
Result<std::string, std::string> IOSAudioRecorder::start()
|
|
75
|
+
{
|
|
76
|
+
if (isRecording()) {
|
|
77
|
+
return Result<std::string, std::string>::Err("Already recording");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
std::scoped_lock startLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
|
|
81
|
+
AudioSessionManager *audioSessionManager = [AudioSessionManager sharedInstance];
|
|
82
|
+
|
|
83
|
+
if ([[audioSessionManager checkRecordingPermissions] isEqual:@"Denied"]) {
|
|
84
|
+
return Result<std::string, std::string>::Err("Microphone permissions are not granted");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// TODO: recorder should probably request the options if not set by user
|
|
88
|
+
// but lets handle that in another PR
|
|
89
|
+
if (![audioSessionManager isSessionActive]) {
|
|
90
|
+
return Result<std::string, std::string>::Err("Audio session is not active");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// TODO: this is a bit ugly, and could be written slightly better
|
|
94
|
+
// we need to stop the audio engine if it's running, to be able to get
|
|
95
|
+
// proper input format values, otherwise the system my zero out the sample rate or channel count
|
|
96
|
+
// if input wasn't used yet
|
|
97
|
+
// (especially on simulators)
|
|
98
|
+
// Engine will be started again once the native recorder starts
|
|
99
|
+
[AudioEngine.sharedInstance stopIfNecessary];
|
|
100
|
+
|
|
101
|
+
// Estimate the maximum input buffer lengths that can be expected from the sink node
|
|
102
|
+
size_t maxInputBufferLength = [nativeRecorder_ getBufferSize];
|
|
103
|
+
auto inputFormat = [nativeRecorder_ getInputFormat];
|
|
104
|
+
|
|
105
|
+
if (usesFileOutput()) {
|
|
106
|
+
auto fileResult = std::static_pointer_cast<IOSFileWriter>(fileWriter_)
|
|
107
|
+
->openFile(inputFormat, maxInputBufferLength);
|
|
108
|
+
|
|
109
|
+
if (fileResult.is_err()) {
|
|
110
|
+
return Result<std::string, std::string>::Err(
|
|
111
|
+
"Failed to open file for writing: " + fileResult.unwrap_err());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
filePath_ = fileResult.unwrap();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (usesCallback()) {
|
|
118
|
+
auto callbackResult = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
|
|
119
|
+
->prepare(inputFormat, maxInputBufferLength);
|
|
120
|
+
|
|
121
|
+
if (callbackResult.is_err()) {
|
|
122
|
+
return Result<std::string, std::string>::Err(
|
|
123
|
+
"Failed to prepare callback: " + callbackResult.unwrap_err());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (isConnected()) {
|
|
128
|
+
// TODO: pass sample rate, in case conversion is necessary
|
|
129
|
+
adapterNode_->init(maxInputBufferLength, inputFormat.channelCount);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
[nativeRecorder_ start];
|
|
133
|
+
state_.store(RecorderState::Recording, std::memory_order_release);
|
|
134
|
+
return Result<std::string, std::string>::Ok(filePath_);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// @brief Stops the audio recording process and releases resources.
|
|
138
|
+
/// It finalizes any data receiver and closes the stream.
|
|
139
|
+
/// This method should be called from the JS thread only.
|
|
140
|
+
/// @returns Result containing a tuple of the output file path, size, and duration if stopped successfully, or an error message.
|
|
141
|
+
Result<std::tuple<std::string, double, double>, std::string> IOSAudioRecorder::stop()
|
|
142
|
+
{
|
|
143
|
+
std::scoped_lock stopLock(callbackMutex_, fileWriterMutex_, adapterNodeMutex_);
|
|
144
|
+
|
|
145
|
+
std::string filePath = filePath_;
|
|
146
|
+
double outputFileSize = 0;
|
|
147
|
+
double outputDuration = 0;
|
|
148
|
+
|
|
149
|
+
if (isIdle()) {
|
|
150
|
+
return Result<std::tuple<std::string, double, double>, std::string>::Err("Not recording");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
state_.store(RecorderState::Idle, std::memory_order_release);
|
|
154
|
+
[nativeRecorder_ stop];
|
|
155
|
+
|
|
156
|
+
if (usesFileOutput()) {
|
|
157
|
+
auto fileResult = fileWriter_->closeFile();
|
|
158
|
+
|
|
159
|
+
if (fileResult.is_err()) {
|
|
160
|
+
return Result<std::tuple<std::string, double, double>, std::string>::Err(
|
|
161
|
+
"Failed to close file: " + fileResult.unwrap_err());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
outputFileSize = std::get<0>(fileResult.unwrap());
|
|
165
|
+
outputDuration = std::get<1>(fileResult.unwrap());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (usesCallback()) {
|
|
169
|
+
dataCallback_->cleanup();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (isConnected()) {
|
|
173
|
+
adapterNode_->cleanup();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
filePath_ = "";
|
|
177
|
+
return Result<std::tuple<std::string, double, double>, std::string>::Ok(
|
|
178
|
+
std::make_tuple(filePath, outputFileSize, outputDuration));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// @brief Enables file output for the recorder with specified properties.
|
|
182
|
+
/// If the recorder is already active, it will open the file for writing immediately.
|
|
183
|
+
/// This method should be called from the JS thread only.
|
|
184
|
+
/// @param properties Shared pointer to AudioFileProperties defining the output file format.
|
|
185
|
+
/// @returns Result containing the output file path if enabled successfully, or an error message.
|
|
186
|
+
Result<std::string, std::string> IOSAudioRecorder::enableFileOutput(
|
|
187
|
+
std::shared_ptr<AudioFileProperties> properties)
|
|
188
|
+
{
|
|
189
|
+
std::scoped_lock lock(fileWriterMutex_, errorCallbackMutex_);
|
|
190
|
+
fileWriter_ = std::make_shared<IOSFileWriter>(audioEventHandlerRegistry_, properties);
|
|
191
|
+
|
|
192
|
+
if (!isIdle()) {
|
|
193
|
+
auto result = std::static_pointer_cast<IOSFileWriter>(fileWriter_)
|
|
194
|
+
->openFile([nativeRecorder_ getInputFormat], [nativeRecorder_ getBufferSize]);
|
|
195
|
+
|
|
196
|
+
if (result.is_err()) {
|
|
197
|
+
return Result<std::string, std::string>::Err(
|
|
198
|
+
"Failed to open file for writing: " + result.unwrap_err());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
filePath_ = result.unwrap();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fileWriter_->setOnErrorCallback(errorCallbackId_.load(std::memory_order_acquire));
|
|
205
|
+
|
|
206
|
+
fileOutputEnabled_.store(true, std::memory_order_release);
|
|
207
|
+
return Result<std::string, std::string>::Ok(filePath_);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
void IOSAudioRecorder::disableFileOutput()
|
|
211
|
+
{
|
|
212
|
+
std::scoped_lock lock(fileWriterMutex_);
|
|
213
|
+
fileOutputEnabled_.store(false, std::memory_order_release);
|
|
214
|
+
fileWriter_ = nullptr;
|
|
46
215
|
}
|
|
47
216
|
|
|
48
|
-
|
|
217
|
+
/// @brief Connects a RecorderAdapterNode to the recorder for audio data routing.
|
|
218
|
+
/// If the recorder is already active, it will initialize the adapter node immediately.
|
|
219
|
+
/// This method should be called from the JS thread only.
|
|
220
|
+
/// @param node Shared pointer to the RecorderAdapterNode to connect.
|
|
221
|
+
void IOSAudioRecorder::connect(const std::shared_ptr<RecorderAdapterNode> &node)
|
|
49
222
|
{
|
|
50
|
-
|
|
223
|
+
std::scoped_lock lock(adapterNodeMutex_);
|
|
224
|
+
adapterNode_ = node;
|
|
225
|
+
|
|
226
|
+
if (!isIdle()) {
|
|
227
|
+
adapterNode_->init(
|
|
228
|
+
[nativeRecorder_ getBufferSize], [nativeRecorder_ getInputFormat].channelCount);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
isConnected_.store(true, std::memory_order_release);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// @brief Disconnects the currently connected RecorderAdapterNode from the recorder.
|
|
235
|
+
/// If the recorder is currently active, it will stop routing audio data immediately.
|
|
236
|
+
/// This method should be called from the JS thread only.
|
|
237
|
+
void IOSAudioRecorder::disconnect()
|
|
238
|
+
{
|
|
239
|
+
std::scoped_lock lock(adapterNodeMutex_);
|
|
240
|
+
adapterNode_ = nullptr;
|
|
241
|
+
isConnected_.store(false, std::memory_order_release);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
void IOSAudioRecorder::pause()
|
|
245
|
+
{
|
|
246
|
+
if (!isRecording()) {
|
|
51
247
|
return;
|
|
52
248
|
}
|
|
53
249
|
|
|
54
|
-
[
|
|
55
|
-
|
|
250
|
+
[nativeRecorder_ pause];
|
|
251
|
+
state_.store(RecorderState::Paused, std::memory_order_release);
|
|
56
252
|
}
|
|
57
253
|
|
|
58
|
-
void IOSAudioRecorder::
|
|
254
|
+
void IOSAudioRecorder::resume()
|
|
59
255
|
{
|
|
60
|
-
if (!
|
|
256
|
+
if (!isPaused()) {
|
|
61
257
|
return;
|
|
62
258
|
}
|
|
63
259
|
|
|
64
|
-
|
|
65
|
-
|
|
260
|
+
[nativeRecorder_ resume];
|
|
261
|
+
state_.store(RecorderState::Recording, std::memory_order_release);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// @brief Checks if the recorder is currently recording.
|
|
265
|
+
/// Besides recorder internal state, it also check if the audio engine is running.
|
|
266
|
+
/// this helps with restarts after interruptions or other audio session changes.
|
|
267
|
+
/// This method can be called from any thread.
|
|
268
|
+
/// @returns True if recording, false otherwise.
|
|
269
|
+
bool IOSAudioRecorder::isRecording() const
|
|
270
|
+
{
|
|
271
|
+
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
272
|
+
return state_.load(std::memory_order_acquire) == RecorderState::Recording &&
|
|
273
|
+
[audioEngine getState] == AudioEngineState::AudioEngineStateRunning;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// @brief Checks if the recorder is currently paused.
|
|
277
|
+
/// Besides recorder internal state, it also check if the audio engine is running.
|
|
278
|
+
/// this helps with restarts after interruptions or other audio session changes.
|
|
279
|
+
/// This method can be called from any thread.
|
|
280
|
+
/// @returns True if paused, false otherwise.
|
|
281
|
+
bool IOSAudioRecorder::isPaused() const
|
|
282
|
+
{
|
|
283
|
+
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
284
|
+
auto currentState = state_.load(std::memory_order_acquire);
|
|
285
|
+
|
|
286
|
+
if (currentState == RecorderState::Idle) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return currentState == RecorderState::Paused ||
|
|
291
|
+
[audioEngine getState] != AudioEngineState::AudioEngineStateRunning;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// @brief Checks if the recorder is currently idle (not recording or paused).
|
|
295
|
+
/// This method can be called from any thread.
|
|
296
|
+
/// @returns True if idle, false otherwise.
|
|
297
|
+
bool IOSAudioRecorder::isIdle() const
|
|
298
|
+
{
|
|
299
|
+
return state_.load(std::memory_order_acquire) == RecorderState::Idle;
|
|
300
|
+
}
|
|
66
301
|
|
|
67
|
-
|
|
302
|
+
/// @brief Sets the callback to be invoked when audio data is ready.
|
|
303
|
+
/// If the recorder is already active, it will prepare the callback for receiving audio data immediately.
|
|
304
|
+
/// This method should be called from the JS thread only.
|
|
305
|
+
/// @param sampleRate Desired sample rate for the callback audio data.
|
|
306
|
+
/// @param bufferLength Desired buffer length in frames for the callback audio data.
|
|
307
|
+
/// @param channelCount Number of channels for the callback audio data.
|
|
308
|
+
/// @param callbackId Identifier for the JS callback to be invoked.
|
|
309
|
+
/// @returns Success status or Error status with message.
|
|
310
|
+
Result<NoneType, std::string> IOSAudioRecorder::setOnAudioReadyCallback(
|
|
311
|
+
float sampleRate,
|
|
312
|
+
size_t bufferLength,
|
|
313
|
+
int channelCount,
|
|
314
|
+
uint64_t callbackId)
|
|
315
|
+
{
|
|
316
|
+
std::scoped_lock lock(callbackMutex_, errorCallbackMutex_);
|
|
317
|
+
|
|
318
|
+
dataCallback_ = std::make_shared<IOSRecorderCallback>(
|
|
319
|
+
audioEventHandlerRegistry_, sampleRate, bufferLength, channelCount, callbackId);
|
|
320
|
+
|
|
321
|
+
if (!isIdle()) {
|
|
322
|
+
auto result = std::static_pointer_cast<IOSRecorderCallback>(dataCallback_)
|
|
323
|
+
->prepare([nativeRecorder_ getInputFormat], [nativeRecorder_ getBufferSize]);
|
|
324
|
+
|
|
325
|
+
if (result.is_err()) {
|
|
326
|
+
return Result<NoneType, std::string>::Err(result.unwrap_err());
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
dataCallback_->setOnErrorCallback(errorCallbackId_.load(std::memory_order_acquire));
|
|
331
|
+
|
|
332
|
+
callbackOutputEnabled_.store(true, std::memory_order_release);
|
|
333
|
+
return Result<NoneType, std::string>::Ok(None);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/// @brief Clears the audio data callback.
|
|
337
|
+
/// If the recorder is currently active, it will stop invoking the callback immediately.
|
|
338
|
+
/// This method should be called from the JS thread only.
|
|
339
|
+
void IOSAudioRecorder::clearOnAudioReadyCallback()
|
|
340
|
+
{
|
|
341
|
+
std::scoped_lock lock(callbackMutex_);
|
|
342
|
+
callbackOutputEnabled_.store(false, std::memory_order_release);
|
|
343
|
+
dataCallback_ = nullptr;
|
|
68
344
|
}
|
|
69
345
|
|
|
70
346
|
} // namespace audioapi
|
|
@@ -39,8 +39,6 @@
|
|
|
39
39
|
|
|
40
40
|
- (bool)start
|
|
41
41
|
{
|
|
42
|
-
NSLog(@"[AudioPlayer] start");
|
|
43
|
-
|
|
44
42
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
45
43
|
assert(audioEngine != nil);
|
|
46
44
|
|
|
@@ -50,28 +48,25 @@
|
|
|
50
48
|
// break rules of at runtime modifications from docs
|
|
51
49
|
// https://developer.apple.com/documentation/avfaudio/avaudioengine?language=objc
|
|
52
50
|
//
|
|
53
|
-
// Currently we are restarting because we do not see any significant
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
[audioEngine stopEngine];
|
|
51
|
+
// Currently we are restarting because we do not see any significant performance issue and case when
|
|
52
|
+
// you will need to start and stop player very frequently
|
|
53
|
+
[audioEngine stopIfNecessary];
|
|
57
54
|
self.sourceNodeId = [audioEngine attachSourceNode:self.sourceNode format:self.format];
|
|
58
55
|
return [audioEngine startIfNecessary];
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
- (void)stop
|
|
62
59
|
{
|
|
63
|
-
NSLog(@"[AudioPlayer] stop");
|
|
64
|
-
|
|
65
60
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
66
61
|
assert(audioEngine != nil);
|
|
62
|
+
|
|
67
63
|
[audioEngine detachSourceNodeWithId:self.sourceNodeId];
|
|
68
|
-
[audioEngine
|
|
64
|
+
[audioEngine stopIfPossible];
|
|
69
65
|
self.sourceNodeId = nil;
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
- (bool)resume
|
|
73
69
|
{
|
|
74
|
-
NSLog(@"[AudioPlayer] resume");
|
|
75
70
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
76
71
|
assert(audioEngine != nil);
|
|
77
72
|
|
|
@@ -82,7 +77,8 @@
|
|
|
82
77
|
{
|
|
83
78
|
AudioEngine *audioEngine = [AudioEngine sharedInstance];
|
|
84
79
|
assert(audioEngine != nil);
|
|
85
|
-
|
|
80
|
+
|
|
81
|
+
[audioEngine pauseIfNecessary];
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
- (void)cleanup
|
|
@@ -7,25 +7,24 @@ typedef void (^AudioReceiverBlock)(const AudioBufferList *inputBuffer, int numFr
|
|
|
7
7
|
|
|
8
8
|
@interface NativeAudioRecorder : NSObject
|
|
9
9
|
|
|
10
|
-
@property (nonatomic, assign) int bufferLength;
|
|
11
|
-
@property (nonatomic, assign) float sampleRate;
|
|
12
|
-
|
|
13
10
|
@property (nonatomic, strong) AVAudioSinkNode *sinkNode;
|
|
14
11
|
@property (nonatomic, copy) AVAudioSinkNodeReceiverBlock receiverSinkBlock;
|
|
15
12
|
@property (nonatomic, copy) AudioReceiverBlock receiverBlock;
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
- (instancetype)initWithReceiverBlock:(AudioReceiverBlock)receiverBlock;
|
|
15
|
+
|
|
16
|
+
- (AVAudioFormat *)getInputFormat;
|
|
20
17
|
|
|
21
|
-
- (
|
|
22
|
-
bufferLength:(int)bufferLength
|
|
23
|
-
sampleRate:(float)sampleRate;
|
|
18
|
+
- (int)getBufferSize;
|
|
24
19
|
|
|
25
20
|
- (void)start;
|
|
26
21
|
|
|
27
22
|
- (void)stop;
|
|
28
23
|
|
|
24
|
+
- (void)pause;
|
|
25
|
+
|
|
26
|
+
- (void)resume;
|
|
27
|
+
|
|
29
28
|
- (void)cleanup;
|
|
30
29
|
|
|
31
30
|
@end
|