react-native-nitro-player 0.7.0 → 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 (107) 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/nitrogen/generated/android/NitroPlayerOnLoad.cpp +2 -0
  50. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__std__vector_TrackItem_.hpp +122 -0
  51. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.cpp +31 -6
  52. package/nitrogen/generated/android/c++/JHybridAndroidAutoMediaLibrarySpec.hpp +2 -2
  53. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.cpp +16 -3
  54. package/nitrogen/generated/android/c++/JHybridAudioDevicesSpec.hpp +1 -1
  55. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.cpp +154 -44
  56. package/nitrogen/generated/android/c++/JHybridDownloadManagerSpec.hpp +10 -10
  57. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.cpp +130 -34
  58. package/nitrogen/generated/android/c++/JHybridEqualizerSpec.hpp +9 -9
  59. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.cpp +115 -24
  60. package/nitrogen/generated/android/c++/JHybridPlayerQueueSpec.hpp +8 -8
  61. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +243 -24
  62. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +16 -8
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/Func_void_std__vector_TrackItem__std__vector_TrackItem_.kt +80 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAndroidAutoMediaLibrarySpec.kt +3 -2
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridAudioDevicesSpec.kt +2 -1
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridDownloadManagerSpec.kt +10 -10
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridEqualizerSpec.kt +10 -9
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridPlayerQueueSpec.kt +9 -8
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitroplayer/HybridTrackPlayerSpec.kt +45 -8
  70. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.cpp +74 -18
  71. package/nitrogen/generated/ios/NitroPlayer-Swift-Cxx-Bridge.hpp +380 -151
  72. package/nitrogen/generated/ios/c++/HybridDownloadManagerSpecSwift.hpp +10 -10
  73. package/nitrogen/generated/ios/c++/HybridEqualizerSpecSwift.hpp +12 -9
  74. package/nitrogen/generated/ios/c++/HybridPlayerQueueSpecSwift.hpp +23 -8
  75. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +82 -8
  76. package/nitrogen/generated/ios/swift/Func_void_EqualizerState.swift +46 -0
  77. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedPlaylist_.swift +58 -0
  78. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__DownloadedTrack_.swift +58 -0
  79. package/nitrogen/generated/ios/swift/Func_void_std__variant_nitro__NullType__std__string_.swift +58 -0
  80. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedPlaylist_.swift +46 -0
  81. package/nitrogen/generated/ios/swift/Func_void_std__vector_DownloadedTrack_.swift +46 -0
  82. package/nitrogen/generated/ios/swift/Func_void_std__vector_EqualizerBand_.swift +5 -5
  83. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__std__vector_TrackItem_.swift +46 -0
  84. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec.swift +10 -10
  85. package/nitrogen/generated/ios/swift/HybridDownloadManagerSpec_cxx.swift +141 -71
  86. package/nitrogen/generated/ios/swift/HybridEqualizerSpec.swift +9 -9
  87. package/nitrogen/generated/ios/swift/HybridEqualizerSpec_cxx.swift +105 -41
  88. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec.swift +8 -8
  89. package/nitrogen/generated/ios/swift/HybridPlayerQueueSpec_cxx.swift +95 -32
  90. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +16 -8
  91. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +267 -32
  92. package/nitrogen/generated/shared/c++/HybridAndroidAutoMediaLibrarySpec.hpp +3 -2
  93. package/nitrogen/generated/shared/c++/HybridAudioDevicesSpec.hpp +2 -1
  94. package/nitrogen/generated/shared/c++/HybridDownloadManagerSpec.hpp +10 -10
  95. package/nitrogen/generated/shared/c++/HybridEqualizerSpec.hpp +10 -9
  96. package/nitrogen/generated/shared/c++/HybridPlayerQueueSpec.hpp +9 -8
  97. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +8 -0
  98. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +16 -8
  99. package/package.json +1 -1
  100. package/src/hooks/useDownloadedTracks.ts +17 -13
  101. package/src/hooks/useEqualizer.ts +16 -16
  102. package/src/hooks/useEqualizerPresets.ts +15 -21
  103. package/src/specs/AndroidAutoMediaLibrary.nitro.ts +2 -2
  104. package/src/specs/AudioDevices.nitro.ts +2 -2
  105. package/src/specs/DownloadManager.nitro.ts +10 -10
  106. package/src/specs/Equalizer.nitro.ts +9 -9
  107. package/src/specs/TrackPlayer.nitro.ts +52 -16
@@ -1,11 +1,15 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
1
3
  package com.margelo.nitro.nitroplayer
2
4
 
3
5
  import androidx.annotation.Keep
