react-native-audio-api 0.6.5-rc.0 → 0.6.5
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/README.md +4 -1
- package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +4 -2
- package/android/src/main/cpp/audioapi/android/core/AudioDecoder.cpp +12 -14
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +38 -8
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +9 -3
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +5 -1
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +2 -2
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +2 -2
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +46 -0
- package/android/src/main/res/drawable/skip_backward_15.xml +16 -0
- package/android/src/main/res/drawable/skip_forward_15.xml +16 -0
- package/android/src/oldarch/NativeAudioAPIModuleSpec.java +4 -0
- package/common/cpp/audioapi/HostObjects/AudioBufferBaseSourceNodeHostObject.h +17 -1
- package/common/cpp/audioapi/HostObjects/AudioBufferSourceNodeHostObject.h +3 -5
- package/common/cpp/audioapi/HostObjects/AudioScheduledSourceNodeHostObject.h +11 -2
- package/common/cpp/audioapi/core/AudioContext.cpp +6 -4
- package/common/cpp/audioapi/core/AudioContext.h +1 -1
- package/common/cpp/audioapi/core/AudioParam.cpp +17 -5
- package/common/cpp/audioapi/core/AudioParam.h +3 -0
- package/common/cpp/audioapi/core/BaseAudioContext.cpp +1 -1
- package/common/cpp/audioapi/core/BaseAudioContext.h +3 -2
- package/common/cpp/audioapi/core/OfflineAudioContext.cpp +2 -1
- package/common/cpp/audioapi/core/OfflineAudioContext.h +1 -1
- package/common/cpp/audioapi/core/effects/PeriodicWave.cpp +1 -1
- package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +4 -2
- package/common/cpp/audioapi/core/inputs/AudioRecorder.h +1 -0
- package/common/cpp/audioapi/core/sources/AudioBuffer.h +1 -0
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +26 -1
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +10 -5
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +24 -2
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +3 -1
- package/common/cpp/audioapi/core/utils/AudioNodeDestructor.h +1 -0
- package/common/cpp/audioapi/core/utils/AudioNodeManager.h +1 -2
- package/common/cpp/audioapi/dsp/AudioUtils.cpp +1 -1
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +121 -41
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +9 -5
- package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +25 -0
- package/common/cpp/audioapi/jsi/JsiPromise.cpp +1 -0
- package/common/cpp/audioapi/utils/AudioArray.h +1 -0
- package/common/cpp/audioapi/utils/CircularAudioArray.h +1 -0
- package/common/cpp/test/CMakeLists.txt +63 -0
- package/common/cpp/test/GainTest.cpp +78 -0
- package/common/cpp/test/MockAudioEventHandlerRegistry.h +22 -0
- package/common/cpp/test/OscillatorTest.cpp +22 -0
- package/common/cpp/test/RunTests.sh +26 -0
- package/ios/audioapi/ios/AudioAPIModule.mm +17 -12
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +2 -1
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +5 -2
- package/ios/audioapi/ios/system/AudioSessionManager.h +4 -0
- package/ios/audioapi/ios/system/AudioSessionManager.mm +26 -0
- package/ios/audioapi/ios/system/LockScreenManager.mm +2 -0
- package/lib/commonjs/core/AudioBufferBaseSourceNode.js +10 -6
- package/lib/commonjs/core/AudioBufferBaseSourceNode.js.map +1 -1
- package/lib/commonjs/core/AudioBufferSourceNode.js +2 -2
- package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -1
- package/lib/commonjs/core/AudioScheduledSourceNode.js +8 -4
- package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -1
- package/lib/commonjs/plugin/withAudioAPI.js +1 -1
- package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/system/AudioManager.js +3 -0
- package/lib/commonjs/system/AudioManager.js.map +1 -1
- package/lib/module/core/AudioBufferBaseSourceNode.js +10 -6
- package/lib/module/core/AudioBufferBaseSourceNode.js.map +1 -1
- package/lib/module/core/AudioBufferSourceNode.js +2 -2
- package/lib/module/core/AudioBufferSourceNode.js.map +1 -1
- package/lib/module/core/AudioScheduledSourceNode.js +8 -4
- package/lib/module/core/AudioScheduledSourceNode.js.map +1 -1
- package/lib/module/plugin/withAudioAPI.js +1 -1
- package/lib/module/plugin/withAudioAPI.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/system/AudioManager.js +3 -0
- package/lib/module/system/AudioManager.js.map +1 -1
- package/lib/typescript/core/AudioBufferBaseSourceNode.d.ts +6 -3
- package/lib/typescript/core/AudioBufferBaseSourceNode.d.ts.map +1 -1
- package/lib/typescript/core/AudioBufferSourceNode.d.ts.map +1 -1
- package/lib/typescript/core/AudioScheduledSourceNode.d.ts +3 -1
- package/lib/typescript/core/AudioScheduledSourceNode.d.ts.map +1 -1
- package/lib/typescript/interfaces.d.ts +2 -2
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +2 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/system/AudioManager.d.ts +2 -1
- package/lib/typescript/system/AudioManager.d.ts.map +1 -1
- package/lib/typescript/system/types.d.ts +11 -0
- package/lib/typescript/system/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/AudioBufferBaseSourceNode.ts +20 -7
- package/src/core/AudioBufferSourceNode.ts +2 -8
- package/src/core/AudioScheduledSourceNode.ts +11 -3
- package/src/interfaces.ts +2 -5
- package/src/plugin/withAudioAPI.ts +1 -1
- package/src/specs/NativeAudioAPIModule.ts +4 -1
- package/src/system/AudioManager.ts +10 -1
- package/src/system/types.ts +14 -0
- package/android/src/main/res/drawable/skip_backward_10.xml +0 -9
- package/android/src/main/res/drawable/skip_forward_10.xml +0 -9
|
@@ -36,8 +36,10 @@ void AudioRecorder::invokeOnAudioReadyCallback(
|
|
|
36
36
|
body.insert({"numFrames", numFrames});
|
|
37
37
|
body.insert({"when", when});
|
|
38
38
|
|
|
39
|
-
audioEventHandlerRegistry_
|
|
40
|
-
|
|
39
|
+
if (audioEventHandlerRegistry_ != nullptr) {
|
|
40
|
+
audioEventHandlerRegistry_->invokeHandlerWithEventBody(
|
|
41
|
+
"audioReady", onAudioReadyCallbackId_, body);
|
|
42
|
+
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
void AudioRecorder::sendRemainingData() {
|
|
@@ -23,6 +23,15 @@ AudioBufferBaseSourceNode::AudioBufferBaseSourceNode(BaseAudioContext *context)
|
|
|
23
23
|
std::make_shared<signalsmith::stretch::SignalsmithStretch<float>>();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
AudioBufferBaseSourceNode::~AudioBufferBaseSourceNode() {
|
|
27
|
+
if (onPositionChangedCallbackId_ != 0 &&
|
|
28
|
+
context_->audioEventHandlerRegistry_ != nullptr) {
|
|
29
|
+
context_->audioEventHandlerRegistry_->unregisterHandler(
|
|
30
|
+
"positionChanged", onPositionChangedCallbackId_);
|
|
31
|
+
onPositionChangedCallbackId_ = 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
std::shared_ptr<AudioParam> AudioBufferBaseSourceNode::getDetuneParam() const {
|
|
27
36
|
return detuneParam_;
|
|
28
37
|
}
|
|
@@ -32,6 +41,17 @@ std::shared_ptr<AudioParam> AudioBufferBaseSourceNode::getPlaybackRateParam()
|
|
|
32
41
|
return playbackRateParam_;
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
void AudioBufferBaseSourceNode::clearOnPositionChangedCallback() {
|
|
45
|
+
if (onPositionChangedCallbackId_ == 0 || context_ == nullptr ||
|
|
46
|
+
context_->audioEventHandlerRegistry_ == nullptr) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
context_->audioEventHandlerRegistry_->unregisterHandler(
|
|
51
|
+
"positionChanged", onPositionChangedCallbackId_);
|
|
52
|
+
onPositionChangedCallbackId_ = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
void AudioBufferBaseSourceNode::setOnPositionChangedCallbackId(
|
|
36
56
|
uint64_t callbackId) {
|
|
37
57
|
onPositionChangedCallbackId_ = callbackId;
|
|
@@ -42,13 +62,18 @@ void AudioBufferBaseSourceNode::setOnPositionChangedInterval(int interval) {
|
|
|
42
62
|
context_->getSampleRate() * static_cast<float>(interval) / 1000);
|
|
43
63
|
}
|
|
44
64
|
|
|
65
|
+
int AudioBufferBaseSourceNode::getOnPositionChangedInterval() {
|
|
66
|
+
return onPositionChangedInterval_;
|
|
67
|
+
}
|
|
68
|
+
|
|
45
69
|
std::mutex &AudioBufferBaseSourceNode::getBufferLock() {
|
|
46
70
|
return bufferLock_;
|
|
47
71
|
}
|
|
48
72
|
|
|
49
73
|
void AudioBufferBaseSourceNode::sendOnPositionChangedEvent() {
|
|
50
74
|
if (onPositionChangedCallbackId_ != 0 &&
|
|
51
|
-
onPositionChangedTime_ > onPositionChangedInterval_
|
|
75
|
+
onPositionChangedTime_ > onPositionChangedInterval_ &&
|
|
76
|
+
context_->audioEventHandlerRegistry_ != nullptr) {
|
|
52
77
|
std::unordered_map<std::string, EventValue> body = {
|
|
53
78
|
{"value", getCurrentPosition()}};
|
|
54
79
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
#include <audioapi/libs/signalsmith-stretch/signalsmith-stretch.h>
|
|
5
5
|
|
|
6
6
|
#include <memory>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <atomic>
|
|
7
9
|
|
|
8
10
|
namespace audioapi {
|
|
9
11
|
|
|
@@ -13,12 +15,15 @@ class AudioParam;
|
|
|
13
15
|
class AudioBufferBaseSourceNode : public AudioScheduledSourceNode {
|
|
14
16
|
public:
|
|
15
17
|
explicit AudioBufferBaseSourceNode(BaseAudioContext *context);
|
|
18
|
+
virtual ~AudioBufferBaseSourceNode();
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
[[nodiscard]] std::shared_ptr<AudioParam> getDetuneParam() const;
|
|
21
|
+
[[nodiscard]] std::shared_ptr<AudioParam> getPlaybackRateParam() const;
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
void clearOnPositionChangedCallback();
|
|
24
|
+
void setOnPositionChangedCallbackId(uint64_t callbackId);
|
|
25
|
+
void setOnPositionChangedInterval(int interval);
|
|
26
|
+
[[nodiscard]] int getOnPositionChangedInterval();
|
|
22
27
|
|
|
23
28
|
protected:
|
|
24
29
|
std::mutex bufferLock_;
|
|
@@ -34,7 +39,7 @@ class AudioBufferBaseSourceNode : public AudioScheduledSourceNode {
|
|
|
34
39
|
// internal helper
|
|
35
40
|
double vReadIndex_;
|
|
36
41
|
|
|
37
|
-
uint64_t onPositionChangedCallbackId_ = 0; // 0 means no callback
|
|
42
|
+
std::atomic<uint64_t> onPositionChangedCallbackId_ = 0; // 0 means no callback
|
|
38
43
|
int onPositionChangedInterval_;
|
|
39
44
|
int onPositionChangedTime_ = 0;
|
|
40
45
|
|
|
@@ -16,6 +16,15 @@ AudioScheduledSourceNode::AudioScheduledSourceNode(BaseAudioContext *context)
|
|
|
16
16
|
numberOfInputs_ = 0;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
AudioScheduledSourceNode::~AudioScheduledSourceNode() {
|
|
20
|
+
if (onEndedCallbackId_ != 0 &&
|
|
21
|
+
context_->audioEventHandlerRegistry_ != nullptr) {
|
|
22
|
+
context_->audioEventHandlerRegistry_->unregisterHandler(
|
|
23
|
+
"ended", onEndedCallbackId_);
|
|
24
|
+
onEndedCallbackId_ = 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
void AudioScheduledSourceNode::start(double when) {
|
|
20
29
|
playbackState_ = PlaybackState::SCHEDULED;
|
|
21
30
|
startTime_ = when;
|
|
@@ -45,6 +54,17 @@ bool AudioScheduledSourceNode::isStopScheduled() {
|
|
|
45
54
|
return playbackState_ == PlaybackState::STOP_SCHEDULED;
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
void AudioScheduledSourceNode::clearOnEndedCallback() {
|
|
58
|
+
if (onEndedCallbackId_ == 0 || context_ == nullptr ||
|
|
59
|
+
context_->audioEventHandlerRegistry_ == nullptr) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
context_->audioEventHandlerRegistry_->unregisterHandler(
|
|
64
|
+
"ended", onEndedCallbackId_);
|
|
65
|
+
onEndedCallbackId_ = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
void AudioScheduledSourceNode::setOnEndedCallbackId(const uint64_t callbackId) {
|
|
49
69
|
onEndedCallbackId_ = callbackId;
|
|
50
70
|
}
|
|
@@ -149,8 +169,10 @@ void AudioScheduledSourceNode::updatePlaybackInfo(
|
|
|
149
169
|
void AudioScheduledSourceNode::disable() {
|
|
150
170
|
AudioNode::disable();
|
|
151
171
|
|
|
152
|
-
context_->audioEventHandlerRegistry_
|
|
153
|
-
|
|
172
|
+
if (context_->audioEventHandlerRegistry_ != nullptr) {
|
|
173
|
+
context_->audioEventHandlerRegistry_->invokeHandlerWithEventBody(
|
|
174
|
+
"ended", onEndedCallbackId_, {});
|
|
175
|
+
}
|
|
154
176
|
}
|
|
155
177
|
|
|
156
178
|
void AudioScheduledSourceNode::handleStopScheduled() {
|
|
@@ -27,6 +27,7 @@ class AudioScheduledSourceNode : public AudioNode {
|
|
|
27
27
|
// FINISHED: The node has finished playing.
|
|
28
28
|
enum class PlaybackState { UNSCHEDULED, SCHEDULED, PLAYING, STOP_SCHEDULED, FINISHED };
|
|
29
29
|
explicit AudioScheduledSourceNode(BaseAudioContext *context);
|
|
30
|
+
virtual ~AudioScheduledSourceNode();
|
|
30
31
|
|
|
31
32
|
void start(double when);
|
|
32
33
|
virtual void stop(double when);
|
|
@@ -37,6 +38,7 @@ class AudioScheduledSourceNode : public AudioNode {
|
|
|
37
38
|
bool isFinished();
|
|
38
39
|
bool isStopScheduled();
|
|
39
40
|
|
|
41
|
+
void clearOnEndedCallback();
|
|
40
42
|
void setOnEndedCallbackId(uint64_t callbackId);
|
|
41
43
|
|
|
42
44
|
void disable() override;
|
|
@@ -47,7 +49,7 @@ class AudioScheduledSourceNode : public AudioNode {
|
|
|
47
49
|
|
|
48
50
|
PlaybackState playbackState_;
|
|
49
51
|
|
|
50
|
-
uint64_t onEndedCallbackId_ = 0;
|
|
52
|
+
std::atomic<uint64_t> onEndedCallbackId_ = 0;
|
|
51
53
|
|
|
52
54
|
void updatePlaybackInfo(
|
|
53
55
|
const std::shared_ptr<AudioBus>& processingBus,
|
|
@@ -20,8 +20,6 @@ class AudioNodeManager {
|
|
|
20
20
|
AudioNodeManager() = default;
|
|
21
21
|
~AudioNodeManager();
|
|
22
22
|
|
|
23
|
-
std::mutex &getGraphLock();
|
|
24
|
-
|
|
25
23
|
void preProcessGraph();
|
|
26
24
|
|
|
27
25
|
void addPendingNodeConnection(
|
|
@@ -62,6 +60,7 @@ class AudioNodeManager {
|
|
|
62
60
|
ConnectionType>>
|
|
63
61
|
audioParamToConnect_;
|
|
64
62
|
|
|
63
|
+
std::mutex &getGraphLock();
|
|
65
64
|
void settlePendingConnections();
|
|
66
65
|
void cleanupNode(const std::shared_ptr<AudioNode> &node);
|
|
67
66
|
void prepareNodesForDestruction();
|
|
@@ -4,7 +4,8 @@ namespace audioapi {
|
|
|
4
4
|
|
|
5
5
|
AudioEventHandlerRegistry::AudioEventHandlerRegistry(
|
|
6
6
|
jsi::Runtime *runtime,
|
|
7
|
-
const std::shared_ptr<react::CallInvoker> &callInvoker)
|
|
7
|
+
const std::shared_ptr<react::CallInvoker> &callInvoker)
|
|
8
|
+
: IAudioEventHandlerRegistry() {
|
|
8
9
|
runtime_ = runtime;
|
|
9
10
|
callInvoker_ = callInvoker;
|
|
10
11
|
|
|
@@ -24,57 +25,135 @@ AudioEventHandlerRegistry::~AudioEventHandlerRegistry() {
|
|
|
24
25
|
uint64_t AudioEventHandlerRegistry::registerHandler(
|
|
25
26
|
const std::string &eventName,
|
|
26
27
|
const std::shared_ptr<jsi::Function> &handler) {
|
|
27
|
-
|
|
28
|
+
uint64_t listenerId = listenerIdCounter_++;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
if (callInvoker_ == nullptr || runtime_ == nullptr) {
|
|
31
|
+
// If callInvoker or runtime is not valid, we cannot register the handler
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Modify the eventHandlers_ map only on the main RN thread
|
|
36
|
+
callInvoker_->invokeAsync([this, eventName, listenerId, handler]() {
|
|
37
|
+
eventHandlers_[eventName][listenerId] = handler;
|
|
38
|
+
});
|
|
30
39
|
|
|
31
|
-
return
|
|
40
|
+
return listenerId;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
void AudioEventHandlerRegistry::unregisterHandler(
|
|
35
44
|
const std::string &eventName,
|
|
36
45
|
uint64_t listenerId) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
if (callInvoker_ == nullptr || runtime_ == nullptr) {
|
|
47
|
+
// If callInvoker or runtime is not valid, we cannot unregister the handler
|
|
48
|
+
return;
|
|
40
49
|
}
|
|
50
|
+
|
|
51
|
+
callInvoker_->invokeAsync([this, eventName, listenerId]() {
|
|
52
|
+
auto it = eventHandlers_.find(eventName);
|
|
53
|
+
|
|
54
|
+
if (it == eventHandlers_.end()) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
auto &handlersMap = it->second;
|
|
59
|
+
auto handlerIt = handlersMap.find(listenerId);
|
|
60
|
+
|
|
61
|
+
if (handlerIt != handlersMap.end()) {
|
|
62
|
+
handlersMap.erase(handlerIt);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
41
65
|
}
|
|
42
66
|
|
|
43
67
|
void AudioEventHandlerRegistry::invokeHandlerWithEventBody(
|
|
44
68
|
const std::string &eventName,
|
|
45
69
|
const std::unordered_map<std::string, EventValue> &body) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
// callInvoker_ and runtime_ must be valid to invoke handlers
|
|
71
|
+
// this might happen when react-native is reloaded or the app is closed
|
|
72
|
+
if (callInvoker_ == nullptr || runtime_ == nullptr) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Do any logic regarding triggering the event on the main RN thread
|
|
77
|
+
callInvoker_->invokeAsync([this, eventName, body]() {
|
|
78
|
+
auto it = eventHandlers_.find(eventName);
|
|
79
|
+
|
|
80
|
+
if (it == eventHandlers_.end()) {
|
|
81
|
+
// If the event name is not registered, we can skip invoking handlers
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
auto handlersMap = it->second;
|
|
86
|
+
|
|
87
|
+
for (const auto &pair : handlersMap) {
|
|
49
88
|
auto handler = pair.second;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
89
|
+
|
|
90
|
+
if (!handler || !handler->isFunction(*runtime_)) {
|
|
91
|
+
// If the handler is not valid, we can skip it
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
auto eventObject = createEventObject(body);
|
|
97
|
+
handler->call(*runtime_, eventObject);
|
|
98
|
+
} catch (const std::exception &e) {
|
|
99
|
+
// re-throw the exception to be handled by the caller
|
|
100
|
+
// std::exception is safe to parse by the rn bridge
|
|
101
|
+
throw;
|
|
102
|
+
} catch (...) {
|
|
103
|
+
printf(
|
|
104
|
+
"Unknown exception occurred while invoking handler for event: %s\n",
|
|
105
|
+
eventName.c_str());
|
|
55
106
|
}
|
|
56
107
|
}
|
|
57
|
-
}
|
|
108
|
+
});
|
|
58
109
|
}
|
|
59
110
|
|
|
60
111
|
void AudioEventHandlerRegistry::invokeHandlerWithEventBody(
|
|
61
112
|
const std::string &eventName,
|
|
62
113
|
uint64_t listenerId,
|
|
63
114
|
const std::unordered_map<std::string, EventValue> &body) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (handlerIt != handlersMap.end()) {
|
|
69
|
-
auto handler = handlerIt->second;
|
|
70
|
-
if (handler) {
|
|
71
|
-
callInvoker_->invokeAsync([this, handler, body]() {
|
|
72
|
-
auto eventObject = createEventObject(body);
|
|
73
|
-
handler->call(*runtime_, eventObject);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
115
|
+
// callInvoker_ and runtime_ must be valid to invoke handlers
|
|
116
|
+
// this might happen when react-native is reloaded or the app is closed
|
|
117
|
+
if (callInvoker_ == nullptr || runtime_ == nullptr) {
|
|
118
|
+
return;
|
|
77
119
|
}
|
|
120
|
+
|
|
121
|
+
callInvoker_->invokeAsync([this, eventName, listenerId, body]() {
|
|
122
|
+
auto it = eventHandlers_.find(eventName);
|
|
123
|
+
|
|
124
|
+
if (it == eventHandlers_.end()) {
|
|
125
|
+
// If the event name is not registered, we can skip invoking handlers
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
auto handlerIt = it->second.find(listenerId);
|
|
130
|
+
|
|
131
|
+
if (handlerIt == it->second.end()) {
|
|
132
|
+
// If the listener ID is not registered, we can skip invoking handlers
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Depending on how the AudioBufferSourceNode is handled on the JS side,
|
|
137
|
+
// it sometimes might enter race condition where the ABSN is deleted on JS
|
|
138
|
+
// side, but it is still processed on the audio thread, leading to a crash
|
|
139
|
+
// when f.e. `positionChanged` event is triggered.
|
|
140
|
+
|
|
141
|
+
// In case of debugging this, please increment the hours spent counter
|
|
142
|
+
|
|
143
|
+
// Hours spent on this: 5
|
|
144
|
+
try {
|
|
145
|
+
auto eventObject = createEventObject(body);
|
|
146
|
+
handlerIt->second->call(*runtime_, eventObject);
|
|
147
|
+
} catch (const std::exception &e) {
|
|
148
|
+
// re-throw the exception to be handled by the caller
|
|
149
|
+
// std::exception is safe to parse by the rn bridge
|
|
150
|
+
throw;
|
|
151
|
+
} catch (...) {
|
|
152
|
+
printf(
|
|
153
|
+
"Unknown exception occurred while invoking handler for event: %s\n",
|
|
154
|
+
eventName.c_str());
|
|
155
|
+
}
|
|
156
|
+
});
|
|
78
157
|
}
|
|
79
158
|
|
|
80
159
|
jsi::Object AudioEventHandlerRegistry::createEventObject(
|
|
@@ -85,19 +164,20 @@ jsi::Object AudioEventHandlerRegistry::createEventObject(
|
|
|
85
164
|
const auto name = pair.first.data();
|
|
86
165
|
const auto &value = pair.second;
|
|
87
166
|
|
|
88
|
-
if (holds_alternative<int>(value)) {
|
|
89
|
-
eventObject.setProperty(*runtime_, name, get<int>(value));
|
|
90
|
-
} else if (holds_alternative<double>(value)) {
|
|
91
|
-
eventObject.setProperty(*runtime_, name, get<double>(value));
|
|
92
|
-
} else if (holds_alternative<float>(value)) {
|
|
93
|
-
eventObject.setProperty(*runtime_, name, get<float>(value));
|
|
94
|
-
} else if (holds_alternative<bool>(value)) {
|
|
95
|
-
eventObject.setProperty(*runtime_, name, get<bool>(value));
|
|
96
|
-
} else if (holds_alternative<std::string>(value)) {
|
|
97
|
-
eventObject.setProperty(*runtime_, name, get<std::string>(value));
|
|
98
|
-
} else if (holds_alternative<std::shared_ptr<jsi::HostObject>>(
|
|
167
|
+
if (std::holds_alternative<int>(value)) {
|
|
168
|
+
eventObject.setProperty(*runtime_, name, std::get<int>(value));
|
|
169
|
+
} else if (std::holds_alternative<double>(value)) {
|
|
170
|
+
eventObject.setProperty(*runtime_, name, std::get<double>(value));
|
|
171
|
+
} else if (std::holds_alternative<float>(value)) {
|
|
172
|
+
eventObject.setProperty(*runtime_, name, std::get<float>(value));
|
|
173
|
+
} else if (std::holds_alternative<bool>(value)) {
|
|
174
|
+
eventObject.setProperty(*runtime_, name, std::get<bool>(value));
|
|
175
|
+
} else if (std::holds_alternative<std::string>(value)) {
|
|
176
|
+
eventObject.setProperty(*runtime_, name, std::get<std::string>(value));
|
|
177
|
+
} else if (std::holds_alternative<std::shared_ptr<jsi::HostObject>>(
|
|
178
|
+
value)) {
|
|
99
179
|
auto hostObject = jsi::Object::createFromHostObject(
|
|
100
|
-
*runtime_, get<std::shared_ptr<jsi::HostObject>>(value));
|
|
180
|
+
*runtime_, std::get<std::shared_ptr<jsi::HostObject>>(value));
|
|
101
181
|
eventObject.setProperty(*runtime_, name, hostObject);
|
|
102
182
|
}
|
|
103
183
|
}
|
|
@@ -2,31 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
#include <jsi/jsi.h>
|
|
4
4
|
#include <ReactCommon/CallInvoker.h>
|
|
5
|
+
#include <audioapi/events/IAudioEventHandlerRegistry.h>
|
|
5
6
|
#include <memory>
|
|
6
7
|
#include <unordered_map>
|
|
7
8
|
#include <array>
|
|
8
9
|
#include <string>
|
|
9
10
|
#include <variant>
|
|
11
|
+
#include <atomic>
|
|
10
12
|
|
|
11
13
|
namespace audioapi {
|
|
12
14
|
using namespace facebook;
|
|
13
15
|
|
|
14
16
|
using EventValue = std::variant<int, float, double, std::string, bool, std::shared_ptr<jsi::HostObject>>;
|
|
15
17
|
|
|
16
|
-
class AudioEventHandlerRegistry {
|
|
18
|
+
class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry {
|
|
17
19
|
public:
|
|
18
20
|
explicit AudioEventHandlerRegistry(
|
|
19
21
|
jsi::Runtime *runtime,
|
|
20
22
|
const std::shared_ptr<react::CallInvoker> &callInvoker);
|
|
21
23
|
~AudioEventHandlerRegistry();
|
|
22
24
|
|
|
23
|
-
uint64_t registerHandler(const std::string &eventName, const std::shared_ptr<jsi::Function> &handler);
|
|
24
|
-
void unregisterHandler(const std::string &eventName, uint64_t listenerId);
|
|
25
|
+
uint64_t registerHandler(const std::string &eventName, const std::shared_ptr<jsi::Function> &handler) override;
|
|
26
|
+
void unregisterHandler(const std::string &eventName, uint64_t listenerId) override;
|
|
25
27
|
|
|
26
|
-
void invokeHandlerWithEventBody(const std::string &eventName, const std::unordered_map<std::string, EventValue> &body);
|
|
27
|
-
void invokeHandlerWithEventBody(const std::string &eventName, uint64_t listenerId, const std::unordered_map<std::string, EventValue> &body);
|
|
28
|
+
void invokeHandlerWithEventBody(const std::string &eventName, const std::unordered_map<std::string, EventValue> &body) override;
|
|
29
|
+
void invokeHandlerWithEventBody(const std::string &eventName, uint64_t listenerId, const std::unordered_map<std::string, EventValue> &body) override;
|
|
28
30
|
|
|
29
31
|
private:
|
|
32
|
+
std::atomic<uint64_t> listenerIdCounter_{1}; // Atomic counter for listener IDs
|
|
33
|
+
|
|
30
34
|
std::shared_ptr<react::CallInvoker> callInvoker_;
|
|
31
35
|
jsi::Runtime *runtime_;
|
|
32
36
|
std::unordered_map<std::string, std::unordered_map<uint64_t, std::shared_ptr<jsi::Function>>> eventHandlers_;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
#include <ReactCommon/CallInvoker.h>
|
|
5
|
+
#include <unordered_map>
|
|
6
|
+
#include <variant>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <memory>
|
|
9
|
+
|
|
10
|
+
namespace audioapi {
|
|
11
|
+
|
|
12
|
+
using EventValue = std::variant<int, float, double, std::string, bool, std::shared_ptr<facebook::jsi::HostObject>>;
|
|
13
|
+
|
|
14
|
+
class IAudioEventHandlerRegistry {
|
|
15
|
+
public:
|
|
16
|
+
virtual ~IAudioEventHandlerRegistry() = default;
|
|
17
|
+
|
|
18
|
+
virtual uint64_t registerHandler(const std::string &eventName, const std::shared_ptr<facebook::jsi::Function> &handler) = 0;
|
|
19
|
+
virtual void unregisterHandler(const std::string &eventName, uint64_t listenerId) = 0;
|
|
20
|
+
|
|
21
|
+
virtual void invokeHandlerWithEventBody(const std::string &eventName, const std::unordered_map<std::string, EventValue> &body) = 0;
|
|
22
|
+
virtual void invokeHandlerWithEventBody(const std::string &eventName, uint64_t listenerId, const std::unordered_map<std::string, EventValue> &body) = 0;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.14)
|
|
2
|
+
project(rnaudioapi_test)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(ROOT ${CMAKE_SOURCE_DIR}/../../../../..)
|
|
7
|
+
set(REACT_NATIVE_DIR "${ROOT}/node_modules/react-native")
|
|
8
|
+
set(JSI_DIR "${REACT_NATIVE_DIR}/ReactCommon/jsi")
|
|
9
|
+
|
|
10
|
+
include(FetchContent)
|
|
11
|
+
FetchContent_Declare(
|
|
12
|
+
googletest
|
|
13
|
+
URL https://github.com/google/googletest/archive/3983f67e32fb3e9294487b9d4f9586efa6e5d088.zip
|
|
14
|
+
)
|
|
15
|
+
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
|
16
|
+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
|
17
|
+
FetchContent_MakeAvailable(googletest)
|
|
18
|
+
|
|
19
|
+
enable_testing()
|
|
20
|
+
|
|
21
|
+
file(GLOB_RECURSE RNAUDIOAPI_SRC
|
|
22
|
+
CONFIGURE_DEPENDS
|
|
23
|
+
"${ROOT}/node_modules/react-native-audio-api/common/cpp/audioapi/*.cpp"
|
|
24
|
+
"${ROOT}/node_modules/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioDecoder.cpp"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
list(REMOVE_ITEM RNAUDIOAPI_SRC "${ROOT}/node_modules/react-native-audio-api/common/cpp/audioapi/core/AudioContext.cpp")
|
|
28
|
+
|
|
29
|
+
file(GLOB_RECURSE RNAUDIOAPI_LIBS
|
|
30
|
+
CONFIGURE_DEPENDS
|
|
31
|
+
"${ROOT}/node_modules/react-native-audio-api/common/cpp/audioapi/libs/*.c"
|
|
32
|
+
"${ROOT}/node_modules/react-native-audio-api/common/cpp/audioapi/libs/*.h"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
add_library(rnaudioapi STATIC ${RNAUDIOAPI_SRC})
|
|
36
|
+
add_library(rnaudioapi_libs STATIC ${RNAUDIOAPI_LIBS})
|
|
37
|
+
|
|
38
|
+
target_include_directories(rnaudioapi PUBLIC
|
|
39
|
+
${ROOT}/packages/react-native-audio-api/common/cpp
|
|
40
|
+
${JSI_DIR}
|
|
41
|
+
"${REACT_NATIVE_DIR}/ReactCommon"
|
|
42
|
+
"${REACT_NATIVE_DIR}/ReactCommon/callinvoker"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
target_include_directories(rnaudioapi_libs PUBLIC
|
|
46
|
+
${ROOT}/packages/react-native-audio-api/common/cpp
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
add_executable(
|
|
50
|
+
tests
|
|
51
|
+
OscillatorTest.cpp
|
|
52
|
+
GainTest.cpp
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
target_link_libraries(tests
|
|
56
|
+
rnaudioapi
|
|
57
|
+
rnaudioapi_libs
|
|
58
|
+
GTest::gtest_main
|
|
59
|
+
GTest::gmock
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
include(GoogleTest)
|
|
63
|
+
gtest_discover_tests(tests)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#include <audioapi/core/OfflineAudioContext.h>
|
|
2
|
+
#include <audioapi/core/effects/GainNode.h>
|
|
3
|
+
#include <audioapi/utils/AudioArray.h>
|
|
4
|
+
#include <audioapi/utils/AudioBus.h>
|
|
5
|
+
#include <gtest/gtest.h>
|
|
6
|
+
#include "MockAudioEventHandlerRegistry.h"
|
|
7
|
+
|
|
8
|
+
class GainTest : public ::testing::Test {
|
|
9
|
+
protected:
|
|
10
|
+
std::shared_ptr<audioapi::IAudioEventHandlerRegistry> eventRegistry;
|
|
11
|
+
std::unique_ptr<audioapi::OfflineAudioContext> context;
|
|
12
|
+
static constexpr int sampleRate = 44100;
|
|
13
|
+
|
|
14
|
+
void SetUp() override {
|
|
15
|
+
eventRegistry = std::make_shared<MockAudioEventHandlerRegistry>();
|
|
16
|
+
context = std::make_unique<audioapi::OfflineAudioContext>(
|
|
17
|
+
2, 5 * sampleRate, sampleRate, eventRegistry);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class TestableGainNode : public audioapi::GainNode {
|
|
22
|
+
public:
|
|
23
|
+
explicit TestableGainNode(audioapi::BaseAudioContext *context)
|
|
24
|
+
: audioapi::GainNode(context) {}
|
|
25
|
+
|
|
26
|
+
void setGainParam(float value) {
|
|
27
|
+
getGainParam()->setValue(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void processNode(
|
|
31
|
+
const std::shared_ptr<audioapi::AudioBus> &processingBus,
|
|
32
|
+
int framesToProcess) override {
|
|
33
|
+
audioapi::GainNode::processNode(processingBus, framesToProcess);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
TEST_F(GainTest, GainCanBeCreated) {
|
|
38
|
+
auto gain = context->createGain();
|
|
39
|
+
ASSERT_NE(gain, nullptr);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
TEST_F(GainTest, GainModulatesVolumeCorrectly) {
|
|
43
|
+
static constexpr float GAIN_VALUE = 0.5f;
|
|
44
|
+
static constexpr int FRAMES_TO_PROCESS = 4;
|
|
45
|
+
auto gainNode = std::make_shared<TestableGainNode>(context.get());
|
|
46
|
+
gainNode->setGainParam(GAIN_VALUE);
|
|
47
|
+
|
|
48
|
+
auto bus =
|
|
49
|
+
std::make_shared<audioapi::AudioBus>(FRAMES_TO_PROCESS, 1, sampleRate);
|
|
50
|
+
for (size_t i = 0; i < bus->getSize(); ++i) {
|
|
51
|
+
bus->getChannel(0)->getData()[i] = i + 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
gainNode->processNode(bus, FRAMES_TO_PROCESS);
|
|
55
|
+
for (size_t i = 0; i < bus->getSize(); ++i) {
|
|
56
|
+
EXPECT_FLOAT_EQ((*bus->getChannel(0))[i], (i + 1) * GAIN_VALUE);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
TEST_F(GainTest, GainModulatesVolumeCorrectlyMultiChannel) {
|
|
61
|
+
static constexpr float GAIN_VALUE = 0.5f;
|
|
62
|
+
static constexpr int FRAMES_TO_PROCESS = 4;
|
|
63
|
+
auto gainNode = std::make_shared<TestableGainNode>(context.get());
|
|
64
|
+
gainNode->setGainParam(GAIN_VALUE);
|
|
65
|
+
|
|
66
|
+
auto bus =
|
|
67
|
+
std::make_shared<audioapi::AudioBus>(FRAMES_TO_PROCESS, 2, sampleRate);
|
|
68
|
+
for (size_t i = 0; i < bus->getSize(); ++i) {
|
|
69
|
+
bus->getChannel(0)->getData()[i] = i + 1;
|
|
70
|
+
bus->getChannel(1)->getData()[i] = -i - 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
gainNode->processNode(bus, FRAMES_TO_PROCESS);
|
|
74
|
+
for (size_t i = 0; i < bus->getSize(); ++i) {
|
|
75
|
+
EXPECT_FLOAT_EQ((*bus->getChannel(0))[i], (i + 1) * GAIN_VALUE);
|
|
76
|
+
EXPECT_FLOAT_EQ((*bus->getChannel(1))[i], (-i - 1) * GAIN_VALUE);
|
|
77
|
+
}
|
|
78
|
+
}
|