react-native-queue-player 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt
CHANGED
|
@@ -2,8 +2,10 @@ package com.margelo.nitro.queueplayer
|
|
|
2
2
|
|
|
3
3
|
import androidx.media3.common.util.UnstableApi
|
|
4
4
|
import androidx.test.core.app.ApplicationProvider
|
|
5
|
+
import kotlinx.coroutines.runBlocking
|
|
5
6
|
import org.junit.After
|
|
6
7
|
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Assert.assertFalse
|
|
7
9
|
import org.junit.Assert.assertNotEquals
|
|
8
10
|
import org.junit.Assert.assertNotNull
|
|
9
11
|
import org.junit.Assert.assertNull
|
|
@@ -70,7 +72,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
70
72
|
// Size is configure-time (PlayerConfig.lookaheadCacheMaxSizeMb), not a
|
|
71
73
|
// runtime dimension. Unset → 100 MB default; set → reflected read-only.
|
|
72
74
|
player.configureInternal(emptyConfig(), context())
|
|
73
|
-
assertEquals(100.0, player.
|
|
75
|
+
assertEquals(100.0, player.buildCacheStatusSnapshot().maxSizeMb, 0.001)
|
|
74
76
|
|
|
75
77
|
player.destroyInternal()
|
|
76
78
|
serviceHandle.destroy()
|
|
@@ -79,12 +81,14 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
79
81
|
PlayerConfig(
|
|
80
82
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
81
83
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
82
|
-
|
|
84
|
+
voiceVocabularyDonationEnabled = null,
|
|
85
|
+
visualizationEnabled = null, clampSeekToBuffered = null,
|
|
86
|
+
lookaheadCacheMaxSizeMb = 250.0, lookaheadCacheEvictionPolicy = null,
|
|
83
87
|
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
84
88
|
),
|
|
85
89
|
context()
|
|
86
90
|
)
|
|
87
|
-
assertEquals(250.0, player.
|
|
91
|
+
assertEquals(250.0, player.buildCacheStatusSnapshot().maxSizeMb, 0.001)
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
@Test
|
|
@@ -115,39 +119,263 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
115
119
|
assertNotNull(player.lookaheadCache)
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
private fun configWithPolicy(policy: EvictionPolicy) = PlayerConfig(
|
|
122
|
+
private fun configWithPolicy(policy: EvictionPolicy, maxSizeMb: Double? = null) = PlayerConfig(
|
|
119
123
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
120
124
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
121
|
-
|
|
125
|
+
voiceVocabularyDonationEnabled = null,
|
|
126
|
+
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = maxSizeMb,
|
|
122
127
|
lookaheadCacheEvictionPolicy = policy,
|
|
123
128
|
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
124
129
|
)
|
|
125
130
|
|
|
131
|
+
@Test
|
|
132
|
+
fun `re-configuring with an unchanged policy keeps the cache`() {
|
|
133
|
+
// The policy comparison reads what the live cache was built with, not the
|
|
134
|
+
// in-memory config, which starts at the LRU default on every player.
|
|
135
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.FIFO), context())
|
|
136
|
+
val first = player.lookaheadCache?.simpleCache
|
|
137
|
+
assertNotNull(first)
|
|
138
|
+
|
|
139
|
+
player.destroyInternal()
|
|
140
|
+
serviceHandle.destroy()
|
|
141
|
+
player = TrackPlayer()
|
|
142
|
+
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
143
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.FIFO), context())
|
|
144
|
+
|
|
145
|
+
assertSame(
|
|
146
|
+
"an unchanged policy must not discard the shared cache",
|
|
147
|
+
first, player.lookaheadCache?.simpleCache
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* With no live cache in the process, the policy the directory was built
|
|
153
|
+
* with is read from prefs. A previous process is stood in for by a cache
|
|
154
|
+
* built and populated here, then released from the process-wide map; the
|
|
155
|
+
* span it holds is what a discard removes and a kept cache still serves.
|
|
156
|
+
*/
|
|
157
|
+
@Test
|
|
158
|
+
fun `a policy recorded by a previous process is read from disk`() {
|
|
159
|
+
val ctx = context()
|
|
160
|
+
val url = "https://example.com/previous.m4a"
|
|
161
|
+
seedCacheFromAPreviousProcess(url, EvictionPolicy.FIFO)
|
|
162
|
+
|
|
163
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.LRU), ctx)
|
|
164
|
+
|
|
165
|
+
assertTrue(
|
|
166
|
+
"the recorded policy differs from the requested one, so the old cache is discarded",
|
|
167
|
+
player.lookaheadCache!!.simpleCache.getCachedSpans(url).isEmpty())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@Test
|
|
171
|
+
fun `a policy recorded by a previous process that matches keeps the cache`() {
|
|
172
|
+
val ctx = context()
|
|
173
|
+
val url = "https://example.com/previous.m4a"
|
|
174
|
+
seedCacheFromAPreviousProcess(url, EvictionPolicy.FIFO)
|
|
175
|
+
|
|
176
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.FIFO), ctx)
|
|
177
|
+
|
|
178
|
+
assertFalse(
|
|
179
|
+
"an unchanged policy keeps the cache and its spans",
|
|
180
|
+
player.lookaheadCache!!.simpleCache.getCachedSpans(url).isEmpty())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build a cache under [policy], commit one span for [url], then leave the
|
|
185
|
+
* process with no live instance and prefs recording [policy] — the state
|
|
186
|
+
* the next launch finds.
|
|
187
|
+
*/
|
|
188
|
+
private fun seedCacheFromAPreviousProcess(url: String, policy: EvictionPolicy) {
|
|
189
|
+
val ctx = context()
|
|
190
|
+
player.configureInternal(configWithPolicy(policy), ctx)
|
|
191
|
+
player.setLookaheadCacheInternal(
|
|
192
|
+
LookaheadCacheConfig(enabled = false, lookaheadCount = 0.0, allowsCellularAccess = null)
|
|
193
|
+
)
|
|
194
|
+
writeSpansAndListSurvivors(listOf(url))
|
|
195
|
+
val dir = player.lookaheadCache!!.cacheDir
|
|
196
|
+
player.destroyInternal()
|
|
197
|
+
serviceHandle.destroy()
|
|
198
|
+
LookaheadCache.releaseSharedInstance(dir)
|
|
199
|
+
ctx.getSharedPreferences("rnqp_lookahead_cache", android.content.Context.MODE_PRIVATE)
|
|
200
|
+
.edit().putString("eviction_policy", policy.name).commit()
|
|
201
|
+
player = TrackPlayer()
|
|
202
|
+
serviceHandle = bindPlaybackServiceForTest(player, ctx)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private fun track(id: String) = TrackItem(
|
|
206
|
+
id = id, url = "https://example.com/$id.m4a",
|
|
207
|
+
title = null, artist = null, album = null,
|
|
208
|
+
duration = null, artworkUrl = null, extras = null,
|
|
209
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
210
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
// --- cache protection window
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The window is observed through the evictor: five 400 KB tracks against a
|
|
217
|
+
* 1 MB budget under FIFO leave exactly the protected ones — the one behind the
|
|
218
|
+
* playhead, the playing one and `lookaheadCount` ahead — on disk.
|
|
219
|
+
*/
|
|
220
|
+
@Test
|
|
221
|
+
fun `the protection window keeps the playing track and its neighbours through eviction`() {
|
|
222
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.FIFO, maxSizeMb = 1.0), context())
|
|
223
|
+
// The window is refreshed whether or not prefetch is enabled; disabled,
|
|
224
|
+
// the writer fetches nothing and the evictor is the only thing that
|
|
225
|
+
// decides which spans survive.
|
|
226
|
+
player.setLookaheadCacheInternal(
|
|
227
|
+
LookaheadCacheConfig(enabled = false, lookaheadCount = 1.0, allowsCellularAccess = null)
|
|
228
|
+
)
|
|
229
|
+
player.setQueueInternal((1..5).map { track("t$it") }, startAtIndex = 2)
|
|
230
|
+
|
|
231
|
+
val survivors = writeSpansAndListSurvivors((1..5).map { "https://example.com/t$it.m4a" })
|
|
232
|
+
|
|
233
|
+
assertEquals(
|
|
234
|
+
"t1 is the oldest unprotected entry and t5 lands over budget with nothing else to take",
|
|
235
|
+
listOf("https://example.com/t2.m4a", "https://example.com/t3.m4a", "https://example.com/t4.m4a"),
|
|
236
|
+
survivors)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@Test
|
|
240
|
+
fun `an out-of-range lookahead count clamps the protection window`() {
|
|
241
|
+
// lookaheadCount crosses from JS as a Double. The window drops non-finite
|
|
242
|
+
// values before any `toInt()`, and clamps a finite one to the queue and
|
|
243
|
+
// the protected-ahead ceiling before it is added to the index — so every
|
|
244
|
+
// one of these yields a well-formed set rather than a thrown subList.
|
|
245
|
+
player.configureInternal(configWithPolicy(EvictionPolicy.FIFO, maxSizeMb = 1.0), context())
|
|
246
|
+
player.setLookaheadCacheInternal(
|
|
247
|
+
LookaheadCacheConfig(enabled = false, lookaheadCount = 0.0, allowsCellularAccess = null)
|
|
248
|
+
)
|
|
249
|
+
player.setQueueInternal((1..5).map { track("t$it") }, startAtIndex = 2)
|
|
250
|
+
|
|
251
|
+
listOf(Double.POSITIVE_INFINITY, Double.NaN, Double.MAX_VALUE, -1.0).forEach { count ->
|
|
252
|
+
player.setLookaheadCacheInternal(
|
|
253
|
+
LookaheadCacheConfig(enabled = false, lookaheadCount = count, allowsCellularAccess = null)
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
// -1.0 was applied last: a count of zero ahead protects t2 and t3 only.
|
|
257
|
+
val survivors = writeSpansAndListSurvivors((1..5).map { "https://example.com/t$it.m4a" })
|
|
258
|
+
|
|
259
|
+
assertEquals(listOf("https://example.com/t2.m4a", "https://example.com/t3.m4a"), survivors)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Commit one 400 KB span per url in order and return the urls still cached. */
|
|
263
|
+
private fun writeSpansAndListSurvivors(urls: List<String>): List<String> {
|
|
264
|
+
val cache = player.lookaheadCache!!.simpleCache
|
|
265
|
+
val spanBytes = 400L * 1024
|
|
266
|
+
for (url in urls) {
|
|
267
|
+
// The write lock a CacheDataSink would hold: startFile requires it.
|
|
268
|
+
val hole = cache.startReadWrite(url, 0, spanBytes)
|
|
269
|
+
val file = cache.startFile(url, 0, spanBytes)
|
|
270
|
+
file.writeBytes(ByteArray(spanBytes.toInt()))
|
|
271
|
+
cache.commitFile(file, spanBytes)
|
|
272
|
+
cache.releaseHoleSpan(hole)
|
|
273
|
+
}
|
|
274
|
+
return urls.filter { cache.getCachedSpans(it).isNotEmpty() }
|
|
275
|
+
}
|
|
276
|
+
|
|
126
277
|
// --- setLookaheadCacheInternal
|
|
127
278
|
|
|
128
279
|
@Test
|
|
129
|
-
fun `setLookaheadCacheInternal disable tears down writer
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// next destroy() / configure() cycle.
|
|
280
|
+
fun `setLookaheadCacheInternal disable tears down writer and stops serving reads`() {
|
|
281
|
+
// Disabling stops new prefetches and routes the player's reads to the
|
|
282
|
+
// origin; the cache instance stays alive (one per directory) and is
|
|
283
|
+
// released on the next destroy() / configure() cycle.
|
|
134
284
|
player.configureInternal(emptyConfig(), context())
|
|
135
285
|
val cacheBeforeDisable = player.lookaheadCache
|
|
136
286
|
assertNotNull(cacheBeforeDisable)
|
|
137
287
|
assertNotNull(player.lookaheadCacheWriter)
|
|
288
|
+
assertTrue("enabled: playback reads go through the cache", cacheBeforeDisable!!.servesPlayback)
|
|
138
289
|
|
|
139
290
|
player.setLookaheadCacheInternal(
|
|
140
291
|
LookaheadCacheConfig(
|
|
141
|
-
enabled = false, lookaheadCount = 3.0
|
|
292
|
+
enabled = false, lookaheadCount = 3.0,
|
|
293
|
+
allowsCellularAccess = null
|
|
142
294
|
)
|
|
143
295
|
)
|
|
144
296
|
|
|
145
|
-
assertSame(
|
|
146
|
-
"cache must stay alive — player's MediaSource.Factory still reads it",
|
|
147
|
-
cacheBeforeDisable, player.lookaheadCache
|
|
148
|
-
)
|
|
297
|
+
assertSame("the cache instance stays alive", cacheBeforeDisable, player.lookaheadCache)
|
|
149
298
|
assertNull("writer torn down — no new prefetches", player.lookaheadCacheWriter)
|
|
299
|
+
assertFalse("disabled: playback reads go to the origin", cacheBeforeDisable.servesPlayback)
|
|
150
300
|
assertEquals(false, player.lookaheadConfig.enabled)
|
|
301
|
+
|
|
302
|
+
player.setLookaheadCacheInternal(
|
|
303
|
+
LookaheadCacheConfig(enabled = true, lookaheadCount = 3.0, allowsCellularAccess = null)
|
|
304
|
+
)
|
|
305
|
+
assertTrue("re-enabled: reads go through the cache again", cacheBeforeDisable.servesPlayback)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- allowsCellularAccess
|
|
309
|
+
|
|
310
|
+
@Test
|
|
311
|
+
fun `configure builds the writer permitting cellular by default`() {
|
|
312
|
+
player.configureInternal(emptyConfig(), context())
|
|
313
|
+
|
|
314
|
+
assertEquals(true, player.lookaheadCacheWriter?.allowsCellularAccess)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@Test
|
|
318
|
+
fun `configure carries a non-default cellular policy into the writer`() {
|
|
319
|
+
player.lookaheadConfig = LookaheadCacheConfig(
|
|
320
|
+
enabled = true, lookaheadCount = 3.0, allowsCellularAccess = false
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
player.configureInternal(emptyConfig(), context())
|
|
324
|
+
|
|
325
|
+
assertEquals(false, player.lookaheadCacheWriter?.allowsCellularAccess)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@Test
|
|
329
|
+
fun `configure starts a transport monitor and destroy stops it`() {
|
|
330
|
+
player.configureInternal(emptyConfig(), context())
|
|
331
|
+
assertNotNull(player.cellularTransportMonitor)
|
|
332
|
+
|
|
333
|
+
player.destroyInternal()
|
|
334
|
+
|
|
335
|
+
assertNull(player.cellularTransportMonitor)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@Test
|
|
339
|
+
fun `setLookaheadCacheInternal applies allowsCellularAccess to the live writer`() {
|
|
340
|
+
player.configureInternal(emptyConfig(), context())
|
|
341
|
+
val writer = player.lookaheadCacheWriter
|
|
342
|
+
assertNotNull(writer)
|
|
343
|
+
|
|
344
|
+
player.setLookaheadCacheInternal(
|
|
345
|
+
LookaheadCacheConfig(
|
|
346
|
+
enabled = true, lookaheadCount = 3.0, allowsCellularAccess = false
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
assertSame("the writer is reused — only its policy changes", writer, player.lookaheadCacheWriter)
|
|
351
|
+
assertEquals(false, writer?.allowsCellularAccess)
|
|
352
|
+
|
|
353
|
+
player.setLookaheadCacheInternal(
|
|
354
|
+
LookaheadCacheConfig(
|
|
355
|
+
enabled = true, lookaheadCount = 3.0, allowsCellularAccess = true
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
assertEquals(true, writer?.allowsCellularAccess)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
@Test
|
|
363
|
+
fun `a writer rebuilt by an enable flip carries the current cellular policy`() {
|
|
364
|
+
player.configureInternal(emptyConfig(), context())
|
|
365
|
+
player.setLookaheadCacheInternal(
|
|
366
|
+
LookaheadCacheConfig(
|
|
367
|
+
enabled = false, lookaheadCount = 3.0, allowsCellularAccess = false
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
assertNull(player.lookaheadCacheWriter)
|
|
371
|
+
|
|
372
|
+
player.setLookaheadCacheInternal(
|
|
373
|
+
LookaheadCacheConfig(
|
|
374
|
+
enabled = true, lookaheadCount = 3.0, allowsCellularAccess = false
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
assertEquals(false, player.lookaheadCacheWriter?.allowsCellularAccess)
|
|
151
379
|
}
|
|
152
380
|
|
|
153
381
|
@Test
|
|
@@ -164,7 +392,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
164
392
|
// Disable → writer released, cache kept for reads.
|
|
165
393
|
player.setLookaheadCacheInternal(
|
|
166
394
|
LookaheadCacheConfig(
|
|
167
|
-
enabled = false, lookaheadCount = 3.0
|
|
395
|
+
enabled = false, lookaheadCount = 3.0,
|
|
396
|
+
allowsCellularAccess = null
|
|
168
397
|
)
|
|
169
398
|
)
|
|
170
399
|
assertNull("disable releases the writer", player.lookaheadCacheWriter)
|
|
@@ -173,7 +402,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
173
402
|
// Re-enable → writer rebuilt at runtime, same cache, no configure().
|
|
174
403
|
player.setLookaheadCacheInternal(
|
|
175
404
|
LookaheadCacheConfig(
|
|
176
|
-
enabled = true, lookaheadCount = 5.0
|
|
405
|
+
enabled = true, lookaheadCount = 5.0,
|
|
406
|
+
allowsCellularAccess = null
|
|
177
407
|
)
|
|
178
408
|
)
|
|
179
409
|
assertNotNull(
|
|
@@ -190,7 +420,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
190
420
|
// Toggle off again → writer released once more (clean repeatable switch).
|
|
191
421
|
player.setLookaheadCacheInternal(
|
|
192
422
|
LookaheadCacheConfig(
|
|
193
|
-
enabled = false, lookaheadCount = 5.0
|
|
423
|
+
enabled = false, lookaheadCount = 5.0,
|
|
424
|
+
allowsCellularAccess = null
|
|
194
425
|
)
|
|
195
426
|
)
|
|
196
427
|
assertNull("second disable releases the writer again", player.lookaheadCacheWriter)
|
|
@@ -203,16 +434,21 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
203
434
|
// (built even when disabled) so the runtime enable can attach a writer.
|
|
204
435
|
player.setLookaheadCacheInternal(
|
|
205
436
|
LookaheadCacheConfig(
|
|
206
|
-
enabled = false, lookaheadCount = 3.0
|
|
437
|
+
enabled = false, lookaheadCount = 3.0,
|
|
438
|
+
allowsCellularAccess = null
|
|
207
439
|
)
|
|
208
440
|
)
|
|
209
441
|
player.configureInternal(emptyConfig(), context())
|
|
210
442
|
assertNotNull("cache is built even when disabled at configure", player.lookaheadCache)
|
|
211
443
|
assertNull("no writer while disabled", player.lookaheadCacheWriter)
|
|
444
|
+
assertFalse(
|
|
445
|
+
"built disabled: the player's reads go to the origin",
|
|
446
|
+
player.lookaheadCache!!.servesPlayback)
|
|
212
447
|
|
|
213
448
|
player.setLookaheadCacheInternal(
|
|
214
449
|
LookaheadCacheConfig(
|
|
215
|
-
enabled = true, lookaheadCount = 4.0
|
|
450
|
+
enabled = true, lookaheadCount = 4.0,
|
|
451
|
+
allowsCellularAccess = null
|
|
216
452
|
)
|
|
217
453
|
)
|
|
218
454
|
assertNotNull(
|
|
@@ -230,7 +466,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
230
466
|
|
|
231
467
|
player.setLookaheadCacheInternal(
|
|
232
468
|
LookaheadCacheConfig(
|
|
233
|
-
enabled = true, lookaheadCount = 7.0
|
|
469
|
+
enabled = true, lookaheadCount = 7.0,
|
|
470
|
+
allowsCellularAccess = null
|
|
234
471
|
)
|
|
235
472
|
)
|
|
236
473
|
|
|
@@ -253,7 +490,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
253
490
|
val writer = player.lookaheadCacheWriter
|
|
254
491
|
assertNotNull(writer)
|
|
255
492
|
|
|
256
|
-
player.clearLookaheadCacheInternal()
|
|
493
|
+
runBlocking { player.clearLookaheadCacheInternal()?.join() }
|
|
257
494
|
|
|
258
495
|
assertNotNull("cache stays — clear is wipe, not destroy", player.lookaheadCache)
|
|
259
496
|
assertNotNull("writer stays — clear is wipe, not destroy", player.lookaheadCacheWriter)
|
|
@@ -269,7 +506,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
269
506
|
@Test
|
|
270
507
|
fun `getLookaheadCacheStatus on a fresh player reports defaults`() {
|
|
271
508
|
player.configureInternal(emptyConfig(), context())
|
|
272
|
-
val status = player.
|
|
509
|
+
val status = player.buildCacheStatusSnapshot()
|
|
273
510
|
assertEquals(true, status.enabled)
|
|
274
511
|
assertEquals(0.0, status.currentSizeMb, 0.001)
|
|
275
512
|
assertEquals(100.0, status.maxSizeMb, 0.001)
|
|
@@ -282,10 +519,11 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
282
519
|
player.configureInternal(emptyConfig(), context())
|
|
283
520
|
player.setLookaheadCacheInternal(
|
|
284
521
|
LookaheadCacheConfig(
|
|
285
|
-
enabled = false, lookaheadCount = 0.0
|
|
522
|
+
enabled = false, lookaheadCount = 0.0,
|
|
523
|
+
allowsCellularAccess = null
|
|
286
524
|
)
|
|
287
525
|
)
|
|
288
|
-
val status = player.
|
|
526
|
+
val status = player.buildCacheStatusSnapshot()
|
|
289
527
|
assertEquals(false, status.enabled)
|
|
290
528
|
assertEquals(0.0, status.currentSizeMb, 0.001)
|
|
291
529
|
// maxSizeMb is the configure-time budget (100 MB default), reported
|
|
@@ -298,7 +536,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
298
536
|
@Test
|
|
299
537
|
fun `re-configure with changed httpHeaders rebuilds the player (writer + cache)`() {
|
|
300
538
|
player.configureInternal(
|
|
301
|
-
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-1"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
539
|
+
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-1"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
302
540
|
context()
|
|
303
541
|
)
|
|
304
542
|
val firstWriter = player.lookaheadCacheWriter
|
|
@@ -307,15 +545,14 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
307
545
|
assertNotNull(firstCache)
|
|
308
546
|
|
|
309
547
|
player.configureInternal(
|
|
310
|
-
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-2"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
548
|
+
PlayerConfig(httpHeaders = mapOf("X-Auth" to "token-2"), userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null),
|
|
311
549
|
context()
|
|
312
550
|
)
|
|
313
551
|
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// carry the new auth. The on-disk cache directory is
|
|
552
|
+
// A header change on re-configure rebuilds the entire player (and
|
|
553
|
+
// the cache + writer with it), not just the writer, so the player's
|
|
554
|
+
// MediaSource.Factory and every subsequent playback request carry
|
|
555
|
+
// the new auth. The on-disk cache directory is
|
|
319
556
|
// preserved across the rebuild — `SimpleCache.release()` only
|
|
320
557
|
// frees the in-memory instance + file lock; entries stay on
|
|
321
558
|
// disk and the next `LookaheadCache(...)` constructor reads
|
|
@@ -339,7 +576,7 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
339
576
|
// simulated here by `destroyInternal` between
|
|
340
577
|
// `configureInternal` calls) yields a fresh writer instance,
|
|
341
578
|
// even when headers are unchanged.
|
|
342
|
-
val cfg = PlayerConfig(httpHeaders = mapOf("X-Auth" to "token"), userAgent = "rnqp/test", autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
579
|
+
val cfg = PlayerConfig(httpHeaders = mapOf("X-Auth" to "token"), userAgent = "rnqp/test", autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
343
580
|
player.configureInternal(cfg, context())
|
|
344
581
|
val firstWriter = player.lookaheadCacheWriter
|
|
345
582
|
|
|
@@ -369,7 +606,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
369
606
|
// Step 1: disable. Writer torn down, cache stays.
|
|
370
607
|
player.setLookaheadCacheInternal(
|
|
371
608
|
LookaheadCacheConfig(
|
|
372
|
-
enabled = false, lookaheadCount = 3.0
|
|
609
|
+
enabled = false, lookaheadCount = 3.0,
|
|
610
|
+
allowsCellularAccess = null
|
|
373
611
|
)
|
|
374
612
|
)
|
|
375
613
|
assertSame(firstCache, player.lookaheadCache)
|
|
@@ -379,7 +617,8 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
379
617
|
// same cache instance.
|
|
380
618
|
player.setLookaheadCacheInternal(
|
|
381
619
|
LookaheadCacheConfig(
|
|
382
|
-
enabled = true, lookaheadCount = 7.0
|
|
620
|
+
enabled = true, lookaheadCount = 7.0,
|
|
621
|
+
allowsCellularAccess = null
|
|
383
622
|
)
|
|
384
623
|
)
|
|
385
624
|
assertSame("re-enable reuses the same cache instance", firstCache, player.lookaheadCache)
|
|
@@ -402,9 +641,9 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
402
641
|
@Test
|
|
403
642
|
fun `clearLookaheadCacheInternal then status snapshot reports zero size + empty currentlyCaching`() {
|
|
404
643
|
player.configureInternal(emptyConfig(), context())
|
|
405
|
-
player.clearLookaheadCacheInternal()
|
|
644
|
+
runBlocking { player.clearLookaheadCacheInternal()?.join() }
|
|
406
645
|
|
|
407
|
-
val status = player.
|
|
646
|
+
val status = player.buildCacheStatusSnapshot()
|
|
408
647
|
assertEquals(true, status.enabled)
|
|
409
648
|
assertEquals(0.0, status.currentSizeMb, 0.001)
|
|
410
649
|
assertEquals(0.0, status.tracksFullyCached, 0.001)
|
|
@@ -413,5 +652,5 @@ class TrackPlayerLookaheadConfigTest {
|
|
|
413
652
|
|
|
414
653
|
// --- Helpers
|
|
415
654
|
|
|
416
|
-
private fun emptyConfig() = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
655
|
+
private fun emptyConfig() = PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null)
|
|
417
656
|
}
|
|
@@ -35,7 +35,9 @@ class TrackPlayerMutationTest {
|
|
|
35
35
|
private fun track(id: String) = TrackItem(
|
|
36
36
|
id = id, url = "https://example.com/$id.m4a",
|
|
37
37
|
title = null, artist = null, album = null,
|
|
38
|
-
duration = null, artworkUrl = null, extras = null
|
|
38
|
+
duration = null, artworkUrl = null, extras = null,
|
|
39
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
40
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
39
41
|
)
|
|
40
42
|
|
|
41
43
|
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
@@ -45,7 +47,7 @@ class TrackPlayerMutationTest {
|
|
|
45
47
|
player = TrackPlayer()
|
|
46
48
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
47
49
|
player.configureInternal(
|
|
48
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
50
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
49
51
|
)
|
|
50
52
|
player.setQueueInternal((1..5).map { track("t$it") })
|
|
51
53
|
// cur defaults to 0 after setQueue; tests needing mid-queue
|
|
@@ -127,7 +129,7 @@ class TrackPlayerMutationTest {
|
|
|
127
129
|
val freshHandle = bindPlaybackServiceForTest(fresh, context())
|
|
128
130
|
try {
|
|
129
131
|
fresh.configureInternal(
|
|
130
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
132
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
131
133
|
)
|
|
132
134
|
assertEquals(-1, fresh.currentTrackIndex)
|
|
133
135
|
assertEquals(Player.STATE_IDLE, fresh.player!!.playbackState)
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt
CHANGED
|
@@ -34,7 +34,8 @@ class TrackPlayerProgressThrottleTest {
|
|
|
34
34
|
private fun config(foregroundMs: Double?, backgroundMs: Double? = null) = PlayerConfig(
|
|
35
35
|
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
36
36
|
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
37
|
-
|
|
37
|
+
voiceVocabularyDonationEnabled = null,
|
|
38
|
+
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null,
|
|
38
39
|
progressUpdateIntervalMs = foregroundMs, backgroundProgressUpdateIntervalMs = backgroundMs, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
39
40
|
)
|
|
40
41
|
|
|
@@ -28,9 +28,11 @@ class TrackPlayerQueueChangeTest {
|
|
|
28
28
|
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
29
29
|
|
|
30
30
|
private data class QueueEvent(
|
|
31
|
+
/** The mirror after applying this event's delta. */
|
|
31
32
|
val queue: List<TrackItem>,
|
|
32
33
|
val index: Int,
|
|
33
34
|
val reason: QueueChangeReason,
|
|
35
|
+
val delta: QueueChangeDelta,
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
private fun context() =
|
|
@@ -40,6 +42,8 @@ class TrackPlayerQueueChangeTest {
|
|
|
40
42
|
id = id, url = "https://example.com/$id.m4a",
|
|
41
43
|
title = null, artist = null, album = null,
|
|
42
44
|
duration = null, artworkUrl = null, extras = null,
|
|
45
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
46
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
43
47
|
)
|
|
44
48
|
|
|
45
49
|
@Before
|
|
@@ -47,7 +51,7 @@ class TrackPlayerQueueChangeTest {
|
|
|
47
51
|
player = TrackPlayer()
|
|
48
52
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
49
53
|
player.configureInternal(
|
|
50
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
54
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
51
55
|
)
|
|
52
56
|
player.setQueueInternal((1..5).map { track("t$it") })
|
|
53
57
|
}
|
|
@@ -62,12 +66,90 @@ class TrackPlayerQueueChangeTest {
|
|
|
62
66
|
* therefore not captured — onQueueChange does not auto-fire). */
|
|
63
67
|
private fun capture(): Pair<MutableList<QueueEvent>, () -> Unit> {
|
|
64
68
|
val events = mutableListOf<QueueEvent>()
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
// Mirror the queue by applying each delta, the way a consumer must. Every
|
|
70
|
+
// assertion below reads that mirror, so it doubles as proof the delta
|
|
71
|
+
// carries enough to reconstruct the queue.
|
|
72
|
+
var mirror = player.tracks.toList()
|
|
73
|
+
val dispose = player.onQueueChange { delta, idx, reason ->
|
|
74
|
+
mirror = applyDelta(mirror, delta, reason)
|
|
75
|
+
events.add(QueueEvent(mirror, idx.toInt(), reason, delta))
|
|
67
76
|
}
|
|
68
77
|
return events to dispose
|
|
69
78
|
}
|
|
70
79
|
|
|
80
|
+
/** The reference delta application, mirroring what `useQueue()` does in JS. */
|
|
81
|
+
private fun applyDelta(
|
|
82
|
+
previous: List<TrackItem>,
|
|
83
|
+
delta: QueueChangeDelta,
|
|
84
|
+
reason: QueueChangeReason
|
|
85
|
+
): List<TrackItem> = when (reason) {
|
|
86
|
+
QueueChangeReason.SET_QUEUE -> delta.inserted.toList()
|
|
87
|
+
QueueChangeReason.CLEAR -> emptyList()
|
|
88
|
+
QueueChangeReason.ADD -> previous.toMutableList().apply {
|
|
89
|
+
addAll(delta.insertedAt.toInt(), delta.inserted.toList())
|
|
90
|
+
}
|
|
91
|
+
QueueChangeReason.REMOVE -> {
|
|
92
|
+
val drop = delta.removed.map { it.toInt() }.toSet()
|
|
93
|
+
previous.filterIndexed { i, _ -> i !in drop }
|
|
94
|
+
}
|
|
95
|
+
QueueChangeReason.MOVE -> previous.toMutableList().apply {
|
|
96
|
+
add(delta.movedTo.toInt(), removeAt(delta.movedFrom.toInt()))
|
|
97
|
+
}
|
|
98
|
+
QueueChangeReason.SHUFFLE -> delta.order.map { previous[it.toInt()] }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Test
|
|
102
|
+
fun `a mirror built only from deltas matches the live queue`() {
|
|
103
|
+
val (events, dispose) = capture()
|
|
104
|
+
player.setQueueInternal((1..6).map { track("s$it") }, startAtIndex = 0)
|
|
105
|
+
player.addToQueueInternal(listOf(track("added")), insertBefore = 2)
|
|
106
|
+
player.removeFromQueueInternal(listOf(4, 0))
|
|
107
|
+
player.moveInQueueInternal(3, 1)
|
|
108
|
+
dispose()
|
|
109
|
+
|
|
110
|
+
assertEquals(
|
|
111
|
+
"the delta stream must reconstruct the queue exactly",
|
|
112
|
+
player.tracks.map { it.id },
|
|
113
|
+
events.last().queue.map { it.id }
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
fun `each emit advances the revision by one`() {
|
|
119
|
+
val (events, dispose) = capture()
|
|
120
|
+
player.setQueueInternal((1..3).map { track("r$it") })
|
|
121
|
+
player.addToQueueInternal(listOf(track("r4")), insertBefore = null)
|
|
122
|
+
player.clearQueueInternal()
|
|
123
|
+
dispose()
|
|
124
|
+
|
|
125
|
+
val revisions = events.map { it.delta.revision.toInt() }
|
|
126
|
+
val first = revisions.first()
|
|
127
|
+
assertEquals(revisions.indices.map { first + it }, revisions)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* `destroy` and `configure` clear the queue without an emit; the revision
|
|
132
|
+
* the next emit carries skips one, which is the signal a delta mirror
|
|
133
|
+
* re-reads on.
|
|
134
|
+
*/
|
|
135
|
+
@Test
|
|
136
|
+
fun `a silent clear leaves a revision gap for the next emit`() {
|
|
137
|
+
val (events, dispose) = capture()
|
|
138
|
+
player.setQueueInternal((1..3).map { track("g$it") })
|
|
139
|
+
val before = events.last().delta.revision.toInt()
|
|
140
|
+
|
|
141
|
+
player.destroyInternal()
|
|
142
|
+
serviceHandle.destroy()
|
|
143
|
+
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
144
|
+
player.configureInternal(
|
|
145
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
146
|
+
)
|
|
147
|
+
player.setQueueInternal(listOf(track("after")))
|
|
148
|
+
dispose()
|
|
149
|
+
|
|
150
|
+
assertEquals(before + 2, events.last().delta.revision.toInt())
|
|
151
|
+
}
|
|
152
|
+
|
|
71
153
|
// --- one emission per mutation, with the post-mutation snapshot
|
|
72
154
|
|
|
73
155
|
@Test
|