react-native-queue-player 1.0.3 → 1.0.6
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/BufferStateTranslator.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CarConnectionState.kt +46 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +1 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +2 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +7 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/MilestoneTracker.kt +1 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/NotificationProvider.kt +6 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +33 -25
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +16 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/ProgressEmissionGate.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/RemoteCommandDeduper.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayer.kt +9 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/SleepTimerCore.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +40 -27
- package/android/src/main/java/com/margelo/nitro/queueplayer/TransportStateTranslator.kt +2 -2
- package/android/src/main/res/values/strings.xml +0 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/AudioPipelineRenderersFactoryTest.kt +2 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/BrowseCallbackRegistryTest.kt +1 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineReplayGainTest.kt +3 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +0 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +3 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/HeadlessJsMediaServiceTest.kt +2 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaButtonDispatchTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaButtonPreferencesTest.kt +99 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/MilestoneTrackerTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchCorrectionTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +148 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +2 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ProgressEmissionGateTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +58 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +2 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPitchCorrectionTest.kt +1 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPlaybackModeTest.kt +2 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReplayGainTest.kt +1 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +2 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +1 -2
- package/ios/AVPlayerItemQueueItemId.swift +2 -2
- package/ios/AVQueueBuilder.swift +12 -14
- package/ios/CarPlayCoordinator.swift +38 -0
- package/ios/CarPlayQueueBuilder.swift +82 -0
- package/ios/CarPlayQueueProvider.swift +73 -0
- package/ios/CarPlaySceneDelegate.swift +81 -7
- package/ios/CrossfadeEngine.swift +4 -5
- package/ios/InputGuards.swift +3 -3
- package/ios/LookaheadCache.swift +4 -6
- package/ios/LookaheadCachePrefetcher.swift +1 -1
- package/ios/MilestoneTracker.swift +1 -2
- package/ios/PlaybackErrorMapping.swift +6 -7
- package/ios/PlayerStateDerivation.swift +1 -2
- package/ios/ProgressEmissionGate.swift +1 -1
- package/ios/QueueMutationArithmetic.swift +2 -2
- package/ios/QueueSkipArithmetic.swift +8 -12
- package/ios/Siri/PlayMediaIntentHandler.swift +1 -5
- package/ios/SleepTimerCore.swift +1 -1
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +5 -9
- package/ios/Tests/CarPlayBridgeTests.swift +2 -6
- package/ios/Tests/CarPlayBrowseBuilderTests.swift +1 -2
- package/ios/Tests/CarPlayCoordinatorTests.swift +35 -0
- package/ios/Tests/CarPlayQueueBuilderTests.swift +145 -0
- package/ios/Tests/EQProcessorTests.swift +1 -4
- package/ios/Tests/EQTapTests.swift +1 -2
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +3 -4
- package/ios/Tests/EqualizerHybridTests.swift +1 -3
- package/ios/Tests/FFTProcessorTests.swift +10 -10
- package/ios/Tests/GaplessEngineLifecycleTests.swift +0 -2
- package/ios/Tests/LookaheadCacheEvictionTests.swift +1 -4
- package/ios/Tests/MetadataReaderTests.swift +0 -2
- package/ios/Tests/MilestoneTrackerTests.swift +1 -3
- package/ios/Tests/NowPlayingFormatExtractorTests.swift +3 -4
- package/ios/Tests/PlaybackErrorMappingTests.swift +0 -3
- package/ios/Tests/PlaybackModeStateMachineTests.swift +5 -9
- package/ios/Tests/PlayerStateDerivationTests.swift +1 -2
- package/ios/Tests/ProgressEmissionGateTests.swift +1 -3
- package/ios/Tests/QueueMutationArithmeticTests.swift +0 -3
- package/ios/Tests/RemoteCommandsTests.swift +2 -5
- package/ios/Tests/ReplayGainExtractorTests.swift +0 -5
- package/ios/Tests/SkipCapabilityTests.swift +4 -7
- package/ios/Tests/VoiceDonationTests.swift +4 -4
- package/ios/TrackPlayer.swift +52 -2
- package/lib/typescript/TrackPlayer.nitro.d.ts +17 -0
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +5 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +1 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +4 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +1 -0
- package/package.json +1 -1
- package/src/TrackPlayer.nitro.ts +18 -0
|
@@ -16,9 +16,7 @@ import org.robolectric.annotation.Config
|
|
|
16
16
|
/**
|
|
17
17
|
* Wiring coverage for the `Equalizer` HybridObject — engine
|
|
18
18
|
* selection, JS-facing surface routing through the synchronous
|
|
19
|
-
* `*Internal` core, listener registry behaviour.
|
|
20
|
-
* effect is verified at the integration layer via demo Maestro
|
|
21
|
-
* once the engine is hooked to ExoPlayer's audio session.
|
|
19
|
+
* `*Internal` core, listener registry behaviour.
|
|
22
20
|
*/
|
|
23
21
|
@RunWith(RobolectricTestRunner::class)
|
|
24
22
|
@Config(sdk = [34])
|
|
@@ -13,9 +13,8 @@ import org.robolectric.annotation.Config
|
|
|
13
13
|
* State-mirror coverage for [EqualizerLegacyEngine] on the API <28 path.
|
|
14
14
|
* Robolectric's audio-effect shadow doesn't model the hardware-side
|
|
15
15
|
* band centre frequencies + gain range; the band-mapping + dB↔mB
|
|
16
|
-
* conversion are the
|
|
17
|
-
*
|
|
18
|
-
* behaviour varies per OEM).
|
|
16
|
+
* conversion are the invariants this covers. Legacy AudioEffect
|
|
17
|
+
* behaviour varies per OEM.
|
|
19
18
|
*/
|
|
20
19
|
@RunWith(RobolectricTestRunner::class)
|
|
21
20
|
@Config(sdk = [27])
|
|
@@ -19,8 +19,7 @@ import kotlin.math.sin
|
|
|
19
19
|
* Unit coverage for [FFTProcessorTee] — buffer ingestion across PCM
|
|
20
20
|
* encodings, intervalMs throttling, includeSamples flag, and
|
|
21
21
|
* multi-channel mixdown. The Tee callback contract is mocked
|
|
22
|
-
* directly
|
|
23
|
-
* Maestro layer (no Robolectric harness for ExoPlayer audio output).
|
|
22
|
+
* directly.
|
|
24
23
|
*/
|
|
25
24
|
@RunWith(RobolectricTestRunner::class)
|
|
26
25
|
@Config(sdk = [34])
|
|
@@ -15,8 +15,6 @@ import org.robolectric.annotation.Config
|
|
|
15
15
|
/**
|
|
16
16
|
* Lifecycle smoke coverage for [GaplessEngine] — empty-state
|
|
17
17
|
* sentinels and the idempotent / delegate-clearing release contract.
|
|
18
|
-
* Behavioural coverage of transport / queue mutation runs through
|
|
19
|
-
* the existing TrackPlayer integration suites + Maestro flows.
|
|
20
18
|
*/
|
|
21
19
|
@RunWith(RobolectricTestRunner::class)
|
|
22
20
|
@Config(sdk = [34])
|
|
@@ -19,10 +19,8 @@ import org.robolectric.annotation.Config
|
|
|
19
19
|
* Real Media3 metadata dispatch is not emulated by Robolectric
|
|
20
20
|
* without playback, so the listener body composes its work through
|
|
21
21
|
* the engine-internal `handleReplayGainParse` helper which these
|
|
22
|
-
* tests drive directly with synthetic [ReplayGainData].
|
|
23
|
-
*
|
|
24
|
-
* end-to-end propagation lands in the demo app's `replaygain`
|
|
25
|
-
* Maestro suite. This file covers the no-data baseline,
|
|
22
|
+
* tests drive directly with synthetic [ReplayGainData].
|
|
23
|
+
* This file covers the no-data baseline,
|
|
26
24
|
* mode-change-without-data behaviour, and the
|
|
27
25
|
* `handleReplayGainParse` cache + recompute contract.
|
|
28
26
|
*/
|
|
@@ -147,8 +145,5 @@ class GaplessEngineReplayGainTest {
|
|
|
147
145
|
|
|
148
146
|
// The cache-clear-on-transition path lives inline in the
|
|
149
147
|
// `Player.Listener.onMediaItemTransition` body. Robolectric does not
|
|
150
|
-
// drive that callback without real playback
|
|
151
|
-
// covered at the integration layer by `RNQP-Demo/src/tests/suites/
|
|
152
|
-
// replaygain.ts` (steps `cross-track-active-updates` +
|
|
153
|
-
// `skip-next-mid-playback-resets-from-stale-cache`).
|
|
148
|
+
// drive that callback without real playback.
|
|
154
149
|
}
|
|
@@ -21,11 +21,9 @@ import org.robolectric.annotation.Config
|
|
|
21
21
|
* surface: the default no-task `getTaskConfig` contract and the
|
|
22
22
|
* process-wide wake-lock helper.
|
|
23
23
|
*
|
|
24
|
-
* The full `onStartCommand` chain
|
|
24
|
+
* The full `onStartCommand` chain routes through Media3's
|
|
25
25
|
* `MediaLibraryService.onStartCommand` and into native code that
|
|
26
|
-
* Robolectric can't satisfy
|
|
27
|
-
* [PlaybackServiceLifecycleTest] (which subclasses into the new
|
|
28
|
-
* `HeadlessJsMediaService` base) plus the Maestro demo cycle.
|
|
26
|
+
* Robolectric can't satisfy, so it is out of scope here.
|
|
29
27
|
*/
|
|
30
28
|
@RunWith(RobolectricTestRunner::class)
|
|
31
29
|
@Config(sdk = [34])
|
|
@@ -179,6 +179,7 @@ class MediaButtonDispatchTest {
|
|
|
179
179
|
override fun onTransportCommand() { transportCalls++ }
|
|
180
180
|
override fun onSkipToNextCommand(): Boolean { skipNextCmdCalls++; return true }
|
|
181
181
|
override fun onSkipToPreviousCommand(): Boolean { skipPreviousCmdCalls++; return true }
|
|
182
|
+
override fun onSeekToMediaItemCommand(mediaItemIndex: Int): Boolean = false
|
|
182
183
|
override fun getCachedSkipCapability(): SkipCapability =
|
|
183
184
|
SkipCapability(canSkipPrevious = true, canSkipNext = true)
|
|
184
185
|
override fun onMediaPlay() { playCalls++ }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import androidx.media3.common.Player
|
|
5
|
+
import androidx.media3.common.util.UnstableApi
|
|
6
|
+
import androidx.media3.session.CommandButton
|
|
7
|
+
import androidx.media3.session.MediaSession
|
|
8
|
+
import androidx.test.core.app.ApplicationProvider
|
|
9
|
+
import org.junit.After
|
|
10
|
+
import org.junit.Assert.assertFalse
|
|
11
|
+
import org.junit.Assert.assertTrue
|
|
12
|
+
import org.junit.Before
|
|
13
|
+
import org.junit.Test
|
|
14
|
+
import org.junit.runner.RunWith
|
|
15
|
+
import org.robolectric.Robolectric
|
|
16
|
+
import org.robolectric.RobolectricTestRunner
|
|
17
|
+
import org.robolectric.android.controller.ServiceController
|
|
18
|
+
import org.robolectric.annotation.Config
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Robolectric coverage for the media button preferences the notification
|
|
22
|
+
* compact view + Android Auto control row consume, produced by
|
|
23
|
+
* [PlaybackService.buildMediaButtonPreferences] and read back off the
|
|
24
|
+
* session's public `mediaButtonPreferences`.
|
|
25
|
+
*
|
|
26
|
+
* The lib supplies only the back / forward buttons. Play/pause belongs to
|
|
27
|
+
* Media3's central slot: `DefaultMediaNotificationProvider` synthesizes a
|
|
28
|
+
* state-aware play/pause from `COMMAND_PLAY_PAUSE`, and Android Auto fills
|
|
29
|
+
* the empty central slot the same way — so a lib-supplied `SLOT_CENTRAL`
|
|
30
|
+
* button is dropped on the notification and forces a static, non-toggling
|
|
31
|
+
* icon on Android Auto.
|
|
32
|
+
*/
|
|
33
|
+
@RunWith(RobolectricTestRunner::class)
|
|
34
|
+
@Config(sdk = [34])
|
|
35
|
+
@UnstableApi
|
|
36
|
+
class MediaButtonPreferencesTest {
|
|
37
|
+
|
|
38
|
+
private lateinit var controller: ServiceController<PlaybackService>
|
|
39
|
+
private lateinit var service: PlaybackService
|
|
40
|
+
private lateinit var session: MediaSession
|
|
41
|
+
|
|
42
|
+
@Before
|
|
43
|
+
fun setUp() {
|
|
44
|
+
controller = Robolectric.buildService(PlaybackService::class.java).create()
|
|
45
|
+
service = controller.get()
|
|
46
|
+
val intent = Intent(
|
|
47
|
+
ApplicationProvider.getApplicationContext<android.content.Context>(),
|
|
48
|
+
PlaybackService::class.java,
|
|
49
|
+
).setAction(PlaybackService.ACTION_LOCAL_BIND)
|
|
50
|
+
val binder = service.onBind(intent) as PlaybackService.LocalBinder
|
|
51
|
+
session = binder.session
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@After
|
|
55
|
+
fun tearDown() {
|
|
56
|
+
controller.destroy()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
fun `track mode supplies previous plus next and leaves play-pause to Media3`() {
|
|
61
|
+
service.applyRemoteControls(RemoteSkipMode.TRACK, 15_000L, 15_000L)
|
|
62
|
+
val prefs = session.mediaButtonPreferences
|
|
63
|
+
assertNoLibPlayPause(prefs)
|
|
64
|
+
assertTrue(
|
|
65
|
+
"previous occupies the back slot",
|
|
66
|
+
prefs.any { it.slots.contains(CommandButton.SLOT_BACK) },
|
|
67
|
+
)
|
|
68
|
+
assertTrue(
|
|
69
|
+
"next occupies the forward slot",
|
|
70
|
+
prefs.any { it.slots.contains(CommandButton.SLOT_FORWARD) },
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Test
|
|
75
|
+
fun `interval mode supplies skip-back plus skip-forward and leaves play-pause to Media3`() {
|
|
76
|
+
service.applyRemoteControls(RemoteSkipMode.INTERVAL, 15_000L, 10_000L)
|
|
77
|
+
val prefs = session.mediaButtonPreferences
|
|
78
|
+
assertNoLibPlayPause(prefs)
|
|
79
|
+
assertTrue(
|
|
80
|
+
"skip-back occupies the back slot",
|
|
81
|
+
prefs.any { it.slots.contains(CommandButton.SLOT_BACK) },
|
|
82
|
+
)
|
|
83
|
+
assertTrue(
|
|
84
|
+
"skip-forward occupies the forward slot",
|
|
85
|
+
prefs.any { it.slots.contains(CommandButton.SLOT_FORWARD) },
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private fun assertNoLibPlayPause(prefs: List<CommandButton>) {
|
|
90
|
+
assertFalse(
|
|
91
|
+
"play/pause is Media3's central-slot button, never supplied by the lib",
|
|
92
|
+
prefs.any { it.playerCommand == Player.COMMAND_PLAY_PAUSE },
|
|
93
|
+
)
|
|
94
|
+
assertFalse(
|
|
95
|
+
"the lib never claims SLOT_CENTRAL — Media3 owns it",
|
|
96
|
+
prefs.any { it.slots.contains(CommandButton.SLOT_CENTRAL) },
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -26,11 +26,10 @@ import org.robolectric.annotation.Config
|
|
|
26
26
|
* both produce items — the file/remote routing difference is a
|
|
27
27
|
* DataSource.Factory concern, not a MediaItem concern).
|
|
28
28
|
*
|
|
29
|
-
* Factory-building is
|
|
29
|
+
* Factory-building is checked by type only (the internal
|
|
30
30
|
* `DefaultHttpDataSource.Factory` is configured via a builder chain
|
|
31
31
|
* whose effective state is hard to read back without reflection —
|
|
32
|
-
* the factory is wired into ExoPlayer at construction time
|
|
33
|
-
* exercised implicitly through the TrackPlayer lifecycle tests).
|
|
32
|
+
* the factory is wired into ExoPlayer at construction time).
|
|
34
33
|
*
|
|
35
34
|
* @Config sdk=34 — Robolectric 4.11.1's max supported SDK.
|
|
36
35
|
*/
|
|
@@ -5,9 +5,7 @@ import org.junit.Test
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Unit tests for [MilestoneTracker] — the pure 25/50/75/90% crossing
|
|
8
|
-
* logic behind `onPlaybackMilestone`.
|
|
9
|
-
* seek, track-change reset) is covered by the `playback-milestones`
|
|
10
|
-
* demo suite.
|
|
8
|
+
* logic behind `onPlaybackMilestone`.
|
|
11
9
|
*/
|
|
12
10
|
class MilestoneTrackerTest {
|
|
13
11
|
|
package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt
CHANGED
|
@@ -7,8 +7,7 @@ import org.junit.Assert.assertEquals
|
|
|
7
7
|
import org.junit.Test
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Pure-mapping coverage for [NowPlayingFormatExtractor].
|
|
11
|
-
* `extract(player:)` path is exercised through demo suites; these
|
|
10
|
+
* Pure-mapping coverage for [NowPlayingFormatExtractor]. These
|
|
12
11
|
* tests pin the codec / container tables so the JS-visible enum can't
|
|
13
12
|
* drift without a test failure.
|
|
14
13
|
*/
|
package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt
CHANGED
|
@@ -17,7 +17,7 @@ import org.robolectric.annotation.Config
|
|
|
17
17
|
/**
|
|
18
18
|
* Routing coverage for [PitchAwareAudioProcessorChain]: which stretcher is
|
|
19
19
|
* actually stretching for each [PitchCorrectionMode] and speed. The native
|
|
20
|
-
* stretch itself
|
|
20
|
+
* stretch itself runs on-device; these assert only the mode gate, which
|
|
21
21
|
* runs without the native library.
|
|
22
22
|
*
|
|
23
23
|
* Note Signalsmith is intentionally ALWAYS active on the float path (so Media3's
|
|
@@ -10,8 +10,8 @@ import org.robolectric.annotation.Config
|
|
|
10
10
|
* Unit coverage for [PitchCorrection.playbackParameters], the pure mapping
|
|
11
11
|
* from a [PitchCorrectionMode] + speed to Media3 [androidx.media3.common.PlaybackParameters].
|
|
12
12
|
*
|
|
13
|
-
* Engine wiring (which player the params land on) is
|
|
14
|
-
*
|
|
13
|
+
* Engine wiring (which player the params land on) is out of scope for
|
|
14
|
+
* this pure test.
|
|
15
15
|
*/
|
|
16
16
|
@RunWith(RobolectricTestRunner::class)
|
|
17
17
|
@Config(sdk = [34])
|
|
@@ -70,6 +70,9 @@ class PlaybackServiceCallbackTest {
|
|
|
70
70
|
controllerInfo = MediaSession.ControllerInfo.createTestOnlyControllerInfo(
|
|
71
71
|
"test-controller", 0, 0, 0, 0, false, android.os.Bundle.EMPTY, false
|
|
72
72
|
)
|
|
73
|
+
// Process-wide count; reset so headless connect/disconnect assertions
|
|
74
|
+
// start from a known-disconnected state regardless of test order.
|
|
75
|
+
CarConnectionState.reset()
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
@After
|
|
@@ -87,8 +90,7 @@ class PlaybackServiceCallbackTest {
|
|
|
87
90
|
|
|
88
91
|
// Controller-driven `Player.Command` interception (skip-next /
|
|
89
92
|
// skip-previous veto, transport `system`-reason stamping) now lives
|
|
90
|
-
// on [SessionCommandForwardingPlayer]
|
|
91
|
-
// SessionCommandForwardingPlayerTest. This suite keeps the
|
|
93
|
+
// on [SessionCommandForwardingPlayer]. This suite keeps the
|
|
92
94
|
// callback's connect / resumption / available-commands surface.
|
|
93
95
|
|
|
94
96
|
@Test
|
|
@@ -270,9 +272,7 @@ class PlaybackServiceCallbackTest {
|
|
|
270
272
|
// The per-controller iteration body (session.setAvailableCommands)
|
|
271
273
|
// requires a live MediaController that has completed handshake,
|
|
272
274
|
// which is not reachable from Robolectric without standing up a
|
|
273
|
-
// full MediaController.Builder.buildAsync() flow.
|
|
274
|
-
// coverage of the lock-screen-greying behaviour lives in the
|
|
275
|
-
// Maestro suites for repeat-mode + queue-boundary scenarios.
|
|
275
|
+
// full MediaController.Builder.buildAsync() flow.
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
// --- TrackPlayer-side handler implementation
|
|
@@ -375,6 +375,76 @@ class PlaybackServiceCallbackTest {
|
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
@Test
|
|
379
|
+
fun `TrackPlayer onSeekToMediaItemCommand jumps to the tapped index and stamps reasons`() {
|
|
380
|
+
val player = newConfiguredPlayer()
|
|
381
|
+
try {
|
|
382
|
+
player.tracks = listOf(
|
|
383
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
384
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
385
|
+
TrackItem("c", "https://example.com/c.m4a", null, null, null, null, null, null),
|
|
386
|
+
)
|
|
387
|
+
player.currentTrackIndex = 0
|
|
388
|
+
|
|
389
|
+
val intercepted = player.mediaSessionCommandHandler.onSeekToMediaItemCommand(2)
|
|
390
|
+
|
|
391
|
+
assertTrue(
|
|
392
|
+
"an Android Auto queue-row tap is queue-aware and reports interception",
|
|
393
|
+
intercepted
|
|
394
|
+
)
|
|
395
|
+
assertEquals(2, player.currentTrackIndex)
|
|
396
|
+
assertEquals(StateChangeReason.SYSTEM, player.pendingStateChangeReason)
|
|
397
|
+
assertEquals(
|
|
398
|
+
TrackChangeReason.USER_SKIP_TO_INDEX, player.pendingTrackChangeReason
|
|
399
|
+
)
|
|
400
|
+
} finally {
|
|
401
|
+
tearDownPlayer(player)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
@Test
|
|
406
|
+
fun `TrackPlayer onSeekToMediaItemCommand declines an out-of-range index and leaves the current index`() {
|
|
407
|
+
val player = newConfiguredPlayer()
|
|
408
|
+
try {
|
|
409
|
+
player.tracks = listOf(
|
|
410
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
411
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
412
|
+
)
|
|
413
|
+
player.currentTrackIndex = 1
|
|
414
|
+
|
|
415
|
+
val intercepted = player.mediaSessionCommandHandler.onSeekToMediaItemCommand(5)
|
|
416
|
+
|
|
417
|
+
assertFalse(
|
|
418
|
+
"an out-of-range tap is declined so the default routing no-ops it",
|
|
419
|
+
intercepted
|
|
420
|
+
)
|
|
421
|
+
assertEquals(1, player.currentTrackIndex)
|
|
422
|
+
} finally {
|
|
423
|
+
tearDownPlayer(player)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@Test
|
|
428
|
+
fun `TrackPlayer onSeekToMediaItemCommand on the current index is handled and keeps the index`() {
|
|
429
|
+
val player = newConfiguredPlayer()
|
|
430
|
+
try {
|
|
431
|
+
player.tracks = listOf(
|
|
432
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
433
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
434
|
+
)
|
|
435
|
+
player.currentTrackIndex = 1
|
|
436
|
+
|
|
437
|
+
val intercepted = player.mediaSessionCommandHandler.onSeekToMediaItemCommand(1)
|
|
438
|
+
|
|
439
|
+
// Tapping the playing row behaves like skipToIndex(current): handled,
|
|
440
|
+
// index unchanged, the engine restarts the track from the top.
|
|
441
|
+
assertTrue(intercepted)
|
|
442
|
+
assertEquals(1, player.currentTrackIndex)
|
|
443
|
+
} finally {
|
|
444
|
+
tearDownPlayer(player)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
378
448
|
// --- Voice / browse-leaf routing through onAddMediaItems / onSetMediaItems
|
|
379
449
|
|
|
380
450
|
@Test
|
|
@@ -615,6 +685,76 @@ class PlaybackServiceCallbackTest {
|
|
|
615
685
|
assertEquals(0, fakeEnv.headlessConnectPackages.size)
|
|
616
686
|
}
|
|
617
687
|
|
|
688
|
+
// --- isCarConnected: the process-wide flag TrackPlayer.isCarConnected() reads
|
|
689
|
+
|
|
690
|
+
@Test
|
|
691
|
+
fun `CarConnectionState is disconnected with no controller attached`() {
|
|
692
|
+
assertFalse(CarConnectionState.isConnected())
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
@Test
|
|
696
|
+
fun `headless connect marks connected, last disconnect marks disconnected`() {
|
|
697
|
+
val cb = PlaybackServiceCallback(RecordingEnvironment(service))
|
|
698
|
+
val gearhead = controllerFor("com.google.android.projection.gearhead")
|
|
699
|
+
cb.onConnect(session, gearhead)
|
|
700
|
+
assertTrue("connected after the first headless controller", CarConnectionState.isConnected())
|
|
701
|
+
cb.onDisconnected(session, gearhead)
|
|
702
|
+
assertFalse("disconnected after the last controller drops", CarConnectionState.isConnected())
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
@Test
|
|
706
|
+
fun `stays connected while any headless controller remains`() {
|
|
707
|
+
val cb = PlaybackServiceCallback(RecordingEnvironment(service))
|
|
708
|
+
val gearhead = controllerFor("com.google.android.projection.gearhead")
|
|
709
|
+
val wear = controllerFor("com.google.android.wearable.app")
|
|
710
|
+
cb.onConnect(session, gearhead)
|
|
711
|
+
cb.onConnect(session, wear)
|
|
712
|
+
cb.onDisconnected(session, gearhead)
|
|
713
|
+
assertTrue("still connected with one controller remaining", CarConnectionState.isConnected())
|
|
714
|
+
cb.onDisconnected(session, wear)
|
|
715
|
+
assertFalse("disconnected once the last controller drops", CarConnectionState.isConnected())
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
@Test
|
|
719
|
+
fun `non-headless controller leaves the flag disconnected`() {
|
|
720
|
+
val cb = PlaybackServiceCallback(RecordingEnvironment(service))
|
|
721
|
+
val phone = controllerFor("com.example.regular.app")
|
|
722
|
+
cb.onConnect(session, phone)
|
|
723
|
+
assertFalse(CarConnectionState.isConnected())
|
|
724
|
+
cb.onDisconnected(session, phone)
|
|
725
|
+
assertFalse(CarConnectionState.isConnected())
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
@Test
|
|
729
|
+
fun `untracked headless disconnect does not mark connected`() {
|
|
730
|
+
val cb = PlaybackServiceCallback(RecordingEnvironment(service))
|
|
731
|
+
val gearhead = controllerFor("com.google.android.projection.gearhead")
|
|
732
|
+
// A stray disconnect for a controller that never connected (service-
|
|
733
|
+
// restart edge) must not mark connected; the count floors at zero.
|
|
734
|
+
cb.onDisconnected(session, gearhead)
|
|
735
|
+
assertFalse(CarConnectionState.isConnected())
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
@Test
|
|
739
|
+
fun `a stray disconnect floors the count so a later connect still reads connected`() {
|
|
740
|
+
val cb = PlaybackServiceCallback(RecordingEnvironment(service))
|
|
741
|
+
val gearhead = controllerFor("com.google.android.projection.gearhead")
|
|
742
|
+
cb.onConnect(session, gearhead)
|
|
743
|
+
cb.onDisconnected(session, gearhead)
|
|
744
|
+
// Extra (stray) disconnect: the count must floor at zero, not go negative.
|
|
745
|
+
cb.onDisconnected(session, gearhead)
|
|
746
|
+
assertFalse("disconnected after the real drop", CarConnectionState.isConnected())
|
|
747
|
+
// If the stray disconnect had driven the count to -1, this connect would
|
|
748
|
+
// land it back at 0 and read disconnected.
|
|
749
|
+
cb.onConnect(session, gearhead)
|
|
750
|
+
assertTrue("connected again after a fresh attach", CarConnectionState.isConnected())
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
private fun controllerFor(packageName: String): MediaSession.ControllerInfo =
|
|
754
|
+
MediaSession.ControllerInfo.createTestOnlyControllerInfo(
|
|
755
|
+
packageName, 0, 0, 0, 0, false, android.os.Bundle.EMPTY, false
|
|
756
|
+
)
|
|
757
|
+
|
|
618
758
|
@Test
|
|
619
759
|
fun `becoming-noisy receiver unregisters in onDestroy`() {
|
|
620
760
|
val handler = RecordingHandler()
|
|
@@ -689,10 +829,11 @@ class PlaybackServiceCallbackTest {
|
|
|
689
829
|
return skipPreviousHandled
|
|
690
830
|
}
|
|
691
831
|
|
|
832
|
+
override fun onSeekToMediaItemCommand(mediaItemIndex: Int): Boolean = false
|
|
833
|
+
|
|
692
834
|
override fun getCachedSkipCapability(): SkipCapability = capability
|
|
693
835
|
|
|
694
|
-
//
|
|
695
|
-
// suite covers the controller-driven callback routing only.
|
|
836
|
+
// This suite covers the controller-driven callback routing only.
|
|
696
837
|
override fun onMediaPlay() {}
|
|
697
838
|
override fun onMediaPause() {}
|
|
698
839
|
override fun onMediaTogglePlayPause() {}
|
|
@@ -300,6 +300,8 @@ class PlaybackServiceLifecycleTest {
|
|
|
300
300
|
return true
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
override fun onSeekToMediaItemCommand(mediaItemIndex: Int): Boolean = false
|
|
304
|
+
|
|
303
305
|
override fun getCachedSkipCapability(): SkipCapability =
|
|
304
306
|
SkipCapability(canSkipPrevious = true, canSkipNext = true)
|
|
305
307
|
|
|
@@ -6,9 +6,7 @@ import org.junit.Test
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Unit tests for [ProgressEmissionGate] — the pure throttle behind the
|
|
9
|
-
* configurable / background-reduced `onProgress` cadence.
|
|
10
|
-
* progress tick + the ProcessLifecycleOwner observer) is covered by
|
|
11
|
-
* [TrackPlayerProgressThrottleTest] and the progress demo suites.
|
|
9
|
+
* configurable / background-reduced `onProgress` cadence.
|
|
12
10
|
*
|
|
13
11
|
* Most tests pass `samplerPeriodMs = 0.0` to exercise exact interval semantics;
|
|
14
12
|
* the jitter + quantization tests pass the production `500` to verify the
|
package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt
CHANGED
|
@@ -135,6 +135,37 @@ class SessionCommandForwardingPlayerTest {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// --- Controller seek-to-queue-item (Android Auto queue-row tap): queue-aware veto
|
|
139
|
+
// (COMMAND_SEEK_TO_MEDIA_ITEM routes through onSeekToMediaItemCommand rather than
|
|
140
|
+
// the raw per-item seek, so the lib's current index + track-change reason land.)
|
|
141
|
+
|
|
142
|
+
@Test
|
|
143
|
+
fun `controller seek-to-queue-item runs queue-aware skip and vetoes the default seek when handled`() {
|
|
144
|
+
val wrapped = RecordingPlayer()
|
|
145
|
+
val handler = RecordingHandler(seekToMediaItemHandled = true)
|
|
146
|
+
val wrapper = newWrapper(wrapped, handler)
|
|
147
|
+
|
|
148
|
+
asController { wrapper.handleSeek(2, 0L, Player.COMMAND_SEEK_TO_MEDIA_ITEM) }
|
|
149
|
+
|
|
150
|
+
assertEquals("seek-to-queue-item routes into onSeekToMediaItemCommand", 1, handler.seekToMediaItemCalls)
|
|
151
|
+
assertEquals("the tapped queue index is forwarded", 2, handler.lastSeekToMediaItemIndex)
|
|
152
|
+
assertTrue("a handled queue tap vetoes the default seek", wrapped.seekCommands.isEmpty())
|
|
153
|
+
assertEquals(0, handler.transportCalls)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@Test
|
|
157
|
+
fun `controller seek-to-queue-item falls through to the default seek when not handled`() {
|
|
158
|
+
val wrapped = RecordingPlayer()
|
|
159
|
+
val handler = RecordingHandler(seekToMediaItemHandled = false)
|
|
160
|
+
val wrapper = newWrapper(wrapped, handler)
|
|
161
|
+
|
|
162
|
+
asController { wrapper.handleSeek(2, 0L, Player.COMMAND_SEEK_TO_MEDIA_ITEM) }
|
|
163
|
+
|
|
164
|
+
assertEquals(1, handler.seekToMediaItemCalls)
|
|
165
|
+
assertEquals("a declined queue tap delegates one seek to the wrapped player",
|
|
166
|
+
listOf(Player.COMMAND_SEEK_TO_MEDIA_ITEM), wrapped.seekCommands)
|
|
167
|
+
}
|
|
168
|
+
|
|
138
169
|
// --- Controller transport seeks: stamp system then delegate
|
|
139
170
|
// (SEEK_BACK / SEEK_FORWARD are recomputed to the runtime interval — see the
|
|
140
171
|
// interval-skip tests above — so they are not part of this pass-through set.)
|
|
@@ -144,7 +175,6 @@ class SessionCommandForwardingPlayerTest {
|
|
|
144
175
|
val transportSeeks = listOf(
|
|
145
176
|
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
|
146
177
|
Player.COMMAND_SEEK_TO_DEFAULT_POSITION,
|
|
147
|
-
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
|
148
178
|
)
|
|
149
179
|
for (command in transportSeeks) {
|
|
150
180
|
val wrapped = RecordingPlayer()
|
|
@@ -319,6 +349,21 @@ class SessionCommandForwardingPlayerTest {
|
|
|
319
349
|
}
|
|
320
350
|
}
|
|
321
351
|
|
|
352
|
+
@Test
|
|
353
|
+
fun `non-controller seek-to-queue-item delegates straight through without consulting the handler`() {
|
|
354
|
+
val wrapped = RecordingPlayer()
|
|
355
|
+
val handler = RecordingHandler(seekToMediaItemHandled = true)
|
|
356
|
+
val wrapper = newWrapper(wrapped, handler)
|
|
357
|
+
|
|
358
|
+
// No controller-request scope: models the JS bridge / engine driving
|
|
359
|
+
// the player directly, which must never re-enter the skip path.
|
|
360
|
+
wrapper.handleSeek(C_INDEX, 0L, Player.COMMAND_SEEK_TO_MEDIA_ITEM)
|
|
361
|
+
|
|
362
|
+
assertEquals("internal seek never consults onSeekToMediaItemCommand", 0, handler.seekToMediaItemCalls)
|
|
363
|
+
assertEquals("internal seek delegates straight to the wrapped player",
|
|
364
|
+
listOf(Player.COMMAND_SEEK_TO_MEDIA_ITEM), wrapped.seekCommands)
|
|
365
|
+
}
|
|
366
|
+
|
|
322
367
|
@Test
|
|
323
368
|
fun `non-controller transport delegates straight through without consulting the handler`() {
|
|
324
369
|
val wrapped = RecordingPlayer()
|
|
@@ -344,16 +389,18 @@ class SessionCommandForwardingPlayerTest {
|
|
|
344
389
|
|
|
345
390
|
asController { wrapper.handleSeek(C_INDEX, 0L, Player.COMMAND_SEEK_TO_NEXT) }
|
|
346
391
|
asController { wrapper.handleSeek(C_INDEX, 0L, Player.COMMAND_SEEK_TO_PREVIOUS) }
|
|
392
|
+
asController { wrapper.handleSeek(C_INDEX, 0L, Player.COMMAND_SEEK_TO_MEDIA_ITEM) }
|
|
347
393
|
asController { wrapper.handleSeek(C_INDEX, 0L, Player.COMMAND_SEEK_BACK) }
|
|
348
394
|
asController { wrapper.handleSetPlayWhenReady(true) }
|
|
349
395
|
asController { wrapper.handleSetPlaybackParameters(PlaybackParameters(1.25f)) }
|
|
350
396
|
|
|
351
|
-
// With no handler the skip cannot be vetoed, so all
|
|
397
|
+
// With no handler the skip / queue-tap cannot be vetoed, so all delegate.
|
|
352
398
|
// SEEK_BACK is recomputed to the interval and forwarded as an in-item seek.
|
|
353
399
|
assertEquals(
|
|
354
400
|
listOf(
|
|
355
401
|
Player.COMMAND_SEEK_TO_NEXT,
|
|
356
402
|
Player.COMMAND_SEEK_TO_PREVIOUS,
|
|
403
|
+
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
|
357
404
|
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
|
358
405
|
),
|
|
359
406
|
wrapped.seekCommands
|
|
@@ -482,10 +529,13 @@ class SessionCommandForwardingPlayerTest {
|
|
|
482
529
|
private class RecordingHandler(
|
|
483
530
|
private val skipNextHandled: Boolean = true,
|
|
484
531
|
private val skipPreviousHandled: Boolean = true,
|
|
532
|
+
private val seekToMediaItemHandled: Boolean = true,
|
|
485
533
|
) : PlaybackService.MediaSessionCommandHandler {
|
|
486
534
|
var transportCalls = 0
|
|
487
535
|
var skipNextCalls = 0
|
|
488
536
|
var skipPreviousCalls = 0
|
|
537
|
+
var seekToMediaItemCalls = 0
|
|
538
|
+
var lastSeekToMediaItemIndex = Int.MIN_VALUE
|
|
489
539
|
|
|
490
540
|
override fun onTransportCommand() {
|
|
491
541
|
transportCalls++
|
|
@@ -501,6 +551,12 @@ class SessionCommandForwardingPlayerTest {
|
|
|
501
551
|
return skipPreviousHandled
|
|
502
552
|
}
|
|
503
553
|
|
|
554
|
+
override fun onSeekToMediaItemCommand(mediaItemIndex: Int): Boolean {
|
|
555
|
+
seekToMediaItemCalls++
|
|
556
|
+
lastSeekToMediaItemIndex = mediaItemIndex
|
|
557
|
+
return seekToMediaItemHandled
|
|
558
|
+
}
|
|
559
|
+
|
|
504
560
|
override fun getCachedSkipCapability(): SkipCapability =
|
|
505
561
|
SkipCapability(canSkipPrevious = true, canSkipNext = true)
|
|
506
562
|
|
|
@@ -348,8 +348,7 @@ class TrackPlayerEventsTest {
|
|
|
348
348
|
//
|
|
349
349
|
// The measured silence gap rides on onTrackChange's 4th arg only for a
|
|
350
350
|
// natural gapless auto-advance. Every other transition carries `null`.
|
|
351
|
-
// The positive numeric value
|
|
352
|
-
// verified on-device by the `gapless-album` demo suite.
|
|
351
|
+
// The positive numeric value is a native nanoTime delta.
|
|
353
352
|
|
|
354
353
|
@Test
|
|
355
354
|
fun `handleMediaItemTransition PLAYLIST_CHANGED reports null transitionGapMs`() {
|
|
@@ -20,10 +20,8 @@ import org.robolectric.annotation.Config
|
|
|
20
20
|
* ExoPlayer timeline synchronisation (`mediaItemCount`,
|
|
21
21
|
* `currentMediaItemIndex` where applicable).
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* correctness — that the arithmetic result is actually applied
|
|
26
|
-
* to both state structures in concert.
|
|
23
|
+
* These tests focus on wiring correctness — that the arithmetic
|
|
24
|
+
* result is actually applied to both state structures in concert.
|
|
27
25
|
*/
|
|
28
26
|
@RunWith(RobolectricTestRunner::class)
|
|
29
27
|
@Config(sdk = [34])
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerPitchCorrectionTest.kt
CHANGED
|
@@ -19,8 +19,7 @@ import org.robolectric.annotation.Config
|
|
|
19
19
|
* `initHybrid()` → JNI which the Robolectric runtime doesn't load. This
|
|
20
20
|
* file pins the default and the synchronous read path
|
|
21
21
|
* ([TrackPlayer.getPitchCorrectionMode]) against the canonical state
|
|
22
|
-
* field
|
|
23
|
-
* the demo `pitch-correction` suite.
|
|
22
|
+
* field.
|
|
24
23
|
*/
|
|
25
24
|
@RunWith(RobolectricTestRunner::class)
|
|
26
25
|
@Config(sdk = [34])
|
|
@@ -9,9 +9,8 @@ import org.robolectric.annotation.Config
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* `setPlaybackMode` / `getPlaybackMode` imperative spec coverage —
|
|
12
|
-
* the engine-swap path
|
|
13
|
-
*
|
|
14
|
-
* service + ExoPlayer to verify a real swap.
|
|
12
|
+
* the engine-swap path can't be fully bound under Robolectric, which
|
|
13
|
+
* can't stand up the service + ExoPlayer to verify a real swap.
|
|
15
14
|
*/
|
|
16
15
|
@RunWith(RobolectricTestRunner::class)
|
|
17
16
|
@Config(sdk = [34])
|
|
@@ -20,9 +20,7 @@ import org.robolectric.annotation.Config
|
|
|
20
20
|
* [ReplayGainConfig.setMode] then asks the active engine to
|
|
21
21
|
* recompute; this file pins the synchronous read path
|
|
22
22
|
* ([TrackPlayer.getReplayGainMode]) round-trips with
|
|
23
|
-
* [ReplayGainConfig]
|
|
24
|
-
* [ReplayGainConfigTest]. End-to-end Promise dispatch is exercised
|
|
25
|
-
* through the demo `replaygain` Maestro suite.
|
|
23
|
+
* [ReplayGainConfig].
|
|
26
24
|
*/
|
|
27
25
|
@RunWith(RobolectricTestRunner::class)
|
|
28
26
|
@Config(sdk = [34])
|