react-native-queue-player 1.0.8 → 1.1.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.
- package/README.md +12 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +21 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderArtwork.kt +30 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoader.kt +9 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +4 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +57 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +53 -20
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +55 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderArtworkTest.kt +72 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoaderTest.kt +21 -9
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt +9 -9
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +103 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +7 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +6 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +41 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +34 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +1 -1
- package/ios/Cast/Core/CastNowPlayingController.swift +7 -4
- package/ios/NowPlayingInfo.swift +7 -9
- package/ios/PlaceholderArtwork.swift +24 -1
- package/ios/QueueSkipArithmetic.swift +55 -9
- package/ios/Tests/AVQueueBuilderTests.swift +9 -9
- package/ios/Tests/CastNowPlayingControllerTests.swift +33 -0
- package/ios/Tests/PlaceholderArtworkTests.swift +81 -0
- package/ios/Tests/SkipIndexTests.swift +97 -0
- package/ios/TrackPlayer.swift +42 -14
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +9 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +7 -7
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +41 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +4 -0
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +11 -1
- package/nitrogen/generated/android/c++/JSkipToPreviousBehavior.hpp +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +12 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/SkipToPreviousBehavior.kt +23 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +18 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +3 -0
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +30 -1
- package/nitrogen/generated/ios/swift/SkipToPreviousBehavior.swift +40 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +12 -1
- package/nitrogen/generated/shared/c++/SkipToPreviousBehavior.hpp +76 -0
- package/package.json +1 -1
- package/src/TrackPlayer.nitro.ts +7 -7
- package/src/index.ts +1 -0
- package/src/types.ts +42 -0
package/android/src/test/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoaderTest.kt
CHANGED
|
@@ -40,7 +40,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
40
40
|
|
|
41
41
|
@Test
|
|
42
42
|
fun `loadBitmapFromMetadata returns placeholder when artwork URI and data are absent`() {
|
|
43
|
-
val loader = PlaceholderFallbackBitmapLoader(NeverCalledLoader, placeholder)
|
|
43
|
+
val loader = PlaceholderFallbackBitmapLoader(NeverCalledLoader, { placeholder })
|
|
44
44
|
val metadata = MediaMetadata.Builder().setTitle("no artwork").build()
|
|
45
45
|
|
|
46
46
|
val future = loader.loadBitmapFromMetadata(metadata)
|
|
@@ -49,10 +49,22 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
49
49
|
assertSame(placeholder, future!!.get())
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
@Test
|
|
53
|
+
fun `loadBitmapFromMetadata reads the placeholder provider on every call so a live swap is reflected`() {
|
|
54
|
+
val swapped = Bitmap.createBitmap(4, 4, Bitmap.Config.ARGB_8888)
|
|
55
|
+
var current = placeholder
|
|
56
|
+
val loader = PlaceholderFallbackBitmapLoader(NeverCalledLoader) { current }
|
|
57
|
+
val metadata = MediaMetadata.Builder().setTitle("no artwork").build()
|
|
58
|
+
|
|
59
|
+
assertSame(placeholder, loader.loadBitmapFromMetadata(metadata)!!.get())
|
|
60
|
+
current = swapped
|
|
61
|
+
assertSame(swapped, loader.loadBitmapFromMetadata(metadata)!!.get())
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
@Test
|
|
53
65
|
fun `loadBitmapFromMetadata returns delegate result when artwork URI is present`() {
|
|
54
66
|
val delegate = StubLoader(metadataResult = Futures.immediateFuture(delegateBitmap))
|
|
55
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
67
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
56
68
|
val metadata = MediaMetadata.Builder()
|
|
57
69
|
.setArtworkUri(Uri.parse("https://example.com/cover.jpg"))
|
|
58
70
|
.build()
|
|
@@ -68,7 +80,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
68
80
|
val delegate = StubLoader(
|
|
69
81
|
metadataResult = Futures.immediateFailedFuture(RuntimeException("decode failed"))
|
|
70
82
|
)
|
|
71
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
83
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
72
84
|
val metadata = MediaMetadata.Builder()
|
|
73
85
|
.setArtworkUri(Uri.parse("https://example.com/broken.jpg"))
|
|
74
86
|
.build()
|
|
@@ -82,7 +94,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
82
94
|
@Test
|
|
83
95
|
fun `loadBitmapFromMetadata returns placeholder when delegate returns null future`() {
|
|
84
96
|
val delegate = StubLoader(metadataResult = null)
|
|
85
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
97
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
86
98
|
val metadata = MediaMetadata.Builder()
|
|
87
99
|
.setArtworkUri(Uri.parse("https://example.com/cover.jpg"))
|
|
88
100
|
.build()
|
|
@@ -98,7 +110,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
98
110
|
val delegate = StubLoader(
|
|
99
111
|
uriResult = Futures.immediateFailedFuture(RuntimeException("network error"))
|
|
100
112
|
)
|
|
101
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
113
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
102
114
|
|
|
103
115
|
val result = loader.loadBitmap(Uri.parse("https://example.com/cover.jpg")).get()
|
|
104
116
|
|
|
@@ -108,7 +120,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
108
120
|
@Test
|
|
109
121
|
fun `loadBitmap returns delegate result on success`() {
|
|
110
122
|
val delegate = StubLoader(uriResult = Futures.immediateFuture(delegateBitmap))
|
|
111
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
123
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
112
124
|
|
|
113
125
|
val result = loader.loadBitmap(Uri.parse("https://example.com/cover.jpg")).get()
|
|
114
126
|
|
|
@@ -120,7 +132,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
120
132
|
val delegate = StubLoader(
|
|
121
133
|
bytesResult = Futures.immediateFailedFuture(RuntimeException("malformed bytes"))
|
|
122
134
|
)
|
|
123
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
135
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
124
136
|
|
|
125
137
|
val result = loader.decodeBitmap(byteArrayOf(0x00, 0x01)).get()
|
|
126
138
|
|
|
@@ -130,7 +142,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
130
142
|
@Test
|
|
131
143
|
fun `decodeBitmap returns delegate result on success`() {
|
|
132
144
|
val delegate = StubLoader(bytesResult = Futures.immediateFuture(delegateBitmap))
|
|
133
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
145
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
134
146
|
|
|
135
147
|
val result = loader.decodeBitmap(byteArrayOf(0x00, 0x01)).get()
|
|
136
148
|
|
|
@@ -140,7 +152,7 @@ class PlaceholderFallbackBitmapLoaderTest {
|
|
|
140
152
|
@Test
|
|
141
153
|
fun `supportsMimeType proxies through to delegate`() {
|
|
142
154
|
val delegate = StubLoader(supportedMimeTypes = setOf("image/jpeg"))
|
|
143
|
-
val loader = PlaceholderFallbackBitmapLoader(delegate, placeholder)
|
|
155
|
+
val loader = PlaceholderFallbackBitmapLoader(delegate, { placeholder })
|
|
144
156
|
|
|
145
157
|
assertTrue(loader.supportsMimeType("image/jpeg"))
|
|
146
158
|
assertFalse(loader.supportsMimeType("image/heic"))
|
package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt
CHANGED
|
@@ -80,16 +80,16 @@ class PlaybackServiceArtworkLoaderTest {
|
|
|
80
80
|
|
|
81
81
|
@Test
|
|
82
82
|
fun `repeat builds reuse the same placeholder bitmap`() {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
// memory cost.
|
|
83
|
+
// The loader holds a placeholder *provider* closure; invoking it
|
|
84
|
+
// reads the process-cached PlaceholderArtwork bitmap so the ~10 KB
|
|
85
|
+
// JPEG decode runs once — service-recreate cycles + multiple
|
|
86
|
+
// consumer-side reconfigures do not multiply the memory cost.
|
|
87
87
|
val first = service.buildBitmapLoader()
|
|
88
88
|
val second = service.buildBitmapLoader()
|
|
89
|
-
val
|
|
90
|
-
.apply { isAccessible = true }.get(first)
|
|
91
|
-
val
|
|
92
|
-
.apply { isAccessible = true }.get(second)
|
|
93
|
-
assertSame(
|
|
89
|
+
val firstProvider = first.javaClass.getDeclaredField("placeholder")
|
|
90
|
+
.apply { isAccessible = true }.get(first) as kotlin.jvm.functions.Function0<*>
|
|
91
|
+
val secondProvider = second.javaClass.getDeclaredField("placeholder")
|
|
92
|
+
.apply { isAccessible = true }.get(second) as kotlin.jvm.functions.Function0<*>
|
|
93
|
+
assertSame(firstProvider.invoke(), secondProvider.invoke())
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -785,7 +785,7 @@ class PlaybackServiceCallbackTest {
|
|
|
785
785
|
audioCategoryOptions = null,
|
|
786
786
|
visualizationEnabled = null,
|
|
787
787
|
clampSeekToBuffered = null,
|
|
788
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null,
|
|
788
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null,
|
|
789
789
|
),
|
|
790
790
|
context,
|
|
791
791
|
)
|
|
@@ -340,4 +340,107 @@ class QueueSkipArithmeticTest {
|
|
|
340
340
|
assertFalse(QueueSkipArithmetic.canSkipNext(1, 0, QueueSkipRepeatMode.TRACK))
|
|
341
341
|
assertFalse(QueueSkipArithmetic.canSkipPrevious(1, 0, QueueSkipRepeatMode.TRACK))
|
|
342
342
|
}
|
|
343
|
+
|
|
344
|
+
// --- resolveSkipToPrevious — mode OFF (position ignored, current behavior)
|
|
345
|
+
|
|
346
|
+
private fun resolve(
|
|
347
|
+
count: Int = 3, index: Int, mode: QueueSkipRepeatMode,
|
|
348
|
+
restart: Boolean, positionMs: Long
|
|
349
|
+
) = QueueSkipArithmetic.resolveSkipToPrevious(count, index, mode, restart, positionMs)
|
|
350
|
+
|
|
351
|
+
private val previous1 = QueueSkipArithmetic.SkipToPreviousAction.Previous(1)
|
|
352
|
+
private val previous2 = QueueSkipArithmetic.SkipToPreviousAction.Previous(2)
|
|
353
|
+
private val restart = QueueSkipArithmetic.SkipToPreviousAction.RestartCurrent
|
|
354
|
+
private val noOp = QueueSkipArithmetic.SkipToPreviousAction.NoOp
|
|
355
|
+
|
|
356
|
+
@Test
|
|
357
|
+
fun `resolve mode OFF ignores position and goes previous`() {
|
|
358
|
+
assertEquals(previous1, resolve(index = 2, mode = QueueSkipRepeatMode.OFF, restart = false, positionMs = 99_000L))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@Test
|
|
362
|
+
fun `resolve mode OFF first track is noop`() {
|
|
363
|
+
assertEquals(noOp, resolve(index = 0, mode = QueueSkipRepeatMode.OFF, restart = false, positionMs = 99_000L))
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@Test
|
|
367
|
+
fun `resolve mode OFF queue wraps`() {
|
|
368
|
+
assertEquals(previous2, resolve(index = 0, mode = QueueSkipRepeatMode.QUEUE, restart = false, positionMs = 0L))
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@Test
|
|
372
|
+
fun `resolve mode OFF empty queue is noop`() {
|
|
373
|
+
assertEquals(noOp, resolve(count = 0, index = -1, mode = QueueSkipRepeatMode.OFF, restart = false, positionMs = 0L))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// --- resolveSkipToPrevious — mode ON (3s threshold model)
|
|
377
|
+
|
|
378
|
+
@Test
|
|
379
|
+
fun `resolve past threshold restarts even with a previous`() {
|
|
380
|
+
for (pos in listOf(3001L, 5000L, 600_000L)) {
|
|
381
|
+
assertEquals("position $pos > 3000 restarts", restart,
|
|
382
|
+
resolve(index = 2, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = pos))
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@Test
|
|
387
|
+
fun `resolve threshold boundary goes previous, one past restarts`() {
|
|
388
|
+
assertEquals(previous1, resolve(index = 2, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = 3000L))
|
|
389
|
+
assertEquals(restart, resolve(index = 2, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = 3001L))
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@Test
|
|
393
|
+
fun `resolve under threshold goes previous`() {
|
|
394
|
+
for (pos in listOf(0L, 1L, 2999L)) {
|
|
395
|
+
assertEquals("position $pos <= 3000 goes previous", previous1,
|
|
396
|
+
resolve(index = 2, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = pos))
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
@Test
|
|
401
|
+
fun `resolve first track under threshold restarts when no previous`() {
|
|
402
|
+
assertEquals(restart, resolve(index = 0, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = 1000L))
|
|
403
|
+
assertEquals(restart, resolve(index = 0, mode = QueueSkipRepeatMode.TRACK, restart = true, positionMs = 1000L))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
@Test
|
|
407
|
+
fun `resolve first track under threshold queue wraps`() {
|
|
408
|
+
assertEquals(previous2, resolve(index = 0, mode = QueueSkipRepeatMode.QUEUE, restart = true, positionMs = 1000L))
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@Test
|
|
412
|
+
fun `resolve first track past threshold restarts under every mode`() {
|
|
413
|
+
for (mode in QueueSkipRepeatMode.values()) {
|
|
414
|
+
assertEquals("first track past threshold restarts under $mode", restart,
|
|
415
|
+
resolve(index = 0, mode = mode, restart = true, positionMs = 9000L))
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
@Test
|
|
420
|
+
fun `resolve mode ON empty queue is noop`() {
|
|
421
|
+
assertEquals(noOp, resolve(count = 0, index = -1, mode = QueueSkipRepeatMode.OFF, restart = true, positionMs = 9000L))
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// --- canSkipPrevious with restartEnabled (forced true)
|
|
425
|
+
|
|
426
|
+
@Test
|
|
427
|
+
fun `canSkipPrevious forced true at every index and mode`() {
|
|
428
|
+
for (mode in QueueSkipRepeatMode.values()) {
|
|
429
|
+
for (index in 0 until 3) {
|
|
430
|
+
assertTrue("restart mode forces canSkipPrevious true at index $index under $mode",
|
|
431
|
+
QueueSkipArithmetic.canSkipPrevious(3, index, mode, restartEnabled = true))
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@Test
|
|
437
|
+
fun `canSkipPrevious restartEnabled false for empty queue`() {
|
|
438
|
+
assertFalse(QueueSkipArithmetic.canSkipPrevious(0, -1, QueueSkipRepeatMode.OFF, restartEnabled = true))
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@Test
|
|
442
|
+
fun `canSkipPrevious restart disabled matches existing`() {
|
|
443
|
+
assertFalse(QueueSkipArithmetic.canSkipPrevious(3, 0, QueueSkipRepeatMode.OFF, restartEnabled = false))
|
|
444
|
+
assertTrue(QueueSkipArithmetic.canSkipPrevious(3, 1, QueueSkipRepeatMode.OFF, restartEnabled = false))
|
|
445
|
+
}
|
|
343
446
|
}
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt
CHANGED
|
@@ -66,7 +66,7 @@ class TrackPlayerAirPlayMetadataWiringTest {
|
|
|
66
66
|
retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null,
|
|
67
67
|
audioCategoryOptions = null, visualizationEnabled = null,
|
|
68
68
|
clampSeekToBuffered = null,
|
|
69
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null,
|
|
69
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null,
|
|
70
70
|
),
|
|
71
71
|
context(),
|
|
72
72
|
)
|
|
@@ -44,7 +44,7 @@ class TrackPlayerAudioFocusTest {
|
|
|
44
44
|
player = TrackPlayer()
|
|
45
45
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
46
46
|
player.configureInternal(
|
|
47
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
47
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -51,7 +51,7 @@ class TrackPlayerEventsTest {
|
|
|
51
51
|
player = TrackPlayer()
|
|
52
52
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
53
53
|
player.configureInternal(
|
|
54
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
54
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
55
55
|
)
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -230,7 +230,7 @@ class TrackPlayerEventsTest {
|
|
|
230
230
|
// the public effects — a new configure + getStateInternal
|
|
231
231
|
// should return NONE.
|
|
232
232
|
player.configureInternal(
|
|
233
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
233
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
234
234
|
)
|
|
235
235
|
assertEquals(PlayerState.NONE, player.getStateInternal())
|
|
236
236
|
}
|
|
@@ -479,7 +479,7 @@ class TrackPlayerEventsTest {
|
|
|
479
479
|
serviceHandle.destroy()
|
|
480
480
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
481
481
|
player.configureInternal(
|
|
482
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
482
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
483
483
|
)
|
|
484
484
|
// No listener is bound to the new ExoPlayer's actual STATE_ENDED
|
|
485
485
|
// path in a Robolectric test, but the callback slot itself is
|
|
@@ -68,7 +68,7 @@ class TrackPlayerLifecycleTest {
|
|
|
68
68
|
autoRetries = null,
|
|
69
69
|
retryBackoffMs = null,
|
|
70
70
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
71
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
71
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
72
72
|
)
|
|
73
73
|
player.configureInternal(config, context())
|
|
74
74
|
|
|
@@ -92,7 +92,7 @@ class TrackPlayerLifecycleTest {
|
|
|
92
92
|
// public path owns the destroy step. This test drives the
|
|
93
93
|
// destroy + reconstruct shape directly to mirror what
|
|
94
94
|
// `configure()` does end-to-end.
|
|
95
|
-
val empty = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null)
|
|
95
|
+
val empty = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
96
96
|
|
|
97
97
|
player.configureInternal(empty, context())
|
|
98
98
|
val firstInstance = player.player
|
|
@@ -125,12 +125,12 @@ class TrackPlayerLifecycleTest {
|
|
|
125
125
|
val first = PlayerConfig(
|
|
126
126
|
httpHeaders = mapOf("A" to "1"), userAgent = "ua-1",
|
|
127
127
|
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
128
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
128
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
129
129
|
)
|
|
130
130
|
val second = PlayerConfig(
|
|
131
131
|
httpHeaders = mapOf("B" to "2"), userAgent = "ua-2",
|
|
132
132
|
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
133
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
133
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
134
134
|
)
|
|
135
135
|
|
|
136
136
|
player.configureInternal(first, context())
|
|
@@ -144,7 +144,7 @@ class TrackPlayerLifecycleTest {
|
|
|
144
144
|
val configured = PlayerConfig(
|
|
145
145
|
httpHeaders = mapOf("h" to "v"), userAgent = "ua",
|
|
146
146
|
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
147
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
147
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
148
148
|
)
|
|
149
149
|
player.configureInternal(configured, context())
|
|
150
150
|
// Non-default playback state the consumer set post-configure.
|
|
@@ -180,7 +180,7 @@ class TrackPlayerLifecycleTest {
|
|
|
180
180
|
@Test
|
|
181
181
|
fun `destroyInternal is safe to call twice`() {
|
|
182
182
|
player.configureInternal(
|
|
183
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
183
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
184
184
|
)
|
|
185
185
|
player.destroyInternal()
|
|
186
186
|
player.destroyInternal()
|
|
@@ -189,7 +189,7 @@ class TrackPlayerLifecycleTest {
|
|
|
189
189
|
|
|
190
190
|
@Test
|
|
191
191
|
fun `configureInternal after destroyInternal rebuilds the ExoPlayer`() {
|
|
192
|
-
val cfg = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null)
|
|
192
|
+
val cfg = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
193
193
|
|
|
194
194
|
player.configureInternal(cfg, context())
|
|
195
195
|
val first = player.player
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt
CHANGED
|
@@ -80,7 +80,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
80
80
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
81
81
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
82
82
|
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = 250.0, lookaheadCacheEvictionPolicy = null,
|
|
83
|
-
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
83
|
+
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
84
84
|
),
|
|
85
85
|
context()
|
|
86
86
|
)
|
|
@@ -120,7 +120,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
120
120
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
121
121
|
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null,
|
|
122
122
|
lookaheadCacheEvictionPolicy = policy,
|
|
123
|
-
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
123
|
+
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
// --- setLookaheadCacheInternal
|
|
@@ -298,7 +298,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
298
298
|
@Test
|
|
299
299
|
fun `re-configure with changed httpHeaders rebuilds the player (writer + cache)`() {
|
|
300
300
|
player.configureInternal(
|
|
301
|
-
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-1"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null),
|
|
301
|
+
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-1"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
302
302
|
context()
|
|
303
303
|
)
|
|
304
304
|
val firstWriter = player.lookaheadCacheWriter
|
|
@@ -307,7 +307,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
307
307
|
assertNotNull(firstCache)
|
|
308
308
|
|
|
309
309
|
player.configureInternal(
|
|
310
|
-
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-2"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null),
|
|
310
|
+
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-2"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
311
311
|
context()
|
|
312
312
|
)
|
|
313
313
|
|
|
@@ -339,7 +339,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
339
339
|
// simulated here by `destroyInternal` between
|
|
340
340
|
// `configureInternal` calls) yields a fresh writer instance,
|
|
341
341
|
// even when headers are unchanged.
|
|
342
|
-
val cfg = PlayerConfig(httpHeaders = mapOf("X-Auth" to "token"), userAgent = "rnqp/test", autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null)
|
|
342
|
+
val cfg = PlayerConfig(httpHeaders = mapOf("X-Auth" to "token"), userAgent = "rnqp/test", autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
343
343
|
player.configureInternal(cfg, context())
|
|
344
344
|
val firstWriter = player.lookaheadCacheWriter
|
|
345
345
|
|
|
@@ -413,5 +413,5 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
413
413
|
|
|
414
414
|
// --- Helpers
|
|
415
415
|
|
|
416
|
-
private fun emptyConfig() = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null)
|
|
416
|
+
private fun emptyConfig() = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
417
417
|
}
|
|
@@ -45,7 +45,7 @@ class TrackPlayerMutationTest {
|
|
|
45
45
|
player = TrackPlayer()
|
|
46
46
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
47
47
|
player.configureInternal(
|
|
48
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
48
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
49
49
|
)
|
|
50
50
|
player.setQueueInternal((1..5).map { track("t$it") })
|
|
51
51
|
// cur defaults to 0 after setQueue; tests needing mid-queue
|
|
@@ -127,7 +127,7 @@ class TrackPlayerMutationTest {
|
|
|
127
127
|
val freshHandle = bindPlaybackServiceForTest(fresh, context())
|
|
128
128
|
try {
|
|
129
129
|
fresh.configureInternal(
|
|
130
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
130
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
131
131
|
)
|
|
132
132
|
assertEquals(-1, fresh.currentTrackIndex)
|
|
133
133
|
assertEquals(Player.STATE_IDLE, fresh.player!!.playbackState)
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt
CHANGED
|
@@ -35,7 +35,7 @@ class TrackPlayerProgressThrottleTest {
|
|
|
35
35
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
36
36
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
37
37
|
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null,
|
|
38
|
-
progressUpdateIntervalMs = foregroundMs, backgroundProgressUpdateIntervalMs = backgroundMs
|
|
38
|
+
progressUpdateIntervalMs = foregroundMs, backgroundProgressUpdateIntervalMs = backgroundMs, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
@Before
|
|
@@ -47,7 +47,7 @@ class TrackPlayerQueueChangeTest {
|
|
|
47
47
|
player = TrackPlayer()
|
|
48
48
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
49
49
|
player.configureInternal(
|
|
50
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
50
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
51
51
|
)
|
|
52
52
|
player.setQueueInternal((1..5).map { track("t$it") })
|
|
53
53
|
}
|
|
@@ -55,7 +55,7 @@ class TrackPlayerQueueTest {
|
|
|
55
55
|
player = TrackPlayer()
|
|
56
56
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
57
57
|
player.configureInternal(
|
|
58
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
58
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -42,7 +42,7 @@ class TrackPlayerReadersTest {
|
|
|
42
42
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
43
43
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
44
44
|
visualizationEnabled = null, clampSeekToBuffered = null,
|
|
45
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null
|
|
45
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
46
46
|
),
|
|
47
47
|
context()
|
|
48
48
|
)
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt
CHANGED
|
@@ -53,7 +53,7 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
53
53
|
player = TrackPlayer()
|
|
54
54
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
55
55
|
player.configureInternal(
|
|
56
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
56
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
57
57
|
)
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -318,7 +318,46 @@ class TrackPlayerSkipCapabilityTest {
|
|
|
318
318
|
player = TrackPlayer()
|
|
319
319
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
320
320
|
player.configureInternal(
|
|
321
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
321
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// --- skipToPreviousBehavior = restart-or-previous forces canSkipPrevious true
|
|
326
|
+
|
|
327
|
+
private fun configureRestart() {
|
|
328
|
+
player.configureInternal(
|
|
329
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = SkipToPreviousBehavior.RESTART_OR_PREVIOUS), context()
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@Test
|
|
334
|
+
fun `restart mode forces canSkipPrevious true at index 0`() {
|
|
335
|
+
configureRestart()
|
|
336
|
+
player.setQueueInternal((1..5).map { track("t$it") })
|
|
337
|
+
// At index 0 under OFF the default mode reports false (no previous);
|
|
338
|
+
// restart mode forces it true so Previous never greys out.
|
|
339
|
+
assertEquals(true, player.getCanSkipPrevious())
|
|
340
|
+
assertEquals(true, player.getCanSkipNext())
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@Test
|
|
344
|
+
fun `default previous mode leaves canSkipPrevious false at index 0`() {
|
|
345
|
+
// setUp configured skipToPreviousBehavior = null (= 'previous').
|
|
346
|
+
player.setQueueInternal((1..5).map { track("t$it") })
|
|
347
|
+
assertEquals(false, player.getCanSkipPrevious())
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
@Test
|
|
351
|
+
fun `restart mode emits canSkipPrevious true via onSkipCapabilityChange`() {
|
|
352
|
+
configureRestart()
|
|
353
|
+
val emissions = mutableListOf<SkipCapability>()
|
|
354
|
+
player.onSkipCapabilityChange { cap -> emissions.add(cap) }
|
|
355
|
+
flushMain() // auto-fire: empty queue → (false, false)
|
|
356
|
+
player.setQueueInternal((1..5).map { track("t$it") })
|
|
357
|
+
flushMain()
|
|
358
|
+
assertTrue(
|
|
359
|
+
"restart mode makes canSkipPrevious true once a track is loaded",
|
|
360
|
+
emissions.last().canSkipPrevious
|
|
322
361
|
)
|
|
323
362
|
}
|
|
324
363
|
}
|
|
@@ -51,7 +51,7 @@ class TrackPlayerSkipTest {
|
|
|
51
51
|
player = TrackPlayer()
|
|
52
52
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
53
53
|
player.configureInternal(
|
|
54
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
54
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
55
55
|
)
|
|
56
56
|
player.tracks = (1..5).map { track("t$it") }
|
|
57
57
|
player.currentTrackIndex = 2 // mid-queue default for most cases
|
|
@@ -284,4 +284,37 @@ class TrackPlayerSkipTest {
|
|
|
284
284
|
0, fresh.currentTrackIndex
|
|
285
285
|
)
|
|
286
286
|
}
|
|
287
|
+
|
|
288
|
+
// --- skipToPreviousInternal with skipToPreviousBehavior = restart-or-previous
|
|
289
|
+
//
|
|
290
|
+
// The test harness leaves the ExoPlayer timeline empty, so engine position
|
|
291
|
+
// reads 0 (<= the 3s threshold). Past-threshold restart with a real position
|
|
292
|
+
// is covered by the pure `QueueSkipArithmeticTest` matrix + the demo suite.
|
|
293
|
+
|
|
294
|
+
private fun configureSkipBehavior(behavior: SkipToPreviousBehavior?) {
|
|
295
|
+
player.configureInternal(
|
|
296
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = behavior), context()
|
|
297
|
+
)
|
|
298
|
+
player.tracks = (1..5).map { track("t$it") }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@Test
|
|
302
|
+
fun `skipToPreviousInternal restart mode under threshold goes to previous track`() {
|
|
303
|
+
configureSkipBehavior(SkipToPreviousBehavior.RESTART_OR_PREVIOUS)
|
|
304
|
+
player.repeatModeState = RepeatMode.OFF
|
|
305
|
+
player.currentTrackIndex = 3
|
|
306
|
+
player.skipToPreviousInternal()
|
|
307
|
+
assertEquals("position 0 <= threshold → previous track", 2, player.currentTrackIndex)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@Test
|
|
311
|
+
fun `skipToPreviousInternal restart mode at first track restarts, index unchanged`() {
|
|
312
|
+
configureSkipBehavior(SkipToPreviousBehavior.RESTART_OR_PREVIOUS)
|
|
313
|
+
player.repeatModeState = RepeatMode.OFF
|
|
314
|
+
player.currentTrackIndex = 0
|
|
315
|
+
// No previous track → restart current; the restart branch seeks to 0 and
|
|
316
|
+
// leaves currentTrackIndex put (a seek, not a skip).
|
|
317
|
+
player.skipToPreviousInternal()
|
|
318
|
+
assertEquals("restart leaves the index unchanged", 0, player.currentTrackIndex)
|
|
319
|
+
}
|
|
287
320
|
}
|
|
@@ -50,7 +50,7 @@ class TrackPlayerTransportTest {
|
|
|
50
50
|
player = TrackPlayer()
|
|
51
51
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
52
52
|
player.configureInternal(
|
|
53
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null), context()
|
|
53
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -35,12 +35,15 @@ final class CastNowPlayingController {
|
|
|
35
35
|
/// `TrackPlayer.configure` via `configureArtwork`.
|
|
36
36
|
private let artworkResolver = ArtworkResolver()
|
|
37
37
|
|
|
38
|
-
///
|
|
39
|
-
///
|
|
40
|
-
|
|
38
|
+
/// Placeholder shown until the real cover resolves, so the lockscreen
|
|
39
|
+
/// never renders artwork-less during the network fetch. Read live —
|
|
40
|
+
/// `PlaceholderArtwork.image` is the consumer-configured image when one
|
|
41
|
+
/// is set (`PlayerConfig.placeholderArtworkUri`), else the bundled one —
|
|
42
|
+
/// so a per-refresh write here never clobbers it back to the built-in.
|
|
43
|
+
private static var placeholderArtwork: MPMediaItemArtwork {
|
|
41
44
|
let image = PlaceholderArtwork.image
|
|
42
45
|
return MPMediaItemArtwork(boundsSize: image.size) { _ in image }
|
|
43
|
-
}
|
|
46
|
+
}
|
|
44
47
|
|
|
45
48
|
/// Snapshot of the active receiver track for the lockscreen surface.
|
|
46
49
|
/// Pushed by `TrackPlayer` when the receiver's current track (resolved
|