diff --git a/apps/common-app/src/examples/AudioFile/AudioFile.tsx b/apps/common-app/src/examples/AudioFile/AudioFile.tsx index ceee2d6c3..099f6ed91 100644 --- a/apps/common-app/src/examples/AudioFile/AudioFile.tsx +++ b/apps/common-app/src/examples/AudioFile/AudioFile.tsx @@ -59,6 +59,7 @@ const AudioFile: FC = () => { 'remotePlay', () => { AudioPlayer.play(); + setIsPlaying(true); } ); @@ -66,6 +67,7 @@ const AudioFile: FC = () => { 'remotePause', () => { AudioPlayer.pause(); + setIsPlaying(false); } ); diff --git a/apps/fabric-example/android/app/src/main/AndroidManifest.xml b/apps/fabric-example/android/app/src/main/AndroidManifest.xml index 4d01862b1..444e9f223 100644 --- a/apps/fabric-example/android/app/src/main/AndroidManifest.xml +++ b/apps/fabric-example/android/app/src/main/AndroidManifest.xml @@ -5,7 +5,6 @@ - - + start(); auto result = mStream_->requestStart(); return result == oboe::Result::OK; } @@ -56,6 +59,7 @@ bool AudioPlayer::start() { void AudioPlayer::stop() { if (mStream_) { + nativeAudioPlayer_->stop(); mStream_->requestStop(); } } diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h index 1a797877f..5283a4316 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.h @@ -5,6 +5,8 @@ #include #include +#include + namespace audioapi { using namespace oboe; @@ -19,13 +21,18 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { float sampleRate, int channelCount); + ~AudioPlayer() override { + nativeAudioPlayer_.release(); + cleanup(); + } + bool start(); void stop(); bool resume(); void suspend(); void cleanup(); - bool isRunning() const; + [[nodiscard]] bool isRunning() const; DataCallbackResult onAudioReady( AudioStream *oboeStream, @@ -44,6 +51,8 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { int channelCount_; bool openAudioStream(); + + facebook::jni::global_ref nativeAudioPlayer_; }; } // namespace audioapi diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp new file mode 100644 index 000000000..54d3b490b --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp @@ -0,0 +1,36 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include + +namespace audioapi { + +using namespace facebook; +using namespace react; + +class NativeAudioPlayer : public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/swmansion/audioapi/core/NativeAudioPlayer;"; + + static jni::local_ref create() { + return newInstance(); + } + + void start() { + static const auto method = javaClassStatic()->getMethod("start"); + method(self()); + } + + void stop() { + static const auto method = javaClassStatic()->getMethod("stop"); + method(self()); + } +}; + +} // namespace audioapi diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt new file mode 100644 index 000000000..ee177c337 --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt @@ -0,0 +1,24 @@ +package com.swmansion.audioapi.core + +import com.facebook.common.internal.DoNotStrip +import com.swmansion.audioapi.system.MediaSessionManager + +@DoNotStrip +class NativeAudioPlayer { + private var sourceNodeId: String? = null + + @DoNotStrip + fun start() { + this.sourceNodeId = MediaSessionManager.attachAudioPlayer(this) + MediaSessionManager.startForegroundServiceIfNecessary() + } + + @DoNotStrip + fun stop() { + this.sourceNodeId?.let { + MediaSessionManager.detachAudioPlayer(it) + this.sourceNodeId = null + } + MediaSessionManager.stopForegroundServiceIfNecessary() + } +} diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt index 6d3bcd578..be1b20629 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt @@ -122,7 +122,7 @@ class LockScreenManager( ) nb.setLargeIcon(bitmap) - mediaNotificationManager.get()?.show(nb, isPlaying) + mediaNotificationManager.get()?.updateNotification(nb, isPlaying) artworkThread = null } catch (ex: Exception) { @@ -167,14 +167,14 @@ class LockScreenManager( mediaSession.get()?.setMetadata(md.build()) mediaSession.get()?.setActive(true) - mediaNotificationManager.get()?.show(nb, isPlaying) + mediaNotificationManager.get()?.updateNotification(nb, isPlaying) } fun resetLockScreenInfo() { if (artworkThread != null && artworkThread!!.isAlive) artworkThread!!.interrupt() artworkThread = null - mediaNotificationManager.get()?.hide() + mediaNotificationManager.get()?.cancelNotification() mediaSession.get()?.setActive(false) } @@ -235,7 +235,7 @@ class LockScreenManager( updateNotificationMediaStyle() if (mediaSession.get()?.isActive == true) { - mediaNotificationManager.get()?.show(nb, isPlaying) + mediaNotificationManager.get()?.updateNotification(nb, isPlaying) } } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt index 41b770717..5c046338b 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt @@ -1,21 +1,22 @@ package com.swmansion.audioapi.system +import android.Manifest import android.annotation.SuppressLint import android.app.Notification import android.app.PendingIntent import android.app.Service import android.content.Intent +import android.content.pm.ServiceInfo import android.content.res.Resources -import android.os.Binder import android.os.Build import android.os.IBinder import android.provider.ContactsContract import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import android.view.KeyEvent +import androidx.annotation.RequiresPermission import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.ContextCompat import com.facebook.react.bridge.ReactApplicationContext import com.swmansion.audioapi.R import java.lang.ref.WeakReference @@ -23,7 +24,7 @@ import java.lang.ref.WeakReference class MediaNotificationManager( private val reactContext: WeakReference, ) { - private var smallIcon: Int = R.drawable.play + private var smallIcon: Int = R.drawable.logo private var customIcon: Int = 0 private var play: NotificationCompat.Action? = null @@ -40,6 +41,16 @@ class MediaNotificationManager( const val MEDIA_BUTTON: String = "audio_manager_media_button" } + enum class ForegroundAction { + START_FOREGROUND, + STOP_FOREGROUND, + ; + + companion object { + fun fromAction(action: String?): ForegroundAction? = entries.firstOrNull { it.name == action } + } + } + @SuppressLint("RestrictedApi") @Synchronized fun prepareNotification( @@ -107,34 +118,20 @@ class MediaNotificationManager( return builder.build() } - @SuppressLint("MissingPermission") + @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS) @Synchronized - fun show( + fun updateNotification( builder: NotificationCompat.Builder?, isPlaying: Boolean, ) { NotificationManagerCompat.from(reactContext.get()!!).notify( MediaSessionManager.NOTIFICATION_ID, - prepareNotification( - builder!!, - isPlaying, - ), + prepareNotification(builder!!, isPlaying), ) } - fun hide() { + fun cancelNotification() { NotificationManagerCompat.from(reactContext.get()!!).cancel(MediaSessionManager.NOTIFICATION_ID) - - try { - val myIntent = - Intent( - reactContext.get(), - NotificationService::class.java, - ) - reactContext.get()?.stopService(myIntent) - } catch (e: java.lang.Exception) { - Log.w("AudioManagerModule", "Error stopping service: ${e.message}") - } } @Synchronized @@ -182,45 +179,42 @@ class MediaNotificationManager( return NotificationCompat.Action(icon!!, title, i) } - class NotificationService : Service() { - private val binder = LocalBinder() + class AudioForegroundService : Service() { private var notification: Notification? = null - - inner class LocalBinder : Binder() { - private var weakService: WeakReference? = null - - fun onBind(service: NotificationService) { - weakService = WeakReference(service) - } - - fun getService(): NotificationService? = weakService?.get() - } - - override fun onBind(intent: Intent): IBinder { - binder.onBind(this) - return binder - } - - fun forceForeground() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val intent = Intent(this, NotificationService::class.java) - ContextCompat.startForegroundService(this, intent) - notification = - MediaSessionManager.mediaNotificationManager - .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID), false) - startForeground(MediaSessionManager.NOTIFICATION_ID, notification) - } - } - - override fun onCreate() { - super.onCreate() - try { - notification = - MediaSessionManager.mediaNotificationManager - .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID), false) - startForeground(MediaSessionManager.NOTIFICATION_ID, notification) - } catch (ex: Exception) { - Log.w("AudioManagerModule", "Error starting service: ${ex.message}") + private var isServiceStarted = false + private val serviceLock = Any() + + override fun onBind(intent: Intent): IBinder? = null + + private fun startForegroundService() { + synchronized(serviceLock) { + if (!isServiceStarted) { + try { + notification = + MediaSessionManager.mediaNotificationManager + .prepareNotification( + NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID), + false, + ) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground( + MediaSessionManager.NOTIFICATION_ID, + notification!!, + ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST, + ) + } else { + startForeground( + MediaSessionManager.NOTIFICATION_ID, + notification, + ) + } + isServiceStarted = true + } catch (ex: Exception) { + Log.e("AudioManagerModule", "Error starting foreground service: ${ex.message}") + stopSelf() + } + } } } @@ -229,26 +223,40 @@ class MediaNotificationManager( flags: Int, startId: Int, ): Int { - onCreate() + val action = ForegroundAction.fromAction(intent?.action) + + when (action) { + ForegroundAction.START_FOREGROUND -> startForegroundService() + ForegroundAction.STOP_FOREGROUND -> stopForegroundService() + else -> startForegroundService() + } + return START_NOT_STICKY } + private fun stopForegroundService() { + synchronized(serviceLock) { + if (isServiceStarted) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + stopForeground(STOP_FOREGROUND_REMOVE) + } + isServiceStarted = false + stopSelf() + } + } + } + override fun onTaskRemoved(rootIntent: Intent?) { super.onTaskRemoved(rootIntent) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_REMOVE) - } - stopSelf() + stopForegroundService() } override fun onDestroy() { - super.onDestroy() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_REMOVE) + synchronized(serviceLock) { + notification = null + isServiceStarted = false } - - stopSelf() + super.onDestroy() } } } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt index c877d0b45..5832c56aa 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt @@ -25,7 +25,8 @@ class MediaReceiver( if (MediaNotificationManager.REMOVE_NOTIFICATION == action) { if (!checkApp(intent)) return - mediaNotificationManager.get()?.hide() + mediaNotificationManager.get()?.cancelNotification() + MediaSessionManager.stopForegroundServiceIfNecessary() mediaSession.get()?.isActive = false audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("closeNotification", mapOf()) // add to ts events diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt index 46bfe6b59..fc5a49466 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt @@ -1,17 +1,14 @@ package com.swmansion.audioapi.system -import android.content.Intent -import android.os.Build import android.os.Bundle import android.support.v4.media.session.MediaSessionCompat import android.util.Log -import androidx.core.app.NotificationManagerCompat import com.swmansion.audioapi.AudioAPIModule import java.lang.ref.WeakReference -import java.util.HashMap class MediaSessionCallback( private val audioAPIModule: WeakReference, + private val mediaNotificationManager: WeakReference, ) : MediaSessionCompat.Callback() { override fun onPlay() { audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remotePlay", mapOf()) @@ -22,13 +19,8 @@ class MediaSessionCallback( } override fun onStop() { - val reactContext = audioAPIModule.get()?.reactContext?.get()!! - NotificationManagerCompat.from(reactContext).cancel(MediaSessionManager.NOTIFICATION_ID) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val myIntent = - Intent(reactContext, MediaNotificationManager.NotificationService::class.java) - reactContext.stopService(myIntent) - } + mediaNotificationManager.get()?.cancelNotification() + MediaSessionManager.stopForegroundServiceIfNecessary() audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("remoteStop", mapOf()) } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt index d50cbd054..7ec82d0b2 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt @@ -3,18 +3,14 @@ package com.swmansion.audioapi.system import android.Manifest import android.app.NotificationChannel import android.app.NotificationManager -import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.ServiceConnection import android.content.pm.PackageManager import android.media.AudioDeviceInfo import android.media.AudioManager import android.os.Build -import android.os.IBinder import android.support.v4.media.session.MediaSessionCompat -import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat @@ -24,8 +20,10 @@ import com.facebook.react.bridge.ReadableMap import com.facebook.react.modules.core.PermissionAwareActivity import com.facebook.react.modules.core.PermissionListener import com.swmansion.audioapi.AudioAPIModule +import com.swmansion.audioapi.core.NativeAudioPlayer import com.swmansion.audioapi.system.PermissionRequestListener.Companion.RECORDING_REQUEST_CODE import java.lang.ref.WeakReference +import java.util.UUID object MediaSessionManager { private lateinit var audioAPIModule: WeakReference @@ -41,31 +39,9 @@ object MediaSessionManager { private lateinit var volumeChangeListener: VolumeChangeListener private lateinit var mediaReceiver: MediaReceiver - private val connection = - object : ServiceConnection { - override fun onServiceConnected( - name: ComponentName, - service: IBinder, - ) { - Log.w("MediaSessionManager", "onServiceConnected") - val binder = service as MediaNotificationManager.NotificationService.LocalBinder - val notificationService = binder.getService() - notificationService?.forceForeground() - reactContext.get()?.unbindService(this) - } - - override fun onServiceDisconnected(name: ComponentName) { - Log.w("MediaSessionManager", "Service is disconnected.") - } - - override fun onBindingDied(name: ComponentName) { - Log.w("MediaSessionManager", "Binding has died.") - } - - override fun onNullBinding(name: ComponentName) { - Log.w("MediaSessionManager", "Bind was null.") - } - } + private var isServiceRunning = false + private val serviceStateLock = Any() + private val nativeAudioPlayers = mutableMapOf() fun initialize( audioAPIModule: WeakReference, @@ -83,8 +59,8 @@ object MediaSessionManager { this.mediaNotificationManager = MediaNotificationManager(this.reactContext) this.lockScreenManager = LockScreenManager(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager)) this.mediaReceiver = - MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(mediaNotificationManager), this.audioAPIModule) - this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule)) + MediaReceiver(this.reactContext, WeakReference(this.mediaSession), WeakReference(this.mediaNotificationManager), this.audioAPIModule) + this.mediaSession.setCallback(MediaSessionCallback(this.audioAPIModule, WeakReference(this.mediaNotificationManager))) val filter = IntentFilter() filter.addAction(MediaNotificationManager.REMOVE_NOTIFICATION) @@ -106,17 +82,59 @@ object MediaSessionManager { this.audioFocusListener = AudioFocusListener(WeakReference(this.audioManager), this.audioAPIModule, WeakReference(this.lockScreenManager)) this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule) + } - val myIntent = Intent(this.reactContext.get(), MediaNotificationManager.NotificationService::class.java) + fun attachAudioPlayer(player: NativeAudioPlayer): String { + val uuid = UUID.randomUUID().toString() + nativeAudioPlayers[uuid] = player - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - try { - this.reactContext.get()?.bindService(myIntent, connection, Context.BIND_AUTO_CREATE) - } catch (ignored: Exception) { - ContextCompat.startForegroundService(this.reactContext.get()!!, myIntent) + return uuid + } + + fun detachAudioPlayer(uuid: String) { + nativeAudioPlayers.remove(uuid) + } + + fun startForegroundServiceIfNecessary() { + if (nativeAudioPlayers.isNotEmpty()) { + startForegroundService() + } + } + + fun stopForegroundServiceIfNecessary() { + if (nativeAudioPlayers.isEmpty()) { + stopForegroundService() + } + } + + private fun startForegroundService() { + synchronized(serviceStateLock) { + if (isServiceRunning || reactContext.get() == null) { + return } - } else { - this.reactContext.get()?.startService(myIntent) + + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.name + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.startForegroundService(reactContext.get()!!, intent) + } else { + reactContext.get()!!.startService(intent) + } + isServiceRunning = true + } + } + + private fun stopForegroundService() { + synchronized(serviceStateLock) { + if (!isServiceRunning || reactContext.get() == null) { + return + } + + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = MediaNotificationManager.ForegroundAction.STOP_FOREGROUND.name + reactContext.get()!!.startService(intent) + isServiceRunning = false } } diff --git a/packages/react-native-audio-api/android/src/main/res/drawable/logo.xml b/packages/react-native-audio-api/android/src/main/res/drawable/logo.xml new file mode 100644 index 000000000..6eb728c9b --- /dev/null +++ b/packages/react-native-audio-api/android/src/main/res/drawable/logo.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioContextHostObject.h b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioContextHostObject.h index 5af7b366c..8d581a40e 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioContextHostObject.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/HostObjects/AudioContextHostObject.h @@ -25,45 +25,39 @@ class AudioContextHostObject : public BaseAudioContextHostObject { } JSI_HOST_FUNCTION(close) { - auto promise = promiseVendor_->createPromise([this](std::shared_ptr promise) { - std::thread([this, promise = std::move(promise)]() { - auto audioContext = std::static_pointer_cast(context_); - audioContext->close(); + auto audioContext = std::static_pointer_cast(context_); + auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr& promise) { + audioContext->close(); - promise->resolve([](jsi::Runtime &runtime) { - return jsi::Value::undefined(); - }); - }).detach(); + promise->resolve([](jsi::Runtime &runtime) { + return jsi::Value::undefined(); + }); }); return promise; } JSI_HOST_FUNCTION(resume) { - auto promise = promiseVendor_->createPromise([this](std::shared_ptr promise) { - std::thread([this, promise = std::move(promise)]() { - auto audioContext = std::static_pointer_cast(context_); - auto result = audioContext->resume(); + auto audioContext = std::static_pointer_cast(context_); + auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr& promise) { + auto result = audioContext->resume(); - promise->resolve([result](jsi::Runtime &runtime) { + promise->resolve([result](jsi::Runtime &runtime) { return jsi::Value(result); - }); - }).detach(); + }); }); return promise; } JSI_HOST_FUNCTION(suspend) { - auto promise = promiseVendor_->createPromise([this](std::shared_ptr promise) { - std::thread([this, promise = std::move(promise)]() { - auto audioContext = std::static_pointer_cast(context_); - auto result = audioContext->suspend(); + auto audioContext = std::static_pointer_cast(context_); + auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr& promise) { + auto result = audioContext->suspend(); - promise->resolve([result](jsi::Runtime &runtime) { - return jsi::Value(result); - }); - }).detach(); + promise->resolve([result](jsi::Runtime &runtime) { + return jsi::Value(result); + }); }); return promise; diff --git a/packages/react-native-audio-api/src/plugin/withAudioAPI.ts b/packages/react-native-audio-api/src/plugin/withAudioAPI.ts index e025283bb..973393f22 100644 --- a/packages/react-native-audio-api/src/plugin/withAudioAPI.ts +++ b/packages/react-native-audio-api/src/plugin/withAudioAPI.ts @@ -71,7 +71,7 @@ const withForegroundService: ConfigPlugin = ( const serviceElement = { $: { 'android:name': - 'com.swmansion.audioapi.system.MediaNotificationManager$NotificationService', + 'com.swmansion.audioapi.system.MediaNotificationManager$AudioForegroundService', 'android:stopWithTask': 'true', 'android:foregroundServiceType': SFTypes, },