one 2.11.5 → 2.12.0
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/margelo/nitro/one/HybridOneAudio.kt +309 -124
- package/dist/cjs/cli/build.cjs +31 -33
- package/dist/cjs/cli/build.native.cjs +31 -33
- package/dist/cjs/cli/build.native.js +31 -33
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/cli/buildPage.cjs +11 -7
- package/dist/cjs/cli/buildPage.native.cjs +11 -7
- package/dist/cjs/cli/buildPage.native.js +11 -7
- package/dist/cjs/cli/buildPage.native.js.map +1 -1
- package/dist/cjs/cli/buildPageWorker.cjs +1 -1
- package/dist/cjs/cli/buildPageWorker.native.cjs +1 -1
- package/dist/cjs/cli/buildPageWorker.native.js +1 -1
- package/dist/cjs/cli/buildPageWorker.native.js.map +1 -1
- package/dist/cjs/headless-server.cjs +2 -5
- package/dist/cjs/headless-server.native.cjs +2 -5
- package/dist/cjs/headless-server.native.js +2 -5
- package/dist/cjs/headless-server.native.js.map +1 -1
- package/dist/cjs/platform/audio/index.cjs +83 -76
- package/dist/cjs/platform/audio/index.native.cjs +2 -2
- package/dist/cjs/platform/audio/index.native.js +2 -2
- package/dist/cjs/platform/audio/index.native.js.map +1 -1
- package/dist/cjs/platform/audio/nowPlaying.cjs +126 -0
- package/dist/cjs/platform/audio/nowPlaying.native.cjs +160 -0
- package/dist/cjs/platform/audio/nowPlaying.native.js +162 -0
- package/dist/cjs/platform/audio/nowPlaying.native.js.map +1 -0
- package/dist/cjs/platform/audio/player.cjs +15 -0
- package/dist/cjs/platform/audio/player.native.cjs +14 -0
- package/dist/cjs/platform/audio/player.native.js +14 -0
- package/dist/cjs/platform/audio/player.native.js.map +1 -1
- package/dist/cjs/platform/audio/unavailable.cjs +1 -1
- package/dist/cjs/platform/audio/unavailable.native.cjs +1 -1
- package/dist/cjs/platform/audio/unavailable.native.js +1 -1
- package/dist/cjs/platform/audio/unavailable.native.js.map +1 -1
- package/dist/cjs/serve-worker.cjs +0 -3
- package/dist/cjs/serve-worker.native.cjs +0 -3
- package/dist/cjs/serve-worker.native.js +0 -3
- package/dist/cjs/serve-worker.native.js.map +1 -1
- package/dist/cjs/server/oneServe.cjs +0 -6
- package/dist/cjs/server/oneServe.native.cjs +0 -6
- package/dist/cjs/server/oneServe.native.js +0 -6
- package/dist/cjs/server/oneServe.native.js.map +1 -1
- package/dist/cjs/server/workerHandler.cjs +12 -10
- package/dist/cjs/server/workerHandler.native.cjs +14 -10
- package/dist/cjs/server/workerHandler.native.js +14 -10
- package/dist/cjs/server/workerHandler.native.js.map +1 -1
- package/dist/cjs/vite/plugins/workerdDevPlugin.cjs +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.cjs +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.js +33 -37
- package/dist/cjs/vite/plugins/workerdDevPlugin.native.js.map +1 -1
- package/dist/esm/cli/build.mjs +31 -33
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +31 -33
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/cli/buildPage.mjs +11 -7
- package/dist/esm/cli/buildPage.mjs.map +1 -1
- package/dist/esm/cli/buildPage.native.js +11 -7
- package/dist/esm/cli/buildPage.native.js.map +1 -1
- package/dist/esm/cli/buildPageWorker.mjs +1 -1
- package/dist/esm/cli/buildPageWorker.mjs.map +1 -1
- package/dist/esm/cli/buildPageWorker.native.js +1 -1
- package/dist/esm/cli/buildPageWorker.native.js.map +1 -1
- package/dist/esm/headless-server.mjs +2 -3
- package/dist/esm/headless-server.mjs.map +1 -1
- package/dist/esm/headless-server.native.js +2 -3
- package/dist/esm/headless-server.native.js.map +1 -1
- package/dist/esm/platform/audio/index.mjs +83 -76
- package/dist/esm/platform/audio/index.mjs.map +1 -1
- package/dist/esm/platform/audio/index.native.js +2 -2
- package/dist/esm/platform/audio/index.native.js.map +1 -1
- package/dist/esm/platform/audio/nowPlaying.mjs +102 -0
- package/dist/esm/platform/audio/nowPlaying.mjs.map +1 -0
- package/dist/esm/platform/audio/nowPlaying.native.js +134 -0
- package/dist/esm/platform/audio/nowPlaying.native.js.map +1 -0
- package/dist/esm/platform/audio/player.mjs +15 -0
- package/dist/esm/platform/audio/player.mjs.map +1 -1
- package/dist/esm/platform/audio/player.native.js +14 -0
- package/dist/esm/platform/audio/player.native.js.map +1 -1
- package/dist/esm/platform/audio/unavailable.mjs +1 -1
- package/dist/esm/platform/audio/unavailable.mjs.map +1 -1
- package/dist/esm/platform/audio/unavailable.native.js +1 -1
- package/dist/esm/platform/audio/unavailable.native.js.map +1 -1
- package/dist/esm/serve-worker.mjs +1 -2
- package/dist/esm/serve-worker.mjs.map +1 -1
- package/dist/esm/serve-worker.native.js +1 -2
- package/dist/esm/serve-worker.native.js.map +1 -1
- package/dist/esm/server/oneServe.mjs +0 -6
- package/dist/esm/server/oneServe.mjs.map +1 -1
- package/dist/esm/server/oneServe.native.js +0 -6
- package/dist/esm/server/oneServe.native.js.map +1 -1
- package/dist/esm/server/workerHandler.mjs +12 -10
- package/dist/esm/server/workerHandler.mjs.map +1 -1
- package/dist/esm/server/workerHandler.native.js +14 -10
- package/dist/esm/server/workerHandler.native.js.map +1 -1
- package/dist/esm/vite/plugins/workerdDevPlugin.mjs +33 -37
- package/dist/esm/vite/plugins/workerdDevPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/workerdDevPlugin.native.js +33 -37
- package/dist/esm/vite/plugins/workerdDevPlugin.native.js.map +1 -1
- package/ios/Nitro/HybridOneAudio.swift +309 -96
- package/nitrogen/generated/android/c++/JAudioPlayOptions.hpp +61 -0
- package/nitrogen/generated/android/c++/JAudioRemoteCommandEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_AudioRemoteCommandEvent.hpp +1 -0
- package/nitrogen/generated/android/c++/JHybridOneAudioSpec.cpp +37 -3
- package/nitrogen/generated/android/c++/JHybridOneAudioSpec.hpp +3 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/AudioPlayOptions.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/AudioRemoteCommandEvent.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/one/HybridOneAudioSpec.kt +40 -32
- package/nitrogen/generated/ios/One-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridOneAudioSpecSwift.hpp +21 -2
- package/nitrogen/generated/ios/swift/AudioPlayOptions.swift +60 -0
- package/nitrogen/generated/ios/swift/AudioRemoteCommandEvent.swift +19 -1
- package/nitrogen/generated/ios/swift/HybridOneAudioSpec.swift +3 -1
- package/nitrogen/generated/ios/swift/HybridOneAudioSpec_cxx.swift +70 -32
- package/nitrogen/generated/shared/c++/AudioPlayOptions.hpp +87 -0
- package/nitrogen/generated/shared/c++/AudioRemoteCommandEvent.hpp +7 -2
- package/nitrogen/generated/shared/c++/HybridOneAudioSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridOneAudioSpec.hpp +6 -1
- package/package.json +13 -13
- package/src/cli/build.ts +35 -32
- package/src/cli/buildPage.test.ts +152 -0
- package/src/cli/buildPage.ts +14 -9
- package/src/cli/buildPageWorker.ts +2 -1
- package/src/cli/workerPool.ts +1 -0
- package/src/headless-server.ts +2 -2
- package/src/index.ts +1 -0
- package/src/platform/audio/index.native.ts +4 -2
- package/src/platform/audio/index.ts +102 -74
- package/src/platform/audio/nowPlaying.ts +122 -0
- package/src/platform/audio/player.native.ts +17 -1
- package/src/platform/audio/player.ts +23 -1
- package/src/platform/audio/types.ts +6 -1
- package/src/platform/audio/unavailable.ts +3 -1
- package/src/platform/index.native.ts +1 -0
- package/src/platform/index.ts +1 -0
- package/src/platform/specs/OneAudio.nitro.ts +11 -1
- package/src/serve-worker.ts +2 -3
- package/src/server/oneServe.ts +0 -6
- package/src/server/workerHandler.ts +34 -18
- package/src/vite/plugins/workerdDevPlugin.ts +33 -37
- package/src/vite/resolveResponse.ts +2 -2
- package/types/cli/build.d.ts.map +1 -1
- package/types/cli/buildPage.d.ts +1 -1
- package/types/cli/buildPage.d.ts.map +1 -1
- package/types/cli/workerPool.d.ts +1 -0
- package/types/cli/workerPool.d.ts.map +1 -1
- package/types/headless-server.d.ts +3 -3
- package/types/headless-server.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/platform/audio/index.d.ts +2 -2
- package/types/platform/audio/index.d.ts.map +1 -1
- package/types/platform/audio/index.native.d.ts +3 -3
- package/types/platform/audio/index.native.d.ts.map +1 -1
- package/types/platform/audio/nowPlaying.d.ts +12 -0
- package/types/platform/audio/nowPlaying.d.ts.map +1 -0
- package/types/platform/audio/player.d.ts +3 -1
- package/types/platform/audio/player.d.ts.map +1 -1
- package/types/platform/audio/player.native.d.ts +3 -1
- package/types/platform/audio/player.native.d.ts.map +1 -1
- package/types/platform/audio/types.d.ts +3 -1
- package/types/platform/audio/types.d.ts.map +1 -1
- package/types/platform/audio/unavailable.d.ts +3 -3
- package/types/platform/audio/unavailable.d.ts.map +1 -1
- package/types/platform/index.d.ts +1 -1
- package/types/platform/index.d.ts.map +1 -1
- package/types/platform/index.native.d.ts +1 -1
- package/types/platform/index.native.d.ts.map +1 -1
- package/types/platform/specs/OneAudio.nitro.d.ts +8 -1
- package/types/platform/specs/OneAudio.nitro.d.ts.map +1 -1
- package/types/serve-worker.d.ts +3 -3
- package/types/serve-worker.d.ts.map +1 -1
- package/types/server/oneServe.d.ts.map +1 -1
- package/types/server/workerHandler.d.ts +1 -1
- package/types/server/workerHandler.d.ts.map +1 -1
- package/types/vite/plugins/workerdDevPlugin.d.ts.map +1 -1
- package/types/vite/resolveResponse.d.ts +1 -1
- package/types/vite/resolveResponse.d.ts.map +1 -1
- package/dist/cjs/server/staticHtmlFetcher.cjs +0 -33
- package/dist/cjs/server/staticHtmlFetcher.native.cjs +0 -35
- package/dist/cjs/server/staticHtmlFetcher.native.js +0 -37
- package/dist/cjs/server/staticHtmlFetcher.native.js.map +0 -1
- package/dist/esm/server/staticHtmlFetcher.mjs +0 -10
- package/dist/esm/server/staticHtmlFetcher.mjs.map +0 -1
- package/dist/esm/server/staticHtmlFetcher.native.js +0 -10
- package/dist/esm/server/staticHtmlFetcher.native.js.map +0 -1
- package/src/server/staticHtmlFetcher.ts +0 -12
- package/types/server/staticHtmlFetcher.d.ts +0 -3
- package/types/server/staticHtmlFetcher.d.ts.map +0 -1
|
@@ -64,6 +64,12 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
64
64
|
private var mediaSession: MediaSession? = null
|
|
65
65
|
private var nowPlayingInfo: AudioNowPlayingInfo? = null
|
|
66
66
|
private var nowPlayingArtworkPath: String? = null
|
|
67
|
+
// one media session at a time, owned by the singleton or by one managed
|
|
68
|
+
// player. owners clear only their own claim, so an old owner's cleanup
|
|
69
|
+
// never clears a newer one.
|
|
70
|
+
private var nowPlayingOwner: NowPlayingOwner? = null
|
|
71
|
+
private var pendingLoad: PendingLoad? = null
|
|
72
|
+
private var serviceRunning = false
|
|
67
73
|
private val interruptionListeners = mutableMapOf<String, (AudioInterruptionEvent) -> Unit>()
|
|
68
74
|
private val remoteCommandListeners = mutableMapOf<String, (AudioRemoteCommandEvent) -> Unit>()
|
|
69
75
|
|
|
@@ -85,6 +91,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
85
91
|
permission?.reject(OneNativeError(E_PERMISSION, "Audio.requestRecordingPermission: torn down mid-request"))
|
|
86
92
|
main.post {
|
|
87
93
|
clearPlayer()
|
|
94
|
+
clearNowPlaying()
|
|
88
95
|
val managed = managedPlayers.values.toList()
|
|
89
96
|
managedPlayers.clear()
|
|
90
97
|
managed.forEach { releaseManagedPlayer(it) }
|
|
@@ -155,9 +162,11 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
155
162
|
return true
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
override fun play(uri: String): Promise<AudioPlaybackStatus> {
|
|
165
|
+
override fun play(uri: String, options: AudioPlayOptions): Promise<AudioPlaybackStatus> {
|
|
159
166
|
val promise = Promise<AudioPlaybackStatus>()
|
|
160
167
|
main.post {
|
|
168
|
+
val prepare = options.positionMs != null || options.paused != null
|
|
169
|
+
val positionMs = options.positionMs ?: 0.0
|
|
161
170
|
if (recorder != null) {
|
|
162
171
|
promise.reject(OneNativeError(E_BUSY, "Audio.play: stop the current audio operation first"))
|
|
163
172
|
return@post
|
|
@@ -178,6 +187,10 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
178
187
|
promise.reject(OneNativeError(E_FILE, "Audio.play: the audio file does not exist"))
|
|
179
188
|
return@post
|
|
180
189
|
}
|
|
190
|
+
if (!positionMs.isFinite() || positionMs < 0) {
|
|
191
|
+
promise.reject(OneNativeError(E_POSITION, "Audio.play: the seek position must be finite and nonnegative"))
|
|
192
|
+
return@post
|
|
193
|
+
}
|
|
181
194
|
try {
|
|
182
195
|
requestFocus()
|
|
183
196
|
clearPlayer()
|
|
@@ -196,28 +209,43 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
196
209
|
player = fresh
|
|
197
210
|
playerUri = parsed.toString()
|
|
198
211
|
playerPrepared = false
|
|
199
|
-
playbackPaused = false
|
|
212
|
+
playbackPaused = options.paused ?: false
|
|
200
213
|
playbackEnded = false
|
|
201
214
|
playbackError = null
|
|
202
215
|
fresh.setOnPreparedListener { mp ->
|
|
216
|
+
if (player !== mp) return@setOnPreparedListener
|
|
203
217
|
playerPrepared = true
|
|
204
|
-
|
|
205
|
-
|
|
218
|
+
val load = pendingLoad
|
|
219
|
+
if (load != null) {
|
|
220
|
+
seekLoad(mp, load)
|
|
221
|
+
return@setOnPreparedListener
|
|
222
|
+
}
|
|
223
|
+
// a pause while loading holds; resume starts it later.
|
|
224
|
+
if (!playbackPaused) mp.start()
|
|
225
|
+
syncService()
|
|
206
226
|
syncNowPlaying()
|
|
207
227
|
}
|
|
208
228
|
fresh.setOnCompletionListener {
|
|
209
229
|
playbackEnded = true
|
|
210
|
-
|
|
230
|
+
syncService()
|
|
211
231
|
syncNowPlaying()
|
|
212
232
|
}
|
|
213
233
|
fresh.setOnErrorListener { _, what, extra ->
|
|
214
234
|
playbackError = "playback failed ($what/$extra)"
|
|
215
|
-
|
|
235
|
+
pendingLoad?.let { load ->
|
|
236
|
+
pendingLoad = null
|
|
237
|
+
load.promise.reject(OneNativeError(E_FAILED, "Audio.play: $playbackError"))
|
|
238
|
+
}
|
|
239
|
+
syncService()
|
|
216
240
|
syncNowPlaying()
|
|
217
241
|
true
|
|
218
242
|
}
|
|
219
243
|
fresh.prepareAsync()
|
|
220
|
-
|
|
244
|
+
if (prepare) {
|
|
245
|
+
pendingLoad = PendingLoad(promise, positionMs)
|
|
246
|
+
} else {
|
|
247
|
+
promise.resolve(playbackStatus())
|
|
248
|
+
}
|
|
221
249
|
} catch (e: Exception) {
|
|
222
250
|
clearPlayer()
|
|
223
251
|
abandonFocus()
|
|
@@ -246,7 +274,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
246
274
|
} catch (e: Exception) {
|
|
247
275
|
}
|
|
248
276
|
playbackPaused = true
|
|
249
|
-
|
|
277
|
+
syncService()
|
|
250
278
|
syncNowPlaying()
|
|
251
279
|
promise.resolve(playbackStatus())
|
|
252
280
|
}
|
|
@@ -267,8 +295,9 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
267
295
|
playbackEnded = false
|
|
268
296
|
}
|
|
269
297
|
playbackPaused = false
|
|
270
|
-
|
|
271
|
-
|
|
298
|
+
// a prepared load starts itself once its seek lands.
|
|
299
|
+
if (playerPrepared && pendingLoad == null) current.start()
|
|
300
|
+
syncService()
|
|
272
301
|
syncNowPlaying()
|
|
273
302
|
promise.resolve(playbackStatus())
|
|
274
303
|
} catch (e: Exception) {
|
|
@@ -286,7 +315,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
286
315
|
return@post
|
|
287
316
|
}
|
|
288
317
|
val current = player
|
|
289
|
-
if (current == null || !playerPrepared) {
|
|
318
|
+
if (current == null || !playerPrepared || pendingLoad != null) {
|
|
290
319
|
promise.reject(OneNativeError(E_STATE, "Audio.seek: the audio operation is not ready"))
|
|
291
320
|
return@post
|
|
292
321
|
}
|
|
@@ -496,65 +525,49 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
496
525
|
override fun setNowPlayingInfo(info: AudioNowPlayingInfo): Promise<Unit> {
|
|
497
526
|
val promise = Promise<Unit>()
|
|
498
527
|
main.post {
|
|
499
|
-
|
|
500
|
-
if (current == null) {
|
|
528
|
+
if (player == null) {
|
|
501
529
|
promise.reject(OneNativeError(E_STATE, "Audio.setNowPlayingInfo: the audio operation is not ready"))
|
|
502
530
|
return@post
|
|
503
531
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
val parsed = try {
|
|
511
|
-
Uri.parse(artworkUri)
|
|
512
|
-
} catch (e: Exception) {
|
|
513
|
-
null
|
|
514
|
-
}
|
|
515
|
-
val file = if (parsed?.scheme == "file") parsed.path?.let { File(it) } else null
|
|
516
|
-
if (file == null || !file.exists() || file.isDirectory ||
|
|
517
|
-
BitmapFactory.decodeFile(file.absolutePath) == null
|
|
518
|
-
) {
|
|
519
|
-
promise.reject(
|
|
520
|
-
OneNativeError(E_ARTWORK, "Audio.setNowPlayingInfo: now playing artwork must be an existing image file")
|
|
521
|
-
)
|
|
522
|
-
return@post
|
|
523
|
-
}
|
|
524
|
-
artworkPath = file.absolutePath
|
|
532
|
+
try {
|
|
533
|
+
val artworkPath = nowPlayingArtwork("setNowPlayingInfo", info)
|
|
534
|
+
claimNowPlaying(NowPlayingOwner.Global, info, artworkPath)
|
|
535
|
+
promise.resolve(Unit)
|
|
536
|
+
} catch (e: OneNativeError) {
|
|
537
|
+
promise.reject(e)
|
|
525
538
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
object : MediaSession.Callback() {
|
|
530
|
-
override fun onPlay() {
|
|
531
|
-
handleRemoteCommand(AudioRemoteCommandType.PLAY, null)
|
|
532
|
-
}
|
|
539
|
+
}
|
|
540
|
+
return promise
|
|
541
|
+
}
|
|
533
542
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
543
|
+
override fun clearNowPlayingInfo(): Promise<Unit> {
|
|
544
|
+
val promise = Promise<Unit>()
|
|
545
|
+
main.post {
|
|
546
|
+
if (nowPlayingOwner == NowPlayingOwner.Global) clearNowPlaying()
|
|
547
|
+
promise.resolve(Unit)
|
|
548
|
+
}
|
|
549
|
+
return promise
|
|
550
|
+
}
|
|
537
551
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
)
|
|
543
|
-
|
|
552
|
+
override fun setPlayerNowPlayingInfo(playerId: String, info: AudioNowPlayingInfo): Promise<Unit> {
|
|
553
|
+
val promise = Promise<Unit>()
|
|
554
|
+
main.post {
|
|
555
|
+
try {
|
|
556
|
+
managedPlayer(playerId)
|
|
557
|
+
val artworkPath = nowPlayingArtwork("setPlayerNowPlayingInfo", info)
|
|
558
|
+
claimNowPlaying(NowPlayingOwner.Managed(playerId), info, artworkPath)
|
|
559
|
+
promise.resolve(Unit)
|
|
560
|
+
} catch (e: OneNativeError) {
|
|
561
|
+
promise.reject(e)
|
|
544
562
|
}
|
|
545
|
-
nowPlayingInfo = info
|
|
546
|
-
nowPlayingArtworkPath = artworkPath
|
|
547
|
-
mediaSession?.isActive = true
|
|
548
|
-
syncNowPlaying()
|
|
549
|
-
promise.resolve(Unit)
|
|
550
563
|
}
|
|
551
564
|
return promise
|
|
552
565
|
}
|
|
553
566
|
|
|
554
|
-
override fun
|
|
567
|
+
override fun clearPlayerNowPlayingInfo(playerId: String): Promise<Unit> {
|
|
555
568
|
val promise = Promise<Unit>()
|
|
556
569
|
main.post {
|
|
557
|
-
clearNowPlaying()
|
|
570
|
+
if (nowPlayingOwner == NowPlayingOwner.Managed(playerId)) clearNowPlaying()
|
|
558
571
|
promise.resolve(Unit)
|
|
559
572
|
}
|
|
560
573
|
return promise
|
|
@@ -582,32 +595,12 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
582
595
|
}
|
|
583
596
|
|
|
584
597
|
override fun playerPlay(playerId: String) {
|
|
585
|
-
mainSync("playerPlay") {
|
|
586
|
-
val managed = managedPlayer(playerId)
|
|
587
|
-
requestFocus()
|
|
588
|
-
val current = managed.player
|
|
589
|
-
if (managed.ended && current != null && managed.prepared) {
|
|
590
|
-
try { current.seekTo(0) } catch (e: Exception) { }
|
|
591
|
-
managed.ended = false
|
|
592
|
-
}
|
|
593
|
-
managed.paused = false
|
|
594
|
-
if (current != null && managed.prepared) {
|
|
595
|
-
applyPlayerRate(managed, current)
|
|
596
|
-
try { current.start() } catch (e: Exception) {
|
|
597
|
-
throw OneNativeError(E_STATE, "Audio.playerPlay: the audio operation is not ready")
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
598
|
+
mainSync("playerPlay") { startManaged(managedPlayer(playerId)) }
|
|
601
599
|
}
|
|
602
600
|
|
|
603
601
|
override fun playerPause(playerId: String) {
|
|
604
602
|
mainSync("playerPause") {
|
|
605
|
-
|
|
606
|
-
try {
|
|
607
|
-
if (managed.player?.isPlaying == true) managed.player?.pause()
|
|
608
|
-
} catch (e: Exception) {
|
|
609
|
-
}
|
|
610
|
-
managed.paused = true
|
|
603
|
+
pauseManaged(managedPlayer(playerId))
|
|
611
604
|
}
|
|
612
605
|
}
|
|
613
606
|
|
|
@@ -639,6 +632,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
639
632
|
current.setOnSeekCompleteListener { mp ->
|
|
640
633
|
if (generation != managed.seekGeneration || managed.player !== mp) return@setOnSeekCompleteListener
|
|
641
634
|
managed.ended = false
|
|
635
|
+
syncManaged(managed)
|
|
642
636
|
promise.resolve(Unit)
|
|
643
637
|
}
|
|
644
638
|
val ms = (seconds * 1000).toLong().coerceAtLeast(0L)
|
|
@@ -661,22 +655,30 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
661
655
|
if (sourceUri != null) attachPlayerSource(managed, sourceUri, headers)
|
|
662
656
|
managed.paused = true
|
|
663
657
|
managed.ended = false
|
|
658
|
+
syncManaged(managed)
|
|
664
659
|
}
|
|
665
660
|
}
|
|
666
661
|
|
|
667
662
|
override fun removePlayer(playerId: String) {
|
|
668
663
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
669
|
-
|
|
664
|
+
discardPlayer(playerId)
|
|
670
665
|
return
|
|
671
666
|
}
|
|
672
667
|
val latch = CountDownLatch(1)
|
|
673
668
|
main.post {
|
|
674
|
-
|
|
669
|
+
discardPlayer(playerId)
|
|
675
670
|
latch.countDown()
|
|
676
671
|
}
|
|
677
672
|
latch.await()
|
|
678
673
|
}
|
|
679
674
|
|
|
675
|
+
private fun discardPlayer(playerId: String) {
|
|
676
|
+
val managed = managedPlayers.remove(playerId) ?: return
|
|
677
|
+
if (nowPlayingOwner == NowPlayingOwner.Managed(playerId)) clearNowPlaying()
|
|
678
|
+
releaseManagedPlayer(managed)
|
|
679
|
+
syncService()
|
|
680
|
+
}
|
|
681
|
+
|
|
680
682
|
override fun getPlayerStatus(playerId: String): AudioPlayerStatus =
|
|
681
683
|
mainSync("getPlayerStatus") { playerStatus(managedPlayer(playerId)) }
|
|
682
684
|
|
|
@@ -743,6 +745,142 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
743
745
|
|
|
744
746
|
private val managedPlayers = mutableMapOf<String, ManagedAudioPlayer>()
|
|
745
747
|
|
|
748
|
+
private sealed class NowPlayingOwner {
|
|
749
|
+
object Global : NowPlayingOwner()
|
|
750
|
+
data class Managed(val id: String) : NowPlayingOwner()
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// a singleton play with a position or paused flag: it resolves after the
|
|
754
|
+
// player is prepared and the seek lands, before any sound.
|
|
755
|
+
private class PendingLoad(val promise: Promise<AudioPlaybackStatus>, val positionMs: Double)
|
|
756
|
+
|
|
757
|
+
private class OwnerState(val playing: Boolean, val positionMs: Double, val durationMs: Double?)
|
|
758
|
+
|
|
759
|
+
private fun seekLoad(current: MediaPlayer, load: PendingLoad) {
|
|
760
|
+
val duration = try { current.duration } catch (e: Exception) { -1 }
|
|
761
|
+
val target = if (duration >= 0) minOf(load.positionMs.toLong(), duration.toLong()) else load.positionMs.toLong()
|
|
762
|
+
val generation = ++seekGeneration
|
|
763
|
+
try {
|
|
764
|
+
current.setOnSeekCompleteListener { mp ->
|
|
765
|
+
// replacement or stop rejects the load in clearPlayer, so a
|
|
766
|
+
// late completion finds a different load and does nothing.
|
|
767
|
+
if (generation != seekGeneration || player !== mp || pendingLoad !== load) {
|
|
768
|
+
return@setOnSeekCompleteListener
|
|
769
|
+
}
|
|
770
|
+
pendingLoad = null
|
|
771
|
+
playbackEnded = false
|
|
772
|
+
if (!playbackPaused) {
|
|
773
|
+
try { mp.start() } catch (e: Exception) { }
|
|
774
|
+
}
|
|
775
|
+
syncService()
|
|
776
|
+
syncNowPlaying()
|
|
777
|
+
load.promise.resolve(playbackStatus())
|
|
778
|
+
}
|
|
779
|
+
if (Build.VERSION.SDK_INT >= 26) {
|
|
780
|
+
current.seekTo(target, MediaPlayer.SEEK_CLOSEST)
|
|
781
|
+
} else {
|
|
782
|
+
current.seekTo(target.coerceAtMost(Int.MAX_VALUE.toLong()).toInt())
|
|
783
|
+
}
|
|
784
|
+
} catch (e: Exception) {
|
|
785
|
+
pendingLoad = null
|
|
786
|
+
load.promise.reject(OneNativeError(E_FAILED, "Audio.play: ${e.message ?: "the seek failed"}"))
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private fun startManaged(managed: ManagedAudioPlayer) {
|
|
791
|
+
requestFocus()
|
|
792
|
+
val current = managed.player
|
|
793
|
+
if (managed.ended && current != null && managed.prepared) {
|
|
794
|
+
try { current.seekTo(0) } catch (e: Exception) { }
|
|
795
|
+
managed.ended = false
|
|
796
|
+
}
|
|
797
|
+
managed.paused = false
|
|
798
|
+
if (current != null && managed.prepared) {
|
|
799
|
+
applyPlayerRate(managed, current)
|
|
800
|
+
try { current.start() } catch (e: Exception) {
|
|
801
|
+
throw OneNativeError(E_STATE, "Audio.playerPlay: the audio operation is not ready")
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
syncManaged(managed)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private fun pauseManaged(managed: ManagedAudioPlayer) {
|
|
808
|
+
try {
|
|
809
|
+
if (managed.player?.isPlaying == true) managed.player?.pause()
|
|
810
|
+
} catch (e: Exception) {
|
|
811
|
+
}
|
|
812
|
+
managed.paused = true
|
|
813
|
+
syncManaged(managed)
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private fun ownerManaged(): ManagedAudioPlayer? =
|
|
817
|
+
(nowPlayingOwner as? NowPlayingOwner.Managed)?.let { managedPlayers[it.id] }
|
|
818
|
+
|
|
819
|
+
private fun syncManaged(managed: ManagedAudioPlayer) {
|
|
820
|
+
if (ownerManaged() !== managed) return
|
|
821
|
+
syncService()
|
|
822
|
+
syncNowPlaying()
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
private fun nowPlayingArtwork(verb: String, info: AudioNowPlayingInfo): String? {
|
|
826
|
+
if (info.title.trim().isEmpty()) {
|
|
827
|
+
throw OneNativeError(E_METADATA, "Audio.$verb: now playing title must not be empty")
|
|
828
|
+
}
|
|
829
|
+
val artworkUri = info.artworkUri ?: return null
|
|
830
|
+
val parsed = try {
|
|
831
|
+
Uri.parse(artworkUri)
|
|
832
|
+
} catch (e: Exception) {
|
|
833
|
+
null
|
|
834
|
+
}
|
|
835
|
+
val file = if (parsed?.scheme == "file") parsed.path?.let { File(it) } else null
|
|
836
|
+
if (file == null || !file.exists() || file.isDirectory ||
|
|
837
|
+
BitmapFactory.decodeFile(file.absolutePath) == null
|
|
838
|
+
) {
|
|
839
|
+
throw OneNativeError(E_ARTWORK, "Audio.$verb: now playing artwork must be an existing image file")
|
|
840
|
+
}
|
|
841
|
+
return file.absolutePath
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// a transfer keeps the media session and swaps the owner, so the system
|
|
845
|
+
// controls stay up while ownership moves.
|
|
846
|
+
private fun claimNowPlaying(owner: NowPlayingOwner, info: AudioNowPlayingInfo, artworkPath: String?) {
|
|
847
|
+
if (mediaSession == null) {
|
|
848
|
+
val session = MediaSession(context, "OneAudio")
|
|
849
|
+
session.setCallback(
|
|
850
|
+
object : MediaSession.Callback() {
|
|
851
|
+
override fun onPlay() {
|
|
852
|
+
handleRemoteCommand(AudioRemoteCommandType.PLAY, null)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
override fun onPause() {
|
|
856
|
+
handleRemoteCommand(AudioRemoteCommandType.PAUSE, null)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
override fun onSeekTo(pos: Long) {
|
|
860
|
+
handleRemoteCommand(AudioRemoteCommandType.SEEK, pos.toDouble())
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
)
|
|
864
|
+
mediaSession = session
|
|
865
|
+
}
|
|
866
|
+
nowPlayingOwner = owner
|
|
867
|
+
nowPlayingInfo = info
|
|
868
|
+
nowPlayingArtworkPath = artworkPath
|
|
869
|
+
mediaSession?.isActive = true
|
|
870
|
+
syncService()
|
|
871
|
+
syncNowPlaying()
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private fun ownerState(): OwnerState? = when (val owner = nowPlayingOwner) {
|
|
875
|
+
is NowPlayingOwner.Global -> if (player == null) null else playbackStatus().let {
|
|
876
|
+
OwnerState(it.state == AudioPlaybackState.PLAYING, it.positionMs, it.durationMs)
|
|
877
|
+
}
|
|
878
|
+
is NowPlayingOwner.Managed -> managedPlayers[owner.id]?.let { playerStatus(it) }?.let {
|
|
879
|
+
OwnerState(it.playing, it.currentTime * 1000, if (it.duration > 0) it.duration * 1000 else null)
|
|
880
|
+
}
|
|
881
|
+
null -> null
|
|
882
|
+
}
|
|
883
|
+
|
|
746
884
|
private fun <T> mainSync(verb: String, work: () -> T): T {
|
|
747
885
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
748
886
|
try {
|
|
@@ -820,6 +958,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
820
958
|
applyPlayerRate(managed, mp)
|
|
821
959
|
if (!managed.paused && !managed.ended) {
|
|
822
960
|
try { mp.start() } catch (e: Exception) { }
|
|
961
|
+
syncManaged(managed)
|
|
823
962
|
} else {
|
|
824
963
|
// a freshly prepared MediaPlayer can report isPlaying on
|
|
825
964
|
// some emulators without ever being started; stop any
|
|
@@ -834,10 +973,12 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
834
973
|
fresh.setOnCompletionListener { mp ->
|
|
835
974
|
if (managed.player !== mp) return@setOnCompletionListener
|
|
836
975
|
managed.ended = true
|
|
976
|
+
syncManaged(managed)
|
|
837
977
|
}
|
|
838
978
|
fresh.setOnErrorListener { mp, what, extra ->
|
|
839
979
|
if (managed.player !== mp) return@setOnErrorListener true
|
|
840
980
|
managed.error = "playback failed ($what/$extra)"
|
|
981
|
+
syncManaged(managed)
|
|
841
982
|
true
|
|
842
983
|
}
|
|
843
984
|
managed.player = fresh
|
|
@@ -944,7 +1085,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
944
1085
|
} catch (e: Exception) {
|
|
945
1086
|
}
|
|
946
1087
|
}
|
|
947
|
-
|
|
1088
|
+
syncService()
|
|
948
1089
|
syncNowPlaying()
|
|
949
1090
|
emitInterruption(AudioInterruptionEvent(AudioInterruptionType.BEGAN, false))
|
|
950
1091
|
}
|
|
@@ -970,42 +1111,75 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
970
1111
|
|
|
971
1112
|
private fun handleRemoteCommand(type: AudioRemoteCommandType, positionMs: Double?) {
|
|
972
1113
|
main.post {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1114
|
+
if (mediaSession == null) return@post
|
|
1115
|
+
val owner = nowPlayingOwner ?: return@post
|
|
1116
|
+
if (type == AudioRemoteCommandType.SEEK &&
|
|
1117
|
+
(positionMs == null || !positionMs.isFinite() || positionMs < 0)
|
|
1118
|
+
) return@post
|
|
1119
|
+
val seekMs = (positionMs ?: 0.0).toLong().coerceAtMost(Int.MAX_VALUE.toLong()).toInt()
|
|
1120
|
+
var playerId: String? = null
|
|
1121
|
+
when (owner) {
|
|
1122
|
+
is NowPlayingOwner.Global -> {
|
|
1123
|
+
val current = player ?: return@post
|
|
1124
|
+
when (type) {
|
|
1125
|
+
AudioRemoteCommandType.PLAY -> {
|
|
1126
|
+
try {
|
|
1127
|
+
if (playbackEnded) {
|
|
1128
|
+
current.seekTo(0)
|
|
1129
|
+
playbackEnded = false
|
|
1130
|
+
}
|
|
1131
|
+
playbackPaused = false
|
|
1132
|
+
if (playerPrepared && pendingLoad == null) current.start()
|
|
1133
|
+
} catch (e: Exception) {
|
|
1134
|
+
return@post
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
AudioRemoteCommandType.PAUSE -> {
|
|
1138
|
+
try {
|
|
1139
|
+
if (current.isPlaying) current.pause()
|
|
1140
|
+
} catch (e: Exception) {
|
|
1141
|
+
}
|
|
1142
|
+
playbackPaused = true
|
|
1143
|
+
}
|
|
1144
|
+
AudioRemoteCommandType.SEEK -> {
|
|
1145
|
+
if (pendingLoad != null) return@post
|
|
1146
|
+
try {
|
|
1147
|
+
current.seekTo(seekMs)
|
|
1148
|
+
} catch (e: Exception) {
|
|
1149
|
+
return@post
|
|
1150
|
+
}
|
|
980
1151
|
playbackEnded = false
|
|
981
1152
|
}
|
|
982
|
-
playbackPaused = false
|
|
983
|
-
if (playerPrepared) current.start()
|
|
984
|
-
startServiceIfBackground()
|
|
985
|
-
} catch (e: Exception) {
|
|
986
|
-
return@post
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
AudioRemoteCommandType.PAUSE -> {
|
|
990
|
-
try {
|
|
991
|
-
if (current.isPlaying) current.pause()
|
|
992
|
-
} catch (e: Exception) {
|
|
993
1153
|
}
|
|
994
|
-
playbackPaused = true
|
|
995
|
-
stopService()
|
|
996
1154
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1155
|
+
is NowPlayingOwner.Managed -> {
|
|
1156
|
+
val managed = managedPlayers[owner.id] ?: return@post
|
|
1157
|
+
playerId = owner.id
|
|
1158
|
+
when (type) {
|
|
1159
|
+
AudioRemoteCommandType.PLAY -> try {
|
|
1160
|
+
startManaged(managed)
|
|
1161
|
+
} catch (e: Exception) {
|
|
1162
|
+
return@post
|
|
1163
|
+
}
|
|
1164
|
+
AudioRemoteCommandType.PAUSE -> pauseManaged(managed)
|
|
1165
|
+
AudioRemoteCommandType.SEEK -> {
|
|
1166
|
+
val current = managed.player
|
|
1167
|
+
if (current == null || !managed.prepared) return@post
|
|
1168
|
+
// retire any pending playerSeek listener first.
|
|
1169
|
+
managed.seekGeneration += 1
|
|
1170
|
+
try {
|
|
1171
|
+
current.seekTo(seekMs)
|
|
1172
|
+
} catch (e: Exception) {
|
|
1173
|
+
return@post
|
|
1174
|
+
}
|
|
1175
|
+
managed.ended = false
|
|
1176
|
+
}
|
|
1003
1177
|
}
|
|
1004
|
-
playbackEnded = false
|
|
1005
1178
|
}
|
|
1006
1179
|
}
|
|
1180
|
+
syncService()
|
|
1007
1181
|
syncNowPlaying()
|
|
1008
|
-
val event = AudioRemoteCommandEvent(type, positionMs)
|
|
1182
|
+
val event = AudioRemoteCommandEvent(type, positionMs, playerId)
|
|
1009
1183
|
val listeners = synchronized(lock) { remoteCommandListeners.values.toList() }
|
|
1010
1184
|
for (listener in listeners) {
|
|
1011
1185
|
try {
|
|
@@ -1022,7 +1196,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1022
1196
|
return AudioPlaybackStatus(AudioPlaybackState.IDLE, null, 0.0, null, null)
|
|
1023
1197
|
}
|
|
1024
1198
|
// duration and position are unavailable until preparation completes.
|
|
1025
|
-
if (!playerPrepared || playbackError != null) {
|
|
1199
|
+
if (!playerPrepared || pendingLoad != null || playbackError != null) {
|
|
1026
1200
|
return AudioPlaybackStatus(
|
|
1027
1201
|
if (playbackError != null) AudioPlaybackState.FAILED else AudioPlaybackState.LOADING,
|
|
1028
1202
|
playerUri,
|
|
@@ -1083,8 +1257,8 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1083
1257
|
private fun syncNowPlaying() {
|
|
1084
1258
|
val session = mediaSession
|
|
1085
1259
|
val info = nowPlayingInfo
|
|
1086
|
-
val
|
|
1087
|
-
if (session == null || info == null ||
|
|
1260
|
+
val status = ownerState()
|
|
1261
|
+
if (session == null || info == null || status == null) return
|
|
1088
1262
|
val metadata = MediaMetadata.Builder()
|
|
1089
1263
|
.putString(MediaMetadata.METADATA_KEY_TITLE, info.title)
|
|
1090
1264
|
info.artist?.let { metadata.putString(MediaMetadata.METADATA_KEY_ARTIST, it) }
|
|
@@ -1097,11 +1271,10 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1097
1271
|
} catch (e: Exception) {
|
|
1098
1272
|
}
|
|
1099
1273
|
}
|
|
1100
|
-
val status = playbackStatus()
|
|
1101
1274
|
status.durationMs?.let { metadata.putLong(MediaMetadata.METADATA_KEY_DURATION, it.toLong()) }
|
|
1102
1275
|
session.setMetadata(metadata.build())
|
|
1103
1276
|
val position = status.positionMs.toLong()
|
|
1104
|
-
val playing = status.
|
|
1277
|
+
val playing = status.playing
|
|
1105
1278
|
session.setPlaybackState(
|
|
1106
1279
|
PlaybackState.Builder()
|
|
1107
1280
|
.setActions(
|
|
@@ -1126,10 +1299,15 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1126
1299
|
mediaSession = null
|
|
1127
1300
|
nowPlayingInfo = null
|
|
1128
1301
|
nowPlayingArtworkPath = null
|
|
1302
|
+
nowPlayingOwner = null
|
|
1129
1303
|
}
|
|
1130
1304
|
|
|
1131
1305
|
private fun clearPlayer() {
|
|
1132
|
-
clearNowPlaying()
|
|
1306
|
+
if (nowPlayingOwner == NowPlayingOwner.Global) clearNowPlaying()
|
|
1307
|
+
pendingLoad?.let { load ->
|
|
1308
|
+
pendingLoad = null
|
|
1309
|
+
load.promise.reject(OneNativeError(E_STATE, "Audio.play: playback was replaced before it was ready"))
|
|
1310
|
+
}
|
|
1133
1311
|
seekGeneration++
|
|
1134
1312
|
try {
|
|
1135
1313
|
player?.setOnSeekCompleteListener(null)
|
|
@@ -1149,7 +1327,7 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1149
1327
|
playbackPaused = false
|
|
1150
1328
|
playbackEnded = false
|
|
1151
1329
|
playbackError = null
|
|
1152
|
-
|
|
1330
|
+
syncService()
|
|
1153
1331
|
}
|
|
1154
1332
|
|
|
1155
1333
|
private fun teardownRecorder(deleteFile: Boolean) {
|
|
@@ -1249,14 +1427,21 @@ class HybridOneAudio : HybridOneAudioSpec(), PermissionListener {
|
|
|
1249
1427
|
}
|
|
1250
1428
|
}
|
|
1251
1429
|
|
|
1252
|
-
|
|
1430
|
+
// the foreground service runs while the singleton or the managed now
|
|
1431
|
+
// playing owner is playing.
|
|
1432
|
+
private fun syncService() {
|
|
1253
1433
|
if (!backgroundEnabled()) return
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
if (
|
|
1259
|
-
|
|
1434
|
+
val owner = ownerManaged()
|
|
1435
|
+
val playing = (player != null && playerPrepared && pendingLoad == null && !playbackPaused &&
|
|
1436
|
+
!playbackEnded && playbackError == null) ||
|
|
1437
|
+
(owner != null && owner.player != null && !owner.paused && !owner.ended && owner.error == null)
|
|
1438
|
+
if (playing) {
|
|
1439
|
+
OneAudioService.start(context, nowPlayingInfo?.title ?: "Audio playing")
|
|
1440
|
+
serviceRunning = true
|
|
1441
|
+
} else if (serviceRunning) {
|
|
1442
|
+
OneAudioService.stop(context)
|
|
1443
|
+
serviceRunning = false
|
|
1444
|
+
}
|
|
1260
1445
|
}
|
|
1261
1446
|
|
|
1262
1447
|
private fun permissionStatus(): AudioRecordingPermission {
|