diff --git a/apps/common-app/src/examples/Worklets/Worklets.tsx b/apps/common-app/src/examples/Worklets/Worklets.tsx index bd3cab830..9e142df36 100644 --- a/apps/common-app/src/examples/Worklets/Worklets.tsx +++ b/apps/common-app/src/examples/Worklets/Worklets.tsx @@ -32,11 +32,19 @@ function Worklets() { const bar4 = useSharedValue(0); useEffect(() => { + if (!aCtxRef.current) { + aCtxRef.current = new AudioContext({ sampleRate: SAMPLE_RATE }); + } + AudioManager.setAudioSessionOptions({ iosCategory: 'playAndRecord', iosMode: 'spokenAudio', iosOptions: ['defaultToSpeaker', 'allowBluetoothA2DP'], }); + + return () => { + aCtxRef.current?.close(); + }; }, []); const start = () => { @@ -45,7 +53,7 @@ function Worklets() { inputAudioData: Array, outputAudioData: Array, framesToProcess: number, - currentTime: number + _currentTime: number ) => { 'worklet'; const gain = 0.5; @@ -62,7 +70,7 @@ function Worklets() { audioData: Array, framesToProcess: number, currentTime: number, - startOffset: number + _startOffset: number ) => { 'worklet'; const frequency = 440; // A4 note @@ -70,20 +78,24 @@ function Worklets() { const modulationFreq = 2; // 2 Hz modulation const modulationDepth = 0.8; - const amplitudeModulation = Math.sin(2 * Math.PI * modulationFreq * currentTime); - const dynamicAmplitude = baseAmplitude * (1 + modulationDepth * amplitudeModulation); + const amplitudeModulation = Math.sin( + 2 * Math.PI * modulationFreq * currentTime + ); + const dynamicAmplitude = + baseAmplitude * (1 + modulationDepth * amplitudeModulation); for (let channel = 0; channel < audioData.length; channel++) { for (let sample = 0; sample < framesToProcess; sample++) { // Calculate phase based on sample position and time - const phase = 2 * Math.PI * frequency * (currentTime + sample / SAMPLE_RATE); + const phase = + 2 * Math.PI * frequency * (currentTime + sample / SAMPLE_RATE); audioData[channel][sample] = dynamicAmplitude * Math.sin(phase); } } }; const worklet = ( audioData: Array, - inputChannelCount: number + _inputChannelCount: number ) => { 'worklet'; @@ -106,7 +118,10 @@ function Worklets() { bar2.value = withSpring(scaledAmplitude, { damping: 15, stiffness: 200 }); }; - aCtxRef.current = new AudioContext({ sampleRate: SAMPLE_RATE }); + if (!aCtxRef.current) { + aCtxRef.current = new AudioContext({ sampleRate: SAMPLE_RATE }); + } + workletSourceNodeRef.current = aCtxRef.current.createWorkletSourceNode( sourceWorklet, 'AudioRuntime' diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp index 092c1b3a4..4781792f4 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.cpp @@ -51,6 +51,7 @@ void AudioAPIModule::registerNatives() { makeNativeMethod( "invokeHandlerWithEventNameAndEventBody", AudioAPIModule::invokeHandlerWithEventNameAndEventBody), + makeNativeMethod("closeAllContexts", AudioAPIModule::closeAllContexts), }); } @@ -101,4 +102,8 @@ void AudioAPIModule::invokeHandlerWithEventNameAndEventBody( eventName->toStdString(), body); } } + +void AudioAPIModule::closeAllContexts() { + AudioAPIModuleInstaller::closeAllContexts(); +} } // namespace audioapi diff --git a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.h b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.h index 2504b37a8..886acd211 100644 --- a/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.h +++ b/packages/react-native-audio-api/android/src/main/cpp/audioapi/android/AudioAPIModule.h @@ -34,6 +34,7 @@ class AudioAPIModule : public jni::HybridClass { void injectJSIBindings(); void invokeHandlerWithEventNameAndEventBody(jni::alias_ref eventName, jni::alias_ref> eventBody); + void closeAllContexts(); private: friend HybridBase; 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 4ba4d79c6..3f1e7513a 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 @@ -4,6 +4,7 @@ #include #include #include +#include namespace audioapi { @@ -49,7 +50,8 @@ bool AudioPlayer::openAudioStream() { bool AudioPlayer::start() { if (mStream_) { - nativeAudioPlayer_->start(); + jni::ThreadScope::WithClassLoader( + [this]() { nativeAudioPlayer_->start(); }); auto result = mStream_->requestStart(); return result == oboe::Result::OK; } @@ -59,7 +61,7 @@ bool AudioPlayer::start() { void AudioPlayer::stop() { if (mStream_) { - nativeAudioPlayer_->stop(); + jni::ThreadScope::WithClassLoader([this]() { nativeAudioPlayer_->stop(); }); mStream_->requestStop(); } } diff --git a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt index 265a68784..9c9ee2c64 100644 --- a/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +++ b/packages/react-native-audio-api/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt @@ -1,6 +1,8 @@ package com.swmansion.audioapi import com.facebook.jni.HybridData +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray @@ -16,7 +18,8 @@ import java.lang.ref.WeakReference @ReactModule(name = AudioAPIModule.NAME) class AudioAPIModule( reactContext: ReactApplicationContext, -) : NativeAudioAPIModuleSpec(reactContext) { +) : NativeAudioAPIModuleSpec(reactContext), + LifecycleEventListener { companion object { const val NAME = NativeAudioAPIModuleSpec.NAME } @@ -24,6 +27,7 @@ class AudioAPIModule( val reactContext: WeakReference = WeakReference(reactContext) private val mHybridData: HybridData + private var reanimatedModule: NativeModule? = null external fun initHybrid( workletsModule: Any?, @@ -38,6 +42,8 @@ class AudioAPIModule( eventBody: Map, ) + private external fun closeAllContexts() + init { try { System.loadLibrary("react-native-audio-api") @@ -47,6 +53,8 @@ class AudioAPIModule( if (BuildConfig.RN_AUDIO_API_ENABLE_WORKLETS) { try { workletsModule = reactContext.getNativeModule("WorkletsModule") + reanimatedModule = reactContext.getNativeModule("ReanimatedModule") + reanimatedModule } catch (ex: Exception) { throw RuntimeException("WorkletsModule not found - make sure react-native-worklets is properly installed") } @@ -64,6 +72,26 @@ class AudioAPIModule( return true } + override fun onHostResume() { + // do nothing + } + + override fun onHostPause() { + closeAllContexts() + } + + override fun onHostDestroy() { + // do nothing + } + + override fun initialize() { + reactContext.get()?.addLifecycleEventListener(this) + } + + override fun invalidate() { + // think about cleaning up resources, singletons etc. + } + override fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate() override fun setAudioSessionActivity( diff --git a/packages/react-native-audio-api/common/cpp/audioapi/AudioAPIModuleInstaller.h b/packages/react-native-audio-api/common/cpp/audioapi/AudioAPIModuleInstaller.h index e3f3856a0..73778ce14 100644 --- a/packages/react-native-audio-api/common/cpp/audioapi/AudioAPIModuleInstaller.h +++ b/packages/react-native-audio-api/common/cpp/audioapi/AudioAPIModuleInstaller.h @@ -16,12 +16,16 @@ #include #include +#include namespace audioapi { using namespace facebook; class AudioAPIModuleInstaller { + private: + inline static std::vector> contexts_ = {}; + public: static void injectJSIBindings( jsi::Runtime *jsiRuntime, @@ -61,6 +65,19 @@ class AudioAPIModuleInstaller { *jsiRuntime, audioEventHandlerRegistryHostObject)); } + static void closeAllContexts() { + for (auto it = contexts_.begin(); it != contexts_.end(); ++it) { + auto weakContext = *it; + + if (auto context = weakContext.lock()) { + context->close(); + } + + it = contexts_.erase(it); + --it; + } + } + private: static jsi::Function getCreateAudioContextFunction( jsi::Runtime *jsiRuntime, @@ -95,6 +112,8 @@ class AudioAPIModuleInstaller { initSuspended, audioEventHandlerRegistry, runtimeRegistry); + AudioAPIModuleInstaller::contexts_.push_back(audioContext); + auto audioContextHostObject = std::make_shared( audioContext, &runtime, jsCallInvoker); @@ -138,6 +157,7 @@ class AudioAPIModuleInstaller { sampleRate, audioEventHandlerRegistry, runtimeRegistry); + auto audioContextHostObject = std::make_shared( offlineAudioContext, &runtime, jsCallInvoker); diff --git a/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.h b/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.h index 7f618fcf3..3e2a4d9dc 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.h @@ -1,5 +1,6 @@ #ifdef RCT_NEW_ARCH_ENABLED #import +#import #import #else // RCT_NEW_ARCH_ENABLED #import @@ -14,7 +15,7 @@ @interface AudioAPIModule : RCTEventEmitter #ifdef RCT_NEW_ARCH_ENABLED - + #else #endif // RCT_NEW_ARCH_ENABLED diff --git a/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm b/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm index cc2caccdd..1974d8171 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm +++ b/packages/react-native-audio-api/ios/audioapi/ios/AudioAPIModule.mm @@ -54,6 +54,8 @@ - (void)invalidate _eventHandler = nullptr; + audioapi::AudioAPIModuleInstaller::closeAllContexts(); + [super invalidate]; } diff --git a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.h b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.h index 5f5233774..39a2e0b0c 100644 --- a/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.h +++ b/packages/react-native-audio-api/ios/audioapi/ios/core/IOSAudioRecorder.h @@ -18,8 +18,7 @@ class IOSAudioRecorder : public AudioRecorder { IOSAudioRecorder( float sampleRate, int bufferLength, - const std::shared_ptr - &audioEventHandlerRegistry); + const std::shared_ptr &audioEventHandlerRegistry); ~IOSAudioRecorder() override;