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.
Files changed (97) hide show
  1. package/README.md +4 -1
  2. package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +4 -2
  3. package/android/src/main/cpp/audioapi/android/core/AudioDecoder.cpp +12 -14
  4. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +38 -8
  5. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +9 -3
  6. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +5 -1
  7. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +2 -2
  8. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +2 -2
  9. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +46 -0
  10. package/android/src/main/res/drawable/skip_backward_15.xml +16 -0
  11. package/android/src/main/res/drawable/skip_forward_15.xml +16 -0
  12. package/android/src/oldarch/NativeAudioAPIModuleSpec.java +4 -0
  13. package/common/cpp/audioapi/HostObjects/AudioBufferBaseSourceNodeHostObject.h +17 -1
  14. package/common/cpp/audioapi/HostObjects/AudioBufferSourceNodeHostObject.h +3 -5
  15. package/common/cpp/audioapi/HostObjects/AudioScheduledSourceNodeHostObject.h +11 -2
  16. package/common/cpp/audioapi/core/AudioContext.cpp +6 -4
  17. package/common/cpp/audioapi/core/AudioContext.h +1 -1
  18. package/common/cpp/audioapi/core/AudioParam.cpp +17 -5
  19. package/common/cpp/audioapi/core/AudioParam.h +3 -0
  20. package/common/cpp/audioapi/core/BaseAudioContext.cpp +1 -1
  21. package/common/cpp/audioapi/core/BaseAudioContext.h +3 -2
  22. package/common/cpp/audioapi/core/OfflineAudioContext.cpp +2 -1
  23. package/common/cpp/audioapi/core/OfflineAudioContext.h +1 -1
  24. package/common/cpp/audioapi/core/effects/PeriodicWave.cpp +1 -1
  25. package/common/cpp/audioapi/core/inputs/AudioRecorder.cpp +4 -2
  26. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +1 -0
  27. package/common/cpp/audioapi/core/sources/AudioBuffer.h +1 -0
  28. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +26 -1
  29. package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +10 -5
  30. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +24 -2
  31. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +3 -1
  32. package/common/cpp/audioapi/core/utils/AudioNodeDestructor.h +1 -0
  33. package/common/cpp/audioapi/core/utils/AudioNodeManager.h +1 -2
  34. package/common/cpp/audioapi/dsp/AudioUtils.cpp +1 -1
  35. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +121 -41
  36. package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +9 -5
  37. package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +25 -0
  38. package/common/cpp/audioapi/jsi/JsiPromise.cpp +1 -0
  39. package/common/cpp/audioapi/utils/AudioArray.h +1 -0
  40. package/common/cpp/audioapi/utils/CircularAudioArray.h +1 -0
  41. package/common/cpp/test/CMakeLists.txt +63 -0
  42. package/common/cpp/test/GainTest.cpp +78 -0
  43. package/common/cpp/test/MockAudioEventHandlerRegistry.h +22 -0
  44. package/common/cpp/test/OscillatorTest.cpp +22 -0
  45. package/common/cpp/test/RunTests.sh +26 -0
  46. package/ios/audioapi/ios/AudioAPIModule.mm +17 -12
  47. package/ios/audioapi/ios/core/IOSAudioPlayer.h +2 -1
  48. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +5 -2
  49. package/ios/audioapi/ios/system/AudioSessionManager.h +4 -0
  50. package/ios/audioapi/ios/system/AudioSessionManager.mm +26 -0
  51. package/ios/audioapi/ios/system/LockScreenManager.mm +2 -0
  52. package/lib/commonjs/core/AudioBufferBaseSourceNode.js +10 -6
  53. package/lib/commonjs/core/AudioBufferBaseSourceNode.js.map +1 -1
  54. package/lib/commonjs/core/AudioBufferSourceNode.js +2 -2
  55. package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -1
  56. package/lib/commonjs/core/AudioScheduledSourceNode.js +8 -4
  57. package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -1
  58. package/lib/commonjs/plugin/withAudioAPI.js +1 -1
  59. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
  60. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  61. package/lib/commonjs/system/AudioManager.js +3 -0
  62. package/lib/commonjs/system/AudioManager.js.map +1 -1
  63. package/lib/module/core/AudioBufferBaseSourceNode.js +10 -6
  64. package/lib/module/core/AudioBufferBaseSourceNode.js.map +1 -1
  65. package/lib/module/core/AudioBufferSourceNode.js +2 -2
  66. package/lib/module/core/AudioBufferSourceNode.js.map +1 -1
  67. package/lib/module/core/AudioScheduledSourceNode.js +8 -4
  68. package/lib/module/core/AudioScheduledSourceNode.js.map +1 -1
  69. package/lib/module/plugin/withAudioAPI.js +1 -1
  70. package/lib/module/plugin/withAudioAPI.js.map +1 -1
  71. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  72. package/lib/module/system/AudioManager.js +3 -0
  73. package/lib/module/system/AudioManager.js.map +1 -1
  74. package/lib/typescript/core/AudioBufferBaseSourceNode.d.ts +6 -3
  75. package/lib/typescript/core/AudioBufferBaseSourceNode.d.ts.map +1 -1
  76. package/lib/typescript/core/AudioBufferSourceNode.d.ts.map +1 -1
  77. package/lib/typescript/core/AudioScheduledSourceNode.d.ts +3 -1
  78. package/lib/typescript/core/AudioScheduledSourceNode.d.ts.map +1 -1
  79. package/lib/typescript/interfaces.d.ts +2 -2
  80. package/lib/typescript/interfaces.d.ts.map +1 -1
  81. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +2 -1
  82. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  83. package/lib/typescript/system/AudioManager.d.ts +2 -1
  84. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  85. package/lib/typescript/system/types.d.ts +11 -0
  86. package/lib/typescript/system/types.d.ts.map +1 -1
  87. package/package.json +1 -1
  88. package/src/core/AudioBufferBaseSourceNode.ts +20 -7
  89. package/src/core/AudioBufferSourceNode.ts +2 -8
  90. package/src/core/AudioScheduledSourceNode.ts +11 -3
  91. package/src/interfaces.ts +2 -5
  92. package/src/plugin/withAudioAPI.ts +1 -1
  93. package/src/specs/NativeAudioAPIModule.ts +4 -1
  94. package/src/system/AudioManager.ts +10 -1
  95. package/src/system/types.ts +14 -0
  96. package/android/src/main/res/drawable/skip_backward_10.xml +0 -9
  97. 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_->invokeHandlerWithEventBody(
40
- "audioReady", onAudioReadyCallbackId_, body);
39
+ if (audioEventHandlerRegistry_ != nullptr) {
40
+ audioEventHandlerRegistry_->invokeHandlerWithEventBody(
41
+ "audioReady", onAudioReadyCallbackId_, body);
42
+ }
41
43
  }