4
- import com.facebook.jni.HybridData
5
6
  import com.facebook.proguard.annotations.DoNotStrip
6
7
  import com.margelo.nitro.NitroModules
7
8
  import com.margelo.nitro.core.NullType
9
+ import com.margelo.nitro.core.Promise
8
10
  import com.margelo.nitro.nitroplayer.core.TrackPlayerCore
11
+ import com.margelo.nitro.nitroplayer.core.loadPlaylist
12
+ import com.margelo.nitro.nitroplayer.core.updatePlaylist
9
13
  import com.margelo.nitro.nitroplayer.playlist.PlaylistManager
10
14
  import java.util.UUID
11
15
  import com.margelo.nitro.nitroplayer.playlist.Playlist as InternalPlaylist
@@ -17,7 +21,8 @@ class HybridPlayerQueue : HybridPlayerQueueSpec() {
17
21
  private val playlistManager: PlaylistManager
18
22
 
19
23
  init {
20
- val context = NitroModules.applicationContext ?: throw IllegalStateException("React Context is not initialized")
24
+ val context = NitroModules.applicationContext
25
+ ?: throw IllegalStateException("React Context is not initialized")
21
26
  core = TrackPlayerCore.getInstance(context)
22
27
  playlistManager = core.getPlaylistManager()
23
28
  }
@@ -25,143 +30,89 @@ class HybridPlayerQueue : HybridPlayerQueueSpec() {
25
30
  private val playlistsChangeListeners = java.util.concurrent.CopyOnWriteArrayList<() -> Unit>()
26
31
  private val playlistChangeListeners = java.util.concurrent.ConcurrentHashMap<String, () -> Unit>()
27
32
 
28
- @DoNotStrip
29
- @Keep
30
- override fun createPlaylist(
31
- name: String,
32
- description: String?,
33
- artwork: String?,
34
- ): String = playlistManager.createPlaylist(name, description, artwork)
35
-
36
- @DoNotStrip
37
- @Keep
38
- override fun deletePlaylist(playlistId: String) {
33
+ // ── Playlist CRUD ─────────────────────────────────────────────────────────
34
+
35
+ override fun createPlaylist(name: String, description: String?, artwork: String?): Promise<String> =
36
+ Promise.async { playlistManager.createPlaylist(name, description, artwork) }
37
+
38
+ override fun deletePlaylist(playlistId: String): Promise<Unit> = Promise.async {
39
39
  playlistManager.deletePlaylist(playlistId)
40
40
  }
41
41
 
42
- @DoNotStrip
43
- @Keep
44
- override fun updatePlaylist(
45
- playlistId: String,
46
- name: String?,
47
- description: String?,
48
- artwork: String?,
49
- ) {
42
+ override fun updatePlaylist(playlistId: String, name: String?, description: String?, artwork: String?): Promise<Unit> = Promise.async {
50
43
  playlistManager.updatePlaylist(playlistId, name, description, artwork)
44
+ core.updatePlaylist(playlistId)
51
45
  }
52
46
 
53
- @DoNotStrip
54
- @Keep
55
47
  override fun getPlaylist(playlistId: String): Variant_NullType_Playlist {
56
48
  val playlist = playlistManager.getPlaylist(playlistId)
57
- return if (playlist != null) {
58
- Variant_NullType_Playlist.create(playlist.toPlaylist())
59
- } else {
60
- Variant_NullType_Playlist.create(NullType.NULL)
61
- }
49
+ return if (playlist != null) Variant_NullType_Playlist.create(playlist.toPlaylist())
50
+ else Variant_NullType_Playlist.create(NullType.NULL)
62
51
  }
63
52
 
64
- @DoNotStrip
65
- @Keep
66
53
  override fun getAllPlaylists(): Array<Playlist> =
67
- playlistManager
68
- .getAllPlaylists()
69
- .map {
70
- it.toPlaylist()
71
- }.toTypedArray()
72
-
73
- @DoNotStrip
74
- @Keep
75
- override fun addTrackToPlaylist(
76
- playlistId: String,
77
- track: TrackItem,
78
- index: Double?,
79
- ) {
80
- val insertIndex = index?.toInt()
81
- playlistManager.addTrackToPlaylist(playlistId, track, insertIndex)
54
+ playlistManager.getAllPlaylists().map { it.toPlaylist() }.toTypedArray()
55
+
56
+ // ── Track mutations ───────────────────────────────────────────────────────
57
+
58
+ override fun addTrackToPlaylist(playlistId: String, track: TrackItem, index: Double?): Promise<Unit> = Promise.async {
59
+ playlistManager.addTrackToPlaylist(playlistId, track, index?.toInt())
60
+ core.updatePlaylist(playlistId)
82
61
  }
83
62
 
84
- @DoNotStrip
85
- @Keep
86
- override fun addTracksToPlaylist(
87
- playlistId: String,
88
- tracks: Array<TrackItem>,
89
- index: Double?,
90
- ) {
91
- val insertIndex = index?.toInt()
92
- playlistManager.addTracksToPlaylist(playlistId, tracks.toList(), insertIndex)
63
+ override fun addTracksToPlaylist(playlistId: String, tracks: Array<TrackItem>, index: Double?): Promise<Unit> = Promise.async {
64
+ playlistManager.addTracksToPlaylist(playlistId, tracks.toList(), index?.toInt())
65
+ core.updatePlaylist(playlistId)
93
66
  }
94
67
 
95
- @DoNotStrip
96
- @Keep
97
- override fun removeTrackFromPlaylist(
98
- playlistId: String,
99
- trackId: String,
100
- ) {
68
+ override fun removeTrackFromPlaylist(playlistId: String, trackId: String): Promise<Unit> = Promise.async {
101
69
  playlistManager.removeTrackFromPlaylist(playlistId, trackId)
70
+ core.updatePlaylist(playlistId)
102
71
  }
103
72
 
104
- @DoNotStrip
105
- @Keep
106
- override fun reorderTrackInPlaylist(
107
- playlistId: String,
108
- trackId: String,
109
- newIndex: Double,
110
- ) {
73
+ override fun reorderTrackInPlaylist(playlistId: String, trackId: String, newIndex: Double): Promise<Unit> = Promise.async {
111
74
  playlistManager.reorderTrackInPlaylist(playlistId, trackId, newIndex.toInt())
75
+ core.updatePlaylist(playlistId)
112
76
  }
113
77
 
114
- @DoNotStrip
115
- @Keep
116
- override fun loadPlaylist(playlistId: String) {
117
- playlistManager.loadPlaylist(playlistId)
78
+ // ── Playback control ──────────────────────────────────────────────────────
79
+
80
+ override fun loadPlaylist(playlistId: String): Promise<Unit> = Promise.async {
81
+ core.loadPlaylist(playlistId)
118
82
  }
119
83
 
120
- @DoNotStrip
121
- @Keep
122
84
  override fun getCurrentPlaylistId(): Variant_NullType_String {
123
- val playlistId = playlistManager.getCurrentPlaylistId()
124
- return if (playlistId != null) {
125
- Variant_NullType_String.create(playlistId)
126
- } else {
127
- Variant_NullType_String.create(NullType.NULL)
128
- }
85
+ val id = core.getCurrentPlaylistId()
86
+ return if (id != null) Variant_NullType_String.create(id)
87
+ else Variant_NullType_String.create(NullType.NULL)
129
88
  }
130
89
 
131
- @DoNotStrip
132
- @Keep
90
+ // ── Events ────────────────────────────────────────────────────────────────
91
+
133
92
  override fun onPlaylistsChanged(callback: (playlists: Array<Playlist>, operation: QueueOperation?) -> Unit) {
134
- // Add new listener and store the cleanup function
135
- val removeListener =
136
- playlistManager.addPlaylistsChangeListener { playlists, operation ->
137
- callback(playlists.map { it.toPlaylist() }.toTypedArray(), operation)
138
- }
93
+ val removeListener = playlistManager.addPlaylistsChangeListener { playlists, operation ->
94
+ callback(playlists.map { it.toPlaylist() }.toTypedArray(), operation)
95
+ }
139
96
  playlistsChangeListeners.add(removeListener)
140
97
  }
141
98
 
142
- @DoNotStrip
143
- @Keep
144
99
  override fun onPlaylistChanged(callback: (playlistId: String, playlist: Playlist, operation: QueueOperation?) -> Unit) {
145
- // Listen to all playlists and filter by playlistId
146
100
  val listenerId = UUID.randomUUID().toString()
147
-
148
- // For each playlist, add a listener
149
101
  playlistManager.getAllPlaylists().forEach { internalPlaylist ->
150
- val removeListener =
151
- playlistManager.addPlaylistChangeListener(internalPlaylist.id) { playlist, operation ->
152
- callback(playlist.id, playlist.toPlaylist(), operation)
153
- }
102
+ val removeListener = playlistManager.addPlaylistChangeListener(internalPlaylist.id) { playlist, operation ->
103
+ callback(playlist.id, playlist.toPlaylist(), operation)
104
+ }
154
105
  playlistChangeListeners[listenerId] = removeListener
155
106
  }
156
107
  }
157
108
 
158
- // Helper to convert internal Playlist to generated Playlist type
159
- private fun InternalPlaylist.toPlaylist(): Playlist =
160
- Playlist(
161
- id = this.id,
162
- name = this.name,
163
- description = this.description?.let { Variant_NullType_String.create(it) },
164
- artwork = this.artwork?.let { Variant_NullType_String.create(it) },
165
- tracks = this.tracks.toTypedArray(),
166
- )
109
+ // ── Helper ────────────────────────────────────────────────────────────────
110
+
111
+ private fun InternalPlaylist.toPlaylist(): Playlist = Playlist(
112
+ id = this.id,
113
+ name = this.name,
114
+ description = this.description?.let { Variant_NullType_String.create(it) },
115
+ artwork = this.artwork?.let { Variant_NullType_String.create(it) },
116
+ tracks = this.tracks.toTypedArray(),
117
+ )
167
118
  }
@@ -1,3 +1,5 @@
1
+ @file:Suppress("ktlint:standard:max-line-length")
2
+
1
3
  package com.margelo.nitro.nitroplayer
2
4
 
3
5
  import androidx.annotation.Keep
@@ -5,169 +7,157 @@ import com.facebook.proguard.annotations.DoNotStrip
5
7
  import com.margelo.nitro.NitroModules
6
8
  import com.margelo.nitro.core.Promise
7
9
  import com.margelo.nitro.nitroplayer.core.TrackPlayerCore
10
+ import com.margelo.nitro.nitroplayer.core.addToUpNext
11
+ import com.margelo.nitro.nitroplayer.core.clearPlayNext
12
+ import com.margelo.nitro.nitroplayer.core.clearUpNext
13
+ import com.margelo.nitro.nitroplayer.core.configure
14
+ import com.margelo.nitro.nitroplayer.core.getActualQueue
15
+ import com.margelo.nitro.nitroplayer.core.getCurrentTrackIndex
16
+ import com.margelo.nitro.nitroplayer.core.getNextTracks
17
+ import com.margelo.nitro.nitroplayer.core.getPlayBackSpeed
18
+ import com.margelo.nitro.nitroplayer.core.getPlayNextQueue
19
+ import com.margelo.nitro.nitroplayer.core.getRepeatMode
20
+ import com.margelo.nitro.nitroplayer.core.getState
21
+ import com.margelo.nitro.nitroplayer.core.getTracksById
22
+ import com.margelo.nitro.nitroplayer.core.getTracksNeedingUrls
23
+ import com.margelo.nitro.nitroplayer.core.getUpNextQueue
24
+ import com.margelo.nitro.nitroplayer.core.pause
25
+ import com.margelo.nitro.nitroplayer.core.play
26
+ import com.margelo.nitro.nitroplayer.core.playNext
27
+ import com.margelo.nitro.nitroplayer.core.playSong
28
+ import com.margelo.nitro.nitroplayer.core.removeFromPlayNext
29
+ import com.margelo.nitro.nitroplayer.core.removeFromUpNext
30
+ import com.margelo.nitro.nitroplayer.core.reorderTemporaryTrack
31
+ import com.margelo.nitro.nitroplayer.core.seek
32
+ import com.margelo.nitro.nitroplayer.core.setPlayBackSpeed
33
+ import com.margelo.nitro.nitroplayer.core.setRepeatMode
34
+ import com.margelo.nitro.nitroplayer.core.setVolume
35
+ import com.margelo.nitro.nitroplayer.core.skipToIndex
36
+ import com.margelo.nitro.nitroplayer.core.skipToNext
37
+ import com.margelo.nitro.nitroplayer.core.skipToPrevious
38
+ import com.margelo.nitro.nitroplayer.core.updateTracks
8
39
 
9
40
  @DoNotStrip
10
41
  @Keep
11
42
  class HybridTrackPlayer : HybridTrackPlayerSpec() {
12
43
  private val core: TrackPlayerCore
13
44
 
45
+ // Track listener IDs for cleanup in dispose()
46
+ private val listenerIds = mutableListOf<Pair<String, Long>>()
47
+
14
48
  init {
15
- val context =
16
- NitroModules.applicationContext ?: throw IllegalStateException("React Context is not initialized")
49
+ val context = NitroModules.applicationContext
50
+ ?: throw IllegalStateException("React Context is not initialized")
17
51
  core = TrackPlayerCore.getInstance(context)
18
52
  }
19
53
 
20
- @DoNotStrip
21
- @Keep
22
- override fun play() {
23
- core.play()
24
- }
54
+ // ── Playback ─────────────────────────────────────────────────────────────
25
55
 
26
- @DoNotStrip
27
- @Keep
28
- override fun pause() {
29
- core.pause()
30
- }
56
+ override fun play(): Promise<Unit> = Promise.async { core.play() }
57
+ override fun pause(): Promise<Unit> = Promise.async { core.pause() }
58
+ override fun seek(position: Double): Promise<Unit> = Promise.async { core.seek(position) }
59
+ override fun skipToNext(): Promise<Unit> = Promise.async { core.skipToNext() }
60
+ override fun skipToPrevious(): Promise<Unit> = Promise.async { core.skipToPrevious() }
61
+ override fun playSong(songId: String, fromPlaylist: String?): Promise<Unit> = Promise.async { core.playSong(songId, fromPlaylist) }
62
+ override fun skipToIndex(index: Double): Promise<Boolean> = Promise.async { core.skipToIndex(index.toInt()) }
31
63
 
32
- override fun playSong(
33
- songId: String,
34
- fromPlaylist: String?,
35
- ): Promise<Unit> =
36
- Promise.async {
37
- core.playSong(songId, fromPlaylist)
38
- }
64
+ override fun setRepeatMode(mode: RepeatMode): Promise<Unit> = Promise.async { core.setRepeatMode(mode) }
65
+ override fun getRepeatMode(): RepeatMode = core.getRepeatMode()
39
66
 
40
- @DoNotStrip
41
- @Keep
42
- override fun skipToNext() {
43
- core.skipToNext()
44
- }
67
+ override fun setVolume(volume: Double): Promise<Unit> = Promise.async { core.setVolume(volume) }
45
68
 
46
- @DoNotStrip
47
- @Keep
48
- override fun skipToPrevious() {
49
- core.skipToPrevious()
50
- }
69
+ override fun configure(config: PlayerConfig): Promise<Unit> = Promise.async { core.configure(config) }
51
70
 
52
- @DoNotStrip
53
- @Keep
54
- override fun seek(position: Double) {
55
- core.seek(position)
56
- }
71
+ // ── Queue / state reads ───────────────────────────────────────────────────
57
72
 
58
- override fun addToUpNext(trackId: String): Promise<Unit> =
59
- Promise.async {
60
- core.addToUpNext(trackId)
61
- }
73
+ override fun getActualQueue(): Promise<Array<TrackItem>> = Promise.async { core.getActualQueue().toTypedArray() }
74
+ override fun getState(): Promise<PlayerState> = Promise.async { core.getState() }
75
+ override fun getTracksById(trackIds: Array<String>): Promise<Array<TrackItem>> = Promise.async { core.getTracksById(trackIds.toList()).toTypedArray() }
76
+ override fun getTracksNeedingUrls(): Promise<Array<TrackItem>> = Promise.async { core.getTracksNeedingUrls().toTypedArray() }
77
+ override fun getNextTracks(count: Double): Promise<Array<TrackItem>> = Promise.async { core.getNextTracks(count.toInt()).toTypedArray() }
78
+ override fun getCurrentTrackIndex(): Promise<Double> = Promise.async { core.getCurrentTrackIndex().toDouble() }
62
79
 
63
- override fun playNext(trackId: String): Promise<Unit> =
64
- Promise.async {
65
- core.playNext(trackId)
66
- }
80
+ // ── URL updates ───────────────────────────────────────────────────────────
67
81
 
68
- override fun getActualQueue(): Promise<Array<TrackItem>> =
69
- Promise.async {
70
- core.getActualQueue().toTypedArray()
71
- }
82
+ override fun updateTracks(tracks: Array<TrackItem>): Promise<Unit> = Promise.async { core.updateTracks(tracks.toList()) }
72
83
 
73
- override fun getState(): Promise<PlayerState> =
74
- Promise.async {
75
- core.getState()
76
- }
84
+ // ── Temporary queue ───────────────────────────────────────────────────────
77
85
 
78
- @DoNotStrip
79
- @Keep
80
- override fun setRepeatMode(mode: RepeatMode): Boolean = core.setRepeatMode(mode)
86
+ override fun addToUpNext(trackId: String): Promise<Unit> = Promise.async { core.addToUpNext(trackId) }
87
+ override fun playNext(trackId: String): Promise<Unit> = Promise.async { core.playNext(trackId) }
88
+ override fun removeFromPlayNext(trackId: String): Promise<Boolean> = Promise.async { core.removeFromPlayNext(trackId) }
89
+ override fun removeFromUpNext(trackId: String): Promise<Boolean> = Promise.async { core.removeFromUpNext(trackId) }
90
+ override fun clearPlayNext(): Promise<Unit> = Promise.async { core.clearPlayNext() }
91
+ override fun clearUpNext(): Promise<Unit> = Promise.async { core.clearUpNext() }
92
+ override fun reorderTemporaryTrack(trackId: String, newIndex: Double): Promise<Boolean> = Promise.async { core.reorderTemporaryTrack(trackId, newIndex.toInt()) }
93
+ override fun getPlayNextQueue(): Promise<Array<TrackItem>> = Promise.async { core.getPlayNextQueue().toTypedArray() }
94
+ override fun getUpNextQueue(): Promise<Array<TrackItem>> = Promise.async { core.getUpNextQueue().toTypedArray() }
81
95
 
82
- @DoNotStrip
83
- @Keep
84
- override fun getRepeatMode(): RepeatMode = core.getRepeatMode()
96
+ // ── Playback speed ────────────────────────────────────────────────────────
97
+
98
+ override fun setPlaybackSpeed(speed: Double): Promise<Unit> = Promise.async { core.setPlayBackSpeed(speed) }
99
+ override fun getPlaybackSpeed(): Promise<Double> = Promise.async { core.getPlayBackSpeed() }
100
+
101
+ // ── Android Auto ──────────────────────────────────────────────────────────
102
+
103
+ override fun isAndroidAutoConnected(): Boolean = core.isAndroidAutoConnected()
104
+
105
+ // ── Event listeners ───────────────────────────────────────────────────────
85
106
 
86
107
  override fun onChangeTrack(callback: (track: TrackItem, reason: Reason?) -> Unit) {
87
- core.addOnChangeTrackListener(callback)
108
+ val id = core.addOnChangeTrackListener(callback)
109
+ listenerIds += "onChangeTrack" to id
88
110
  }
89
111
 
90
112
  override fun onPlaybackStateChange(callback: (state: TrackPlayerState, reason: Reason?) -> Unit) {
91
- core.addOnPlaybackStateChangeListener(callback)
113
+ val id = core.addOnPlaybackStateChangeListener(callback)
114
+ listenerIds += "onPlaybackStateChange" to id
92
115
  }
93
116
 
94
117
  override fun onSeek(callback: (position: Double, totalDuration: Double) -> Unit) {
95
- core.addOnSeekListener(callback)
118
+ val id = core.addOnSeekListener(callback)
119
+ listenerIds += "onSeek" to id
96
120
  }
97
121
 
98
122
  override fun onPlaybackProgressChange(callback: (position: Double, totalDuration: Double, isManuallySeeked: Boolean?) -> Unit) {
99
- core.addOnPlaybackProgressChangeListener(callback)
100
- }
101
-
102
- @DoNotStrip
103
- @Keep
104
- override fun configure(config: PlayerConfig) {
105
- core.configure(
106
- androidAutoEnabled = config.androidAutoEnabled,
107
- carPlayEnabled = config.carPlayEnabled,
108
- showInNotification = config.showInNotification,
109
- lookaheadCount = config.lookaheadCount?.toInt(),
110
- )
123
+ val id = core.addOnPlaybackProgressChangeListener(callback)
124
+ listenerIds += "onPlaybackProgressChange" to id
111
125
  }
112
126
 
113
- @Keep
114
- override fun onAndroidAutoConnectionChange(callback: (Boolean) -> Unit) {
115
- core.onAndroidAutoConnectionChange = callback
127
+ override fun onAndroidAutoConnectionChange(callback: (connected: Boolean) -> Unit) {
128
+ val id = core.addOnAndroidAutoConnectionListener(callback)
129
+ listenerIds += "onAndroidAutoConnectionChange" to id
116
130
  }
117
131
 
118
- @Keep
119
- override fun isAndroidAutoConnected(): Boolean = core.isAndroidAutoConnected()
120
-
121
- @DoNotStrip
122
- @Keep
123
- override fun setVolume(volume: Double): Boolean = core.setVolume(volume)
124
-
125
- @DoNotStrip
126
- @Keep
127
- override fun skipToIndex(index: Double): Promise<Boolean> =
128
- Promise.async {
129
- core.skipToIndex(index.toInt())
130
- }
131
-
132
- override fun updateTracks(tracks: Array<TrackItem>): Promise<Unit> =
133
- Promise.async {
134
- core.updateTracks(tracks.toList())
135
- }
136
-
137
- override fun getTracksById(trackIds: Array<String>): Promise<Array<TrackItem>> =
138
- Promise.async {
139
- core.getTracksById(trackIds.toList()).toTypedArray()
140
- }
141
-
142
- override fun getTracksNeedingUrls(): Promise<Array<TrackItem>> =
143
- Promise.async {
144
- core.getTracksNeedingUrls().toTypedArray()
145
- }
146
-
147
- override fun getNextTracks(count: Double): Promise<Array<TrackItem>> =
148
- Promise.async {
149
- core.getNextTracks(count.toInt()).toTypedArray()
150
- }
151
-
152
- override fun getCurrentTrackIndex(): Promise<Double> =
153
- Promise.async {
154
- core.getCurrentTrackIndex().toDouble()
155
- }
156
-
157
- override fun setPlaybackSpeed(speed: Double): Promise<Unit> =
158
- Promise.async {
159
- core.setPlayBackSpeed(speed)
160
- Unit
132
+ override fun onTracksNeedUpdate(callback: (tracks: Array<TrackItem>, lookahead: Double) -> Unit) {
133
+ val id = core.addOnTracksNeedUpdateListener { tracks, lookahead ->
134
+ callback(tracks.toTypedArray(), lookahead.toDouble())
161
135
  }
136
+ listenerIds += "onTracksNeedUpdate" to id
137
+ }
162
138
 
163
- override fun getPlaybackSpeed(): Promise<Double> =
164
- Promise.async {
165
- core.getPlayBackSpeed()
139
+ override fun onTemporaryQueueChange(callback: (playNextQueue: Array<TrackItem>, upNextQueue: Array<TrackItem>) -> Unit) {
140
+ val id = core.addOnTemporaryQueueChangeListener { pn, un ->
141
+ callback(pn.toTypedArray(), un.toTypedArray())
166
142
  }
143
+ listenerIds += "onTemporaryQueueChange" to id
144
+ }
167
145
 
168
- override fun onTracksNeedUpdate(callback: (tracks: Array<TrackItem>, lookahead: Double) -> Unit) {
169
- core.addOnTracksNeedUpdateListener { tracks, lookahead ->
170
- callback(tracks.toTypedArray(), lookahead.toDouble())
146
+ // ── Cleanup ───────────────────────────────────────────────────────────────
147
+
148
+ override fun dispose() {
149
+ super.dispose()
150
+ listenerIds.forEach { (type, id) ->
151
+ when (type) {
152
+ "onChangeTrack" -> core.removeOnChangeTrackListener(id)
153
+ "onPlaybackStateChange" -> core.removeOnPlaybackStateChangeListener(id)
154
+ "onSeek" -> core.removeOnSeekListener(id)
155
+ "onPlaybackProgressChange" -> core.removeOnPlaybackProgressChangeListener(id)
156
+ "onAndroidAutoConnectionChange" -> core.removeOnAndroidAutoConnectionListener(id)
157
+ "onTracksNeedUpdate" -> core.removeOnTracksNeedUpdateListener(id)
158
+ "onTemporaryQueueChange" -> core.removeOnTemporaryQueueChangeListener(id)
159
+ }
171
160
  }
161
+ listenerIds.clear()
172
162
  }
173
163
  }
@@ -0,0 +1,82 @@
1
+ package com.margelo.nitro.nitroplayer.core
2
+
3
+ import android.os.HandlerThread
4
+ import android.content.Context
5
+ import androidx.media3.common.AudioAttributes
6
+ import androidx.media3.common.C
7
+ import androidx.media3.common.MediaItem
8
+ import androidx.media3.common.Player
9
+ import androidx.media3.exoplayer.DefaultLoadControl
10
+ import androidx.media3.exoplayer.ExoPlayer
11
+
12
+
13
+ class ExoPlayerCore(context: Context, playerThread: HandlerThread) {
14
+
15
+ /** The underlying ExoPlayer instance — accessible for MediaSessionManager wiring. */
16
+ internal val player: ExoPlayer = build(context, playerThread)
17
+
18
+ private fun build(context: Context, playerThread: HandlerThread): ExoPlayer {
19
+ val loadControl = DefaultLoadControl.Builder()
20
+ .setBufferDurationsMs(
21
+ /* minBufferMs */ 30_000,
22
+ /* maxBufferMs */ 120_000,
23
+ /* bufferForPlayback */ 2_500,
24
+ /* bufferForRebuffer */ 5_000,
25
+ )
26
+ .setBackBuffer(30_000, /* retainBackBufferFromKeyframe */ true)
27
+ .setTargetBufferBytes(C.LENGTH_UNSET)
28
+ .setPrioritizeTimeOverSizeThresholds(true)
29
+ .build()
30
+
31
+ val audioAttrs = AudioAttributes.Builder()
32
+ .setUsage(C.USAGE_MEDIA)
33
+ .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
34
+ .build()
35
+
36
+ return ExoPlayer.Builder(context)
37
+ .setLooper(playerThread.looper)
38
+ .setLoadControl(loadControl)
39
+ .setAudioAttributes(audioAttrs, /* handleAudioFocus */ true)
40
+ .setHandleAudioBecomingNoisy(true)
41
+ .setPauseAtEndOfMediaItems(false)
42
+ .build()
43
+ }
44
+
45
+ // ── Playback ───────────────────────────────────────────────────────────
46
+ fun play() = player.play()
47
+ fun pause() = player.pause()
48
+ fun seekTo(positionMs: Long) = player.seekTo(positionMs)
49
+ fun seekToNext() = player.seekToNextMediaItem()
50
+ fun hasNextMediaItem(): Boolean = player.hasNextMediaItem()
51
+ fun setRepeatMode(mode: Int) { player.repeatMode = mode }
52
+ fun setVolume(volume: Float) { player.volume = volume }
53
+ fun setPlaybackSpeed(speed: Float) = player.setPlaybackSpeed(speed)
54
+ fun getPlaybackSpeed(): Float = player.playbackParameters.speed
55
+
56
+ // ── Queue mutations ────────────────────────────────────────────────────
57
+ fun prepare() = player.prepare()
58
+ fun seekToDefaultPosition(windowIndex: Int) = player.seekToDefaultPosition(windowIndex)
59
+ fun clearMediaItems() = player.clearMediaItems()
60
+ fun setMediaItems(items: List<MediaItem>, resetPosition: Boolean = false) =
61
+ player.setMediaItems(items, resetPosition)
62
+ fun addMediaItems(items: List<MediaItem>) = player.addMediaItems(items)
63
+ fun removeMediaItems(fromIndex: Int, toIndex: Int) =
64
+ player.removeMediaItems(fromIndex, toIndex)
65
+ fun replaceMediaItem(index: Int, item: MediaItem) = player.replaceMediaItem(index, item)
66
+
67
+ // ── Listener wiring ────────────────────────────────────────────────────
68
+ fun addListener(listener: Player.Listener) = player.addListener(listener)
69
+ fun removeListener(listener: Player.Listener) = player.removeListener(listener)
70
+
71
+ // ── State reads ────────────────────────────────────────────────────────
72
+ val playbackState: Int get() = player.playbackState
73
+ val isPlaying: Boolean get() = player.isPlaying
74
+ var playWhenReady: Boolean
75
+ get() = player.playWhenReady
76
+ set(value) { player.playWhenReady = value }
77
+ val currentMediaItem: MediaItem? get() = player.currentMediaItem
78
+ val currentMediaItemIndex: Int get() = player.currentMediaItemIndex
79
+ val currentPosition: Long get() = player.currentPosition
80
+ val duration: Long get() = player.duration
81
+ val mediaItemCount: Int get() = player.mediaItemCount
82
+ }
@@ -0,0 +1,48 @@
1
+ package com.margelo.nitro.nitroplayer.core
2
+
3
+ import java.util.concurrent.CopyOnWriteArrayList
4
+ import java.util.concurrent.atomic.AtomicLong
5
+
6
+ /**
7
+ * Thread-safe listener registry with stable numeric IDs for add/remove.
8
+ * Uses CopyOnWriteArrayList for lock-free iteration and AtomicLong for ID generation.
9
+ */
10
+ class ListenerRegistry<T> {
11
+ private data class Entry<T>(val id: Long, val callback: T)
12
+
13
+ private val entries = CopyOnWriteArrayList<Entry<T>>()
14
+ private val nextId = AtomicLong(0)
15
+
16
+ /** Register a callback and return its stable ID for later removal. */
17
+ fun add(callback: T): Long {
18
+ val id = nextId.incrementAndGet()
19
+ entries.add(Entry(id, callback))
20
+ return id
21
+ }
22
+
23
+ /** Remove the callback with the given ID. Returns true if found. */
24
+ fun remove(id: Long): Boolean {
25
+ val iterator = entries.iterator()
26
+ while (iterator.hasNext()) {
27
+ val entry = iterator.next()
28
+ if (entry.id == id) {
29
+ entries.remove(entry)
30
+ return true
31
+ }
32
+ }
33
+ return false
34
+ }
35
+
36
+ /** Remove all registered callbacks. */
37
+ fun clear() = entries.clear()
38
+
39
+ /** Invoke action for every registered callback (snapshot iteration — safe under mutation). */
40
+ fun forEach(action: (T) -> Unit) {
41
+ for (entry in entries) {
42
+ action(entry.callback)
43
+ }
44
+ }
45
+
46
+ /** True when no callbacks are registered. */
47
+ val isEmpty: Boolean get() = entries.isEmpty()
48
+ }