react-native-audio-api 0.12.1 → 0.12.2
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 +8 -3
- package/RNAudioAPI.podspec +0 -2
- package/android/src/main/cpp/audioapi/CMakeLists.txt +8 -2
- package/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +7 -29
- package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.cpp +83 -0
- package/android/src/main/cpp/audioapi/android/JniEventPayloadParser.h +14 -0
- package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +4 -3
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +1 -0
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.cpp +37 -21
- package/android/src/main/cpp/audioapi/android/core/utils/AndroidRecorderCallback.h +1 -1
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +21 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.cpp +26 -4
- package/common/cpp/audioapi/HostObjects/sources/AudioBufferQueueSourceNodeHostObject.h +1 -0
- package/common/cpp/audioapi/HostObjects/sources/AudioFileSourceNodeHostObject.cpp +2 -2
- package/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +3 -3
- package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.cpp +60 -0
- package/common/cpp/audioapi/HostObjects/utils/AudioFileUtilsHostObject.h +24 -0
- package/common/cpp/audioapi/HostObjects/utils/NodeOptionsParser.h +2 -2
- package/common/cpp/audioapi/core/OfflineAudioContext.cpp +0 -1
- package/common/cpp/audioapi/core/OfflineAudioContext.h +0 -1
- package/common/cpp/audioapi/core/effects/ConvolverNode.cpp +4 -10
- package/common/cpp/audioapi/core/effects/ConvolverNode.h +0 -4
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.cpp +13 -11
- package/common/cpp/audioapi/core/sources/AudioBufferBaseSourceNode.h +5 -9
- package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.cpp +47 -139
- package/common/cpp/audioapi/core/sources/AudioBufferQueueSourceNode.h +11 -8
- package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +29 -114
- package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.h +6 -8
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.cpp +9 -11
- package/common/cpp/audioapi/core/sources/AudioFileSourceNode.h +1 -1
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +2 -2
- package/common/cpp/audioapi/core/types/AudioFormat.h +3 -1
- package/common/cpp/audioapi/core/utils/AudioDecoder.cpp +120 -91
- package/common/cpp/audioapi/core/utils/AudioDecoder.h +24 -101
- package/common/cpp/audioapi/core/utils/AudioFileConcatenator.cpp +862 -0
- package/common/cpp/audioapi/core/utils/AudioFileConcatenator.h +164 -0
- package/common/cpp/audioapi/core/utils/AudioFileWriter.cpp +2 -4
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.cpp +7 -12
- package/common/cpp/audioapi/core/utils/AudioRecorderCallback.h +2 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessingDirection.h +6 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.cpp +110 -0
- package/common/cpp/audioapi/core/utils/buffer/BufferProcessorBase.h +75 -0
- package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.cpp +129 -0
- package/common/cpp/audioapi/core/utils/buffer/QueueBufferProcessor.h +55 -0
- package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.cpp +95 -0
- package/common/cpp/audioapi/core/utils/buffer/SingleBufferProcessor.h +52 -0
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.cpp +65 -157
- package/common/cpp/audioapi/events/AudioEventHandlerRegistry.h +52 -33
- package/common/cpp/audioapi/events/AudioEventPayload.h +87 -0
- package/common/cpp/audioapi/events/IAudioEventHandlerRegistry.h +12 -12
- package/common/cpp/audioapi/libs/decoding/IncrementalAudioDecoder.h +12 -10
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +152 -78
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +6 -6
- package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.cpp +34 -20
- package/common/cpp/audioapi/libs/miniaudio/MiniAudioDecoding.h +4 -4
- package/common/cpp/audioapi/utils/AudioArray.hpp +6 -1
- package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +1 -1
- package/common/cpp/audioapi/utils/TaskOffloader.hpp +3 -5
- package/ios/audioapi/ios/AudioAPIModule.h +2 -1
- package/ios/audioapi/ios/AudioAPIModule.mm +4 -25
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +16 -2
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +90 -24
- package/ios/audioapi/ios/core/IOSAudioRecorder.mm +1 -1
- package/ios/audioapi/ios/core/utils/IOSRecorderCallback.mm +64 -20
- package/ios/audioapi/ios/system/AudioSessionManager.mm +18 -7
- package/ios/audioapi/ios/system/SystemNotificationManager.mm +22 -22
- package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +10 -13
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/commonjs/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/commonjs/api.js +8 -0
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/api.web.js +5 -0
- package/lib/commonjs/api.web.js.map +1 -1
- package/lib/commonjs/core/AudioFileUtils.js +35 -0
- package/lib/commonjs/core/AudioFileUtils.js.map +1 -0
- package/lib/commonjs/mock/index.js +15 -1
- package/lib/commonjs/mock/index.js.map +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js +1 -1
- package/lib/module/AudioAPIModule/AudioAPIModule.js.map +1 -1
- package/lib/module/api.js +1 -0
- package/lib/module/api.js.map +1 -1
- package/lib/module/api.web.js +3 -0
- package/lib/module/api.web.js.map +1 -1
- package/lib/module/core/AudioFileUtils.js +31 -0
- package/lib/module/core/AudioFileUtils.js.map +1 -0
- package/lib/module/mock/index.js +14 -1
- package/lib/module/mock/index.js.map +1 -1
- package/lib/typescript/AudioAPIModule/AudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/api.d.ts +1 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/api.web.d.ts +1 -0
- package/lib/typescript/api.web.d.ts.map +1 -1
- package/lib/typescript/core/AudioFileUtils.d.ts +2 -0
- package/lib/typescript/core/AudioFileUtils.d.ts.map +1 -0
- package/lib/typescript/interfaces.d.ts +3 -0
- package/lib/typescript/interfaces.d.ts.map +1 -1
- package/lib/typescript/mock/index.d.ts +3 -1
- package/lib/typescript/mock/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/download-prebuilt-binaries.sh +34 -1
- package/src/AudioAPIModule/AudioAPIModule.ts +1 -0
- package/src/AudioAPIModule/globals.d.ts +3 -0
- package/src/api.ts +1 -0
- package/src/api.web.ts +7 -0
- package/src/core/AudioFileUtils.ts +49 -0
- package/src/interfaces.ts +7 -0
- package/src/mock/index.ts +29 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#include <audioapi/core/utils/buffer/SingleBufferProcessor.h>
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <memory>
|
|
6
|
+
|
|
7
|
+
namespace audioapi {
|
|
8
|
+
|
|
9
|
+
CursorState SingleBufferProcessor::advance(double rate) {
|
|
10
|
+
const double currentPosition = position_;
|
|
11
|
+
const auto index = static_cast<size_t>(currentPosition);
|
|
12
|
+
const auto factor = static_cast<float>(currentPosition - static_cast<double>(index));
|
|
13
|
+
|
|
14
|
+
size_t nextIndex;
|
|
15
|
+
if (direction_ == BufferProcessingDirection::FORWARD) {
|
|
16
|
+
nextIndex = index + 1;
|
|
17
|
+
if (nextIndex >= endFrame_) {
|
|
18
|
+
nextIndex = loop_ ? startFrame_ : index;
|
|
19
|
+
}
|
|
20
|
+
} else { // REVERSE — interpolate toward the previous sample.
|
|
21
|
+
if (index > startFrame_) {
|
|
22
|
+
nextIndex = index - 1;
|
|
23
|
+
} else {
|
|
24
|
+
nextIndex = loop_ ? endFrame_ - 1 : index;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
position_ += rate;
|
|
29
|
+
|
|
30
|
+
const bool atEnd = shouldStop() &&
|
|
31
|
+
(currentPosition >= static_cast<double>(endFrame_) ||
|
|
32
|
+
currentPosition < static_cast<double>(startFrame_));
|
|
33
|
+
|
|
34
|
+
return {.index = index, .nextIndex = nextIndex, .factor = factor, .atEndOfBuffer = atEnd};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
size_t SingleBufferProcessor::remainingInContiguousBlock() const {
|
|
38
|
+
if (atBoundary()) {
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (direction_ == BufferProcessingDirection::REVERSE) {
|
|
42
|
+
// +1 because we read down to and including startFrame_
|
|
43
|
+
return currentIndex() - startFrame_ + 1;
|
|
44
|
+
}
|
|
45
|
+
// Pure size_t arithmetic — no double promotion, no fractional truncation.
|
|
46
|
+
return endFrame_ - currentIndex();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void SingleBufferProcessor::consume(size_t frames) {
|
|
50
|
+
if (direction_ == BufferProcessingDirection::REVERSE) {
|
|
51
|
+
position_ -= static_cast<double>(frames);
|
|
52
|
+
} else {
|
|
53
|
+
position_ += static_cast<double>(frames);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
size_t SingleBufferProcessor::currentIndex() const {
|
|
58
|
+
return static_cast<size_t>(std::floor(position_));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
std::shared_ptr<const AudioBuffer> SingleBufferProcessor::getBuffer() const {
|
|
62
|
+
return buffer_;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
std::shared_ptr<const AudioBuffer> SingleBufferProcessor::getNextBuffer() const {
|
|
66
|
+
return buffer_;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
bool SingleBufferProcessor::atBoundary() const {
|
|
70
|
+
return position_ < static_cast<double>(startFrame_) ||
|
|
71
|
+
position_ >= static_cast<double>(endFrame_);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bool SingleBufferProcessor::shouldStop() const {
|
|
75
|
+
return !loop_;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void SingleBufferProcessor::handleBoundary() {
|
|
79
|
+
if (shouldStop()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (endFrame_ <= startFrame_) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const auto range = static_cast<double>(endFrame_ - startFrame_);
|
|
87
|
+
|
|
88
|
+
if (position_ >= static_cast<double>(endFrame_)) {
|
|
89
|
+
position_ -= range;
|
|
90
|
+
} else if (position_ < static_cast<double>(startFrame_)) {
|
|
91
|
+
position_ += range;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <audioapi/core/utils/buffer/BufferProcessingDirection.h>
|
|
4
|
+
#include <audioapi/core/utils/buffer/BufferProcessorBase.h>
|
|
5
|
+
|
|
6
|
+
#include <audioapi/utils/AudioBuffer.hpp>
|
|
7
|
+
#include <cstddef>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
namespace audioapi {
|
|
12
|
+
|
|
13
|
+
class SingleBufferProcessor : public BufferProcessorBase {
|
|
14
|
+
public:
|
|
15
|
+
SingleBufferProcessor() = default;
|
|
16
|
+
|
|
17
|
+
[[nodiscard]] bool atBoundary() const override;
|
|
18
|
+
[[nodiscard]] bool shouldStop() const override;
|
|
19
|
+
|
|
20
|
+
void setStartFrame(size_t startFrame) {
|
|
21
|
+
startFrame_ = startFrame;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void setEndFrame(size_t endFrame) {
|
|
25
|
+
endFrame_ = endFrame;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void setLoop(bool loop) {
|
|
29
|
+
loop_ = loop;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void setBuffer(std::shared_ptr<const AudioBuffer> buffer) {
|
|
33
|
+
buffer_ = std::move(buffer);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected:
|
|
37
|
+
CursorState advance(double rate) override;
|
|
38
|
+
void consume(size_t frames) override;
|
|
39
|
+
[[nodiscard]] size_t remainingInContiguousBlock() const override;
|
|
40
|
+
[[nodiscard]] size_t currentIndex() const override;
|
|
41
|
+
[[nodiscard]] std::shared_ptr<const AudioBuffer> getBuffer() const override;
|
|
42
|
+
[[nodiscard]] std::shared_ptr<const AudioBuffer> getNextBuffer() const override;
|
|
43
|
+
void handleBoundary() override;
|
|
44
|
+
|
|
45
|
+
private:
|
|
46
|
+
std::shared_ptr<const AudioBuffer> buffer_ = nullptr;
|
|
47
|
+
bool loop_ = false;
|
|
48
|
+
size_t startFrame_ = 0;
|
|
49
|
+
size_t endFrame_ = 0;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} // namespace audioapi
|
|
@@ -1,18 +1,46 @@
|
|
|
1
|
-
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
|
|
2
1
|
#include <audioapi/events/AudioEventHandlerRegistry.h>
|
|
3
2
|
#include <cstdio>
|
|
4
3
|
#include <memory>
|
|
5
|
-
#include <string>
|
|
6
4
|
#include <unordered_map>
|
|
5
|
+
#include <utility>
|
|
7
6
|
|
|
8
7
|
namespace audioapi {
|
|
9
8
|
|
|
10
9
|
AudioEventHandlerRegistry::AudioEventHandlerRegistry(
|
|
11
10
|
jsi::Runtime *runtime,
|
|
12
11
|
const std::shared_ptr<react::CallInvoker> &callInvoker)
|
|
13
|
-
:
|
|
12
|
+
: callInvoker_(callInvoker), runtime_(runtime), isExiting_(false) {
|
|
13
|
+
auto [sender, receiver] = channels::spsc::channel<
|
|
14
|
+
DispatchEvent,
|
|
15
|
+
EVENT_DISPATCHER_SPSC_OVERFLOW_STRATEGY,
|
|
16
|
+
EVENT_DISPATCHER_SPSC_WAIT_STRATEGY>(kDispatchCapacity);
|
|
17
|
+
sender_ = std::move(sender);
|
|
18
|
+
receiver_ = std::move(receiver);
|
|
19
|
+
|
|
20
|
+
workerThread_ = std::thread([this]() {
|
|
21
|
+
while (!isExiting_.load(std::memory_order_acquire)) {
|
|
22
|
+
auto item = receiver_.receive();
|
|
23
|
+
if (isExiting_.load(std::memory_order_acquire)) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
auto weak = weak_from_this();
|
|
28
|
+
callInvoker_->invokeAsync([weak, capturedItem = std::move(item)]() {
|
|
29
|
+
if (auto self = weak.lock()) {
|
|
30
|
+
self->handleEventOnJSThread(
|
|
31
|
+
capturedItem.event, capturedItem.listenerId, capturedItem.payload);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
14
37
|
|
|
15
38
|
AudioEventHandlerRegistry::~AudioEventHandlerRegistry() {
|
|
39
|
+
isExiting_.store(true, std::memory_order_release);
|
|
40
|
+
sender_.send(DispatchEvent{});
|
|
41
|
+
if (workerThread_.joinable()) {
|
|
42
|
+
workerThread_.join();
|
|
43
|
+
}
|
|
16
44
|
eventHandlers_.clear();
|
|
17
45
|
}
|
|
18
46
|
|
|
@@ -22,13 +50,10 @@ uint64_t AudioEventHandlerRegistry::registerHandler(
|
|
|
22
50
|
auto listenerId = listenerIdCounter_.fetch_add(1, std::memory_order_relaxed);
|
|
23
51
|
|
|
24
52
|
if (runtime_ == nullptr) {
|
|
25
|
-
// If runtime is not valid, we cannot register the handler
|
|
26
53
|
return 0;
|
|
27
54
|
}
|
|
28
55
|
|
|
29
56
|
auto weakSelf = weak_from_this();
|
|
30
|
-
|
|
31
|
-
// Read/Write on eventHandlers_ map only on the JS thread
|
|
32
57
|
callInvoker_->invokeAsync([weakSelf, eventName, listenerId, handler]() {
|
|
33
58
|
if (auto self = weakSelf.lock()) {
|
|
34
59
|
self->eventHandlers_[eventName][listenerId] = handler;
|
|
@@ -40,24 +65,19 @@ uint64_t AudioEventHandlerRegistry::registerHandler(
|
|
|
40
65
|
|
|
41
66
|
void AudioEventHandlerRegistry::unregisterHandler(AudioEvent eventName, uint64_t listenerId) {
|
|
42
67
|
if (runtime_ == nullptr) {
|
|
43
|
-
// If runtime is not valid, we cannot unregister the handler
|
|
44
68
|
return;
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
auto weakSelf = weak_from_this();
|
|
48
|
-
|
|
49
|
-
// Read/Write on eventHandlers_ map only on the JS thread
|
|
50
72
|
callInvoker_->invokeAsync([weakSelf, eventName, listenerId]() {
|
|
51
73
|
if (auto self = weakSelf.lock()) {
|
|
52
74
|
auto it = self->eventHandlers_.find(eventName);
|
|
53
|
-
|
|
54
75
|
if (it == self->eventHandlers_.end()) {
|
|
55
76
|
return;
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
auto &handlersMap = it->second;
|
|
59
80
|
auto handlerIt = handlersMap.find(listenerId);
|
|
60
|
-
|
|
61
81
|
if (handlerIt != handlersMap.end()) {
|
|
62
82
|
handlersMap.erase(handlerIt);
|
|
63
83
|
}
|
|
@@ -65,167 +85,55 @@ void AudioEventHandlerRegistry::unregisterHandler(AudioEvent eventName, uint64_t
|
|
|
65
85
|
});
|
|
66
86
|
}
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
70
|
-
void AudioEventHandlerRegistry::invokeHandlerWithEventBody(
|
|
88
|
+
bool AudioEventHandlerRegistry::dispatchEvent(
|
|
71
89
|
AudioEvent eventName,
|
|
72
|
-
|
|
90
|
+
uint64_t listenerId,
|
|
91
|
+
AudioEventPayload &&payload) noexcept {
|
|
73
92
|
if (runtime_ == nullptr) {
|
|
74
|
-
|
|
75
|
-
return;
|
|
93
|
+
return false;
|
|
76
94
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
callInvoker_->invokeAsync([weakSelf, eventName, body]() {
|
|
82
|
-
if (auto self = weakSelf.lock()) {
|
|
83
|
-
auto it = self->eventHandlers_.find(eventName);
|
|
84
|
-
|
|
85
|
-
if (it == self->eventHandlers_.end()) {
|
|
86
|
-
// If the event name is not registered, we can skip invoking handlers
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
auto handlersMap = it->second;
|
|
91
|
-
|
|
92
|
-
for (const auto &pair : handlersMap) {
|
|
93
|
-
auto handler = pair.second;
|
|
94
|
-
|
|
95
|
-
if (!handler || !handler->isFunction(*self->runtime_)) {
|
|
96
|
-
// If the handler is not valid, we can skip it
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
jsi::Object eventObject(*self->runtime_);
|
|
102
|
-
// handle special logic for microphone, because we pass audio buffer
|
|
103
|
-
// which has significant size
|
|
104
|
-
if (eventName == AudioEvent::AUDIO_READY) {
|
|
105
|
-
auto bufferIt = body.find("buffer");
|
|
106
|
-
if (bufferIt != body.end()) {
|
|
107
|
-
auto bufferHostObject = std::static_pointer_cast<AudioBufferHostObject>(
|
|
108
|
-
std::get<std::shared_ptr<jsi::HostObject>>(bufferIt->second));
|
|
109
|
-
eventObject = self->createEventObject(body, bufferHostObject->getSizeInBytes());
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
eventObject = self->createEventObject(body);
|
|
113
|
-
}
|
|
114
|
-
handler->call(*self->runtime_, eventObject);
|
|
115
|
-
} catch (const std::exception &e) {
|
|
116
|
-
// re-throw the exception to be handled by the caller
|
|
117
|
-
// std::exception is safe to parse by the rn bridge
|
|
118
|
-
throw;
|
|
119
|
-
} catch (...) {
|
|
120
|
-
printf("Unknown exception occurred while invoking handler for event: %d\n", eventName);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
});
|
|
95
|
+
return sender_.try_send(
|
|
96
|
+
DispatchEvent{
|
|
97
|
+
.event = eventName, .listenerId = listenerId, .payload = std::move(payload)}) ==
|
|
98
|
+
channels::spsc::ResponseStatus::SUCCESS;
|
|
125
99
|
}
|
|
126
100
|
|
|
127
|
-
|
|
128
|
-
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
129
|
-
void AudioEventHandlerRegistry::invokeHandlerWithEventBody(
|
|
101
|
+
void AudioEventHandlerRegistry::handleEventOnJSThread(
|
|
130
102
|
AudioEvent eventName,
|
|
131
103
|
uint64_t listenerId,
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
104
|
+
const AudioEventPayload &payload) {
|
|
105
|
+
auto it = eventHandlers_.find(eventName);
|
|
106
|
+
if (it == eventHandlers_.end()) {
|
|
135
107
|
return;
|
|
136
108
|
}
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (auto self = weakSelf.lock()) {
|
|
143
|
-
auto it = self->eventHandlers_.find(eventName);
|
|
144
|
-
|
|
145
|
-
if (it == self->eventHandlers_.end()) {
|
|
146
|
-
// If the event name is not registered, we can skip invoking handlers
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
auto handlerIt = it->second.find(listenerId);
|
|
151
|
-
|
|
152
|
-
if (handlerIt == it->second.end()) {
|
|
153
|
-
// If the listener ID is not registered, we can skip invoking handlers
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Depending on how the AudioBufferSourceNode is handled on the JS side,
|
|
158
|
-
// it sometimes might enter race condition where the ABSN is deleted on JS
|
|
159
|
-
// side, but it is still processed on the audio thread, leading to a crash
|
|
160
|
-
// when f.e. `positionChanged` event is triggered.
|
|
161
|
-
|
|
162
|
-
// In case of debugging this, please increment the hours spent counter
|
|
163
|
-
|
|
164
|
-
// Hours spent on this: 8
|
|
165
|
-
try {
|
|
166
|
-
if (!handlerIt->second || !handlerIt->second->isFunction(*self->runtime_)) {
|
|
167
|
-
// If the handler is not valid, we can skip it
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
jsi::Object eventObject(*self->runtime_);
|
|
171
|
-
// handle special logic for microphone, because we pass audio buffer which
|
|
172
|
-
// has significant size
|
|
173
|
-
if (eventName == AudioEvent::AUDIO_READY) {
|
|
174
|
-
auto bufferIt = body.find("buffer");
|
|
175
|
-
if (bufferIt != body.end()) {
|
|
176
|
-
auto bufferHostObject = std::static_pointer_cast<AudioBufferHostObject>(
|
|
177
|
-
std::get<std::shared_ptr<jsi::HostObject>>(bufferIt->second));
|
|
178
|
-
eventObject = self->createEventObject(body, bufferHostObject->getSizeInBytes());
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
eventObject = self->createEventObject(body);
|
|
182
|
-
}
|
|
183
|
-
handlerIt->second->call(*self->runtime_, eventObject);
|
|
184
|
-
} catch (const std::exception &e) {
|
|
185
|
-
// re-throw the exception to be handled by the caller
|
|
186
|
-
// std::exception is safe to parse by the rn bridge
|
|
187
|
-
throw;
|
|
188
|
-
} catch (...) {
|
|
189
|
-
printf("Unknown exception occurred while invoking handler for event: %d\n", eventName);
|
|
190
|
-
}
|
|
110
|
+
if (listenerId == kBroadcastListenerId) {
|
|
111
|
+
auto handlersCopy = it->second;
|
|
112
|
+
for (const auto &pair : handlersCopy) {
|
|
113
|
+
invokeHandler(pair.second, payload);
|
|
191
114
|
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const std::unordered_map<std::string, EventValue> &body) {
|
|
197
|
-
auto eventObject = jsi::Object(*runtime_);
|
|
198
|
-
|
|
199
|
-
for (const auto &pair : body) {
|
|
200
|
-
const auto *name = pair.first.data();
|
|
201
|
-
const auto &value = pair.second;
|
|
202
|
-
|
|
203
|
-
if (std::holds_alternative<int>(value)) {
|
|
204
|
-
eventObject.setProperty(*runtime_, name, std::get<int>(value));
|
|
205
|
-
} else if (std::holds_alternative<double>(value)) {
|
|
206
|
-
eventObject.setProperty(*runtime_, name, std::get<double>(value));
|
|
207
|
-
} else if (std::holds_alternative<float>(value)) {
|
|
208
|
-
eventObject.setProperty(*runtime_, name, std::get<float>(value));
|
|
209
|
-
} else if (std::holds_alternative<bool>(value)) {
|
|
210
|
-
eventObject.setProperty(*runtime_, name, std::get<bool>(value));
|
|
211
|
-
} else if (std::holds_alternative<std::string>(value)) {
|
|
212
|
-
eventObject.setProperty(*runtime_, name, std::get<std::string>(value));
|
|
213
|
-
} else if (std::holds_alternative<std::shared_ptr<jsi::HostObject>>(value)) {
|
|
214
|
-
auto hostObject = jsi::Object::createFromHostObject(
|
|
215
|
-
*runtime_, std::get<std::shared_ptr<jsi::HostObject>>(value));
|
|
216
|
-
eventObject.setProperty(*runtime_, name, hostObject);
|
|
115
|
+
} else {
|
|
116
|
+
auto handlerIt = it->second.find(listenerId);
|
|
117
|
+
if (handlerIt != it->second.end()) {
|
|
118
|
+
invokeHandler(handlerIt->second, payload);
|
|
217
119
|
}
|
|
218
120
|
}
|
|
219
|
-
|
|
220
|
-
return eventObject;
|
|
221
121
|
}
|
|
222
122
|
|
|
223
|
-
|
|
224
|
-
const std::
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
123
|
+
void AudioEventHandlerRegistry::invokeHandler(
|
|
124
|
+
const std::shared_ptr<jsi::Function> &handler,
|
|
125
|
+
const AudioEventPayload &payload) {
|
|
126
|
+
if (!handler || !handler->isFunction(*runtime_)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
auto eventObject = buildJsiObject(payload);
|
|
131
|
+
handler->call(*runtime_, eventObject);
|
|
132
|
+
} catch (const std::exception &) {
|
|
133
|
+
throw;
|
|
134
|
+
} catch (...) {
|
|
135
|
+
printf("Unknown exception occurred while invoking audio event handler\n");
|
|
136
|
+
}
|
|
229
137
|
}
|
|
230
138
|
|
|
231
139
|
} // namespace audioapi
|
|
@@ -2,24 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
#include <ReactCommon/CallInvoker.h>
|
|
4
4
|
#include <audioapi/events/AudioEvent.h>
|
|
5
|
+
#include <audioapi/events/AudioEventPayload.h>
|
|
5
6
|
#include <audioapi/events/IAudioEventHandlerRegistry.h>
|
|
7
|
+
#include <audioapi/utils/Macros.h>
|
|
8
|
+
#include <audioapi/utils/SpscChannel.hpp>
|
|
6
9
|
#include <jsi/jsi.h>
|
|
7
10
|
#include <atomic>
|
|
8
11
|
#include <memory>
|
|
9
|
-
#include <
|
|
12
|
+
#include <thread>
|
|
10
13
|
#include <unordered_map>
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
inline constexpr auto EVENT_DISPATCHER_SPSC_OVERFLOW_STRATEGY =
|
|
16
|
+
audioapi::channels::spsc::OverflowStrategy::WAIT_ON_FULL;
|
|
17
|
+
inline constexpr auto EVENT_DISPATCHER_SPSC_WAIT_STRATEGY =
|
|
18
|
+
audioapi::channels::spsc::WaitStrategy::ATOMIC_WAIT;
|
|
12
19
|
|
|
13
20
|
namespace audioapi {
|
|
14
21
|
using namespace facebook;
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
/// State changes are performed only on the JS thread.
|
|
22
|
-
/// State access is thread-safe via the RN CallInvoker.
|
|
23
|
+
/// @brief Registry for audio event handlers with built-in dispatcher.
|
|
24
|
+
///
|
|
25
|
+
/// Handlers are registered/unregistered on the JS thread.
|
|
26
|
+
/// dispatchEvent() is lock-free and allocation-free — can be called from the
|
|
27
|
+
/// Audio Thread.
|
|
23
28
|
class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry,
|
|
24
29
|
public std::enable_shared_from_this<AudioEventHandlerRegistry> {
|
|
25
30
|
public:
|
|
@@ -28,45 +33,59 @@ class AudioEventHandlerRegistry : public IAudioEventHandlerRegistry,
|
|
|
28
33
|
const std::shared_ptr<react::CallInvoker> &callInvoker);
|
|
29
34
|
~AudioEventHandlerRegistry() override;
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/// @param handler The JavaScript function to be called when the event occurs.
|
|
34
|
-
/// @return A unique listener ID for the registered handler.
|
|
35
|
-
/// @note Thread safe
|
|
36
|
+
DELETE_COPY_AND_MOVE(AudioEventHandlerRegistry);
|
|
37
|
+
|
|
36
38
|
uint64_t registerHandler(AudioEvent eventName, const std::shared_ptr<jsi::Function> &handler)
|
|
37
39
|
override;
|
|
38
40
|
|
|
39
|
-
/// @brief Unregisters an event handler for a specific audio event using its listener ID.
|
|
40
|
-
/// @param eventName The name of the audio event.
|
|
41
|
-
/// @param listenerId The unique listener ID of the handler to be unregistered.
|
|
42
|
-
/// @note Thread safe
|
|
43
41
|
void unregisterHandler(AudioEvent eventName, uint64_t listenerId) override;
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
/// @note Thread safe
|
|
47
|
-
void invokeHandlerWithEventBody(
|
|
48
|
-
AudioEvent eventName,
|
|
49
|
-
const std::unordered_map<std::string, EventValue> &body) override;
|
|
50
|
-
|
|
51
|
-
/// @brief Invokes the event handler(s) for a specific event with the provided event body.
|
|
52
|
-
/// @note Thread safe
|
|
53
|
-
void invokeHandlerWithEventBody(
|
|
43
|
+
bool dispatchEvent(
|
|
54
44
|
AudioEvent eventName,
|
|
55
45
|
uint64_t listenerId,
|
|
56
|
-
|
|
46
|
+
AudioEventPayload &&payload) noexcept override;
|
|
57
47
|
|
|
58
48
|
private:
|
|
59
|
-
|
|
49
|
+
static constexpr size_t kDispatchCapacity = 256;
|
|
50
|
+
|
|
51
|
+
struct DispatchEvent {
|
|
52
|
+
AudioEvent event{};
|
|
53
|
+
uint64_t listenerId{kBroadcastListenerId};
|
|
54
|
+
AudioEventPayload payload{EmptyPayload{}};
|
|
55
|
+
};
|
|
60
56
|
|
|
57
|
+
using Sender = channels::spsc::Sender<
|
|
58
|
+
DispatchEvent,
|
|
59
|
+
EVENT_DISPATCHER_SPSC_OVERFLOW_STRATEGY,
|
|
60
|
+
EVENT_DISPATCHER_SPSC_WAIT_STRATEGY>;
|
|
61
|
+
using Receiver = channels::spsc::Receiver<
|
|
62
|
+
DispatchEvent,
|
|
63
|
+
EVENT_DISPATCHER_SPSC_OVERFLOW_STRATEGY,
|
|
64
|
+
EVENT_DISPATCHER_SPSC_WAIT_STRATEGY>;
|
|
65
|
+
|
|
66
|
+
std::atomic<uint64_t> listenerIdCounter_{1};
|
|
61
67
|
const std::shared_ptr<react::CallInvoker> callInvoker_;
|
|
62
68
|
jsi::Runtime *runtime_;
|
|
63
69
|
std::unordered_map<AudioEvent, std::unordered_map<uint64_t, std::shared_ptr<jsi::Function>>>
|
|
64
70
|
eventHandlers_;
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
Sender sender_;
|
|
73
|
+
Receiver receiver_;
|
|
74
|
+
std::atomic<bool> isExiting_;
|
|
75
|
+
std::thread workerThread_;
|
|
76
|
+
|
|
77
|
+
void handleEventOnJSThread(
|
|
78
|
+
AudioEvent eventName,
|
|
79
|
+
uint64_t listenerId,
|
|
80
|
+
const AudioEventPayload &payload);
|
|
81
|
+
void invokeHandler(
|
|
82
|
+
const std::shared_ptr<jsi::Function> &handler,
|
|
83
|
+
const AudioEventPayload &payload);
|
|
84
|
+
|
|
85
|
+
jsi::Object buildJsiObject(const AudioEventPayload &payload) {
|
|
86
|
+
return std::visit(
|
|
87
|
+
[this](auto &&p) -> jsi::Object { return p.toJsiObject(*runtime_); }, payload);
|
|
88
|
+
}
|
|
70
89
|
};
|
|
71
90
|
|
|
72
91
|
} // namespace audioapi
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <audioapi/HostObjects/sources/AudioBufferHostObject.h>
|
|
4
|
+
#include <jsi/jsi.h>
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <variant>
|
|
8
|
+
|
|
9
|
+
namespace audioapi {
|
|
10
|
+
|
|
11
|
+
// broadcast sentinel — listenerId 0 means dispatch to all registered handlers.
|
|
12
|
+
static constexpr uint64_t kBroadcastListenerId = 0;
|
|
13
|
+
|
|
14
|
+
struct EmptyPayload {
|
|
15
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
16
|
+
return facebook::jsi::Object(rt);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
struct DoubleValuePayload {
|
|
21
|
+
double value;
|
|
22
|
+
|
|
23
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
24
|
+
facebook::jsi::Object obj(rt);
|
|
25
|
+
obj.setProperty(rt, "value", value);
|
|
26
|
+
return obj;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
struct InterruptionPayload {
|
|
31
|
+
std::string type;
|
|
32
|
+
bool shouldResume;
|
|
33
|
+
|
|
34
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
35
|
+
facebook::jsi::Object obj(rt);
|
|
36
|
+
obj.setProperty(rt, "type", facebook::jsi::String::createFromUtf8(rt, type));
|
|
37
|
+
obj.setProperty(rt, "shouldResume", shouldResume);
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
struct StringPayload {
|
|
43
|
+
std::string name;
|
|
44
|
+
std::string reason;
|
|
45
|
+
|
|
46
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
47
|
+
facebook::jsi::Object obj(rt);
|
|
48
|
+
obj.setProperty(rt, name.c_str(), facebook::jsi::String::createFromUtf8(rt, reason));
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
struct BufferEndedPayload {
|
|
54
|
+
size_t bufferId;
|
|
55
|
+
bool isLastBufferInQueue;
|
|
56
|
+
|
|
57
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
58
|
+
facebook::jsi::Object obj(rt);
|
|
59
|
+
obj.setProperty(
|
|
60
|
+
rt, "bufferId", facebook::jsi::String::createFromUtf8(rt, std::to_string(bufferId)));
|
|
61
|
+
obj.setProperty(rt, "isLastBufferInQueue", isLastBufferInQueue);
|
|
62
|
+
return obj;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
struct AudioReadyPayload {
|
|
67
|
+
std::shared_ptr<AudioBufferHostObject> buffer;
|
|
68
|
+
int numFrames;
|
|
69
|
+
|
|
70
|
+
facebook::jsi::Object toJsiObject(facebook::jsi::Runtime &rt) const {
|
|
71
|
+
facebook::jsi::Object obj(rt);
|
|
72
|
+
obj.setProperty(rt, "buffer", facebook::jsi::Object::createFromHostObject(rt, buffer));
|
|
73
|
+
obj.setProperty(rt, "numFrames", numFrames);
|
|
74
|
+
obj.setExternalMemoryPressure(rt, buffer->getSizeInBytes());
|
|
75
|
+
return obj;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
using AudioEventPayload = std::variant<
|
|
80
|
+
EmptyPayload,
|
|
81
|
+
DoubleValuePayload,
|
|
82
|
+
InterruptionPayload,
|
|
83
|
+
StringPayload,
|
|
84
|
+
BufferEndedPayload,
|
|
85
|
+
AudioReadyPayload>;
|
|
86
|
+
|
|
87
|
+
} // namespace audioapi
|