expo-gliph-player 1.3.13 → 1.3.15
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/gliphplayer/GliphPlayerModule.kt +22 -15
- package/android/src/main/java/com/gliphplayer/GliphPlayerService.kt +147 -79
- package/ios/GliphAudioPlayer.swift +71 -44
- package/ios/GliphPlayerModule.mm +18 -4
- package/lib/typescript/src/GliphPlayer.d.ts +35 -5
- package/package.json +1 -1
- package/src/GliphPlayer.ts +312 -305
- package/src/specs/NativeGliphPlayer.ts +86 -82
|
@@ -148,33 +148,40 @@ class GliphPlayerModule(
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
151
|
+
@ReactMethod
|
|
152
|
+
override fun skip(index: Double, initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
153
|
+
scope.launch {
|
|
154
|
+
try {
|
|
155
|
+
val pos = if (initialPosition < 0) -1.0 else initialPosition
|
|
156
|
+
playerService?.skip(index.toInt(), pos, autoPlay)
|
|
157
|
+
promise.resolve(null)
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
promise.reject("skip_error", e.message, e)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
158
162
|
}
|
|
159
|
-
}
|
|
160
163
|
|
|
161
164
|
@ReactMethod
|
|
162
|
-
override fun skipToNext(initialPosition: Double, promise: Promise) {
|
|
165
|
+
override fun skipToNext(initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
163
166
|
scope.launch {
|
|
164
167
|
try {
|
|
165
|
-
playerService?.skipToNext(if (initialPosition < 0) -1.0 else initialPosition)
|
|
168
|
+
playerService?.skipToNext(if (initialPosition < 0) -1.0 else initialPosition, autoPlay)
|
|
166
169
|
promise.resolve(null)
|
|
167
|
-
} catch (e: Exception) {
|
|
170
|
+
} catch (e: Exception) {
|
|
171
|
+
promise.reject("skip_error", e.message, e)
|
|
172
|
+
}
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
@ReactMethod
|
|
172
|
-
override fun skipToPrevious(initialPosition: Double, promise: Promise) {
|
|
177
|
+
override fun skipToPrevious(initialPosition: Double, autoPlay: Boolean, promise: Promise) {
|
|
173
178
|
scope.launch {
|
|
174
179
|
try {
|
|
175
|
-
playerService?.skipToPrevious(if (initialPosition < 0) -1.0 else initialPosition)
|
|
180
|
+
playerService?.skipToPrevious(if (initialPosition < 0) -1.0 else initialPosition, autoPlay)
|
|
176
181
|
promise.resolve(null)
|
|
177
|
-
} catch (e: Exception) {
|
|
182
|
+
} catch (e: Exception) {
|
|
183
|
+
promise.reject("skip_error", e.message, e)
|
|
184
|
+
}
|
|
178
185
|
}
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -357,4 +364,4 @@ class GliphPlayerModule(
|
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
override fun getName() = NAME
|
|
360
|
-
}
|
|
367
|
+
}
|
|
@@ -12,6 +12,7 @@ import android.os.IBinder
|
|
|
12
12
|
import androidx.media3.common.*
|
|
13
13
|
import androidx.media3.common.util.UnstableApi
|
|
14
14
|
import androidx.media3.exoplayer.ExoPlayer
|
|
15
|
+
import androidx.media3.exoplayer.DefaultLoadControl
|
|
15
16
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
|
16
17
|
import androidx.media3.session.*
|
|
17
18
|
import androidx.core.app.NotificationCompat
|
|
@@ -52,16 +53,12 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
52
53
|
private val binder = LocalBinder()
|
|
53
54
|
|
|
54
55
|
override fun onBind(intent: Intent?): IBinder? {
|
|
55
|
-
// MediaLibraryService.onBind handles media browser connections.
|
|
56
|
-
// For our local binding from GliphPlayerModule, we return our LocalBinder.
|
|
57
|
-
// We must check the action to distinguish the two callers.
|
|
58
56
|
val action = intent?.action
|
|
59
57
|
if (action == null ||
|
|
60
58
|
action == "com.gliphplayer.BIND_LOCAL" ||
|
|
61
59
|
action == Intent.ACTION_MAIN) {
|
|
62
60
|
return binder
|
|
63
61
|
}
|
|
64
|
-
// Let MediaLibraryService handle MediaBrowser / MediaSession connections
|
|
65
62
|
return super.onBind(intent)
|
|
66
63
|
}
|
|
67
64
|
|
|
@@ -75,7 +72,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
75
72
|
private var progressJob: Job? = null
|
|
76
73
|
private var options: ReadableMap? = null
|
|
77
74
|
|
|
78
|
-
// Internal queue (mirrors ExoPlayer's playlist)
|
|
79
75
|
private val queue = java.util.Collections.synchronizedList(mutableListOf<ReadableMap>())
|
|
80
76
|
|
|
81
77
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -96,7 +92,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
96
92
|
|
|
97
93
|
override fun onCreate() {
|
|
98
94
|
super.onCreate()
|
|
99
|
-
// 1. Create high-importance notification channel
|
|
100
95
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
101
96
|
val channel = android.app.NotificationChannel(
|
|
102
97
|
CHANNEL_ID,
|
|
@@ -111,7 +106,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
111
106
|
manager.createNotificationChannel(channel)
|
|
112
107
|
}
|
|
113
108
|
|
|
114
|
-
// 2. Immediate Foregrounding with a safe system icon
|
|
115
109
|
val initialNotification = NotificationCompat.Builder(this, CHANNEL_ID)
|
|
116
110
|
.setSmallIcon(android.R.drawable.ic_media_play)
|
|
117
111
|
.setContentTitle("Gliph Player")
|
|
@@ -126,7 +120,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
126
120
|
startForeground(NOTIFICATION_ID, initialNotification)
|
|
127
121
|
}
|
|
128
122
|
|
|
129
|
-
// 3. Initialize player
|
|
130
123
|
initPlayer()
|
|
131
124
|
}
|
|
132
125
|
|
|
@@ -135,8 +128,8 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
135
128
|
when (intent?.action) {
|
|
136
129
|
ACTION_PLAY -> play()
|
|
137
130
|
ACTION_PAUSE -> pause()
|
|
138
|
-
ACTION_SKIP_NEXT -> skipToNext(-1.0)
|
|
139
|
-
ACTION_SKIP_PREVIOUS -> skipToPrevious(-1.0)
|
|
131
|
+
ACTION_SKIP_NEXT -> skipToNext(-1.0, true)
|
|
132
|
+
ACTION_SKIP_PREVIOUS -> skipToPrevious(-1.0, true)
|
|
140
133
|
ACTION_STOP -> {
|
|
141
134
|
stop()
|
|
142
135
|
stopSelf()
|
|
@@ -171,38 +164,39 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
171
164
|
// ── Init ────────────────────────────────────────────────────────────────────
|
|
172
165
|
|
|
173
166
|
private fun initPlayer(opts: ReadableMap? = null) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
167
|
+
val playBufferSec = getDouble(opts, "playBuffer", 2.5)
|
|
168
|
+
val minBufferSec = getDouble(opts, "minBuffer", 15.0)
|
|
169
|
+
val maxBufferSec = getDouble(opts, "maxBuffer", 50.0)
|
|
170
|
+
val backBufferSec = getDouble(opts, "backBuffer", 0.0)
|
|
171
|
+
|
|
172
|
+
val playBufferMs = (playBufferSec * 1000).toInt() // 2500ms
|
|
173
|
+
var minBufferMs = (minBufferSec * 1000).toInt() // 15000ms
|
|
174
|
+
val maxBufferMs = (maxBufferSec * 1000).toInt() // 50000ms
|
|
175
|
+
val backBufferMs = (backBufferSec * 1000).toInt() // 0ms
|
|
176
|
+
|
|
177
|
+
if (minBufferMs < playBufferMs) {
|
|
178
|
+
Log.w("GliphPlayer", "minBufferMs ($minBufferMs) < playBufferMs ($playBufferMs), adjusting...")
|
|
179
|
+
minBufferMs = playBufferMs
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
val loadControl = DefaultLoadControl.Builder()
|
|
183
|
+
.setBufferDurationsMs(minBufferMs, maxBufferMs, playBufferMs, playBufferMs)
|
|
184
|
+
.setBackBuffer(backBufferMs, false)
|
|
185
|
+
.build()
|
|
192
186
|
|
|
193
187
|
player = ExoPlayer.Builder(this)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
188
|
+
.setMediaSourceFactory(DefaultMediaSourceFactory(this))
|
|
189
|
+
.setLoadControl(loadControl)
|
|
190
|
+
.setHandleAudioBecomingNoisy(true)
|
|
191
|
+
.setWakeMode(C.WAKE_MODE_NETWORK)
|
|
192
|
+
.setAudioAttributes(
|
|
193
|
+
AudioAttributes.Builder()
|
|
194
|
+
.setUsage(C.USAGE_MEDIA)
|
|
195
|
+
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
|
196
|
+
.build(),
|
|
197
|
+
true
|
|
198
|
+
)
|
|
199
|
+
.build()
|
|
206
200
|
|
|
207
201
|
player.addListener(playerListener)
|
|
208
202
|
|
|
@@ -313,10 +307,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
313
307
|
Log.d("GliphPlayer", "setupPlayer called with opts: $opts")
|
|
314
308
|
options = opts
|
|
315
309
|
|
|
316
|
-
// ── Fix #5: Android-specific options (appKilledPlaybackBehavior, audioUsage,
|
|
317
|
-
// audioContentType) are applied HERE at setup time, not in updateOptions.
|
|
318
|
-
// This avoids Codegen type mismatches and ensures the ExoPlayer AudioAttributes
|
|
319
|
-
// are configured before the first track is loaded.
|
|
320
310
|
val androidOpts = if (opts.hasKey("android")) opts.getMap("android") else null
|
|
321
311
|
val audioUsageStr = getString(androidOpts, "audioUsage", null)
|
|
322
312
|
val audioUsage = when (audioUsageStr) {
|
|
@@ -324,24 +314,22 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
324
314
|
"alarm" -> C.USAGE_ALARM
|
|
325
315
|
"notification" -> C.USAGE_NOTIFICATION
|
|
326
316
|
"game" -> C.USAGE_GAME
|
|
327
|
-
else -> C.USAGE_MEDIA
|
|
317
|
+
else -> C.USAGE_MEDIA
|
|
328
318
|
}
|
|
329
319
|
val audioContentTypeStr = getString(androidOpts, "audioContentType", null)
|
|
330
320
|
val audioContentType = when (audioContentTypeStr) {
|
|
331
321
|
"speech" -> C.AUDIO_CONTENT_TYPE_SPEECH
|
|
332
322
|
"sonification" -> C.AUDIO_CONTENT_TYPE_SONIFICATION
|
|
333
323
|
"movie" -> C.AUDIO_CONTENT_TYPE_MOVIE
|
|
334
|
-
else -> C.AUDIO_CONTENT_TYPE_MUSIC
|
|
324
|
+
else -> C.AUDIO_CONTENT_TYPE_MUSIC
|
|
335
325
|
}
|
|
336
326
|
|
|
337
|
-
// Re-init player with the correct buffer settings and audio attributes
|
|
338
327
|
if (::player.isInitialized) {
|
|
339
328
|
player.release()
|
|
340
329
|
mediaSession.release()
|
|
341
330
|
}
|
|
342
331
|
initPlayer(opts)
|
|
343
332
|
|
|
344
|
-
// Apply audio attributes after init (ExoPlayer allows this before first play)
|
|
345
333
|
player.setAudioAttributes(
|
|
346
334
|
AudioAttributes.Builder()
|
|
347
335
|
.setUsage(audioUsage)
|
|
@@ -366,6 +354,38 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
366
354
|
}
|
|
367
355
|
}
|
|
368
356
|
|
|
357
|
+
// ── 🆕 HELPER: Отправка события об изменении активного трека ──────────────
|
|
358
|
+
|
|
359
|
+
private fun emitActiveTrackChanged(lastIndex: Int = -1) {
|
|
360
|
+
val newIndex = player.currentMediaItemIndex
|
|
361
|
+
val map = Arguments.createMap()
|
|
362
|
+
map.putInt("index", newIndex)
|
|
363
|
+
map.putInt("lastIndex", if (lastIndex >= 0) lastIndex else newIndex)
|
|
364
|
+
map.putDouble("lastPosition", player.currentPosition / 1000.0)
|
|
365
|
+
|
|
366
|
+
if (newIndex >= 0 && newIndex < queue.size) {
|
|
367
|
+
map.putMap("track", Arguments.makeNativeMap(queue[newIndex].toHashMap()))
|
|
368
|
+
} else {
|
|
369
|
+
map.putNull("track")
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (lastIndex >= 0 && lastIndex < queue.size) {
|
|
373
|
+
map.putMap("lastTrack", Arguments.makeNativeMap(queue[lastIndex].toHashMap()))
|
|
374
|
+
} else {
|
|
375
|
+
map.putNull("lastTrack")
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
eventEmitter?.invoke("playback-active-track-changed", map)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ── 🆕 HELPER: Отправка состояния ──────────────────────────────────────────
|
|
382
|
+
|
|
383
|
+
private fun emitPlaybackState(state: String) {
|
|
384
|
+
val map = Arguments.createMap()
|
|
385
|
+
map.putString("state", state)
|
|
386
|
+
eventEmitter?.invoke("playback-state", map)
|
|
387
|
+
}
|
|
388
|
+
|
|
369
389
|
// ── Queue management ────────────────────────────────────────────────────────
|
|
370
390
|
|
|
371
391
|
fun add(tracks: ReadableArray, insertBeforeIndex: Int): Int {
|
|
@@ -375,11 +395,13 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
375
395
|
insertBeforeIndex
|
|
376
396
|
}
|
|
377
397
|
|
|
398
|
+
val wasEmpty = queue.isEmpty()
|
|
378
399
|
val mediaItems = mutableListOf<MediaItem>()
|
|
400
|
+
|
|
379
401
|
for (i in 0 until tracks.size()) {
|
|
380
402
|
val track = tracks.getMap(i) ?: continue
|
|
381
403
|
Log.d("GliphPlayer", "Adding track: ${track.getString("title")} (URL: ${track.getString("url")})")
|
|
382
|
-
queue.add(startIndex +
|
|
404
|
+
queue.add(startIndex + i, track)
|
|
383
405
|
mediaItems.add(buildMediaItem(track))
|
|
384
406
|
}
|
|
385
407
|
|
|
@@ -389,6 +411,10 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
389
411
|
player.addMediaItems(insertBeforeIndex, mediaItems)
|
|
390
412
|
}
|
|
391
413
|
|
|
414
|
+
if (wasEmpty && queue.isNotEmpty()) {
|
|
415
|
+
emitActiveTrackChanged()
|
|
416
|
+
}
|
|
417
|
+
|
|
392
418
|
updateMediaNotification()
|
|
393
419
|
return startIndex
|
|
394
420
|
}
|
|
@@ -416,24 +442,57 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
416
442
|
}
|
|
417
443
|
}
|
|
418
444
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
445
|
+
fun skip(index: Int, initialPosition: Double, autoPlay: Boolean = true) {
|
|
446
|
+
val lastIndex = player.currentMediaItemIndex
|
|
447
|
+
|
|
448
|
+
if (index < 0 || index >= queue.size) {
|
|
449
|
+
Log.e("GliphPlayer", "skip: index $index out of bounds")
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
player.seekTo(index, if (initialPosition >= 0) (initialPosition * 1000).toLong() else C.TIME_UNSET)
|
|
423
454
|
|
|
424
|
-
|
|
455
|
+
if (autoPlay) {
|
|
456
|
+
player.playWhenReady = true
|
|
457
|
+
|
|
458
|
+
if (player.playbackState == Player.STATE_IDLE) {
|
|
459
|
+
player.prepare()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
player.play()
|
|
463
|
+
emitPlaybackState("playing")
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
emitActiveTrackChanged(lastIndex)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
fun skipToNext(initialPosition: Double, autoPlay: Boolean = true) {
|
|
425
470
|
if (player.hasNextMediaItem()) {
|
|
471
|
+
val lastIndex = player.currentMediaItemIndex
|
|
426
472
|
player.seekToNextMediaItem()
|
|
427
473
|
if (initialPosition >= 0) player.seekTo((initialPosition * 1000).toLong())
|
|
428
|
-
|
|
474
|
+
|
|
475
|
+
if (autoPlay) {
|
|
476
|
+
player.play()
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
emitActiveTrackChanged(lastIndex)
|
|
480
|
+
emitPlaybackState(if (autoPlay) "playing" else "paused")
|
|
429
481
|
}
|
|
430
482
|
}
|
|
431
483
|
|
|
432
|
-
fun skipToPrevious(initialPosition: Double) {
|
|
484
|
+
fun skipToPrevious(initialPosition: Double, autoPlay: Boolean = true) {
|
|
433
485
|
if (player.hasPreviousMediaItem()) {
|
|
486
|
+
val lastIndex = player.currentMediaItemIndex
|
|
434
487
|
player.seekToPreviousMediaItem()
|
|
435
488
|
if (initialPosition >= 0) player.seekTo((initialPosition * 1000).toLong())
|
|
436
|
-
|
|
489
|
+
|
|
490
|
+
if (autoPlay) {
|
|
491
|
+
player.play()
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
emitActiveTrackChanged(lastIndex)
|
|
495
|
+
emitPlaybackState(if (autoPlay) "playing" else "paused")
|
|
437
496
|
}
|
|
438
497
|
}
|
|
439
498
|
|
|
@@ -451,23 +510,47 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
451
510
|
player.prepare()
|
|
452
511
|
}
|
|
453
512
|
player.play()
|
|
513
|
+
|
|
514
|
+
emitPlaybackState("playing")
|
|
515
|
+
|
|
516
|
+
if (player.currentMediaItemIndex >= 0 && player.currentMediaItemIndex < queue.size) {
|
|
517
|
+
emitActiveTrackChanged()
|
|
518
|
+
}
|
|
519
|
+
|
|
454
520
|
updateMediaNotification()
|
|
455
521
|
}
|
|
456
522
|
|
|
457
523
|
fun pause() {
|
|
458
524
|
player.pause()
|
|
525
|
+
|
|
526
|
+
emitPlaybackState("paused")
|
|
527
|
+
|
|
459
528
|
updateMediaNotification()
|
|
460
529
|
}
|
|
461
530
|
|
|
462
531
|
fun stop() {
|
|
463
532
|
player.stop()
|
|
533
|
+
|
|
534
|
+
emitPlaybackState("stopped")
|
|
535
|
+
|
|
464
536
|
updateMediaNotification()
|
|
465
537
|
}
|
|
466
538
|
|
|
467
539
|
fun reset() {
|
|
540
|
+
val lastIndex = player.currentMediaItemIndex
|
|
468
541
|
player.stop()
|
|
469
542
|
player.clearMediaItems()
|
|
470
543
|
queue.clear()
|
|
544
|
+
|
|
545
|
+
val map = Arguments.createMap()
|
|
546
|
+
map.putInt("index", -1)
|
|
547
|
+
map.putNull("track")
|
|
548
|
+
map.putInt("lastIndex", lastIndex)
|
|
549
|
+
map.putDouble("lastPosition", 0.0)
|
|
550
|
+
eventEmitter?.invoke("playback-active-track-changed", map)
|
|
551
|
+
|
|
552
|
+
emitPlaybackState("none")
|
|
553
|
+
|
|
471
554
|
updateMediaNotification()
|
|
472
555
|
}
|
|
473
556
|
|
|
@@ -577,10 +660,8 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
577
660
|
if (index < 0 || index >= queue.size) return
|
|
578
661
|
val existing = queue[index].toHashMap()
|
|
579
662
|
metadata.toHashMap().forEach { (k, v) -> existing[k] = v }
|
|
580
|
-
// Rebuild the queue entry (ReadableMap is immutable, so we use a WritableMap)
|
|
581
663
|
val updated = Arguments.makeNativeMap(existing)
|
|
582
664
|
queue[index] = updated
|
|
583
|
-
// Update ExoPlayer media item metadata
|
|
584
665
|
player.replaceMediaItem(index, buildMediaItem(updated))
|
|
585
666
|
updateMediaNotification()
|
|
586
667
|
}
|
|
@@ -598,7 +679,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
598
679
|
val updated = Arguments.makeNativeMap(existing)
|
|
599
680
|
queue[currentIndex] = updated
|
|
600
681
|
|
|
601
|
-
// Update the live MediaItem in the player
|
|
602
682
|
val currentItem = player.currentMediaItem ?: return
|
|
603
683
|
val newMetadata = currentItem.mediaMetadata.buildUpon()
|
|
604
684
|
.also { builder ->
|
|
@@ -614,13 +694,11 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
614
694
|
}
|
|
615
695
|
|
|
616
696
|
fun updateOptions(opts: ReadableMap) {
|
|
617
|
-
// Merge new options with existing ones to avoid wiping out buffer settings
|
|
618
697
|
val merged = Arguments.createMap()
|
|
619
698
|
options?.let { merged.merge(it) }
|
|
620
699
|
merged.merge(opts)
|
|
621
700
|
options = merged
|
|
622
701
|
|
|
623
|
-
// Restart progress updates if the interval changed
|
|
624
702
|
if (opts.hasKey("progressUpdateEventInterval")) {
|
|
625
703
|
startProgressUpdates()
|
|
626
704
|
}
|
|
@@ -676,7 +754,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
676
754
|
.setMediaId(track.getString("id") ?: url)
|
|
677
755
|
.setMediaMetadata(metadata)
|
|
678
756
|
.also { builder ->
|
|
679
|
-
// Custom headers
|
|
680
757
|
val headers = track.getMap("headers")
|
|
681
758
|
if (headers != null) {
|
|
682
759
|
val headersMap = headers.toHashMap().mapValues { it.value.toString() }
|
|
@@ -701,15 +778,14 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
701
778
|
private var lastIndex = -1
|
|
702
779
|
|
|
703
780
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
704
|
-
emitPlaybackState()
|
|
781
|
+
emitPlaybackState(mapPlayerState())
|
|
705
782
|
updateMediaNotification()
|
|
783
|
+
|
|
706
784
|
if (playbackState == Player.STATE_ENDED) {
|
|
707
|
-
// Emit track ended for the last item in the queue if it just finished
|
|
708
785
|
val map = Arguments.createMap()
|
|
709
786
|
map.putInt("index", player.currentMediaItemIndex)
|
|
710
787
|
eventEmitter?.invoke("playback-track-ended", map)
|
|
711
788
|
|
|
712
|
-
// Then emit queue ended
|
|
713
789
|
val queueMap = Arguments.createMap()
|
|
714
790
|
queueMap.putInt("index", player.currentMediaItemIndex)
|
|
715
791
|
queueMap.putDouble("position", player.currentPosition / 1000.0)
|
|
@@ -718,13 +794,13 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
718
794
|
}
|
|
719
795
|
|
|
720
796
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
721
|
-
emitPlaybackState()
|
|
797
|
+
emitPlaybackState(mapPlayerState())
|
|
722
798
|
updateMediaNotification()
|
|
723
799
|
}
|
|
724
800
|
|
|
725
801
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
726
|
-
|
|
727
|
-
|
|
802
|
+
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO ||
|
|
803
|
+
reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
|
|
728
804
|
val endedMap = Arguments.createMap()
|
|
729
805
|
endedMap.putInt("index", lastIndex)
|
|
730
806
|
eventEmitter?.invoke("playback-track-ended", endedMap)
|
|
@@ -758,9 +834,8 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
758
834
|
map.putString("code", "playback_error_${error.errorCode}")
|
|
759
835
|
map.putString("message", error.message ?: "Unknown playback error")
|
|
760
836
|
eventEmitter?.invoke("playback-error", map)
|
|
761
|
-
emitPlaybackState()
|
|
837
|
+
emitPlaybackState("error")
|
|
762
838
|
|
|
763
|
-
// Optional auto-skip recovery
|
|
764
839
|
val autoSkip = options?.getMap("android")?.let { androidMap ->
|
|
765
840
|
androidMap.hasKey("autoSkipOnError") && androidMap.getBoolean("autoSkipOnError")
|
|
766
841
|
} ?: false
|
|
@@ -770,12 +845,6 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
770
845
|
player.play()
|
|
771
846
|
}
|
|
772
847
|
}
|
|
773
|
-
|
|
774
|
-
private fun emitPlaybackState() {
|
|
775
|
-
val map = Arguments.createMap()
|
|
776
|
-
map.putString("state", mapPlayerState())
|
|
777
|
-
eventEmitter?.invoke("playback-state", map)
|
|
778
|
-
}
|
|
779
848
|
}
|
|
780
849
|
|
|
781
850
|
// ── MediaSession callback ───────────────────────────────────────────────────
|
|
@@ -787,11 +856,10 @@ class GliphPlayerService : MediaLibraryService() {
|
|
|
787
856
|
controller: MediaSession.ControllerInfo,
|
|
788
857
|
mediaItems: MutableList<MediaItem>
|
|
789
858
|
): com.google.common.util.concurrent.ListenableFuture<MutableList<MediaItem>> {
|
|
790
|
-
// Resolve URIs for Android Auto / external controllers
|
|
791
859
|
val resolved = mediaItems.map { item ->
|
|
792
860
|
item.buildUpon().setUri(item.requestMetadata.mediaUri).build()
|
|
793
861
|
}.toMutableList()
|
|
794
862
|
return com.google.common.util.concurrent.Futures.immediateFuture(resolved)
|
|
795
863
|
}
|
|
796
864
|
}
|
|
797
|
-
}
|
|
865
|
+
}
|