42
44
 
43
45
  void AudioRecorder::sendRemainingData() {
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <memory>
4
+ #include <atomic>
4
5
 
5
6
  namespace audioapi {
6
7
  class AudioBus;
@@ -5,6 +5,7 @@
5
5
  #include <string>
6
6
  #include <vector>
7
7
  #include <cstddef>
8
+ #include <cstring>
8
9
 
9
10
  namespace audioapi {
10
11
 
@@ -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
- [[nodiscard]] std::shared_ptr<AudioParam> getDetuneParam() const;
18
- [[nodiscard]] std::shared_ptr<AudioParam> getPlaybackRateParam() const;
20
+ [[nodiscard]] std::shared_ptr<AudioParam> getDetuneParam() const;
21
+ [[nodiscard]] std::shared_ptr<AudioParam> getPlaybackRateParam() const;
19
22
 
20
- void setOnPositionChangedCallbackId(uint64_t callbackId);
21
- void setOnPositionChangedInterval(int interval);
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_->invokeHandlerWithEventBody(
153
- "ended", onEndedCallbackId_, {});
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,
@@ -4,6 +4,7 @@
4
4
  #include <mutex>
5
5
  #include <thread>
6
6
  #include <atomic>
7
+ #include <functional>
7
8
  #include <vector>
8
9
  #include <memory>
9
10
 
@@ -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();
@@ -28,6 +28,6 @@ float linearToDecibels(float value) {
28
28
  }
29
29
 
30
30
  float decibelsToLinear(float value) {
31
- return powf(10, value / 20);
31
+ return pow(10, value / 20);
32
32
  }
33
33
  } // namespace audioapi::dsp
@@ -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
- static uint64_t LISTENER_ID = 1;
28
+ uint64_t listenerId = listenerIdCounter_++;
28
29
 
29
- eventHandlers_[eventName][LISTENER_ID] = handler;
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 LISTENER_ID++;
40
+ return listenerId;
32
41
  }
33
42
 
34
43
  void AudioEventHandlerRegistry::unregisterHandler(
35
44
  const std::string &eventName,
36
45
  uint64_t listenerId) {
37
- auto it = eventHandlers_.find(eventName);
38
- if (it != eventHandlers_.end()) {
39
- it->second.erase(listenerId);
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
- auto it = eventHandlers_.find(eventName);
47
- if (it != eventHandlers_.end()) {
48
- for (const auto &pair : it->second) {
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
- if (handler) {
51
- callInvoker_->invokeAsync([this, handler, body]() {
52
- auto eventObject = createEventObject(body);
53
- handler->call(*runtime_, eventObject);
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
- auto it = eventHandlers_.find(eventName);
65
- if (it != eventHandlers_.end()) {
66
- auto handlersMap = it->second;
67
- auto handlerIt = handlersMap.find(listenerId);
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>>(value)) {
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
@@ -1,4 +1,5 @@
1
1
  #include <audioapi/jsi/JsiPromise.h>
2
+ #include <stdexcept>
2
3
 
3
4
  namespace audioapi {
4
5
 
@@ -3,6 +3,7 @@
3
3
  #include <algorithm>
4
4
  #include <memory>
5
5
  #include <cstddef>
6
+ #include <cstring>
6
7
 
7
8
  namespace audioapi {
8
9
 
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <audioapi/utils/AudioArray.h>
4
+ #include <stdexcept>
4
5
 
5
6
  namespace audioapi {
6
7
 
@@ -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
+ }