react-native-nitro-player 0.5.3 → 0.5.4

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.
Files changed (43) hide show
  1. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +5 -3
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +4 -0
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/connection/AndroidAutoConnectionDetector.kt +14 -13
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/NitroPlayerLogger.kt +31 -0
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +142 -95
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +7 -6
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +2 -1
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadWorker.kt +1 -2
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +3 -2
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaBrowserService.kt +25 -24
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaLibraryManager.kt +3 -2
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +20 -19
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +16 -9
  14. package/ios/HybridAudioRoutePicker.swift +1 -1
  15. package/ios/HybridDownloadManager.swift +3 -3
  16. package/ios/HybridEqualizer.swift +3 -3
  17. package/ios/HybridTrackPlayer.swift +8 -4
  18. package/ios/core/NitroPlayerLogger.swift +22 -0
  19. package/ios/core/TrackPlayerCore.swift +195 -256
  20. package/ios/download/DownloadDatabase.swift +35 -39
  21. package/ios/download/DownloadFileManager.swift +17 -17
  22. package/ios/download/DownloadManagerCore.swift +29 -33
  23. package/ios/equalizer/EqualizerCore.swift +25 -20
  24. package/ios/playlist/PlaylistManager.swift +19 -9
  25. package/ios/queue/QueueManager.swift +1 -1
  26. package/lib/specs/TrackPlayer.nitro.d.ts +1 -0
  27. package/lib/types/PlayerQueue.d.ts +1 -1
  28. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +5 -0
  29. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
  30. package/nitrogen/generated/android/c++/JReason.hpp +3 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +4 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Reason.kt +2 -1
  33. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +12 -0
  34. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +8 -0
  35. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
  36. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +12 -0
  37. package/nitrogen/generated/ios/swift/Reason.swift +4 -0
  38. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
  39. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -0
  40. package/nitrogen/generated/shared/c++/Reason.hpp +4 -0
  41. package/package.json +1 -1
  42. package/src/specs/TrackPlayer.nitro.ts +1 -0
  43. package/src/types/PlayerQueue.ts +1 -1
@@ -82,6 +82,8 @@ class TrackPlayerCore private constructor(
82
82
  private val onPlaybackProgressChangeListeners =
83
83
  Collections.synchronizedList(mutableListOf<WeakCallbackBox<(Double, Double, Boolean?) -> Unit>>())
84
84
 
85
+ private var currentRepeatMode: RepeatMode = RepeatMode.OFF
86
+
85
87
  // Temporary tracks for addToUpNext and playNext
86
88
  private var playNextStack: MutableList<TrackItem> = mutableListOf() // LIFO - last added plays first
87
89
  private var upNextQueue: MutableList<TrackItem> = mutableListOf() // FIFO - first added plays first
@@ -168,7 +170,7 @@ class TrackPlayerCore private constructor(
168
170
  // Notify JavaScript
169
171
  onAndroidAutoConnectionChange?.invoke(connected)
170
172
 
171
- println("šŸš— Android Auto connection changed: connected=$connected, type=$connectionType")
173
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸš— Android Auto connection changed: connected=$connected, type=$connectionType")
172
174
  }
173
175
  }
174
176
  registerCarConnectionReceiver()
@@ -180,20 +182,25 @@ class TrackPlayerCore private constructor(
180
182
  mediaItem: MediaItem?,
181
183
  reason: Int,
182
184
  ) {
183
- println("\nšŸ”„ onMediaItemTransition called")
184
- println(
185
+ NitroPlayerLogger.log("TrackPlayerCore") { "\nšŸ”„ onMediaItemTransition called" }
186
+ NitroPlayerLogger.log("TrackPlayerCore") {
185
187
  " reason: ${when (reason) {
186
188
  Player.MEDIA_ITEM_TRANSITION_REASON_AUTO -> "AUTO (track ended)"
187
189
  Player.MEDIA_ITEM_TRANSITION_REASON_SEEK -> "SEEK"
188
190
  Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED -> "PLAYLIST_CHANGED"
189
- Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT -> "REPEAT"
190
191
  else -> "UNKNOWN($reason)"
191
- }}",
192
- )
193
- println(" previousMediaItem: ${previousMediaItem?.mediaId}")
194
- println(" new mediaItem: ${mediaItem?.mediaId}")
195
- println(" playNextStack: ${playNextStack.map { it.id }}")
196
- println(" upNextQueue: ${upNextQueue.map { it.id }}")
192
+ }}"
193
+ }
194
+ NitroPlayerLogger.log("TrackPlayerCore") { " previousMediaItem: ${previousMediaItem?.mediaId}" }
195
+ NitroPlayerLogger.log("TrackPlayerCore") { " new mediaItem: ${mediaItem?.mediaId}" }
196
+ NitroPlayerLogger.log("TrackPlayerCore") { " playNextStack: ${playNextStack.map { it.id }}" }
197
+ NitroPlayerLogger.log("TrackPlayerCore") { " upNextQueue: ${upNextQueue.map { it.id }}" }
198
+
199
+ // TRACK repeat: REPEAT_MODE_ONE fires this callback every loop — skip entirely
200
+ if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
201
+ NitroPlayerLogger.log("TrackPlayerCore") { " šŸ” TRACK repeat loop — skipping notifyTrackChange" }
202
+ return
203
+ }
197
204
 
