react-native-nitro-player 0.7.1-alpha.0 → 0.7.1-alpha.2

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 (39) hide show
  1. package/README.md +47 -46
  2. package/android/src/main/AndroidManifest.xml +14 -1
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +5 -6
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +68 -49
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +67 -21
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +27 -5
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +88 -49
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +40 -10
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ExoPlayerCore.kt +70 -29
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ListenerRegistry.kt +4 -1
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerAndroidAuto.kt +38 -32
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +70 -65
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerListener.kt +23 -12
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerNotify.kt +16 -4
  15. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerPlayback.kt +101 -72
  16. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +64 -30
  17. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +54 -28
  18. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +4 -3
  19. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerTempQueue.kt +73 -62
  20. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerUrlLoader.kt +51 -48
  21. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +3 -3
  22. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +12 -3
  23. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +169 -98
  24. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +30 -178
  25. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/PlaybackService.kt +40 -0
  26. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +87 -85
  27. package/ios/core/TrackPlayerQueue.swift +27 -18
  28. package/ios/core/TrackPlayerQueueBuild.swift +16 -5
  29. package/ios/equalizer/EqualizerCore.swift +39 -34
  30. package/lib/hooks/useEqualizer.js +10 -5
  31. package/lib/specs/Equalizer.nitro.d.ts +1 -1
  32. package/lib/types/EqualizerTypes.d.ts +3 -3
  33. package/package.json +5 -5
  34. package/src/hooks/useEqualizer.ts +25 -17
  35. package/src/specs/AndroidAutoMediaLibrary.nitro.ts +3 -2
  36. package/src/specs/DownloadManager.nitro.ts +4 -2
  37. package/src/specs/Equalizer.nitro.ts +5 -3
  38. package/src/specs/TrackPlayer.nitro.ts +18 -6
  39. package/src/types/EqualizerTypes.ts +17 -13
@@ -22,8 +22,9 @@ import kotlinx.coroutines.suspendCancellableCoroutine
22
22
  import kotlin.coroutines.resume
23
23
  import kotlin.coroutines.resumeWithException
24
24
 
