react-native-audio-api 0.5.7 → 0.6.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RNAudioAPI.podspec +1 -1
- package/android/CMakeLists.txt +6 -3
- package/android/build.gradle +1 -0
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +0 -20
- package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +0 -2
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIPackage.kt +13 -0
- package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +62 -0
- package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +60 -0
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +294 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +279 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +46 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +39 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +84 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +144 -0
- package/android/src/main/res/drawable/next.xml +9 -0
- package/android/src/main/res/drawable/pause.xml +9 -0
- package/android/src/main/res/drawable/play.xml +9 -0
- package/android/src/main/res/drawable/previous.xml +9 -0
- package/android/src/main/res/drawable/skip_backward_5.xml +9 -0
- package/android/src/main/res/drawable/skip_forward_5.xml +9 -0
- package/android/src/main/res/drawable/stop.xml +9 -0
- package/android/src/oldarch/NativeAudioManagerModuleSpec.java +99 -0
- package/app.plugin.js +1 -0
- package/common/cpp/audioapi/AudioAPIModuleInstaller.h +2 -6
- package/common/cpp/audioapi/core/AudioContext.cpp +1 -12
- package/common/cpp/audioapi/core/AudioContext.h +0 -1
- package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +1 -6
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +8 -4
- package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +1 -0
- package/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp +3 -3
- package/ios/audioapi/ios/AudioAPIModule.mm +2 -3
- package/ios/audioapi/ios/AudioManagerModule.h +18 -0
- package/ios/audioapi/ios/AudioManagerModule.mm +94 -0
- package/ios/audioapi/ios/core/AudioPlayer.h +4 -12
- package/ios/audioapi/ios/core/AudioPlayer.m +26 -108
- package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -3
- package/ios/audioapi/ios/core/IOSAudioPlayer.mm +4 -28
- package/ios/audioapi/ios/system/AudioEngine.h +23 -0
- package/ios/audioapi/ios/system/AudioEngine.mm +137 -0
- package/ios/audioapi/ios/system/AudioSessionManager.h +22 -0
- package/ios/audioapi/ios/system/AudioSessionManager.mm +183 -0
- package/ios/audioapi/ios/system/LockScreenManager.h +23 -0
- package/ios/audioapi/ios/system/LockScreenManager.mm +295 -0
- package/ios/audioapi/ios/system/NotificationManager.h +22 -0
- package/ios/audioapi/ios/system/NotificationManager.mm +173 -0
- package/lib/commonjs/api.js +197 -0
- package/lib/commonjs/api.js.map +1 -0
- package/lib/commonjs/api.web.js +219 -0
- package/lib/commonjs/api.web.js.map +1 -0
- package/lib/commonjs/core/AnalyserNode.js +71 -0
- package/lib/commonjs/core/AnalyserNode.js.map +1 -0
- package/lib/commonjs/core/AudioBuffer.js +44 -0
- package/lib/commonjs/core/AudioBuffer.js.map +1 -0
- package/lib/commonjs/core/AudioBufferSourceNode.js +68 -0
- package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -0
- package/lib/commonjs/core/AudioContext.js +29 -0
- package/lib/commonjs/core/AudioContext.js.map +1 -0
- package/lib/commonjs/core/AudioDestinationNode.js +11 -0
- package/lib/commonjs/core/AudioDestinationNode.js.map +1 -0
- package/lib/commonjs/core/AudioNode.js +30 -0
- package/lib/commonjs/core/AudioNode.js.map +1 -0
- package/lib/commonjs/core/AudioParam.js +82 -0
- package/lib/commonjs/core/AudioParam.js.map +1 -0
- package/lib/commonjs/core/AudioScheduledSourceNode.js +38 -0
- package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -0
- package/lib/commonjs/core/BaseAudioContext.js +80 -0
- package/lib/commonjs/core/BaseAudioContext.js.map +1 -0
- package/lib/commonjs/core/BiquadFilterNode.js +33 -0
- package/lib/commonjs/core/BiquadFilterNode.js.map +1 -0
- package/lib/commonjs/core/GainNode.js +17 -0
- package/lib/commonjs/core/GainNode.js.map +1 -0
- package/lib/commonjs/core/OfflineAudioContext.js +63 -0
- package/lib/commonjs/core/OfflineAudioContext.js.map +1 -0
- package/lib/commonjs/core/OscillatorNode.js +32 -0
- package/lib/commonjs/core/OscillatorNode.js.map +1 -0
- package/lib/commonjs/core/PeriodicWave.js +15 -0
- package/lib/commonjs/core/PeriodicWave.js.map +1 -0
- package/lib/commonjs/core/StereoPannerNode.js +17 -0
- package/lib/commonjs/core/StereoPannerNode.js.map +1 -0
- package/lib/commonjs/errors/IndexSizeError.js +14 -0
- package/lib/commonjs/errors/IndexSizeError.js.map +1 -0
- package/lib/commonjs/errors/InvalidAccessError.js +14 -0
- package/lib/commonjs/errors/InvalidAccessError.js.map +1 -0
- package/lib/commonjs/errors/InvalidStateError.js +14 -0
- package/lib/commonjs/errors/InvalidStateError.js.map +1 -0
- package/lib/commonjs/errors/NotSupportedError.js +14 -0
- package/lib/commonjs/errors/NotSupportedError.js.map +1 -0
- package/lib/commonjs/errors/RangeError.js +14 -0
- package/lib/commonjs/errors/RangeError.js.map +1 -0
- package/lib/commonjs/errors/index.js +42 -0
- package/lib/commonjs/errors/index.js.map +1 -0
- package/lib/commonjs/index.js +17 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/interfaces.js +6 -0
- package/lib/commonjs/interfaces.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/plugin/withAudioAPI.js +62 -0
- package/lib/commonjs/plugin/withAudioAPI.js.map +1 -0
- package/lib/commonjs/specs/NativeAudioAPIModule.js +9 -0
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -0
- package/lib/commonjs/specs/NativeAudioManagerModule.js +33 -0
- package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -0
- package/lib/commonjs/specs/index.js +27 -0
- package/lib/commonjs/specs/index.js.map +1 -0
- package/lib/commonjs/system/AudioManager.js +79 -0
- package/lib/commonjs/system/AudioManager.js.map +1 -0
- package/lib/commonjs/system/index.js +14 -0
- package/lib/commonjs/system/index.js.map +1 -0
- package/lib/commonjs/system/types.js +2 -0
- package/lib/commonjs/system/types.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/index.js +10 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/commonjs/web-core/AnalyserNode.js +38 -0
- package/lib/commonjs/web-core/AnalyserNode.js.map +1 -0
- package/lib/commonjs/web-core/AudioBuffer.js +44 -0
- package/lib/commonjs/web-core/AudioBuffer.js.map +1 -0
- package/lib/commonjs/web-core/AudioBufferSourceNode.js +214 -0
- package/lib/commonjs/web-core/AudioBufferSourceNode.js.map +1 -0
- package/lib/commonjs/web-core/AudioContext.js +93 -0
- package/lib/commonjs/web-core/AudioContext.js.map +1 -0
- package/lib/commonjs/web-core/AudioDestinationNode.js +11 -0
- package/lib/commonjs/web-core/AudioDestinationNode.js.map +1 -0
- package/lib/commonjs/web-core/AudioNode.js +33 -0
- package/lib/commonjs/web-core/AudioNode.js.map +1 -0
- package/lib/commonjs/web-core/AudioParam.js +81 -0
- package/lib/commonjs/web-core/AudioParam.js.map +1 -0
- package/lib/commonjs/web-core/AudioScheduledSourceNode.js +41 -0
- package/lib/commonjs/web-core/AudioScheduledSourceNode.js.map +1 -0
- package/lib/commonjs/web-core/BaseAudioContext.js +2 -0
- package/lib/commonjs/web-core/BaseAudioContext.js.map +1 -0
- package/lib/commonjs/web-core/BiquadFilterNode.js +33 -0
- package/lib/commonjs/web-core/BiquadFilterNode.js.map +1 -0
- package/lib/commonjs/web-core/GainNode.js +17 -0
- package/lib/commonjs/web-core/GainNode.js.map +1 -0
- package/lib/commonjs/web-core/OfflineAudioContext.js +96 -0
- package/lib/commonjs/web-core/OfflineAudioContext.js.map +1 -0
- package/lib/commonjs/web-core/OscillatorNode.js +31 -0
- package/lib/commonjs/web-core/OscillatorNode.js.map +1 -0
- package/lib/commonjs/web-core/PeriodicWave.js +15 -0
- package/lib/commonjs/web-core/PeriodicWave.js.map +1 -0
- package/lib/commonjs/web-core/StereoPannerNode.js +17 -0
- package/lib/commonjs/web-core/StereoPannerNode.js.map +1 -0
- package/lib/commonjs/web-core/custom/LoadCustomWasm.js +37 -0
- package/lib/commonjs/web-core/custom/LoadCustomWasm.js.map +1 -0
- package/lib/commonjs/web-core/custom/index.js +14 -0
- package/lib/commonjs/web-core/custom/index.js.map +1 -0
- package/lib/commonjs/web-core/custom/signalsmithStretch/LICENSE.txt +21 -0
- package/lib/commonjs/web-core/custom/signalsmithStretch/README.md +46 -0
- package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs +826 -0
- package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs.map +1 -0
- package/lib/module/api.js +1 -0
- package/lib/module/api.js.map +1 -1
- package/lib/module/core/AudioContext.js +2 -1
- package/lib/module/core/AudioContext.js.map +1 -1
- package/lib/module/plugin/withAudioAPI.js +58 -0
- package/lib/module/plugin/withAudioAPI.js.map +1 -0
- package/lib/module/specs/NativeAudioManagerModule.js +30 -0
- package/lib/module/specs/NativeAudioManagerModule.js.map +1 -0
- package/lib/module/specs/index.js +6 -0
- package/lib/module/specs/index.js.map +1 -0
- package/lib/module/system/AudioManager.js +75 -0
- package/lib/module/system/AudioManager.js.map +1 -0
- package/lib/module/system/index.js +4 -0
- package/lib/module/system/index.js.map +1 -0
- package/lib/module/system/types.js +2 -0
- package/lib/module/system/types.js.map +1 -0
- package/lib/typescript/api.d.ts +1 -0
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/core/AudioContext.d.ts.map +1 -1
- package/lib/typescript/plugin/withAudioAPI.d.ts +9 -0
- package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -0
- package/lib/typescript/specs/NativeAudioManagerModule.d.ts +14 -0
- package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -0
- package/lib/typescript/specs/index.d.ts +4 -0
- package/lib/typescript/specs/index.d.ts.map +1 -0
- package/lib/typescript/system/AudioManager.d.ts +14 -0
- package/lib/typescript/system/AudioManager.d.ts.map +1 -0
- package/lib/typescript/system/index.d.ts +2 -0
- package/lib/typescript/system/index.d.ts.map +1 -0
- package/lib/typescript/system/types.d.ts +40 -0
- package/lib/typescript/system/types.d.ts.map +1 -0
- package/package.json +7 -4
- package/src/api.ts +1 -0
- package/src/core/AudioContext.ts +6 -1
- package/src/plugin/withAudioAPI.ts +90 -0
- package/src/specs/NativeAudioManagerModule.ts +48 -0
- package/src/specs/index.ts +6 -0
- package/src/system/AudioManager.ts +149 -0
- package/src/system/index.ts +1 -0
- package/src/system/types.ts +84 -0
package/RNAudioAPI.podspec
CHANGED
|
@@ -31,7 +31,7 @@ Pod::Spec.new do |s|
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
s.ios.frameworks = 'CoreFoundation', 'CoreAudio', 'AudioToolbox', 'Accelerate'
|
|
34
|
+
s.ios.frameworks = 'CoreFoundation', 'CoreAudio', 'AudioToolbox', 'Accelerate', 'MediaPlayer', 'AVFoundation'
|
|
35
35
|
|
|
36
36
|
s.compiler_flags = "#{folly_flags}"
|
|
37
37
|
|
package/android/CMakeLists.txt
CHANGED
|
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.12.0)
|
|
|
2
2
|
project(react-native-audio-api)
|
|
3
3
|
|
|
4
4
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
5
|
+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
5
6
|
set(CMAKE_CXX_STANDARD 20)
|
|
6
7
|
|
|
7
8
|
# Detect the processor and SIMD support
|
|
@@ -14,9 +15,11 @@ endif()
|
|
|
14
15
|
include("${REACT_NATIVE_DIR}/ReactAndroid/cmake-utils/folly-flags.cmake")
|
|
15
16
|
add_compile_options(${folly_FLAGS})
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
18
|
+
# frtti - enable Run-Time Type Information (dynamic_cast, typeid)
|
|
19
|
+
# -std=c++20 - use C++20 standard
|
|
20
|
+
# -Wall - enable all compiler's warning messages
|
|
21
|
+
string(APPEND CMAKE_CXX_FLAGS
|
|
22
|
+
" -frtti -std=c++${CMAKE_CXX_STANDARD} -Wall")
|
|
20
23
|
|
|
21
24
|
if(${IS_NEW_ARCHITECTURE_ENABLED})
|
|
22
25
|
string(APPEND CMAKE_CXX_FLAGS " -DRCT_NEW_ARCH_ENABLED")
|
package/android/build.gradle
CHANGED
|
@@ -218,6 +218,7 @@ dependencies {
|
|
|
218
218
|
implementation 'androidx.core:core-ktx:1.13.1'
|
|
219
219
|
implementation 'com.facebook.fbjni:fbjni:0.6.0'
|
|
220
220
|
implementation 'com.google.oboe:oboe:1.9.0'
|
|
221
|
+
implementation 'androidx.media:media:1.7.0'
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
def assertMinimalReactNativeVersion = task assertMinimalReactNativeVersionTask {
|
|
@@ -6,26 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
namespace audioapi {
|
|
8
8
|
|
|
9
|
-
AudioPlayer::AudioPlayer(
|
|
10
|
-
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio)
|
|
11
|
-
: renderAudio_(renderAudio), channelCount_(2) {
|
|
12
|
-
AudioStreamBuilder builder;
|
|
13
|
-
|
|
14
|
-
builder.setSharingMode(SharingMode::Exclusive)
|
|
15
|
-
->setFormat(AudioFormat::Float)
|
|
16
|
-
->setFormatConversionAllowed(true)
|
|
17
|
-
->setPerformanceMode(PerformanceMode::None)
|
|
18
|
-
->setChannelCount(channelCount_)
|
|
19
|
-
->setSampleRateConversionQuality(SampleRateConversionQuality::Medium)
|
|
20
|
-
->setDataCallback(this)
|
|
21
|
-
->openStream(mStream_);
|
|
22
|
-
|
|
23
|
-
sampleRate_ = static_cast<float>(mStream_->getSampleRate());
|
|
24
|
-
mBus_ = std::make_shared<AudioBus>(
|
|
25
|
-
RENDER_QUANTUM_SIZE, channelCount_, sampleRate_);
|
|
26
|
-
isInitialized_ = true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
9
|
AudioPlayer::AudioPlayer(
|
|
30
10
|
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio,
|
|
31
11
|
float sampleRate)
|
|
@@ -14,8 +14,6 @@ class AudioBus;
|
|
|
14
14
|
|
|
15
15
|
class AudioPlayer : public AudioStreamDataCallback {
|
|
16
16
|
public:
|
|
17
|
-
explicit AudioPlayer(
|
|
18
|
-
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio);
|
|
19
17
|
AudioPlayer(
|
|
20
18
|
const std::function<void(std::shared_ptr<AudioBus>, int)> &renderAudio,
|
|
21
19
|
float sampleRate);
|
|
@@ -10,6 +10,7 @@ import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
|
10
10
|
@ReactModuleList(
|
|
11
11
|
nativeModules = [
|
|
12
12
|
AudioAPIModule::class,
|
|
13
|
+
AudioManagerModule::class,
|
|
13
14
|
],
|
|
14
15
|
)
|
|
15
16
|
class AudioAPIPackage : BaseReactPackage() {
|
|
@@ -19,6 +20,7 @@ class AudioAPIPackage : BaseReactPackage() {
|
|
|
19
20
|
): NativeModule? {
|
|
20
21
|
when (name) {
|
|
21
22
|
AudioAPIModule.NAME -> return AudioAPIModule(reactContext)
|
|
23
|
+
AudioManagerModule.NAME -> return AudioManagerModule(reactContext)
|
|
22
24
|
}
|
|
23
25
|
return null
|
|
24
26
|
}
|
|
@@ -37,6 +39,17 @@ class AudioAPIPackage : BaseReactPackage() {
|
|
|
37
39
|
isCxxModule = false,
|
|
38
40
|
isTurboModule = isTurboModule,
|
|
39
41
|
)
|
|
42
|
+
|
|
43
|
+
moduleInfos[AudioManagerModule.NAME] =
|
|
44
|
+
ReactModuleInfo(
|
|
45
|
+
AudioManagerModule.NAME,
|
|
46
|
+
AudioManagerModule.NAME,
|
|
47
|
+
canOverrideExistingModule = true,
|
|
48
|
+
needsEagerInit = false,
|
|
49
|
+
hasConstants = true,
|
|
50
|
+
isCxxModule = false,
|
|
51
|
+
isTurboModule = false,
|
|
52
|
+
)
|
|
40
53
|
moduleInfos
|
|
41
54
|
}
|
|
42
55
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package com.swmansion.audioapi
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
import com.facebook.react.bridge.ReadableArray
|
|
7
|
+
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.swmansion.audioapi.system.MediaSessionManager
|
|
9
|
+
|
|
10
|
+
class AudioManagerModule(
|
|
11
|
+
reactContext: ReactApplicationContext,
|
|
12
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
13
|
+
companion object {
|
|
14
|
+
const val NAME = "AudioManagerModule"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private val mediaSessionManager: MediaSessionManager = MediaSessionManager(reactContext)
|
|
18
|
+
|
|
19
|
+
init {
|
|
20
|
+
try {
|
|
21
|
+
System.loadLibrary("react-native-audio-api")
|
|
22
|
+
} catch (exception: UnsatisfiedLinkError) {
|
|
23
|
+
throw RuntimeException("Could not load native module AudioAPIModule", exception)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
28
|
+
fun setLockScreenInfo(info: ReadableMap?) {
|
|
29
|
+
mediaSessionManager.setLockScreenInfo(info)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
33
|
+
fun resetLockScreenInfo() {
|
|
34
|
+
mediaSessionManager.resetLockScreenInfo()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
38
|
+
fun enableRemoteCommand(
|
|
39
|
+
name: String,
|
|
40
|
+
enabled: Boolean,
|
|
41
|
+
) {
|
|
42
|
+
mediaSessionManager.enableRemoteCommand(name, enabled)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
46
|
+
fun setAudioSessionOptions(
|
|
47
|
+
category: String?,
|
|
48
|
+
mode: String?,
|
|
49
|
+
options: ReadableArray?,
|
|
50
|
+
active: Boolean,
|
|
51
|
+
) {
|
|
52
|
+
// Nothing to do here
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
56
|
+
fun getDevicePreferredSampleRate(): Double = mediaSessionManager.getDevicePreferredSampleRate()
|
|
57
|
+
|
|
58
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
59
|
+
fun observeAudioInterruptions(enable: Boolean) = mediaSessionManager.observeAudioInterruptions(enable)
|
|
60
|
+
|
|
61
|
+
override fun getName(): String = NAME
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system
|
|
2
|
+
|
|
3
|
+
import android.media.AudioFocusRequest
|
|
4
|
+
import android.media.AudioManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.util.Log
|
|
7
|
+
|
|
8
|
+
class AudioFocusListener(
|
|
9
|
+
private val audioManager: AudioManager,
|
|
10
|
+
val eventEmitter: MediaSessionEventEmitter,
|
|
11
|
+
private val lockScreenManager: LockScreenManager,
|
|
12
|
+
) : AudioManager.OnAudioFocusChangeListener {
|
|
13
|
+
private var playOnAudioFocus = false
|
|
14
|
+
private var focusRequest: AudioFocusRequest? = null
|
|
15
|
+
|
|
16
|
+
override fun onAudioFocusChange(focusChange: Int) {
|
|
17
|
+
Log.d("AudioFocusListener", "onAudioFocusChange: $focusChange")
|
|
18
|
+
when (focusChange) {
|
|
19
|
+
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
20
|
+
playOnAudioFocus = false
|
|
21
|
+
eventEmitter.onInterruption(mapOf("type" to "began", "shouldResume" to false))
|
|
22
|
+
}
|
|
23
|
+
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
24
|
+
playOnAudioFocus = lockScreenManager.isPlaying
|
|
25
|
+
eventEmitter.onInterruption(mapOf("type" to "began", "shouldResume" to playOnAudioFocus))
|
|
26
|
+
}
|
|
27
|
+
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
28
|
+
if (playOnAudioFocus) {
|
|
29
|
+
eventEmitter.onInterruption(mapOf("type" to "ended", "shouldResume" to true))
|
|
30
|
+
} else {
|
|
31
|
+
eventEmitter.onInterruption(mapOf("type" to "ended", "shouldResume" to false))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
playOnAudioFocus = false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun requestAudioFocus() {
|
|
40
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
41
|
+
this.focusRequest =
|
|
42
|
+
AudioFocusRequest
|
|
43
|
+
.Builder(AudioManager.AUDIOFOCUS_GAIN)
|
|
44
|
+
.setOnAudioFocusChangeListener(this)
|
|
45
|
+
.build()
|
|
46
|
+
|
|
47
|
+
audioManager.requestAudioFocus(focusRequest!!)
|
|
48
|
+
} else {
|
|
49
|
+
audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fun abandonAudioFocus() {
|
|
54
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.focusRequest != null) {
|
|
55
|
+
audioManager.abandonAudioFocusRequest(focusRequest!!)
|
|
56
|
+
} else {
|
|
57
|
+
audioManager.abandonAudioFocus(this)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.BitmapFactory
|
|
5
|
+
import android.graphics.drawable.BitmapDrawable
|
|
6
|
+
import android.support.v4.media.MediaMetadataCompat
|
|
7
|
+
import android.support.v4.media.session.MediaSessionCompat
|
|
8
|
+
import android.support.v4.media.session.PlaybackStateCompat
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import androidx.core.app.NotificationCompat
|
|
11
|
+
import androidx.media.app.NotificationCompat.MediaStyle
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.bridge.ReadableMap
|
|
14
|
+
import com.facebook.react.bridge.ReadableType
|
|
15
|
+
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper.Companion.instance
|
|
16
|
+
import java.io.IOException
|
|
17
|
+
import java.net.URL
|
|
18
|
+
|
|
19
|
+
class LockScreenManager(
|
|
20
|
+
private val reactContext: ReactApplicationContext,
|
|
21
|
+
private val mediaSession: MediaSessionCompat,
|
|
22
|
+
private val mediaNotificationManager: MediaNotificationManager,
|
|
23
|
+
val channelId: String,
|
|
24
|
+
) {
|
|
25
|
+
private var pb: PlaybackStateCompat.Builder = PlaybackStateCompat.Builder()
|
|
26
|
+
private var state: PlaybackStateCompat = pb.build()
|
|
27
|
+
private var controls: Long = 0
|
|
28
|
+
var isPlaying: Boolean = false
|
|
29
|
+
|
|
30
|
+
private var nb: NotificationCompat.Builder = NotificationCompat.Builder(reactContext, channelId)
|
|
31
|
+
|
|
32
|
+
private var artworkThread: Thread? = null
|
|
33
|
+
|
|
34
|
+
private var title: String? = null
|
|
35
|
+
private var artist: String? = null
|
|
36
|
+
private var album: String? = null
|
|
37
|
+
private var description: String? = null
|
|
38
|
+
private var duration: Long = 0
|
|
39
|
+
private var speed: Float = 1.0F
|
|
40
|
+
private var elapsedTime: Long = 0L
|
|
41
|
+
private var artwork: String? = null
|
|
42
|
+
private var playbackState: Int = PlaybackStateCompat.STATE_PAUSED
|
|
43
|
+
|
|
44
|
+
init {
|
|
45
|
+
this.pb.setActions(controls)
|
|
46
|
+
|
|
47
|
+
this.nb.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
48
|
+
this.nb.setPriority(NotificationCompat.PRIORITY_HIGH)
|
|
49
|
+
|
|
50
|
+
updateNotificationMediaStyle()
|
|
51
|
+
|
|
52
|
+
mediaNotificationManager.updateActions(controls)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun setLockScreenInfo(info: ReadableMap?) {
|
|
56
|
+
if (artworkThread != null && artworkThread!!.isAlive) {
|
|
57
|
+
artworkThread!!.interrupt()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
artworkThread = null
|
|
61
|
+
|
|
62
|
+
if (info == null) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
val md = MediaMetadataCompat.Builder()
|
|
67
|
+
|
|
68
|
+
if (info.hasKey("title")) {
|
|
69
|
+
title = info.getString("title")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (info.hasKey("artist")) {
|
|
73
|
+
artist = info.getString("artist")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (info.hasKey("album")) {
|
|
77
|
+
album = info.getString("album")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (info.hasKey("description")) {
|
|
81
|
+
description = info.getString("description")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (info.hasKey("duration")) {
|
|
85
|
+
duration = (info.getDouble("duration") * 1000).toLong()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
md.putText(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
|
89
|
+
md.putText(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
|
|
90
|
+
md.putText(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
|
|
91
|
+
md.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, description)
|
|
92
|
+
md.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
|
|
93
|
+
|
|
94
|
+
nb.setContentTitle(title)
|
|
95
|
+
nb.setContentText(artist)
|
|
96
|
+
nb.setContentInfo(album)
|
|
97
|
+
|
|
98
|
+
if (info.hasKey("artwork")) {
|
|
99
|
+
var localArtwork = false
|
|
100
|
+
|
|
101
|
+
if (info.getType("artwork") == ReadableType.Map) {
|
|
102
|
+
artwork = info.getMap("artwork")?.getString("uri")
|
|
103
|
+
localArtwork = true
|
|
104
|
+
} else {
|
|
105
|
+
artwork = info.getString("artwork")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
val artworkLocal = localArtwork
|
|
109
|
+
|
|
110
|
+
artworkThread =
|
|
111
|
+
Thread {
|
|
112
|
+
try {
|
|
113
|
+
val bitmap: Bitmap? = artwork?.let { loadArtwork(it, artworkLocal) }
|
|
114
|
+
|
|
115
|
+
val currentMetadata: MediaMetadataCompat = mediaSession.controller.metadata
|
|
116
|
+
val newBuilder =
|
|
117
|
+
MediaMetadataCompat.Builder(
|
|
118
|
+
currentMetadata,
|
|
119
|
+
)
|
|
120
|
+
mediaSession.setMetadata(
|
|
121
|
+
newBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap).build(),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
nb.setLargeIcon(bitmap)
|
|
125
|
+
mediaNotificationManager.show(nb, isPlaying)
|
|
126
|
+
|
|
127
|
+
artworkThread = null
|
|
128
|
+
} catch (ex: Exception) {
|
|
129
|
+
ex.printStackTrace()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
artworkThread!!.start()
|
|
133
|
+
} else {
|
|
134
|
+
md.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, null)
|
|
135
|
+
nb.setLargeIcon(null as Bitmap?)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
speed =
|
|
139
|
+
if (info.hasKey("speed")) {
|
|
140
|
+
info.getDouble("speed").toFloat()
|
|
141
|
+
} else {
|
|
142
|
+
state.playbackSpeed
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
elapsedTime =
|
|
146
|
+
if (info.hasKey("elapsedTime")) {
|
|
147
|
+
info.getDouble("elapsedTime").toLong()
|
|
148
|
+
} else {
|
|
149
|
+
state.position
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (info.hasKey("state")) {
|
|
153
|
+
val state = info.getString("state")
|
|
154
|
+
|
|
155
|
+
when (state) {
|
|
156
|
+
"state_playing" -> {
|
|
157
|
+
this.playbackState = PlaybackStateCompat.STATE_PLAYING
|
|
158
|
+
}
|
|
159
|
+
"state_paused" -> {
|
|
160
|
+
this.playbackState = PlaybackStateCompat.STATE_PAUSED
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
updatePlaybackState(this.playbackState)
|
|
166
|
+
|
|
167
|
+
mediaSession.setMetadata(md.build())
|
|
168
|
+
mediaSession.setActive(true)
|
|
169
|
+
mediaNotificationManager.show(nb, isPlaying)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun resetLockScreenInfo() {
|
|
173
|
+
if (artworkThread != null && artworkThread!!.isAlive) artworkThread!!.interrupt()
|
|
174
|
+
artworkThread = null
|
|
175
|
+
|
|
176
|
+
mediaNotificationManager.hide()
|
|
177
|
+
mediaSession.setActive(false)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fun enableRemoteCommand(
|
|
181
|
+
name: String,
|
|
182
|
+
enabled: Boolean,
|
|
183
|
+
) {
|
|
184
|
+
var controlValue = 0L
|
|
185
|
+
when (name) {
|
|
186
|
+
"play" -> controlValue = PlaybackStateCompat.ACTION_PLAY
|
|
187
|
+
"pause" -> controlValue = PlaybackStateCompat.ACTION_PAUSE
|
|
188
|
+
"stop" -> controlValue = PlaybackStateCompat.ACTION_STOP
|
|
189
|
+
"togglePlayPause" -> controlValue = PlaybackStateCompat.ACTION_PLAY_PAUSE
|
|
190
|
+
"nextTrack" -> controlValue = PlaybackStateCompat.ACTION_SKIP_TO_NEXT
|
|
191
|
+
"previousTrack" -> controlValue = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
|
|
192
|
+
"skipForward" -> controlValue = PlaybackStateCompat.ACTION_REWIND
|
|
193
|
+
"skipBackward" -> controlValue = PlaybackStateCompat.ACTION_REWIND
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
controls =
|
|
197
|
+
if (enabled) {
|
|
198
|
+
controls or controlValue
|
|
199
|
+
} else {
|
|
200
|
+
controls and controlValue.inv()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
mediaNotificationManager.updateActions(controls)
|
|
204
|
+
pb.setActions(controls)
|
|
205
|
+
|
|
206
|
+
state = pb.build()
|
|
207
|
+
mediaSession.setPlaybackState(state)
|
|
208
|
+
|
|
209
|
+
updateNotificationMediaStyle()
|
|
210
|
+
|
|
211
|
+
if (mediaSession.isActive) {
|
|
212
|
+
mediaNotificationManager.show(nb, isPlaying)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private fun loadArtwork(
|
|
217
|
+
url: String,
|
|
218
|
+
local: Boolean,
|
|
219
|
+
): Bitmap? {
|
|
220
|
+
var bitmap: Bitmap? = null
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// If we are running the app in debug mode, the "local" image will be served from htt://localhost:8080, so we need to check for this case and load those images from URL
|
|
224
|
+
if (local && !url.startsWith("http")) {
|
|
225
|
+
// Gets the drawable from the RN's helper for local resources
|
|
226
|
+
val helper = instance
|
|
227
|
+
val image = helper.getResourceDrawable(reactContext, url)
|
|
228
|
+
|
|
229
|
+
bitmap =
|
|
230
|
+
if (image is BitmapDrawable) {
|
|
231
|
+
image.bitmap
|
|
232
|
+
} else {
|
|
233
|
+
BitmapFactory.decodeFile(url)
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
// Open connection to the URL and decodes the image
|
|
237
|
+
val con = URL(url).openConnection()
|
|
238
|
+
con.connect()
|
|
239
|
+
val input = con.getInputStream()
|
|
240
|
+
bitmap = BitmapFactory.decodeStream(input)
|
|
241
|
+
input.close()
|
|
242
|
+
}
|
|
243
|
+
} catch (ex: IOException) {
|
|
244
|
+
Log.w("MediaSessionManager", "Could not load the artwork", ex)
|
|
245
|
+
} catch (ex: IndexOutOfBoundsException) {
|
|
246
|
+
Log.w("MediaSessionManager", "Could not load the artwork", ex)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return bitmap
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fun updatePlaybackState(playbackState: Int) {
|
|
253
|
+
isPlaying = playbackState == PlaybackStateCompat.STATE_PLAYING
|
|
254
|
+
|
|
255
|
+
pb.setState(playbackState, elapsedTime, speed)
|
|
256
|
+
pb.setActions(controls)
|
|
257
|
+
state = pb.build()
|
|
258
|
+
mediaSession.setPlaybackState(state)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private fun hasControl(control: Long): Boolean = (controls and control) == control
|
|
262
|
+
|
|
263
|
+
private fun updateNotificationMediaStyle() {
|
|
264
|
+
val style = MediaStyle()
|
|
265
|
+
style.setMediaSession(mediaSession.sessionToken)
|
|
266
|
+
var controlCount = 0
|
|
267
|
+
if (hasControl(PlaybackStateCompat.ACTION_PLAY) ||
|
|
268
|
+
hasControl(PlaybackStateCompat.ACTION_PAUSE) ||
|
|
269
|
+
hasControl(
|
|
270
|
+
PlaybackStateCompat.ACTION_PLAY_PAUSE,
|
|
271
|
+
)
|
|
272
|
+
) {
|
|
273
|
+
controlCount += 1
|
|
274
|
+
}
|
|
275
|
+
if (hasControl(PlaybackStateCompat.ACTION_SKIP_TO_NEXT)) {
|
|
276
|
+
controlCount += 1
|
|
277
|
+
}
|
|
278
|
+
if (hasControl(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)) {
|
|
279
|
+
controlCount += 1
|
|
280
|
+
}
|
|
281
|
+
if (hasControl(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
|
|
282
|
+
controlCount += 1
|
|
283
|
+
}
|
|
284
|
+
if (hasControl(PlaybackStateCompat.ACTION_REWIND)) {
|
|
285
|
+
controlCount += 1
|
|
286
|
+
}
|
|
287
|
+
val actions = IntArray(controlCount)
|
|
288
|
+
for (i in actions.indices) {
|
|
289
|
+
actions[i] = i
|
|
290
|
+
}
|
|
291
|
+
style.setShowActionsInCompactView(*actions)
|
|
292
|
+
nb.setStyle(style)
|
|
293
|
+
}
|
|
294
|
+
}
|