198
205
  // Remove finished track from temporary lists
199
206
  // Handle AUTO (natural end) and SEEK (skip next) transitions
@@ -205,26 +212,26 @@ class TrackPlayerCore private constructor(
205
212
  ) {
206
213
  previousMediaItem?.mediaId?.let { mediaId ->
207
214
  val trackId = extractTrackId(mediaId)
208
- println("šŸ Track finished/skipped, checking for removal: $trackId")
215
+ NitroPlayerLogger.log("TrackPlayerCore") { "šŸ Track finished/skipped, checking for removal: $trackId" }
209
216
 
210
217
  // Find and remove from playNext stack (like iOS does)
211
218
  val playNextIndex = playNextStack.indexOfFirst { it.id == trackId }
212
219
  if (playNextIndex >= 0) {
213
220
  val track = playNextStack.removeAt(playNextIndex)
214
- println(" āœ… Removed from playNext stack: ${track.title}")
221
+ NitroPlayerLogger.log("TrackPlayerCore") { " āœ… Removed from playNext stack: ${track.title}" }
215
222
  } else {
216
223
  // Find and remove from upNext queue
217
224
  val upNextIndex = upNextQueue.indexOfFirst { it.id == trackId }
218
225
  if (upNextIndex >= 0) {
219
226
  val track = upNextQueue.removeAt(upNextIndex)
220
- println(" āœ… Removed from upNext queue: ${track.title}")
227
+ NitroPlayerLogger.log("TrackPlayerCore") { " āœ… Removed from upNext queue: ${track.title}" }
221
228
  } else {
222
- println(" ā„¹ļø Was an original playlist track")
229
+ NitroPlayerLogger.log("TrackPlayerCore") { " ā„¹ļø Was an original playlist track" }
223
230
  }
224
231
  }
225
232
  }
226
233
  } else {
227
- println(" ā­ļø Skipping removal (reason=$reason, prev=${previousMediaItem != null})")
234
+ NitroPlayerLogger.log("TrackPlayerCore") { " ā­ļø Skipping removal (reason=$reason, prev=${previousMediaItem != null})" }
228
235
  }
229
236
 
230
237
  // Store current item as previous for next transition
