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

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 (164) hide show
  1. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrary.kt +9 -13
  2. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridAudioDevices.kt +45 -90
  3. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridDownloadManager.kt +48 -182
  4. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridEqualizer.kt +21 -77
  5. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridPlayerQueue.kt +55 -104
  6. package/android/src/main/java/com/margelo/nitro/nitroplayer/HybridTrackPlayer.kt +113 -123
  7. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ExoPlayerCore.kt +82 -0
  8. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/ListenerRegistry.kt +48 -0
  9. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerAndroidAuto.kt +62 -0
  10. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerCore.kt +153 -1887
  11. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerListener.kt +122 -0
  12. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerNotify.kt +44 -0
  13. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerPlayback.kt +162 -0
  14. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueue.kt +165 -0
  15. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerQueueBuild.kt +161 -0
  16. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerSetup.kt +28 -0
  17. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerTempQueue.kt +121 -0
  18. package/android/src/main/java/com/margelo/nitro/nitroplayer/core/TrackPlayerUrlLoader.kt +98 -0
  19. package/android/src/main/java/com/margelo/nitro/nitroplayer/download/DownloadDatabase.kt +27 -18
  20. package/android/src/main/java/com/margelo/nitro/nitroplayer/equalizer/EqualizerCore.kt +11 -58
  21. package/android/src/main/java/com/margelo/nitro/nitroplayer/media/MediaSessionManager.kt +13 -30
  22. package/android/src/main/java/com/margelo/nitro/nitroplayer/playlist/PlaylistManager.kt +102 -162
  23. package/ios/HybridDownloadManager.swift +32 -26
  24. package/ios/HybridEqualizer.swift +48 -35
  25. package/ios/HybridTrackPlayer.swift +127 -102
  26. package/ios/core/ListenerRegistry.swift +60 -0
  27. package/ios/core/TrackPlayerCore.swift +130 -2356
  28. package/ios/core/TrackPlayerListener.swift +395 -0
  29. package/ios/core/TrackPlayerNotify.swift +52 -0
  30. package/ios/core/TrackPlayerPlayback.swift +274 -0
  31. package/ios/core/TrackPlayerQueue.swift +212 -0
  32. package/ios/core/TrackPlayerQueueBuild.swift +482 -0
  33. package/ios/core/TrackPlayerTempQueue.swift +167 -0
  34. package/ios/core/TrackPlayerUrlLoader.swift +169 -0
  35. package/ios/equalizer/EqualizerCore.swift +24 -89
  36. package/ios/media/MediaSessionManager.swift +32 -49
  37. package/ios/playlist/PlaylistManager.swift +2 -9
  38. package/ios/queue/HybridPlayerQueue.swift +69 -66
  39. package/lib/hooks/useDownloadedTracks.js +16 -13
  40. package/lib/hooks/useEqualizer.d.ts +4 -4
  41. package/lib/hooks/useEqualizer.js +12 -12
  42. package/lib/hooks/useEqualizerPresets.d.ts +3 -3
  43. package/lib/hooks/useEqualizerPresets.js +12 -18
  44. package/lib/specs/AndroidAutoMediaLibrary.nitro.d.ts +2 -2
  45. package/lib/specs/AudioDevices.nitro.d.ts +2 -2
  46. package/lib/specs/DownloadManager.nitro.d.ts +10 -10
  47. package/lib/specs/Equalizer.nitro.d.ts +9 -9
  48. package/lib/specs/TrackPlayer.nitro.d.ts +38 -16
  49. package/nitro.json +44 -11
  50. package/nitrogen/generated/android/NitroPlayerOnLoad.cpp +63 -24
  51. package/nitrogen/generated/android/c++/JCurrentPlayingType.hpp +1 -1
  52. package/nitrogen/generated/android/c++/JDownloadConfig.hpp +1 -1
  53. package/nitrogen/generated/android/c++/JDownloadError.hpp +1 -1
  54. package/nitrogen/generated/android/c++/JDownloadErrorReason.hpp +1 -1
  55. package/nitrogen/generated/android/c++/JDownloadProgress.hpp +1 -1
  56. package/nitrogen/generated/android/c++/JDownloadQueueStatus.hpp +1 -1
  57. package/nitrogen/generated/android/c++/JDownloadState.hpp +1 -1
  58. package/nitrogen/generated/android/c++/JDownloadStorageInfo.hpp +1 -1
  59. package/nitrogen/generated/android/c++/JDownloadTask.hpp +1 -1
  60. package/nitrogen/generated/android/c++/JDownloadedPlaylist.hpp +1 -1
  61. package/nitrogen/generated/android/c++/JDownloadedTrack.hpp +1 -1
  62. package/nitrogen/generated/android/c++/JEqualizerBand.hpp +1 -1
  63. package/nitrogen/generated/android/c++/JEqualizerPreset.hpp +1 -1
  64. package/nitrogen/generated/android/c++/JEqualizerState.hpp +1 -1
  65. package/nitrogen/generated/android/c++/JFunc_void_DownloadProgress.hpp +2 -2
  66. package/nitrogen/generated/android/c++/JFunc_void_DownloadedTrack.hpp +2 -2
  67. package/nitrogen/generated/android/c++/JFunc_void_TrackItem_std__optional_Reason_.hpp +2 -2
  68. package/nitrogen/generated/android/c++/JFunc_void_TrackPlayerState_std__optional_Reason_.hpp +2 -2
  69. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +2 -2
  70. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +2 -2
  71. package/nitrogen/generated/android/c++/JFunc_void_double_double_std__optional_bool_.hpp +2 -2
  72. package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__std__string__.hpp +2 -2
  73. package/nitrogen/generated/android/c++/JFunc_void_std__string_Playlist_std__optional_QueueOperation_.hpp +2 -2
  74. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__string_DownloadState_std__optional_DownloadError_.hpp +2 -2
  75. package/nitrogen/generated/android/c++/JFunc_void_std__vector_EqualizerBand_.hpp +2 -2
  76. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Playlist__std__optional_QueueOperation_.hpp +2 -2
  77. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double.hpp +2 -2
  78. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__std__vector_TrackItem_.hpp +122 -0
  79. package/nitrogen/generated/android/c++/JGainRange.hpp +1 -1
  80. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +49 -30
  81. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +21 -24
  82. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +35 -28
  83. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +20 -23
  84. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +197 -93
  85. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +29 -32
  86. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +157 -67
  87. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +28 -31
  88. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +138 -53
  89. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +27 -30
  90. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +282 -69
  91. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +35 -30
  92. package/nitrogen/generated/android/c++/JPlaybackSource.hpp +1 -1
  93. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +1 -1
  94. package/nitrogen/generated/android/c++/JPlayerState.hpp +1 -1
  95. package/nitrogen/generated/android/c++/JPlaylist.hpp +1 -1
  96. package/nitrogen/generated/android/c++/JPresetType.hpp +1 -1
  97. package/nitrogen/generated/android/c++/JQueueOperation.hpp +1 -1
  98. package/nitrogen/generated/android/c++/JReason.hpp +1 -1
  99. package/nitrogen/generated/android/c++/JRepeatMode.hpp +1 -1
  100. package/nitrogen/generated/android/c++/JStorageLocation.hpp +1 -1
  101. package/nitrogen/generated/android/c++/JTAudioDevice.hpp +1 -1
  102. package/nitrogen/generated/android/c++/JTrackItem.hpp +1 -1
  103. package/nitrogen/generated/android/c++/JTrackPlayerState.hpp +1 -1
  104. package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +3 -3
  105. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadError.hpp +3 -3
  106. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadTask.hpp +3 -3
  107. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedPlaylist.hpp +3 -3
  108. package/nitrogen/generated/android/c++/JVariant_NullType_DownloadedTrack.hpp +3 -3
  109. package/nitrogen/generated/android/c++/JVariant_NullType_Playlist.hpp +3 -3
  110. package/nitrogen/generated/android/c++/JVariant_NullType_String.hpp +3 -3
  111. package/nitrogen/generated/android/c++/JVariant_NullType_TrackItem.hpp +3 -3
  112. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__std__vector_TrackItem_.kt +80 -0
  113. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +18 -20
  114. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +17 -19
  115. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +25 -28
  116. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +25 -27
  117. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +24 -26
  118. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +60 -26
  119. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Double.kt +0 -6
  120. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadError.kt +0 -6
  121. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadTask.kt +0 -6
  122. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedPlaylist.kt +0 -6
  123. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_DownloadedTrack.kt +0 -6
  124. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_Playlist.kt +0 -6
  125. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_String.kt +0 -6
  126. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Variant_NullType_TrackItem.kt +0 -6
  127. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +74 -18
  128. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +380 -151
  129. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +10 -10
  130. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +12 -9
  131. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +23 -8
  132. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +82 -8
  133. package/nitrogen/generated/ios/swift/Func_void_EqualizerState.swift +46 -0
  134. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedPlaylist_.swift +58 -0
  135. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedTrack_.swift +58 -0
  136. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__std__string_.swift +58 -0
  137. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedPlaylist_.swift +46 -0
  138. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedTrack_.swift +46 -0
  139. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +5 -5
  140. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__std__vector_TrackItem_.swift +46 -0
  141. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +10 -10
  142. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +141 -71
  143. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +9 -9
  144. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +105 -41
  145. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +8 -8
  146. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +95 -32
  147. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +16 -8
  148. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +267 -32
  149. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +3 -2
  150. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +2 -1
  151. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +10 -10
  152. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +10 -9
  153. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +9 -8
  154. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +8 -0
  155. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +16 -8
  156. package/package.json +3 -3
  157. package/src/hooks/useDownloadedTracks.ts +17 -13
  158. package/src/hooks/useEqualizer.ts +16 -16
  159. package/src/hooks/useEqualizerPresets.ts +15 -21
  160. package/src/specs/AndroidAutoMediaLibrary.nitro.ts +2 -2
  161. package/src/specs/AudioDevices.nitro.ts +2 -2
  162. package/src/specs/DownloadManager.nitro.ts +10 -10
  163. package/src/specs/Equalizer.nitro.ts +9 -9
  164. package/src/specs/TrackPlayer.nitro.ts +52 -16
