react-native-audio-api 0.10.0-nightly-cced1c6-20251014 → 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.
@@ -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_->createPromise(
34
- [data, size, sampleRate](std::shared_ptr<Promise> promise) {
35
- std::thread([data, size, sampleRate, promise = std::move(promise)]() {
36
- auto result =
37
- AudioDecoder::decodeWithMemoryBlock(data, size, sampleRate);
38
-
39
- if (!result) {
40
- promise->reject("Failed to decode audio data.");
41
- return;
42
- }
43
-
44
- auto audioBufferHostObject =
45
- std::make_shared<AudioBufferHostObject>(result);
46
-
47
- promise->resolve([audioBufferHostObject = std::move(
48
- audioBufferHostObject)](jsi::Runtime &runtime) {
49
- auto jsiObject = jsi::Object::createFromHostObject(
50
- runtime, audioBufferHostObject);
51
- jsiObject.setExternalMemoryPressure(
52
- runtime, audioBufferHostObject->getSizeInBytes());
53
- return jsiObject;
54
- });
55
- }).detach();
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_->createPromise(
65
- [sourcePath, sampleRate](std::shared_ptr<Promise> promise) {
66
- std::thread([sourcePath, sampleRate, promise = std::move(promise)]() {
67
- auto result =
68
- AudioDecoder::decodeWithFilePath(sourcePath, sampleRate);
69
-
70
- if (!result) {
71
- promise->reject("Failed to decode audio data source.");
72
- return;
73
- }
74
-
75
- auto audioBufferHostObject =
76
- std::make_shared<AudioBufferHostObject>(result);
77
-
78
- promise->resolve([audioBufferHostObject = std::move(
79
- audioBufferHostObject)](jsi::Runtime &runtime) {
80
- auto jsiObject = jsi::Object::createFromHostObject(
81
- runtime, audioBufferHostObject);
82
- jsiObject.setExternalMemoryPressure(
83
- runtime, audioBufferHostObject->getSizeInBytes());
84
- return jsiObject;
85
- });
86
- }).detach();
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_->createPromise(
99
- [b64, inputSampleRate, inputChannelCount, interleaved](
100
- std::shared_ptr<Promise> promise) {
101
- std::thread([b64,
102
- inputSampleRate,
103
- inputChannelCount,
104
- interleaved,
105
- promise = std::move(promise)]() {
106
- auto result = AudioDecoder::decodeWithPCMInBase64(
107
- b64, inputSampleRate, inputChannelCount, interleaved);
108
-
109
- if (!result) {
110
- promise->reject("Failed to decode audio data source.");
111
- return;
112
- }
113
-
114
- auto audioBufferHostObject =
115
- std::make_shared<AudioBufferHostObject>(result);
116
-
117
- promise->resolve([audioBufferHostObject = std::move(
118
- audioBufferHostObject)](jsi::Runtime &runtime) {
119
- auto jsiObject = jsi::Object::createFromHostObject(
120
- runtime, audioBufferHostObject);
121
- jsiObject.setExternalMemoryPressure(
122
- runtime, audioBufferHostObject->getSizeInBytes());
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<std::variant<jsi::Value, std::string>(jsi::Runtime &)>
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([callInvoker = std::move(callInvoker),
84
- function = std::move(function),
85
- resolve = std::move(resolve),
86
- reject = std::move(reject),
87
- &runtime]() {
88
- auto result = function(runtime);
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
- /// [](jsi::Runtime& rt) -> std::variant<jsi::Value, std::string> {
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::String::createFromUtf8(rt, "Promise resolved successfully!");
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<std::variant<jsi::Value, std::string>(jsi::Runtime&)> &&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 { std::function<void()> task; };
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
- /// @param task The task to be executed
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 capture any required variables by value.
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
- void schedule(std::function<void()> &&task) noexcept {
56
- loadBalancerSender.send(TaskEvent{std::move(task)});
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-cced1c6-20251014",
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"