react-native-audio-api 0.8.1-nightly-2c9c6a6-20250903 → 0.8.1-nightly-fc8149e-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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +4 -0
  3. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +10 -1
  4. package/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp +36 -0
  5. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +24 -0
  6. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +4 -4
  7. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +78 -70
  8. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +2 -1
  9. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +3 -11
  10. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +57 -39
  11. package/android/src/main/res/drawable/logo.xml +16 -0
  12. package/common/cpp/audioapi/HostObjects/AudioContextHostObject.h +17 -23
  13. package/common/cpp/audioapi/HostObjects/AudioParamHostObject.h +4 -2
  14. package/common/cpp/audioapi/core/AudioParam.cpp +205 -254
  15. package/common/cpp/audioapi/core/AudioParam.h +98 -21
  16. package/common/cpp/audioapi/core/AudioParamEventQueue.cpp +65 -0
  17. package/common/cpp/audioapi/core/AudioParamEventQueue.h +63 -0
  18. package/common/cpp/audioapi/core/sources/StreamerNode.cpp +6 -5
  19. package/common/cpp/audioapi/core/sources/StreamerNode.h +5 -4
  20. package/common/cpp/audioapi/core/utils/ParamChangeEvent.cpp +2 -39
  21. package/common/cpp/audioapi/core/utils/ParamChangeEvent.h +53 -12
  22. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.cpp +10 -0
  23. package/common/cpp/audioapi/libs/ffmpeg/FFmpegDecoding.h +10 -0
  24. package/common/cpp/audioapi/utils/CrossThreadEventScheduler.hpp +58 -0
  25. package/common/cpp/audioapi/utils/RingBiDirectionalBuffer.hpp +199 -0
  26. package/ios/audioapi/ios/system/AudioEngine.mm +1 -0
  27. package/lib/commonjs/plugin/withAudioAPI.js +1 -1
  28. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -1
  29. package/lib/module/plugin/withAudioAPI.js +1 -1
  30. package/lib/module/plugin/withAudioAPI.js.map +1 -1
  31. package/package.json +1 -1
  32. 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 copyrights are held by respective organizations, check [COPYING](./COPYING) file for further details
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()?.show(nb, isPlaying)
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()?.show(nb, isPlaying)
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()?.hide()
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()?.show(nb, isPlaying)
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.play
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
- @SuppressLint("MissingPermission")
121
+ @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
111
122
  @Synchronized
112
- fun show(
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 hide() {
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 NotificationService : Service() {
186
- private val binder = LocalBinder()
182
+ class AudioForegroundService : Service() {
187
183
  private var notification: Notification? = null
188
-
189
- inner class LocalBinder : Binder() {
190
- private var weakService: WeakReference<NotificationService>? = null
191
-
192
- fun onBind(service: NotificationService) {
193
- weakService = WeakReference(service)
194
- }
195
-
196
- fun getService(): NotificationService? = weakService?.get()
197
- }
198
-
199
- override fun onBind(intent: Intent): IBinder {
200
- binder.onBind(this)
201
- return binder
202
- }
203
-
204
- fun forceForeground() {
205
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
206
- val intent = Intent(this, NotificationService::class.java)
207
- ContextCompat.startForegroundService(this, intent)
208
- notification =
209
- MediaSessionManager.mediaNotificationManager
210
- .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID), false)
211
- startForeground(MediaSessionManager.NOTIFICATION_ID, notification)
212
- }
213
- }
214
-
215
- override fun onCreate() {
216
- super.onCreate()
217
- try {
218
- notification =
219
- MediaSessionManager.mediaNotificationManager
220
- .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID), false)
221
- startForeground(MediaSessionManager.NOTIFICATION_ID, notification)
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
- onCreate()
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
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
239
- stopForeground(STOP_FOREGROUND_REMOVE)
240
- }
241
- stopSelf()
251
+ stopForegroundService()
242
252
  }
243
253
 
244
254
  override fun onDestroy() {
245
- super.onDestroy()
246
-
247
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
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()?.hide()
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
- val reactContext = audioAPIModule.get()?.reactContext?.get()!!
26
- NotificationManagerCompat.from(reactContext).cancel(MediaSessionManager.NOTIFICATION_ID)
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 val connection =
45
- object : ServiceConnection {
46
- override fun onServiceConnected(
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
- val myIntent = Intent(this.reactContext.get(), MediaNotificationManager.NotificationService::class.java)
87
+ fun attachAudioPlayer(player: NativeAudioPlayer): String {
88
+ val uuid = UUID.randomUUID().toString()
89
+ nativeAudioPlayers[uuid] = player
111
90
 
112
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
113
- try {
114
- this.reactContext.get()?.bindService(myIntent, connection, Context.BIND_AUTO_CREATE)
115
- } catch (ignored: Exception) {
116
- ContextCompat.startForegroundService(this.reactContext.get()!!, myIntent)
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
- } else {
119
- this.reactContext.get()?.startService(myIntent)
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 promise = promiseVendor_->createPromise([this](std::shared_ptr<Promise> promise) {
29
- std::thread([this, promise = std::move(promise)]() {
30
- auto audioContext = std::static_pointer_cast<AudioContext>(context_);
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
- promise->resolve([](jsi::Runtime &runtime) {
34
- return jsi::Value::undefined();
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 promise = promiseVendor_->createPromise([this](std::shared_ptr<Promise> promise) {
44
- std::thread([this, promise = std::move(promise)]() {
45
- auto audioContext = std::static_pointer_cast<AudioContext>(context_);
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
- promise->resolve([result](jsi::Runtime &runtime) {
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 promise = promiseVendor_->createPromise([this](std::shared_ptr<Promise> promise) {
59
- std::thread([this, promise = std::move(promise)]() {
60
- auto audioContext = std::static_pointer_cast<AudioContext>(context_);
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
- promise->resolve([result](jsi::Runtime &runtime) {
64
- return jsi::Value(result);
65
- });
66
- }).detach();
58
+ promise->resolve([result](jsi::Runtime &runtime) {
59
+ return jsi::Value(result);
60
+ });
67
61
  });
68
62
 
69
63
  return promise;