@rntp/player 5.0.0-beta.3 → 5.0.0-beta.5

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 (61) hide show
  1. package/android/build.gradle +7 -0
  2. package/android/src/main/java/com/doublesymmetry/trackplayer/SleepTimerController.kt +128 -0
  3. package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayerModule.kt +40 -0
  4. package/android/src/main/java/com/doublesymmetry/trackplayer/TrackPlayerPlaybackService.kt +107 -87
  5. package/android/src/main/java/com/doublesymmetry/trackplayer/models/BrowseTree.kt +51 -20
  6. package/android/src/main/java/com/doublesymmetry/trackplayer/models/PlayerConfig.kt +12 -1
  7. package/android/src/test/java/com/doublesymmetry/trackplayer/ExoPlayerIntegrationTest.kt +319 -0
  8. package/android/src/test/java/com/doublesymmetry/trackplayer/SleepTimerIntegrationTest.kt +473 -0
  9. package/android/src/test/java/com/doublesymmetry/trackplayer/SleepTimerStateTest.kt +58 -0
  10. package/android/src/test/java/com/doublesymmetry/trackplayer/models/BrowseNavigationTest.kt +215 -0
  11. package/android/src/test/java/com/doublesymmetry/trackplayer/models/BrowseTreeTest.kt +166 -0
  12. package/android/src/test/java/com/doublesymmetry/trackplayer/models/EmitEventTest.kt +68 -0
  13. package/android/src/test/java/com/doublesymmetry/trackplayer/models/PlayerConfigTest.kt +400 -0
  14. package/android/src/test/java/com/doublesymmetry/trackplayer/models/TrackPlayerMediaItemTest.kt +380 -0
  15. package/android/src/test/resources/robolectric.properties +1 -0
  16. package/ios/CarPlay/RNTPCarPlaySceneDelegate.swift +43 -14
  17. package/ios/TrackPlayer.swift +46 -101
  18. package/ios/TrackPlayerBridge.mm +2 -0
  19. package/ios/player/AVPlayerEngine.swift +46 -32
  20. package/ios/player/AudioCache.swift +34 -0
  21. package/ios/player/AudioPlayer.swift +36 -21
  22. package/ios/player/CacheProxyServer.swift +429 -0
  23. package/ios/player/DownloadCoordinator.swift +242 -0
  24. package/ios/player/Preloader.swift +21 -90
  25. package/ios/player/SleepTimerController.swift +147 -0
  26. package/ios/tests/AVPlayerEngineIntegrationTests.swift +230 -0
  27. package/ios/tests/AudioPlayerTests.swift +6 -0
  28. package/ios/tests/CacheProxyServerTests.swift +403 -0
  29. package/ios/tests/DownloadCoordinatorTests.swift +197 -0
  30. package/ios/tests/LocalAudioServer.swift +171 -0
  31. package/ios/tests/MockPlayerEngine.swift +1 -0
  32. package/ios/tests/QueueManagerTests.swift +6 -0
  33. package/ios/tests/SleepTimerIntegrationTests.swift +408 -0
  34. package/ios/tests/SleepTimerTests.swift +70 -0
  35. package/lib/commonjs/NativeTrackPlayer.js.map +1 -1
  36. package/lib/commonjs/audio.js +39 -4
  37. package/lib/commonjs/audio.js.map +1 -1
  38. package/lib/commonjs/interfaces/PlayerConfig.js +1 -1
  39. package/lib/commonjs/interfaces/PlayerConfig.js.map +1 -1
  40. package/lib/module/NativeTrackPlayer.js.map +1 -1
  41. package/lib/module/audio.js +37 -4
  42. package/lib/module/audio.js.map +1 -1
  43. package/lib/module/interfaces/PlayerConfig.js +1 -1
  44. package/lib/module/interfaces/PlayerConfig.js.map +1 -1
  45. package/lib/typescript/src/NativeTrackPlayer.d.ts +2 -0
  46. package/lib/typescript/src/NativeTrackPlayer.d.ts.map +1 -1
  47. package/lib/typescript/src/audio.d.ts +16 -4
  48. package/lib/typescript/src/audio.d.ts.map +1 -1
  49. package/lib/typescript/src/interfaces/BrowseTree.d.ts +35 -5
  50. package/lib/typescript/src/interfaces/BrowseTree.d.ts.map +1 -1
  51. package/lib/typescript/src/interfaces/MediaItem.d.ts +4 -1
  52. package/lib/typescript/src/interfaces/MediaItem.d.ts.map +1 -1
  53. package/lib/typescript/src/interfaces/PlayerConfig.d.ts +19 -2
  54. package/lib/typescript/src/interfaces/PlayerConfig.d.ts.map +1 -1
  55. package/package.json +4 -1
  56. package/src/NativeTrackPlayer.ts +4 -0
  57. package/src/audio.ts +37 -4
  58. package/src/interfaces/BrowseTree.ts +40 -5
  59. package/src/interfaces/MediaItem.ts +4 -1
  60. package/src/interfaces/PlayerConfig.ts +22 -3
  61. 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
+ }