react-native-queue-player 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/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/TrackPlayer.kt +9 -5
- 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/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 +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +1 -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/Tests/AVQueueBuilderTests.swift +9 -9
- package/ios/Tests/CastNowPlayingControllerTests.swift +33 -0
- package/ios/Tests/PlaceholderArtworkTests.swift +81 -0
- package/ios/TrackPlayer.swift +5 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/types.d.ts +17 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/package.json +1 -1
- package/src/types.ts +17 -0
|
@@ -57,7 +57,8 @@ object MediaItemBuilder {
|
|
|
57
57
|
fun buildMediaItems(
|
|
58
58
|
context: Context,
|
|
59
59
|
tracks: List<TrackItem>,
|
|
60
|
-
lookaheadCache: LookaheadCache? = null
|
|
60
|
+
lookaheadCache: LookaheadCache? = null,
|
|
61
|
+
customPlaceholderUri: String? = null
|
|
61
62
|
): List<MediaItem> {
|
|
62
63
|
return tracks.map { track ->
|
|
63
64
|
val builder = MediaItem.Builder()
|
|
@@ -68,7 +69,7 @@ object MediaItemBuilder {
|
|
|
68
69
|
// Media3's documented default when track.id is omitted.
|
|
69
70
|
.setMediaId(track.id ?: "")
|
|
70
71
|
.setUri(Uri.parse(track.url))
|
|
71
|
-
.setMediaMetadata(buildMetadata(context, track))
|
|
72
|
+
.setMediaMetadata(buildMetadata(context, track, customPlaceholderUri))
|
|
72
73
|
val mime = resolveMimeTypeForBuild(track.url, lookaheadCache)
|
|
73
74
|
if (mime != null) builder.setMimeType(mime)
|
|
74
75
|
builder.build()
|
|
@@ -86,13 +87,26 @@ object MediaItemBuilder {
|
|
|
86
87
|
* so Media3's BitmapLoader resolves it once per process rather than
|
|
87
88
|
* cloning a byte[] into every MediaMetadata.
|
|
88
89
|
*/
|
|
89
|
-
private fun buildMetadata(
|
|
90
|
+
private fun buildMetadata(
|
|
91
|
+
context: Context,
|
|
92
|
+
track: TrackItem,
|
|
93
|
+
customPlaceholderUri: String?
|
|
94
|
+
): MediaMetadata {
|
|
90
95
|
val builder = MediaMetadata.Builder()
|
|
91
96
|
.setTitle(track.title.orEmpty())
|
|
92
97
|
.setArtist(track.artist.orEmpty())
|
|
93
98
|
.setAlbumTitle(track.album.orEmpty())
|
|
94
99
|
|
|
95
100
|
val artworkUrl = track.artworkUrl?.trim()
|
|
101
|
+
// Only a local `file://` placeholder is honoured (the built-in is a
|
|
102
|
+
// local file; a remote URL would break offline + change behaviour).
|
|
103
|
+
// Scheme match is case-insensitive to mirror iOS's `URL.isFileURL`;
|
|
104
|
+
// normalize to a lowercase `file` scheme so the case-sensitive
|
|
105
|
+
// `content://` rewrite below applies to an uppercase `FILE://` too.
|
|
106
|
+
val customFileUri = customPlaceholderUri
|
|
107
|
+
?.let { Uri.parse(it) }
|
|
108
|
+
?.takeIf { it.scheme.equals("file", ignoreCase = true) }
|
|
109
|
+
?.buildUpon()?.scheme("file")?.build()
|
|
96
110
|
val artworkUri = if (!artworkUrl.isNullOrEmpty()) {
|
|
97
111
|
// Rewrite private `file://` paths through the lib's exported
|
|
98
112
|
// `ArtworkContentProvider` so cross-process controllers (Android
|
|
@@ -100,6 +114,10 @@ object MediaItemBuilder {
|
|
|
100
114
|
// their own process. `http(s)://` and `content://` URIs pass
|
|
101
115
|
// through unchanged.
|
|
102
116
|
ArtworkContentProvider.mapFileUri(Uri.parse(artworkUrl))
|
|
117
|
+
} else if (customFileUri != null) {
|
|
118
|
+
// Consumer-supplied placeholder in place of the built-in — same
|
|
119
|
+
// cross-process file-URI rewrite as the real-art branch.
|
|
120
|
+
ArtworkContentProvider.mapFileUri(customFileUri)
|
|
103
121
|
} else {
|
|
104
122
|
placeholderArtworkUri(context)
|
|
105
123
|
}
|
|
@@ -3,6 +3,7 @@ package com.margelo.nitro.queueplayer
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Bitmap
|
|
5
5
|
import android.graphics.BitmapFactory
|
|
6
|
+
import android.net.Uri
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Bundled fallback artwork used by the MediaSession + notification
|
|
@@ -26,8 +27,10 @@ internal object PlaceholderArtwork {
|
|
|
26
27
|
* to call from any thread (the bitmap is immutable once decoded).
|
|
27
28
|
*/
|
|
28
29
|
fun bitmap(context: Context): Bitmap {
|
|
30
|
+
customBitmap?.let { return it }
|
|
29
31
|
cached?.let { return it }
|
|
30
32
|
synchronized(this) {
|
|
33
|
+
customBitmap?.let { return it }
|
|
31
34
|
cached?.let { return it }
|
|
32
35
|
val decoded = decode(context)
|
|
33
36
|
cached = decoded
|
|
@@ -35,6 +38,32 @@ internal object PlaceholderArtwork {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Set (or clear) the consumer-supplied placeholder from
|
|
43
|
+
* `PlayerConfig.placeholderArtworkUri`. Decoded once from the local
|
|
44
|
+
* `file://` path via `BitmapFactory.decodeFile` (scheme match is
|
|
45
|
+
* case-insensitive); a non-`file://` URI, an unset URI, or a decode
|
|
46
|
+
* failure leaves the bundled placeholder as the floor. Decode is
|
|
47
|
+
* synchronous, so a very large consumer image briefly blocks the
|
|
48
|
+
* `configure` call. Called from configure.
|
|
49
|
+
*/
|
|
50
|
+
fun setCustom(uri: String?) {
|
|
51
|
+
synchronized(this) {
|
|
52
|
+
// Local file:// only — remote URLs would break offline + change
|
|
53
|
+
// behaviour, so anything else leaves the built-in as the floor.
|
|
54
|
+
customBitmap = uri
|
|
55
|
+
?.takeIf { Uri.parse(it).scheme.equals("file", ignoreCase = true) }
|
|
56
|
+
?.let { decodeUri(it) }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun decodeUri(uri: String): Bitmap? =
|
|
61
|
+
try {
|
|
62
|
+
Uri.parse(uri).path?.let { BitmapFactory.decodeFile(it) }
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
null
|
|
65
|
+
}
|
|
66
|
+
|
|
38
67
|
/**
|
|
39
68
|
* Returns the placeholder bytes (raw JPEG payload). Useful for
|
|
40
69
|
* paths that want to write the bytes into a `MediaMetadata`
|
|
@@ -53,6 +82,7 @@ internal object PlaceholderArtwork {
|
|
|
53
82
|
|
|
54
83
|
@Volatile private var cached: Bitmap? = null
|
|
55
84
|
@Volatile private var cachedBytes: ByteArray? = null
|
|
85
|
+
@Volatile private var customBitmap: Bitmap? = null
|
|
56
86
|
|
|
57
87
|
private fun decode(context: Context): Bitmap {
|
|
58
88
|
val stream = context.resources.openRawResource(R.raw.rnqp_placeholder)
|
package/android/src/main/java/com/margelo/nitro/queueplayer/PlaceholderFallbackBitmapLoader.kt
CHANGED
|
@@ -37,7 +37,10 @@ import com.google.common.util.concurrent.MoreExecutors
|
|
|
37
37
|
@UnstableApi
|
|
38
38
|
internal class PlaceholderFallbackBitmapLoader(
|
|
39
39
|
private val delegate: BitmapLoader,
|
|
40
|
-
|
|
40
|
+
// Read on each fallback (not captured at construction) so a placeholder
|
|
41
|
+
// swapped at `configure` — after the session/loader was built in
|
|
42
|
+
// `onCreate` — is reflected without rebuilding the loader.
|
|
43
|
+
private val placeholder: () -> Bitmap,
|
|
41
44
|
) : BitmapLoader {
|
|
42
45
|
|
|
43
46
|
override fun supportsMimeType(mimeType: String): Boolean =
|
|
@@ -47,7 +50,7 @@ internal class PlaceholderFallbackBitmapLoader(
|
|
|
47
50
|
Futures.catching(
|
|
48
51
|
delegate.decodeBitmap(data),
|
|
49
52
|
Throwable::class.java,
|
|
50
|
-
{ placeholder },
|
|
53
|
+
{ placeholder() },
|
|
51
54
|
MoreExecutors.directExecutor(),
|
|
52
55
|
)
|
|
53
56
|
|
|
@@ -55,20 +58,20 @@ internal class PlaceholderFallbackBitmapLoader(
|
|
|
55
58
|
Futures.catching(
|
|
56
59
|
delegate.loadBitmap(uri),
|
|
57
60
|
Throwable::class.java,
|
|
58
|
-
{ placeholder },
|
|
61
|
+
{ placeholder() },
|
|
59
62
|
MoreExecutors.directExecutor(),
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
override fun loadBitmapFromMetadata(metadata: MediaMetadata): ListenableFuture<Bitmap>? {
|
|
63
66
|
if (metadata.artworkUri == null && metadata.artworkData == null) {
|
|
64
|
-
return Futures.immediateFuture(placeholder)
|
|
67
|
+
return Futures.immediateFuture(placeholder())
|
|
65
68
|
}
|
|
66
69
|
val delegated = delegate.loadBitmapFromMetadata(metadata)
|
|
67
|
-
?: return Futures.immediateFuture(placeholder)
|
|
70
|
+
?: return Futures.immediateFuture(placeholder())
|
|
68
71
|
return Futures.catching(
|
|
69
72
|
delegated,
|
|
70
73
|
Throwable::class.java,
|
|
71
|
-
{ placeholder },
|
|
74
|
+
{ placeholder() },
|
|
72
75
|
MoreExecutors.directExecutor(),
|
|
73
76
|
)
|
|
74
77
|
}
|
|
@@ -777,7 +777,10 @@ class PlaybackService : HeadlessJsMediaService() {
|
|
|
777
777
|
internal fun buildBitmapLoader(): PlaceholderFallbackBitmapLoader =
|
|
778
778
|
PlaceholderFallbackBitmapLoader(
|
|
779
779
|
delegate = CacheBitmapLoader(DataSourceBitmapLoader.Builder(this).build()),
|
|
780
|
-
placeholder
|
|
780
|
+
// Read on each fallback so a placeholder set at `configure` (after
|
|
781
|
+
// this loader was built in onCreate) is reflected — `PlaceholderArtwork`
|
|
782
|
+
// returns the consumer image when one is configured, else the built-in.
|
|
783
|
+
placeholder = { PlaceholderArtwork.bitmap(applicationContext) },
|
|
781
784
|
)
|
|
782
785
|
|
|
783
786
|
/**
|
|
@@ -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
|
|
204
|
+
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
@Volatile
|
|
@@ -880,6 +880,10 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
880
880
|
// shared instance is released and its directory deleted below.
|
|
881
881
|
val previousEvictionPolicy = evictionPolicyFrom(this.config)
|
|
882
882
|
this.config = newConfig
|
|
883
|
+
// Swap the cover-art placeholder bitmap to the consumer-supplied image
|
|
884
|
+
// (or back to the built-in when unset). The no-art metadata URI is
|
|
885
|
+
// threaded separately at each buildMediaItems call.
|
|
886
|
+
PlaceholderArtwork.setCustom(newConfig.placeholderArtworkUri)
|
|
883
887
|
if (evictionPolicyFrom(newConfig) != previousEvictionPolicy) {
|
|
884
888
|
lookaheadCacheWriter?.release()
|
|
885
889
|
lookaheadCacheWriter = null
|
|
@@ -1480,7 +1484,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1480
1484
|
val attempts = effectiveAutoRetries()
|
|
1481
1485
|
retryAttemptsRemaining =
|
|
1482
1486
|
(newTracks.indices.associateWith { attempts }).toMutableMap()
|
|
1483
|
-
return MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
1487
|
+
return MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
1484
1488
|
}
|
|
1485
1489
|
|
|
1486
1490
|
/**
|
|
@@ -1578,7 +1582,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1578
1582
|
for (newIdx in at until at + shift) {
|
|
1579
1583
|
retryAttemptsRemaining[newIdx] = attempts
|
|
1580
1584
|
}
|
|
1581
|
-
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
1585
|
+
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
1582
1586
|
serviceBinder?.engine?.addMediaItems(items, insertBefore = at)
|
|
1583
1587
|
// Diverges from iOS: add-into-empty-queue on iOS leaves the
|
|
1584
1588
|
// AVQueuePlayer empty until the consumer calls skipToIndex(0).
|
|
@@ -2404,7 +2408,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2404
2408
|
// playback continues from the current track / position. The
|
|
2405
2409
|
// post-swap engine starts empty by construction.
|
|
2406
2410
|
if (resumeIndex >= 0 && resumeIndex < tracks.count()) {
|
|
2407
|
-
val media = MediaItemBuilder.buildMediaItems(service, tracks, lookaheadCache)
|
|
2411
|
+
val media = MediaItemBuilder.buildMediaItems(service, tracks, lookaheadCache, config.placeholderArtworkUri)
|
|
2408
2412
|
newEngine.setMediaItems(
|
|
2409
2413
|
items = media,
|
|
2410
2414
|
startIndex = resumeIndex,
|
|
@@ -2806,7 +2810,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2806
2810
|
retryAttemptsRemaining.clear()
|
|
2807
2811
|
for (i in newTracks.indices) retryAttemptsRemaining[i] = attempts
|
|
2808
2812
|
|
|
2809
|
-
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
2813
|
+
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
2810
2814
|
serviceBinder?.engine?.setMediaItems(items, startIndex = 0, startPositionMs = 0L)
|
|
2811
2815
|
p.prepare()
|
|
2812
2816
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(newTracks))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.margelo.nitro.queueplayer
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.net.Uri
|
|
4
5
|
import androidx.media3.common.C
|
|
5
6
|
import androidx.media3.common.MediaMetadata
|
|
6
7
|
import androidx.media3.common.util.UnstableApi
|
|
@@ -277,6 +278,60 @@ class MediaItemBuilderTest {
|
|
|
277
278
|
assertEquals(expected, metadata.artworkUri?.toString())
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
@Test
|
|
282
|
+
fun `MediaMetadata uses the custom placeholder URI when artworkUrl is null and a file URI is configured`() {
|
|
283
|
+
val customUri = "file:///data/user/0/com.example/files/logo.png"
|
|
284
|
+
val metadata = MediaItemBuilder.buildMediaItems(
|
|
285
|
+
context,
|
|
286
|
+
listOf(makeTrack(id = "t", url = "https://example.com/s.mp3")),
|
|
287
|
+
customPlaceholderUri = customUri
|
|
288
|
+
)[0].mediaMetadata
|
|
289
|
+
val expected = ArtworkContentProvider.mapFileUri(Uri.parse(customUri)).toString()
|
|
290
|
+
assertEquals(expected, metadata.artworkUri?.toString())
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
@Test
|
|
294
|
+
fun `MediaMetadata ignores a non-file custom placeholder URI and uses the built-in`() {
|
|
295
|
+
val metadata = MediaItemBuilder.buildMediaItems(
|
|
296
|
+
context,
|
|
297
|
+
listOf(makeTrack(id = "t", url = "https://example.com/s.mp3")),
|
|
298
|
+
customPlaceholderUri = "https://example.com/logo.png"
|
|
299
|
+
)[0].mediaMetadata
|
|
300
|
+
val expected =
|
|
301
|
+
"android.resource://${context.packageName}/${R.raw.rnqp_placeholder}"
|
|
302
|
+
assertEquals(expected, metadata.artworkUri?.toString())
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@Test
|
|
306
|
+
fun `MediaMetadata prefers the real artworkUrl over a configured custom placeholder`() {
|
|
307
|
+
val metadata = MediaItemBuilder.buildMediaItems(
|
|
308
|
+
context,
|
|
309
|
+
listOf(
|
|
310
|
+
makeTrack(
|
|
311
|
+
id = "t",
|
|
312
|
+
url = "https://example.com/s.mp3",
|
|
313
|
+
artworkUrl = "https://example.com/cover.jpg"
|
|
314
|
+
)
|
|
315
|
+
),
|
|
316
|
+
customPlaceholderUri = "file:///data/user/0/com.example/files/logo.png"
|
|
317
|
+
)[0].mediaMetadata
|
|
318
|
+
assertEquals("https://example.com/cover.jpg", metadata.artworkUri?.toString())
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
@Test
|
|
322
|
+
fun `MediaMetadata normalizes an uppercase FILE scheme custom placeholder to lowercase file`() {
|
|
323
|
+
val customUri = "FILE:///data/user/0/com.example/files/logo.png"
|
|
324
|
+
val metadata = MediaItemBuilder.buildMediaItems(
|
|
325
|
+
context,
|
|
326
|
+
listOf(makeTrack(id = "t", url = "https://example.com/s.mp3")),
|
|
327
|
+
customPlaceholderUri = customUri
|
|
328
|
+
)[0].mediaMetadata
|
|
329
|
+
val normalized = Uri.parse(customUri).buildUpon().scheme("file").build()
|
|
330
|
+
val expected = ArtworkContentProvider.mapFileUri(normalized).toString()
|
|
331
|
+
assertEquals(expected, metadata.artworkUri?.toString())
|
|
332
|
+
assertEquals("file", metadata.artworkUri?.scheme)
|
|
333
|
+
}
|
|
334
|
+
|
|
280
335
|
@Test
|
|
281
336
|
fun `MediaMetadata sets durationMs from positive seconds`() {
|
|
282
337
|
val track = makeTrack(
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import androidx.test.core.app.ApplicationProvider
|
|
6
|
+
import org.junit.After
|
|
7
|
+
import org.junit.Assert.assertNotNull
|
|
8
|
+
import org.junit.Assert.assertNull
|
|
9
|
+
import org.junit.Assert.assertSame
|
|
10
|
+
import org.junit.Test
|
|
11
|
+
import org.junit.runner.RunWith
|
|
12
|
+
import org.robolectric.RobolectricTestRunner
|
|
13
|
+
import org.robolectric.annotation.Config
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Coverage for [PlaceholderArtwork.setCustom] — the consumer-supplied
|
|
17
|
+
* cover-art placeholder from `PlayerConfig.placeholderArtworkUri`. Only a
|
|
18
|
+
* local `file://` URI is honoured; a remote or unset URI leaves the
|
|
19
|
+
* bundled placeholder as the floor.
|
|
20
|
+
*/
|
|
21
|
+
@RunWith(RobolectricTestRunner::class)
|
|
22
|
+
@Config(sdk = [34])
|
|
23
|
+
class PlaceholderArtworkTest {
|
|
24
|
+
|
|
25
|
+
private val context: Context
|
|
26
|
+
get() = ApplicationProvider.getApplicationContext()
|
|
27
|
+
|
|
28
|
+
@After
|
|
29
|
+
fun tearDown() {
|
|
30
|
+
// Process-wide singleton — reset so a set custom bitmap does not
|
|
31
|
+
// leak into other suites that read the built-in placeholder.
|
|
32
|
+
PlaceholderArtwork.setCustom(null)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
fun `setCustom with a file URI installs a custom bitmap that bitmap returns`() {
|
|
37
|
+
PlaceholderArtwork.setCustom("file:///data/user/0/com.example/files/logo.png")
|
|
38
|
+
val custom = customBitmap()
|
|
39
|
+
assertNotNull(custom)
|
|
40
|
+
assertSame(custom, PlaceholderArtwork.bitmap(context))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
fun `setCustom ignores a non-file URI and leaves the built-in placeholder`() {
|
|
45
|
+
PlaceholderArtwork.setCustom("https://example.com/logo.png")
|
|
46
|
+
assertNull(customBitmap())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Test
|
|
50
|
+
fun `setCustom ignores an empty string and leaves the built-in placeholder`() {
|
|
51
|
+
PlaceholderArtwork.setCustom("")
|
|
52
|
+
assertNull(customBitmap())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun `setCustom accepts an uppercase FILE scheme (case-insensitive)`() {
|
|
57
|
+
PlaceholderArtwork.setCustom("FILE:///data/user/0/com.example/files/logo.png")
|
|
58
|
+
assertNotNull(customBitmap())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
fun `setCustom with null clears a previously-installed custom bitmap`() {
|
|
63
|
+
PlaceholderArtwork.setCustom("file:///data/user/0/com.example/files/logo.png")
|
|
64
|
+
PlaceholderArtwork.setCustom(null)
|
|
65
|
+
assertNull(customBitmap())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun customBitmap(): Bitmap? =
|
|
69
|
+
PlaceholderArtwork.javaClass.getDeclaredField("customBitmap")
|
|
70
|
+
.apply { isAccessible = true }
|
|
71
|
+
.get(PlaceholderArtwork) as Bitmap?
|
|
72
|
+
}
|
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,
|
|
789
789
|
),
|
|
790
790
|
context,
|
|
791
791
|
)
|
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,
|
|
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), 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), 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), 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), 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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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), 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)
|
|
193
193
|
|
|
194
194
|
player.configureInternal(cfg, context())
|
|
195
195
|
val first = player.player
|