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.
Files changed (192) hide show
  1. package/RNAudioAPI.podspec +1 -1
  2. package/android/CMakeLists.txt +6 -3
  3. package/android/build.gradle +1 -0
  4. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +0 -20
  5. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +0 -2
  6. package/android/src/main/java/com/swmansion/audioapi/AudioAPIPackage.kt +13 -0
  7. package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +62 -0
  8. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +60 -0
  9. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +294 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +279 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +46 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +39 -0
  13. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +84 -0
  14. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +144 -0
  15. package/android/src/main/res/drawable/next.xml +9 -0
  16. package/android/src/main/res/drawable/pause.xml +9 -0
  17. package/android/src/main/res/drawable/play.xml +9 -0
  18. package/android/src/main/res/drawable/previous.xml +9 -0
  19. package/android/src/main/res/drawable/skip_backward_5.xml +9 -0
  20. package/android/src/main/res/drawable/skip_forward_5.xml +9 -0
  21. package/android/src/main/res/drawable/stop.xml +9 -0
  22. package/android/src/oldarch/NativeAudioManagerModuleSpec.java +99 -0
  23. package/app.plugin.js +1 -0
  24. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +2 -6
  25. package/common/cpp/audioapi/core/AudioContext.cpp +1 -12
  26. package/common/cpp/audioapi/core/AudioContext.h +0 -1
  27. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +1 -6
  28. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +8 -4
  29. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +1 -0
  30. package/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp +3 -3
  31. package/ios/audioapi/ios/AudioAPIModule.mm +2 -3
  32. package/ios/audioapi/ios/AudioManagerModule.h +18 -0
  33. package/ios/audioapi/ios/AudioManagerModule.mm +94 -0
  34. package/ios/audioapi/ios/core/AudioPlayer.h +4 -12
  35. package/ios/audioapi/ios/core/AudioPlayer.m +26 -108
  36. package/ios/audioapi/ios/core/IOSAudioPlayer.h +1 -3
  37. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +4 -28
  38. package/ios/audioapi/ios/system/AudioEngine.h +23 -0
  39. package/ios/audioapi/ios/system/AudioEngine.mm +137 -0
  40. package/ios/audioapi/ios/system/AudioSessionManager.h +22 -0
  41. package/ios/audioapi/ios/system/AudioSessionManager.mm +183 -0
  42. package/ios/audioapi/ios/system/LockScreenManager.h +23 -0
  43. package/ios/audioapi/ios/system/LockScreenManager.mm +295 -0
  44. package/ios/audioapi/ios/system/NotificationManager.h +22 -0
  45. package/ios/audioapi/ios/system/NotificationManager.mm +173 -0
  46. package/lib/commonjs/api.js +197 -0
  47. package/lib/commonjs/api.js.map +1 -0
  48. package/lib/commonjs/api.web.js +219 -0
  49. package/lib/commonjs/api.web.js.map +1 -0
  50. package/lib/commonjs/core/AnalyserNode.js +71 -0
  51. package/lib/commonjs/core/AnalyserNode.js.map +1 -0
  52. package/lib/commonjs/core/AudioBuffer.js +44 -0
  53. package/lib/commonjs/core/AudioBuffer.js.map +1 -0
  54. package/lib/commonjs/core/AudioBufferSourceNode.js +68 -0
  55. package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -0
  56. package/lib/commonjs/core/AudioContext.js +29 -0
  57. package/lib/commonjs/core/AudioContext.js.map +1 -0
  58. package/lib/commonjs/core/AudioDestinationNode.js +11 -0
  59. package/lib/commonjs/core/AudioDestinationNode.js.map +1 -0
  60. package/lib/commonjs/core/AudioNode.js +30 -0
  61. package/lib/commonjs/core/AudioNode.js.map +1 -0
  62. package/lib/commonjs/core/AudioParam.js +82 -0
  63. package/lib/commonjs/core/AudioParam.js.map +1 -0
  64. package/lib/commonjs/core/AudioScheduledSourceNode.js +38 -0
  65. package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -0
  66. package/lib/commonjs/core/BaseAudioContext.js +80 -0
  67. package/lib/commonjs/core/BaseAudioContext.js.map +1 -0
  68. package/lib/commonjs/core/BiquadFilterNode.js +33 -0
  69. package/lib/commonjs/core/BiquadFilterNode.js.map +1 -0
  70. package/lib/commonjs/core/GainNode.js +17 -0
  71. package/lib/commonjs/core/GainNode.js.map +1 -0
  72. package/lib/commonjs/core/OfflineAudioContext.js +63 -0
  73. package/lib/commonjs/core/OfflineAudioContext.js.map +1 -0
  74. package/lib/commonjs/core/OscillatorNode.js +32 -0
  75. package/lib/commonjs/core/OscillatorNode.js.map +1 -0
  76. package/lib/commonjs/core/PeriodicWave.js +15 -0
  77. package/lib/commonjs/core/PeriodicWave.js.map +1 -0
  78. package/lib/commonjs/core/StereoPannerNode.js +17 -0
  79. package/lib/commonjs/core/StereoPannerNode.js.map +1 -0
  80. package/lib/commonjs/errors/IndexSizeError.js +14 -0
  81. package/lib/commonjs/errors/IndexSizeError.js.map +1 -0
  82. package/lib/commonjs/errors/InvalidAccessError.js +14 -0
  83. package/lib/commonjs/errors/InvalidAccessError.js.map +1 -0
  84. package/lib/commonjs/errors/InvalidStateError.js +14 -0
  85. package/lib/commonjs/errors/InvalidStateError.js.map +1 -0
  86. package/lib/commonjs/errors/NotSupportedError.js +14 -0
  87. package/lib/commonjs/errors/NotSupportedError.js.map +1 -0
  88. package/lib/commonjs/errors/RangeError.js +14 -0
  89. package/lib/commonjs/errors/RangeError.js.map +1 -0
  90. package/lib/commonjs/errors/index.js +42 -0
  91. package/lib/commonjs/errors/index.js.map +1 -0
  92. package/lib/commonjs/index.js +17 -0
  93. package/lib/commonjs/index.js.map +1 -0
  94. package/lib/commonjs/interfaces.js +6 -0
  95. package/lib/commonjs/interfaces.js.map +1 -0
  96. package/lib/commonjs/package.json +1 -0
  97. package/lib/commonjs/plugin/withAudioAPI.js +62 -0
  98. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -0
  99. package/lib/commonjs/specs/NativeAudioAPIModule.js +9 -0
  100. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -0
  101. package/lib/commonjs/specs/NativeAudioManagerModule.js +33 -0
  102. package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -0
  103. package/lib/commonjs/specs/index.js +27 -0
  104. package/lib/commonjs/specs/index.js.map +1 -0
  105. package/lib/commonjs/system/AudioManager.js +79 -0
  106. package/lib/commonjs/system/AudioManager.js.map +1 -0
  107. package/lib/commonjs/system/index.js +14 -0
  108. package/lib/commonjs/system/index.js.map +1 -0
  109. package/lib/commonjs/system/types.js +2 -0
  110. package/lib/commonjs/system/types.js.map +1 -0
  111. package/lib/commonjs/types.js +2 -0
  112. package/lib/commonjs/types.js.map +1 -0
  113. package/lib/commonjs/utils/index.js +10 -0
  114. package/lib/commonjs/utils/index.js.map +1 -0
  115. package/lib/commonjs/web-core/AnalyserNode.js +38 -0
  116. package/lib/commonjs/web-core/AnalyserNode.js.map +1 -0
  117. package/lib/commonjs/web-core/AudioBuffer.js +44 -0
  118. package/lib/commonjs/web-core/AudioBuffer.js.map +1 -0
  119. package/lib/commonjs/web-core/AudioBufferSourceNode.js +214 -0
  120. package/lib/commonjs/web-core/AudioBufferSourceNode.js.map +1 -0
  121. package/lib/commonjs/web-core/AudioContext.js +93 -0
  122. package/lib/commonjs/web-core/AudioContext.js.map +1 -0
  123. package/lib/commonjs/web-core/AudioDestinationNode.js +11 -0
  124. package/lib/commonjs/web-core/AudioDestinationNode.js.map +1 -0
  125. package/lib/commonjs/web-core/AudioNode.js +33 -0
  126. package/lib/commonjs/web-core/AudioNode.js.map +1 -0
  127. package/lib/commonjs/web-core/AudioParam.js +81 -0
  128. package/lib/commonjs/web-core/AudioParam.js.map +1 -0
  129. package/lib/commonjs/web-core/AudioScheduledSourceNode.js +41 -0
  130. package/lib/commonjs/web-core/AudioScheduledSourceNode.js.map +1 -0
  131. package/lib/commonjs/web-core/BaseAudioContext.js +2 -0
  132. package/lib/commonjs/web-core/BaseAudioContext.js.map +1 -0
  133. package/lib/commonjs/web-core/BiquadFilterNode.js +33 -0
  134. package/lib/commonjs/web-core/BiquadFilterNode.js.map +1 -0
  135. package/lib/commonjs/web-core/GainNode.js +17 -0
  136. package/lib/commonjs/web-core/GainNode.js.map +1 -0
  137. package/lib/commonjs/web-core/OfflineAudioContext.js +96 -0
  138. package/lib/commonjs/web-core/OfflineAudioContext.js.map +1 -0
  139. package/lib/commonjs/web-core/OscillatorNode.js +31 -0
  140. package/lib/commonjs/web-core/OscillatorNode.js.map +1 -0
  141. package/lib/commonjs/web-core/PeriodicWave.js +15 -0
  142. package/lib/commonjs/web-core/PeriodicWave.js.map +1 -0
  143. package/lib/commonjs/web-core/StereoPannerNode.js +17 -0
  144. package/lib/commonjs/web-core/StereoPannerNode.js.map +1 -0
  145. package/lib/commonjs/web-core/custom/LoadCustomWasm.js +37 -0
  146. package/lib/commonjs/web-core/custom/LoadCustomWasm.js.map +1 -0
  147. package/lib/commonjs/web-core/custom/index.js +14 -0
  148. package/lib/commonjs/web-core/custom/index.js.map +1 -0
  149. package/lib/commonjs/web-core/custom/signalsmithStretch/LICENSE.txt +21 -0
  150. package/lib/commonjs/web-core/custom/signalsmithStretch/README.md +46 -0
  151. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs +826 -0
  152. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs.map +1 -0
  153. package/lib/module/api.js +1 -0
  154. package/lib/module/api.js.map +1 -1
  155. package/lib/module/core/AudioContext.js +2 -1
  156. package/lib/module/core/AudioContext.js.map +1 -1
  157. package/lib/module/plugin/withAudioAPI.js +58 -0
  158. package/lib/module/plugin/withAudioAPI.js.map +1 -0
  159. package/lib/module/specs/NativeAudioManagerModule.js +30 -0
  160. package/lib/module/specs/NativeAudioManagerModule.js.map +1 -0
  161. package/lib/module/specs/index.js +6 -0
  162. package/lib/module/specs/index.js.map +1 -0
  163. package/lib/module/system/AudioManager.js +75 -0
  164. package/lib/module/system/AudioManager.js.map +1 -0
  165. package/lib/module/system/index.js +4 -0
  166. package/lib/module/system/index.js.map +1 -0
  167. package/lib/module/system/types.js +2 -0
  168. package/lib/module/system/types.js.map +1 -0
  169. package/lib/typescript/api.d.ts +1 -0
  170. package/lib/typescript/api.d.ts.map +1 -1
  171. package/lib/typescript/core/AudioContext.d.ts.map +1 -1
  172. package/lib/typescript/plugin/withAudioAPI.d.ts +9 -0
  173. package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -0
  174. package/lib/typescript/specs/NativeAudioManagerModule.d.ts +14 -0
  175. package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -0
  176. package/lib/typescript/specs/index.d.ts +4 -0
  177. package/lib/typescript/specs/index.d.ts.map +1 -0
  178. package/lib/typescript/system/AudioManager.d.ts +14 -0
  179. package/lib/typescript/system/AudioManager.d.ts.map +1 -0
  180. package/lib/typescript/system/index.d.ts +2 -0
  181. package/lib/typescript/system/index.d.ts.map +1 -0
  182. package/lib/typescript/system/types.d.ts +40 -0
  183. package/lib/typescript/system/types.d.ts.map +1 -0
  184. package/package.json +7 -4
  185. package/src/api.ts +1 -0
  186. package/src/core/AudioContext.ts +6 -1
  187. package/src/plugin/withAudioAPI.ts +90 -0
  188. package/src/specs/NativeAudioManagerModule.ts +48 -0
  189. package/src/specs/index.ts +6 -0
  190. package/src/system/AudioManager.ts +149 -0
  191. package/src/system/index.ts +1 -0
  192. package/src/system/types.ts +84 -0
@@ -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
 
@@ -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
- # string(APPEND CMAKE_CXX_FLAGS
18
- # " -fexceptions -fno-omit-frame-pointer -frtti -fstack-protector-all\
19
- # -std=c++${CMAKE_CXX_STANDARD} -Wall -Werror")
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")
@@ -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
+ }