react-native-audio-api 0.8.1-nightly-2c9c6a6-20250903 → 0.8.1-nightly-da0ddc8-20250904
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 +1 -1
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +4 -0
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +10 -1
- package/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp +36 -0
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +24 -0
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +4 -4
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +78 -70
- package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +2 -1
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +3 -11
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +57 -39
- package/android/src/main/res/drawable/logo.xml +16 -0
- package/common/cpp/audioapi/HostObjects/AudioContextHostObject.h +17 -23
- package/common/cpp/audioapi/HostObjects/AudioParamHostObject.h +4 -2
- package/common/cpp/audioapi/core/AudioParam.cpp +205 -254
- package/common/cpp/audioapi/core/AudioParam.h +98 -21
- package/common/cpp/audioapi/core/AudioParamEventQueue.cpp +65 -0
- package/common/cpp/audioapi/core/AudioParamEventQueue.h +63 -0
- package/common/cpp/audioapi/core/sources/StreamerNode.cpp +6 -5
- package/common/cpp/audioapi/core/sources/StreamerNode.h +5 -4
- package/common/cpp/audioapi/core/utils/ParamChangeEvent.cpp +2 -39
- package/common/cpp/audioapi/core/utils/ParamChangeEvent.h +53 -12
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +10 -0
- package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +10 -0
- package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +58 -0
- package/common/cpp/audioapi/utils/RingBiDirectionalBuffer.hpp +199 -0
- package/ios/audioapi/ios/system/AudioEngine.mm +1 -0
- package/lib/commonjs/plugin/withAudioAPI.js +1 -1
- package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
- package/lib/module/plugin/withAudioAPI.js +1 -1
- package/lib/module/plugin/withAudioAPI.js.map +1 -1
- package/package.json +1 -1
- package/src/plugin/withAudioAPI.ts +1 -1
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ We are open to new ideas and general feedback. If you want to share your opinion
|
|
|
81
81
|
|
|
82
82
|
## License
|
|
83
83
|
|
|
84
|
-
react-native-audio-api library is licensed under [The MIT License](./LICENSE). Some of the source code uses implementation directly copied from Webkit and
|
|
84
|
+
react-native-audio-api library is licensed under [The MIT License](./LICENSE). Some of the source code uses implementation directly copied from Webkit and utilizes FFmpeg binaries. Copyrights are held by respective organizations, check [THIRD_PARTY](./THIRD_PARTY.md) file for further details.
|
|
85
85
|
|
|
86
86
|
## Credits
|
|
87
87
|
|
|
@@ -15,6 +15,8 @@ AudioPlayer::AudioPlayer(
|
|
|
15
15
|
sampleRate_(sampleRate),
|
|
16
16
|
channelCount_(channelCount) {
|
|
17
17
|
isInitialized_ = openAudioStream();
|
|
18
|
+
|
|
19
|
+
nativeAudioPlayer_ = jni::make_global(NativeAudioPlayer::create());
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
bool AudioPlayer::openAudioStream() {
|
|
@@ -47,6 +49,7 @@ bool AudioPlayer::openAudioStream() {
|
|
|
47
49
|
|
|
48
50
|
bool AudioPlayer::start() {
|
|
49
51
|
if (mStream_) {
|
|
52
|
+
nativeAudioPlayer_->start();
|
|
50
53
|
auto result = mStream_->requestStart();
|
|
51
54
|
return result == oboe::Result::OK;
|
|
52
55
|
}
|
|
@@ -56,6 +59,7 @@ bool AudioPlayer::start() {
|
|
|
56
59
|
|
|
57
60
|
void AudioPlayer::stop() {
|
|
58
61
|
if (mStream_) {
|
|
62
|
+
nativeAudioPlayer_->stop();
|
|
59
63
|
mStream_->requestStop();
|
|
60
64
|
}
|
|
61
65
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
#include <functional>
|
|
6
6
|
#include <memory>
|
|
7
7
|
|
|
8
|
+
#include <audioapi/android/core/NativeAudioPlayer.hpp>
|
|
9
|
+
|
|
8
10
|
namespace audioapi {
|
|
9
11
|
|
|
10
12
|
using namespace oboe;
|
|
@@ -19,13 +21,18 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback {
|
|
|
19
21
|
float sampleRate,
|
|
20
22
|
int channelCount);
|
|
21
23
|
|
|
24
|
+
~AudioPlayer() override {
|
|
25
|
+
nativeAudioPlayer_.release();
|
|
26
|
+
cleanup();
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
bool start();
|
|
23
30
|
void stop();
|
|
24
31
|
bool resume();
|
|
25
32
|
void suspend();
|
|
26
33
|
void cleanup();
|
|
27
34
|
|
|
28
|
-
bool isRunning() const;
|
|
35
|
+
[[nodiscard]] bool isRunning() const;
|
|
29
36
|
|
|
30
37
|
DataCallbackResult onAudioReady(
|
|
31
38
|
AudioStream *oboeStream,
|
|
@@ -44,6 +51,8 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback {
|
|
|
44
51
|
int channelCount_;
|
|
45
52
|
|
|
46
53
|
bool openAudioStream();
|
|
54
|
+
|
|
55
|
+
facebook::jni::global_ref<NativeAudioPlayer> nativeAudioPlayer_;
|
|
47
56
|
};
|
|
48
57
|
|
|
49
58
|
} // namespace audioapi
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#include <react/jni/CxxModuleWrapper.h>
|
|
6
|
+
#include <react/jni/JMessageQueueThread.h>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <utility>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
|
|
11
|
+
namespace audioapi {
|
|
12
|
+
|
|
13
|
+
using namespace facebook;
|
|
14
|
+
using namespace react;
|
|
15
|
+
|
|
16
|
+
class NativeAudioPlayer : public jni::JavaClass<NativeAudioPlayer> {
|
|
17
|
+
public:
|
|
18
|
+
static auto constexpr kJavaDescriptor =
|
|
19
|
+
"Lcom/swmansion/audioapi/core/NativeAudioPlayer;";
|
|
20
|
+
|
|
21
|
+
static jni::local_ref<NativeAudioPlayer> create() {
|
|
22
|
+
return newInstance();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void start() {
|
|
26
|
+
static const auto method = javaClassStatic()->getMethod<void()>("start");
|
|
27
|
+
method(self());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void stop() {
|
|
31
|
+
static const auto method = javaClassStatic()->getMethod<void()>("stop");
|
|
32
|
+
method(self());
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
} // namespace audioapi
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.swmansion.audioapi.core
|
|
2
|
+
|
|
3
|
+
import com.facebook.common.internal.DoNotStrip
|
|
4
|
+
import com.swmansion.audioapi.system.MediaSessionManager
|
|
5
|
+
|
|
6
|
+
@DoNotStrip
|
|
7
|
+
class NativeAudioPlayer {
|
|
8
|
+
private var sourceNodeId: String? = null
|
|
9
|
+
|
|
10
|
+
@DoNotStrip
|
|
11
|
+
fun start() {
|
|
12
|
+
this.sourceNodeId = MediaSessionManager.attachAudioPlayer(this)
|
|
13
|
+
MediaSessionManager.startForegroundServiceIfNecessary()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@DoNotStrip
|
|
17
|
+
fun stop() {
|
|
18
|
+
this.sourceNodeId?.let {
|
|
19
|
+
MediaSessionManager.detachAudioPlayer(it)
|
|
20
|
+
this.sourceNodeId = null
|
|
21
|
+
}
|
|
22
|
+
MediaSessionManager.stopForegroundServiceIfNecessary()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -122,7 +122,7 @@ class LockScreenManager(
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
nb.setLargeIcon(bitmap)
|
|
125
|
-
mediaNotificationManager.get()?.
|
|
125
|
+
mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
|
|
126
126
|
|
|
127
127
|
artworkThread = null
|
|
128
128
|
} catch (ex: Exception) {
|
|
@@ -167,14 +167,14 @@ class LockScreenManager(
|
|
|
167
167
|
|
|
168
168
|
mediaSession.get()?.setMetadata(md.build())
|
|
169
169
|
mediaSession.get()?.setActive(true)
|
|
170
|
-
mediaNotificationManager.get()?.
|
|
170
|
+
mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
fun resetLockScreenInfo() {
|
|
174
174
|
if (artworkThread != null && artworkThread!!.isAlive) artworkThread!!.interrupt()
|
|
175
175
|
artworkThread = null
|
|
176
176
|
|
|
177
|
-
mediaNotificationManager.get()?.
|
|
177
|
+
mediaNotificationManager.get()?.cancelNotification()
|
|
178
178
|
mediaSession.get()?.setActive(false)
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -235,7 +235,7 @@ class LockScreenManager(
|
|
|
235
235
|
updateNotificationMediaStyle()
|
|
236
236
|
|
|
237
237
|
if (mediaSession.get()?.isActive == true) {
|
|
238
|
-
mediaNotificationManager.get()?.
|
|
238
|
+
mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
package com.swmansion.audioapi.system
|
|
2
2
|
|
|
3
|
+
import android.Manifest
|
|
3
4
|
import android.annotation.SuppressLint
|
|
4
5
|
import android.app.Notification
|
|
5
6
|
import android.app.PendingIntent
|
|
6
7
|
import android.app.Service
|
|
7
8
|
import android.content.Intent
|
|
9
|
+
import android.content.pm.ServiceInfo
|
|
8
10
|
import android.content.res.Resources
|
|
9
|
-
import android.os.Binder
|
|
10
11
|
import android.os.Build
|
|
11
12
|
import android.os.IBinder
|
|
12
13
|
import android.provider.ContactsContract
|
|
13
14
|
import android.support.v4.media.session.PlaybackStateCompat
|
|
14
15
|
import android.util.Log
|
|
15
16
|
import android.view.KeyEvent
|
|
17
|
+
import androidx.annotation.RequiresPermission
|
|
16
18
|
import androidx.core.app.NotificationCompat
|
|
17
19
|
import androidx.core.app.NotificationManagerCompat
|
|
18
|
-
import androidx.core.content.ContextCompat
|
|
19
20
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
20
21
|
import com.swmansion.audioapi.R
|
|
21
22
|
import java.lang.ref.WeakReference
|
|
@@ -23,7 +24,7 @@ import java.lang.ref.WeakReference
|
|
|
23
24
|
class MediaNotificationManager(
|
|
24
25
|
private val reactContext: WeakReference<ReactApplicationContext>,
|
|
25
26
|
) {
|
|
26
|
-
private var smallIcon: Int = R.drawable.
|
|
27
|
+
private var smallIcon: Int = R.drawable.logo
|
|
27
28
|
private var customIcon: Int = 0
|
|
28
29
|
|
|
29
30
|
private var play: NotificationCompat.Action? = null
|
|
@@ -40,6 +41,16 @@ class MediaNotificationManager(
|
|
|
40
41
|
const val MEDIA_BUTTON: String = "audio_manager_media_button"
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
enum class ForegroundAction {
|
|
45
|
+
START_FOREGROUND,
|
|
46
|
+
STOP_FOREGROUND,
|
|
47
|
+
;
|
|
48
|
+
|
|
49
|
+
companion object {
|
|
50
|
+
fun fromAction(action: String?): ForegroundAction? = entries.firstOrNull { it.name == action }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
@SuppressLint("RestrictedApi")
|
|
44
55
|
@Synchronized
|
|
45
56
|
fun prepareNotification(
|
|
@@ -107,34 +118,20 @@ class MediaNotificationManager(
|
|
|
107
118
|
return builder.build()
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
@
|
|
121
|
+
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
|
|
111
122
|
@Synchronized
|
|
112
|
-
fun
|
|
123
|
+
fun updateNotification(
|
|
113
124
|
builder: NotificationCompat.Builder?,
|
|
114
125
|
isPlaying: Boolean,
|
|
115
126
|
) {
|
|
116
127
|
NotificationManagerCompat.from(reactContext.get()!!).notify(
|
|
117
128
|
MediaSessionManager.NOTIFICATION_ID,
|
|
118
|
-
prepareNotification(
|
|
119
|
-
builder!!,
|
|
120
|
-
isPlaying,
|
|
121
|
-
),
|
|
129
|
+
prepareNotification(builder!!, isPlaying),
|
|
122
130
|
)
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
fun
|
|
133
|
+
fun cancelNotification() {
|
|
126
134
|
NotificationManagerCompat.from(reactContext.get()!!).cancel(MediaSessionManager.NOTIFICATION_ID)
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
val myIntent =
|
|
130
|
-
Intent(
|
|
131
|
-
reactContext.get(),
|
|
132
|
-
NotificationService::class.java,
|
|
133
|
-
)
|
|
134
|
-
reactContext.get()?.stopService(myIntent)
|
|
135
|
-
} catch (e: java.lang.Exception) {
|
|
136
|
-
Log.w("AudioManagerModule", "Error stopping service: ${e.message}")
|
|
137
|
-
}
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
@Synchronized
|
|
@@ -182,45 +179,42 @@ class MediaNotificationManager(
|
|
|
182
179
|
return NotificationCompat.Action(icon!!, title, i)
|
|
183
180
|
}
|
|
184
181
|
|
|
185
|
-
class
|
|
186
|
-
private val binder = LocalBinder()
|
|
182
|
+
class AudioForegroundService : Service() {
|
|
187
183
|
private var notification: Notification? = null
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} catch (ex: Exception) {
|
|
223
|
-
Log.w("AudioManagerModule", "Error starting service: ${ex.message}")
|
|
184
|
+
private var isServiceStarted = false
|
|
185
|
+
private val serviceLock = Any()
|
|
186
|
+
|
|
187
|
+
override fun onBind(intent: Intent): IBinder? = null
|
|
188
|
+
|
|
189
|
+
private fun startForegroundService() {
|
|
190
|
+
synchronized(serviceLock) {
|
|
191
|
+
if (!isServiceStarted) {
|
|
192
|
+
try {
|
|
193
|
+
notification =
|
|
194
|
+
MediaSessionManager.mediaNotificationManager
|
|
195
|
+
.prepareNotification(
|
|
196
|
+
NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID),
|
|
197
|
+
false,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
201
|
+
startForeground(
|
|
202
|
+
MediaSessionManager.NOTIFICATION_ID,
|
|
203
|
+
notification!!,
|
|
204
|
+
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST,
|
|
205
|
+
)
|
|
206
|
+
} else {
|
|
207
|
+
startForeground(
|
|
208
|
+
MediaSessionManager.NOTIFICATION_ID,
|
|
209
|
+
notification,
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
isServiceStarted = true
|
|
213
|
+
} catch (ex: Exception) {
|
|
214
|
+
Log.e("AudioManagerModule", "Error starting foreground service: ${ex.message}")
|
|
215
|
+
stopSelf()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
224
218
|
}
|
|
225
219
|
}
|
|
226
220
|
|
|
@@ -229,26 +223,40 @@ class MediaNotificationManager(
|
|
|
229
223
|
flags: Int,
|
|
230
224
|
startId: Int,
|
|
231
225
|
): Int {
|
|
232
|
-
|
|
226
|
+
val action = ForegroundAction.fromAction(intent?.action)
|
|
227
|
+
|
|
228
|
+
when (action) {
|
|
229
|
+
ForegroundAction.START_FOREGROUND -> startForegroundService()
|
|
230
|
+
ForegroundAction.STOP_FOREGROUND -> stopForegroundService()
|
|
231
|
+
else -> startForegroundService()
|
|
232
|
+
}
|
|
233
|
+
|
|
233
234
|
return START_NOT_STICKY
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
private fun stopForegroundService() {
|
|
238
|
+
synchronized(serviceLock) {
|
|
239
|
+
if (isServiceStarted) {
|
|
240
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
241
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
242
|
+
}
|
|
243
|
+
isServiceStarted = false
|
|
244
|
+
stopSelf()
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
|
237
250
|
super.onTaskRemoved(rootIntent)
|
|
238
|
-
|
|
239
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
240
|
-
}
|
|
241
|
-
stopSelf()
|
|
251
|
+
stopForegroundService()
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
override fun onDestroy() {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
255
|
+
synchronized(serviceLock) {
|
|
256
|
+
notification = null
|
|
257
|
+
isServiceStarted = false
|
|
249
258
|
}
|
|
250
|
-
|
|
251
|
-
stopSelf()
|
|
259
|
+
super.onDestroy()
|
|
252
260
|
}
|
|
253
261
|
}
|
|
254
262
|
}
|
|
@@ -25,7 +25,8 @@ class MediaReceiver(
|
|
|
25
25
|
if (MediaNotificationManager.REMOVE_NOTIFICATION == action) {
|
|
26
26
|
if (!checkApp(intent)) return
|
|
27
27
|
|
|
28
|
-
mediaNotificationManager.get()?.
|
|
28
|
+
mediaNotificationManager.get()?.cancelNotification()
|
|
29
|
+
MediaSessionManager.stopForegroundServiceIfNecessary()
|
|
29
30
|
mediaSession.get()?.isActive = false
|
|
30
31
|
|
|
31
32
|
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("closeNotification", mapOf()) // add to ts events
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
package com.swmansion.audioapi.system
|
|
2
2
|
|
|
3
|
-
import android.content.Intent
|
|
4
|
-
import android.os.Build
|
|
5
3
|
import android.os.Bundle
|
|
6
4
|
import android.support.v4.media.session.MediaSessionCompat
|
|
7
5
|
import android.util.Log
|
|
8
|
-
import androidx.core.app.NotificationManagerCompat
|
|
9
6
|
import com.swmansion.audioapi.AudioAPIModule
|
|
10
7
|
import java.lang.ref.WeakReference
|
|
11
|
-
import java.util.HashMap
|
|
12
8
|
|
|
13
9
|
class MediaSessionCallback(
|
|
14
10
|
private val audioAPIModule: WeakReference<AudioAPIModule>,
|
|
11
|
+
private val mediaNotificationManager: WeakReference<MediaNotificationManager>,
|
|
15
12
|
) : MediaSessionCompat.Callback() {
|
|
16
13
|
override fun onPlay() {
|
|
17
14
|
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remotePlay", mapOf())
|
|
@@ -22,13 +19,8 @@ class MediaSessionCallback(
|
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
override fun onStop() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
28
|
-
val myIntent =
|
|
29
|
-
Intent(reactContext, MediaNotificationManager.NotificationService::class.java)
|
|
30
|
-
reactContext.stopService(myIntent)
|
|
31
|
-
}
|
|
22
|
+
mediaNotificationManager.get()?.cancelNotification()
|
|
23
|
+
MediaSessionManager.stopForegroundServiceIfNecessary()
|
|
32
24
|
|
|
33
25
|
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remoteStop", mapOf())
|
|
34
26
|
}
|
|
@@ -3,18 +3,14 @@ package com.swmansion.audioapi.system
|
|
|
3
3
|
import android.Manifest
|
|
4
4
|
import android.app.NotificationChannel
|
|
5
5
|
import android.app.NotificationManager
|
|
6
|
-
import android.content.ComponentName
|
|
7
6
|
import android.content.Context
|
|
8
7
|
import android.content.Intent
|
|
9
8
|
import android.content.IntentFilter
|
|
10
|
-
import android.content.ServiceConnection
|
|
11
9
|
import android.content.pm.PackageManager
|
|
12
10
|
import android.media.AudioDeviceInfo
|
|
13
11
|
import android.media.AudioManager
|
|
14
12
|
import android.os.Build
|
|
15
|
-
import android.os.IBinder
|
|
16
13
|
import android.support.v4.media.session.MediaSessionCompat
|
|
17
|
-
import android.util.Log
|
|
18
14
|
import androidx.annotation.RequiresApi
|
|
19
15
|
import androidx.core.app.NotificationCompat
|
|
20
16
|
import androidx.core.content.ContextCompat
|
|
@@ -24,8 +20,10 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
24
20
|
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
25
21
|
import com.facebook.react.modules.core.PermissionListener
|
|
26
22
|
import com.swmansion.audioapi.AudioAPIModule
|
|
23
|
+
import com.swmansion.audioapi.core.NativeAudioPlayer
|
|
27
24
|
import com.swmansion.audioapi.system.PermissionRequestListener.Companion.RECORDING_REQUEST_CODE
|
|
28
25
|
import java.lang.ref.WeakReference
|
|
26
|
+
import java.util.UUID
|
|
29
27
|
|
|
30
28
|
object MediaSessionManager {
|
|
31
29
|
private lateinit var audioAPIModule: WeakReference<AudioAPIModule>
|
|
@@ -41,31 +39,9 @@ object MediaSessionManager {
|
|
|
41
39
|
private lateinit var volumeChangeListener: VolumeChangeListener
|
|
42
40
|
private lateinit var mediaReceiver: MediaReceiver
|
|
43
41
|
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
name: ComponentName,
|
|
48
|
-
service: IBinder,
|
|
49
|
-
) {
|
|
50
|
-
Log.w("MediaSessionManager", "onServiceConnected")
|
|
51
|
-
val binder = service as MediaNotificationManager.NotificationService.LocalBinder
|
|
52
|
-
val notificationService = binder.getService()
|
|
53
|
-
notificationService?.forceForeground()
|
|
54
|
-
reactContext.get()?.unbindService(this)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
override fun onServiceDisconnected(name: ComponentName) {
|
|
58
|
-
Log.w("MediaSessionManager", "Service is disconnected.")
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
override fun onBindingDied(name: ComponentName) {
|
|
62
|
-
Log.w("MediaSessionManager", "Binding has died.")
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
override fun onNullBinding(name: ComponentName) {
|
|
66
|
-
Log.w("MediaSessionManager", "Bind was null.")
|
|
67
|
-
}
|
|
68
|
-
}
|
|
42
|
+
private var isServiceRunning = false
|
|
43
|
+
private val serviceStateLock = Any()
|
|
44
|
+
private val nativeAudioPlayers = mutableMapOf<String, NativeAudioPlayer>()
|
|
69
45
|
|
|
70
46
|
fun initialize(
|
|
71
47
|
audioAPIModule: WeakReference<AudioAPIModule>,
|
|
@@ -83,8 +59,8 @@ object MediaSessionManager {
|
|
|
83
59
|
this.mediaNotificationManager = MediaNotificationManager(this.reactContext)
|
|
84
60
|
this.lockScreenManager = LockScreenManager(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager))
|
|
85
61
|
this.mediaReceiver =
|
|
86
|
-
MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager), this.audioAPIModule)
|
|
87
|
-
this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule))
|
|
62
|
+
MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(this.mediaNotificationManager), this.audioAPIModule)
|
|
63
|
+
this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule, WeakReference(this.mediaNotificationManager)))
|
|
88
64
|
|
|
89
65
|
val filter = IntentFilter()
|
|
90
66
|
filter.addAction(MediaNotificationManager.REMOVE_NOTIFICATION)
|
|
@@ -106,17 +82,59 @@ object MediaSessionManager {
|
|
|
106
82
|
this.audioFocusListener =
|
|
107
83
|
AudioFocusListener(WeakReference(this.audioManager), this.audioAPIModule, WeakReference(this.lockScreenManager))
|
|
108
84
|
this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule)
|
|
85
|
+
}
|
|
109
86
|
|
|
110
|
-
|
|
87
|
+
fun attachAudioPlayer(player: NativeAudioPlayer): String {
|
|
88
|
+
val uuid = UUID.randomUUID().toString()
|
|
89
|
+
nativeAudioPlayers[uuid] = player
|
|
111
90
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
91
|
+
return uuid
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fun detachAudioPlayer(uuid: String) {
|
|
95
|
+
nativeAudioPlayers.remove(uuid)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun startForegroundServiceIfNecessary() {
|
|
99
|
+
if (nativeAudioPlayers.isNotEmpty()) {
|
|
100
|
+
startForegroundService()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fun stopForegroundServiceIfNecessary() {
|
|
105
|
+
if (nativeAudioPlayers.isEmpty()) {
|
|
106
|
+
stopForegroundService()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private fun startForegroundService() {
|
|
111
|
+
synchronized(serviceStateLock) {
|
|
112
|
+
if (isServiceRunning || reactContext.get() == null) {
|
|
113
|
+
return
|
|
117
114
|
}
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
|
|
116
|
+
val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java)
|
|
117
|
+
intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.name
|
|
118
|
+
|
|
119
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
120
|
+
ContextCompat.startForegroundService(reactContext.get()!!, intent)
|
|
121
|
+
} else {
|
|
122
|
+
reactContext.get()!!.startService(intent)
|
|
123
|
+
}
|
|
124
|
+
isServiceRunning = true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun stopForegroundService() {
|
|
129
|
+
synchronized(serviceStateLock) {
|
|
130
|
+
if (!isServiceRunning || reactContext.get() == null) {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java)
|
|
135
|
+
intent.action = MediaNotificationManager.ForegroundAction.STOP_FOREGROUND.name
|
|
136
|
+
reactContext.get()!!.startService(intent)
|
|
137
|
+
isServiceRunning = false
|
|
120
138
|
}
|
|
121
139
|
}
|
|
122
140
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
android:width="36dp"
|
|
3
|
+
android:height="40dp"
|
|
4
|
+
android:viewportWidth="36"
|
|
5
|
+
android:viewportHeight="40">
|
|
6
|
+
<path
|
|
7
|
+
android:pathData="M17.397,0C21.203,0.046 23.27,5.296 24.179,8.243C27.21,7.54 32.708,6.626 34.647,9.87C36.595,13.128 33.04,17.387 30.914,19.563C33.119,21.806 36.799,26.046 34.949,29.24C33.109,32.416 27.554,31.459 24.556,30.713C24.255,31.785 23.807,33.136 23.178,34.422H21.299C22.103,32.984 22.632,31.292 22.924,30.254C21.313,29.752 19.606,29.081 17.859,28.265C16.146,29.045 14.486,29.679 12.922,30.146C13.596,32.348 15.113,37.036 17.942,37.07C18.571,37.078 19.137,36.851 19.642,36.47V38.386C19.129,38.609 18.571,38.732 17.967,38.725C14.175,38.679 12.217,33.563 11.307,30.576C8.246,31.289 2.705,32.064 0.751,28.796C-1.189,25.549 2.304,21.448 4.464,19.251C2.234,16.992 -1.343,12.643 0.519,9.426C2.388,6.201 7.879,7.256 10.974,8.06C11.796,5.112 13.607,-0.045 17.397,0ZM29.727,20.706C28.536,21.787 27.16,22.861 25.645,23.896C25.517,25.774 25.284,27.531 24.947,29.115C27.313,29.701 32.09,30.789 33.478,28.394C34.853,26.021 31.341,22.355 29.727,20.706ZM5.689,20.42C4.098,22.039 0.752,25.567 2.199,27.988C3.654,30.42 8.949,29.416 10.864,28.969C10.462,27.348 10.155,25.583 9.952,23.74C8.36,22.649 6.924,21.533 5.689,20.42ZM23.853,25.061C22.536,25.876 21.18,26.632 19.795,27.334C21.007,27.856 22.188,28.299 23.314,28.653C23.546,27.543 23.726,26.339 23.853,25.061ZM11.796,24.948C11.974,26.203 12.204,27.408 12.48,28.536C13.569,28.206 14.713,27.791 15.893,27.296C14.496,26.57 13.13,25.784 11.796,24.948ZM17.621,12.313C15.473,13.304 13.408,14.458 11.446,15.76C11.298,18.111 11.334,20.479 11.55,22.834C13.561,24.165 15.658,25.375 17.827,26.431C19.965,25.413 22.038,24.259 24.01,22.964C24.129,20.623 24.098,18.269 23.896,15.925C21.897,14.57 19.797,13.362 17.621,12.313ZM25.674,17.194C25.764,18.716 25.784,20.24 25.745,21.761C26.751,21.027 27.678,20.279 28.507,19.531C27.645,18.744 26.695,17.96 25.674,17.194ZM9.697,16.982C8.679,17.733 7.733,18.504 6.88,19.278C7.742,20.049 8.71,20.826 9.767,21.597C9.67,20.059 9.646,18.518 9.697,16.982ZM33.199,10.678C31.759,8.27 26.844,9.336 24.623,9.85C25.013,11.441 25.308,13.189 25.5,15.035C27.038,16.117 28.452,17.247 29.696,18.388C31.236,16.81 34.652,13.11 33.199,10.678ZM10.576,9.657C8.293,9.061 3.383,7.868 1.99,10.273C0.597,12.679 4.074,16.508 5.652,18.109C6.881,16.989 8.287,15.88 9.82,14.825C9.967,13 10.222,11.259 10.576,9.657ZM23.003,10.274C21.898,10.596 20.754,10.993 19.591,11.461C20.983,12.18 22.341,12.961 23.66,13.804C23.49,12.558 23.27,11.374 23.003,10.274ZM12.206,10.129C11.965,11.234 11.772,12.41 11.632,13.638C12.925,12.833 14.259,12.088 15.627,11.408C14.459,10.911 13.311,10.482 12.206,10.129ZM17.421,1.655C14.583,1.62 13.266,6.153 12.602,8.53C14.208,9.038 15.893,9.698 17.596,10.484C19.286,9.74 20.959,9.127 22.561,8.665C21.899,6.529 20.262,1.689 17.421,1.655Z"
|
|
8
|
+
android:fillColor="#919FCF"
|
|
9
|
+
android:fillType="evenOdd"/>
|
|
10
|
+
<path
|
|
11
|
+
android:pathData="M17.103,14.821L17.181,14.822L17.198,14.822C19.534,14.881 21.403,16.791 21.403,19.131V20.738C21.709,20.669 22.028,20.632 22.356,20.632C22.801,20.632 23.231,20.7 23.636,20.826C24.283,19.264 25.823,18.164 27.624,18.164L27.736,18.165C30.033,18.223 31.88,20.074 31.938,22.362L31.939,22.473V33.581C31.939,35.921 30.07,37.831 27.736,37.89L27.624,37.891C25.823,37.891 24.283,36.791 23.636,35.229C23.231,35.354 22.802,35.423 22.356,35.423C22.028,35.423 21.709,35.386 21.403,35.317V35.69C21.403,38.03 19.534,39.939 17.199,39.999L17.087,40C14.99,40 13.246,38.507 12.855,36.531C12.559,36.604 12.252,36.647 11.936,36.655L11.819,36.657C9.601,36.657 7.778,34.986 7.532,32.842C7.217,32.915 6.888,32.954 6.551,32.954C4.164,32.954 2.236,31.021 2.236,28.645V26.176C2.236,23.799 4.164,21.867 6.551,21.866C6.888,21.866 7.217,21.906 7.532,21.979C7.778,19.834 9.601,18.164 11.819,18.164L11.936,18.166C12.252,18.174 12.559,18.216 12.855,18.289C13.246,16.314 14.99,14.821 17.087,14.821L17.103,14.821Z"
|
|
12
|
+
android:fillColor="#ffffff"/>
|
|
13
|
+
<path
|
|
14
|
+
android:pathData="M29.065,22.473C29.065,21.676 28.418,21.032 27.624,21.032C26.83,21.032 26.184,21.676 26.184,22.473V33.581C26.184,34.379 26.83,35.023 27.624,35.023C28.418,35.023 29.065,34.379 29.065,33.581V22.473ZM22.356,23.501C21.561,23.501 20.915,24.145 20.915,24.942V31.113C20.915,31.91 21.561,32.554 22.356,32.554C23.15,32.554 23.796,31.91 23.796,31.113V24.942C23.796,24.145 23.15,23.501 22.356,23.501ZM5.11,28.645C5.11,29.442 5.756,30.086 6.551,30.086C7.345,30.086 7.991,29.442 7.991,28.645V26.176C7.991,25.379 7.345,24.735 6.551,24.735C5.756,24.735 5.11,25.379 5.11,26.176V28.645ZM10.378,32.347C10.378,33.144 11.025,33.788 11.819,33.788C12.613,33.788 13.26,33.144 13.26,32.347V22.473C13.26,21.676 12.613,21.032 11.819,21.032C11.025,21.032 10.378,21.676 10.378,22.473V32.347ZM18.528,19.131C18.528,18.358 17.922,17.73 17.162,17.691L17.087,17.689C16.293,17.689 15.647,18.333 15.647,19.131V35.69C15.647,36.487 16.293,37.132 17.087,37.132C17.882,37.132 18.528,36.487 18.528,35.69V19.131ZM20.125,22.882C20.68,22.283 21.473,21.907 22.356,21.907C23.238,21.907 24.032,22.283 24.587,22.882V22.473C24.587,20.799 25.945,19.439 27.624,19.439C29.303,19.439 30.662,20.799 30.662,22.473V33.581C30.662,35.256 29.303,36.616 27.624,36.616C25.945,36.616 24.587,35.256 24.587,33.581V33.172C24.032,33.772 23.238,34.148 22.356,34.148C21.473,34.148 20.68,33.772 20.125,33.172V35.69C20.125,37.365 18.766,38.725 17.087,38.725C15.409,38.725 14.05,37.365 14.05,35.69V34.406C13.495,35.006 12.701,35.382 11.819,35.382C10.14,35.382 8.782,34.022 8.782,32.347V30.704C8.227,31.304 7.433,31.679 6.551,31.679C4.872,31.679 3.513,30.319 3.513,28.645V26.176C3.513,24.501 4.872,23.141 6.551,23.141C7.433,23.141 8.227,23.517 8.782,24.117V22.473C8.782,20.799 10.14,19.439 11.819,19.439C12.701,19.439 13.495,19.815 14.05,20.414V19.131C14.05,17.456 15.409,16.096 17.087,16.096L17.166,16.097C18.808,16.138 20.125,17.482 20.125,19.131V22.882Z"
|
|
15
|
+
android:fillColor="#001A72"/>
|
|
16
|
+
</vector>
|
|
@@ -25,45 +25,39 @@ class AudioContextHostObject : public BaseAudioContextHostObject {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
JSI_HOST_FUNCTION(close) {
|
|
28
|
-
auto
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
audioContext->close();
|
|
28
|
+
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
|
|
29
|
+
auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr<Promise>& promise) {
|
|
30
|
+
audioContext->close();
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}).detach();
|
|
32
|
+
promise->resolve([](jsi::Runtime &runtime) {
|
|
33
|
+
return jsi::Value::undefined();
|
|
34
|
+
});
|
|
37
35
|
});
|
|
38
36
|
|
|
39
37
|
return promise;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
JSI_HOST_FUNCTION(resume) {
|
|
43
|
-
auto
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
auto result = audioContext->resume();
|
|
41
|
+
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
|
|
42
|
+
auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr<Promise>& promise) {
|
|
43
|
+
auto result = audioContext->resume();
|
|
47
44
|
|
|
48
|
-
|
|
45
|
+
promise->resolve([result](jsi::Runtime &runtime) {
|
|
49
46
|
return jsi::Value(result);
|
|
50
|
-
|
|
51
|
-
}).detach();
|
|
47
|
+
});
|
|
52
48
|
});
|
|
53
49
|
|
|
54
50
|
return promise;
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
JSI_HOST_FUNCTION(suspend) {
|
|
58
|
-
auto
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
auto result = audioContext->suspend();
|
|
54
|
+
auto audioContext = std::static_pointer_cast<AudioContext>(context_);
|
|
55
|
+
auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr<Promise>& promise) {
|
|
56
|
+
auto result = audioContext->suspend();
|
|
62
57
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}).detach();
|
|
58
|
+
promise->resolve([result](jsi::Runtime &runtime) {
|
|
59
|
+
return jsi::Value(result);
|
|
60
|
+
});
|
|
67
61
|
});
|
|
68
62
|
|
|
69
63
|
return promise;
|