react-native-audio-api 0.11.0-nightly-db51488-20251208 → 0.11.0-nightly-6ba0571-20251209

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 (134) hide show
  1. package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
  2. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
  3. package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
  4. package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +8 -23
  5. package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +127 -0
  6. package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
  7. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
  8. package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
  9. package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
  10. package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
  11. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +668 -0
  12. package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
  13. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
  14. package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +43 -0
  15. package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
  16. package/ios/audioapi/ios/AudioAPIModule.h +2 -2
  17. package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
  18. package/ios/audioapi/ios/system/AudioEngine.mm +2 -2
  19. package/ios/audioapi/ios/system/AudioSessionManager.mm +1 -1
  20. package/ios/audioapi/ios/system/NotificationManager.mm +1 -1
  21. package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
  22. package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
  23. package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
  24. package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
  25. package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
  26. package/lib/commonjs/api.js +59 -10
  27. package/lib/commonjs/api.js.map +1 -1
  28. package/lib/commonjs/api.web.js +27 -14
  29. package/lib/commonjs/api.web.js.map +1 -1
  30. package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
  31. package/lib/commonjs/system/AudioManager.js +6 -9
  32. package/lib/commonjs/system/AudioManager.js.map +1 -1
  33. package/lib/commonjs/system/index.js +13 -0
  34. package/lib/commonjs/system/index.js.map +1 -1
  35. package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
  36. package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
  37. package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
  38. package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
  39. package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
  40. package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
  41. package/lib/commonjs/system/notification/index.js +45 -0
  42. package/lib/commonjs/system/notification/index.js.map +1 -0
  43. package/lib/commonjs/system/notification/types.js +6 -0
  44. package/lib/commonjs/system/notification/types.js.map +1 -0
  45. package/lib/commonjs/web-system/index.js +17 -0
  46. package/lib/commonjs/web-system/index.js.map +1 -0
  47. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
  48. package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  49. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
  50. package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
  51. package/lib/commonjs/web-system/notification/index.js +21 -0
  52. package/lib/commonjs/web-system/notification/index.js.map +1 -0
  53. package/lib/module/api.js +5 -1
  54. package/lib/module/api.js.map +1 -1
  55. package/lib/module/api.web.js +3 -1
  56. package/lib/module/api.web.js.map +1 -1
  57. package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
  58. package/lib/module/system/AudioManager.js +6 -9
  59. package/lib/module/system/AudioManager.js.map +1 -1
  60. package/lib/module/system/index.js +1 -0
  61. package/lib/module/system/index.js.map +1 -1
  62. package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
  63. package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
  64. package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
  65. package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
  66. package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
  67. package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
  68. package/lib/module/system/notification/index.js +7 -0
  69. package/lib/module/system/notification/index.js.map +1 -0
  70. package/lib/module/system/notification/types.js +4 -0
  71. package/lib/module/system/notification/types.js.map +1 -0
  72. package/lib/module/web-system/index.js +4 -0
  73. package/lib/module/web-system/index.js.map +1 -0
  74. package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
  75. package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
  76. package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
  77. package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
  78. package/lib/module/web-system/notification/index.js +5 -0
  79. package/lib/module/web-system/notification/index.js.map +1 -0
  80. package/lib/typescript/api.d.ts +3 -1
  81. package/lib/typescript/api.d.ts.map +1 -1
  82. package/lib/typescript/api.web.d.ts +3 -1
  83. package/lib/typescript/api.web.d.ts.map +1 -1
  84. package/lib/typescript/events/types.d.ts +4 -18
  85. package/lib/typescript/events/types.d.ts.map +1 -1
  86. package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
  87. package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
  88. package/lib/typescript/system/AudioManager.d.ts +4 -5
  89. package/lib/typescript/system/AudioManager.d.ts.map +1 -1
  90. package/lib/typescript/system/index.d.ts +1 -0
  91. package/lib/typescript/system/index.d.ts.map +1 -1
  92. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
  93. package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  94. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
  95. package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
  96. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
  97. package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
  98. package/lib/typescript/system/notification/index.d.ts +5 -0
  99. package/lib/typescript/system/notification/index.d.ts.map +1 -0
  100. package/lib/typescript/system/notification/types.d.ts +65 -0
  101. package/lib/typescript/system/notification/types.d.ts.map +1 -0
  102. package/lib/typescript/system/types.d.ts +0 -16
  103. package/lib/typescript/system/types.d.ts.map +1 -1
  104. package/lib/typescript/web-system/index.d.ts +2 -0
  105. package/lib/typescript/web-system/index.d.ts.map +1 -0
  106. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
  107. package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
  108. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
  109. package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
  110. package/lib/typescript/web-system/notification/index.d.ts +3 -0
  111. package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
  112. package/package.json +1 -1
  113. package/src/api.ts +17 -2
  114. package/src/api.web.ts +7 -2
  115. package/src/events/types.ts +4 -20
  116. package/src/specs/NativeAudioAPIModule.ts +23 -7
  117. package/src/system/AudioManager.ts +10 -23
  118. package/src/system/index.ts +1 -0
  119. package/src/system/notification/PlaybackNotificationManager.ts +193 -0
  120. package/src/system/notification/RecordingNotificationManager.ts +242 -0
  121. package/src/system/notification/SimpleNotificationManager.ts +170 -0
  122. package/src/system/notification/index.ts +4 -0
  123. package/src/system/notification/types.ts +110 -0
  124. package/src/system/types.ts +0 -18
  125. package/src/web-system/index.ts +1 -0
  126. package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
  127. package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
  128. package/src/web-system/notification/index.ts +2 -0
  129. package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
  130. package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
  131. package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
  132. package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +0 -61
  133. package/ios/audioapi/ios/system/LockScreenManager.h +0 -23
  134. package/ios/audioapi/ios/system/LockScreenManager.mm +0 -314
