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.
- 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,6 +1,7 @@
|
|
|
1
1
|
package com.swmansion.audioapi
|
|
2
2
|
|
|
3
3
|
import com.facebook.jni.HybridData
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
4
5
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
5
6
|
import com.facebook.react.bridge.Promise
|
|
6
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
@@ -9,6 +10,7 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
9
10
|
import com.facebook.react.common.annotations.FrameworkAPI
|
|
10
11
|
import com.facebook.react.module.annotations.ReactModule
|
|
11
12
|
import com.facebook.react.turbomodule.core.CallInvokerHolderImpl
|
|
13
|
+
import com.swmansion.audioapi.system.ForegroundServiceManager
|
|
12
14
|
import com.swmansion.audioapi.system.MediaSessionManager
|
|
13
15
|
import com.swmansion.audioapi.system.PermissionRequestListener
|
|
14
16
|
import java.lang.ref.WeakReference
|
|
@@ -84,7 +86,8 @@ class AudioAPIModule(
|
|
|
84
86
|
|
|
85
87
|
override fun invalidate() {
|
|
86
88
|
reactContext.get()?.removeLifecycleEventListener(this)
|
|
87
|
-
//
|
|
89
|
+
// Cleanup foreground service manager
|
|
90
|
+
ForegroundServiceManager.cleanup()
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
override fun getDevicePreferredSampleRate(): Double = MediaSessionManager.getDevicePreferredSampleRate()
|
|
@@ -109,21 +112,6 @@ class AudioAPIModule(
|
|
|
109
112
|
// nothing to do here
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
override fun setLockScreenInfo(info: ReadableMap?) {
|
|
113
|
-
MediaSessionManager.setLockScreenInfo(info)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
override fun resetLockScreenInfo() {
|
|
117
|
-
MediaSessionManager.resetLockScreenInfo()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
override fun enableRemoteCommand(
|
|
121
|
-
name: String?,
|
|
122
|
-
enabled: Boolean,
|
|
123
|
-
) {
|
|
124
|
-
MediaSessionManager.enableRemoteCommand(name!!, enabled)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
115
|
override fun observeAudioInterruptions(enabled: Boolean) {
|
|
128
116
|
MediaSessionManager.observeAudioInterruptions(enabled)
|
|
129
117
|
}
|
|
@@ -145,7 +133,167 @@ class AudioAPIModule(
|
|
|
145
133
|
promise.resolve(MediaSessionManager.checkRecordingPermissions())
|
|
146
134
|
}
|
|
147
135
|
|
|
136
|
+
override fun requestNotificationPermissions(promise: Promise) {
|
|
137
|
+
val permissionRequestListener = PermissionRequestListener(promise)
|
|
138
|
+
MediaSessionManager.requestNotificationPermissions(permissionRequestListener)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override fun checkNotificationPermissions(promise: Promise) {
|
|
142
|
+
promise.resolve(MediaSessionManager.checkNotificationPermissions())
|
|
143
|
+
}
|
|
144
|
+
|
|
148
145
|
override fun getDevicesInfo(promise: Promise) {
|
|
149
146
|
promise.resolve(MediaSessionManager.getDevicesInfo())
|
|
150
147
|
}
|
|
148
|
+
|
|
149
|
+
// New notification system methods
|
|
150
|
+
override fun registerNotification(
|
|
151
|
+
type: String?,
|
|
152
|
+
key: String?,
|
|
153
|
+
promise: Promise?,
|
|
154
|
+
) {
|
|
155
|
+
try {
|
|
156
|
+
if (type == null || key == null) {
|
|
157
|
+
val result = Arguments.createMap()
|
|
158
|
+
result.putBoolean("success", false)
|
|
159
|
+
result.putString("error", "Type and key are required")
|
|
160
|
+
promise?.resolve(result)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
MediaSessionManager.registerNotification(type, key)
|
|
165
|
+
|
|
166
|
+
val result = Arguments.createMap()
|
|
167
|
+
result.putBoolean("success", true)
|
|
168
|
+
promise?.resolve(result)
|
|
169
|
+
} catch (e: Exception) {
|
|
170
|
+
val result = Arguments.createMap()
|
|
171
|
+
result.putBoolean("success", false)
|
|
172
|
+
result.putString("error", e.message ?: "Unknown error")
|
|
173
|
+
promise?.resolve(result)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override fun showNotification(
|
|
178
|
+
key: String?,
|
|
179
|
+
options: ReadableMap?,
|
|
180
|
+
promise: Promise?,
|
|
181
|
+
) {
|
|
182
|
+
try {
|
|
183
|
+
if (key == null) {
|
|
184
|
+
val result = Arguments.createMap()
|
|
185
|
+
result.putBoolean("success", false)
|
|
186
|
+
result.putString("error", "Key is required")
|
|
187
|
+
promise?.resolve(result)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
MediaSessionManager.showNotification(key, options)
|
|
192
|
+
|
|
193
|
+
val result = Arguments.createMap()
|
|
194
|
+
result.putBoolean("success", true)
|
|
195
|
+
promise?.resolve(result)
|
|
196
|
+
} catch (e: Exception) {
|
|
197
|
+
val result = Arguments.createMap()
|
|
198
|
+
result.putBoolean("success", false)
|
|
199
|
+
result.putString("error", e.message ?: "Unknown error")
|
|
200
|
+
promise?.resolve(result)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
override fun updateNotification(
|
|
205
|
+
key: String?,
|
|
206
|
+
options: ReadableMap?,
|
|
207
|
+
promise: Promise?,
|
|
208
|
+
) {
|
|
209
|
+
try {
|
|
210
|
+
if (key == null) {
|
|
211
|
+
val result = Arguments.createMap()
|
|
212
|
+
result.putBoolean("success", false)
|
|
213
|
+
result.putString("error", "Key is required")
|
|
214
|
+
promise?.resolve(result)
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
MediaSessionManager.updateNotification(key, options)
|
|
219
|
+
|
|
220
|
+
val result = Arguments.createMap()
|
|
221
|
+
result.putBoolean("success", true)
|
|
222
|
+
promise?.resolve(result)
|
|
223
|
+
} catch (e: Exception) {
|
|
224
|
+
val result = Arguments.createMap()
|
|
225
|
+
result.putBoolean("success", false)
|
|
226
|
+
result.putString("error", e.message ?: "Unknown error")
|
|
227
|
+
promise?.resolve(result)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
override fun hideNotification(
|
|
232
|
+
key: String?,
|
|
233
|
+
promise: Promise?,
|
|
234
|
+
) {
|
|
235
|
+
try {
|
|
236
|
+
if (key == null) {
|
|
237
|
+
val result = Arguments.createMap()
|
|
238
|
+
result.putBoolean("success", false)
|
|
239
|
+
result.putString("error", "Key is required")
|
|
240
|
+
promise?.resolve(result)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
MediaSessionManager.hideNotification(key)
|
|
245
|
+
|
|
246
|
+
val result = Arguments.createMap()
|
|
247
|
+
result.putBoolean("success", true)
|
|
248
|
+
promise?.resolve(result)
|
|
249
|
+
} catch (e: Exception) {
|
|
250
|
+
val result = Arguments.createMap()
|
|
251
|
+
result.putBoolean("success", false)
|
|
252
|
+
result.putString("error", e.message ?: "Unknown error")
|
|
253
|
+
promise?.resolve(result)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
override fun unregisterNotification(
|
|
258
|
+
key: String?,
|
|
259
|
+
promise: Promise?,
|
|
260
|
+
) {
|
|
261
|
+
try {
|
|
262
|
+
if (key == null) {
|
|
263
|
+
val result = Arguments.createMap()
|
|
264
|
+
result.putBoolean("success", false)
|
|
265
|
+
result.putString("error", "Key is required")
|
|
266
|
+
promise?.resolve(result)
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
MediaSessionManager.unregisterNotification(key)
|
|
271
|
+
|
|
272
|
+
val result = Arguments.createMap()
|
|
273
|
+
result.putBoolean("success", true)
|
|
274
|
+
promise?.resolve(result)
|
|
275
|
+
} catch (e: Exception) {
|
|
276
|
+
val result = Arguments.createMap()
|
|
277
|
+
result.putBoolean("success", false)
|
|
278
|
+
result.putString("error", e.message ?: "Unknown error")
|
|
279
|
+
promise?.resolve(result)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
override fun isNotificationActive(
|
|
284
|
+
key: String?,
|
|
285
|
+
promise: Promise?,
|
|
286
|
+
) {
|
|
287
|
+
try {
|
|
288
|
+
if (key == null) {
|
|
289
|
+
promise?.resolve(false)
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
val isActive = MediaSessionManager.isNotificationActive(key)
|
|
294
|
+
promise?.resolve(isActive)
|
|
295
|
+
} catch (e: Exception) {
|
|
296
|
+
promise?.resolve(false)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
151
299
|
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
package com.swmansion.audioapi.core
|
|
2
2
|
|
|
3
3
|
import com.facebook.common.internal.DoNotStrip
|
|
4
|
-
import com.swmansion.audioapi.system.
|
|
4
|
+
import com.swmansion.audioapi.system.ForegroundServiceManager
|
|
5
|
+
import java.util.UUID
|
|
5
6
|
|
|
6
7
|
@DoNotStrip
|
|
7
8
|
class NativeAudioPlayer {
|
|
8
|
-
private var
|
|
9
|
+
private var playerId: String? = null
|
|
9
10
|
|
|
10
11
|
@DoNotStrip
|
|
11
12
|
fun start() {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (playerId == null) {
|
|
14
|
+
playerId = UUID.randomUUID().toString()
|
|
15
|
+
ForegroundServiceManager.subscribe("player_$playerId")
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
@DoNotStrip
|
|
17
20
|
fun stop() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
playerId?.let {
|
|
22
|
+
ForegroundServiceManager.unsubscribe("player_$it")
|
|
23
|
+
playerId = null
|
|
21
24
|
}
|
|
22
|
-
MediaSessionManager.stopForegroundServiceIfNecessary()
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
package com.swmansion.audioapi.core
|
|
2
2
|
|
|
3
3
|
import com.facebook.common.internal.DoNotStrip
|
|
4
|
-
import com.swmansion.audioapi.system.
|
|
4
|
+
import com.swmansion.audioapi.system.ForegroundServiceManager
|
|
5
|
+
import java.util.UUID
|
|
5
6
|
|
|
6
7
|
@DoNotStrip
|
|
7
8
|
class NativeAudioRecorder {
|
|
8
|
-
private var
|
|
9
|
+
private var recorderId: String? = null
|
|
9
10
|
|
|
10
11
|
@DoNotStrip
|
|
11
12
|
fun start() {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (recorderId == null) {
|
|
14
|
+
recorderId = UUID.randomUUID().toString()
|
|
15
|
+
ForegroundServiceManager.subscribe("recorder_$recorderId")
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
@DoNotStrip
|
|
17
20
|
fun stop() {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
recorderId?.let {
|
|
22
|
+
ForegroundServiceManager.unsubscribe("recorder_$it")
|
|
23
|
+
recorderId = null
|
|
21
24
|
}
|
|
22
|
-
MediaSessionManager.stopForegroundServiceIfNecessary()
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -11,52 +11,37 @@ import java.util.HashMap
|
|
|
11
11
|
class AudioFocusListener(
|
|
12
12
|
private val audioManager: WeakReference<AudioManager>,
|
|
13
13
|
private val audioAPIModule: WeakReference<AudioAPIModule>,
|
|
14
|
-
private val lockScreenManager: WeakReference<LockScreenManager>,
|
|
15
14
|
) : AudioManager.OnAudioFocusChangeListener {
|
|
16
|
-
private var playOnAudioFocus: Boolean = false
|
|
17
15
|
private var focusRequest: AudioFocusRequest? = null
|
|
18
16
|
|
|
19
17
|
override fun onAudioFocusChange(focusChange: Int) {
|
|
20
18
|
Log.d("AudioFocusListener", "onAudioFocusChange: $focusChange")
|
|
21
19
|
when (focusChange) {
|
|
22
20
|
AudioManager.AUDIOFOCUS_LOSS -> {
|
|
23
|
-
playOnAudioFocus = false
|
|
24
21
|
val body =
|
|
25
22
|
HashMap<String, Any>().apply {
|
|
26
23
|
put("type", "began")
|
|
27
|
-
put("
|
|
24
|
+
put("isTransient", false)
|
|
28
25
|
}
|
|
29
26
|
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
|
|
33
|
-
playOnAudioFocus = lockScreenManager.get()?.isPlaying == true
|
|
34
30
|
val body =
|
|
35
31
|
HashMap<String, Any>().apply {
|
|
36
32
|
put("type", "began")
|
|
37
|
-
put("
|
|
33
|
+
put("isTransient", true)
|
|
38
34
|
}
|
|
39
35
|
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
AudioManager.AUDIOFOCUS_GAIN -> {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
|
|
50
|
-
} else {
|
|
51
|
-
val body =
|
|
52
|
-
HashMap<String, Any>().apply {
|
|
53
|
-
put("type", "ended")
|
|
54
|
-
put("shouldResume", false)
|
|
55
|
-
}
|
|
56
|
-
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
playOnAudioFocus = false
|
|
39
|
+
val body =
|
|
40
|
+
HashMap<String, Any>().apply {
|
|
41
|
+
put("type", "ended")
|
|
42
|
+
put("isTransient", false)
|
|
43
|
+
}
|
|
44
|
+
audioAPIModule.get()?.invokeHandlerWithEventNameAndEventBody("interruption", body)
|
|
60
45
|
}
|
|
61
46
|
}
|
|
62
47
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system
|
|
2
|
+
|
|
3
|
+
import android.app.Notification
|
|
4
|
+
import android.app.NotificationChannel
|
|
5
|
+
import android.app.NotificationManager
|
|
6
|
+
import android.app.Service
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import android.content.Intent
|
|
9
|
+
import android.content.pm.ServiceInfo
|
|
10
|
+
import android.os.Build
|
|
11
|
+
import android.os.IBinder
|
|
12
|
+
import android.util.Log
|
|
13
|
+
import androidx.core.app.NotificationCompat
|
|
14
|
+
import com.swmansion.audioapi.system.MediaSessionManager.CHANNEL_ID
|
|
15
|
+
import com.swmansion.audioapi.system.notification.NotificationRegistry
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Centralized foreground service that can be used by any component that needs foreground capabilities.
|
|
19
|
+
*/
|
|
20
|
+
class CentralizedForegroundService : Service() {
|
|
21
|
+
companion object {
|
|
22
|
+
private const val TAG = "CentralizedForegroundService"
|
|
23
|
+
private const val NOTIFICATION_ID = 100
|
|
24
|
+
const val ACTION_START = "START_FOREGROUND"
|
|
25
|
+
const val ACTION_STOP = "STOP_FOREGROUND"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
29
|
+
|
|
30
|
+
override fun onStartCommand(
|
|
31
|
+
intent: Intent?,
|
|
32
|
+
flags: Int,
|
|
33
|
+
startId: Int,
|
|
34
|
+
): Int {
|
|
35
|
+
when (intent?.action) {
|
|
36
|
+
ACTION_START -> {
|
|
37
|
+
startForegroundWithNotification()
|
|
38
|
+
}
|
|
39
|
+
ACTION_STOP -> {
|
|
40
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
41
|
+
stopSelf()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return START_NOT_STICKY
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun startForegroundWithNotification() {
|
|
48
|
+
try {
|
|
49
|
+
createNotificationChannelIfNeeded()
|
|
50
|
+
|
|
51
|
+
// Try to use an existing notification first
|
|
52
|
+
val existingNotification = findExistingNotification()
|
|
53
|
+
val (notificationId, notification) =
|
|
54
|
+
if (existingNotification != null) {
|
|
55
|
+
existingNotification
|
|
56
|
+
} else {
|
|
57
|
+
// Fallback to default service notification
|
|
58
|
+
NOTIFICATION_ID to createServiceNotification()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
62
|
+
startForeground(
|
|
63
|
+
notificationId,
|
|
64
|
+
notification,
|
|
65
|
+
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK,
|
|
66
|
+
)
|
|
67
|
+
} else {
|
|
68
|
+
startForeground(notificationId, notification)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Log.d(TAG, "Centralized foreground service started with notification ID: $notificationId")
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
Log.e(TAG, "Error starting foreground service: ${e.message}", e)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private fun findExistingNotification(): Pair<Int, Notification>? {
|
|
78
|
+
// Check for recording notification first (priority)
|
|
79
|
+
NotificationRegistry.getBuiltNotification(101)?.let {
|
|
80
|
+
return 101 to it
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for playback notification
|
|
84
|
+
NotificationRegistry.getBuiltNotification(100)?.let {
|
|
85
|
+
return 100 to it
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun createServiceNotification(): Notification =
|
|
92
|
+
NotificationCompat
|
|
93
|
+
.Builder(this, CHANNEL_ID)
|
|
94
|
+
.setContentTitle("Audio Service")
|
|
95
|
+
.setContentText("Audio processing in progress")
|
|
96
|
+
.setSmallIcon(android.R.drawable.ic_btn_speak_now)
|
|
97
|
+
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
98
|
+
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
99
|
+
.setOngoing(true)
|
|
100
|
+
.setAutoCancel(false)
|
|
101
|
+
.build()
|
|
102
|
+
|
|
103
|
+
private fun createNotificationChannelIfNeeded() {
|
|
104
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
105
|
+
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
106
|
+
|
|
107
|
+
if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
|
|
108
|
+
val channel =
|
|
109
|
+
NotificationChannel(
|
|
110
|
+
CHANNEL_ID,
|
|
111
|
+
"Audio Service",
|
|
112
|
+
NotificationManager.IMPORTANCE_LOW,
|
|
113
|
+
).apply {
|
|
114
|
+
description = "Background audio processing"
|
|
115
|
+
setShowBadge(false)
|
|
116
|
+
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
|
|
117
|
+
}
|
|
118
|
+
notificationManager.createNotificationChannel(channel)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override fun onDestroy() {
|
|
124
|
+
Log.d(TAG, "Centralized foreground service destroyed")
|
|
125
|
+
super.onDestroy()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
package com.swmansion.audioapi.system
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import java.lang.ref.WeakReference
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Centralized manager for foreground service lifecycle.
|
|
11
|
+
* Handles starting/stopping foreground service based on active subscribers.
|
|
12
|
+
*/
|
|
13
|
+
object ForegroundServiceManager {
|
|
14
|
+
private const val TAG = "ForegroundServiceManager"
|
|
15
|
+
|
|
16
|
+
private lateinit var reactContext: WeakReference<ReactApplicationContext>
|
|
17
|
+
private val subscribers = mutableSetOf<String>()
|
|
18
|
+
private var isServiceRunning = false
|
|
19
|
+
|
|
20
|
+
fun initialize(reactContext: WeakReference<ReactApplicationContext>) {
|
|
21
|
+
this.reactContext = reactContext
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Subscribe to foreground service. Service will start if not already running.
|
|
26
|
+
* @param subscriberId Unique identifier for the subscriber
|
|
27
|
+
*/
|
|
28
|
+
@Synchronized
|
|
29
|
+
fun subscribe(subscriberId: String) {
|
|
30
|
+
if (subscribers.add(subscriberId)) {
|
|
31
|
+
Log.d(TAG, "Subscriber added: $subscriberId (total: ${subscribers.size})")
|
|
32
|
+
startServiceIfNeeded()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Unsubscribe from foreground service. Service will stop if no more subscribers.
|
|
38
|
+
* @param subscriberId Unique identifier for the subscriber
|
|
39
|
+
*/
|
|
40
|
+
@Synchronized
|
|
41
|
+
fun unsubscribe(subscriberId: String) {
|
|
42
|
+
if (subscribers.remove(subscriberId)) {
|
|
43
|
+
Log.d(TAG, "Subscriber removed: $subscriberId (total: ${subscribers.size})")
|
|
44
|
+
stopServiceIfNotNeeded()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get count of active subscribers
|
|
50
|
+
*/
|
|
51
|
+
fun getSubscriberCount(): Int = subscribers.size
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if service is currently running
|
|
55
|
+
*/
|
|
56
|
+
fun isServiceRunning(): Boolean = isServiceRunning
|
|
57
|
+
|
|
58
|
+
private fun startServiceIfNeeded() {
|
|
59
|
+
if (!isServiceRunning && subscribers.isNotEmpty()) {
|
|
60
|
+
startForegroundService()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private fun stopServiceIfNotNeeded() {
|
|
65
|
+
if (isServiceRunning && subscribers.isEmpty()) {
|
|
66
|
+
stopForegroundService()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private fun startForegroundService() {
|
|
71
|
+
val context = reactContext.get() ?: return
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
val intent = Intent(context, CentralizedForegroundService::class.java)
|
|
75
|
+
intent.action = CentralizedForegroundService.ACTION_START
|
|
76
|
+
|
|
77
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
78
|
+
context.startForegroundService(intent)
|
|
79
|
+
} else {
|
|
80
|
+
context.startService(intent)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isServiceRunning = true
|
|
84
|
+
Log.d(TAG, "Centralized foreground service started")
|
|
85
|
+
} catch (e: Exception) {
|
|
86
|
+
Log.e(TAG, "Error starting foreground service: ${e.message}", e)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private fun stopForegroundService() {
|
|
91
|
+
val context = reactContext.get() ?: return
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
val intent = Intent(context, CentralizedForegroundService::class.java)
|
|
95
|
+
intent.action = CentralizedForegroundService.ACTION_STOP
|
|
96
|
+
|
|
97
|
+
context.startService(intent)
|
|
98
|
+
isServiceRunning = false
|
|
99
|
+
Log.d(TAG, "Centralized foreground service stopped")
|
|
100
|
+
} catch (e: Exception) {
|
|
101
|
+
Log.e(TAG, "Error stopping foreground service: ${e.message}", e)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Cleanup all subscribers and stop service
|
|
107
|
+
*/
|
|
108
|
+
fun cleanup() {
|
|
109
|
+
synchronized(this) {
|
|
110
|
+
subscribers.clear()
|
|
111
|
+
if (isServiceRunning) {
|
|
112
|
+
stopForegroundService()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|