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.
@@ -148,33 +148,40 @@ class GliphPlayerModule(
148
148
  }
149
149
  }
150
150
 
151
- @ReactMethod
152
- override fun skip(index: Double, initialPosition: Double, promise: Promise) {
153
- scope.launch {
154
- try {
155
- playerService?.skip(index.toInt(), if (initialPosition < 0) -1.0 else initialPosition)
156
- promise.resolve(null)
157
- } catch (e: Exception) { promise.reject("skip_error", e.message, e) }
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) { promise.reject("skip_error", e.message, e) }
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) { promise.reject("skip_error", e.message, e) }
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
- // ── Fix #4: Buffer values from JS are in SECONDS (Double).
175
- // ExoPlayer's DefaultLoadControl expects MILLISECONDS (Long).
176
- // We convert here so callers never accidentally buffer for 1000 seconds.
177
- //
178
- // JS API contract (all values in seconds):
179
- // minBuffer — minimum seconds to buffer before playback starts (default 15s)
180
- // maxBuffer — maximum seconds to buffer ahead (default 50s)
181
- // playBuffer — seconds buffered before playback resumes after stall (default 2.5s)
182
- // backBuffer — seconds of audio to keep behind current position (default 0s)
183
- val minBufferMs = (getDouble(opts, "minBuffer", 15.0) * 1000).toInt()
184
- val maxBufferMs = (getDouble(opts, "maxBuffer", 50.0) * 1000).toInt()
185
- val playBufferMs = (getDouble(opts, "playBuffer", 2.5) * 1000).toInt()
186
- val backBufferMs = (getDouble(opts, "backBuffer", 0.0) * 1000).toInt()
187
-
188
- val loadControl = androidx.media3.exoplayer.DefaultLoadControl.Builder()
189
- .setBufferDurationsMs(minBufferMs, maxBufferMs, playBufferMs, playBufferMs)
190
- .setBackBuffer(backBufferMs, /* retainBackBufferFromKeyframe= */ false)
191
- .build()
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
- .setMediaSourceFactory(DefaultMediaSourceFactory(this))
195
- .setLoadControl(loadControl)
196
- .setHandleAudioBecomingNoisy(true)
197
- .setWakeMode(C.WAKE_MODE_NETWORK)
198
- .setAudioAttributes(
199
- AudioAttributes.Builder()
200
- .setUsage(C.USAGE_MEDIA)
201
- .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
202
- .build(),
203
- /* handleAudioFocus= */ true
204
- )
205
- .build()
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 // default: music
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 // default
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 + (i), track)
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
- fun skip(index: Int, initialPosition: Double) {
420
- player.seekTo(index, if (initialPosition >= 0) (initialPosition * 1000).toLong() else C.TIME_UNSET)
421
- player.play()
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
- fun skipToNext(initialPosition: Double) {
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
- player.play()
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
- player.play()
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
- // If we transitioned to a new item, the previous one "ended" its playback
727
- if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO || reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
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
+ }