@@ -1,347 +0,0 @@
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.swmansion.audioapi.R
16
- import java.io.IOException
17
- import java.lang.ref.WeakReference
18
- import java.net.URL
19
-
20
- class LockScreenManager(
21
- private val reactContext: WeakReference<ReactApplicationContext>,
22
- private val mediaSession: WeakReference<MediaSessionCompat>,
23
- private val mediaNotificationManager: WeakReference<MediaNotificationManager>,
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.get()!!, MediaSessionManager.CHANNEL_ID)
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 = 0L
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
- pb.setActions(controls)
46
- nb.setPriority(NotificationCompat.PRIORITY_HIGH)
47
- nb.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
48
-
49
- updateNotificationMediaStyle()
50
- mediaNotificationManager.get()?.updateActions(controls)
51
- }
52
-
53
- fun setLockScreenInfo(info: ReadableMap?) {
54
- if (artworkThread != null && artworkThread!!.isAlive) {
55
- artworkThread!!.interrupt()
56
- }
57
-
58
- artworkThread = null
59
-
60
- if (info == null) {
61
- return
62
- }
63
-
64
- val md = MediaMetadataCompat.Builder()
65
-
66
- if (info.hasKey("title")) {
67
- title = info.getString("title")
68
- }
69
-
70
- if (info.hasKey("artist")) {
71
- artist = info.getString("artist")
72
- }
73
-
74
- if (info.hasKey("album")) {
75
- album = info.getString("album")
76
- }
77
-
78
- if (info.hasKey("description")) {
79
- description = info.getString("description")
80
- }
81
-
82
- if (info.hasKey("duration")) {
83
- duration = (info.getDouble("duration") * 1000).toLong()
84
- }
85
-
86
- md.putText(MediaMetadataCompat.METADATA_KEY_TITLE, title)
87
- md.putText(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
88
- md.putText(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
89
- md.putText(MediaMetadataCompat.METADATA_KEY_DISPLAY_DESCRIPTION, description)
90
- md.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
91
-
92
- nb.setContentTitle(title)
93
- nb.setContentText(artist)
94
- nb.setContentInfo(album)
95
-
96
- if (info.hasKey("artwork")) {
97
- var localArtwork = false
98
-
99
- if (info.getType("artwork") == ReadableType.Map) {
100
- artwork = info.getMap("artwork")?.getString("uri")
101
- localArtwork = true
102
- } else {
103
- artwork = info.getString("artwork")
104
- }
105
-
106
- val artworkLocal = localArtwork
107
-
108
- artworkThread =
109
- Thread {
110
- try {
111
- val bitmap: Bitmap? = artwork?.let { loadArtwork(it, artworkLocal) }
112
-
113
- val currentMetadata = mediaSession.get()?.controller?.metadata
114
- val newBuilder =
115
- MediaMetadataCompat.Builder(
116
- currentMetadata,
117
- )
118
- mediaSession.get()?.setMetadata(
119
- newBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap).build(),
120
- )
121
-
122
- nb.setLargeIcon(bitmap)
123
- mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
124
-
125
- artworkThread = null
126
- } catch (ex: Exception) {
127
- ex.printStackTrace()
128
- }
129
- }
130
- artworkThread!!.start()
131
- }
132
-
133
- speed =
134
- if (info.hasKey("speed")) {
135
- info.getDouble("speed").toFloat()
136
- } else {
137
- state.playbackSpeed
138
- }
139
-
140
- if (isPlaying && speed == 0F) {
141
- speed = 1F
142
- }
143
-
144
- elapsedTime =
145
- if (info.hasKey("elapsedTime")) {
146
- (info.getDouble("elapsedTime") * 1000).toLong()
147
- } else {
148
- state.position
149
- }
150
-
151
- if (info.hasKey("state")) {
152
- val state = info.getString("state")
153
-
154
- when (state) {
155
- "state_playing" -> {
156
- this.playbackState = PlaybackStateCompat.STATE_PLAYING
157
- }
158
-
159
- "state_paused" -> {
160
- this.playbackState = PlaybackStateCompat.STATE_PAUSED
161
- }
162
- }
163
- }
164
-
165
- updatePlaybackState(this.playbackState)
166
-
167
- mediaSession.get()?.setMetadata(md.build())
168
- mediaSession.get()?.setActive(true)
169
- mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
170
- }
171
-
172
- fun resetLockScreenInfo() {
173
- if (artworkThread != null && artworkThread!!.isAlive) artworkThread!!.interrupt()
174
- artworkThread = null
175
-
176
- title = null
177
- artist = null
178
- album = null
179
- description = null
180
- duration = 0L
181
- speed = 1.0F
182
- elapsedTime = 0L
183
- artwork = null
184
- playbackState = PlaybackStateCompat.STATE_PAUSED
185
- isPlaying = false
186
-
187
- val emptyMetadata = MediaMetadataCompat.Builder().build()
188
- mediaSession.get()?.setMetadata(emptyMetadata)
189
-
190
- pb.setState(PlaybackStateCompat.STATE_NONE, 0, 0f)
191
- pb.setActions(controls)
192
- state = pb.build()
193
- mediaSession.get()?.setPlaybackState(state)
194
- mediaSession.get()?.setActive(false)
195
-
196
- nb.setContentTitle("")
197
- nb.setContentText("")
198
- nb.setContentInfo("")
199
-
200
- mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
201
- }
202
-
203
- fun enableRemoteCommand(
204
- name: String,
205
- enabled: Boolean,
206
- ) {
207
- pb = PlaybackStateCompat.Builder()
208
- var controlValue = 0L
209
- when (name) {
210
- "remotePlay" -> controlValue = PlaybackStateCompat.ACTION_PLAY
211
- "remotePause" -> controlValue = PlaybackStateCompat.ACTION_PAUSE
212
- "remoteStop" -> controlValue = PlaybackStateCompat.ACTION_STOP
213
- "remoteTogglePlayPause" -> controlValue = PlaybackStateCompat.ACTION_PLAY_PAUSE
214
- "remoteNextTrack" -> controlValue = PlaybackStateCompat.ACTION_SKIP_TO_NEXT
215
- "remotePreviousTrack" -> controlValue = PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
216
- "remoteSkipForward" -> controlValue = PlaybackStateCompat.ACTION_FAST_FORWARD
217
- "remoteSkipBackward" -> controlValue = PlaybackStateCompat.ACTION_REWIND
218
- "remoteChangePlaybackPosition" -> controlValue = PlaybackStateCompat.ACTION_SEEK_TO
219
- }
220
-
221
- controls =
222
- if (enabled) {
223
- controls or controlValue
224
- } else {
225
- controls and controlValue.inv()
226
- }
227
-
228
- mediaNotificationManager.get()?.updateActions(controls)
229
-
230
- if (hasControl(PlaybackStateCompat.ACTION_REWIND)) {
231
- pb.addCustomAction(
232
- PlaybackStateCompat.CustomAction
233
- .Builder(
234
- "SkipBackward",
235
- "Skip Backward",
236
- R.drawable.skip_backward_15,
237
- ).build(),
238
- )
239
- }
240
-
241
- pb.setActions(controls)
242
-
243
- if (hasControl(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
244
- pb.addCustomAction(
245
- PlaybackStateCompat.CustomAction
246
- .Builder(
247
- "SkipForward",
248
- "Skip Forward",
249
- R.drawable.skip_forward_15,
250
- ).build(),
251
- )
252
- }
253
-
254
- state = pb.build()
255
- mediaSession.get()?.setPlaybackState(state)
256
-
257
- updateNotificationMediaStyle()
258
-
259
- if (mediaSession.get()?.isActive == true) {
260
- mediaNotificationManager.get()?.updateNotification(nb, isPlaying)
261
- }
262
- }
263
-
264
- private fun loadArtwork(
265
- url: String,
266
- local: Boolean,
267
- ): Bitmap? {
268
- var bitmap: Bitmap? = null
269
-
270
- try {
271
- // 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
272
- if (local && !url.startsWith("http")) {
273
- // Gets the drawable from the RN's helper for local resources
274
- val helper = com.facebook.react.views.imagehelper.ResourceDrawableIdHelper.instance
275
- val image = helper.getResourceDrawable(reactContext.get()!!, url)
276
-
277
- bitmap =
278
- if (image is BitmapDrawable) {
279
- image.bitmap
280
- } else {
281
- BitmapFactory.decodeFile(url)
282
- }
283
- } else {
284
- // Open connection to the URL and decodes the image
285
- val con = URL(url).openConnection()
286
- con.connect()
287
- val input = con.getInputStream()
288
- bitmap = BitmapFactory.decodeStream(input)
289
- input.close()
290
- }
291
- } catch (ex: IOException) {
292
- Log.w("MediaSessionManager", "Could not load the artwork", ex)
293
- } catch (ex: IndexOutOfBoundsException) {
294
- Log.w("MediaSessionManager", "Could not load the artwork", ex)
295
- }
296
-
297
- return bitmap
298
- }
299
-
300
- private fun updatePlaybackState(playbackState: Int) {
301
- isPlaying = playbackState == PlaybackStateCompat.STATE_PLAYING
302
-
303
- pb.setState(playbackState, elapsedTime, speed)
304
- pb.setActions(controls)
305
- state = pb.build()
306
- mediaSession.get()?.setPlaybackState(state)
307
- }
308
-
309
- private fun hasControl(control: Long): Boolean = (controls and control) == control
310
-
311
- private fun updateNotificationMediaStyle() {
312
- val style = MediaStyle()
313
- style.setMediaSession(mediaSession.get()?.sessionToken)
314
- var controlCount = 0
315
- if (hasControl(PlaybackStateCompat.ACTION_PLAY) ||
316
- hasControl(PlaybackStateCompat.ACTION_PAUSE) ||
317
- hasControl(
318
- PlaybackStateCompat.ACTION_PLAY_PAUSE,
319
- )
320
- ) {
321
- controlCount += 1
322
- }
323
- if (hasControl(PlaybackStateCompat.ACTION_SKIP_TO_NEXT)) {
324
- controlCount += 1
325
- }
326
- if (hasControl(PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)) {
327
- controlCount += 1
328
- }
329
- if (hasControl(PlaybackStateCompat.ACTION_FAST_FORWARD)) {
330
- controlCount += 1
331
- }
332
- if (hasControl(PlaybackStateCompat.ACTION_REWIND)) {
333
- controlCount += 1
334
- }
335
-
336
- if (hasControl(PlaybackStateCompat.ACTION_SEEK_TO)) {
337
- controlCount += 1
338
- }
339
-
340
- val actions = IntArray(controlCount)
341
- for (i in actions.indices) {
342
- actions[i] = i
343
- }
344
- style.setShowActionsInCompactView(*actions)
345
- nb.setStyle(style)
346
- }
347
- }
@@ -1,273 +0,0 @@
1
- package com.swmansion.audioapi.system
2
-
3
- import android.Manifest
4
- import android.annotation.SuppressLint
5
- import android.app.Notification
6
- import android.app.PendingIntent
7
- import android.app.Service
8
- import android.content.Intent
9
- import android.content.pm.ServiceInfo
10
- import android.content.res.Resources
11
- import android.os.Build
12
- import android.os.IBinder
13
- import android.provider.ContactsContract
14
- import android.support.v4.media.session.PlaybackStateCompat
15
- import android.util.Log
16
- import android.view.KeyEvent
17
- import androidx.annotation.RequiresPermission
18
- import androidx.core.app.NotificationCompat
19
- import androidx.core.app.NotificationManagerCompat
20
- import com.facebook.react.bridge.ReactApplicationContext
21
- import com.swmansion.audioapi.R
22
- import java.lang.ref.WeakReference
23
-
24
- class MediaNotificationManager(
25
- private val reactContext: WeakReference<ReactApplicationContext>,
26
- ) {
27
- private var smallIcon: Int = R.drawable.logo
28
- private var customIcon: Int = 0
29
-
30
- private var play: NotificationCompat.Action? = null
31
- private var pause: NotificationCompat.Action? = null
32
- private var stop: NotificationCompat.Action? = null
33
- private var next: NotificationCompat.Action? = null
34
- private var previous: NotificationCompat.Action? = null
35
- private var skipForward: NotificationCompat.Action? = null
36
- private var skipBackward: NotificationCompat.Action? = null
37
-
38
- companion object {
39
- const val REMOVE_NOTIFICATION: String = "audio_manager_remove_notification"
40
- const val PACKAGE_NAME: String = "com.swmansion.audioapi.system"
41
- const val MEDIA_BUTTON: String = "audio_manager_media_button"
42
- }
43
-
44
- enum class ForegroundAction {
45
- START_FOREGROUND,
46
- STOP_FOREGROUND,
47
- ;
48
-
49
- companion object {
50
- fun fromAction(action: String?): ForegroundAction? = entries.firstOrNull { it.name == action }
51
- }
52
- }
53
-
54
- @SuppressLint("RestrictedApi")
55
- @Synchronized
56
- fun prepareNotification(
57
- builder: NotificationCompat.Builder,
58
- isPlaying: Boolean,
59
- ): Notification {
60
- builder.mActions.clear()
61
-
62
- if (previous != null) {
63
- builder.addAction(previous)
64
- }
65
-
66
- if (skipBackward != null) {
67
- builder.addAction(skipBackward)
68
- }
69
-
70
- if (play != null && !isPlaying) {
71
- builder.addAction(play)
72
- }
73
-
74
- if (pause != null && isPlaying) {
75
- builder.addAction(pause)
76
- }
77
-
78
- if (stop != null) {
79
- builder.addAction(stop)
80
- }
81
-
82
- if (next != null) {
83
- builder.addAction(next)
84
- }
85
-
86
- if (skipForward != null) {
87
- builder.addAction(skipForward)
88
- }
89
-
90
- builder.setSmallIcon(if (customIcon != 0) customIcon else smallIcon)
91
-
92
- val packageName: String? = reactContext.get()?.packageName
93
- val openApp: Intent? = reactContext.get()?.packageManager?.getLaunchIntentForPackage(packageName!!)
94
- try {
95
- builder.setContentIntent(
96
- PendingIntent.getActivity(
97
- reactContext.get(),
98
- 0,
99
- openApp,
100
- PendingIntent.FLAG_IMMUTABLE,
101
- ),
102
- )
103
- } catch (e: Exception) {
104
- Log.w("AudioManagerModule", "Error creating content intent: ${e.message}")
105
- }
106
-
107
- val remove = Intent(REMOVE_NOTIFICATION)
108
- remove.putExtra(PACKAGE_NAME, reactContext.get()?.applicationInfo?.packageName)
109
- builder.setDeleteIntent(
110
- PendingIntent.getBroadcast(
111
- reactContext.get(),
112
- 0,
113
- remove,
114
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
115
- ),
116
- )
117
-
118
- return builder.build()
119
- }
120
-
121
- @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
122
- @Synchronized
123
- fun updateNotification(
124
- builder: NotificationCompat.Builder?,
125
- isPlaying: Boolean,
126
- ) {
127
- NotificationManagerCompat.from(reactContext.get()!!).notify(
128
- MediaSessionManager.NOTIFICATION_ID,
129
- prepareNotification(builder!!, isPlaying),
130
- )
131
- }
132
-
133
- fun cancelNotification() {
134
- NotificationManagerCompat.from(reactContext.get()!!).cancel(MediaSessionManager.NOTIFICATION_ID)
135
- }
136
-
137
- @Synchronized
138
- fun updateActions(mask: Long) {
139
- play = createAction("play", "Play", mask, PlaybackStateCompat.ACTION_PLAY, play)
140
- pause = createAction("pause", "Pause", mask, PlaybackStateCompat.ACTION_PAUSE, pause)
141
- stop = createAction("stop", "Stop", mask, PlaybackStateCompat.ACTION_STOP, stop)
142
- next = createAction("next", "Next", mask, PlaybackStateCompat.ACTION_SKIP_TO_NEXT, next)
143
- previous = createAction("previous", "Previous", mask, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS, previous)
144
- skipForward = createAction("skip_forward_15", "Skip Forward", mask, PlaybackStateCompat.ACTION_FAST_FORWARD, skipForward)
145
- skipBackward = createAction("skip_backward_15", "Skip Backward", mask, PlaybackStateCompat.ACTION_REWIND, skipBackward)
146
- }
147
-
148
- private fun createAction(
149
- iconName: String,
150
- title: String,
151
- mask: Long,
152
- action: Long,
153
- oldAction: NotificationCompat.Action?,
154
- ): NotificationCompat.Action? {
155
- if ((mask and action) == 0L) {
156
- return null
157
- }
158
-
159
- if (oldAction != null) {
160
- return oldAction
161
- }
162
-
163
- val r: Resources? = reactContext.get()?.resources
164
- val packageName: String? = reactContext.get()?.packageName
165
- val icon = r?.getIdentifier(iconName, "drawable", packageName)
166
-
167
- val keyCode = PlaybackStateCompat.toKeyCode(action)
168
- val intent = Intent(MEDIA_BUTTON)
169
- intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keyCode))
170
- intent.putExtra(ContactsContract.Directory.PACKAGE_NAME, packageName)
171
- val i =
172
- PendingIntent.getBroadcast(
173
- reactContext.get(),
174
- keyCode,
175
- intent,
176
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
177
- )
178
-
179
- return NotificationCompat.Action(icon!!, title, i)
180
- }
181
-
182
- class AudioForegroundService : Service() {
183
- private var notification: Notification? = null
184
- private var isServiceStarted = false
185
- private val serviceLock = Any()
186
-
187
- override fun onBind(intent: Intent): IBinder? = null
188
-
189
- private fun startForegroundService() {
190
- synchronized(serviceLock) {
191
- if (!isServiceStarted) {
192
- try {
193
- notification =
194
- MediaSessionManager.mediaNotificationManager
195
- .prepareNotification(
196
- NotificationCompat.Builder(this, MediaSessionManager.CHANNEL_ID),
197
- false,
198
- )
199
-
200
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
201
- startForeground(
202
- MediaSessionManager.NOTIFICATION_ID,
203
- notification!!,
204
- ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST,
205
- )
206
- } else {
207
- startForeground(
208
- MediaSessionManager.NOTIFICATION_ID,
209
- notification,
210
- )
211
- }
212
- isServiceStarted = true
213
- } catch (ex: Exception) {
214
- Log.e("AudioManagerModule", "Error starting foreground service: ${ex.message}")
215
- stopSelf()
216
- }
217
- }
218
- }
219
- }
220
-
221
- override fun onStartCommand(
222
- intent: Intent?,
223
- flags: Int,
224
- startId: Int,
225
- ): Int {
226
- val action = ForegroundAction.fromAction(intent?.action)
227
-
228
- when (action) {
229
- ForegroundAction.START_FOREGROUND -> startForegroundService()
230
- ForegroundAction.STOP_FOREGROUND -> stopForegroundService()
231
- else -> startForegroundService()
232
- }
233
-
234
- return START_NOT_STICKY
235
- }
236
-
237
- private fun stopForegroundService() {
238
- synchronized(serviceLock) {
239
- if (isServiceStarted) {
240
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
241
- stopForeground(STOP_FOREGROUND_REMOVE)
242
- }
243
- isServiceStarted = false
244
- stopSelf()
245
- }
246
- }
247
- }
248
-
249
- override fun onTaskRemoved(rootIntent: Intent?) {
250
- super.onTaskRemoved(rootIntent)
251
- stopForegroundService()
252
- }
253
-
254
- override fun onDestroy() {
255
- synchronized(serviceLock) {
256
- notification = null
257
- isServiceStarted = false
258
- }
259
- super.onDestroy()
260
- }
261
-
262
- override fun onTimeout(startId: Int) {
263
- stopForegroundService()
264
- }
265
-
266
- override fun onTimeout(
267
- startId: Int,
268
- fgsType: Int,
269
- ) {
270
- stopForegroundService()
271
- }
272
- }
273
- }
@@ -1,57 +0,0 @@
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.support.v4.media.session.MediaSessionCompat
8
- import android.view.KeyEvent
9
- import com.facebook.react.bridge.ReactApplicationContext
10
- import com.swmansion.audioapi.AudioAPIModule
11
- import java.lang.ref.WeakReference
12
-
13
- class MediaReceiver(
14
- private val reactContext: WeakReference<ReactApplicationContext>,
15
- private val mediaSession: WeakReference<MediaSessionCompat>,
16
- private val mediaNotificationManager: WeakReference<MediaNotificationManager>,
17
- private val audioAPIModule: WeakReference<AudioAPIModule>,
18
- ) : BroadcastReceiver() {
19
- override fun onReceive(
20
- context: Context?,
21
- intent: Intent?,
22
- ) {
23
- val action = intent!!.action
24
-
25
- if (MediaNotificationManager.REMOVE_NOTIFICATION == action) {
26
- if (!checkApp(intent)) return
27
-
28
- mediaNotificationManager.get()?.cancelNotification()
29
- MediaSessionManager.stopForegroundServiceIfNecessary()
30
- mediaSession.get()?.isActive = false
31
-
32
- audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("closeNotification", mapOf()) // add to ts events
33
- } else if (MediaNotificationManager.MEDIA_BUTTON == action || Intent.ACTION_MEDIA_BUTTON == action) {
34
- if (!intent.hasExtra(Intent.EXTRA_KEY_EVENT)) return
35
- if (!checkApp(intent)) return
36
-
37
- val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
38
- mediaSession.get()?.controller?.dispatchMediaButtonEvent(keyEvent)
39
- } else if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == action) {
40
- mediaSession
41
- .get()
42
- ?.controller
43
- ?.transportControls
44
- ?.pause()
45
- }
46
- }
47
-
48
- private fun checkApp(intent: Intent): Boolean {
49
- if (intent.hasExtra(MediaNotificationManager.PACKAGE_NAME)) {
50
- val name = intent.getStringExtra(MediaNotificationManager.PACKAGE_NAME)
51
- if (!reactContext.get()?.packageName.equals(name)) {
52
- return false
53
- }
54
- }
55
- return true
56
- }
57
- }