react-native-audio-api 0.6.0-rc.0 → 0.6.0-rc.2

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 (207) hide show
  1. package/README.md +35 -22
  2. package/android/CMakeLists.txt +6 -3
  3. package/android/build.gradle +1 -0
  4. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.cpp +73 -0
  5. package/android/src/main/cpp/audioapi/android/core/AndroidAudioRecorder.h +37 -0
  6. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +6 -10
  7. package/android/src/main/cpp/audioapi/android/core/AudioPlayer.h +2 -3
  8. package/android/src/main/java/com/swmansion/audioapi/AudioManagerModule.kt +19 -14
  9. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +60 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +294 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +279 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +46 -0
  13. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +39 -0
  14. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionEventEmitter.kt +88 -0
  15. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +162 -0
  16. package/android/src/main/java/com/swmansion/audioapi/system/VolumeChangeListener.kt +27 -0
  17. package/android/src/main/res/drawable/next.xml +9 -0
  18. package/android/src/main/res/drawable/pause.xml +9 -0
  19. package/android/src/main/res/drawable/play.xml +9 -0
  20. package/android/src/main/res/drawable/previous.xml +9 -0
  21. package/android/src/main/res/drawable/skip_backward_5.xml +9 -0
  22. package/android/src/main/res/drawable/skip_forward_5.xml +9 -0
  23. package/android/src/main/res/drawable/stop.xml +9 -0
  24. package/app.plugin.js +1 -0
  25. package/common/cpp/audioapi/AudioAPIModuleInstaller.h +29 -5
  26. package/common/cpp/audioapi/HostObjects/AnalyserNodeHostObject.h +1 -0
  27. package/common/cpp/audioapi/HostObjects/AudioRecorderHostObject.h +149 -0
  28. package/common/cpp/audioapi/core/AudioContext.cpp +4 -3
  29. package/common/cpp/audioapi/core/BaseAudioContext.cpp +6 -6
  30. package/common/cpp/audioapi/core/inputs/AudioRecorder.h +38 -0
  31. package/common/cpp/audioapi/core/sources/AudioBufferSourceNode.cpp +1 -6
  32. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.cpp +8 -4
  33. package/common/cpp/audioapi/core/sources/AudioScheduledSourceNode.h +6 -0
  34. package/common/cpp/audioapi/core/sources/OscillatorNode.cpp +1 -1
  35. package/common/cpp/audioapi/core/utils/AudioNodeDestructor.cpp +3 -3
  36. package/common/cpp/audioapi/core/utils/AudioNodeManager.cpp +45 -11
  37. package/common/cpp/audioapi/core/utils/AudioNodeManager.h +6 -2
  38. package/ios/audioapi/ios/AudioManagerModule.mm +16 -15
  39. package/ios/audioapi/ios/core/IOSAudioPlayer.h +11 -12
  40. package/ios/audioapi/ios/core/IOSAudioPlayer.mm +22 -16
  41. package/ios/audioapi/ios/core/IOSAudioRecorder.h +36 -0
  42. package/ios/audioapi/ios/core/IOSAudioRecorder.mm +62 -0
  43. package/ios/audioapi/ios/core/{AudioPlayer.h → NativeAudioPlayer.h} +1 -8
  44. package/ios/audioapi/ios/core/{AudioPlayer.m → NativeAudioPlayer.m} +4 -33
  45. package/ios/audioapi/ios/core/NativeAudioRecorder.h +25 -0
  46. package/ios/audioapi/ios/core/NativeAudioRecorder.m +47 -0
  47. package/ios/audioapi/ios/system/AudioEngine.h +7 -1
  48. package/ios/audioapi/ios/system/AudioEngine.mm +64 -20
  49. package/ios/audioapi/ios/system/AudioSessionManager.h +3 -1
  50. package/ios/audioapi/ios/system/AudioSessionManager.mm +37 -25
  51. package/ios/audioapi/ios/system/LockScreenManager.mm +4 -8
  52. package/ios/audioapi/ios/system/NotificationManager.h +13 -1
  53. package/ios/audioapi/ios/system/NotificationManager.mm +96 -44
  54. package/lib/commonjs/api.js +211 -0
  55. package/lib/commonjs/api.js.map +1 -0
  56. package/lib/commonjs/api.web.js +219 -0
  57. package/lib/commonjs/api.web.js.map +1 -0
  58. package/lib/commonjs/core/AnalyserNode.js +71 -0
  59. package/lib/commonjs/core/AnalyserNode.js.map +1 -0
  60. package/lib/commonjs/core/AudioBuffer.js +44 -0
  61. package/lib/commonjs/core/AudioBuffer.js.map +1 -0
  62. package/lib/commonjs/core/AudioBufferSourceNode.js +68 -0
  63. package/lib/commonjs/core/AudioBufferSourceNode.js.map +1 -0
  64. package/lib/commonjs/core/AudioContext.js +29 -0
  65. package/lib/commonjs/core/AudioContext.js.map +1 -0
  66. package/lib/commonjs/core/AudioDestinationNode.js +11 -0
  67. package/lib/commonjs/core/AudioDestinationNode.js.map +1 -0
  68. package/lib/commonjs/core/AudioNode.js +30 -0
  69. package/lib/commonjs/core/AudioNode.js.map +1 -0
  70. package/lib/commonjs/core/AudioParam.js +82 -0
  71. package/lib/commonjs/core/AudioParam.js.map +1 -0
  72. package/lib/commonjs/core/AudioRecorder.js +51 -0
  73. package/lib/commonjs/core/AudioRecorder.js.map +1 -0
  74. package/lib/commonjs/core/AudioScheduledSourceNode.js +38 -0
  75. package/lib/commonjs/core/AudioScheduledSourceNode.js.map +1 -0
  76. package/lib/commonjs/core/BaseAudioContext.js +80 -0
  77. package/lib/commonjs/core/BaseAudioContext.js.map +1 -0
  78. package/lib/commonjs/core/BiquadFilterNode.js +33 -0
  79. package/lib/commonjs/core/BiquadFilterNode.js.map +1 -0
  80. package/lib/commonjs/core/GainNode.js +17 -0
  81. package/lib/commonjs/core/GainNode.js.map +1 -0
  82. package/lib/commonjs/core/OfflineAudioContext.js +63 -0
  83. package/lib/commonjs/core/OfflineAudioContext.js.map +1 -0
  84. package/lib/commonjs/core/OscillatorNode.js +32 -0
  85. package/lib/commonjs/core/OscillatorNode.js.map +1 -0
  86. package/lib/commonjs/core/PeriodicWave.js +15 -0
  87. package/lib/commonjs/core/PeriodicWave.js.map +1 -0
  88. package/lib/commonjs/core/StereoPannerNode.js +17 -0
  89. package/lib/commonjs/core/StereoPannerNode.js.map +1 -0
  90. package/lib/commonjs/errors/IndexSizeError.js +14 -0
  91. package/lib/commonjs/errors/IndexSizeError.js.map +1 -0
  92. package/lib/commonjs/errors/InvalidAccessError.js +14 -0
  93. package/lib/commonjs/errors/InvalidAccessError.js.map +1 -0
  94. package/lib/commonjs/errors/InvalidStateError.js +14 -0
  95. package/lib/commonjs/errors/InvalidStateError.js.map +1 -0
  96. package/lib/commonjs/errors/NotSupportedError.js +14 -0
  97. package/lib/commonjs/errors/NotSupportedError.js.map +1 -0
  98. package/lib/commonjs/errors/RangeError.js +14 -0
  99. package/lib/commonjs/errors/RangeError.js.map +1 -0
  100. package/lib/commonjs/errors/index.js +42 -0
  101. package/lib/commonjs/errors/index.js.map +1 -0
  102. package/lib/commonjs/hooks/useSytemVolume.js +24 -0
  103. package/lib/commonjs/hooks/useSytemVolume.js.map +1 -0
  104. package/lib/commonjs/index.js +17 -0
  105. package/lib/commonjs/index.js.map +1 -0
  106. package/lib/commonjs/interfaces.js +6 -0
  107. package/lib/commonjs/interfaces.js.map +1 -0
  108. package/lib/commonjs/package.json +1 -0
  109. package/lib/commonjs/plugin/withAudioAPI.js +62 -0
  110. package/lib/commonjs/plugin/withAudioAPI.js.map +1 -0
  111. package/lib/commonjs/specs/NativeAudioAPIModule.js +9 -0
  112. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -0
  113. package/lib/commonjs/specs/NativeAudioManagerModule.js +36 -0
  114. package/lib/commonjs/specs/NativeAudioManagerModule.js.map +1 -0
  115. package/lib/commonjs/specs/index.js +27 -0
  116. package/lib/commonjs/specs/index.js.map +1 -0
  117. package/lib/commonjs/system/AudioManager.js +52 -0
  118. package/lib/commonjs/system/AudioManager.js.map +1 -0
  119. package/lib/commonjs/system/index.js +14 -0
  120. package/lib/commonjs/system/index.js.map +1 -0
  121. package/lib/commonjs/system/types.js +2 -0
  122. package/lib/commonjs/system/types.js.map +1 -0
  123. package/lib/commonjs/types.js +2 -0
  124. package/lib/commonjs/types.js.map +1 -0
  125. package/lib/commonjs/utils/index.js +10 -0
  126. package/lib/commonjs/utils/index.js.map +1 -0
  127. package/lib/commonjs/web-core/AnalyserNode.js +38 -0
  128. package/lib/commonjs/web-core/AnalyserNode.js.map +1 -0
  129. package/lib/commonjs/web-core/AudioBuffer.js +44 -0
  130. package/lib/commonjs/web-core/AudioBuffer.js.map +1 -0
  131. package/lib/commonjs/web-core/AudioBufferSourceNode.js +214 -0
  132. package/lib/commonjs/web-core/AudioBufferSourceNode.js.map +1 -0
  133. package/lib/commonjs/web-core/AudioContext.js +93 -0
  134. package/lib/commonjs/web-core/AudioContext.js.map +1 -0
  135. package/lib/commonjs/web-core/AudioDestinationNode.js +11 -0
  136. package/lib/commonjs/web-core/AudioDestinationNode.js.map +1 -0
  137. package/lib/commonjs/web-core/AudioNode.js +33 -0
  138. package/lib/commonjs/web-core/AudioNode.js.map +1 -0
  139. package/lib/commonjs/web-core/AudioParam.js +81 -0
  140. package/lib/commonjs/web-core/AudioParam.js.map +1 -0
  141. package/lib/commonjs/web-core/AudioScheduledSourceNode.js +41 -0
  142. package/lib/commonjs/web-core/AudioScheduledSourceNode.js.map +1 -0
  143. package/lib/commonjs/web-core/BaseAudioContext.js +2 -0
  144. package/lib/commonjs/web-core/BaseAudioContext.js.map +1 -0
  145. package/lib/commonjs/web-core/BiquadFilterNode.js +33 -0
  146. package/lib/commonjs/web-core/BiquadFilterNode.js.map +1 -0
  147. package/lib/commonjs/web-core/GainNode.js +17 -0
  148. package/lib/commonjs/web-core/GainNode.js.map +1 -0
  149. package/lib/commonjs/web-core/OfflineAudioContext.js +96 -0
  150. package/lib/commonjs/web-core/OfflineAudioContext.js.map +1 -0
  151. package/lib/commonjs/web-core/OscillatorNode.js +31 -0
  152. package/lib/commonjs/web-core/OscillatorNode.js.map +1 -0
  153. package/lib/commonjs/web-core/PeriodicWave.js +15 -0
  154. package/lib/commonjs/web-core/PeriodicWave.js.map +1 -0
  155. package/lib/commonjs/web-core/StereoPannerNode.js +17 -0
  156. package/lib/commonjs/web-core/StereoPannerNode.js.map +1 -0
  157. package/lib/commonjs/web-core/custom/LoadCustomWasm.js +37 -0
  158. package/lib/commonjs/web-core/custom/LoadCustomWasm.js.map +1 -0
  159. package/lib/commonjs/web-core/custom/index.js +14 -0
  160. package/lib/commonjs/web-core/custom/index.js.map +1 -0
  161. package/lib/commonjs/web-core/custom/signalsmithStretch/LICENSE.txt +21 -0
  162. package/lib/commonjs/web-core/custom/signalsmithStretch/README.md +46 -0
  163. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs +826 -0
  164. package/lib/commonjs/web-core/custom/signalsmithStretch/SignalsmithStretch.mjs.map +1 -0
  165. package/lib/module/api.js +3 -1
  166. package/lib/module/api.js.map +1 -1
  167. package/lib/module/core/AudioRecorder.js +45 -0
  168. package/lib/module/core/AudioRecorder.js.map +1 -0
  169. package/lib/module/errors/NotSupportedError.js.map +1 -1
  170. package/lib/module/hooks/useSytemVolume.js +19 -0
  171. package/lib/module/hooks/useSytemVolume.js.map +1 -0
  172. package/lib/module/plugin/withAudioAPI.js +58 -0
  173. package/lib/module/plugin/withAudioAPI.js.map +1 -0
  174. package/lib/module/specs/NativeAudioManagerModule.js +10 -8
  175. package/lib/module/specs/NativeAudioManagerModule.js.map +1 -1
  176. package/lib/module/system/AudioManager.js +26 -44
  177. package/lib/module/system/AudioManager.js.map +1 -1
  178. package/lib/typescript/api.d.ts +5 -1
  179. package/lib/typescript/api.d.ts.map +1 -1
  180. package/lib/typescript/core/AudioRecorder.d.ts +22 -0
  181. package/lib/typescript/core/AudioRecorder.d.ts.map +1 -0
  182. package/lib/typescript/errors/NotSupportedError.d.ts.map +1 -1
  183. package/lib/typescript/hooks/useSytemVolume.d.ts +2 -0
  184. package/lib/typescript/hooks/useSytemVolume.d.ts.map +1 -0
  185. package/lib/typescript/interfaces.d.ts +11 -5
  186. package/lib/typescript/interfaces.d.ts.map +1 -1
  187. package/lib/typescript/plugin/withAudioAPI.d.ts +9 -0
  188. package/lib/typescript/plugin/withAudioAPI.d.ts.map +1 -0
  189. package/lib/typescript/specs/NativeAudioManagerModule.d.ts +4 -2
  190. package/lib/typescript/specs/NativeAudioManagerModule.d.ts.map +1 -1
  191. package/lib/typescript/system/AudioManager.d.ts +5 -2
  192. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  193. package/lib/typescript/system/types.d.ts +36 -5
  194. package/lib/typescript/system/types.d.ts.map +1 -1
  195. package/lib/typescript/types.d.ts +5 -0
  196. package/lib/typescript/types.d.ts.map +1 -1
  197. package/package.json +7 -3
  198. package/src/api.ts +13 -2
  199. package/src/core/AudioRecorder.ts +81 -0
  200. package/src/hooks/useSytemVolume.ts +19 -0
  201. package/src/interfaces.ts +25 -11
  202. package/src/plugin/withAudioAPI.ts +91 -0
  203. package/src/specs/NativeAudioManagerModule.ts +13 -19
  204. package/src/system/AudioManager.ts +37 -87
  205. package/src/system/types.ts +43 -17
  206. package/src/types.ts +13 -0
  207. /package/src/errors/{NotSupportedError.tsx → NotSupportedError.ts} +0 -0
