@rntp/player 5.0.0-beta.4 → 5.0.0-beta.6
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/android/build.gradle +7 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/SleepTimerController.kt +128 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayerModule.kt +40 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayerPlaybackService.kt +99 -87
- package/android/src/main/java/com/doublesymmetry/trackplayer/models/PlayerConfig.kt +12 -1
- package/android/src/test/java/com/doublesymmetry/trackplayer/ExoPlayerIntegrationTest.kt +319 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/SleepTimerIntegrationTest.kt +473 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/SleepTimerStateTest.kt +58 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/models/BrowseNavigationTest.kt +215 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/models/BrowseTreeTest.kt +166 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/models/EmitEventTest.kt +68 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/models/PlayerConfigTest.kt +400 -0
- package/android/src/test/java/com/doublesymmetry/trackplayer/models/TrackPlayerMediaItemTest.kt +380 -0
- package/android/src/test/resources/robolectric.properties +1 -0
- package/ios/TrackPlayer.swift +47 -101
- package/ios/TrackPlayerBridge.mm +2 -0
- package/ios/player/AVPlayerEngine.swift +47 -35
- package/ios/player/AudioCache.swift +34 -0
- package/ios/player/AudioPlayer.swift +70 -22
- package/ios/player/CacheProxyServer.swift +429 -0
- package/ios/player/DownloadCoordinator.swift +242 -0
- package/ios/player/Preloader.swift +21 -90
- package/ios/player/SleepTimerController.swift +147 -0
- package/ios/tests/AVPlayerEngineIntegrationTests.swift +230 -0
- package/ios/tests/AudioPlayerTests.swift +6 -0
- package/ios/tests/CacheProxyServerTests.swift +403 -0
- package/ios/tests/DownloadCoordinatorTests.swift +197 -0
- package/ios/tests/LocalAudioServer.swift +171 -0
- package/ios/tests/MockPlayerEngine.swift +1 -0
- package/ios/tests/QueueManagerTests.swift +6 -0
- package/ios/tests/SleepTimerIntegrationTests.swift +408 -0
- package/ios/tests/SleepTimerTests.swift +70 -0
- package/lib/commonjs/NativeTrackPlayer.js.map +1 -1
- package/lib/commonjs/audio.js +19 -0
- package/lib/commonjs/audio.js.map +1 -1
- package/lib/commonjs/interfaces/PlayerConfig.js +1 -1
- package/lib/commonjs/interfaces/PlayerConfig.js.map +1 -1
- package/lib/module/NativeTrackPlayer.js.map +1 -1
- package/lib/module/audio.js +17 -0
- package/lib/module/audio.js.map +1 -1
- package/lib/module/interfaces/PlayerConfig.js +1 -1
- package/lib/module/interfaces/PlayerConfig.js.map +1 -1
- package/lib/typescript/src/NativeTrackPlayer.d.ts +2 -0
- package/lib/typescript/src/NativeTrackPlayer.d.ts.map +1 -1
- package/lib/typescript/src/audio.d.ts +12 -1
- package/lib/typescript/src/audio.d.ts.map +1 -1
- package/lib/typescript/src/interfaces/MediaItem.d.ts +4 -1
- package/lib/typescript/src/interfaces/MediaItem.d.ts.map +1 -1
- package/lib/typescript/src/interfaces/PlayerConfig.d.ts +21 -2
- package/lib/typescript/src/interfaces/PlayerConfig.d.ts.map +1 -1
- package/package.json +4 -1
- package/src/NativeTrackPlayer.ts +4 -0
- package/src/audio.ts +18 -0
- package/src/interfaces/MediaItem.ts +4 -1
- package/src/interfaces/PlayerConfig.ts +24 -3
- package/ios/player/CachingResourceLoader.swift +0 -273
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
package com.doublesymmetry.trackplayer
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.MediaItem
|
|
4
|
+
import androidx.media3.common.MediaMetadata
|
|
5
|
+
import androidx.media3.common.Player
|
|
6
|
+
import androidx.media3.exoplayer.ExoPlayer
|
|
7
|
+
import org.junit.After
|
|
8
|
+
import org.junit.Before
|
|
9
|
+
import org.junit.Test
|
|
10
|
+
import org.junit.Assert.*
|
|
11
|
+
import org.junit.runner.RunWith
|
|
12
|
+
import org.robolectric.RobolectricTestRunner
|
|
13
|
+
import org.robolectric.RuntimeEnvironment
|
|
14
|
+
import org.robolectric.annotation.Config
|
|
15
|
+
|
|
16
|
+
@RunWith(RobolectricTestRunner::class)
|
|
17
|
+
@Config(sdk = [33])
|
|
18
|
+
class ExoPlayerIntegrationTest {
|
|
19
|
+
|
|
20
|
+
private lateinit var player: ExoPlayer
|
|
21
|
+
|
|
22
|
+
@Before
|
|
23
|
+
fun setUp() {
|
|
24
|
+
val context = RuntimeEnvironment.getApplication()
|
|
25
|
+
player = ExoPlayer.Builder(context).build()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@After
|
|
29
|
+
fun tearDown() {
|
|
30
|
+
player.release()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---- Helpers ----
|
|
34
|
+
|
|
35
|
+
private fun buildMediaItem(id: String, title: String, url: String = "https://example.com/$id.mp3"): MediaItem {
|
|
36
|
+
return MediaItem.Builder()
|
|
37
|
+
.setMediaId(id)
|
|
38
|
+
.setUri(url)
|
|
39
|
+
.setMediaMetadata(MediaMetadata.Builder().setTitle(title).build())
|
|
40
|
+
.build()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun buildQueue(count: Int): List<MediaItem> =
|
|
44
|
+
(1..count).map { buildMediaItem("track-$it", "Track $it") }
|
|
45
|
+
|
|
46
|
+
// ---- setMediaItems ----
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
fun `setMediaItems loads queue`() {
|
|
50
|
+
val items = buildQueue(3)
|
|
51
|
+
player.setMediaItems(items)
|
|
52
|
+
|
|
53
|
+
assertEquals(3, player.mediaItemCount)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
fun `setMediaItems replaces existing queue`() {
|
|
58
|
+
player.setMediaItems(buildQueue(5))
|
|
59
|
+
assertEquals(5, player.mediaItemCount)
|
|
60
|
+
|
|
61
|
+
player.setMediaItems(buildQueue(2))
|
|
62
|
+
assertEquals(2, player.mediaItemCount)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Test
|
|
66
|
+
fun `setMediaItems preserves mediaId`() {
|
|
67
|
+
val item = buildMediaItem("my-id", "My Track")
|
|
68
|
+
player.setMediaItems(listOf(item))
|
|
69
|
+
|
|
70
|
+
assertEquals("my-id", player.getMediaItemAt(0).mediaId)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---- seekToNextMediaItem / seekToPreviousMediaItem ----
|
|
74
|
+
|
|
75
|
+
@Test
|
|
76
|
+
fun `seekToNextMediaItem advances index`() {
|
|
77
|
+
player.setMediaItems(buildQueue(3))
|
|
78
|
+
assertEquals(0, player.currentMediaItemIndex)
|
|
79
|
+
|
|
80
|
+
player.seekToNextMediaItem()
|
|
81
|
+
assertEquals(1, player.currentMediaItemIndex)
|
|
82
|
+
|
|
83
|
+
player.seekToNextMediaItem()
|
|
84
|
+
assertEquals(2, player.currentMediaItemIndex)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Test
|
|
88
|
+
fun `seekToPreviousMediaItem goes back`() {
|
|
89
|
+
player.setMediaItems(buildQueue(3))
|
|
90
|
+
player.seekTo(2, 0L)
|
|
91
|
+
assertEquals(2, player.currentMediaItemIndex)
|
|
92
|
+
|
|
93
|
+
player.seekToPreviousMediaItem()
|
|
94
|
+
assertEquals(1, player.currentMediaItemIndex)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Test
|
|
98
|
+
fun `seekToNextMediaItem does not advance past last item without repeat`() {
|
|
99
|
+
player.setMediaItems(buildQueue(2))
|
|
100
|
+
player.repeatMode = Player.REPEAT_MODE_OFF
|
|
101
|
+
player.seekTo(1, 0L)
|
|
102
|
+
|
|
103
|
+
// Already at last item; seekToNext should have no effect
|
|
104
|
+
player.seekToNextMediaItem()
|
|
105
|
+
assertEquals(1, player.currentMediaItemIndex)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---- repeat modes ----
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
fun `repeat mode one can be set`() {
|
|
112
|
+
player.repeatMode = Player.REPEAT_MODE_ONE
|
|
113
|
+
assertEquals(Player.REPEAT_MODE_ONE, player.repeatMode)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Test
|
|
117
|
+
fun `repeat mode all wraps around`() {
|
|
118
|
+
player.setMediaItems(buildQueue(3))
|
|
119
|
+
player.repeatMode = Player.REPEAT_MODE_ALL
|
|
120
|
+
assertEquals(Player.REPEAT_MODE_ALL, player.repeatMode)
|
|
121
|
+
|
|
122
|
+
// At last item, next should wrap to index 0
|
|
123
|
+
player.seekTo(2, 0L)
|
|
124
|
+
player.seekToNextMediaItem()
|
|
125
|
+
assertEquals(0, player.currentMediaItemIndex)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Test
|
|
129
|
+
fun `repeat mode off is the default`() {
|
|
130
|
+
assertEquals(Player.REPEAT_MODE_OFF, player.repeatMode)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---- shuffle mode ----
|
|
134
|
+
|
|
135
|
+
@Test
|
|
136
|
+
fun `shuffle mode can be enabled`() {
|
|
137
|
+
player.shuffleModeEnabled = true
|
|
138
|
+
assertTrue(player.shuffleModeEnabled)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
fun `shuffle mode can be disabled`() {
|
|
143
|
+
player.shuffleModeEnabled = true
|
|
144
|
+
player.shuffleModeEnabled = false
|
|
145
|
+
assertFalse(player.shuffleModeEnabled)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun `shuffle mode does not change item count`() {
|
|
150
|
+
player.setMediaItems(buildQueue(5))
|
|
151
|
+
player.shuffleModeEnabled = true
|
|
152
|
+
assertEquals(5, player.mediaItemCount)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---- removeMediaItem ----
|
|
156
|
+
|
|
157
|
+
@Test
|
|
158
|
+
fun `removeMediaItem removes from queue`() {
|
|
159
|
+
player.setMediaItems(buildQueue(3))
|
|
160
|
+
player.removeMediaItem(1)
|
|
161
|
+
|
|
162
|
+
assertEquals(2, player.mediaItemCount)
|
|
163
|
+
assertEquals("track-1", player.getMediaItemAt(0).mediaId)
|
|
164
|
+
assertEquals("track-3", player.getMediaItemAt(1).mediaId)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Test
|
|
168
|
+
fun `removeMediaItem at index 0`() {
|
|
169
|
+
player.setMediaItems(buildQueue(3))
|
|
170
|
+
player.removeMediaItem(0)
|
|
171
|
+
|
|
172
|
+
assertEquals(2, player.mediaItemCount)
|
|
173
|
+
assertEquals("track-2", player.getMediaItemAt(0).mediaId)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun `removeMediaItem at last index`() {
|
|
178
|
+
player.setMediaItems(buildQueue(3))
|
|
179
|
+
player.removeMediaItem(2)
|
|
180
|
+
|
|
181
|
+
assertEquals(2, player.mediaItemCount)
|
|
182
|
+
assertEquals("track-1", player.getMediaItemAt(0).mediaId)
|
|
183
|
+
assertEquals("track-2", player.getMediaItemAt(1).mediaId)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---- replaceMediaItem ----
|
|
187
|
+
|
|
188
|
+
@Test
|
|
189
|
+
fun `replaceMediaItem swaps item at index`() {
|
|
190
|
+
player.setMediaItems(buildQueue(3))
|
|
191
|
+
|
|
192
|
+
val replacement = buildMediaItem("replacement", "Replacement Track")
|
|
193
|
+
player.replaceMediaItem(1, replacement)
|
|
194
|
+
|
|
195
|
+
assertEquals(3, player.mediaItemCount)
|
|
196
|
+
assertEquals("replacement", player.getMediaItemAt(1).mediaId)
|
|
197
|
+
assertEquals("track-1", player.getMediaItemAt(0).mediaId)
|
|
198
|
+
assertEquals("track-3", player.getMediaItemAt(2).mediaId)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@Test
|
|
202
|
+
fun `replaceMediaItem at index 0`() {
|
|
203
|
+
player.setMediaItems(buildQueue(2))
|
|
204
|
+
val newItem = buildMediaItem("new-first", "New First")
|
|
205
|
+
player.replaceMediaItem(0, newItem)
|
|
206
|
+
|
|
207
|
+
assertEquals("new-first", player.getMediaItemAt(0).mediaId)
|
|
208
|
+
assertEquals("track-2", player.getMediaItemAt(1).mediaId)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---- clearMediaItems ----
|
|
212
|
+
|
|
213
|
+
@Test
|
|
214
|
+
fun `clearMediaItems empties queue`() {
|
|
215
|
+
player.setMediaItems(buildQueue(5))
|
|
216
|
+
assertEquals(5, player.mediaItemCount)
|
|
217
|
+
|
|
218
|
+
player.clearMediaItems()
|
|
219
|
+
assertEquals(0, player.mediaItemCount)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Test
|
|
223
|
+
fun `clearMediaItems on empty queue is safe`() {
|
|
224
|
+
player.clearMediaItems()
|
|
225
|
+
assertEquals(0, player.mediaItemCount)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ---- addMediaItem / addMediaItems ----
|
|
229
|
+
|
|
230
|
+
@Test
|
|
231
|
+
fun `addMediaItem appends to queue`() {
|
|
232
|
+
player.setMediaItems(buildQueue(2))
|
|
233
|
+
player.addMediaItem(buildMediaItem("track-3", "Track 3"))
|
|
234
|
+
|
|
235
|
+
assertEquals(3, player.mediaItemCount)
|
|
236
|
+
assertEquals("track-3", player.getMediaItemAt(2).mediaId)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@Test
|
|
240
|
+
fun `addMediaItem at index inserts at correct position`() {
|
|
241
|
+
player.setMediaItems(buildQueue(3))
|
|
242
|
+
val inserted = buildMediaItem("inserted", "Inserted")
|
|
243
|
+
player.addMediaItem(1, inserted)
|
|
244
|
+
|
|
245
|
+
assertEquals(4, player.mediaItemCount)
|
|
246
|
+
assertEquals("track-1", player.getMediaItemAt(0).mediaId)
|
|
247
|
+
assertEquals("inserted", player.getMediaItemAt(1).mediaId)
|
|
248
|
+
assertEquals("track-2", player.getMediaItemAt(2).mediaId)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---- volume ----
|
|
252
|
+
|
|
253
|
+
@Test
|
|
254
|
+
fun `volume defaults to 1`() {
|
|
255
|
+
assertEquals(1.0f, player.volume, 0.001f)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Test
|
|
259
|
+
fun `volume can be set to 0`() {
|
|
260
|
+
player.volume = 0.0f
|
|
261
|
+
assertEquals(0.0f, player.volume, 0.001f)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@Test
|
|
265
|
+
fun `volume can be set to half`() {
|
|
266
|
+
player.volume = 0.5f
|
|
267
|
+
assertEquals(0.5f, player.volume, 0.001f)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@Test
|
|
271
|
+
fun `volume is clamped between 0 and 1`() {
|
|
272
|
+
// ExoPlayer clamps values; setting beyond range should be bounded
|
|
273
|
+
player.volume = 1.0f
|
|
274
|
+
assertEquals(1.0f, player.volume, 0.001f)
|
|
275
|
+
|
|
276
|
+
player.volume = 0.0f
|
|
277
|
+
assertEquals(0.0f, player.volume, 0.001f)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---- seekTo ----
|
|
281
|
+
|
|
282
|
+
@Test
|
|
283
|
+
fun `seekTo sets position in queue`() {
|
|
284
|
+
player.setMediaItems(buildQueue(3))
|
|
285
|
+
player.seekTo(2, 5000L)
|
|
286
|
+
|
|
287
|
+
assertEquals(2, player.currentMediaItemIndex)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ---- moveMediaItem ----
|
|
291
|
+
|
|
292
|
+
@Test
|
|
293
|
+
fun `moveMediaItem reorders queue`() {
|
|
294
|
+
player.setMediaItems(buildQueue(3))
|
|
295
|
+
player.moveMediaItem(0, 2)
|
|
296
|
+
|
|
297
|
+
// track-1 should now be at index 2
|
|
298
|
+
assertEquals("track-2", player.getMediaItemAt(0).mediaId)
|
|
299
|
+
assertEquals("track-3", player.getMediaItemAt(1).mediaId)
|
|
300
|
+
assertEquals("track-1", player.getMediaItemAt(2).mediaId)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---- metadata retrieval ----
|
|
304
|
+
|
|
305
|
+
@Test
|
|
306
|
+
fun `getMediaItemAt returns correct metadata`() {
|
|
307
|
+
val item = buildMediaItem("meta-test", "Metadata Test Track")
|
|
308
|
+
player.setMediaItems(listOf(item))
|
|
309
|
+
|
|
310
|
+
val retrieved = player.getMediaItemAt(0)
|
|
311
|
+
assertEquals("meta-test", retrieved.mediaId)
|
|
312
|
+
assertEquals("Metadata Test Track", retrieved.mediaMetadata.title.toString())
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@Test
|
|
316
|
+
fun `player initial state is idle`() {
|
|
317
|
+
assertEquals(Player.STATE_IDLE, player.playbackState)
|
|
318
|
+
}
|
|
319
|
+
}
|