From ce01fb409a202bf5bcc444d699a95f73217c2f2f Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Tue, 2 Sep 2025 18:02:46 +0200 Subject: [PATCH 1/8] fix: part of fix for FS --- .../src/examples/AudioFile/AudioFile.tsx | 2 + .../android/app/src/main/AndroidManifest.xml | 2 +- .../audioapi/system/LockScreenManager.kt | 8 +- .../system/MediaNotificationManager.kt | 123 ++++++++---------- .../audioapi/system/MediaReceiver.kt | 3 +- .../audioapi/system/MediaSessionCallback.kt | 14 +- .../audioapi/system/MediaSessionManager.kt | 68 +++++----- .../src/plugin/withAudioAPI.ts | 2 +- 8 files changed, 96 insertions(+), 126 deletions(-) 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..64612cd16 100644 --- a/apps/fabric-example/android/app/src/main/AndroidManifest.xml +++ b/apps/fabric-example/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:allowBackup="false" android:theme="@style/AppTheme" android:supportsRtl="true"> - + ? = 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, + ) + startForeground(MediaSessionManager.NOTIFICATION_ID, notification) + isServiceStarted = true + } catch (ex: Exception) { + Log.w("AudioManagerModule", "Error starting foreground service: ${ex.message}") + stopSelf() + } + } } } @@ -229,26 +200,40 @@ class MediaNotificationManager( flags: Int, startId: Int, ): Int { - onCreate() + val action = intent?.action + + when (action) { + "START_FOREGROUND" -> startForegroundService() + "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..0ffdbebec 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.stopForegroundService() 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..d5767a026 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.stopForegroundService() 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..1c631b0e0 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 @@ -41,31 +37,8 @@ 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() fun initialize( audioAPIModule: WeakReference, @@ -83,8 +56,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) @@ -107,16 +80,33 @@ object MediaSessionManager { 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) + startForegroundService() + } - 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) + fun startForegroundService() { + synchronized(serviceStateLock) { + if (!isServiceRunning) { + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = "START_FOREGROUND" + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.startForegroundService(reactContext.get()!!, intent) + } else { + reactContext.get()?.startService(intent) + } + isServiceRunning = true + } + } + } + + fun stopForegroundService() { + synchronized(serviceStateLock) { + if (isServiceRunning) { + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = "STOP_FOREGROUND" + reactContext.get()?.startService(intent) + isServiceRunning = false } - } else { - this.reactContext.get()?.startService(myIntent) } } 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, }, From 1fdd867b7b1c0d38444cdb2f293579fe793f1791 Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Wed, 3 Sep 2025 15:33:36 +0200 Subject: [PATCH 2/8] fix: final fix for Android 13+ foreground service --- .../cpp/audioapi/android/core/AudioPlayer.cpp | 4 ++ .../cpp/audioapi/android/core/AudioPlayer.h | 12 +++- .../android/core/NativeAudioPlayer.hpp | 36 +++++++++++ .../audioapi/core/NativeAudioPlayer.kt | 24 +++++++ .../audioapi/system/MediaReceiver.kt | 2 +- .../audioapi/system/MediaSessionCallback.kt | 2 +- .../audioapi/system/MediaSessionManager.kt | 64 +++++++++++++------ .../HostObjects/AudioContextHostObject.h | 40 +++++------- 8 files changed, 140 insertions(+), 44 deletions(-) create mode 100644 packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/NativeAudioPlayer.hpp create mode 100644 packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp index 1d2da23b3..e22418c3e 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp @@ -15,6 +15,8 @@ AudioPlayer::AudioPlayer( sampleRate_(sampleRate), channelCount_(channelCount) { isInitialized_ = openAudioStream(); + + nativeAudioPlayer_ = jni::make_global(NativeAudioPlayer::create()); } bool AudioPlayer::openAudioStream() { @@ -47,6 +49,7 @@ bool AudioPlayer::openAudioStream() { bool AudioPlayer::start() { if (mStream_) { + nativeAudioPlayer_->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..721c4d5d5 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,12 +5,15 @@ #include #include +#include + namespace audioapi { using namespace oboe; class AudioContext; class AudioBus; +class NativeAudioPlayer; class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { public: @@ -19,13 +22,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 +52,8 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { int channelCount_; bool openAudioStream(); + + 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..4a9dc1cb7 --- /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.attachSourceNode(this) + MediaSessionManager.startForegroundServiceIfNecessary() + } + + @DoNotStrip + fun stop() { + this.sourceNodeId?.let { + MediaSessionManager.detachSourceNode(it) + this.sourceNodeId = null + } + MediaSessionManager.stopForegroundServiceIfNecessary() + } +} 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 0ffdbebec..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 @@ -26,7 +26,7 @@ class MediaReceiver( if (!checkApp(intent)) return mediaNotificationManager.get()?.cancelNotification() - MediaSessionManager.stopForegroundService() + 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 d5767a026..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 @@ -20,7 +20,7 @@ class MediaSessionCallback( override fun onStop() { mediaNotificationManager.get()?.cancelNotification() - MediaSessionManager.stopForegroundService() + 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 1c631b0e0..ff3627323 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 @@ -20,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 @@ -39,6 +41,7 @@ object MediaSessionManager { private var isServiceRunning = false private val serviceStateLock = Any() + private val nativeAudioPlayers = mutableMapOf() fun initialize( audioAPIModule: WeakReference, @@ -79,34 +82,59 @@ object MediaSessionManager { this.audioFocusListener = AudioFocusListener(WeakReference(this.audioManager), this.audioAPIModule, WeakReference(this.lockScreenManager)) this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule) + } + + fun attachSourceNode(player: NativeAudioPlayer): String { + val uuid = UUID.randomUUID().toString() + nativeAudioPlayers[uuid] = player + + return uuid + } + + fun detachSourceNode(uuid: String) { + nativeAudioPlayers.remove(uuid) + } + + fun startForegroundServiceIfNecessary() { + if (nativeAudioPlayers.isNotEmpty()) { + startForegroundService() + } + } - startForegroundService() + fun stopForegroundServiceIfNecessary() { + if (nativeAudioPlayers.isEmpty()) { + stopForegroundService() + } } - fun startForegroundService() { + private fun startForegroundService() { synchronized(serviceStateLock) { - if (!isServiceRunning) { - val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = "START_FOREGROUND" - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ContextCompat.startForegroundService(reactContext.get()!!, intent) - } else { - reactContext.get()?.startService(intent) - } - isServiceRunning = true + if (isServiceRunning || reactContext.get() == null) { + return } + + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = "START_FOREGROUND" + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.startForegroundService(reactContext.get()!!, intent) + } else { + reactContext.get()!!.startService(intent) + } + isServiceRunning = true } } - fun stopForegroundService() { + private fun stopForegroundService() { synchronized(serviceStateLock) { - if (isServiceRunning) { - val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = "STOP_FOREGROUND" - reactContext.get()?.startService(intent) - isServiceRunning = false + if (!isServiceRunning || reactContext.get() == null) { + return } + + val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) + intent.action = "STOP_FOREGROUND" + reactContext.get()!!.startService(intent) + isServiceRunning = false } } 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..9f01b18e2 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 promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { + auto audioContext = std::static_pointer_cast(context_); + 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 promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { + auto audioContext = std::static_pointer_cast(context_); + 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 promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { + auto audioContext = std::static_pointer_cast(context_); + 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; From a37488f9221159ca802635795b7db7b011190c85 Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Wed, 3 Sep 2025 17:21:36 +0200 Subject: [PATCH 3/8] fix: renaming --- .../java/com/swmansion/audioapi/core/NativeAudioPlayer.kt | 4 ++-- .../java/com/swmansion/audioapi/system/MediaSessionManager.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 index 4a9dc1cb7..ee177c337 100644 --- 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 @@ -9,14 +9,14 @@ class NativeAudioPlayer { @DoNotStrip fun start() { - this.sourceNodeId = MediaSessionManager.attachSourceNode(this) + this.sourceNodeId = MediaSessionManager.attachAudioPlayer(this) MediaSessionManager.startForegroundServiceIfNecessary() } @DoNotStrip fun stop() { this.sourceNodeId?.let { - MediaSessionManager.detachSourceNode(it) + MediaSessionManager.detachAudioPlayer(it) this.sourceNodeId = null } MediaSessionManager.stopForegroundServiceIfNecessary() 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 ff3627323..c570143a5 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 @@ -84,14 +84,14 @@ object MediaSessionManager { this.volumeChangeListener = VolumeChangeListener(WeakReference(this.audioManager), this.audioAPIModule) } - fun attachSourceNode(player: NativeAudioPlayer): String { + fun attachAudioPlayer(player: NativeAudioPlayer): String { val uuid = UUID.randomUUID().toString() nativeAudioPlayers[uuid] = player return uuid } - fun detachSourceNode(uuid: String) { + fun detachAudioPlayer(uuid: String) { nativeAudioPlayers.remove(uuid) } From 26491cca12e7bed7f711f95ea39a748d78ebf6d6 Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Thu, 4 Sep 2025 10:28:12 +0200 Subject: [PATCH 4/8] fix: applied requested changes --- .../system/MediaNotificationManager.kt | 19 +++++++++++++++---- .../audioapi/system/MediaSessionManager.kt | 4 ++-- .../HostObjects/AudioContextHostObject.h | 12 ++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) 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 9fdd88aad..4fa295846 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 @@ -40,6 +40,17 @@ 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? { + return entries.firstOrNull { it.name == action } + } + } + } + @SuppressLint("RestrictedApi") @Synchronized fun prepareNotification( @@ -188,7 +199,7 @@ class MediaNotificationManager( startForeground(MediaSessionManager.NOTIFICATION_ID, notification) isServiceStarted = true } catch (ex: Exception) { - Log.w("AudioManagerModule", "Error starting foreground service: ${ex.message}") + Log.e("AudioManagerModule", "Error starting foreground service: ${ex.message}") stopSelf() } } @@ -200,11 +211,11 @@ class MediaNotificationManager( flags: Int, startId: Int, ): Int { - val action = intent?.action + val action = ForegroundAction.fromAction(intent?.action) when (action) { - "START_FOREGROUND" -> startForegroundService() - "STOP_FOREGROUND" -> stopForegroundService() + ForegroundAction.START_FOREGROUND -> startForegroundService() + ForegroundAction.STOP_FOREGROUND -> stopForegroundService() else -> startForegroundService() } 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 c570143a5..fd026e1d2 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 @@ -114,7 +114,7 @@ object MediaSessionManager { } val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = "START_FOREGROUND" + intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.action if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(reactContext.get()!!, intent) @@ -132,7 +132,7 @@ object MediaSessionManager { } val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = "STOP_FOREGROUND" + intent.action = MediaNotificationManager.ForegroundAction.STOP_FOREGROUND.action reactContext.get()!!.startService(intent) isServiceRunning = false } 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 9f01b18e2..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,8 +25,8 @@ class AudioContextHostObject : public BaseAudioContextHostObject { } JSI_HOST_FUNCTION(close) { - auto promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { - auto audioContext = std::static_pointer_cast(context_); + auto audioContext = std::static_pointer_cast(context_); + auto promise = promiseVendor_->createPromise([audioContext](const std::shared_ptr& promise) { audioContext->close(); promise->resolve([](jsi::Runtime &runtime) { @@ -38,8 +38,8 @@ class AudioContextHostObject : public BaseAudioContextHostObject { } JSI_HOST_FUNCTION(resume) { - auto promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { - auto audioContext = std::static_pointer_cast(context_); + 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) { @@ -51,8 +51,8 @@ class AudioContextHostObject : public BaseAudioContextHostObject { } JSI_HOST_FUNCTION(suspend) { - auto promise = promiseVendor_->createPromise([this](const std::shared_ptr& promise) { - auto audioContext = std::static_pointer_cast(context_); + 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) { From 6d0263903f3ac1fb8e22c5c18634ce3cae3d442c Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Thu, 4 Sep 2025 10:29:12 +0200 Subject: [PATCH 5/8] ci: yarn lint --- .../audioapi/system/MediaNotificationManager.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 4fa295846..f585b2f86 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 @@ -40,14 +40,13 @@ class MediaNotificationManager( const val MEDIA_BUTTON: String = "audio_manager_media_button" } - enum class ForegroundAction() { + enum class ForegroundAction { START_FOREGROUND, - STOP_FOREGROUND; + STOP_FOREGROUND, + ; companion object { - fun fromAction(action: String?): ForegroundAction? { - return entries.firstOrNull { it.name == action } - } + fun fromAction(action: String?): ForegroundAction? = entries.firstOrNull { it.name == action } } } From 81ff17c297573ee6c6b7cb15cf80d24821b9e6de Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Thu, 4 Sep 2025 10:42:52 +0200 Subject: [PATCH 6/8] fix: fixed type and updated notification icon --- .../audioapi/system/MediaNotificationManager.kt | 2 +- .../audioapi/system/MediaSessionManager.kt | 4 ++-- .../android/src/main/res/drawable/logo.xml | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 packages/react-native-audio-api/android/src/main/res/drawable/logo.xml 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 f585b2f86..8353657b9 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 @@ -23,7 +23,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 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 fd026e1d2..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 @@ -114,7 +114,7 @@ object MediaSessionManager { } val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.action + intent.action = MediaNotificationManager.ForegroundAction.START_FOREGROUND.name if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(reactContext.get()!!, intent) @@ -132,7 +132,7 @@ object MediaSessionManager { } val intent = Intent(reactContext.get(), MediaNotificationManager.AudioForegroundService::class.java) - intent.action = MediaNotificationManager.ForegroundAction.STOP_FOREGROUND.action + 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 @@ + + + + + From 434ac8218c7f14e5baf7d0f18b4b5bc6b72b0c59 Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Thu, 4 Sep 2025 13:45:07 +0200 Subject: [PATCH 7/8] refactor: small improvements --- .../android/app/src/main/AndroidManifest.xml | 3 +-- .../audioapi/system/MediaNotificationManager.kt | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/fabric-example/android/app/src/main/AndroidManifest.xml b/apps/fabric-example/android/app/src/main/AndroidManifest.xml index 64612cd16..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 @@ - - + = 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}") From ac3af67e80a8ab29b20b5e77626cbbc0fc82c524 Mon Sep 17 00:00:00 2001 From: maciejmakowski2003 Date: Thu, 4 Sep 2025 13:57:40 +0200 Subject: [PATCH 8/8] fix: removed forward declaration --- .../android/src/main/cpp/audioapi/android/core/AudioPlayer.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 721c4d5d5..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 @@ -13,7 +13,6 @@ using namespace oboe; class AudioContext; class AudioBus; -class NativeAudioPlayer; class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { public: @@ -53,7 +52,7 @@ class AudioPlayer : public AudioStreamDataCallback, AudioStreamErrorCallback { bool openAudioStream(); - jni::global_ref nativeAudioPlayer_; + facebook::jni::global_ref nativeAudioPlayer_; }; } // namespace audioapi