react-native-audio-api 0.11.0-nightly-db51488-20251207 → 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.
- package/android/src/main/java/com/swmansion/audioapi/AudioAPIModule.kt +164 -16
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioPlayer.kt +10 -8
- package/android/src/main/java/com/swmansion/audioapi/core/NativeAudioRecorder.kt +10 -8
- package/android/src/main/java/com/swmansion/audioapi/system/AudioFocusListener.kt +8 -23
- package/android/src/main/java/com/swmansion/audioapi/system/CentralizedForegroundService.kt +127 -0
- package/android/src/main/java/com/swmansion/audioapi/system/ForegroundServiceManager.kt +116 -0
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionManager.kt +115 -107
- package/android/src/main/java/com/swmansion/audioapi/system/PermissionRequestListener.kt +2 -1
- package/android/src/main/java/com/swmansion/audioapi/system/notification/BaseNotification.kt +47 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/NotificationRegistry.kt +191 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotification.kt +668 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/PlaybackNotificationReceiver.kt +33 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotification.kt +303 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/RecordingNotificationReceiver.kt +43 -0
- package/android/src/main/java/com/swmansion/audioapi/system/notification/SimpleNotification.kt +119 -0
- package/ios/audioapi/ios/AudioAPIModule.h +2 -2
- package/ios/audioapi/ios/AudioAPIModule.mm +108 -18
- package/ios/audioapi/ios/system/AudioEngine.mm +2 -2
- package/ios/audioapi/ios/system/AudioSessionManager.mm +1 -1
- package/ios/audioapi/ios/system/NotificationManager.mm +1 -1
- package/ios/audioapi/ios/system/notification/BaseNotification.h +58 -0
- package/ios/audioapi/ios/system/notification/NotificationRegistry.h +70 -0
- package/ios/audioapi/ios/system/notification/NotificationRegistry.mm +172 -0
- package/ios/audioapi/ios/system/notification/PlaybackNotification.h +27 -0
- package/ios/audioapi/ios/system/notification/PlaybackNotification.mm +427 -0
- package/lib/commonjs/api.js +59 -10
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/api.web.js +27 -14
- package/lib/commonjs/api.web.js.map +1 -1
- package/lib/commonjs/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/commonjs/system/AudioManager.js +6 -9
- package/lib/commonjs/system/AudioManager.js.map +1 -1
- package/lib/commonjs/system/index.js +13 -0
- package/lib/commonjs/system/index.js.map +1 -1
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js +135 -0
- package/lib/commonjs/system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.js +182 -0
- package/lib/commonjs/system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/SimpleNotificationManager.js +122 -0
- package/lib/commonjs/system/notification/SimpleNotificationManager.js.map +1 -0
- package/lib/commonjs/system/notification/index.js +45 -0
- package/lib/commonjs/system/notification/index.js.map +1 -0
- package/lib/commonjs/system/notification/types.js +6 -0
- package/lib/commonjs/system/notification/types.js.map +1 -0
- package/lib/commonjs/web-system/index.js +17 -0
- package/lib/commonjs/web-system/index.js.map +1 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js +34 -0
- package/lib/commonjs/web-system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js +34 -0
- package/lib/commonjs/web-system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/commonjs/web-system/notification/index.js +21 -0
- package/lib/commonjs/web-system/notification/index.js.map +1 -0
- package/lib/module/api.js +5 -1
- package/lib/module/api.js.map +1 -1
- package/lib/module/api.web.js +3 -1
- package/lib/module/api.web.js.map +1 -1
- package/lib/module/specs/NativeAudioAPIModule.js.map +1 -1
- package/lib/module/system/AudioManager.js +6 -9
- package/lib/module/system/AudioManager.js.map +1 -1
- package/lib/module/system/index.js +1 -0
- package/lib/module/system/index.js.map +1 -1
- package/lib/module/system/notification/PlaybackNotificationManager.js +131 -0
- package/lib/module/system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/module/system/notification/RecordingNotificationManager.js +178 -0
- package/lib/module/system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/module/system/notification/SimpleNotificationManager.js +118 -0
- package/lib/module/system/notification/SimpleNotificationManager.js.map +1 -0
- package/lib/module/system/notification/index.js +7 -0
- package/lib/module/system/notification/index.js.map +1 -0
- package/lib/module/system/notification/types.js +4 -0
- package/lib/module/system/notification/types.js.map +1 -0
- package/lib/module/web-system/index.js +4 -0
- package/lib/module/web-system/index.js.map +1 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js +30 -0
- package/lib/module/web-system/notification/PlaybackNotificationManager.js.map +1 -0
- package/lib/module/web-system/notification/RecordingNotificationManager.js +30 -0
- package/lib/module/web-system/notification/RecordingNotificationManager.js.map +1 -0
- package/lib/module/web-system/notification/index.js +5 -0
- package/lib/module/web-system/notification/index.js.map +1 -0
- package/lib/typescript/api.d.ts +3 -1
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/api.web.d.ts +3 -1
- package/lib/typescript/api.web.d.ts.map +1 -1
- package/lib/typescript/events/types.d.ts +4 -18
- package/lib/typescript/events/types.d.ts.map +1 -1
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts +16 -5
- package/lib/typescript/specs/NativeAudioAPIModule.d.ts.map +1 -1
- package/lib/typescript/system/AudioManager.d.ts +4 -5
- package/lib/typescript/system/AudioManager.d.ts.map +1 -1
- package/lib/typescript/system/index.d.ts +1 -0
- package/lib/typescript/system/index.d.ts.map +1 -1
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts +22 -0
- package/lib/typescript/system/notification/PlaybackNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts +23 -0
- package/lib/typescript/system/notification/RecordingNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts +20 -0
- package/lib/typescript/system/notification/SimpleNotificationManager.d.ts.map +1 -0
- package/lib/typescript/system/notification/index.d.ts +5 -0
- package/lib/typescript/system/notification/index.d.ts.map +1 -0
- package/lib/typescript/system/notification/types.d.ts +65 -0
- package/lib/typescript/system/notification/types.d.ts.map +1 -0
- package/lib/typescript/system/types.d.ts +0 -16
- package/lib/typescript/system/types.d.ts.map +1 -1
- package/lib/typescript/web-system/index.d.ts +2 -0
- package/lib/typescript/web-system/index.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts +19 -0
- package/lib/typescript/web-system/notification/PlaybackNotificationManager.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts +19 -0
- package/lib/typescript/web-system/notification/RecordingNotificationManager.d.ts.map +1 -0
- package/lib/typescript/web-system/notification/index.d.ts +3 -0
- package/lib/typescript/web-system/notification/index.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/api.ts +17 -2
- package/src/api.web.ts +7 -2
- package/src/events/types.ts +4 -20
- package/src/specs/NativeAudioAPIModule.ts +23 -7
- package/src/system/AudioManager.ts +10 -23
- package/src/system/index.ts +1 -0
- package/src/system/notification/PlaybackNotificationManager.ts +193 -0
- package/src/system/notification/RecordingNotificationManager.ts +242 -0
- package/src/system/notification/SimpleNotificationManager.ts +170 -0
- package/src/system/notification/index.ts +4 -0
- package/src/system/notification/types.ts +110 -0
- package/src/system/types.ts +0 -18
- package/src/web-system/index.ts +1 -0
- package/src/web-system/notification/PlaybackNotificationManager.ts +60 -0
- package/src/web-system/notification/RecordingNotificationManager.ts +60 -0
- package/src/web-system/notification/index.ts +2 -0
- package/android/src/main/java/com/swmansion/audioapi/system/LockScreenManager.kt +0 -347
- package/android/src/main/java/com/swmansion/audioapi/system/MediaNotificationManager.kt +0 -273
- package/android/src/main/java/com/swmansion/audioapi/system/MediaReceiver.kt +0 -57
- package/android/src/main/java/com/swmansion/audioapi/system/MediaSessionCallback.kt +0 -61
- package/ios/audioapi/ios/system/LockScreenManager.h +0 -23
- 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
|
-
}
|