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
|
@@ -21,8 +21,9 @@ class HybridPlayerQueue : HybridPlayerQueueSpec() {
|
|
|
21
21
|
private val playlistManager: PlaylistManager
|
|
22
22
|
|
|
23
23
|
init {
|
|
24
|
-
val context =
|
|
25
|
-
|
|
24
|
+
val context =
|
|
25
|
+
NitroModules.applicationContext
|
|
26
|
+
?: throw IllegalStateException("React Context is not initialized")
|
|
26
27
|
core = TrackPlayerCore.getInstance(context)
|
|
27
28
|
playlistManager = core.getPlaylistManager()
|
|
28
29
|
}
|
|
@@ -32,87 +33,125 @@ class HybridPlayerQueue : HybridPlayerQueueSpec() {
|
|
|
32
33
|
|
|
33
34
|
// ── Playlist CRUD ─────────────────────────────────────────────────────────
|
|
34
35
|
|
|
35
|
-
override fun createPlaylist(
|
|
36
|
-
|
|
36
|
+
override fun createPlaylist(
|
|
37
|
+
name: String,
|
|
38
|
+
description: String?,
|
|
39
|
+
artwork: String?,
|
|
40
|
+
): Promise<String> = Promise.async { playlistManager.createPlaylist(name, description, artwork) }
|
|
37
41
|
|
|
38
|
-
override fun deletePlaylist(playlistId: String): Promise<Unit> =
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
override fun deletePlaylist(playlistId: String): Promise<Unit> =
|
|
43
|
+
Promise.async {
|
|
44
|
+
playlistManager.deletePlaylist(playlistId)
|
|
45
|
+
}
|
|
41
46
|
|
|
42
|
-
override fun updatePlaylist(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
override fun updatePlaylist(
|
|
48
|
+
playlistId: String,
|
|
49
|
+
name: String?,
|
|
50
|
+
description: String?,
|
|
51
|
+
artwork: String?,
|
|
52
|
+
): Promise<Unit> =
|
|
53
|
+
Promise.async {
|
|
54
|
+
playlistManager.updatePlaylist(playlistId, name, description, artwork)
|
|
55
|
+
core.updatePlaylist(playlistId)
|
|
56
|
+
}
|
|
46
57
|
|
|
47
58
|
override fun getPlaylist(playlistId: String): Variant_NullType_Playlist {
|
|
48
59
|
val playlist = playlistManager.getPlaylist(playlistId)
|
|
49
|
-
return if (playlist != null)
|
|
50
|
-
|
|
60
|
+
return if (playlist != null) {
|
|
61
|
+
Variant_NullType_Playlist.create(playlist.toPlaylist())
|
|
62
|
+
} else {
|
|
63
|
+
Variant_NullType_Playlist.create(NullType.NULL)
|
|
64
|
+
}
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
override fun getAllPlaylists(): Array<Playlist> =
|
|
54
|
-
playlistManager.getAllPlaylists().map { it.toPlaylist() }.toTypedArray()
|
|
67
|
+
override fun getAllPlaylists(): Array<Playlist> = playlistManager.getAllPlaylists().map { it.toPlaylist() }.toTypedArray()
|
|
55
68
|
|
|
56
69
|
// ── Track mutations ───────────────────────────────────────────────────────
|
|
57
70
|
|
|
58
|
-
override fun addTrackToPlaylist(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
override fun addTrackToPlaylist(
|
|
72
|
+
playlistId: String,
|
|
73
|
+
track: TrackItem,
|
|
74
|
+
index: Double?,
|
|
75
|
+
): Promise<Unit> =
|
|
76
|
+
Promise.async {
|
|
77
|
+
playlistManager.addTrackToPlaylist(playlistId, track, index?.toInt())
|
|
78
|
+
core.updatePlaylist(playlistId)
|
|
79
|
+
}
|
|
62
80
|
|
|
63
|
-
override fun addTracksToPlaylist(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
override fun addTracksToPlaylist(
|
|
82
|
+
playlistId: String,
|
|
83
|
+
tracks: Array<TrackItem>,
|
|
84
|
+
index: Double?,
|
|
85
|
+
): Promise<Unit> =
|
|
86
|
+
Promise.async {
|
|
87
|
+
playlistManager.addTracksToPlaylist(playlistId, tracks.toList(), index?.toInt())
|
|
88
|
+
core.updatePlaylist(playlistId)
|
|
89
|
+
}
|
|
67
90
|
|
|
68
|
-
override fun removeTrackFromPlaylist(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
override fun removeTrackFromPlaylist(
|
|
92
|
+
playlistId: String,
|
|
93
|
+
trackId: String,
|
|
94
|
+
): Promise<Unit> =
|
|
95
|
+
Promise.async {
|
|
96
|
+
playlistManager.removeTrackFromPlaylist(playlistId, trackId)
|
|
97
|
+
core.updatePlaylist(playlistId)
|
|
98
|
+
}
|
|
72
99
|
|
|
73
|
-
override fun reorderTrackInPlaylist(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
override fun reorderTrackInPlaylist(
|
|
101
|
+
playlistId: String,
|
|
102
|
+
trackId: String,
|
|
103
|
+
newIndex: Double,
|
|
104
|
+
): Promise<Unit> =
|
|
105
|
+
Promise.async {
|
|
106
|
+
playlistManager.reorderTrackInPlaylist(playlistId, trackId, newIndex.toInt())
|
|
107
|
+
core.updatePlaylist(playlistId)
|
|
108
|
+
}
|
|
77
109
|
|
|
78
110
|
// ── Playback control ──────────────────────────────────────────────────────
|
|
79
111
|
|
|
80
|
-
override fun loadPlaylist(playlistId: String): Promise<Unit> =
|
|
81
|
-
|
|
82
|
-
|
|
112
|
+
override fun loadPlaylist(playlistId: String): Promise<Unit> =
|
|
113
|
+
Promise.async {
|
|
114
|
+
core.loadPlaylist(playlistId)
|
|
115
|
+
}
|
|
83
116
|
|
|
84
117
|
override fun getCurrentPlaylistId(): Variant_NullType_String {
|
|
85
118
|
val id = core.getCurrentPlaylistId()
|
|
86
|
-
return if (id != null)
|
|
87
|
-
|
|
119
|
+
return if (id != null) {
|
|
120
|
+
Variant_NullType_String.create(id)
|
|
121
|
+
} else {
|
|
122
|
+
Variant_NullType_String.create(NullType.NULL)
|
|
123
|
+
}
|
|
88
124
|
}
|
|
89
125
|
|
|
90
126
|
// ── Events ────────────────────────────────────────────────────────────────
|
|
91
127
|
|
|
92
128
|
override fun onPlaylistsChanged(callback: (playlists: Array<Playlist>, operation: QueueOperation?) -> Unit) {
|
|
93
|
-
val removeListener =
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
val removeListener =
|
|
130
|
+
playlistManager.addPlaylistsChangeListener { playlists, operation ->
|
|
131
|
+
callback(playlists.map { it.toPlaylist() }.toTypedArray(), operation)
|
|
132
|
+
}
|
|
96
133
|
playlistsChangeListeners.add(removeListener)
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
override fun onPlaylistChanged(callback: (playlistId: String, playlist: Playlist, operation: QueueOperation?) -> Unit) {
|
|
100
137
|
val listenerId = UUID.randomUUID().toString()
|
|
101
138
|
playlistManager.getAllPlaylists().forEach { internalPlaylist ->
|
|
102
|
-
val removeListener =
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
val removeListener =
|
|
140
|
+
playlistManager.addPlaylistChangeListener(internalPlaylist.id) { playlist, operation ->
|
|
141
|
+
callback(playlist.id, playlist.toPlaylist(), operation)
|
|
142
|
+
}
|
|
105
143
|
playlistChangeListeners[listenerId] = removeListener
|
|
106
144
|
}
|
|
107
145
|
}
|
|
108
146
|
|
|
109
147
|
// ── Helper ────────────────────────────────────────────────────────────────
|
|
110
148
|
|
|
111
|
-
private fun InternalPlaylist.toPlaylist(): Playlist =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
private fun InternalPlaylist.toPlaylist(): Playlist =
|
|
150
|
+
Playlist(
|
|
151
|
+
id = this.id,
|
|
152
|
+
name = this.name,
|
|
153
|
+
description = this.description?.let { Variant_NullType_String.create(it) },
|
|
154
|
+
artwork = this.artwork?.let { Variant_NullType_String.create(it) },
|
|
155
|
+
tracks = this.tracks.toTypedArray(),
|
|
156
|
+
)
|
|
118
157
|
}
|
|
@@ -46,22 +46,33 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
|
|
|
46
46
|
private val listenerIds = mutableListOf<Pair<String, Long>>()
|
|
47
47
|
|
|
48
48
|
init {
|
|
49
|
-
val context =
|
|
50
|
-
|
|
49
|
+
val context =
|
|
50
|
+
NitroModules.applicationContext
|
|
51
|
+
?: throw IllegalStateException("React Context is not initialized")
|
|
51
52
|
core = TrackPlayerCore.getInstance(context)
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// ── Playback ─────────────────────────────────────────────────────────────
|
|
55
56
|
|
|
56
57
|
override fun play(): Promise<Unit> = Promise.async { core.play() }
|
|
58
|
+
|
|
57
59
|
override fun pause(): Promise<Unit> = Promise.async { core.pause() }
|
|
60
|
+
|
|
58
61
|
override fun seek(position: Double): Promise<Unit> = Promise.async { core.seek(position) }
|
|
62
|
+
|
|
59
63
|
override fun skipToNext(): Promise<Unit> = Promise.async { core.skipToNext() }
|
|
64
|
+
|
|
60
65
|
override fun skipToPrevious(): Promise<Unit> = Promise.async { core.skipToPrevious() }
|
|
61
|
-
|
|
66
|
+
|
|
67
|
+
override fun playSong(
|
|
68
|
+
songId: String,
|
|
69
|
+
fromPlaylist: String?,
|
|
70
|
+
): Promise<Unit> = Promise.async { core.playSong(songId, fromPlaylist) }
|
|
71
|
+
|
|
62
72
|
override fun skipToIndex(index: Double): Promise<Boolean> = Promise.async { core.skipToIndex(index.toInt()) }
|
|
63
73
|
|
|
64
74
|
override fun setRepeatMode(mode: RepeatMode): Promise<Unit> = Promise.async { core.setRepeatMode(mode) }
|
|
75
|
+
|
|
65
76
|
override fun getRepeatMode(): RepeatMode = core.getRepeatMode()
|
|
66
77
|
|
|
67
78
|
override fun setVolume(volume: Double): Promise<Unit> = Promise.async { core.setVolume(volume) }
|
|
@@ -71,10 +82,15 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
|
|
|
71
82
|
// ── Queue / state reads ───────────────────────────────────────────────────
|
|
72
83
|
|
|
73
84
|
override fun getActualQueue(): Promise<Array<TrackItem>> = Promise.async { core.getActualQueue().toTypedArray() }
|
|
85
|
+
|
|
74
86
|
override fun getState(): Promise<PlayerState> = Promise.async { core.getState() }
|
|
87
|
+
|
|
75
88
|
override fun getTracksById(trackIds: Array<String>): Promise<Array<TrackItem>> = Promise.async { core.getTracksById(trackIds.toList()).toTypedArray() }
|
|
89
|
+
|
|
76
90
|
override fun getTracksNeedingUrls(): Promise<Array<TrackItem>> = Promise.async { core.getTracksNeedingUrls().toTypedArray() }
|
|
91
|
+
|
|
77
92
|
override fun getNextTracks(count: Double): Promise<Array<TrackItem>> = Promise.async { core.getNextTracks(count.toInt()).toTypedArray() }
|
|
93
|
+
|
|
78
94
|
override fun getCurrentTrackIndex(): Promise<Double> = Promise.async { core.getCurrentTrackIndex().toDouble() }
|
|
79
95
|
|
|
80
96
|
// ── URL updates ───────────────────────────────────────────────────────────
|
|
@@ -84,18 +100,30 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
|
|
|
84
100
|
// ── Temporary queue ───────────────────────────────────────────────────────
|
|
85
101
|
|
|
86
102
|
override fun addToUpNext(trackId: String): Promise<Unit> = Promise.async { core.addToUpNext(trackId) }
|
|
103
|
+
|
|
87
104
|
override fun playNext(trackId: String): Promise<Unit> = Promise.async { core.playNext(trackId) }
|
|
105
|
+
|
|
88
106
|
override fun removeFromPlayNext(trackId: String): Promise<Boolean> = Promise.async { core.removeFromPlayNext(trackId) }
|
|
107
|
+
|
|
89
108
|
override fun removeFromUpNext(trackId: String): Promise<Boolean> = Promise.async { core.removeFromUpNext(trackId) }
|
|
109
|
+
|
|
90
110
|
override fun clearPlayNext(): Promise<Unit> = Promise.async { core.clearPlayNext() }
|
|
111
|
+
|
|
91
112
|
override fun clearUpNext(): Promise<Unit> = Promise.async { core.clearUpNext() }
|
|
92
|
-
|
|
113
|
+
|
|
114
|
+
override fun reorderTemporaryTrack(
|
|
115
|
+
trackId: String,
|
|
116
|
+
newIndex: Double,
|
|
117
|
+
): Promise<Boolean> = Promise.async { core.reorderTemporaryTrack(trackId, newIndex.toInt()) }
|
|
118
|
+
|
|
93
119
|
override fun getPlayNextQueue(): Promise<Array<TrackItem>> = Promise.async { core.getPlayNextQueue().toTypedArray() }
|
|
120
|
+
|
|
94
121
|
override fun getUpNextQueue(): Promise<Array<TrackItem>> = Promise.async { core.getUpNextQueue().toTypedArray() }
|
|
95
122
|
|
|
96
123
|
// ── Playback speed ────────────────────────────────────────────────────────
|
|
97
124
|
|
|
98
125
|
override fun setPlaybackSpeed(speed: Double): Promise<Unit> = Promise.async { core.setPlayBackSpeed(speed) }
|
|
126
|
+
|
|
99
127
|
override fun getPlaybackSpeed(): Promise<Double> = Promise.async { core.getPlayBackSpeed() }
|
|
100
128
|
|
|
101
129
|
// ── Android Auto ──────────────────────────────────────────────────────────
|
|
@@ -130,16 +158,18 @@ class HybridTrackPlayer : HybridTrackPlayerSpec() {
|
|
|
130
158
|
}
|
|
131
159
|
|
|
132
160
|
override fun onTracksNeedUpdate(callback: (tracks: Array<TrackItem>, lookahead: Double) -> Unit) {
|
|
133
|
-
val id =
|
|
134
|
-
|
|
135
|
-
|
|
161
|
+
val id =
|
|
162
|
+
core.addOnTracksNeedUpdateListener { tracks, lookahead ->
|
|
163
|
+
callback(tracks.toTypedArray(), lookahead.toDouble())
|
|
164
|
+
}
|
|
136
165
|
listenerIds += "onTracksNeedUpdate" to id
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
override fun onTemporaryQueueChange(callback: (playNextQueue: Array<TrackItem>, upNextQueue: Array<TrackItem>) -> Unit) {
|
|
140
|
-
val id =
|
|
141
|
-
|
|
142
|
-
|
|
169
|
+
val id =
|
|
170
|
+
core.addOnTemporaryQueueChangeListener { pn, un ->
|
|
171
|
+
callback(pn.toTypedArray(), un.toTypedArray())
|
|
172
|
+
}
|
|
143
173
|
listenerIds += "onTemporaryQueueChange" to id
|
|
144
174
|
}
|
|
145
175
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package com.margelo.nitro.nitroplayer.core
|
|
2
2
|
|
|
3
|
-
import android.os.HandlerThread
|
|
4
3
|
import android.content.Context
|
|
4
|
+
import android.os.HandlerThread
|
|
5
5
|
import androidx.media3.common.AudioAttributes
|
|
6
6
|
import androidx.media3.common.C
|
|
7
7
|
import androidx.media3.common.MediaItem
|
|
@@ -9,63 +9,102 @@ import androidx.media3.common.Player
|
|
|
9
9
|
import androidx.media3.exoplayer.DefaultLoadControl
|
|
10
10
|
import androidx.media3.exoplayer.ExoPlayer
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
class ExoPlayerCore(
|
|
13
|
+
context: Context,
|
|
14
|
+
playerThread: HandlerThread,
|
|
15
|
+
) {
|
|
15
16
|
/** The underlying ExoPlayer instance — accessible for MediaSessionManager wiring. */
|
|
16
17
|
internal val player: ExoPlayer = build(context, playerThread)
|
|
17
18
|
|
|
18
|
-
private fun build(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
private fun build(
|
|
20
|
+
context: Context,
|
|
21
|
+
playerThread: HandlerThread,
|
|
22
|
+
): ExoPlayer {
|
|
23
|
+
val loadControl =
|
|
24
|
+
DefaultLoadControl
|
|
25
|
+
.Builder()
|
|
26
|
+
.setBufferDurationsMs(
|
|
27
|
+
// minBufferMs
|
|
28
|
+
30_000,
|
|
29
|
+
// maxBufferMs
|
|
30
|
+
120_000,
|
|
31
|
+
// bufferForPlayback
|
|
32
|
+
2_500,
|
|
33
|
+
// bufferForRebuffer
|
|
34
|
+
5_000,
|
|
35
|
+
).setBackBuffer(30_000, /* retainBackBufferFromKeyframe */ true)
|
|
36
|
+
.setTargetBufferBytes(C.LENGTH_UNSET)
|
|
37
|
+
.setPrioritizeTimeOverSizeThresholds(true)
|
|
38
|
+
.build()
|
|
30
39
|
|
|
31
|
-
val audioAttrs =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
val audioAttrs =
|
|
41
|
+
AudioAttributes
|
|
42
|
+
.Builder()
|
|
43
|
+
.setUsage(C.USAGE_MEDIA)
|
|
44
|
+
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
|
45
|
+
.build()
|
|
35
46
|
|
|
36
|
-
return ExoPlayer
|
|
47
|
+
return ExoPlayer
|
|
48
|
+
.Builder(context)
|
|
37
49
|
.setLooper(playerThread.looper)
|
|
38
50
|
.setLoadControl(loadControl)
|
|
39
51
|
.setAudioAttributes(audioAttrs, /* handleAudioFocus */ true)
|
|
40
52
|
.setHandleAudioBecomingNoisy(true)
|
|
53
|
+
.setWakeMode(C.WAKE_MODE_NETWORK)
|
|
41
54
|
.setPauseAtEndOfMediaItems(false)
|
|
42
55
|
.build()
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
// ── Playback ───────────────────────────────────────────────────────────
|
|
46
59
|
fun play() = player.play()
|
|
60
|
+
|
|
47
61
|
fun pause() = player.pause()
|
|
62
|
+
|
|
48
63
|
fun seekTo(positionMs: Long) = player.seekTo(positionMs)
|
|
64
|
+
|
|
49
65
|
fun seekToNext() = player.seekToNextMediaItem()
|
|
66
|
+
|
|
50
67
|
fun hasNextMediaItem(): Boolean = player.hasNextMediaItem()
|
|
51
|
-
|
|
52
|
-
fun
|
|
68
|
+
|
|
69
|
+
fun setRepeatMode(mode: Int) {
|
|
70
|
+
player.repeatMode = mode
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun setVolume(volume: Float) {
|
|
74
|
+
player.volume = volume
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
fun setPlaybackSpeed(speed: Float) = player.setPlaybackSpeed(speed)
|
|
78
|
+
|
|
54
79
|
fun getPlaybackSpeed(): Float = player.playbackParameters.speed
|
|
55
80
|
|
|
56
81
|
// ── Queue mutations ────────────────────────────────────────────────────
|
|
57
82
|
fun prepare() = player.prepare()
|
|
83
|
+
|
|
58
84
|
fun seekToDefaultPosition(windowIndex: Int) = player.seekToDefaultPosition(windowIndex)
|
|
85
|
+
|
|
59
86
|
fun clearMediaItems() = player.clearMediaItems()
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
|
|
88
|
+
fun setMediaItems(
|
|
89
|
+
items: List<MediaItem>,
|
|
90
|
+
resetPosition: Boolean = false,
|
|
91
|
+
) = player.setMediaItems(items, resetPosition)
|
|
92
|
+
|
|
62
93
|
fun addMediaItems(items: List<MediaItem>) = player.addMediaItems(items)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
94
|
+
|
|
95
|
+
fun removeMediaItems(
|
|
96
|
+
fromIndex: Int,
|
|
97
|
+
toIndex: Int,
|
|
98
|
+
) = player.removeMediaItems(fromIndex, toIndex)
|
|
99
|
+
|
|
100
|
+
fun replaceMediaItem(
|
|
101
|
+
index: Int,
|
|
102
|
+
item: MediaItem,
|
|
103
|
+
) = player.replaceMediaItem(index, item)
|
|
66
104
|
|
|
67
105
|
// ── Listener wiring ────────────────────────────────────────────────────
|
|
68
106
|
fun addListener(listener: Player.Listener) = player.addListener(listener)
|
|
107
|
+
|
|
69
108
|
fun removeListener(listener: Player.Listener) = player.removeListener(listener)
|
|
70
109
|
|
|
71
110
|
// ── State reads ────────────────────────────────────────────────────────
|
|
@@ -73,7 +112,9 @@ class ExoPlayerCore(context: Context, playerThread: HandlerThread) {
|
|
|
73
112
|
val isPlaying: Boolean get() = player.isPlaying
|
|
74
113
|
var playWhenReady: Boolean
|
|
75
114
|
get() = player.playWhenReady
|
|
76
|
-
set(value) {
|
|
115
|
+
set(value) {
|
|
116
|
+
player.playWhenReady = value
|
|
117
|
+
}
|
|
77
118
|
val currentMediaItem: MediaItem? get() = player.currentMediaItem
|
|
78
119
|
val currentMediaItemIndex: Int get() = player.currentMediaItemIndex
|
|
79
120
|
val currentPosition: Long get() = player.currentPosition
|
|
@@ -8,7 +8,10 @@ import java.util.concurrent.atomic.AtomicLong
|
|
|
8
8
|
* Uses CopyOnWriteArrayList for lock-free iteration and AtomicLong for ID generation.
|
|
9
9
|
*/
|
|
10
10
|
class ListenerRegistry<T> {
|
|
11
|
-
private data class Entry<T>(
|
|
11
|
+
private data class Entry<T>(
|
|
12
|
+
val id: Long,
|
|
13
|
+
val callback: T,
|
|
14
|
+
)
|
|
12
15
|
|
|
13
16
|
private val entries = CopyOnWriteArrayList<Entry<T>>()
|
|
14
17
|
private val nextId = AtomicLong(0)
|
|
@@ -13,50 +13,56 @@ import com.margelo.nitro.nitroplayer.media.NitroPlayerMediaBrowserService
|
|
|
13
13
|
|
|
14
14
|
/** Called on the main thread from TrackPlayerCore.init via handler.post. */
|
|
15
15
|
internal fun TrackPlayerCore.setupAndroidAutoDetector() {
|
|
16
|
-
androidAutoConnectionDetector =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
androidAutoConnectionDetector =
|
|
17
|
+
AndroidAutoConnectionDetector(context).apply {
|
|
18
|
+
onConnectionChanged = { connected, _ ->
|
|
19
|
+
handler.post {
|
|
20
|
+
isAndroidAutoConnectedField = connected
|
|
21
|
+
NitroPlayerMediaBrowserService.isAndroidAutoConnected = connected
|
|
22
|
+
notifyAndroidAutoConnection(connected)
|
|
23
|
+
}
|
|
22
24
|
}
|
|
25
|
+
registerCarConnectionReceiver()
|
|
23
26
|
}
|
|
24
|
-
registerCarConnectionReceiver()
|
|
25
|
-
}
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/** Called by MediaBrowserService when the user picks a track in Android Auto. */
|
|
29
|
-
suspend fun TrackPlayerCore.playFromPlaylistTrack(mediaId: String) =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
suspend fun TrackPlayerCore.playFromPlaylistTrack(mediaId: String) =
|
|
31
|
+
withPlayerContext {
|
|
32
|
+
try {
|
|
33
|
+
val colonIndex = mediaId.indexOf(':')
|
|
34
|
+
if (colonIndex <= 0 || colonIndex >= mediaId.length - 1) return@withPlayerContext
|
|
35
|
+
val playlistId = mediaId.substring(0, colonIndex)
|
|
36
|
+
val trackId = mediaId.substring(colonIndex + 1)
|
|
37
|
+
val playlist = playlistManager.getPlaylist(playlistId) ?: return@withPlayerContext
|
|
38
|
+
val trackIndex = playlist.tracks.indexOfFirst { it.id == trackId }
|
|
39
|
+
if (trackIndex < 0) return@withPlayerContext
|
|
40
|
+
if (currentPlaylistId != playlistId) {
|
|
41
|
+
loadPlaylistInternal(playlistId)
|
|
42
|
+
}
|
|
43
|
+
playFromIndexInternal(trackIndex)
|
|
44
|
+
} catch (_: Exception) {
|
|
40
45
|
}
|
|
41
|
-
|
|
42
|
-
} catch (_: Exception) {}
|
|
43
|
-
}
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
private fun TrackPlayerCore.loadPlaylistInternal(playlistId: String) {
|
|
46
|
-
playNextStack.clear()
|
|
49
|
+
playNextStack.clear()
|
|
50
|
+
upNextQueue.clear()
|
|
47
51
|
currentTemporaryType = TrackPlayerCore.TemporaryType.NONE
|
|
48
52
|
val playlist = playlistManager.getPlaylist(playlistId) ?: return
|
|
49
53
|
currentPlaylistId = playlistId
|
|
50
54
|
updatePlayerQueue(playlist.tracks)
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
suspend fun TrackPlayerCore.setAndroidAutoMediaLibrary(libraryJson: String) =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
suspend fun TrackPlayerCore.setAndroidAutoMediaLibrary(libraryJson: String) =
|
|
58
|
+
withPlayerContext {
|
|
59
|
+
val library = MediaLibraryParser.fromJson(libraryJson)
|
|
60
|
+
mediaLibraryManager.setMediaLibrary(library)
|
|
61
|
+
NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
|
|
62
|
+
}
|
|
58
63
|
|
|
59
|
-
suspend fun TrackPlayerCore.clearAndroidAutoMediaLibrary() =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
suspend fun TrackPlayerCore.clearAndroidAutoMediaLibrary() =
|
|
65
|
+
withPlayerContext {
|
|
66
|
+
mediaLibraryManager.clear()
|
|
67
|
+
NitroPlayerMediaBrowserService.getInstance()?.onPlaylistsUpdated()
|
|
68
|
+
}
|