react-native-queue-player 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/consumer-rules.pro +1 -1
- 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 +3 -19
- 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 +8 -69
- 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 +111 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
- 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 +13 -5
- 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 +985 -276
- 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/CastSession.kt +5 -4
- 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 +4 -2
- 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 +1 -1
- 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/AirplayJNI.kt +1 -13
- 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/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 +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- 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 +84 -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/MediaResponseCheckTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
- 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 +2 -2
- 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 +1 -1
- 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 +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +122 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -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 +2 -2
- 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/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- 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 +672 -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 +229 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaResponseCheck.swift +122 -0
- 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 +1 -1
- 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/MediaResponseCheckTests.swift +128 -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/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 +418 -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 +441 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +206 -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 +1042 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +982 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +951 -0
- package/ios/TrackPlayer+Recovery.swift +213 -0
- package/ios/TrackPlayer+Skip.swift +465 -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 +272 -0
- package/ios/TrackPlayer+Window.swift +227 -0
- package/ios/TrackPlayer.swift +331 -4619
- 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/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
|
@@ -41,7 +41,9 @@ class TrackPlayerEventsTest {
|
|
|
41
41
|
private fun track(id: String) = TrackItem(
|
|
42
42
|
id = id, url = "https://example.com/$id.m4a",
|
|
43
43
|
title = null, artist = null, album = null,
|
|
44
|
-
duration = null, artworkUrl = null, extras = null
|
|
44
|
+
duration = null, artworkUrl = null, extras = null,
|
|
45
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
46
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
45
47
|
)
|
|
46
48
|
|
|
47
49
|
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
@@ -51,7 +53,7 @@ class TrackPlayerEventsTest {
|
|
|
51
53
|
player = TrackPlayer()
|
|
52
54
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
53
55
|
player.configureInternal(
|
|
54
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
56
|
+
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()
|
|
55
57
|
)
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -100,44 +102,118 @@ class TrackPlayerEventsTest {
|
|
|
100
102
|
assertEquals(0.0, snap.buffered, 0.001)
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
// --- onStateChange / onError / onQueueEnd registration
|
|
106
|
+
|
|
103
107
|
@Test
|
|
104
|
-
fun `
|
|
105
|
-
|
|
106
|
-
player.
|
|
107
|
-
}
|
|
108
|
+
fun `onStateChange delivers to a subscriber and stops after unsubscribe`() {
|
|
109
|
+
var fired = 0
|
|
110
|
+
val unsubscribe = player.onStateChange { _, _ -> fired += 1 }
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
player.emitRemoteState(PlayerState.PAUSED, StateChangeReason.SYSTEM)
|
|
113
|
+
assertEquals(1, fired)
|
|
114
|
+
|
|
115
|
+
unsubscribe()
|
|
116
|
+
// A different state, so the only thing between it and the listener is
|
|
117
|
+
// the unsubscribe — a repeat of the same one would be deduped.
|
|
118
|
+
player.emitRemoteState(PlayerState.PLAYING, StateChangeReason.SYSTEM)
|
|
119
|
+
assertEquals("nothing reaches a removed subscriber", 1, fired)
|
|
120
|
+
}
|
|
110
121
|
|
|
111
122
|
@Test
|
|
112
|
-
fun `
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
fun `onError delivers to a subscriber and stops after unsubscribe`() {
|
|
124
|
+
player.setQueueInternal((1..2).map { track("t$it") })
|
|
125
|
+
player.currentTrackIndex = 1
|
|
126
|
+
var fired = 0
|
|
127
|
+
val unsubscribe = player.onError { fired += 1 }
|
|
128
|
+
|
|
129
|
+
// A failure on a track that is not playing is reported straight away.
|
|
130
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
131
|
+
assertEquals(1, fired)
|
|
115
132
|
|
|
116
|
-
// No way to force the Player.Listener onto READY without a
|
|
117
|
-
// real media pipeline, but we can unregister + verify no
|
|
118
|
-
// leftover reference causes a crash when destroyInternal runs.
|
|
119
133
|
unsubscribe()
|
|
120
|
-
|
|
134
|
+
// A different failure, so the only thing between it and the listener
|
|
135
|
+
// is the unsubscribe — a repeat of the same one would be deduped.
|
|
136
|
+
player.onError(serviceHandle.engine, decodingFailure(), 0)
|
|
137
|
+
assertEquals("nothing reaches a removed subscriber", 1, fired)
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
@Test
|
|
124
|
-
fun `
|
|
125
|
-
|
|
141
|
+
fun `onQueueEnd delivers to a subscriber and stops after unsubscribe`() {
|
|
142
|
+
player.setQueueInternal((1..2).map { track("t$it") })
|
|
143
|
+
var fired = 0
|
|
144
|
+
val unsubscribe = player.onQueueEnd { fired += 1 }
|
|
145
|
+
|
|
146
|
+
player.maybeEmitQueueEnd(androidx.media3.common.Player.STATE_ENDED)
|
|
147
|
+
assertEquals(1, fired)
|
|
148
|
+
|
|
126
149
|
unsubscribe()
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
player.maybeEmitQueueEnd(androidx.media3.common.Player.STATE_ENDED)
|
|
151
|
+
assertEquals("nothing reaches a removed subscriber", 1, fired)
|
|
129
152
|
}
|
|
130
153
|
|
|
154
|
+
/** Queue end is announced as ENDED / QUEUE_END, and the pause that parks the
|
|
155
|
+
* engine afterwards leaves no reason behind for the next emit. */
|
|
131
156
|
@Test
|
|
132
|
-
fun `
|
|
133
|
-
|
|
157
|
+
fun `queue end announces ENDED with the queue-end reason and stamps nothing after`() {
|
|
158
|
+
player.setQueueInternal((1..2).map { track("t$it") })
|
|
159
|
+
val emits = mutableListOf<Pair<PlayerState, StateChangeReason>>()
|
|
160
|
+
val unsubscribe = player.onStateChange { state, reason -> emits.add(state to reason) }
|
|
161
|
+
|
|
162
|
+
player.maybeEmitQueueEnd(androidx.media3.common.Player.STATE_ENDED)
|
|
134
163
|
unsubscribe()
|
|
164
|
+
|
|
165
|
+
assertEquals(PlayerState.ENDED to StateChangeReason.QUEUE_END, emits.last())
|
|
166
|
+
assertEquals("the parking pause's USER stamp must not survive", null, player.pendingStateChangeReason)
|
|
135
167
|
}
|
|
136
168
|
|
|
137
169
|
@Test
|
|
138
|
-
fun `onTrackChange
|
|
139
|
-
|
|
170
|
+
fun `onTrackChange delivers to a subscriber and stops after unsubscribe`() {
|
|
171
|
+
player.setQueueInternal((1..3).map { track("t$it") })
|
|
172
|
+
var fired = 0
|
|
173
|
+
val unsubscribe = player.onTrackChange { _, _, _, _ -> fired += 1 }
|
|
174
|
+
|
|
175
|
+
player.emitRemoteTrackChange(1)
|
|
176
|
+
assertEquals(1, fired)
|
|
177
|
+
|
|
140
178
|
unsubscribe()
|
|
179
|
+
// A different index, so the only thing between it and the listener is
|
|
180
|
+
// the unsubscribe — a repeat of the current index returns early.
|
|
181
|
+
player.emitRemoteTrackChange(2)
|
|
182
|
+
assertEquals("nothing reaches a removed subscriber", 1, fired)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A source that answered with something that is not media is a format
|
|
187
|
+
* problem, not a reachability one: it was reached, and a retry cannot change
|
|
188
|
+
* what it sends. Classing it transient would spend an automatic attempt on a
|
|
189
|
+
* body that can never become audio.
|
|
190
|
+
*/
|
|
191
|
+
@Test
|
|
192
|
+
fun aNonMediaResponseClassifiesAsAFormatFailureAndIsFatal() {
|
|
193
|
+
val invalidContentType = PlaybackException(
|
|
194
|
+
"msg", null,
|
|
195
|
+
PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE
|
|
196
|
+
)
|
|
197
|
+
assertEquals(
|
|
198
|
+
PlaybackErrorCode.FILE_FORMAT_NOT_RECOGNIZED,
|
|
199
|
+
PlaybackErrorMapping.classify(invalidContentType)
|
|
200
|
+
)
|
|
201
|
+
assertFalse(
|
|
202
|
+
PlaybackErrorMapping.isTransient(PlaybackErrorCode.FILE_FORMAT_NOT_RECOGNIZED)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** A source that is genuinely missing stays a reachability failure. */
|
|
207
|
+
@Test
|
|
208
|
+
fun aMissingFileStillClassifiesAsUnreachable() {
|
|
209
|
+
val notFound = PlaybackException(
|
|
210
|
+
"msg", null,
|
|
211
|
+
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND
|
|
212
|
+
)
|
|
213
|
+
assertEquals(
|
|
214
|
+
PlaybackErrorCode.SOURCE_UNREACHABLE,
|
|
215
|
+
PlaybackErrorMapping.classify(notFound)
|
|
216
|
+
)
|
|
141
217
|
}
|
|
142
218
|
|
|
143
219
|
// --- PlaybackErrorMapping.isTransient classification
|
|
@@ -230,7 +306,7 @@ class TrackPlayerEventsTest {
|
|
|
230
306
|
// the public effects — a new configure + getStateInternal
|
|
231
307
|
// should return NONE.
|
|
232
308
|
player.configureInternal(
|
|
233
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
309
|
+
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()
|
|
234
310
|
)
|
|
235
311
|
assertEquals(PlayerState.NONE, player.getStateInternal())
|
|
236
312
|
}
|
|
@@ -246,8 +322,8 @@ class TrackPlayerEventsTest {
|
|
|
246
322
|
// - listener must NOT clobber the logical -1 from the Kotlin
|
|
247
323
|
// side with the ExoPlayer-derived 0
|
|
248
324
|
player.tracks = listOf(
|
|
249
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
250
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null)
|
|
325
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
326
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
251
327
|
)
|
|
252
328
|
player.currentTrackIndex = -1
|
|
253
329
|
|
|
@@ -267,7 +343,7 @@ class TrackPlayerEventsTest {
|
|
|
267
343
|
// before issuing player.seekTo — the subsequent REASON_SEEK
|
|
268
344
|
// listener fire must not reset it from the player.
|
|
269
345
|
player.tracks = (1..5).map {
|
|
270
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
346
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
271
347
|
}
|
|
272
348
|
player.currentTrackIndex = 3
|
|
273
349
|
|
|
@@ -281,7 +357,7 @@ class TrackPlayerEventsTest {
|
|
|
281
357
|
@Test
|
|
282
358
|
fun `handleMediaItemTransition REASON_SEEK consumes the pending track change reason`() {
|
|
283
359
|
player.tracks = (1..5).map {
|
|
284
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
360
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
285
361
|
}
|
|
286
362
|
player.currentTrackIndex = 2
|
|
287
363
|
|
|
@@ -305,7 +381,7 @@ class TrackPlayerEventsTest {
|
|
|
305
381
|
@Test
|
|
306
382
|
fun `handleMediaItemTransition REASON_SEEK without stashed reason falls back to SEEK`() {
|
|
307
383
|
player.tracks = (1..5).map {
|
|
308
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
384
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
309
385
|
}
|
|
310
386
|
player.currentTrackIndex = 2
|
|
311
387
|
|
|
@@ -321,11 +397,11 @@ class TrackPlayerEventsTest {
|
|
|
321
397
|
|
|
322
398
|
@Test
|
|
323
399
|
fun `handleMediaItemTransition fires callback with current track snapshot`() {
|
|
324
|
-
val t2 = TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null)
|
|
400
|
+
val t2 = TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
325
401
|
player.tracks = listOf(
|
|
326
|
-
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null),
|
|
402
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
327
403
|
t2,
|
|
328
|
-
TrackItem("t3", "https://example.com/t3.m4a", null, null, null, null, null, null)
|
|
404
|
+
TrackItem("t3", "https://example.com/t3.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
329
405
|
)
|
|
330
406
|
player.currentTrackIndex = 1
|
|
331
407
|
|
|
@@ -353,8 +429,8 @@ class TrackPlayerEventsTest {
|
|
|
353
429
|
@Test
|
|
354
430
|
fun `handleMediaItemTransition PLAYLIST_CHANGED reports null transitionGapMs`() {
|
|
355
431
|
player.tracks = listOf(
|
|
356
|
-
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null),
|
|
357
|
-
TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null)
|
|
432
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
433
|
+
TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
358
434
|
)
|
|
359
435
|
player.currentTrackIndex = 0
|
|
360
436
|
|
|
@@ -376,7 +452,7 @@ class TrackPlayerEventsTest {
|
|
|
376
452
|
@Test
|
|
377
453
|
fun `handleMediaItemTransition SEEK reports null transitionGapMs`() {
|
|
378
454
|
player.tracks = (1..3).map {
|
|
379
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
455
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
380
456
|
}
|
|
381
457
|
player.currentTrackIndex = 1
|
|
382
458
|
|
|
@@ -395,7 +471,7 @@ class TrackPlayerEventsTest {
|
|
|
395
471
|
@Test
|
|
396
472
|
fun `maybeEmitQueueEnd does nothing on non-ENDED states`() {
|
|
397
473
|
player.tracks = listOf(
|
|
398
|
-
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null)
|
|
474
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
399
475
|
)
|
|
400
476
|
var queueEndFires = 0
|
|
401
477
|
player.onQueueEnd { queueEndFires += 1 }
|
|
@@ -425,7 +501,7 @@ class TrackPlayerEventsTest {
|
|
|
425
501
|
@Test
|
|
426
502
|
fun `maybeEmitQueueEnd fires queueEnd and ENDED state on STATE_ENDED with tracks`() {
|
|
427
503
|
player.tracks = (1..3).map {
|
|
428
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
504
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
429
505
|
}
|
|
430
506
|
|
|
431
507
|
var queueEndFires = 0
|
|
@@ -450,7 +526,7 @@ class TrackPlayerEventsTest {
|
|
|
450
526
|
fun `emitStateIfChanged defaults pending reason to SYSTEM on terminal state`() {
|
|
451
527
|
player.setQueueInternal(
|
|
452
528
|
(1..1).map {
|
|
453
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
529
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
454
530
|
}
|
|
455
531
|
)
|
|
456
532
|
// Manually reset dedup so the next call emits
|
|
@@ -479,7 +555,7 @@ class TrackPlayerEventsTest {
|
|
|
479
555
|
serviceHandle.destroy()
|
|
480
556
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
481
557
|
player.configureInternal(
|
|
482
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
558
|
+
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()
|
|
483
559
|
)
|
|
484
560
|
// No listener is bound to the new ExoPlayer's actual STATE_ENDED
|
|
485
561
|
// path in a Robolectric test, but the callback slot itself is
|
|
@@ -493,4 +569,12 @@ class TrackPlayerEventsTest {
|
|
|
493
569
|
unsubscribe()
|
|
494
570
|
assertFalse(fired)
|
|
495
571
|
}
|
|
572
|
+
|
|
573
|
+
private fun networkFailure() = androidx.media3.common.PlaybackException(
|
|
574
|
+
"boom", null,
|
|
575
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED)
|
|
576
|
+
|
|
577
|
+
private fun decodingFailure() = androidx.media3.common.PlaybackException(
|
|
578
|
+
"bad frame", null,
|
|
579
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED)
|
|
496
580
|
}
|
|
@@ -4,6 +4,7 @@ import androidx.media3.common.util.UnstableApi
|
|
|
4
4
|
import androidx.test.core.app.ApplicationProvider
|
|
5
5
|
import org.junit.After
|
|
6
6
|
import org.junit.Assert.assertEquals
|
|
7
|
+
import org.junit.Assert.assertFalse
|
|
7
8
|
import org.junit.Assert.assertNotEquals
|
|
8
9
|
import org.junit.Assert.assertNotNull
|
|
9
10
|
import org.junit.Assert.assertNull
|
|
@@ -58,18 +59,293 @@ class TrackPlayerLifecycleTest {
|
|
|
58
59
|
serviceHandle.destroy()
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
@Test
|
|
63
|
+
fun `a failure on a track that is not playing is reported, not swallowed`() {
|
|
64
|
+
seedThreeTrackQueue()
|
|
65
|
+
|
|
66
|
+
var reported = 0
|
|
67
|
+
val dispose = player.onError { reported += 1 }
|
|
68
|
+
val states = mutableListOf<PlayerState>()
|
|
69
|
+
val disposeState = player.onStateChange { state, _ -> states.add(state) }
|
|
70
|
+
// Index 2 is not the current track — the shape a standby preroll failure
|
|
71
|
+
// arrives in. The engine argument is not read by the handler.
|
|
72
|
+
player.onError(
|
|
73
|
+
serviceHandle.engine,
|
|
74
|
+
androidx.media3.common.PlaybackException(
|
|
75
|
+
"boom", null,
|
|
76
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED),
|
|
77
|
+
2)
|
|
78
|
+
dispose()
|
|
79
|
+
disposeState()
|
|
80
|
+
|
|
81
|
+
assertEquals("the failure is surfaced rather than swallowed", 1, reported)
|
|
82
|
+
assertFalse(
|
|
83
|
+
"the state is the current track's, which is still playing",
|
|
84
|
+
states.contains(PlayerState.ERROR))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A repeat of the same failure while the attempt is pending stays inside the
|
|
89
|
+
* library.
|
|
90
|
+
*/
|
|
91
|
+
@Test
|
|
92
|
+
fun `an echo of the pending failure is not reported`() {
|
|
93
|
+
seedThreeTrackQueue()
|
|
94
|
+
|
|
95
|
+
var reported = 0
|
|
96
|
+
val dispose = player.onError { reported += 1 }
|
|
97
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
98
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
99
|
+
dispose()
|
|
100
|
+
|
|
101
|
+
assertEquals("the pending attempt owns both fires", 0, reported)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A failure after the attempt has run is the consumer's to see. */
|
|
105
|
+
@Test
|
|
106
|
+
fun `a failure after the attempt is reported`() {
|
|
107
|
+
seedThreeTrackQueue()
|
|
108
|
+
|
|
109
|
+
var reported = 0
|
|
110
|
+
val dispose = player.onError { reported += 1 }
|
|
111
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
112
|
+
player.retryFailedItem(0)
|
|
113
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
114
|
+
dispose()
|
|
115
|
+
|
|
116
|
+
assertEquals("the attempt was spent and the track is still broken", 1, reported)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Inserting ahead of a track moves it, and its retry budget moves with it.
|
|
121
|
+
* A budget of two with one attempt spent is the one value that tells the
|
|
122
|
+
* moved track apart from both a fresh entry (two left) and a dropped one
|
|
123
|
+
* (none left) at its new index.
|
|
124
|
+
*/
|
|
125
|
+
@Test
|
|
126
|
+
fun `a track keeps its retry budget when a track is inserted before it`() {
|
|
127
|
+
seedThreeTrackQueue(autoRetries = 2.0)
|
|
128
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
129
|
+
player.retryFailedItem(0)
|
|
130
|
+
|
|
131
|
+
player.addToQueueInternal(listOf(track("https://example.com/new.mp3")), insertBefore = 0)
|
|
132
|
+
assertEquals("the playing track moved to index 1", 1, player.currentTrackIndex)
|
|
133
|
+
|
|
134
|
+
var reported = 0
|
|
135
|
+
val dispose = player.onError { reported += 1 }
|
|
136
|
+
player.onError(serviceHandle.engine, networkFailure(), 1)
|
|
137
|
+
assertEquals(
|
|
138
|
+
"one attempt is left at the new index, so the failure is retried silently; "
|
|
139
|
+
+ "a dropped budget would have reported it",
|
|
140
|
+
0, reported)
|
|
141
|
+
player.retryFailedItem(1)
|
|
142
|
+
player.onError(serviceHandle.engine, networkFailure(), 1)
|
|
143
|
+
dispose()
|
|
144
|
+
|
|
145
|
+
assertEquals(
|
|
146
|
+
"the second attempt was spent, so this failure is reported; "
|
|
147
|
+
+ "a fresh budget of two would still be silent",
|
|
148
|
+
1, reported)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A user's retry withdraws the automatic attempt scheduled for the same
|
|
153
|
+
* track: left posted, it would fire after the backoff and rebuild the track
|
|
154
|
+
* a second time.
|
|
155
|
+
*/
|
|
156
|
+
@Test
|
|
157
|
+
fun `a retry withdraws the scheduled automatic attempt`() {
|
|
158
|
+
player.configureInternal(config(retryBackoffMs = 500.0), context())
|
|
159
|
+
player.setQueueInternal((0 until 3).map { track("https://example.com/$it.mp3") })
|
|
160
|
+
player.currentTrackIndex = 0
|
|
161
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
162
|
+
|
|
163
|
+
player.retryFailedItem(0)
|
|
164
|
+
val looper = org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper())
|
|
165
|
+
looper.idle()
|
|
166
|
+
// The retry rebuilt the item once (a remove and an add on the playlist);
|
|
167
|
+
// whatever the looper still holds for the backoff must not rebuild again.
|
|
168
|
+
// Only playlist changes count: the playback thread reports its own
|
|
169
|
+
// source refresh after the prepare, on its own schedule.
|
|
170
|
+
var timelineChanges = 0
|
|
171
|
+
val listener = object : androidx.media3.common.Player.Listener {
|
|
172
|
+
override fun onTimelineChanged(timeline: androidx.media3.common.Timeline, reason: Int) {
|
|
173
|
+
if (reason == androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
|
|
174
|
+
timelineChanges += 1
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
player.player!!.addListener(listener)
|
|
179
|
+
looper.idleFor(java.time.Duration.ofMillis(1_000))
|
|
180
|
+
player.player!!.removeListener(listener)
|
|
181
|
+
|
|
182
|
+
assertEquals(
|
|
183
|
+
"the automatic attempt fired after the user's retry and rebuilt the track again",
|
|
184
|
+
0, timelineChanges)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The track that ends up at a position after a queue change reports its own
|
|
189
|
+
* failure instead of being taken for an echo of the old queue's attempt.
|
|
190
|
+
*/
|
|
191
|
+
@Test
|
|
192
|
+
fun `a failure on the replacement queue is not taken for an echo of the old one`() {
|
|
193
|
+
seedThreeTrackQueue()
|
|
194
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
195
|
+
|
|
196
|
+
// The replacement queue gets no automatic attempt, so a failure on it can
|
|
197
|
+
// only be suppressed by a marker left over from the queue before it.
|
|
198
|
+
player.config = config(autoRetries = 0.0)
|
|
199
|
+
player.setQueueInternal((0 until 2).map {
|
|
200
|
+
track("https://example.com/next-$it.mp3")
|
|
201
|
+
})
|
|
202
|
+
player.currentTrackIndex = 0
|
|
203
|
+
|
|
204
|
+
var reported = 0
|
|
205
|
+
val dispose = player.onError { reported += 1 }
|
|
206
|
+
player.onError(serviceHandle.engine, networkFailure(), 0)
|
|
207
|
+
dispose()
|
|
208
|
+
|
|
209
|
+
assertEquals("the attempt belonged to a queue that is gone", 1, reported)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private fun config(
|
|
213
|
+
httpHeaders: Map<String, String>? = null,
|
|
214
|
+
userAgent: String? = null,
|
|
215
|
+
autoRetries: Double? = null,
|
|
216
|
+
retryBackoffMs: Double? = null,
|
|
217
|
+
) = PlayerConfig(
|
|
218
|
+
httpHeaders = httpHeaders, userAgent = userAgent, autoRetries = autoRetries,
|
|
219
|
+
retryBackoffMs = retryBackoffMs, networkTimeoutMs = null, audioContentType = null,
|
|
220
|
+
audioCategoryOptions = null, voiceVocabularyDonationEnabled = null,
|
|
221
|
+
visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null,
|
|
222
|
+
lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null,
|
|
223
|
+
backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null,
|
|
224
|
+
skipToPreviousBehavior = null)
|
|
225
|
+
|
|
226
|
+
private fun track(url: String) = TrackItem(
|
|
227
|
+
id = null, url = url, title = null, artist = null, album = null, duration = null,
|
|
228
|
+
artworkUrl = null, extras = null, replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
229
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null)
|
|
230
|
+
|
|
231
|
+
private fun networkFailure() = androidx.media3.common.PlaybackException(
|
|
232
|
+
"boom", null,
|
|
233
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED)
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Under crossfade the standby leg prerolls the next track, so a broken one
|
|
237
|
+
* fails while the leading track is still playing and is surfaced there. The
|
|
238
|
+
* engine then seats it and it fails again as the current track — the listener
|
|
239
|
+
* asked for one thing and must be told once.
|
|
240
|
+
*/
|
|
241
|
+
@Test
|
|
242
|
+
fun `a track reported while it was prerolling does not report again when it is seated`() {
|
|
243
|
+
seedThreeTrackQueue(autoRetries = 0.0)
|
|
244
|
+
|
|
245
|
+
var reported = 0
|
|
246
|
+
val dispose = player.onError { reported += 1 }
|
|
247
|
+
|
|
248
|
+
// The standby leg prerolling track 1 fails while track 0 is playing.
|
|
249
|
+
player.onError(serviceHandle.engine, fatalFailure(), 1)
|
|
250
|
+
assertEquals("the listener is told their next track is broken", 1, reported)
|
|
251
|
+
|
|
252
|
+
// The fade begins, so track 1 is the one being heard, and it fails again as
|
|
253
|
+
// the current track.
|
|
254
|
+
player.onCrossfadeBegin(serviceHandle.engine, 1)
|
|
255
|
+
assertEquals("the fade seats the track that was prerolling", 1, player.currentTrackIndex)
|
|
256
|
+
player.onError(serviceHandle.engine, fatalFailure(), 1)
|
|
257
|
+
dispose()
|
|
258
|
+
|
|
259
|
+
assertEquals("and not told a second time about the same track", 1, reported)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* A shuffle renumbers every position, so a record against the pre-shuffle
|
|
264
|
+
* current index names a different track afterwards. Nothing is carried.
|
|
265
|
+
*/
|
|
266
|
+
@Test
|
|
267
|
+
fun `a shuffle leaves no track marked as stopped`() {
|
|
268
|
+
seedThreeTrackQueue(autoRetries = 0.0)
|
|
269
|
+
player.onError(serviceHandle.engine, fatalFailure(), 0)
|
|
270
|
+
|
|
271
|
+
player.shuffleQueueInternal()
|
|
272
|
+
|
|
273
|
+
assertEquals("a shuffle seats position 0", 0, player.currentTrackIndex)
|
|
274
|
+
// A stop standing against the current track silences every other track's
|
|
275
|
+
// failure, so a report for one of them is what shows the set is empty.
|
|
276
|
+
var reported = 0
|
|
277
|
+
val dispose = player.onError { reported += 1 }
|
|
278
|
+
player.onError(serviceHandle.engine, fatalFailure(), 1)
|
|
279
|
+
dispose()
|
|
280
|
+
|
|
281
|
+
assertEquals("no track is holding the queue after a shuffle", 1, reported)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* `-1` means "no track", so a failure that carries no index — an engine that
|
|
286
|
+
* reports without one, or a queue that has been cleared — must not enter the
|
|
287
|
+
* set. A member equal to `currentTrackIndex` is what "stopped" means, and at
|
|
288
|
+
* `-1` that would read as stopped and silence every later failure.
|
|
289
|
+
*/
|
|
290
|
+
@Test
|
|
291
|
+
fun `a failure with no track does not mark the queue as stopped`() {
|
|
292
|
+
player.configureInternal(config(autoRetries = 0.0), context())
|
|
293
|
+
player.setQueueInternal(emptyList())
|
|
294
|
+
assertEquals("an empty queue seats no track", -1, player.currentTrackIndex)
|
|
295
|
+
|
|
296
|
+
// An engine that reports without an index — AirPlay does — while the queue
|
|
297
|
+
// is empty. There is no position to record.
|
|
298
|
+
player.onError(serviceHandle.engine, fatalFailure())
|
|
299
|
+
|
|
300
|
+
// The add empties the set and restores the current track's entry, so with
|
|
301
|
+
// -1 a member that restore is what carries it forward. It leaves the index
|
|
302
|
+
// at -1 by design, which is what keeps it a member.
|
|
303
|
+
player.addToQueueInternal(
|
|
304
|
+
(0 until 2).map { track("https://example.com/$it.mp3") }, insertBefore = 0)
|
|
305
|
+
assertEquals("an add into an empty queue seats nothing", -1, player.currentTrackIndex)
|
|
306
|
+
|
|
307
|
+
var reported = 0
|
|
308
|
+
val dispose = player.onError { reported += 1 }
|
|
309
|
+
player.onError(serviceHandle.engine, fatalFailure(), 1)
|
|
310
|
+
dispose()
|
|
311
|
+
|
|
312
|
+
assertEquals(
|
|
313
|
+
"a track's failure still reaches the listener — nothing is holding the queue",
|
|
314
|
+
1, reported)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private fun fatalFailure() = androidx.media3.common.PlaybackException(
|
|
318
|
+
"boom", null,
|
|
319
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE)
|
|
320
|
+
|
|
321
|
+
private fun seedThreeTrackQueue(autoRetries: Double? = null) {
|
|
322
|
+
player.configureInternal(config(autoRetries = autoRetries), context())
|
|
323
|
+
player.setQueueInternal((0 until 3).map { track("https://example.com/$it.mp3") })
|
|
324
|
+
player.currentTrackIndex = 0
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@Test
|
|
328
|
+
fun `the automatic retry budget is one attempt by default`() {
|
|
329
|
+
assertEquals(1, player.effectiveAutoRetries())
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@Test
|
|
333
|
+
fun `the consumer can ask for none or more`() {
|
|
334
|
+
player.config = config(autoRetries = 0.0)
|
|
335
|
+
assertEquals(0, player.effectiveAutoRetries())
|
|
336
|
+
player.config = config(autoRetries = 4.0)
|
|
337
|
+
assertEquals(4, player.effectiveAutoRetries())
|
|
338
|
+
player.config = config(autoRetries = 99.0)
|
|
339
|
+
assertEquals(5, player.effectiveAutoRetries())
|
|
340
|
+
player.config = config()
|
|
341
|
+
assertEquals(1, player.effectiveAutoRetries())
|
|
342
|
+
}
|
|
343
|
+
|
|
61
344
|
@Test
|
|
62
345
|
fun `configureInternal builds an ExoPlayer and stores config`() {
|
|
63
346
|
assertNull("player not yet built before configure", player.player)
|
|
64
347
|
|
|
65
|
-
val config =
|
|
66
|
-
httpHeaders = mapOf("X-Foo" to "bar"),
|
|
67
|
-
userAgent = "rnqp-test/0.0",
|
|
68
|
-
autoRetries = null,
|
|
69
|
-
retryBackoffMs = null,
|
|
70
|
-
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
71
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
72
|
-
)
|
|
348
|
+
val config = config(httpHeaders = mapOf("X-Foo" to "bar"), userAgent = "rnqp-test/0.0")
|
|
73
349
|
player.configureInternal(config, context())
|
|
74
350
|
|
|
75
351
|
assertNotNull("configure must build ExoPlayer", player.player)
|
|
@@ -87,12 +363,12 @@ class TrackPlayerLifecycleTest {
|
|
|
87
363
|
fun `configure destroys then rebuilds — second call yields a fresh ExoPlayer`() {
|
|
88
364
|
// Universal `configure()` auto-destroy contract: every call to
|
|
89
365
|
// the public configure tears down any prior state before
|
|
90
|
-
// reinitialising. The internal `configureInternal`
|
|
366
|
+
// reinitialising. The internal `configureInternal` entry point is
|
|
91
367
|
// purely constructive (assumes `player == null` on entry); the
|
|
92
368
|
// public path owns the destroy step. This test drives the
|
|
93
369
|
// destroy + reconstruct shape directly to mirror what
|
|
94
370
|
// `configure()` does end-to-end.
|
|
95
|
-
val empty =
|
|
371
|
+
val empty = config()
|
|
96
372
|
|
|
97
373
|
player.configureInternal(empty, context())
|
|
98
374
|
val firstInstance = player.player
|
|
@@ -122,16 +398,8 @@ class TrackPlayerLifecycleTest {
|
|
|
122
398
|
|
|
123
399
|
@Test
|
|
124
400
|
fun `configureInternal stores the latest config on repeat call`() {
|
|
125
|
-
val first =
|
|
126
|
-
|
|
127
|
-
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
128
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
129
|
-
)
|
|
130
|
-
val second = PlayerConfig(
|
|
131
|
-
httpHeaders = mapOf("B" to "2"), userAgent = "ua-2",
|
|
132
|
-
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
133
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
134
|
-
)
|
|
401
|
+
val first = config(httpHeaders = mapOf("A" to "1"), userAgent = "ua-1")
|
|
402
|
+
val second = config(httpHeaders = mapOf("B" to "2"), userAgent = "ua-2")
|
|
135
403
|
|
|
136
404
|
player.configureInternal(first, context())
|
|
137
405
|
player.configureInternal(second, context())
|
|
@@ -141,11 +409,7 @@ class TrackPlayerLifecycleTest {
|
|
|
141
409
|
|
|
142
410
|
@Test
|
|
143
411
|
fun `destroyInternal releases ExoPlayer + cache, persists user config`() {
|
|
144
|
-
val configured =
|
|
145
|
-
httpHeaders = mapOf("h" to "v"), userAgent = "ua",
|
|
146
|
-
autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null,
|
|
147
|
-
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
148
|
-
)
|
|
412
|
+
val configured = config(httpHeaders = mapOf("h" to "v"), userAgent = "ua")
|
|
149
413
|
player.configureInternal(configured, context())
|
|
150
414
|
// Non-default playback state the consumer set post-configure.
|
|
151
415
|
player.repeatModeState = RepeatMode.QUEUE
|
|
@@ -180,7 +444,7 @@ class TrackPlayerLifecycleTest {
|
|
|
180
444
|
@Test
|
|
181
445
|
fun `destroyInternal is safe to call twice`() {
|
|
182
446
|
player.configureInternal(
|
|
183
|
-
|
|
447
|
+
config(), context()
|
|
184
448
|
)
|
|
185
449
|
player.destroyInternal()
|
|
186
450
|
player.destroyInternal()
|
|
@@ -189,7 +453,7 @@ class TrackPlayerLifecycleTest {
|
|
|
189
453
|
|
|
190
454
|
@Test
|
|
191
455
|
fun `configureInternal after destroyInternal rebuilds the ExoPlayer`() {
|
|
192
|
-
val cfg =
|
|
456
|
+
val cfg = config()
|
|
193
457
|
|
|
194
458
|
player.configureInternal(cfg, context())
|
|
195
459
|
val first = player.player
|