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.
- package/README.md +47 -46
- package/android/src/main/AndroidManifest.xml +14 -1
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +68 -49
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +67 -21
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +27 -5
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +88 -49
- package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +40 -10
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ExoPlayerCore.kt +70 -29
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ListenerRegistry.kt +4 -1
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerAndroidAuto.kt +38 -32
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +70 -65
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerListener.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerNotify.kt +16 -4
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerPlayback.kt +101 -72
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +64 -30
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +54 -28
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerTempQueue.kt +73 -62
- package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerUrlLoader.kt +51 -48
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadManagerCore.kt +12 -3
- package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +169 -98
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +30 -178
- package/android/src/main/java/com/margelo/nitro/nitroplayer/media/PlaybackService.kt +40 -0
- package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +87 -85
- package/ios/core/TrackPlayerQueue.swift +27 -18
- package/ios/core/TrackPlayerQueueBuild.swift +16 -5
- package/ios/equalizer/EqualizerCore.swift +39 -34
- package/lib/hooks/useEqualizer.js +10 -5
- package/lib/specs/Equalizer.nitro.d.ts +1 -1
- package/lib/types/EqualizerTypes.d.ts +3 -3
- package/package.json +5 -5
- package/src/hooks/useEqualizer.ts +25 -17
- package/src/specs/AndroidAutoMediaLibrary.nitro.ts +3 -2
- package/src/specs/DownloadManager.nitro.ts +4 -2
- package/src/specs/Equalizer.nitro.ts +5 -3
- package/src/specs/TrackPlayer.nitro.ts +18 -6
- 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(
|
|
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 =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
exo.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
exo.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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 =
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
169
|
-
fun removeOnChangeTrackListener(id: Long): Boolean =
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
fun
|
|
178
|
-
|
|
179
|
-
fun
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
fun
|
|
188
|
-
|
|
189
|
-
fun
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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 ((
|
|
28
|
-
|
|
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 =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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) =
|
|
23
|
-
|
|
24
|
-
|
|
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() =
|
|
28
|
-
|
|
29
|
-
exo.
|
|
30
|
-
|
|
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() =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
64
|
+
currentTrackIndex > 0 -> {
|
|
65
|
+
playFromIndexInternal(currentTrackIndex - 1)
|
|
66
|
+
}
|
|
59
67
|
|
|
60
|
-
|
|
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) =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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) =
|
|
78
|
-
|
|
79
|
-
|
|
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) =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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) {
|
|
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()) {
|
|
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 =
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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) =
|
|
156
|
-
|
|
157
|
-
|
|
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 =
|
|
161
|
-
|
|
162
|
-
|
|
188
|
+
suspend fun TrackPlayerCore.getPlayBackSpeed(): Double =
|
|
189
|
+
withPlayerContext {
|
|
190
|
+
if (isExoInitialized) exo.getPlaybackSpeed().toDouble() else 1.0
|
|
191
|
+
}
|