@@ -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
+ }
@@ -0,0 +1,279 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Notification
5
+ import android.app.PendingIntent
6
+ import android.app.Service
7
+ import android.content.Intent
8
+ import android.content.res.Resources
9
+ import android.os.Binder
10
+ import android.os.Build
11
+ import android.os.IBinder
12
+ import android.provider.ContactsContract
13
+ import android.support.v4.media.session.PlaybackStateCompat
14
+ import android.util.Log
15
+ import android.view.KeyEvent
16
+ import androidx.core.app.NotificationCompat
17
+ import androidx.core.app.NotificationManagerCompat
18
+ import androidx.core.content.ContextCompat
19
+ import com.facebook.react.bridge.ReactApplicationContext
20
+ import com.swmansion.audioapi.R
21
+ import java.lang.ref.WeakReference
22
+
23
+ class MediaNotificationManager(
24
+ val reactContext: ReactApplicationContext,
25
+ val notificationId: Int,
26
+ val channelId: String,
27
+ ) {
28
+ private var smallIcon: Int = R.drawable.play
29
+ private var customIcon: Int = 0
30
+
31
+ private var play: NotificationCompat.Action? = null
32
+ private var pause: NotificationCompat.Action? = null
33
+ private var stop: NotificationCompat.Action? = null
34
+ private var next: NotificationCompat.Action? = null
35
+ private var previous: NotificationCompat.Action? = null
36
+ private var skipForward: NotificationCompat.Action? = null
37
+ private var skipBackward: NotificationCompat.Action? = null
38
+
39
+ companion object {
40
+ const val REMOVE_NOTIFICATION: String = "audio_manager_remove_notification"
41
+ const val PACKAGE_NAME: String = "com.swmansion.audioapi.system"
42
+ const val MEDIA_BUTTON: String = "audio_manager_media_button"
43
+ }
44
+
45
+ @SuppressLint("RestrictedApi")
46
+ @Synchronized
47
+ fun prepareNotification(
48
+ builder: NotificationCompat.Builder,
49
+ isPlaying: Boolean,
50
+ ): Notification {
51
+ builder.mActions.clear()
52
+
53
+ if (previous != null) {
54
+ builder.addAction(previous)
55
+ }
56
+
57
+ if (skipBackward != null) {
58
+ builder.addAction(skipBackward)
59
+ }
60
+
61
+ if (play != null && !isPlaying) {
62
+ builder.addAction(play)
63
+ }
64
+
65
+ if (pause != null && isPlaying) {
66
+ builder.addAction(pause)
67
+ }
68
+
69
+ if (stop != null) {
70
+ builder.addAction(stop)
71
+ }
72
+
73
+ if (next != null) {
74
+ builder.addAction(next)
75
+ }
76
+
77
+ if (skipForward != null) {
78
+ builder.addAction(skipForward)
79
+ }
80
+
81
+ builder.setSmallIcon(if (customIcon != 0) customIcon else smallIcon)
82
+
83
+ val packageName: String = reactContext.packageName
84
+ val openApp: Intent? = reactContext.packageManager.getLaunchIntentForPackage(packageName)
85
+ try {
86
+ builder.setContentIntent(
87
+ PendingIntent.getActivity(
88
+ reactContext,
89
+ 0,
90
+ openApp,
91
+ PendingIntent.FLAG_IMMUTABLE,
92
+ ),
93
+ )
94
+ } catch (e: Exception) {
95
+ Log.w("AudioManagerModule", "Error creating content intent: ${e.message}")
96
+ }
97
+
98
+ val remove = Intent(REMOVE_NOTIFICATION)
99
+ remove.putExtra(PACKAGE_NAME, reactContext.applicationInfo.packageName)
100
+ builder.setDeleteIntent(
101
+ PendingIntent.getBroadcast(
102
+ reactContext,
103
+ 0,
104
+ remove,
105
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
106
+ ),
107
+ )
108
+
109
+ return builder.build()
110
+ }
111
+
112
+ @SuppressLint("MissingPermission")
113
+ @Synchronized
114
+ fun show(
115
+ builder: NotificationCompat.Builder?,
116
+ isPlaying: Boolean,
117
+ ) {
118
+ NotificationManagerCompat.from(reactContext).notify(
119
+ notificationId,
120
+ prepareNotification(
121
+ builder!!,
122
+ isPlaying,
123
+ ),
124
+ )
125
+ }
126
+
127
+ fun hide() {
128
+ NotificationManagerCompat.from(reactContext).cancel(notificationId)
129
+
130
+ try {
131
+ val myIntent =
132
+ Intent(
133
+ reactContext,
134
+ NotificationService::class.java,
135
+ )
136
+ reactContext.stopService(myIntent)
137
+ } catch (e: java.lang.Exception) {
138
+ Log.w("AudioManagerModule", "Error stopping service: ${e.message}")
139
+ }
140
+ }
141
+
142
+ @Synchronized
143
+ fun updateActions(mask: Long) {
144
+ play = createAction("play", "Play", mask, PlaybackStateCompat.ACTION_PLAY, play)
145
+ pause = createAction("pause", "Pause", mask, PlaybackStateCompat.ACTION_PAUSE, pause)
146
+ stop = createAction("stop", "Stop", mask, PlaybackStateCompat.ACTION_STOP, stop)
147
+ next = createAction("next", "Next", mask, PlaybackStateCompat.ACTION_SKIP_TO_NEXT, next)
148
+ previous =
149
+ createAction(
150
+ "previous",
151
+ "Previous",
152
+ mask,
153
+ PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS,
154
+ previous,
155
+ )
156
+
157
+ skipForward =
158
+ createAction(
159
+ "skip_forward_5",
160
+ "Skip Forward",
161
+ mask,
162
+ PlaybackStateCompat.ACTION_FAST_FORWARD,
163
+ skipForward,
164
+ )
165
+
166
+ skipBackward =
167
+ createAction(
168
+ "skip_backward_5",
169
+ "Skip Backward",
170
+ mask,
171
+ PlaybackStateCompat.ACTION_REWIND,
172
+ skipBackward,
173
+ )
174
+ }
175
+
176
+ private fun createAction(
177
+ iconName: String,
178
+ title: String,
179
+ mask: Long,
180
+ action: Long,
181
+ oldAction: NotificationCompat.Action?,
182
+ ): NotificationCompat.Action? {
183
+ if ((mask and action) == 0L) {
184
+ return null
185
+ }
186
+
187
+ if (oldAction != null) {
188
+ return oldAction
189
+ }
190
+
191
+ val r: Resources = reactContext.resources
192
+ val packageName: String = reactContext.packageName
193
+ val icon = r.getIdentifier(iconName, "drawable", packageName)
194
+
195
+ val keyCode = PlaybackStateCompat.toKeyCode(action)
196
+ val intent = Intent(MEDIA_BUTTON)
197
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
198
+ intent.putExtra(ContactsContract.Directory.PACKAGE_NAME, packageName)
199
+ val i =
200
+ PendingIntent.getBroadcast(
201
+ reactContext,
202
+ keyCode,
203
+ intent,
204
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
205
+ )
206
+
207
+ return NotificationCompat.Action(icon, title, i)
208
+ }
209
+
210
+ class NotificationService : Service() {
211
+ private val binder = LocalBinder()
212
+ private var notification: Notification? = null
213
+
214
+ inner class LocalBinder : Binder() {
215
+ private var weakService: WeakReference<NotificationService>? = null
216
+
217
+ fun onBind(service: NotificationService) {
218
+ weakService = WeakReference(service)
219
+ }
220
+
221
+ fun getService(): NotificationService? = weakService?.get()
222
+ }
223
+
224
+ override fun onBind(intent: Intent): IBinder {
225
+ binder.onBind(this)
226
+ return binder
227
+ }
228
+
229
+ fun forceForeground() {
230
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
231
+ val intent = Intent(this, NotificationService::class.java)
232
+ ContextCompat.startForegroundService(this, intent)
233
+ notification =
234
+ MediaSessionManager.mediaNotificationManager
235
+ .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.channelId), false)
236
+ startForeground(MediaSessionManager.notificationId, notification)
237
+ }
238
+ }
239
+
240
+ override fun onCreate() {
241
+ super.onCreate()
242
+ try {
243
+ notification =
244
+ MediaSessionManager.mediaNotificationManager
245
+ .prepareNotification(NotificationCompat.Builder(this, MediaSessionManager.channelId), false)
246
+ startForeground(MediaSessionManager.notificationId, notification)
247
+ } catch (ex: Exception) {
248
+ Log.w("AudioManagerModule", "Error starting service: ${ex.message}")
249
+ }
250
+ }
251
+
252
+ override fun onStartCommand(
253
+ intent: Intent?,
254
+ flags: Int,
255
+ startId: Int,
256
+ ): Int {
257
+ onCreate()
258
+ return START_NOT_STICKY
259
+ }
260
+
261
+ override fun onTaskRemoved(rootIntent: Intent?) {
262
+ super.onTaskRemoved(rootIntent)
263
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
264
+ stopForeground(STOP_FOREGROUND_REMOVE)
265
+ }
266
+ stopSelf()
267
+ }
268
+
269
+ override fun onDestroy() {
270
+ super.onDestroy()
271
+
272
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
273
+ stopForeground(STOP_FOREGROUND_REMOVE)
274
+ }
275
+
276
+ stopSelf()
277
+ }
278
+ }
279
+ }
@@ -0,0 +1,46 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.media.AudioManager
7
+ import android.view.KeyEvent
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+
10
+ class MediaReceiver(
11
+ val reactContext: ReactApplicationContext,
12
+ private val mediaSessionManager: MediaSessionManager,
13
+ ) : BroadcastReceiver() {
14
+ override fun onReceive(
15
+ context: Context?,
16
+ intent: Intent?,
17
+ ) {
18
+ val action = intent!!.action
19
+
20
+ if (MediaNotificationManager.REMOVE_NOTIFICATION == action) {
21
+ if (!checkApp(intent)) return
22
+
23
+ mediaSessionManager.mediaNotificationManager.hide()
24
+ mediaSessionManager.mediaSession.isActive = false
25
+
26
+ mediaSessionManager.eventEmitter.sendEvent("onCloseNotification", null)
27
+ } else if (MediaNotificationManager.MEDIA_BUTTON == action || Intent.ACTION_MEDIA_BUTTON == action) {
28
+ if (!intent.hasExtra(Intent.EXTRA_KEY_EVENT)) return
29
+ if (!checkApp(intent)) return
30
+
31
+ val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
32
+ mediaSessionManager.mediaSession.controller.dispatchMediaButtonEvent(keyEvent)
33
+ } else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == action) {
34
+ mediaSessionManager.mediaSession.controller.transportControls
35
+ .pause()
36
+ }
37
+ }
38
+
39
+ private fun checkApp(intent: Intent): Boolean {
40
+ if (intent.hasExtra(MediaNotificationManager.PACKAGE_NAME)) {
41
+ val name = intent.getStringExtra(MediaNotificationManager.PACKAGE_NAME)
42
+ if (!reactContext.packageName.equals(name)) return false
43
+ }
44
+ return true
45
+ }
46
+ }
@@ -0,0 +1,39 @@
1
+ package com.swmansion.audioapi.system
2
+
3
+ import android.support.v4.media.session.MediaSessionCompat
4
+ import android.support.v4.media.session.PlaybackStateCompat
5
+
6
+ class MediaSessionCallback(
7
+ val eventEmitter: MediaSessionEventEmitter,
8
+ private val lockScreenManager: LockScreenManager,
9
+ ) : MediaSessionCompat.Callback() {
10
+ override fun onPlay() {
11
+ lockScreenManager.updatePlaybackState(PlaybackStateCompat.STATE_PLAYING)
12
+ eventEmitter.onPlay()
13
+ }
14
+
15
+ override fun onPause() {
16
+ lockScreenManager.updatePlaybackState(PlaybackStateCompat.STATE_PAUSED)
17
+ eventEmitter.onPause()
18
+ }
19
+
20
+ override fun onStop() {
21
+ eventEmitter.onStop()
22
+ }
23
+
24
+ override fun onSkipToNext() {
25
+ eventEmitter.onSkipToNext()
26
+ }
27
+
28
+ override fun onSkipToPrevious() {
29
+ eventEmitter.onSkipToPrevious()
30
+ }
31
+
32
+ override fun onFastForward() {
33
+ eventEmitter.onFastForward()
34
+ }
35
+
36
+ override fun onRewind() {
37
+ eventEmitter.onRewind()
38
+ }
39
+ }