@@ -0,0 +1,122 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
3
+ package com.margelo.nitro.nitroplayer.core
4
+
5
+ import androidx.media3.common.MediaItem
6
+ import androidx.media3.common.Player
7
+ import com.margelo.nitro.nitroplayer.Reason
8
+ import com.margelo.nitro.nitroplayer.RepeatMode
9
+ import com.margelo.nitro.nitroplayer.equalizer.EqualizerCore
10
+ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
11
+
12
+ /**
13
+ * ExoPlayer event listener — translates low-level ExoPlayer callbacks into
14
+ * TrackPlayerCore state mutations and JS-facing listener notifications.
15
+ * All callbacks fire on the player thread (ExoPlayerCore is built with playerThread.looper).
16
+ */
17
+ internal class TrackPlayerEventListener(
18
+ private val core: TrackPlayerCore,
19
+ ) : Player.Listener {
20
+
21
+ override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
22
+ with(core) {
23
+ // TRACK repeat: REPEAT_MODE_ONE fires this every loop — not a real track change
24
+ if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) return
25
+
26
+ // 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
+ previousMediaItem != null
30
+ ) {
31
+ previousMediaItem?.mediaId?.let { mediaId ->
32
+ val trackId = extractTrackId(mediaId)
33
+ val pnIdx = playNextStack.indexOfFirst { it.id == trackId }
34
+ if (pnIdx >= 0) {
35
+ playNextStack.removeAt(pnIdx)
36
+ } else {
37
+ val unIdx = upNextQueue.indexOfFirst { it.id == trackId }
38
+ if (unIdx >= 0) upNextQueue.removeAt(unIdx)
39
+ }
40
+ }
41
+ }
42
+
43
+ // Track new current item as "previous" for the next transition
44
+ previousMediaItem = mediaItem
45
+
46
+ // Re-determine temporary type for the new current item
47
+ currentTemporaryType = determineCurrentTemporaryType()
48
+
49
+ // Update currentTrackIndex when landing on an original playlist track
50
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.NONE && mediaItem != null) {
51
+ val trackId = extractTrackId(mediaItem.mediaId)
52
+ val newIdx = currentTracks.indexOfFirst { it.id == trackId }
53
+ if (newIdx >= 0 && newIdx != currentTrackIndex) currentTrackIndex = newIdx
54
+ }
55
+
56
+ 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
+ }
63
+ notifyTrackChange(track, r)
64
+ mediaSessionManager?.onTrackChanged(track)
65
+ checkUpcomingTracksForUrls(lookaheadCount)
66
+ notifyTemporaryQueueChange()
67
+ }
68
+ }
69
+
70
+ override fun onTimelineChanged(timeline: androidx.media3.common.Timeline, reason: Int) {
71
+ if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
72
+ NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
73
+ }
74
+ }
75
+
76
+ override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
77
+ val r = if (reason == Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) Reason.USER_ACTION else null
78
+ core.emitStateChange(r)
79
+ }
80
+
81
+ override fun onPlaybackStateChanged(playbackState: Int) {
82
+ with(core) {
83
+ if (playbackState == Player.STATE_ENDED && currentRepeatMode == RepeatMode.PLAYLIST) {
84
+ playNextStack.clear()
85
+ upNextQueue.clear()
86
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
87
+ rebuildQueueAndPlayFromIndex(0)
88
+ val firstTrack = currentTracks.getOrNull(0)
89
+ if (firstTrack != null) notifyTrackChange(firstTrack, Reason.REPEAT)
90
+ return
91
+ }
92
+ emitStateChange()
93
+ }
94
+ }
95
+
96
+ override fun onIsPlayingChanged(isPlaying: Boolean) {
97
+ core.emitStateChange()
98
+ }
99
+
100
+ override fun onPositionDiscontinuity(
101
+ oldPosition: Player.PositionInfo,
102
+ newPosition: Player.PositionInfo,
103
+ reason: Int,
104
+ ) {
105
+ if (reason == Player.DISCONTINUITY_REASON_SEEK) {
106
+ core.isManuallySeeked = true
107
+ val pos = core.exo.currentPosition / 1000.0
108
+ val dur = if (core.exo.duration > 0) core.exo.duration / 1000.0 else 0.0
109
+ core.notifySeek(pos, dur)
110
+ }
111
+ }
112
+
113
+ override fun onAudioSessionIdChanged(audioSessionId: Int) {
114
+ if (audioSessionId != 0) {
115
+ try {
116
+ EqualizerCore.getInstance(core.context).initialize(audioSessionId)
117
+ } catch (_: Exception) {
118
+ // Non-critical — device may not support equalizer
119
+ }
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,44 @@
1
+ package com.margelo.nitro.nitroplayer.core
2
+
3
+ import com.margelo.nitro.nitroplayer.Reason
4
+ import com.margelo.nitro.nitroplayer.TrackItem
5
+ import com.margelo.nitro.nitroplayer.TrackPlayerState
6
+
7
+ /**
8
+ * Notification helpers — call the registered ListenerRegistry callbacks.
9
+ * All methods must be called from the player thread (already serialised).
10
+ */
11
+
12
+ internal fun TrackPlayerCore.notifyTrackChange(track: TrackItem, reason: Reason?) {
13
+ onChangeTrackListeners.forEach { it(track, reason) }
14
+ }
15
+
16
+ internal fun TrackPlayerCore.notifyPlaybackStateChange(state: TrackPlayerState, reason: Reason?) {
17
+ onPlaybackStateChangeListeners.forEach { it(state, reason) }
18
+ }
19
+
20
+ internal fun TrackPlayerCore.notifySeek(position: Double, duration: Double) {
21
+ onSeekListeners.forEach { it(position, duration) }
22
+ }
23
+
24
+ internal fun TrackPlayerCore.notifyPlaybackProgress(
25
+ position: Double,
26
+ duration: Double,
27
+ isManuallySeeked: Boolean?,
28
+ ) {
29
+ onProgressListeners.forEach { it(position, duration, isManuallySeeked) }
30
+ }
31
+
32
+ internal fun TrackPlayerCore.notifyTracksNeedUpdate(tracks: List<TrackItem>, lookahead: Int) {
33
+ onTracksNeedUpdateListeners.forEach { it(tracks, lookahead) }
34
+ }
35
+
36
+ internal fun TrackPlayerCore.notifyTemporaryQueueChange() {
37
+ val pn = playNextStack.toList()
38
+ val un = upNextQueue.toList()
39
+ onTemporaryQueueChangeListeners.forEach { it(pn, un) }
40
+ }
41
+
42
+ internal fun TrackPlayerCore.notifyAndroidAutoConnection(connected: Boolean) {
43
+ onAndroidAutoConnectionListeners.forEach { it(connected) }
44
+ }
@@ -0,0 +1,162 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
3
+ package com.margelo.nitro.nitroplayer.core
4
+
5
+ import androidx.media3.common.Player
6
+ import com.margelo.nitro.nitroplayer.PlayerConfig
7
+ import com.margelo.nitro.nitroplayer.Reason
8
+ import com.margelo.nitro.nitroplayer.RepeatMode
9
+ import com.margelo.nitro.nitroplayer.TrackPlayerState
10
+ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
11
+
12
+ /**
13
+ * Playback control — all public functions are suspend and execute on the player thread
14
+ * via withPlayerContext.
15
+ */
16
+
17
+ suspend fun TrackPlayerCore.play() = withPlayerContext { exo.play() }
18
+
19
+
20
+ suspend fun TrackPlayerCore.pause() = withPlayerContext { exo.pause() }
21
+
22
+ suspend fun TrackPlayerCore.seek(position: Double) = withPlayerContext {
23
+ isManuallySeeked = true
24
+ exo.seekTo((position * 1000).toLong())
25
+ }
26
+
27
+ suspend fun TrackPlayerCore.skipToNext() = withPlayerContext {
28
+ if (exo.hasNextMediaItem()) {
29
+ exo.seekToNext()
30
+ checkUpcomingTracksForUrls(lookaheadCount)
31
+ }
32
+ }
33
+
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)
50
+ }
51
+ else -> {}
52
+ }
53
+ }
54
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
55
+ playFromIndexInternal(currentTrackIndex)
56
+ }
57
+
58
+ currentTrackIndex > 0 -> playFromIndexInternal(currentTrackIndex - 1)
59
+
60
+ else -> exo.seekTo(0)
61
+ }
62
+ checkUpcomingTracksForUrls(lookaheadCount)
63
+ }
64
+
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
+ }
74
+
75
+ fun TrackPlayerCore.getRepeatMode(): RepeatMode = currentRepeatMode
76
+
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
+ }
81
+
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
+ }
87
+
88
+ suspend fun TrackPlayerCore.playSong(songId: String, fromPlaylist: String?) = withPlayerContext {
89
+ playSongInternal(songId, fromPlaylist)
90
+ }
91
+
92
+ internal fun TrackPlayerCore.playSongInternal(songId: String, fromPlaylist: String?) {
93
+ playNextStack.clear()
94
+ upNextQueue.clear()
95
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
96
+
97
+ var targetPlaylistId: String? = null
98
+ var songIndex: Int = -1
99
+
100
+ if (fromPlaylist != null) {
101
+ val playlist = playlistManager.getPlaylist(fromPlaylist)
102
+ if (playlist != null) {
103
+ songIndex = playlist.tracks.indexOfFirst { it.id == songId }
104
+ if (songIndex >= 0) targetPlaylistId = fromPlaylist else return
105
+ } else return
106
+ } else {
107
+ if (currentPlaylistId != null) {
108
+ val cp = playlistManager.getPlaylist(currentPlaylistId!!)
109
+ if (cp != null) {
110
+ songIndex = cp.tracks.indexOfFirst { it.id == songId }
111
+ if (songIndex >= 0) targetPlaylistId = currentPlaylistId
112
+ }
113
+ }
114
+ if (songIndex == -1) {
115
+ for (playlist in playlistManager.getAllPlaylists()) {
116
+ songIndex = playlist.tracks.indexOfFirst { it.id == songId }
117
+ if (songIndex >= 0) { targetPlaylistId = playlist.id; break }
118
+ }
119
+ }
120
+ if (songIndex == -1) {
121
+ val all = playlistManager.getAllPlaylists()
122
+ if (all.isNotEmpty()) { targetPlaylistId = all[0].id; songIndex = 0 }
123
+ }
124
+ }
125
+
126
+ if (targetPlaylistId == null || songIndex < 0) return
127
+
128
+ if (currentPlaylistId != targetPlaylistId) {
129
+ val playlist = playlistManager.getPlaylist(targetPlaylistId) ?: return
130
+ currentPlaylistId = targetPlaylistId
131
+ updatePlayerQueue(playlist.tracks)
132
+ }
133
+ playFromIndexInternal(songIndex)
134
+ }
135
+
136
+ // ── State emission (called from player thread) ─────────────────────────────
137
+
138
+ internal fun TrackPlayerCore.emitStateChange(reason: Reason? = null) {
139
+ 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
+ }
147
+ val actualReason = reason ?: if (exo.playbackState == Player.STATE_ENDED) Reason.END else null
148
+ notifyPlaybackStateChange(state, actualReason)
149
+ mediaSessionManager?.onPlaybackStateChanged(state == TrackPlayerState.PLAYING)
150
+ }
151
+
152
+
153
+ // ── Playback speed ────────────────────────────────────────────────────────
154
+
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
+ }
159
+
160
+ suspend fun TrackPlayerCore.getPlayBackSpeed(): Double = withPlayerContext {
161
+ if (isExoInitialized) exo.getPlaybackSpeed().toDouble() else 1.0
162
+ }
@@ -0,0 +1,165 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
3
+ package com.margelo.nitro.nitroplayer.core
4
+
5
+ import androidx.media3.common.Player
6
+ import com.margelo.nitro.nitroplayer.CurrentPlayingType
7
+ import com.margelo.nitro.nitroplayer.PlayerState
8
+ import com.margelo.nitro.nitroplayer.TrackItem
9
+ import com.margelo.nitro.nitroplayer.TrackPlayerState
10
+ import com.margelo.nitro.nitroplayer.Variant_NullType_String
11
+ import com.margelo.nitro.nitroplayer.Variant_NullType_TrackItem
12
+
13
+ /**
14
+ * State read + index-navigation — all public functions are suspend and run on the
15
+ * player thread via withPlayerContext.
16
+ */
17
+
18
+ // ── Player state ──────────────────────────────────────────────────────────
19
+
20
+ suspend fun TrackPlayerCore.getState(): PlayerState = withPlayerContext { getStateInternal() }
21
+
22
+ internal fun TrackPlayerCore.getStateInternal(): PlayerState {
23
+ if (!isExoInitialized) {
24
+ return PlayerState(
25
+ currentTrack = null,
26
+ currentPosition = 0.0,
27
+ totalDuration = 0.0,
28
+ currentState = TrackPlayerState.STOPPED,
29
+ currentPlaylistId = currentPlaylistId?.let { Variant_NullType_String.create(it) },
30
+ currentIndex = -1.0,
31
+ currentPlayingType = CurrentPlayingType.NOT_PLAYING,
32
+ )
33
+ }
34
+ val track = getCurrentTrack()
35
+ val currentTrack: Variant_NullType_TrackItem? = track?.let { Variant_NullType_TrackItem.create(it) }
36
+ val position = exo.currentPosition / 1000.0
37
+ val duration = if (exo.duration > 0) exo.duration / 1000.0 else 0.0
38
+ val state = when (exo.playbackState) {
39
+ Player.STATE_IDLE -> TrackPlayerState.STOPPED
40
+ Player.STATE_BUFFERING -> if (exo.playWhenReady) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
41
+ Player.STATE_READY -> if (exo.isPlaying) TrackPlayerState.PLAYING else TrackPlayerState.PAUSED
42
+ Player.STATE_ENDED -> TrackPlayerState.STOPPED
43
+ else -> TrackPlayerState.STOPPED
44
+ }
45
+ val playingType = if (track == null) CurrentPlayingType.NOT_PLAYING else when (currentTemporaryType) {
46
+ TrackPlayerCore.TemporaryType.NONE -> CurrentPlayingType.PLAYLIST
47
+ TrackPlayerCore.TemporaryType.PLAY_NEXT -> CurrentPlayingType.PLAY_NEXT
48
+ TrackPlayerCore.TemporaryType.UP_NEXT -> CurrentPlayingType.UP_NEXT
49
+ }
50
+ return PlayerState(
51
+ currentTrack = currentTrack,
52
+ currentPosition = position,
53
+ totalDuration = duration,
54
+ currentState = state,
55
+ currentPlaylistId = currentPlaylistId?.let { Variant_NullType_String.create(it) },
56
+ currentIndex = if (exo.currentMediaItemIndex >= 0) exo.currentMediaItemIndex.toDouble() else -1.0,
57
+ currentPlayingType = playingType,
58
+ )
59
+ }
60
+
61
+ // ── Actual queue ──────────────────────────────────────────────────────────
62
+
63
+ suspend fun TrackPlayerCore.getActualQueue(): List<TrackItem> = withPlayerContext { getActualQueueInternal() }
64
+
65
+ internal fun TrackPlayerCore.getActualQueueInternal(): List<TrackItem> {
66
+ if (!isExoInitialized) return emptyList()
67
+ val currentIndex = currentTrackIndex
68
+ if (currentIndex < 0) return emptyList()
69
+
70
+ val queue = ArrayList<TrackItem>(currentTracks.size + playNextStack.size + upNextQueue.size)
71
+
72
+ // Tracks before current (include currentTrackIndex when a temp track is playing)
73
+ val beforeEnd = if (currentTemporaryType != TrackPlayerCore.TemporaryType.NONE) {
74
+ minOf(currentIndex + 1, currentTracks.size)
75
+ } else {
76
+ currentIndex
77
+ }
78
+ if (beforeEnd > 0) queue.addAll(currentTracks.subList(0, beforeEnd))
79
+
80
+ // Current track
81
+ getCurrentTrack()?.let { queue.add(it) }
82
+
83
+ // playNext — skip index 0 if it is the current item
84
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
85
+ queue.addAll(playNextStack.subList(1, playNextStack.size))
86
+ } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.PLAY_NEXT) {
87
+ queue.addAll(playNextStack)
88
+ }
89
+
90
+ // upNext — skip index 0 if it is the current item
91
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && upNextQueue.size > 1) {
92
+ queue.addAll(upNextQueue.subList(1, upNextQueue.size))
93
+ } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.UP_NEXT) {
94
+ queue.addAll(upNextQueue)
95
+ }
96
+
97
+ // Remaining original tracks
98
+ if (currentIndex + 1 < currentTracks.size) {
99
+ queue.addAll(currentTracks.subList(currentIndex + 1, currentTracks.size))
100
+ }
101
+ return queue
102
+ }
103
+
104
+ // ── Index navigation ──────────────────────────────────────────────────────
105
+
106
+ suspend fun TrackPlayerCore.skipToIndex(index: Int): Boolean = withPlayerContext { skipToIndexInternal(index) }
107
+
108
+ private fun TrackPlayerCore.skipToIndexInternal(index: Int): Boolean {
109
+ if (!isExoInitialized) return false
110
+ val actualQueue = getActualQueueInternal()
111
+ if (index < 0 || index >= actualQueue.size) return false
112
+
113
+ val currentPos = if (currentTemporaryType != TrackPlayerCore.TemporaryType.NONE) currentTrackIndex + 1 else currentTrackIndex
114
+ val effectivePlayNextSize = if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT) maxOf(0, playNextStack.size - 1) else playNextStack.size
115
+ val effectiveUpNextSize = if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT) maxOf(0, upNextQueue.size - 1) else upNextQueue.size
116
+
117
+ val playNextStart = currentPos + 1
118
+ val playNextEnd = playNextStart + effectivePlayNextSize
119
+ val upNextStart = playNextEnd
120
+ val upNextEnd = upNextStart + effectiveUpNextSize
121
+ val originalRemainingStart = upNextEnd
122
+
123
+ if (index < currentPos) { playFromIndexInternal(index); return true }
124
+ if (index == currentPos) { exo.seekTo(0); return true }
125
+
126
+ if (index in playNextStart until playNextEnd) {
127
+ val listIndex = (index - playNextStart) + if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT) 1 else 0
128
+ if (listIndex > 0) playNextStack.subList(0, listIndex).clear()
129
+ rebuildQueueFromCurrentPosition()
130
+ exo.seekToNext()
131
+ return true
132
+ }
133
+
134
+ if (index in upNextStart until upNextEnd) {
135
+ val listIndex = (index - upNextStart) + if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT) 1 else 0
136
+ playNextStack.clear()
137
+ if (listIndex > 0) upNextQueue.subList(0, listIndex).clear()
138
+ rebuildQueueFromCurrentPosition()
139
+ exo.seekToNext()
140
+ return true
141
+ }
142
+
143
+ if (index >= originalRemainingStart) {
144
+ val targetTrack = actualQueue[index]
145
+ val originalIndex = currentTracks.indexOfFirst { it.id == targetTrack.id }
146
+ if (originalIndex == -1) return false
147
+ playNextStack.clear(); upNextQueue.clear()
148
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
149
+ rebuildQueueAndPlayFromIndex(originalIndex)
150
+ checkUpcomingTracksForUrls(lookaheadCount)
151
+ return true
152
+ }
153
+
154
+ checkUpcomingTracksForUrls(lookaheadCount)
155
+ return false
156
+ }
157
+
158
+ suspend fun TrackPlayerCore.playFromIndex(index: Int) = withPlayerContext { playFromIndexInternal(index) }
159
+
160
+ internal fun TrackPlayerCore.playFromIndexInternal(index: Int) {
161
+ playNextStack.clear()
162
+ upNextQueue.clear()
163
+ currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
164
+ rebuildQueueAndPlayFromIndex(index)
165
+ }
@@ -0,0 +1,161 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
3
+ package com.margelo.nitro.nitroplayer.core
4
+
5
+ import android.net.Uri
6
+ import androidx.media3.common.MediaItem
7
+ import androidx.media3.common.MediaMetadata
8
+ import androidx.media3.common.Player
9
+ import com.margelo.nitro.nitroplayer.TrackItem
10
+
11
+ /**
12
+ * Queue-building helpers — called exclusively on the player thread.
13
+ * Surgical rebuild (removeMediaItems + addMediaItems) preserves the current item
14
+ * for gapless playback; full rebuild (clearMediaItems + setMediaItems) is used only
15
+ * when jumping to a specific index.
16
+ */
17
+
18
+ // ── Full rebuild (jump to index) ───────────────────────────────────────────
19
+
20
+ internal fun TrackPlayerCore.rebuildQueueAndPlayFromIndex(index: Int) {
21
+ if (!isExoInitialized) return
22
+ if (index < 0 || index >= currentTracks.size) return
23
+
24
+ val playlistId = currentPlaylistId ?: ""
25
+ val mediaItems = currentTracks.subList(index, currentTracks.size).map { track ->
26
+ val mediaId = if (playlistId.isNotEmpty()) "$playlistId:${track.id}" else track.id
27
+ makeMediaItem(track, mediaId)
28
+ }
29
+
30
+ currentTrackIndex = index
31
+ exo.clearMediaItems()
32
+ exo.setMediaItems(mediaItems)
33
+ exo.seekToDefaultPosition(0)
34
+ exo.playWhenReady = true
35
+ exo.prepare()
36
+ }
37
+
38
+ // ── Surgical rebuild (preserve current item) ──────────────────────────────
39
+
40
+ internal fun TrackPlayerCore.rebuildQueueFromCurrentPosition() {
41
+ if (!isExoInitialized) return
42
+ val currentIndex = exo.currentMediaItemIndex
43
+ if (currentIndex < 0) return
44
+
45
+ // If current track was removed from the playlist, jump to best substitute
46
+ val currentTrackId = exo.currentMediaItem?.mediaId?.let { extractTrackId(it) }
47
+ if (currentTrackId != null && currentTracks.none { it.id == currentTrackId }) {
48
+ if (currentTracks.isEmpty()) return
49
+ playFromIndexInternal(minOf(currentTrackIndex, currentTracks.size - 1))
50
+ return
51
+ }
52
+
53
+ val newQueueTracks = ArrayList<TrackItem>(playNextStack.size + upNextQueue.size + currentTracks.size)
54
+
55
+ // playNext stack — skip index 0 if it is the currently playing item
56
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.PLAY_NEXT && playNextStack.size > 1) {
57
+ newQueueTracks.addAll(playNextStack.subList(1, playNextStack.size))
58
+ } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.PLAY_NEXT) {
59
+ newQueueTracks.addAll(playNextStack)
60
+ }
61
+
62
+ // upNext queue — skip index 0 if it is the currently playing item
63
+ if (currentTemporaryType == TrackPlayerCore.TemporaryType.UP_NEXT && upNextQueue.size > 1) {
64
+ newQueueTracks.addAll(upNextQueue.subList(1, upNextQueue.size))
65
+ } else if (currentTemporaryType != TrackPlayerCore.TemporaryType.UP_NEXT) {
66
+ newQueueTracks.addAll(upNextQueue)
67
+ }
68
+
69
+ // Remaining original tracks (after currentTrackIndex, not after ExoPlayer's currentIndex)
70
+ if (currentTrackIndex + 1 < currentTracks.size) {
71
+ newQueueTracks.addAll(currentTracks.subList(currentTrackIndex + 1, currentTracks.size))
72
+ }
73
+
74
+ val playlistId = currentPlaylistId ?: ""
75
+ val newMediaItems = newQueueTracks.map { track ->
76
+ val mediaId = if (playlistId.isNotEmpty()) "$playlistId:${track.id}" else track.id
77
+ makeMediaItem(track, mediaId)
78
+ }
79
+
80
+ if (exo.mediaItemCount > currentIndex + 1) {
81
+ exo.removeMediaItems(currentIndex + 1, exo.mediaItemCount)
82
+ }
83
+ exo.addMediaItems(newMediaItems)
84
+ }
85
+
86
+ // ── Full queue set (initial load or no active item) ───────────────────────
87
+
88
+ internal fun TrackPlayerCore.updatePlayerQueue(tracks: List<TrackItem>) {
89
+ currentTracks = tracks
90
+ val playlistId = currentPlaylistId ?: ""
91
+ val mediaItems = tracks.map { track ->
92
+ val mediaId = if (playlistId.isNotEmpty()) "$playlistId:${track.id}" else track.id
93
+ makeMediaItem(track, mediaId)
94
+ }
95
+ exo.setMediaItems(mediaItems, false)
96
+ if (exo.playbackState == Player.STATE_IDLE && mediaItems.isNotEmpty()) {
97
+ exo.prepare()
98
+ }
99
+ }
100
+
101
+ // ── MediaItem construction (member extension to access downloadManager) ────
102
+
103
+ internal fun TrackPlayerCore.makeMediaItem(track: TrackItem, customMediaId: String? = null): MediaItem {
104
+ val metaBuilder = MediaMetadata.Builder()
105
+ .setTitle(track.title)
106
+ .setArtist(track.artist)
107
+ .setAlbumTitle(track.album)
108
+
109
+ track.artwork?.asSecondOrNull()?.let { artworkUrl ->
110
+ try { metaBuilder.setArtworkUri(Uri.parse(artworkUrl)) } catch (_: Exception) {}
111
+ }
112
+
113
+ val effectiveUrl = downloadManager.getEffectiveUrl(track)
114
+
115
+ return MediaItem.Builder()
116
+ .setMediaId(customMediaId ?: track.id)
117
+ .setUri(effectiveUrl)
118
+ .setMediaMetadata(metaBuilder.build())
119
+ .build()
120
+ }
121
+
122
+ // ── Track lookup helpers ───────────────────────────────────────────────────
123
+
124
+ internal fun TrackPlayerCore.findTrack(mediaItem: MediaItem?): TrackItem? {
125
+ if (mediaItem == null) return null
126
+ val trackId = extractTrackId(mediaItem.mediaId)
127
+ return currentTracks.find { it.id == trackId }
128
+ }
129
+
130
+ internal fun TrackPlayerCore.findTrackById(trackId: String): TrackItem? {
131
+ currentTracks.find { it.id == trackId }?.let { return it }
132
+ for (playlist in playlistManager.getAllPlaylists()) {
133
+ playlist.tracks.find { it.id == trackId }?.let { return it }
134
+ }
135
+ return null
136
+ }
137
+
138
+ internal fun TrackPlayerCore.getCurrentTrack(): TrackItem? {
139
+ if (!isExoInitialized) return null
140
+ val currentMediaItem = exo.currentMediaItem ?: return null
141
+ if (currentTemporaryType != TrackPlayerCore.TemporaryType.NONE) {
142
+ val trackId = extractTrackId(currentMediaItem.mediaId)
143
+ return when (currentTemporaryType) {
144
+ TrackPlayerCore.TemporaryType.PLAY_NEXT -> playNextStack.firstOrNull { it.id == trackId }
145
+ TrackPlayerCore.TemporaryType.UP_NEXT -> upNextQueue.firstOrNull { it.id == trackId }
146
+ else -> null
147
+ }
148
+ }
149
+ return findTrack(currentMediaItem)
150
+ }
151
+
152
+ internal fun TrackPlayerCore.determineCurrentTemporaryType(): TrackPlayerCore.TemporaryType {
153
+ val currentItem = exo.currentMediaItem ?: return TrackPlayerCore.TemporaryType.NONE
154
+ val trackId = extractTrackId(currentItem.mediaId)
155
+ if (playNextStack.any { it.id == trackId }) return TrackPlayerCore.TemporaryType.PLAY_NEXT
156
+ if (upNextQueue.any { it.id == trackId }) return TrackPlayerCore.TemporaryType.UP_NEXT
157
+ return TrackPlayerCore.TemporaryType.NONE
158
+ }
159
+
160
+ internal fun TrackPlayerCore.extractTrackId(mediaId: String): String =
161
+ if (mediaId.contains(':')) mediaId.substring(mediaId.indexOf(':') + 1) else mediaId
@@ -0,0 +1,28 @@
1
+ package com.margelo.nitro.nitroplayer.core
2
+
3
+ import com.margelo.nitro.nitroplayer.media.MediaSessionManager
4
+ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
5
+
6
+ /**
7
+ * Initialises ExoPlayer (via ExoPlayerCore) and MediaSessionManager on the player thread.
8
+ * Called once from TrackPlayerCore.init via playerHandler.post.
9
+ */
10
+ internal fun TrackPlayerCore.initExoAndMedia() {
11
+ exo = ExoPlayerCore(context, playerThread)
12
+
13
+ mediaSessionManager = MediaSessionManager(context, exo.player, playlistManager).apply {
14
+ setTrackPlayerCore(this@initExoAndMedia)
15
+ }
16
+
17
+ // Give MediaBrowserService access to this core and media session
18
+ NitroPlayerMediaBrowserService.trackPlayerCore = this
19
+ NitroPlayerMediaBrowserService.mediaSessionManager = mediaSessionManager
20
+
21
+ // Attach player listener
22
+ val listener = TrackPlayerEventListener(this)
23
+ playerListener = listener
24
+ exo.addListener(listener)
25
+
26
+ // Start progress ticks on the player thread
27
+ playerHandler.postDelayed(progressUpdateRunnable, 250)
28
+ }