25
- class TrackPlayerCore private constructor(internal val context: Context) {
26
-
25
+ class TrackPlayerCore private constructor(
26
+ internal val context: Context,
27
+ ) {
27
28
  // ── Thread infrastructure ──────────────────────────────────────────────
28
29
  /** Main-looper handler — only for Android Auto connection callbacks */
29
30
  internal val handler = Handler(Looper.getMainLooper())
@@ -33,6 +34,7 @@ class TrackPlayerCore private constructor(internal val context: Context) {
33
34
 
34
35
  // ── ExoPlayer wrapper (created on player thread inside initExoAndMedia) ──
35
36
  internal lateinit var exo: ExoPlayerCore
37
+
36
38
  /** Safe initialized check — backing field can only be read from the declaring class. */
37
39
  internal val isExoInitialized: Boolean get() = ::exo.isInitialized
38
40
 
@@ -45,9 +47,11 @@ class TrackPlayerCore private constructor(internal val context: Context) {
45
47
  // ── Playback state ─────────────────────────────────────────────────────
46
48
  @Volatile internal var currentPlaylistId: String? = null
47
49
  internal var isManuallySeeked = false
50
+
48
51
  @Volatile internal var isAndroidAutoConnectedField: Boolean = false
49
52
  internal var androidAutoConnectionDetector: AndroidAutoConnectionDetector? = null
50
53
  internal var previousMediaItem: androidx.media3.common.MediaItem? = null
54
+
51
55
  @Volatile internal var currentRepeatMode: RepeatMode = RepeatMode.OFF
52
56
  internal var lookaheadCount: Int = 5
53
57
  internal var playerListener: androidx.media3.common.Player.Listener? = null
@@ -78,33 +82,35 @@ class TrackPlayerCore private constructor(internal val context: Context) {
78
82
  ListenerRegistry<(Boolean) -> Unit>()
79
83
 
80
84
  // ── Progress & playlist-update runnables ───────────────────────────────
81
- internal val progressUpdateRunnable = object : Runnable {
82
- override fun run() {
83
- if (::exo.isInitialized &&
84
- exo.playbackState != androidx.media3.common.Player.STATE_IDLE
85
- ) {
86
- val pos = exo.currentPosition / 1000.0
87
- val dur = if (exo.duration > 0) exo.duration / 1000.0 else 0.0
88
- notifyPlaybackProgress(pos, dur, if (isManuallySeeked) true else null)
89
- isManuallySeeked = false
85
+ internal val progressUpdateRunnable =
86
+ object : Runnable {
87
+ override fun run() {
88
+ if (::exo.isInitialized &&
89
+ exo.playbackState != androidx.media3.common.Player.STATE_IDLE
90
+ ) {
91
+ val pos = exo.currentPosition / 1000.0
92
+ val dur = if (exo.duration > 0) exo.duration / 1000.0 else 0.0
93
+ notifyPlaybackProgress(pos, dur, if (isManuallySeeked) true else null)
94
+ isManuallySeeked = false
95
+ }
96
+ playerHandler.postDelayed(this, 250)
90
97
  }
91
- playerHandler.postDelayed(this, 250)
92
98
  }
93
- }
94
99
 
95
- internal val updateCurrentPlaylistRunnable = Runnable {
96
- val id = currentPlaylistId ?: return@Runnable
97
- val playlist = playlistManager.getPlaylist(id) ?: return@Runnable
98
- currentTracks = playlist.tracks
99
- if (::exo.isInitialized &&
100
- exo.currentMediaItem != null &&
101
- exo.currentMediaItemIndex >= 0
102
- ) {
103
- rebuildQueueFromCurrentPosition()
104
- } else {
105
- updatePlayerQueue(playlist.tracks)
100
+ internal val updateCurrentPlaylistRunnable =
101
+ Runnable {
102
+ val id = currentPlaylistId ?: return@Runnable
103
+ val playlist = playlistManager.getPlaylist(id) ?: return@Runnable
104
+ currentTracks = playlist.tracks
105
+ if (::exo.isInitialized &&
106
+ exo.currentMediaItem != null &&
107
+ exo.currentMediaItemIndex >= 0
108
+ ) {
109
+ rebuildQueueFromCurrentPosition()
110
+ } else {
111
+ updatePlayerQueue(playlist.tracks)
112
+ }
106
113
  }
107
- }
108
114
 
109
115
  // ── Singleton ──────────────────────────────────────────────────────────
110
116
  companion object {
@@ -130,10 +136,14 @@ class TrackPlayerCore private constructor(internal val context: Context) {
130
136
  internal suspend fun <T> withPlayerContext(block: () -> T): T {
131
137
  if (Looper.myLooper() == playerThread.looper) return block()
132
138
  return suspendCancellableCoroutine { cont ->
133
- val r = Runnable {
134
- try { cont.resume(block()) }
135
- catch (e: Exception) { cont.resumeWithException(e) }
136
- }
139
+ val r =
140
+ Runnable {
141
+ try {
142
+ cont.resume(block())
143
+ } catch (e: Exception) {
144
+ cont.resumeWithException(e)
145
+ }
146
+ }
137
147
  playerHandler.post(r)
138
148
  cont.invokeOnCancellation { playerHandler.removeCallbacks(r) }
139
149
  }
@@ -157,45 +167,40 @@ class TrackPlayerCore private constructor(internal val context: Context) {
157
167
  // ── Simple read-only accessors ─────────────────────────────────────────
158
168
 
159
169
  fun isAndroidAutoConnected(): Boolean = isAndroidAutoConnectedField
170
+
160
171
  fun getCurrentPlaylistId(): String? = currentPlaylistId
172
+
161
173
  fun getPlaylistManager(): PlaylistManager = playlistManager
162
- fun getAllPlaylists(): List<com.margelo.nitro.nitroplayer.playlist.Playlist> =
163
- playlistManager.getAllPlaylists()
174
+
175
+ fun getAllPlaylists(): List<com.margelo.nitro.nitroplayer.playlist.Playlist> = playlistManager.getAllPlaylists()
164
176
 
165
177
  // ── Listener add/remove (returns stable ID for cleanup) ───────────────
166
178
 
167
- fun addOnChangeTrackListener(cb: (TrackItem, Reason?) -> Unit): Long =
168
- onChangeTrackListeners.add(cb)
169
- fun removeOnChangeTrackListener(id: Long): Boolean =
170
- onChangeTrackListeners.remove(id)
171
-
172
- fun addOnPlaybackStateChangeListener(cb: (TrackPlayerState, Reason?) -> Unit): Long =
173
- onPlaybackStateChangeListeners.add(cb)
174
- fun removeOnPlaybackStateChangeListener(id: Long): Boolean =
175
- onPlaybackStateChangeListeners.remove(id)
176
-
177
- fun addOnSeekListener(cb: (Double, Double) -> Unit): Long =
178
- onSeekListeners.add(cb)
179
- fun removeOnSeekListener(id: Long): Boolean =
180
- onSeekListeners.remove(id)
181
-
182
- fun addOnPlaybackProgressChangeListener(cb: (Double, Double, Boolean?) -> Unit): Long =
183
- onProgressListeners.add(cb)
184
- fun removeOnPlaybackProgressChangeListener(id: Long): Boolean =
185
- onProgressListeners.remove(id)
186
-
187
- fun addOnTracksNeedUpdateListener(cb: (List<TrackItem>, Int) -> Unit): Long =
188
- onTracksNeedUpdateListeners.add(cb)
189
- fun removeOnTracksNeedUpdateListener(id: Long): Boolean =
190
- onTracksNeedUpdateListeners.remove(id)
191
-
192
- fun addOnTemporaryQueueChangeListener(cb: (List<TrackItem>, List<TrackItem>) -> Unit): Long =
193
- onTemporaryQueueChangeListeners.add(cb)
194
- fun removeOnTemporaryQueueChangeListener(id: Long): Boolean =
195
- onTemporaryQueueChangeListeners.remove(id)
196
-
197
- fun addOnAndroidAutoConnectionListener(cb: (Boolean) -> Unit): Long =
198
- onAndroidAutoConnectionListeners.add(cb)
199
- fun removeOnAndroidAutoConnectionListener(id: Long): Boolean =
200
- onAndroidAutoConnectionListeners.remove(id)
179
+ fun addOnChangeTrackListener(cb: (TrackItem, Reason?) -> Unit): Long = onChangeTrackListeners.add(cb)
180
+
181
+ fun removeOnChangeTrackListener(id: Long): Boolean = onChangeTrackListeners.remove(id)
182
+
183
+ fun addOnPlaybackStateChangeListener(cb: (TrackPlayerState, Reason?) -> Unit): Long = onPlaybackStateChangeListeners.add(cb)
184
+
185
+ fun removeOnPlaybackStateChangeListener(id: Long): Boolean = onPlaybackStateChangeListeners.remove(id)
186
+
187
+ fun addOnSeekListener(cb: (Double, Double) -> Unit): Long = onSeekListeners.add(cb)
188
+
189
+ fun removeOnSeekListener(id: Long): Boolean = onSeekListeners.remove(id)
190
+
191
+ fun addOnPlaybackProgressChangeListener(cb: (Double, Double, Boolean?) -> Unit): Long = onProgressListeners.add(cb)
192
+
193
+ fun removeOnPlaybackProgressChangeListener(id: Long): Boolean = onProgressListeners.remove(id)
194
+
195
+ fun addOnTracksNeedUpdateListener(cb: (List<TrackItem>, Int) -> Unit): Long = onTracksNeedUpdateListeners.add(cb)
196
+
197
+ fun removeOnTracksNeedUpdateListener(id: Long): Boolean = onTracksNeedUpdateListeners.remove(id)
198
+
199
+ fun addOnTemporaryQueueChangeListener(cb: (List<TrackItem>, List<TrackItem>) -> Unit): Long = onTemporaryQueueChangeListeners.add(cb)
200
+
201
+ fun removeOnTemporaryQueueChangeListener(id: Long): Boolean = onTemporaryQueueChangeListeners.remove(id)
202
+
203
+ fun addOnAndroidAutoConnectionListener(cb: (Boolean) -> Unit): Long = onAndroidAutoConnectionListeners.add(cb)
204
+
205
+ fun removeOnAndroidAutoConnectionListener(id: Long): Boolean = onAndroidAutoConnectionListeners.remove(id)
201
206
  }
@@ -17,15 +17,19 @@ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
17
17
  internal class TrackPlayerEventListener(
18
18
  private val core: TrackPlayerCore,
19
19
  ) : Player.Listener {
20
-
21
- override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
20
+ override fun onMediaItemTransition(
21
+ mediaItem: MediaItem?,
22
+ reason: Int,
23
+ ) {
22
24
  with(core) {
23
25
  // TRACK repeat: REPEAT_MODE_ONE fires this every loop — not a real track change
24
26
  if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) return
25
27
 
26
28
  // Remove the track that just finished/was skipped from temp lists
27
- if ((reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO ||
28
- reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK) &&
29
+ if ((
30
+ reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO ||
31
+ reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK
32
+ ) &&
29
33
  previousMediaItem != null
30
34
  ) {
31
35
  previousMediaItem?.mediaId?.let { mediaId ->
@@ -54,12 +58,13 @@ internal class TrackPlayerEventListener(
54
58
  }
55
59
 
56
60
  val track = getCurrentTrack() ?: return
57
- val r = when (reason) {
58
- Player.MEDIA_ITEM_TRANSITION_REASON_AUTO -> Reason.END
59
- Player.MEDIA_ITEM_TRANSITION_REASON_SEEK -> Reason.USER_ACTION
60
- Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED -> Reason.USER_ACTION
61
- else -> null
62
- }
61
+ val r =
62
+ when (reason) {
63
+ Player.MEDIA_ITEM_TRANSITION_REASON_AUTO -> Reason.END
64
+ Player.MEDIA_ITEM_TRANSITION_REASON_SEEK -> Reason.USER_ACTION
65
+ Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED -> Reason.USER_ACTION
66
+ else -> null
67
+ }
63
68
  notifyTrackChange(track, r)
64
69
  mediaSessionManager?.onTrackChanged(track)
65
70
  checkUpcomingTracksForUrls(lookaheadCount)
@@ -67,13 +72,19 @@ internal class TrackPlayerEventListener(
67
72
  }
68
73
  }
69
74
 
70
- override fun onTimelineChanged(timeline: androidx.media3.common.Timeline, reason: Int) {
75
+ override fun onTimelineChanged(
76
+ timeline: androidx.media3.common.Timeline,
77
+ reason: Int,
78
+ ) {
71
79
  if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
72
80
  NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
73
81
  }
74
82
  }
75
83
 
76
- override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
84
+ override fun onPlayWhenReadyChanged(
85
+ playWhenReady: Boolean,
86
+ reason: Int,
87
+ ) {
77
88
  val r = if (reason == Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) Reason.USER_ACTION else null
78
89
  core.emitStateChange(r)
79
90
  }
@@ -9,15 +9,24 @@ import com.margelo.nitro.nitroplayer.TrackPlayerState
9
9
  * All methods must be called from the player thread (already serialised).
10
10
  */
11
11
 
12
- internal fun TrackPlayerCore.notifyTrackChange(track: TrackItem, reason: Reason?) {
12
+ internal fun TrackPlayerCore.notifyTrackChange(
13
+ track: TrackItem,
14
+ reason: Reason?,
15
+ ) {
13
16
  onChangeTrackListeners.forEach { it(track, reason) }
14
17
  }
15
18
 
16
- internal fun TrackPlayerCore.notifyPlaybackStateChange(state: TrackPlayerState, reason: Reason?) {
19
+ internal fun TrackPlayerCore.notifyPlaybackStateChange(
20
+ state: TrackPlayerState,
21
+ reason: Reason?,
22
+ ) {
17
23
  onPlaybackStateChangeListeners.forEach { it(state, reason) }
18
24
  }
19
25
 
20
- internal fun TrackPlayerCore.notifySeek(position: Double, duration: Double) {
26
+ internal fun TrackPlayerCore.notifySeek(
27
+ position: Double,
28
+ duration: Double,
29
+ ) {
21
30
  onSeekListeners.forEach { it(position, duration) }
22
31
  }
23
32
 
@@ -29,7 +38,10 @@ internal fun TrackPlayerCore.notifyPlaybackProgress(
29
38
  onProgressListeners.forEach { it(position, duration, isManuallySeeked) }
30
39
  }
31
40
 
32
- internal fun TrackPlayerCore.notifyTracksNeedUpdate(tracks: List<TrackItem>, lookahead: Int) {
41
+ internal fun TrackPlayerCore.notifyTracksNeedUpdate(
42
+ tracks: List<TrackItem>,
43
+ lookahead: Int,
44
+ ) {
33
45
  onTracksNeedUpdateListeners.forEach { it(tracks, lookahead) }
34
46
  }
35
47
 
@@ -16,80 +16,99 @@ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
16
16
 
17
17
  suspend fun TrackPlayerCore.play() = withPlayerContext { exo.play() }
18
18
 
19
-
20
19
  suspend fun TrackPlayerCore.pause() = withPlayerContext { exo.pause() }
21
20
 
22
- suspend fun TrackPlayerCore.seek(position: Double) = withPlayerContext {
23
- isManuallySeeked = true
24
- exo.seekTo((position * 1000).toLong())
25
- }
21
+ suspend fun TrackPlayerCore.seek(position: Double) =
22
+ withPlayerContext {
23
+ isManuallySeeked = true
24
+ exo.seekTo((position * 1000).toLong())
25
+ }
26
26
 
27
- suspend fun TrackPlayerCore.skipToNext() = withPlayerContext {
28
- if (exo.hasNextMediaItem()) {
29
- exo.seekToNext()
30
- checkUpcomingTracksForUrls(lookaheadCount)
27
+ suspend fun TrackPlayerCore.skipToNext() =
28
+ withPlayerContext {
29
+ if (exo.hasNextMediaItem()) {
30
+ exo.seekToNext()
31
+ checkUpcomingTracksForUrls(lookaheadCount)
32
+ }
31
33
  }
32
- }
33
34
 
34
- suspend fun TrackPlayerCore.skipToPrevious() = withPlayerContext {
35
- val currentPosition = exo.currentPosition
36
- when {
37
- currentPosition > 2000 -> exo.seekTo(0)
38
-
39
- currentTemporaryType != TrackPlayerCore.TemporaryType.NONE -> {
40
- val trackId = exo.currentMediaItem?.mediaId?.let { extractTrackId(it) }
41
- if (trackId != null) {
42
- when (currentTemporaryType) {
43
- TrackPlayerCore.TemporaryType.PLAY_NEXT -> {
44
- val idx = playNextStack.indexOfFirst { it.id == trackId }
45
- if (idx >= 0) playNextStack.removeAt(idx)
46
- }
47
- TrackPlayerCore.TemporaryType.UP_NEXT -> {
48
- val idx = upNextQueue.indexOfFirst { it.id == trackId }
49
- if (idx >= 0) upNextQueue.removeAt(idx)
35
+ suspend fun TrackPlayerCore.skipToPrevious() =
36
+ withPlayerContext {
37
+ val currentPosition = exo.currentPosition
38
+ when {
39
+ currentPosition > 2000 -> {
40
+ exo.seekTo(0)
41
+ }
42
+
43
+ currentTemporaryType != TrackPlayerCore.TemporaryType.NONE -> {
44
+ val trackId = exo.currentMediaItem?.mediaId?.let { extractTrackId(it) }
45
+ if (trackId != null) {
46
+ when (currentTemporaryType) {
47
+ TrackPlayerCore.TemporaryType.PLAY_NEXT -> {
48
+ val idx = playNextStack.indexOfFirst { it.id == trackId }
49
+ if (idx >= 0) playNextStack.removeAt(idx)
50
+ }
51
+
52
+ TrackPlayerCore.TemporaryType.UP_NEXT -> {
53
+ val idx = upNextQueue.indexOfFirst { it.id == trackId }
54
+ if (idx >= 0) upNextQueue.removeAt(idx)
55
+ }
56
+
57
+ else -> {}
50
58
  }
51
- else -> {}
52
59
  }
60
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
61
+ playFromIndexInternal(currentTrackIndex)
53
62
  }
54
- currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
55
- playFromIndexInternal(currentTrackIndex)
56
- }
57
63
 
58
- currentTrackIndex > 0 -> playFromIndexInternal(currentTrackIndex - 1)
64
+ currentTrackIndex > 0 -> {
65
+ playFromIndexInternal(currentTrackIndex - 1)
66
+ }
59
67
 
60
- else -> exo.seekTo(0)
68
+ else -> {
69
+ exo.seekTo(0)
70
+ }
71
+ }
72
+ checkUpcomingTracksForUrls(lookaheadCount)
61
73
  }
62
- checkUpcomingTracksForUrls(lookaheadCount)
63
- }
64
74
 
65
- suspend fun TrackPlayerCore.setRepeatMode(mode: RepeatMode) = withPlayerContext {
66
- currentRepeatMode = mode
67
- exo.setRepeatMode(
68
- when (mode) {
69
- RepeatMode.TRACK -> Player.REPEAT_MODE_ONE
70
- else -> Player.REPEAT_MODE_OFF
71
- }
72
- )
73
- }
75
+ suspend fun TrackPlayerCore.setRepeatMode(mode: RepeatMode) =
76
+ withPlayerContext {
77
+ currentRepeatMode = mode
78
+ exo.setRepeatMode(
79
+ when (mode) {
80
+ RepeatMode.TRACK -> Player.REPEAT_MODE_ONE
81
+ else -> Player.REPEAT_MODE_OFF
82
+ },
83
+ )
84
+ }
74
85
 
75
86
  fun TrackPlayerCore.getRepeatMode(): RepeatMode = currentRepeatMode
76
87
 
77
- suspend fun TrackPlayerCore.setVolume(volume: Double) = withPlayerContext {
78
- val clamped = volume.coerceIn(0.0, 100.0)
79
- exo.setVolume((clamped / 100.0).toFloat())
80
- }
88
+ suspend fun TrackPlayerCore.setVolume(volume: Double) =
89
+ withPlayerContext {
90
+ val clamped = volume.coerceIn(0.0, 100.0)
91
+ exo.setVolume((clamped / 100.0).toFloat())
92
+ }
81
93
 
82
- suspend fun TrackPlayerCore.configure(config: PlayerConfig) = withPlayerContext {
83
- config.androidAutoEnabled?.let { NitroPlayerMediaBrowserService.isAndroidAutoEnabled = it }
84
- config.lookaheadCount?.let { lookaheadCount = it.toInt() }
85
- mediaSessionManager?.configure(config.androidAutoEnabled, config.carPlayEnabled, config.showInNotification)
86
- }
94
+ suspend fun TrackPlayerCore.configure(config: PlayerConfig) =
95
+ withPlayerContext {
96
+ config.androidAutoEnabled?.let { NitroPlayerMediaBrowserService.isAndroidAutoEnabled = it }
97
+ config.lookaheadCount?.let { lookaheadCount = it.toInt() }
98
+ mediaSessionManager?.configure(config.androidAutoEnabled, config.carPlayEnabled, config.showInNotification)
99
+ }
87
100
 
88
- suspend fun TrackPlayerCore.playSong(songId: String, fromPlaylist: String?) = withPlayerContext {
101
+ suspend fun TrackPlayerCore.playSong(
102
+ songId: String,
103
+ fromPlaylist: String?,
104
+ ) = withPlayerContext {
89
105
  playSongInternal(songId, fromPlaylist)
90
106
  }
91
107
 
92
- internal fun TrackPlayerCore.playSongInternal(songId: String, fromPlaylist: String?) {
108
+ internal fun TrackPlayerCore.playSongInternal(
109
+ songId: String,
110
+ fromPlaylist: String?,
111
+ ) {
93
112
  playNextStack.clear()
94
113
  upNextQueue.clear()
95
114
  currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
@@ -102,7 +121,9 @@ internal fun TrackPlayerCore.playSongInternal(songId: String, fromPlaylist: Stri
102
121
  if (playlist != null) {
103
122
  songIndex = playlist.tracks.indexOfFirst { it.id == songId }
104
123
  if (songIndex >= 0) targetPlaylistId = fromPlaylist else return
105
- } else return
124
+ } else {
125
+ return
126
+ }
106
127
  } else {
107
128
  if (currentPlaylistId != null) {
108
129
  val cp = playlistManager.getPlaylist(currentPlaylistId!!)
@@ -114,12 +135,18 @@ internal fun TrackPlayerCore.playSongInternal(songId: String, fromPlaylist: Stri
114
135
  if (songIndex == -1) {
115
136
  for (playlist in playlistManager.getAllPlaylists()) {
116
137
  songIndex = playlist.tracks.indexOfFirst { it.id == songId }
117
- if (songIndex >= 0) { targetPlaylistId = playlist.id; break }
138
+ if (songIndex >= 0) {
139
+ targetPlaylistId = playlist.id
140
+ break
141
+ }
118
142
  }
119
143
  }
120
144
  if (songIndex == -1) {
121
145
  val all = playlistManager.getAllPlaylists()
122
- if (all.isNotEmpty()) { targetPlaylistId = all[0].id; songIndex = 0 }
146
+ if (all.isNotEmpty()) {
147
+ targetPlaylistId = all[0].id
148
+ songIndex = 0
149
+ }
123
150
  }
124
151
  }
125
152
 
@@ -137,26 +164,28 @@ internal fun TrackPlayerCore.playSongInternal(songId: String, fromPlaylist: Stri
137
164
 
138
165
  internal fun TrackPlayerCore.emitStateChange(reason: Reason? = null) {
139
166
  if (!isExoInitialized) return
140
- val state = when (exo.playbackState) {
141
- Player.STATE_IDLE -> TrackPlayerState.STOPPED
142
- Player.STATE_BUFFERING -> if (exo.playWhenReady) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
143
- Player.STATE_READY -> if (exo.isPlaying) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
144
- Player.STATE_ENDED -> TrackPlayerState.STOPPED
145
- else -> TrackPlayerState.STOPPED
146
- }
167
+ val state =
168
+ when (exo.playbackState) {
169
+ Player.STATE_IDLE -> TrackPlayerState.STOPPED
170
+ Player.STATE_BUFFERING -> if (exo.playWhenReady) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
171
+ Player.STATE_READY -> if (exo.isPlaying) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
172
+ Player.STATE_ENDED -> TrackPlayerState.STOPPED
173
+ else -> TrackPlayerState.STOPPED
174
+ }
147
175
  val actualReason = reason ?: if (exo.playbackState == Player.STATE_ENDED) Reason.END else null
148
176
  notifyPlaybackStateChange(state, actualReason)
149
177
  mediaSessionManager?.onPlaybackStateChanged(state == TrackPlayerState.PLAYING)
150
178
  }
151
179
 
152
-
153
180
  // ── Playback speed ────────────────────────────────────────────────────────
154
181
 
155
- suspend fun TrackPlayerCore.setPlayBackSpeed(speed: Double) = withPlayerContext {
156
- if (speed <= 0.0) throw IllegalArgumentException("Speed must be greater than 0")
157
- if (isExoInitialized) exo.setPlaybackSpeed(speed.toFloat())
158
- }
182
+ suspend fun TrackPlayerCore.setPlayBackSpeed(speed: Double) =
183
+ withPlayerContext {
184
+ if (speed <= 0.0) throw IllegalArgumentException("Speed must be greater than 0")
185
+ if (isExoInitialized) exo.setPlaybackSpeed(speed.toFloat())
186
+ }
159
187
 
160
- suspend fun TrackPlayerCore.getPlayBackSpeed(): Double = withPlayerContext {
161
- if (isExoInitialized) exo.getPlaybackSpeed().toDouble() else 1.0
162
- }
188
+ suspend fun TrackPlayerCore.getPlayBackSpeed(): Double =
189
+ withPlayerContext {
190
+ if (isExoInitialized) exo.getPlaybackSpeed().toDouble() else 1.0
191
+ }