react-native-queue-player 1.0.7 → 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 +48 -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 +180 -0
- 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/AVQueueBuilder.swift +29 -0
- package/ios/Cast/Core/CastNowPlayingController.swift +7 -4
- package/ios/NowPlayingInfo.swift +7 -9
- package/ios/PlaceholderArtwork.swift +24 -1
- package/ios/QueueState.swift +14 -2
- package/ios/Tests/AVQueueBuilderTests.swift +83 -9
- package/ios/Tests/CastNowPlayingControllerTests.swift +33 -0
- package/ios/Tests/PlaceholderArtworkTests.swift +81 -0
- package/ios/Tests/QueueStateTests.swift +50 -0
- package/ios/TrackPlayer.swift +126 -15
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useQueue.js +57 -0
- package/lib/module/hooks/useQueue.js.map +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js +7 -0
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +27 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +2 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +20 -0
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +22 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +101 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +18 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeReason.hpp +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_TrackItem__double_QueueChangeReason.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +9 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeReason.kt +27 -0
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +25 -0
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +11 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +26 -0
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeReason.swift +56 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeReason.hpp +92 -0
- package/package.json +1 -1
- package/src/TrackPlayer.nitro.ts +33 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useQueue.ts +56 -0
- package/src/index.ts +1 -0
- package/src/types.ts +29 -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
|
|
@@ -544,6 +544,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
544
544
|
// simultaneously without clobbering each other.
|
|
545
545
|
private val stateChangeListeners = ListenerRegistry<(PlayerState, StateChangeReason) -> Unit>()
|
|
546
546
|
private val trackChangeListeners = ListenerRegistry<(TrackItem?, Double, TrackChangeReason, Double?) -> Unit>()
|
|
547
|
+
private val queueChangeListeners = ListenerRegistry<(Array<TrackItem>, Double, QueueChangeReason) -> Unit>()
|
|
547
548
|
private val progressListeners = ListenerRegistry<(PlayerProgress) -> Unit>()
|
|
548
549
|
// Buffer state — discrete empty/buffering/stalled/full for the active track, derived
|
|
549
550
|
// from Media3's playback state + buffered-ahead. Recomputed on the engine
|
|
@@ -879,6 +880,10 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
879
880
|
// shared instance is released and its directory deleted below.
|
|
880
881
|
val previousEvictionPolicy = evictionPolicyFrom(this.config)
|
|
881
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)
|
|
882
887
|
if (evictionPolicyFrom(newConfig) != previousEvictionPolicy) {
|
|
883
888
|
lookaheadCacheWriter?.release()
|
|
884
889
|
lookaheadCacheWriter = null
|
|
@@ -1445,6 +1450,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1445
1450
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(newTracks))
|
|
1446
1451
|
rescheduleLookahead()
|
|
1447
1452
|
recomputeCapabilities()
|
|
1453
|
+
emitQueueChange(QueueChangeReason.SET_QUEUE)
|
|
1448
1454
|
}
|
|
1449
1455
|
|
|
1450
1456
|
/**
|
|
@@ -1478,7 +1484,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1478
1484
|
val attempts = effectiveAutoRetries()
|
|
1479
1485
|
retryAttemptsRemaining =
|
|
1480
1486
|
(newTracks.indices.associateWith { attempts }).toMutableMap()
|
|
1481
|
-
return MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
1487
|
+
return MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
1482
1488
|
}
|
|
1483
1489
|
|
|
1484
1490
|
/**
|
|
@@ -1503,6 +1509,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1503
1509
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(newTracks))
|
|
1504
1510
|
rescheduleLookahead()
|
|
1505
1511
|
recomputeCapabilities()
|
|
1512
|
+
emitQueueChange(QueueChangeReason.SET_QUEUE)
|
|
1506
1513
|
return items
|
|
1507
1514
|
}
|
|
1508
1515
|
|
|
@@ -1575,7 +1582,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1575
1582
|
for (newIdx in at until at + shift) {
|
|
1576
1583
|
retryAttemptsRemaining[newIdx] = attempts
|
|
1577
1584
|
}
|
|
1578
|
-
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
1585
|
+
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
1579
1586
|
serviceBinder?.engine?.addMediaItems(items, insertBefore = at)
|
|
1580
1587
|
// Diverges from iOS: add-into-empty-queue on iOS leaves the
|
|
1581
1588
|
// AVQueuePlayer empty until the consumer calls skipToIndex(0).
|
|
@@ -1595,6 +1602,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1595
1602
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(this.tracks))
|
|
1596
1603
|
rescheduleLookahead()
|
|
1597
1604
|
recomputeCapabilities()
|
|
1605
|
+
emitQueueChange(QueueChangeReason.ADD)
|
|
1598
1606
|
return at
|
|
1599
1607
|
}
|
|
1600
1608
|
|
|
@@ -1688,6 +1696,9 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1688
1696
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(this.tracks))
|
|
1689
1697
|
rescheduleLookahead()
|
|
1690
1698
|
recomputeCapabilities()
|
|
1699
|
+
// Reached only past the `sanitised.isEmpty()` guard, so ≥1 track was
|
|
1700
|
+
// removed; tracks + index are final.
|
|
1701
|
+
emitQueueChange(QueueChangeReason.REMOVE)
|
|
1691
1702
|
return sanitised
|
|
1692
1703
|
}
|
|
1693
1704
|
|
|
@@ -1753,6 +1764,9 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1753
1764
|
for (i in this.tracks.indices) retryAttemptsRemaining[i] = attempts
|
|
1754
1765
|
rescheduleLookahead()
|
|
1755
1766
|
recomputeCapabilities()
|
|
1767
|
+
// Reached only past the invalid / `from == to` / pinned-current
|
|
1768
|
+
// guards, so the order actually changed.
|
|
1769
|
+
emitQueueChange(QueueChangeReason.MOVE)
|
|
1756
1770
|
return true
|
|
1757
1771
|
}
|
|
1758
1772
|
|
|
@@ -1771,6 +1785,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1771
1785
|
@VisibleForTesting
|
|
1772
1786
|
internal fun clearQueueInternal() {
|
|
1773
1787
|
val p = player ?: return
|
|
1788
|
+
val queueWasNonEmpty = tracks.isNotEmpty()
|
|
1774
1789
|
// Reset authoritative state BEFORE the ExoPlayer mutation.
|
|
1775
1790
|
// `clearMediaItems()` fires `onMediaItemTransition(null,
|
|
1776
1791
|
// REASON_PLAYLIST_CHANGED)` synchronously, which routes through
|
|
@@ -1788,6 +1803,11 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
1788
1803
|
retryAttemptsRemaining.clear()
|
|
1789
1804
|
tracks = emptyList()
|
|
1790
1805
|
currentTrackIndex = -1
|
|
1806
|
+
// Fire queue-change BEFORE setMediaItems (which synchronously fires
|
|
1807
|
+
// onTrackChange via onMediaItemTransition) so a `useQueue()` consumer
|
|
1808
|
+
// sees the empty queue + index -1 together. Only when the queue was
|
|
1809
|
+
// actually non-empty — clearing an empty queue is a no-op.
|
|
1810
|
+
if (queueWasNonEmpty) emitQueueChange(QueueChangeReason.CLEAR)
|
|
1791
1811
|
// Route through the engine surface (not directly through the
|
|
1792
1812
|
// leading leg) so CrossfadeEngine can also tear down the
|
|
1793
1813
|
// standby leg's preroll'd items + cancel any in-flight fade.
|
|
@@ -2388,7 +2408,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2388
2408
|
// playback continues from the current track / position. The
|
|
2389
2409
|
// post-swap engine starts empty by construction.
|
|
2390
2410
|
if (resumeIndex >= 0 && resumeIndex < tracks.count()) {
|
|
2391
|
-
val media = MediaItemBuilder.buildMediaItems(service, tracks, lookaheadCache)
|
|
2411
|
+
val media = MediaItemBuilder.buildMediaItems(service, tracks, lookaheadCache, config.placeholderArtworkUri)
|
|
2392
2412
|
newEngine.setMediaItems(
|
|
2393
2413
|
items = media,
|
|
2394
2414
|
startIndex = resumeIndex,
|
|
@@ -2790,7 +2810,7 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2790
2810
|
retryAttemptsRemaining.clear()
|
|
2791
2811
|
for (i in newTracks.indices) retryAttemptsRemaining[i] = attempts
|
|
2792
2812
|
|
|
2793
|
-
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache)
|
|
2813
|
+
val items = MediaItemBuilder.buildMediaItems(ctx, newTracks, lookaheadCache, config.placeholderArtworkUri)
|
|
2794
2814
|
serviceBinder?.engine?.setMediaItems(items, startIndex = 0, startPositionMs = 0L)
|
|
2795
2815
|
p.prepare()
|
|
2796
2816
|
serviceBinder?.engine?.setWakeMode(pickWakeMode(newTracks))
|
|
@@ -2802,6 +2822,8 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2802
2822
|
|
|
2803
2823
|
rescheduleLookahead()
|
|
2804
2824
|
recomputeCapabilities()
|
|
2825
|
+
// Skip a single-track shuffle — the order can't change.
|
|
2826
|
+
if (newTracks.size > 1) emitQueueChange(QueueChangeReason.SHUFFLE)
|
|
2805
2827
|
|
|
2806
2828
|
return ShuffleResult(
|
|
2807
2829
|
tracks = newTracks.toTypedArray(),
|
|
@@ -2892,6 +2914,27 @@ class TrackPlayer : HybridTrackPlayerSpec(), PlaybackEngineDelegate {
|
|
|
2892
2914
|
return { trackChangeListeners.remove(id) }
|
|
2893
2915
|
}
|
|
2894
2916
|
|
|
2917
|
+
override fun onQueueChange(
|
|
2918
|
+
callback: (queue: Array<TrackItem>, currentIndex: Double, reason: QueueChangeReason) -> Unit
|
|
2919
|
+
): () -> Unit {
|
|
2920
|
+
val id = queueChangeListeners.add(callback)
|
|
2921
|
+
return { queueChangeListeners.remove(id) }
|
|
2922
|
+
}
|
|
2923
|
+
|
|
2924
|
+
/**
|
|
2925
|
+
* Fire onQueueChange with an atomic snapshot of the post-mutation
|
|
2926
|
+
* queue + current index. Main-thread only — every queue mutation
|
|
2927
|
+
* commits on main, so [tracks] / [currentTrackIndex] are settled here.
|
|
2928
|
+
* Callers gate this on an actual content/order change so a no-op
|
|
2929
|
+
* mutation never emits.
|
|
2930
|
+
*/
|
|
2931
|
+
@MainThread
|
|
2932
|
+
private fun emitQueueChange(reason: QueueChangeReason) {
|
|
2933
|
+
val snapshot = tracks.toTypedArray()
|
|
2934
|
+
val index = currentTrackIndex.toDouble()
|
|
2935
|
+
queueChangeListeners.forEach { it(snapshot, index, reason) }
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2895
2938
|
override fun onProgress(
|
|
2896
2939
|
callback: (progress: PlayerProgress) -> Unit
|
|
2897
2940
|
): () -> Unit {
|
|
@@ -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
|