react-native-queue-player 1.0.9 → 1.1.1
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/PlaybackService.kt +15 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +57 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +45 -16
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt +28 -0
- 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/QueueSkipArithmetic.swift +55 -9
- package/ios/Tests/AVQueueBuilderTests.swift +9 -9
- package/ios/Tests/SkipIndexTests.swift +97 -0
- package/ios/TrackPlayer.swift +37 -13
- 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 +24 -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 +7 -1
- package/nitrogen/generated/android/c++/JSkipToPreviousBehavior.hpp +58 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -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 +12 -1
- package/nitrogen/generated/ios/swift/SkipToPreviousBehavior.swift +40 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +8 -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 +25 -0
package/README.md
CHANGED
|
@@ -140,9 +140,17 @@ Lock-screen / Now Playing controls and remote commands work automatically once t
|
|
|
140
140
|
|
|
141
141
|
## Common UX patterns
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### Restart-or-previous skip-back (the "tap Previous restarts, then goes back" model)
|
|
144
144
|
|
|
145
|
-
The
|
|
145
|
+
The standard music-player behaviour — a skip-back restarts the current track when it's past a few seconds, otherwise goes to the previous track — is built in. Enable it with `skipToPreviousBehavior`:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
await player.configure({ skipToPreviousBehavior: 'restart-or-previous' });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Now every `skipToPrevious()` — from your UI, the lock screen, Bluetooth, Android Auto, or CarPlay — restarts the current track (seeks to 0) when the position is past ~3 s, and goes to the previous track under it. Because it's native, it stays consistent across every surface (a JS handler can't cleanly intercept the OS Previous button), and the restart is a seek so it does **not** fire `onTrackChange`. With the option enabled, `canSkipPrevious` stays `true` while a track is loaded, so the Previous control never greys out — even on the first track. The default `'previous'` keeps skip-back always going to the previous track. (Local playback only; during cast, Previous routes to the receiver.)
|
|
152
|
+
|
|
153
|
+
For a different threshold or bespoke logic on **your own** in-app button (the OS controls follow `skipToPreviousBehavior`), leave the option at its default and branch on the live position:
|
|
146
154
|
|
|
147
155
|
```tsx
|
|
148
156
|
import { getTrackPlayer, useProgress } from 'react-native-queue-player';
|
|
@@ -151,18 +159,16 @@ function PreviousButton() {
|
|
|
151
159
|
const { position } = useProgress();
|
|
152
160
|
const onPress = async () => {
|
|
153
161
|
const player = getTrackPlayer();
|
|
154
|
-
if (position >
|
|
162
|
+
if (position > 5) await player.seekTo(0);
|
|
155
163
|
else await player.skipToPrevious();
|
|
156
164
|
};
|
|
157
165
|
return /* your <Button> here */;
|
|
158
166
|
}
|
|
159
167
|
```
|
|
160
168
|
|
|
161
|
-
The 3-second threshold is the Apple Music default; tune to taste.
|
|
162
|
-
|
|
163
169
|
### Skip-button enable/disable from `useCanSkip`
|
|
164
170
|
|
|
165
|
-
`canSkipNext` / `canSkipPrevious`
|
|
171
|
+
`canSkipNext` / `canSkipPrevious` account for repeat mode: under `off` they go false at queue boundaries; under `queue` they wrap and stay true; under `track` they stay true. With `skipToPreviousBehavior: 'restart-or-previous'`, `canSkipPrevious` stays true whenever a track is loaded (the Previous control never greys out).
|
|
166
172
|
|
|
167
173
|
```tsx
|
|
168
174
|
const { canSkipNext, canSkipPrevious } = useCanSkip();
|
|
@@ -762,8 +762,15 @@ class PlaybackService : HeadlessJsMediaService() {
|
|
|
762
762
|
* (the same one ExoPlayer drives for HTTP playback), so consumer
|
|
763
763
|
* `userAgent` + custom HTTP headers configured on the player's
|
|
764
764
|
* `MediaSource.Factory` propagate into artwork fetches without a
|
|
765
|
-
* second HTTP client.
|
|
766
|
-
*
|
|
765
|
+
* second HTTP client. It mirrors Media3's own default loader config:
|
|
766
|
+
* `setMaximumOutputDimension(MediaSession.getBitmapDimensionLimit(...))`
|
|
767
|
+
* downsamples an oversized consumer image during decode (device-adaptive
|
|
768
|
+
* cap; no full-resolution bitmap for a small control), and
|
|
769
|
+
* `setMakeShared(true)` puts the bitmap in shared memory so the System UI
|
|
770
|
+
* process reuses it instead of copying. Supplying our own loader (for the
|
|
771
|
+
* placeholder guarantee below) had otherwise dropped both defaults, which
|
|
772
|
+
* is what left artwork decoding unbounded. `CacheBitmapLoader` deduplicates
|
|
773
|
+
* repeat requests for the same URI inside a single foreground session.
|
|
767
774
|
* The outer [PlaceholderFallbackBitmapLoader] guarantees a bitmap
|
|
768
775
|
* always reaches the notification — the OEM-skin failure mode
|
|
769
776
|
* where a null bitmap drops the transport controls is closed.
|
|
@@ -776,7 +783,12 @@ class PlaybackService : HeadlessJsMediaService() {
|
|
|
776
783
|
@VisibleForTesting
|
|
777
784
|
internal fun buildBitmapLoader(): PlaceholderFallbackBitmapLoader =
|
|
778
785
|
PlaceholderFallbackBitmapLoader(
|
|
779
|
-
delegate = CacheBitmapLoader(
|
|
786
|
+
delegate = CacheBitmapLoader(
|
|
787
|
+
DataSourceBitmapLoader.Builder(this)
|
|
788
|
+
.setMaximumOutputDimension(MediaSession.getBitmapDimensionLimit(this))
|
|
789
|
+
.setMakeShared(true)
|
|
790
|
+
.build()
|
|
791
|
+
),
|
|
780
792
|
// Read on each fallback so a placeholder set at `configure` (after
|
|
781
793
|
// this loader was built in onCreate) is reflected — `PlaceholderArtwork`
|
|
782
794
|
// returns the consumer image when one is configured, else the built-in.
|
|
@@ -59,9 +59,9 @@ internal object QueueSkipArithmetic {
|
|
|
59
59
|
* Previous-track index. Same early-bail rules as [computeNext].
|
|
60
60
|
*
|
|
61
61
|
* OFF and TRACK both return null at index 0 (no previous track, no
|
|
62
|
-
* wrap).
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* wrap). The "restart current when past the threshold" model is
|
|
63
|
+
* available via `PlayerConfig.skipToPreviousBehavior` (resolved by
|
|
64
|
+
* [resolveSkipToPrevious]).
|
|
65
65
|
*/
|
|
66
66
|
fun computePrevious(
|
|
67
67
|
trackCount: Int,
|
|
@@ -99,15 +99,62 @@ internal object QueueSkipArithmetic {
|
|
|
99
99
|
* OFF and TRACK are false at index 0 (no wrap); QUEUE is always
|
|
100
100
|
* true with an active track (wraps).
|
|
101
101
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* primitive (skip if there's a track to skip to), not the policy.
|
|
102
|
+
* When [restartEnabled] is true (`PlayerConfig.skipToPreviousBehavior
|
|
103
|
+
* == RESTART_OR_PREVIOUS`), Previous is enabled whenever a track is
|
|
104
|
+
* loaded — even at index 0, where past the threshold it restarts the
|
|
105
|
+
* current track — so the control never greys out.
|
|
107
106
|
*/
|
|
108
107
|
fun canSkipPrevious(
|
|
109
108
|
trackCount: Int,
|
|
110
109
|
currentIndex: Int,
|
|
111
|
-
repeatMode: QueueSkipRepeatMode
|
|
112
|
-
|
|
110
|
+
repeatMode: QueueSkipRepeatMode,
|
|
111
|
+
restartEnabled: Boolean = false
|
|
112
|
+
): Boolean =
|
|
113
|
+
if (restartEnabled) trackCount > 0
|
|
114
|
+
else computePrevious(trackCount, currentIndex, repeatMode) != null
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Position (ms) at or below which a restart-or-previous skip-back goes
|
|
118
|
+
* to the previous track; past it, the current track restarts. Matches
|
|
119
|
+
* Media3's default `maxSeekToPreviousPosition` (industry-standard 3 s).
|
|
120
|
+
*/
|
|
121
|
+
const val RESTART_THRESHOLD_MS: Long = 3000L
|
|
122
|
+
|
|
123
|
+
/** What a skip-to-previous should do, resolved from position + behavior. */
|
|
124
|
+
sealed interface SkipToPreviousAction {
|
|
125
|
+
/** Navigate to this queue index (a real track change). */
|
|
126
|
+
data class Previous(val index: Int) : SkipToPreviousAction
|
|
127
|
+
/** Restart the current track (seek to 0) — not a track change. */
|
|
128
|
+
object RestartCurrent : SkipToPreviousAction
|
|
129
|
+
/** Nothing to do (empty queue). */
|
|
130
|
+
object NoOp : SkipToPreviousAction
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolves a skip-to-previous. When [restartEnabled] is false this is
|
|
135
|
+
* exactly [computePrevious] (previous track, or nothing). When true it
|
|
136
|
+
* applies the threshold model, matching Media3 `BasePlayer.seekToPrevious`:
|
|
137
|
+
* a previous track exists AND `positionMs <= thresholdMs` -> previous;
|
|
138
|
+
* otherwise (past threshold, or first track with no previous) -> restart
|
|
139
|
+
* the current track.
|
|
140
|
+
*/
|
|
141
|
+
fun resolveSkipToPrevious(
|
|
142
|
+
trackCount: Int,
|
|
143
|
+
currentIndex: Int,
|
|
144
|
+
repeatMode: QueueSkipRepeatMode,
|
|
145
|
+
restartEnabled: Boolean,
|
|
146
|
+
positionMs: Long,
|
|
147
|
+
thresholdMs: Long = RESTART_THRESHOLD_MS
|
|
148
|
+
): SkipToPreviousAction {
|
|
149
|
+
if (trackCount <= 0 || currentIndex < 0) return SkipToPreviousAction.NoOp
|
|
150
|
+
val prev = computePrevious(trackCount, currentIndex, repeatMode)
|
|
151
|
+
if (!restartEnabled) {
|
|
152
|
+
return if (prev != null) SkipToPreviousAction.Previous(prev) else SkipToPreviousAction.NoOp
|
|
153
|
+
}
|
|
154
|
+
return if (prev != null && positionMs <= thresholdMs) {
|
|
155
|
+
SkipToPreviousAction.Previous(prev)
|
|
156
|
+
} else {
|
|
157
|
+
SkipToPreviousAction.RestartCurrent
|
|
158
|
+
}
|
|
159
|
+
}
|
|
113
160
|
}
|
|
@@ -201,7 +201,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
201
201
|
visualizationEnabled = null,
|
|
202
202
|
clampSeekToBuffered = null,
|
|
203
203
|
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null,
|
|
204
|
-
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null
|
|
204
|
+
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
@Volatile
|
|
@@ -2171,25 +2171,46 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2171
2171
|
}
|
|
2172
2172
|
|
|
2173
2173
|
/**
|
|
2174
|
-
*
|
|
2175
|
-
*
|
|
2176
|
-
*
|
|
2177
|
-
*
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2180
|
-
* not the policy.
|
|
2174
|
+
* Skip-previous, resolved by [QueueSkipArithmetic.resolveSkipToPrevious]
|
|
2175
|
+
* from `PlayerConfig.skipToPreviousBehavior`. With the default PREVIOUS this
|
|
2176
|
+
* goes to the previous track if there is one (OFF/TRACK don't wrap, QUEUE
|
|
2177
|
+
* wraps; TRACK navigates like OFF and repeat-one persists). With
|
|
2178
|
+
* RESTART_OR_PREVIOUS it restarts the current track (seek to 0) when past
|
|
2179
|
+
* the threshold, otherwise goes to the previous track.
|
|
2181
2180
|
*/
|
|
2182
2181
|
@VisibleForTesting
|
|
2183
2182
|
internal fun skipToPreviousInternal() {
|
|
2184
2183
|
val p = player ?: return
|
|
2185
|
-
val
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2184
|
+
val action = QueueSkipArithmetic.resolveSkipToPrevious(
|
|
2185
|
+
trackCount = tracks.size,
|
|
2186
|
+
currentIndex = currentTrackIndex,
|
|
2187
|
+
repeatMode = repeatModeAsSkipMode(),
|
|
2188
|
+
restartEnabled = skipRestartEnabled(),
|
|
2189
|
+
positionMs = serviceBinder?.engine?.currentPositionMs ?: 0L
|
|
2190
|
+
)
|
|
2191
|
+
when (action) {
|
|
2192
|
+
is QueueSkipArithmetic.SkipToPreviousAction.Previous -> {
|
|
2193
|
+
val prev = action.index
|
|
2194
|
+
currentTrackIndex = prev
|
|
2195
|
+
pendingTrackChangeReason = TrackChangeReason.USER_SKIP_PREVIOUS
|
|
2196
|
+
if (prev < p.mediaItemCount) {
|
|
2197
|
+
serviceBinder?.engine?.seekTo(prev, 0L)
|
|
2198
|
+
}
|
|
2199
|
+
rescheduleLookahead()
|
|
2200
|
+
recomputeCapabilities()
|
|
2201
|
+
}
|
|
2202
|
+
QueueSkipArithmetic.SkipToPreviousAction.RestartCurrent -> {
|
|
2203
|
+
// Restart the current track from 0 — a seek, not a track change.
|
|
2204
|
+
// A same-index seek emits no onMediaItemTransition, so no
|
|
2205
|
+
// onTrackChange; leave currentTrackIndex + pendingTrackChangeReason
|
|
2206
|
+
// untouched (stashing a reason no transition consumes would leak
|
|
2207
|
+
// into the next real skip).
|
|
2208
|
+
if (currentTrackIndex in 0 until p.mediaItemCount) {
|
|
2209
|
+
serviceBinder?.engine?.seekTo(currentTrackIndex, 0L)
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
QueueSkipArithmetic.SkipToPreviousAction.NoOp -> return
|
|
2190
2213
|
}
|
|
2191
|
-
rescheduleLookahead()
|
|
2192
|
-
recomputeCapabilities()
|
|
2193
2214
|
}
|
|
2194
2215
|
|
|
2195
2216
|
/**
|
|
@@ -2221,6 +2242,14 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2221
2242
|
RepeatMode.QUEUE -> QueueSkipRepeatMode.QUEUE
|
|
2222
2243
|
}
|
|
2223
2244
|
|
|
2245
|
+
/**
|
|
2246
|
+
* Whether skip-to-previous restarts the current track past the threshold
|
|
2247
|
+
* (`PlayerConfig.skipToPreviousBehavior == RESTART_OR_PREVIOUS`). Read live
|
|
2248
|
+
* from the current config at each skip + capability recompute.
|
|
2249
|
+
*/
|
|
2250
|
+
private fun skipRestartEnabled(): Boolean =
|
|
2251
|
+
config.skipToPreviousBehavior == SkipToPreviousBehavior.RESTART_OR_PREVIOUS
|
|
2252
|
+
|
|
2224
2253
|
/**
|
|
2225
2254
|
* Recompute [cachedSkipCapability] from
|
|
2226
2255
|
* authoritative state and fire [onSkipCapabilityChange] if either
|
|
@@ -2245,7 +2274,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2245
2274
|
val mode = repeatModeAsSkipMode()
|
|
2246
2275
|
val count = tracks.size
|
|
2247
2276
|
val cur = currentTrackIndex
|
|
2248
|
-
val newPrev = QueueSkipArithmetic.canSkipPrevious(count, cur, mode)
|
|
2277
|
+
val newPrev = QueueSkipArithmetic.canSkipPrevious(count, cur, mode, skipRestartEnabled())
|
|
2249
2278
|
val newNext = QueueSkipArithmetic.canSkipNext(count, cur, mode)
|
|
2250
2279
|
val current = cachedSkipCapability
|
|
2251
2280
|
val capChanged =
|
package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceArtworkLoaderTest.kt
CHANGED
|
@@ -3,7 +3,9 @@ package com.margelo.nitro.queueplayer
|
|
|
3
3
|
import androidx.media3.common.util.UnstableApi
|
|
4
4
|
import androidx.media3.datasource.DataSourceBitmapLoader
|
|
5
5
|
import androidx.media3.session.CacheBitmapLoader
|
|
6
|
+
import androidx.media3.session.MediaSession
|
|
6
7
|
import org.junit.After
|
|
8
|
+
import org.junit.Assert.assertEquals
|
|
7
9
|
import org.junit.Assert.assertNotNull
|
|
8
10
|
import org.junit.Assert.assertSame
|
|
9
11
|
import org.junit.Assert.assertTrue
|
|
@@ -78,6 +80,32 @@ class PlaybackServiceArtworkLoaderTest {
|
|
|
78
80
|
)
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
@Test
|
|
84
|
+
fun `DataSourceBitmapLoader mirrors Media3's default cap + shared-memory bitmap`() {
|
|
85
|
+
val loader = service.buildBitmapLoader()
|
|
86
|
+
val cached = loader.javaClass.getDeclaredField("delegate")
|
|
87
|
+
.apply { isAccessible = true }.get(loader) as CacheBitmapLoader
|
|
88
|
+
val backing = cached.javaClass.getDeclaredField("bitmapLoader")
|
|
89
|
+
.apply { isAccessible = true }.get(cached) as DataSourceBitmapLoader
|
|
90
|
+
val maxDim = DataSourceBitmapLoader::class.java
|
|
91
|
+
.getDeclaredField("maximumOutputDimension")
|
|
92
|
+
.apply { isAccessible = true }
|
|
93
|
+
.getInt(backing)
|
|
94
|
+
assertEquals(
|
|
95
|
+
"artwork decode is capped to Media3's device-adaptive limit (no full-res allocation)",
|
|
96
|
+
MediaSession.getBitmapDimensionLimit(service),
|
|
97
|
+
maxDim
|
|
98
|
+
)
|
|
99
|
+
val makeShared = DataSourceBitmapLoader::class.java
|
|
100
|
+
.getDeclaredField("makeShared")
|
|
101
|
+
.apply { isAccessible = true }
|
|
102
|
+
.getBoolean(backing)
|
|
103
|
+
assertTrue(
|
|
104
|
+
"decoded bitmap is shared-memory so the System UI process reuses it instead of copying",
|
|
105
|
+
makeShared
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
81
109
|
@Test
|
|
82
110
|
fun `repeat builds reuse the same placeholder bitmap`() {
|
|
83
111
|
// The loader holds a placeholder *provider* closure; invoking it
|
|
@@ -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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = 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, placeholderArtworkUri = null
|
|
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, placeholderArtworkUri = 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, placeholderArtworkUri = 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
|
|