@@ -232,14 +239,14 @@ class TrackPlayerCore private constructor(
232
239
 
233
240
  // Update temporary type for current track
234
241
  currentTemporaryType = determineCurrentTemporaryType()
235
- println(" Updated currentTemporaryType: $currentTemporaryType")
242
+ NitroPlayerLogger.log("TrackPlayerCore") { " Updated currentTemporaryType: $currentTemporaryType" }
236
243
 
237
244
  // Update currentTrackIndex when we land on an original playlist track
238
245
  if (currentTemporaryType == TemporaryType.NONE && mediaItem != null) {
239
246
  val trackId = extractTrackId(mediaItem.mediaId)
240
247
  val newIndex = currentTracks.indexOfFirst { it.id == trackId }
241
248
  if (newIndex >= 0 && newIndex != currentTrackIndex) {
242
- println(" šŸ“ Updating currentTrackIndex from $currentTrackIndex to $newIndex")
249
+ NitroPlayerLogger.log("TrackPlayerCore") { " šŸ“ Updating currentTrackIndex from $currentTrackIndex to $newIndex" }
243
250
  currentTrackIndex = newIndex
244
251
  }
245
252
  }
@@ -255,7 +262,8 @@ class TrackPlayerCore private constructor(
255
262
  if (playlist != null && currentPlaylistId != playlistId) {
256
263
  // This shouldn't happen if playlists are loaded correctly,
257
264
  // but handle it as a safety measure
258
- println(
265
+ NitroPlayerLogger.log(
266
+ "TrackPlayerCore",
259
267
  "āš ļø TrackPlayerCore: Detected track from different playlist, updating...",
260
268
  )
261
269
  }
@@ -301,6 +309,19 @@ class TrackPlayerCore private constructor(
301
309
  }
302
310
 
303
311
  override fun onPlaybackStateChanged(playbackState: Int) {
312
+ if (playbackState == Player.STATE_ENDED && currentRepeatMode == RepeatMode.PLAYLIST) {
313
+ NitroPlayerLogger.log("TrackPlayerCore") { "šŸ” PLAYLIST repeat — rebuilding original queue and restarting" }
314
+ handler.post {
315
+ playNextStack.clear()
316
+ upNextQueue.clear()
317
+ currentTemporaryType = TemporaryType.NONE
318
+ // Rebuild ExoPlayer queue from beginning of original playlist
319
+ rebuildQueueAndPlayFromIndex(0)
320
+ val firstTrack = currentTracks.getOrNull(0)
321
+ if (firstTrack != null) notifyTrackChange(firstTrack, Reason.REPEAT)
322
+ }
323
+ return
324
+ }
304
325
  emitStateChange()
305
326
  }
306
327
 
@@ -353,7 +374,7 @@ class TrackPlayerCore private constructor(
353
374
  playNextStack.clear()
354
375
  upNextQueue.clear()
355
376
  currentTemporaryType = TemporaryType.NONE
356
- println(" 🧹 Cleared temporary tracks")
377
+ NitroPlayerLogger.log("TrackPlayerCore", " 🧹 Cleared temporary tracks")
357
378
 
358
379
  val playlist = playlistManager.getPlaylist(playlistId)
359
380
  if (playlist != null) {
@@ -395,7 +416,7 @@ class TrackPlayerCore private constructor(
395
416
  }
396
417
  }
397
418
  } catch (e: Exception) {
398
- println("āŒ TrackPlayerCore: Error playing from playlist track - ${e.message}")
419
+ NitroPlayerLogger.log("TrackPlayerCore", "āŒ TrackPlayerCore: Error playing from playlist track - ${e.message}")
399
420
  e.printStackTrace()
400
421
  }
401
422
  }
@@ -498,8 +519,9 @@ class TrackPlayerCore private constructor(
498
519
  mediaId
499
520
  }
500
521
 
501
- val playlist = currentPlaylistId?.let { playlistManager.getPlaylist(it) }
502
- return playlist?.tracks?.find { it.id == trackId }
522
+ // currentTracks is already the cached tracks for currentPlaylistId — no need to
523
+ // re-fetch from PlaylistManager on every call.
524
+ return currentTracks.find { it.id == trackId }
503
525
  }
504
526
 
505
527
  fun play() {
@@ -527,32 +549,32 @@ class TrackPlayerCore private constructor(
527
549
  playNextStack.clear()
528
550
  upNextQueue.clear()
529
551
  currentTemporaryType = TemporaryType.NONE
530
- println(" 🧹 Cleared temporary tracks")
552
+ NitroPlayerLogger.log("TrackPlayerCore", " 🧹 Cleared temporary tracks")
531
553
 
532
554
  var targetPlaylistId: String? = null
533
555
  var songIndex: Int = -1
534
556
 
535
557
  // Case 1: If fromPlaylist is provided, use that playlist
536
558
  if (fromPlaylist != null) {
537
- println("šŸŽµ TrackPlayerCore: Looking for song in specified playlist: $fromPlaylist")
559
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸŽµ TrackPlayerCore: Looking for song in specified playlist: $fromPlaylist")
538
560
  val playlist = playlistManager.getPlaylist(fromPlaylist)
539
561
  if (playlist != null) {
540
562
  songIndex = playlist.tracks.indexOfFirst { it.id == songId }
541
563
  if (songIndex >= 0) {
542
564
  targetPlaylistId = fromPlaylist
543
- println("āœ… Found song at index $songIndex in playlist $fromPlaylist")
565
+ NitroPlayerLogger.log("TrackPlayerCore", "āœ… Found song at index $songIndex in playlist $fromPlaylist")
544
566
  } else {
545
- println("āš ļø Song $songId not found in specified playlist $fromPlaylist")
567
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Song $songId not found in specified playlist $fromPlaylist")
546
568
  return
547
569
  }
548
570
  } else {
549
- println("āš ļø Playlist $fromPlaylist not found")
571
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Playlist $fromPlaylist not found")
550
572
  return
551
573
  }
552
574
  }
553
575
  // Case 2: If fromPlaylist is not provided, search in current/loaded playlist first
554
576
  else {
555
- println("šŸŽµ TrackPlayerCore: No playlist specified, checking current playlist")
577
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸŽµ TrackPlayerCore: No playlist specified, checking current playlist")
556
578
 
557
579
  // Check if song exists in currently loaded playlist
558
580
  if (currentPlaylistId != null) {
@@ -561,21 +583,21 @@ class TrackPlayerCore private constructor(
561
583
  songIndex = currentPlaylist.tracks.indexOfFirst { it.id == songId }
562
584
  if (songIndex >= 0) {
563
585
  targetPlaylistId = currentPlaylistId
564
- println("āœ… Found song at index $songIndex in current playlist $currentPlaylistId")
586
+ NitroPlayerLogger.log("TrackPlayerCore", "āœ… Found song at index $songIndex in current playlist $currentPlaylistId")
565
587
  }
566
588
  }
567
589
  }
568
590
 
569
591
  // If not found in current playlist, search in all playlists
570
592
  if (songIndex == -1) {
571
- println("šŸ” Song not found in current playlist, searching all playlists...")
593
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ” Song not found in current playlist, searching all playlists...")
572
594
  val allPlaylists = playlistManager.getAllPlaylists()
573
595
 
574
596
  for (playlist in allPlaylists) {
575
597
  songIndex = playlist.tracks.indexOfFirst { it.id == songId }
576
598
  if (songIndex >= 0) {
577
599
  targetPlaylistId = playlist.id
578
- println("āœ… Found song at index $songIndex in playlist ${playlist.id}")
600
+ NitroPlayerLogger.log("TrackPlayerCore", "āœ… Found song at index $songIndex in playlist ${playlist.id}")
579
601
  break
580
602
  }
581
603
  }
@@ -584,20 +606,20 @@ class TrackPlayerCore private constructor(
584
606
  if (songIndex == -1 && allPlaylists.isNotEmpty()) {
585
607
  targetPlaylistId = allPlaylists[0].id
586
608
  songIndex = 0
587
- println("āš ļø Song not found in any playlist, using first playlist and starting at index 0")
609
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Song not found in any playlist, using first playlist and starting at index 0")
588
610
  }
589
611
  }
590
612
  }
591
613
 
592
614
  // Now play the song
593
615
  if (targetPlaylistId == null || songIndex < 0) {
594
- println("āŒ Could not determine playlist or song index")
616
+ NitroPlayerLogger.log("TrackPlayerCore", "āŒ Could not determine playlist or song index")
595
617
  return
596
618
  }
597
619
 
598
620
  // Load playlist if it's different from current
599
621
  if (currentPlaylistId != targetPlaylistId) {
600
- println("šŸ”„ Loading new playlist: $targetPlaylistId")
622
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”„ Loading new playlist: $targetPlaylistId")
601
623
  val playlist = playlistManager.getPlaylist(targetPlaylistId)
602
624
  if (playlist != null) {
603
625
  currentPlaylistId = targetPlaylistId
@@ -605,12 +627,12 @@ class TrackPlayerCore private constructor(
605
627
 
606
628
  // Wait a bit for playlist to load, then play from index
607
629
  // Note: Removed postDelayed to avoid race conditions with subsequent queue operations
608
- println("ā–¶ļø Playing from index: $songIndex")
630
+ NitroPlayerLogger.log("TrackPlayerCore", "ā–¶ļø Playing from index: $songIndex")
609
631
  playFromIndex(songIndex)
610
632
  }
611
633
  } else {
612
634
  // Playlist already loaded, just play from index
613
- println("ā–¶ļø Playing from index: $songIndex")
635
+ NitroPlayerLogger.log("TrackPlayerCore", "ā–¶ļø Playing from index: $songIndex")
614
636
  playFromIndex(songIndex)
615
637
  }
616
638
  }
@@ -629,11 +651,11 @@ class TrackPlayerCore private constructor(
629
651
 
630
652
  if (currentPosition > 2000) {
631
653
  // More than 2 seconds in, restart current track
632
- println("šŸ”„ TrackPlayerCore: Past threshold, restarting current track")
654
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”„ TrackPlayerCore: Past threshold, restarting current track")
633
655
  player.seekTo(0)
634
656
  } else if (currentTemporaryType != TemporaryType.NONE) {
635
657
  // Playing temporary track within threshold — remove from its list, go back to original
636
- println("šŸ”„ TrackPlayerCore: Removing temp track, going back to original")
658
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”„ TrackPlayerCore: Removing temp track, going back to original")
637
659
  val currentMediaItem = player.currentMediaItem
638
660
  if (currentMediaItem != null) {
639
661
  val trackId = extractTrackId(currentMediaItem.mediaId)
@@ -642,10 +664,12 @@ class TrackPlayerCore private constructor(
642
664
  val idx = playNextStack.indexOfFirst { it.id == trackId }
643
665
  if (idx >= 0) playNextStack.removeAt(idx)
644
666
  }
667
+
645
668
  TemporaryType.UP_NEXT -> {
646
669
  val idx = upNextQueue.indexOfFirst { it.id == trackId }
647
670
  if (idx >= 0) upNextQueue.removeAt(idx)
648
671
  }
672
+
649
673
  else -> {}
650
674
  }
651
675
  }
@@ -653,11 +677,11 @@ class TrackPlayerCore private constructor(
653
677
  playFromIndexInternal(currentTrackIndex)
654
678
  } else if (currentTrackIndex > 0) {
655
679
  // Go to previous track in original playlist
656
- println("šŸ”„ TrackPlayerCore: Going to previous track, currentTrackIndex: $currentTrackIndex -> ${currentTrackIndex - 1}")
680
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”„ TrackPlayerCore: Going to previous track, currentTrackIndex: $currentTrackIndex -> ${currentTrackIndex - 1}")
657
681
  playFromIndexInternal(currentTrackIndex - 1)
658
682
  } else {
659
683
  // Already at first track, seek to beginning
660
- println("šŸ”„ TrackPlayerCore: Already at first track, seeking to beginning")
684
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”„ TrackPlayerCore: Already at first track, seeking to beginning")
661
685
  player.seekTo(0)
662
686
  }
663
687
  }
@@ -671,20 +695,21 @@ class TrackPlayerCore private constructor(
671
695
  }
672
696
 
673
697
  fun setRepeatMode(mode: RepeatMode): Boolean {
674
- println("šŸ” TrackPlayerCore: setRepeatMode called with mode: $mode")
675
- handler.post {
676
- val exoRepeatMode =
677
- when (mode) {
678
- RepeatMode.OFF -> Player.REPEAT_MODE_OFF
698
+ currentRepeatMode = mode
699
+ if (::player.isInitialized) {
700
+ handler.post {
701
+ player.repeatMode = when (mode) {
679
702
  RepeatMode.TRACK -> Player.REPEAT_MODE_ONE
680
- RepeatMode.PLAYLIST -> Player.REPEAT_MODE_ALL
703
+ else -> Player.REPEAT_MODE_OFF
681
704
  }
682
- player.repeatMode = exoRepeatMode
683
- println("šŸ” TrackPlayerCore: ExoPlayer repeat mode set to: $exoRepeatMode")
705
+ }
684
706
  }
707
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ” setRepeatMode: $mode")
685
708
  return true
686
709
  }
687
710
 
711
+ fun getRepeatMode(): RepeatMode = currentRepeatMode
712
+
688
713
  fun getState(): PlayerState {
689
714
  // Called from Promise.async background thread
690
715
  // Check if we're already on the main thread
@@ -739,9 +764,6 @@ class TrackPlayerCore private constructor(
739
764
  else -> TrackPlayerState.STOPPED
740
765
  }
741
766
 
742
- // Get current playlist
743
- val currentPlaylist = currentPlaylistId?.let { playlistManager.getPlaylist(it) }
744
-
745
767
  // Use ExoPlayer's currentMediaItemIndex
746
768
  val currentIndex =
747
769
  if (player.currentMediaItemIndex >= 0) {
@@ -894,12 +916,24 @@ class TrackPlayerCore private constructor(
894
916
  // (reduced by 1 when current track is from that temp list, matching getActualQueueInternal)
895
917
  // When temp is playing, the original track at currentTrackIndex is included in "before",
896
918
  // so the current playing position shifts by 1
897
- val currentPos = if (currentTemporaryType != TemporaryType.NONE)
898
- currentTrackIndex + 1 else currentTrackIndex
899
- val effectivePlayNextSize = if (currentTemporaryType == TemporaryType.PLAY_NEXT)
900
- maxOf(0, playNextStack.size - 1) else playNextStack.size
901
- val effectiveUpNextSize = if (currentTemporaryType == TemporaryType.UP_NEXT)
902
- maxOf(0, upNextQueue.size - 1) else upNextQueue.size
919
+ val currentPos =
920
+ if (currentTemporaryType != TemporaryType.NONE) {
921
+ currentTrackIndex + 1
922
+ } else {
923
+ currentTrackIndex
924
+ }
925
+ val effectivePlayNextSize =
926
+ if (currentTemporaryType == TemporaryType.PLAY_NEXT) {
927
+ maxOf(0, playNextStack.size - 1)
928
+ } else {
929
+ playNextStack.size
930
+ }
931
+ val effectiveUpNextSize =
932
+ if (currentTemporaryType == TemporaryType.UP_NEXT) {
933
+ maxOf(0, upNextQueue.size - 1)
934
+ } else {
935
+ upNextQueue.size
936
+ }
903
937
 
904
938
  val playNextStart = currentPos + 1
905
939
  val playNextEnd = playNextStart + effectivePlayNextSize
@@ -923,8 +957,12 @@ class TrackPlayerCore private constructor(
923
957
  if (index >= playNextStart && index < playNextEnd) {
924
958
  val playNextIndex = index - playNextStart
925
959
  // Offset by 1 if current is from playNext (index 0 is already playing)
926
- val actualListIndex = if (currentTemporaryType == TemporaryType.PLAY_NEXT)
927
- playNextIndex + 1 else playNextIndex
960
+ val actualListIndex =
961
+ if (currentTemporaryType == TemporaryType.PLAY_NEXT) {
962
+ playNextIndex + 1
963
+ } else {
964
+ playNextIndex
965
+ }
928
966
 
929
967
  // Remove tracks before the target from playNext (they're being skipped)
930
968
  if (actualListIndex > 0) {
@@ -941,8 +979,12 @@ class TrackPlayerCore private constructor(
941
979
  if (index >= upNextStart && index < upNextEnd) {
942
980
  val upNextIndex = index - upNextStart
943
981
  // Offset by 1 if current is from upNext (index 0 is already playing)
944
- val actualListIndex = if (currentTemporaryType == TemporaryType.UP_NEXT)
945
- upNextIndex + 1 else upNextIndex
982
+ val actualListIndex =
983
+ if (currentTemporaryType == TemporaryType.UP_NEXT) {
984
+ upNextIndex + 1
985
+ } else {
986
+ upNextIndex
987
+ }
946
988
 
947
989
  // Clear all playNext tracks (they're being skipped)
948
990
  playNextStack.clear()
@@ -993,22 +1035,22 @@ class TrackPlayerCore private constructor(
993
1035
  */
994
1036
  private fun rebuildQueueAndPlayFromIndex(index: Int) {
995
1037
  if (!::player.isInitialized) {
996
- println(" āŒ Player not initialized")
1038
+ NitroPlayerLogger.log("TrackPlayerCore", " āŒ Player not initialized")
997
1039
  return
998
1040
  }
999
1041
 
1000
1042
  if (index < 0 || index >= currentTracks.size) {
1001
- println(" āŒ Invalid index $index for currentTracks size ${currentTracks.size}")
1043
+ NitroPlayerLogger.log("TrackPlayerCore", " āŒ Invalid index $index for currentTracks size ${currentTracks.size}")
1002
1044
  return
1003
1045
  }
1004
1046
 
1005
- println("\nšŸ”„ TrackPlayerCore: REBUILD QUEUE AND PLAY FROM INDEX $index")
1006
- println(" currentTracks.size: ${currentTracks.size}")
1007
- println(" currentTracks IDs: ${currentTracks.map { it.id }}")
1047
+ NitroPlayerLogger.log("TrackPlayerCore") { "\nšŸ”„ TrackPlayerCore: REBUILD QUEUE AND PLAY FROM INDEX $index" }
1048
+ NitroPlayerLogger.log("TrackPlayerCore") { " currentTracks.size: ${currentTracks.size}" }
1049
+ NitroPlayerLogger.log("TrackPlayerCore") { " currentTracks IDs: ${currentTracks.map { it.id }}" }
1008
1050
 
1009
1051
  // Build queue from the target index onwards
1010
1052
  val tracksToPlay = currentTracks.subList(index, currentTracks.size)
1011
- println(" tracksToPlay (${tracksToPlay.size}): ${tracksToPlay.map { it.id }}")
1053
+ NitroPlayerLogger.log("TrackPlayerCore") { " tracksToPlay (${tracksToPlay.size}): ${tracksToPlay.map { it.id }}" }
1012
1054
 
1013
1055
  val playlistId = currentPlaylistId ?: ""
1014
1056
  val mediaItems =
@@ -1019,7 +1061,7 @@ class TrackPlayerCore private constructor(
1019
1061
 
1020
1062
  // Update our internal tracking of the position in original playlist
1021
1063
  currentTrackIndex = index
1022
- println(" Setting currentTrackIndex to $index")
1064
+ NitroPlayerLogger.log("TrackPlayerCore") { " Setting currentTrackIndex to $index" }
1023
1065
 
1024
1066
  // Clear the entire player queue and set new items
1025
1067
  player.clearMediaItems()
@@ -1028,7 +1070,7 @@ class TrackPlayerCore private constructor(
1028
1070
  player.playWhenReady = true
1029
1071
  player.prepare()
1030
1072
 
1031
- println(" āœ… Queue rebuilt with ${player.mediaItemCount} items, playing from index 0 (track ${tracksToPlay.firstOrNull()?.id})")
1073
+ NitroPlayerLogger.log("TrackPlayerCore") { " āœ… Queue rebuilt with ${player.mediaItemCount} items, playing from index 0 (track ${tracksToPlay.firstOrNull()?.id})" }
1032
1074
  }
1033
1075
 
1034
1076
  // MARK: - Temporary Track Management
@@ -1044,18 +1086,18 @@ class TrackPlayerCore private constructor(
1044
1086
  }
1045
1087
 
1046
1088
  private fun addToUpNextInternal(trackId: String) {
1047
- println("šŸ“‹ TrackPlayerCore: addToUpNext($trackId)")
1089
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ“‹ TrackPlayerCore: addToUpNext($trackId)")
1048
1090
 
1049
1091
  // Find the track from current playlist or all playlists
1050
1092
  val track = findTrackById(trackId)
1051
1093
  if (track == null) {
1052
- println("āŒ TrackPlayerCore: Track $trackId not found")
1094
+ NitroPlayerLogger.log("TrackPlayerCore", "āŒ TrackPlayerCore: Track $trackId not found")
1053
1095
  return
1054
1096
  }
1055
1097
 
1056
1098
  // Add to end of upNext queue (FIFO)
1057
1099
  upNextQueue.add(track)
1058
- println(" āœ… Added '${track.title}' to upNext queue (position: ${upNextQueue.size})")
1100
+ NitroPlayerLogger.log("TrackPlayerCore", " āœ… Added '${track.title}' to upNext queue (position: ${upNextQueue.size})")
1059
1101
 
1060
1102
  // Rebuild the player queue if actively playing
1061
1103
  if (::player.isInitialized && player.currentMediaItem != null) {
@@ -1074,18 +1116,18 @@ class TrackPlayerCore private constructor(
1074
1116
  }
1075
1117
 
1076
1118
  private fun playNextInternal(trackId: String) {
1077
- println("ā­ļø TrackPlayerCore: playNext($trackId)")
1119
+ NitroPlayerLogger.log("TrackPlayerCore", "ā­ļø TrackPlayerCore: playNext($trackId)")
1078
1120
 
1079
1121
  // Find the track from current playlist or all playlists
1080
1122
  val track = findTrackById(trackId)
1081
1123
  if (track == null) {
1082
- println("āŒ TrackPlayerCore: Track $trackId not found")
1124
+ NitroPlayerLogger.log("TrackPlayerCore", "āŒ TrackPlayerCore: Track $trackId not found")
1083
1125
  return
1084
1126
  }
1085
1127
 
1086
1128
  // Insert at beginning of playNext stack (LIFO)
1087
1129
  playNextStack.add(0, track)
1088
- println(" āœ… Added '${track.title}' to playNext stack (position: 1)")
1130
+ NitroPlayerLogger.log("TrackPlayerCore", " āœ… Added '${track.title}' to playNext stack (position: 1)")
1089
1131
 
1090
1132
  // Rebuild the player queue if actively playing
1091
1133
  if (::player.isInitialized && player.currentMediaItem != null) {
@@ -1103,7 +1145,7 @@ class TrackPlayerCore private constructor(
1103
1145
  val currentIndex = player.currentMediaItemIndex
1104
1146
  if (currentIndex < 0) return
1105
1147
 
1106
- val newQueueTracks = mutableListOf<TrackItem>()
1148
+ val newQueueTracks = ArrayList<TrackItem>(playNextStack.size + upNextQueue.size + currentTracks.size)
1107
1149
 
1108
1150
  // Add playNext stack (LIFO - most recently added plays first)
1109
1151
  // Skip index 0 if current track is from playNext (it's already playing)
@@ -1135,9 +1177,9 @@ class TrackPlayerCore private constructor(
1135
1177
  track.toMediaItem(mediaId)
1136
1178
  }
1137
1179
 
1138
- // Remove all items after current
1139
- while (player.mediaItemCount > currentIndex + 1) {
1140
- player.removeMediaItem(currentIndex + 1)
1180
+ // Remove all items after current in one batch (single timeline event vs N events)
1181
+ if (player.mediaItemCount > currentIndex + 1) {
1182
+ player.removeMediaItems(currentIndex + 1, player.mediaItemCount)
1141
1183
  }
1142
1184
 
1143
1185
  // Add new items
@@ -1204,9 +1246,9 @@ class TrackPlayerCore private constructor(
1204
1246
  mediaLibraryManager.setMediaLibrary(library)
1205
1247
  // Notify Android Auto to refresh
1206
1248
  NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
1207
- println("āœ… TrackPlayerCore: Android Auto media library set successfully")
1249
+ NitroPlayerLogger.log("TrackPlayerCore", "āœ… TrackPlayerCore: Android Auto media library set successfully")
1208
1250
  } catch (e: Exception) {
1209
- println("āŒ TrackPlayerCore: Error setting media library - ${e.message}")
1251
+ NitroPlayerLogger.log("TrackPlayerCore", "āŒ TrackPlayerCore: Error setting media library - ${e.message}")
1210
1252
  e.printStackTrace()
1211
1253
  }
1212
1254
  }
@@ -1229,11 +1271,11 @@ class TrackPlayerCore private constructor(
1229
1271
  // Convert to 0.0-1.0 range for ExoPlayer
1230
1272
  val normalizedVolume = (clampedVolume / 100.0).toFloat()
1231
1273
  player.volume = normalizedVolume
1232
- println("šŸ”Š TrackPlayerCore: Volume set to $clampedVolume% (normalized: $normalizedVolume)")
1274
+ NitroPlayerLogger.log("TrackPlayerCore", "šŸ”Š TrackPlayerCore: Volume set to $clampedVolume% (normalized: $normalizedVolume)")
1233
1275
  }
1234
1276
  true
1235
1277
  } else {
1236
- println("āš ļø TrackPlayerCore: Cannot set volume - player not initialized")
1278
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø TrackPlayerCore: Cannot set volume - player not initialized")
1237
1279
  false
1238
1280
  }
1239
1281
 
@@ -1266,7 +1308,7 @@ class TrackPlayerCore private constructor(
1266
1308
  val liveCallbacks =
1267
1309
  synchronized(onChangeTrackListeners) {
1268
1310
  onChangeTrackListeners.removeAll { !it.isAlive }
1269
- onChangeTrackListeners.filter { it.isAlive }.map { it.callback }
1311
+ onChangeTrackListeners.map { it.callback }
1270
1312
  }
1271
1313
 
1272
1314
  handler.post {
@@ -1274,7 +1316,7 @@ class TrackPlayerCore private constructor(
1274
1316
  try {
1275
1317
  callback(track, reason)
1276
1318
  } catch (e: Exception) {
1277
- println("āš ļø Error in track change listener: ${e.message}")
1319
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Error in track change listener: ${e.message}")
1278
1320
  }
1279
1321
  }
1280
1322
  }
@@ -1287,7 +1329,7 @@ class TrackPlayerCore private constructor(
1287
1329
  val liveCallbacks =
1288
1330
  synchronized(onPlaybackStateChangeListeners) {
1289
1331
  onPlaybackStateChangeListeners.removeAll { !it.isAlive }
1290
- onPlaybackStateChangeListeners.filter { it.isAlive }.map { it.callback }
1332
+ onPlaybackStateChangeListeners.map { it.callback }
1291
1333
  }
1292
1334
 
1293
1335
  handler.post {
@@ -1295,7 +1337,7 @@ class TrackPlayerCore private constructor(
1295
1337
  try {
1296
1338
  callback(state, reason)
1297
1339
  } catch (e: Exception) {
1298
- println("āš ļø Error in playback state listener: ${e.message}")
1340
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Error in playback state listener: ${e.message}")
1299
1341
  }
1300
1342
  }
1301
1343
  }
@@ -1308,7 +1350,7 @@ class TrackPlayerCore private constructor(
1308
1350
  val liveCallbacks =
1309
1351
  synchronized(onSeekListeners) {
1310
1352
  onSeekListeners.removeAll { !it.isAlive }
1311
- onSeekListeners.filter { it.isAlive }.map { it.callback }
1353
+ onSeekListeners.map { it.callback }
1312
1354
  }
1313
1355
 
1314
1356
  handler.post {
@@ -1316,7 +1358,7 @@ class TrackPlayerCore private constructor(
1316
1358
  try {
1317
1359
  callback(position, duration)
1318
1360
  } catch (e: Exception) {
1319
- println("āš ļø Error in seek listener: ${e.message}")
1361
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Error in seek listener: ${e.message}")
1320
1362
  }
1321
1363
  }
1322
1364
  }
@@ -1330,7 +1372,7 @@ class TrackPlayerCore private constructor(
1330
1372
  val liveCallbacks =
1331
1373
  synchronized(onPlaybackProgressChangeListeners) {
1332
1374
  onPlaybackProgressChangeListeners.removeAll { !it.isAlive }
1333
- onPlaybackProgressChangeListeners.filter { it.isAlive }.map { it.callback }
1375
+ onPlaybackProgressChangeListeners.map { it.callback }
1334
1376
  }
1335
1377
 
1336
1378
  handler.post {
@@ -1338,7 +1380,7 @@ class TrackPlayerCore private constructor(
1338
1380
  try {
1339
1381
  callback(position, duration, isPlaying)
1340
1382
  } catch (e: Exception) {
1341
- println("āš ļø Error in playback progress listener: ${e.message}")
1383
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø Error in playback progress listener: ${e.message}")
1342
1384
  }
1343
1385
  }
1344
1386
  }
@@ -1371,14 +1413,15 @@ class TrackPlayerCore private constructor(
1371
1413
  // Wait up to 5 seconds for the result
1372
1414
  latch.await(5, TimeUnit.SECONDS)
1373
1415
  } catch (e: InterruptedException) {
1374
- println("āš ļø TrackPlayerCore: Interrupted while waiting for actual queue")
1416
+ NitroPlayerLogger.log("TrackPlayerCore", "āš ļø TrackPlayerCore: Interrupted while waiting for actual queue")
1375
1417
  }
1376
1418
 
1377
1419
  return result ?: emptyList()
1378
1420
  }
1379
1421
 
1380
1422
  private fun getActualQueueInternal(): List<TrackItem> {
1381
- val queue = mutableListOf<TrackItem>()
1423
+ val capacity = currentTracks.size + playNextStack.size + upNextQueue.size
1424
+ val queue = ArrayList<TrackItem>(capacity)
1382
1425
 
1383
1426
  if (!::player.isInitialized) return emptyList()
1384
1427
 
@@ -1388,8 +1431,12 @@ class TrackPlayerCore private constructor(
1388
1431
  // Add tracks before current (original playlist)
1389
1432
  // When a temp track is playing, include the original track at currentTrackIndex
1390
1433
  // (it already played before the temp track started)
1391
- val beforeEnd = if (currentTemporaryType != TemporaryType.NONE)
1392
- minOf(currentIndex + 1, currentTracks.size) else currentIndex
1434
+ val beforeEnd =
1435
+ if (currentTemporaryType != TemporaryType.NONE) {
1436
+ minOf(currentIndex + 1, currentTracks.size)
1437
+ } else {
1438
+ currentIndex
1439
+ }
1393
1440
  if (beforeEnd > 0) {
1394
1441
  queue.addAll(currentTracks.subList(0, beforeEnd))
1395
1442
  }