react-native-audio-api 0.10.0-nightly-c815c40-20251026 → 0.10.0-nightly-d3a7f65-20251028
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/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +4 -4
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +27 -6
- package/common/cpp/audioapi/utils/ThreadPool.hpp +59 -1
- package/ios/audioapi/ios/core/IOSAudioRecorder.h +1 -2
- package/package.json +1 -1
|
@@ -53,8 +53,6 @@ class AudioAPIModule(
|
|
|
53
53
|
if (BuildConfig.RN_AUDIO_API_ENABLE_WORKLETS) {
|
|
54
54
|
try {
|
|
55
55
|
workletsModule = reactContext.getNativeModule("WorkletsModule")
|
|
56
|
-
reanimatedModule = reactContext.getNativeModule("ReanimatedModule")
|
|
57
|
-
reanimatedModule
|
|
58
56
|
} catch (ex: Exception) {
|
|
59
57
|
throw RuntimeException("WorkletsModule not found - make sure react-native-worklets is properly installed")
|
|
60
58
|
}
|
|
@@ -77,11 +75,11 @@ class AudioAPIModule(
|
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
override fun onHostPause() {
|
|
80
|
-
|
|
78
|
+
// do nothing
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
override fun onHostDestroy() {
|
|
84
|
-
|
|
82
|
+
closeAllContexts()
|
|
85
83
|
}
|
|
86
84
|
|
|
87
85
|
override fun initialize() {
|
|
@@ -89,6 +87,8 @@ class AudioAPIModule(
|
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
override fun invalidate() {
|
|
90
|
+
closeAllContexts()
|
|
91
|
+
reactContext.get()?.removeLifecycleEventListener(this)
|
|
92
92
|
// think about cleaning up resources, singletons etc.
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -42,13 +42,11 @@ class LockScreenManager(
|
|
|
42
42
|
private var playbackState: Int = PlaybackStateCompat.STATE_PAUSED
|
|
43
43
|
|
|
44
44
|
init {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.nb.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
45
|
+
pb.setActions(controls)
|
|
46
|
+
nb.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
47
|
+
nb.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
49
48
|
|
|
50
49
|
updateNotificationMediaStyle()
|
|
51
|
-
|
|
52
50
|
mediaNotificationManager.get()?.updateActions(controls)
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -174,8 +172,31 @@ class LockScreenManager(
|
|
|
174
172
|
if (artworkThread != null && artworkThread!!.isAlive) artworkThread!!.interrupt()
|
|
175
173
|
artworkThread = null
|
|
176
174
|
|
|
177
|
-
|
|
175
|
+
title = null
|
|
176
|
+
artist = null
|
|
177
|
+
album = null
|
|
178
|
+
description = null
|
|
179
|
+
duration = 0L
|
|
180
|
+
speed = 1.0F
|
|
181
|
+
elapsedTime = 0L
|
|
182
|
+
artwork = null
|
|
183
|
+
playbackState = PlaybackStateCompat.STATE_PAUSED
|
|
184
|
+
isPlaying = false
|
|
185
|
+
|
|
186
|
+
val emptyMetadata = MediaMetadataCompat.Builder().build()
|
|
187
|
+
mediaSession.get()?.setMetadata(emptyMetadata)
|
|
188
|
+
|
|
189
|
+
pb.setState(PlaybackStateCompat.STATE_NONE, 0, 0f)
|
|
190
|
+
pb.setActions(controls)
|
|
191
|
+
state = pb.build()
|
|
192
|
+
mediaSession.get()?.setPlaybackState(state)
|
|
178
193
|
mediaSession.get()?.setActive(false)
|
|
194
|
+
|
|
195
|
+
nb.setContentTitle("")
|
|
196
|
+
nb.setContentText("")
|
|
197
|
+
nb.setContentInfo("")
|
|
198
|
+
|
|
199
|
+
mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
|
|
179
200
|
}
|
|
180
201
|
|
|
181
202
|
fun enableRemoteCommand(
|
|
@@ -20,6 +20,11 @@ class ThreadPool {
|
|
|
20
20
|
struct TaskEvent { audioapi::move_only_function<void()> task; };
|
|
21
21
|
using Event = std::variant<TaskEvent, StopEvent>;
|
|
22
22
|
|
|
23
|
+
struct Cntrl {
|
|
24
|
+
std::atomic<bool> waitingForTasks{false};
|
|
25
|
+
std::atomic<size_t> tasksScheduled{0};
|
|
26
|
+
};
|
|
27
|
+
|
|
23
28
|
using Sender = channels::spsc::Sender<Event, channels::spsc::OverflowStrategy::WAIT_ON_FULL, channels::spsc::WaitStrategy::ATOMIC_WAIT>;
|
|
24
29
|
using Receiver = channels::spsc::Receiver<Event, channels::spsc::OverflowStrategy::WAIT_ON_FULL, channels::spsc::WaitStrategy::ATOMIC_WAIT>;
|
|
25
30
|
public:
|
|
@@ -38,8 +43,30 @@ public:
|
|
|
38
43
|
workerSenders.emplace_back(std::move(workerSender));
|
|
39
44
|
}
|
|
40
45
|
loadBalancerThread = std::thread(&ThreadPool::loadBalancerThreadFunc, this, std::move(receiver), std::move(workerSenders));
|
|
46
|
+
controlBlock_ = std::make_unique<Cntrl>();
|
|
47
|
+
}
|
|
48
|
+
ThreadPool(const ThreadPool&) = delete;
|
|
49
|
+
ThreadPool& operator=(const ThreadPool&) = delete;
|
|
50
|
+
ThreadPool(ThreadPool&& other):
|
|
51
|
+
loadBalancerThread(std::move(other.loadBalancerThread)),
|
|
52
|
+
workers(std::move(other.workers)),
|
|
53
|
+
loadBalancerSender(std::move(other.loadBalancerSender)),
|
|
54
|
+
controlBlock_(std::move(other.controlBlock_)) {}
|
|
55
|
+
ThreadPool& operator=(ThreadPool&& other) {
|
|
56
|
+
if (this != &other) {
|
|
57
|
+
loadBalancerThread = std::move(other.loadBalancerThread);
|
|
58
|
+
workers = std::move(other.workers);
|
|
59
|
+
loadBalancerSender = std::move(other.loadBalancerSender);
|
|
60
|
+
controlBlock_ = std::move(other.controlBlock_);
|
|
61
|
+
other.movedFrom_ = true;
|
|
62
|
+
}
|
|
63
|
+
return *this;
|
|
41
64
|
}
|
|
65
|
+
|
|
42
66
|
~ThreadPool() {
|
|
67
|
+
if (movedFrom_) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
43
70
|
loadBalancerSender.send(StopEvent{});
|
|
44
71
|
loadBalancerThread.join();
|
|
45
72
|
for (auto& worker : workers) {
|
|
@@ -59,16 +86,47 @@ public:
|
|
|
59
86
|
/// @note IMPORTANT: This function is not thread-safe and should be called from a single thread only.
|
|
60
87
|
template<typename Func, typename ... Args, typename = std::enable_if_t<std::is_invocable_r_v<void, Func, Args...>>>
|
|
61
88
|
void schedule(Func &&task, Args &&... args) noexcept {
|
|
62
|
-
|
|
89
|
+
controlBlock_->tasksScheduled.fetch_add(1, std::memory_order_release);
|
|
90
|
+
|
|
91
|
+
/// We know that lifetime of each worker thus spsc thus lambda is strongly bounded by ThreadPool lifetime
|
|
92
|
+
/// so we can safely capture control block pointer unsafely here
|
|
93
|
+
Cntrl *cntrl = controlBlock_.get();
|
|
94
|
+
auto boundTask = [cntrl, f= std::forward<Func>(task), ...capturedArgs = std::forward<Args>(args)]() mutable {
|
|
63
95
|
f(std::forward<Args>(capturedArgs)...);
|
|
96
|
+
size_t left = cntrl->tasksScheduled.fetch_sub(1, std::memory_order_acq_rel) - 1;
|
|
97
|
+
if (left == 0) {
|
|
98
|
+
cntrl->waitingForTasks.store(false, std::memory_order_release);
|
|
99
|
+
cntrl->waitingForTasks.notify_one();
|
|
100
|
+
}
|
|
64
101
|
};
|
|
65
102
|
loadBalancerSender.send(TaskEvent{audioapi::move_only_function<void()>(std::move(boundTask))});
|
|
66
103
|
}
|
|
67
104
|
|
|
105
|
+
/// @brief Waits for all scheduled tasks to complete
|
|
106
|
+
void wait() {
|
|
107
|
+
/// This logic might seem incorrect at first glance
|
|
108
|
+
/// Main principle for this is that there is only one thread scheduling tasks
|
|
109
|
+
/// If he is waiting for the tasks he CANNOT schedule new tasks so we can assume partial
|
|
110
|
+
/// synchronization here.
|
|
111
|
+
/// We first store true so if any task finishes at this moment he will flip it
|
|
112
|
+
/// Then we check if there are any tasks scheduled
|
|
113
|
+
/// If there are none we can return immediately
|
|
114
|
+
/// If there are some we wait until the last task flips the flag to false
|
|
115
|
+
controlBlock_->waitingForTasks.store(true, std::memory_order_release);
|
|
116
|
+
if (controlBlock_->tasksScheduled.load(std::memory_order_acquire) == 0) {
|
|
117
|
+
controlBlock_->waitingForTasks.store(false, std::memory_order_release);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
controlBlock_->waitingForTasks.wait(true, std::memory_order_acquire);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
68
124
|
private:
|
|
69
125
|
std::thread loadBalancerThread;
|
|
70
126
|
std::vector<std::thread> workers;
|
|
71
127
|
Sender loadBalancerSender;
|
|
128
|
+
std::unique_ptr<Cntrl> controlBlock_;
|
|
129
|
+
bool movedFrom_ = false;
|
|
72
130
|
|
|
73
131
|
void workerThreadFunc(Receiver &&receiver) {
|
|
74
132
|
Receiver localReceiver = std::move(receiver);
|
|
@@ -18,8 +18,7 @@ class IOSAudioRecorder : public AudioRecorder {
|
|
|
18
18
|
IOSAudioRecorder(
|
|
19
19
|
float sampleRate,
|
|
20
20
|
int bufferLength,
|
|
21
|
-
const std::shared_ptr<AudioEventHandlerRegistry>
|
|
22
|
-
&audioEventHandlerRegistry);
|
|
21
|
+
const std::shared_ptr<AudioEventHandlerRegistry> &audioEventHandlerRegistry);
|
|
23
22
|
|
|
24
23
|
~IOSAudioRecorder() override;
|
|
25
24
|
|
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-d3a7f65-20251028",
|
|
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"
|