react-native-audio-api 0.10.0-nightly-cced1c6-20251015 → 0.10.0-nightly-034f768-20251016
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/common/cpp/audioapi/HostObjects/utils/AudioDecoderHostObject.cpp +72 -74
- package/common/cpp/audioapi/jsi/JsiPromise.cpp +31 -23
- package/common/cpp/audioapi/jsi/JsiPromise.h +16 -4
- package/common/cpp/audioapi/utils/MoveOnlyFunction.hpp +91 -0
- package/common/cpp/audioapi/utils/ThreadPool.hpp +14 -5
- package/package.json +1 -1
|
@@ -30,29 +30,30 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithMemoryBlock) {
|
|
|
30
30
|
|
|
31
31
|
auto sampleRate = args[1].getNumber();
|
|
32
32
|
|
|
33
|
-
auto promise = promiseVendor_->
|
|
34
|
-
[data, size, sampleRate](
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
33
|
+
auto promise = promiseVendor_->createAsyncPromise(
|
|
34
|
+
[data, size, sampleRate]() -> PromiseResolver {
|
|
35
|
+
auto result =
|
|
36
|
+
AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);
|
|
37
|
+
|
|
38
|
+
if (!result) {
|
|
39
|
+
return [](jsi::Runtime &runtime)
|
|
40
|
+
-> std::variant<jsi::Value, std::string> {
|
|
41
|
+
return std::string("Failed to decode audio data.");
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
auto audioBufferHostObject =
|
|
46
|
+
std::make_shared<AudioBufferHostObject>(result);
|
|
47
|
+
|
|
48
|
+
return [audioBufferHostObject =
|
|
49
|
+
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
|
|
50
|
+
-> std::variant<jsi::Value, std::string> {
|
|
51
|
+
auto jsiObject =
|
|
52
|
+
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
|
|
53
|
+
jsiObject.setExternalMemoryPressure(
|
|
54
|
+
runtime, audioBufferHostObject->getSizeInBytes());
|
|
55
|
+
return jsiObject;
|
|
56
|
+
};
|
|
56
57
|
});
|
|
57
58
|
return promise;
|
|
58
59
|
}
|
|
@@ -61,29 +62,29 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithFilePath) {
|
|
|
61
62
|
auto sourcePath = args[0].getString(runtime).utf8(runtime);
|
|
62
63
|
auto sampleRate = args[1].getNumber();
|
|
63
64
|
|
|
64
|
-
auto promise = promiseVendor_->
|
|
65
|
-
[sourcePath, sampleRate](
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
65
|
+
auto promise = promiseVendor_->createAsyncPromise(
|
|
66
|
+
[sourcePath, sampleRate]() -> PromiseResolver {
|
|
67
|
+
auto result = AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);
|
|
68
|
+
|
|
69
|
+
if (!result) {
|
|
70
|
+
return [](jsi::Runtime &runtime)
|
|
71
|
+
-> std::variant<jsi::Value, std::string> {
|
|
72
|
+
return std::string("Failed to decode audio data source.");
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
auto audioBufferHostObject =
|
|
77
|
+
std::make_shared<AudioBufferHostObject>(result);
|
|
78
|
+
|
|
79
|
+
return [audioBufferHostObject =
|
|
80
|
+
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
|
|
81
|
+
-> std::variant<jsi::Value, std::string> {
|
|
82
|
+
auto jsiObject =
|
|
83
|
+
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
|
|
84
|
+
jsiObject.setExternalMemoryPressure(
|
|
85
|
+
runtime, audioBufferHostObject->getSizeInBytes());
|
|
86
|
+
return jsiObject;
|
|
87
|
+
};
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
return promise;
|
|
@@ -95,34 +96,31 @@ JSI_HOST_FUNCTION_IMPL(AudioDecoderHostObject, decodeWithPCMInBase64) {
|
|
|
95
96
|
auto inputChannelCount = args[2].getNumber();
|
|
96
97
|
auto interleaved = args[3].getBool();
|
|
97
98
|
|
|
98
|
-
auto promise = promiseVendor_->
|
|
99
|
-
[b64, inputSampleRate, inputChannelCount, interleaved](
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return jsiObject;
|
|
124
|
-
});
|
|
125
|
-
}).detach();
|
|
99
|
+
auto promise = promiseVendor_->createAsyncPromise(
|
|
100
|
+
[b64, inputSampleRate, inputChannelCount, interleaved]()
|
|
101
|
+
-> PromiseResolver {
|
|
102
|
+
auto result = AudioDecoder::decodeWithPCMInBase64(
|
|
103
|
+
b64, inputSampleRate, inputChannelCount, interleaved);
|
|
104
|
+
|
|
105
|
+
if (!result) {
|
|
106
|
+
return [](jsi::Runtime &runtime)
|
|
107
|
+
-> std::variant<jsi::Value, std::string> {
|
|
108
|
+
return std::string("Failed to decode audio data source.");
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
auto audioBufferHostObject =
|
|
113
|
+
std::make_shared<AudioBufferHostObject>(result);
|
|
114
|
+
|
|
115
|
+
return [audioBufferHostObject =
|
|
116
|
+
std::move(audioBufferHostObject)](jsi::Runtime &runtime)
|
|
117
|
+
-> std::variant<jsi::Value, std::string> {
|
|
118
|
+
auto jsiObject =
|
|
119
|
+
jsi::Object::createFromHostObject(runtime, audioBufferHostObject);
|
|
120
|
+
jsiObject.setExternalMemoryPressure(
|
|
121
|
+
runtime, audioBufferHostObject->getSizeInBytes());
|
|
122
|
+
return jsiObject;
|
|
123
|
+
};
|
|
126
124
|
});
|
|
127
125
|
|
|
128
126
|
return promise;
|
|
@@ -62,8 +62,7 @@ jsi::Value PromiseVendor::createPromise(
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
jsi::Value PromiseVendor::createAsyncPromise(
|
|
65
|
-
std::function<
|
|
66
|
-
&&function) {
|
|
65
|
+
std::function<PromiseResolver()> &&function) {
|
|
67
66
|
auto &runtime = *runtime_;
|
|
68
67
|
auto callInvoker = callInvoker_;
|
|
69
68
|
auto threadPool = threadPool_;
|
|
@@ -74,32 +73,18 @@ jsi::Value PromiseVendor::createAsyncPromise(
|
|
|
74
73
|
jsi::Runtime &runtime,
|
|
75
74
|
const jsi::Value &thisValue,
|
|
76
75
|
const jsi::Value *arguments,
|
|
77
|
-
size_t count) -> jsi::Value {
|
|
76
|
+
size_t count) mutable -> jsi::Value {
|
|
78
77
|
auto resolveLocal = arguments[0].asObject(runtime).asFunction(runtime);
|
|
79
78
|
auto resolve = std::make_shared<jsi::Function>(std::move(resolveLocal));
|
|
80
79
|
auto rejectLocal = arguments[1].asObject(runtime).asFunction(runtime);
|
|
81
80
|
auto reject = std::make_shared<jsi::Function>(std::move(rejectLocal));
|
|
82
81
|
|
|
83
|
-
threadPool->schedule(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (std::holds_alternative<jsi::Value>(result)) {
|
|
90
|
-
auto valueShared = std::make_shared<jsi::Value>(
|
|
91
|
-
std::move(std::get<jsi::Value>(result)));
|
|
92
|
-
callInvoker->invokeAsync([resolve, &runtime, valueShared]() -> void {
|
|
93
|
-
resolve->call(runtime, *valueShared);
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
auto errorMessage = std::get<std::string>(result);
|
|
97
|
-
callInvoker->invokeAsync([reject, &runtime, errorMessage]() -> void {
|
|
98
|
-
auto error = jsi::JSError(runtime, errorMessage);
|
|
99
|
-
reject->call(runtime, error.value());
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
});
|
|
82
|
+
threadPool->schedule(
|
|
83
|
+
&PromiseVendor::asyncPromiseJob,
|
|
84
|
+
std::move(callInvoker),
|
|
85
|
+
std::move(function),
|
|
86
|
+
std::move(resolve),
|
|
87
|
+
std::move(reject));
|
|
103
88
|
return jsi::Value::undefined();
|
|
104
89
|
};
|
|
105
90
|
auto promiseFunction = jsi::Function::createFromHostFunction(
|
|
@@ -110,4 +95,27 @@ jsi::Value PromiseVendor::createAsyncPromise(
|
|
|
110
95
|
return promiseCtor.callAsConstructor(runtime, std::move(promiseFunction));
|
|
111
96
|
}
|
|
112
97
|
|
|
98
|
+
void PromiseVendor::asyncPromiseJob(
|
|
99
|
+
std::shared_ptr<react::CallInvoker> callInvoker,
|
|
100
|
+
std::function<PromiseResolver()> &&function,
|
|
101
|
+
std::shared_ptr<jsi::Function> &&resolve,
|
|
102
|
+
std::shared_ptr<jsi::Function> &&reject) {
|
|
103
|
+
auto resolver = function();
|
|
104
|
+
callInvoker->invokeAsync(
|
|
105
|
+
[resolver = std::move(resolver),
|
|
106
|
+
reject = std::move(reject),
|
|
107
|
+
resolve = std::move(resolve)](jsi::Runtime &runtime) -> void {
|
|
108
|
+
auto result = resolver(runtime);
|
|
109
|
+
if (std::holds_alternative<jsi::Value>(result)) {
|
|
110
|
+
auto valueShared = std::make_shared<jsi::Value>(
|
|
111
|
+
std::move(std::get<jsi::Value>(result)));
|
|
112
|
+
resolve->call(runtime, *valueShared);
|
|
113
|
+
} else {
|
|
114
|
+
auto errorMessage = std::get<std::string>(result);
|
|
115
|
+
auto error = jsi::JSError(runtime, errorMessage);
|
|
116
|
+
reject->call(runtime, error.value());
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
113
121
|
} // namespace audioapi
|
|
@@ -33,6 +33,8 @@ class Promise {
|
|
|
33
33
|
std::function<void(const std::string &)> reject_;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
using PromiseResolver = std::function<std::variant<jsi::Value, std::string>(jsi::Runtime&)>;
|
|
37
|
+
|
|
36
38
|
class PromiseVendor {
|
|
37
39
|
public:
|
|
38
40
|
PromiseVendor(jsi::Runtime *runtime, const std::shared_ptr<react::CallInvoker> &callInvoker):
|
|
@@ -51,20 +53,30 @@ class PromiseVendor {
|
|
|
51
53
|
/// @example
|
|
52
54
|
/// ```cpp
|
|
53
55
|
/// auto promise = promiseVendor_->createAsyncPromise(
|
|
54
|
-
/// [](
|
|
55
|
-
/// // Simulate some heavy work
|
|
56
|
+
/// []() -> PromiseResolver {
|
|
57
|
+
/// // Simulate some heavy work on a background thread
|
|
56
58
|
/// std::this_thread::sleep_for(std::chrono::seconds(2));
|
|
57
|
-
/// return jsi::
|
|
59
|
+
/// return [](jsi::Runtime &rt) -> std::variant<jsi::Value, std::string> {
|
|
60
|
+
/// // Prepare and return the result on javascript thread
|
|
61
|
+
/// return jsi::String::createFromUtf8(rt, "Promise resolved successfully!");
|
|
62
|
+
/// };
|
|
58
63
|
/// }
|
|
59
64
|
/// );
|
|
60
65
|
///
|
|
61
66
|
/// return promise;
|
|
62
|
-
jsi::Value createAsyncPromise(std::function<
|
|
67
|
+
jsi::Value createAsyncPromise(std::function<PromiseResolver()> &&function);
|
|
63
68
|
|
|
64
69
|
private:
|
|
65
70
|
jsi::Runtime *runtime_;
|
|
66
71
|
std::shared_ptr<react::CallInvoker> callInvoker_;
|
|
67
72
|
std::shared_ptr<ThreadPool> threadPool_;
|
|
73
|
+
|
|
74
|
+
static void asyncPromiseJob(
|
|
75
|
+
std::shared_ptr<react::CallInvoker> callInvoker,
|
|
76
|
+
std::function<PromiseResolver()> &&function,
|
|
77
|
+
std::shared_ptr<jsi::Function> &&resolve,
|
|
78
|
+
std::shared_ptr<jsi::Function> &&reject
|
|
79
|
+
);
|
|
68
80
|
};
|
|
69
81
|
|
|
70
82
|
} // namespace audioapi
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#include <functional>
|
|
3
|
+
|
|
4
|
+
namespace audioapi {
|
|
5
|
+
|
|
6
|
+
/// @brief A forward declaration of a move-only function wrapper.
|
|
7
|
+
/// @note it is somehow required to have <R(Args...)> specialization below
|
|
8
|
+
/// @tparam Signature
|
|
9
|
+
template<typename Signature>
|
|
10
|
+
class move_only_function; // Forward declaration
|
|
11
|
+
|
|
12
|
+
/// @brief A move-only function wrapper similar to std::function but non-copyable.
|
|
13
|
+
/// @details This class allows you to store and invoke callable objects (like lambdas, function pointers, or functors)
|
|
14
|
+
/// that can be moved but not copied. It is useful for scenarios where you want to ensure that the callable
|
|
15
|
+
/// is unique and cannot be duplicated.
|
|
16
|
+
/// @note This implementation uses type erasure to store the callable object.
|
|
17
|
+
/// @note The callable object must be invocable with the specified arguments and return type.
|
|
18
|
+
/// @note IMPORTANT: This thing is implemented in C++23 standard and can be replaced with std::move_only_function once we switch to C++23.
|
|
19
|
+
/// @tparam R
|
|
20
|
+
/// @tparam ...Args
|
|
21
|
+
template<typename R, typename... Args>
|
|
22
|
+
class move_only_function<R(Args...)> {
|
|
23
|
+
/// @brief The base class for type erasure.
|
|
24
|
+
/// @note It gets optimized by Empty Base Optimization (EBO) when possible.
|
|
25
|
+
struct callable_base {
|
|
26
|
+
virtual ~callable_base() = default;
|
|
27
|
+
virtual R operator()(Args... args) = 0;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/// @brief The implementation of the callable object.
|
|
31
|
+
/// @tparam F
|
|
32
|
+
template<typename F>
|
|
33
|
+
struct callable_impl : callable_base {
|
|
34
|
+
/// @brief The stored callable object.
|
|
35
|
+
F f;
|
|
36
|
+
|
|
37
|
+
/// @brief Construct a new callable_impl object.
|
|
38
|
+
/// @tparam G
|
|
39
|
+
/// @param func
|
|
40
|
+
/// @note The enable_if_t ensures that F can be constructed from G&&.
|
|
41
|
+
template<typename G, typename = std::enable_if_t<std::is_constructible_v<F, G&&>>>
|
|
42
|
+
callable_impl(G&& func) : f(std::forward<G>(func)) {}
|
|
43
|
+
|
|
44
|
+
/// @brief Invoke the stored callable object with the given arguments.
|
|
45
|
+
/// @param args
|
|
46
|
+
/// @return R
|
|
47
|
+
/// @note The if constexpr is used to handle the case when R is void.
|
|
48
|
+
inline R operator()(Args... args) override {
|
|
49
|
+
if constexpr (std::is_void_v<R>) {
|
|
50
|
+
/// To avoid "warning: expression result unused" when R is void
|
|
51
|
+
f(std::forward<Args>(args)...);
|
|
52
|
+
} else {
|
|
53
|
+
return f(std::forward<Args>(args)...);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/// @brief The unique pointer to the base callable type.
|
|
59
|
+
std::unique_ptr<callable_base> impl_;
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
move_only_function() = default;
|
|
63
|
+
move_only_function(std::nullptr_t) noexcept : impl_(nullptr) {}
|
|
64
|
+
|
|
65
|
+
template<typename F>
|
|
66
|
+
move_only_function(F&& f)
|
|
67
|
+
: impl_(std::make_unique<callable_impl<std::decay_t<F>>>(std::forward<F>(f))) {}
|
|
68
|
+
|
|
69
|
+
move_only_function(const move_only_function&) = delete;
|
|
70
|
+
move_only_function& operator=(const move_only_function&) = delete;
|
|
71
|
+
|
|
72
|
+
move_only_function(move_only_function&&) = default;
|
|
73
|
+
move_only_function& operator=(move_only_function&&) = default;
|
|
74
|
+
|
|
75
|
+
inline explicit operator bool() const noexcept {
|
|
76
|
+
return impl_ != nullptr;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
inline R operator()(Args... args) {
|
|
80
|
+
/// We are unlikely to hit this case as we want to optimize for the common case.
|
|
81
|
+
if (impl_ == nullptr) [[ unlikely ]] {
|
|
82
|
+
throw std::bad_function_call{};
|
|
83
|
+
}
|
|
84
|
+
return (*impl_)(std::forward<Args>(args)...);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void swap(move_only_function& other) noexcept {
|
|
88
|
+
impl_.swap(other.impl_);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
} // namespace audioapi
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
#include <vector>
|
|
4
4
|
#include <functional>
|
|
5
5
|
#include <variant>
|
|
6
|
+
|
|
7
|
+
#include <audioapi/utils/MoveOnlyFunction.hpp>
|
|
6
8
|
#include <audioapi/utils/SpscChannel.hpp>
|
|
7
9
|
|
|
8
10
|
namespace audioapi {
|
|
@@ -15,7 +17,7 @@ namespace audioapi {
|
|
|
15
17
|
/// @note IMPORTANT: ThreadPool is not thread-safe and events should be scheduled from a single thread only.
|
|
16
18
|
class ThreadPool {
|
|
17
19
|
struct StopEvent {};
|
|
18
|
-
struct TaskEvent {
|
|
20
|
+
struct TaskEvent { audioapi::move_only_function<void()> task; };
|
|
19
21
|
using Event = std::variant<TaskEvent, StopEvent>;
|
|
20
22
|
|
|
21
23
|
using Sender = channels::spsc::Sender<Event, channels::spsc::OverflowStrategy::WAIT_ON_FULL, channels::spsc::WaitStrategy::ATOMIC_WAIT>;
|
|
@@ -46,14 +48,21 @@ public:
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/// @brief Schedule a task to be executed by the thread pool
|
|
49
|
-
/// @
|
|
51
|
+
/// @tparam Func The type of the task function
|
|
52
|
+
/// @tparam Args The types of the task function arguments
|
|
53
|
+
/// @param task The task function to be executed
|
|
54
|
+
/// @param args The arguments to be passed to the task function
|
|
50
55
|
/// @note This function is lock-free and most of the time wait-free, but may block if the load balancer queue is full.
|
|
51
|
-
/// @note Please remember that the task will be executed in a different thread, so make sure to
|
|
56
|
+
/// @note Please remember that the task will be executed in a different thread, so make sure to pass any required variables by value or with std::move.
|
|
52
57
|
/// @note The task should not throw exceptions, as they will not be caught.
|
|
53
58
|
/// @note The task should end at some point, otherwise the thread pool will never be able to shut down.
|
|
54
59
|
/// @note IMPORTANT: This function is not thread-safe and should be called from a single thread only.
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
template<typename Func, typename ... Args, typename = std::enable_if_t<std::is_invocable_r_v<void, Func, Args...>>>
|
|
61
|
+
void schedule(Func &&task, Args &&... args) noexcept {
|
|
62
|
+
auto boundTask = [f = std::forward<Func>(task), ...capturedArgs = std::forward<Args>(args)]() mutable {
|
|
63
|
+
f(std::forward<Args>(capturedArgs)...);
|
|
64
|
+
};
|
|
65
|
+
loadBalancerSender.send(TaskEvent{audioapi::move_only_function<void()>(std::move(boundTask))});
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
private:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-audio-api",
|
|
3
|
-
"version": "0.10.0-nightly-
|
|
3
|
+
"version": "0.10.0-nightly-034f768-20251016",
|
|
4
4
|
"description": "react-native-audio-api provides system for controlling audio in React Native environment compatible with Web Audio API specification",
|
|
5
5
|
"bin": {
|
|
6
6
|
"setup-rn-audio-api-web": "./scripts/setup-rn-audio-api-web.js"
|