react-native-queue-player 1.2.0 → 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/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 +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 +839 -271
- 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 +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 +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 +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 +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 +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 +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/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 +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/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 -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,84 @@ 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)
|
|
141
183
|
}
|
|
142
184
|
|
|
143
185
|
// --- PlaybackErrorMapping.isTransient classification
|
|
@@ -230,7 +272,7 @@ class TrackPlayerEventsTest {
|
|
|
230
272
|
// the public effects — a new configure + getStateInternal
|
|
231
273
|
// should return NONE.
|
|
232
274
|
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()
|
|
275
|
+
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
276
|
)
|
|
235
277
|
assertEquals(PlayerState.NONE, player.getStateInternal())
|
|
236
278
|
}
|
|
@@ -246,8 +288,8 @@ class TrackPlayerEventsTest {
|
|
|
246
288
|
// - listener must NOT clobber the logical -1 from the Kotlin
|
|
247
289
|
// side with the ExoPlayer-derived 0
|
|
248
290
|
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)
|
|
291
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
292
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
251
293
|
)
|
|
252
294
|
player.currentTrackIndex = -1
|
|
253
295
|
|
|
@@ -267,7 +309,7 @@ class TrackPlayerEventsTest {
|
|
|
267
309
|
// before issuing player.seekTo — the subsequent REASON_SEEK
|
|
268
310
|
// listener fire must not reset it from the player.
|
|
269
311
|
player.tracks = (1..5).map {
|
|
270
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
312
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
271
313
|
}
|
|
272
314
|
player.currentTrackIndex = 3
|
|
273
315
|
|
|
@@ -281,7 +323,7 @@ class TrackPlayerEventsTest {
|
|
|
281
323
|
@Test
|
|
282
324
|
fun `handleMediaItemTransition REASON_SEEK consumes the pending track change reason`() {
|
|
283
325
|
player.tracks = (1..5).map {
|
|
284
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
326
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
285
327
|
}
|
|
286
328
|
player.currentTrackIndex = 2
|
|
287
329
|
|
|
@@ -305,7 +347,7 @@ class TrackPlayerEventsTest {
|
|
|
305
347
|
@Test
|
|
306
348
|
fun `handleMediaItemTransition REASON_SEEK without stashed reason falls back to SEEK`() {
|
|
307
349
|
player.tracks = (1..5).map {
|
|
308
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
350
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
309
351
|
}
|
|
310
352
|
player.currentTrackIndex = 2
|
|
311
353
|
|
|
@@ -321,11 +363,11 @@ class TrackPlayerEventsTest {
|
|
|
321
363
|
|
|
322
364
|
@Test
|
|
323
365
|
fun `handleMediaItemTransition fires callback with current track snapshot`() {
|
|
324
|
-
val t2 = TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null)
|
|
366
|
+
val t2 = TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
325
367
|
player.tracks = listOf(
|
|
326
|
-
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null),
|
|
368
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
327
369
|
t2,
|
|
328
|
-
TrackItem("t3", "https://example.com/t3.m4a", null, null, null, null, null, null)
|
|
370
|
+
TrackItem("t3", "https://example.com/t3.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
329
371
|
)
|
|
330
372
|
player.currentTrackIndex = 1
|
|
331
373
|
|
|
@@ -353,8 +395,8 @@ class TrackPlayerEventsTest {
|
|
|
353
395
|
@Test
|
|
354
396
|
fun `handleMediaItemTransition PLAYLIST_CHANGED reports null transitionGapMs`() {
|
|
355
397
|
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)
|
|
398
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
399
|
+
TrackItem("t2", "https://example.com/t2.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
358
400
|
)
|
|
359
401
|
player.currentTrackIndex = 0
|
|
360
402
|
|
|
@@ -376,7 +418,7 @@ class TrackPlayerEventsTest {
|
|
|
376
418
|
@Test
|
|
377
419
|
fun `handleMediaItemTransition SEEK reports null transitionGapMs`() {
|
|
378
420
|
player.tracks = (1..3).map {
|
|
379
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
421
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
380
422
|
}
|
|
381
423
|
player.currentTrackIndex = 1
|
|
382
424
|
|
|
@@ -395,7 +437,7 @@ class TrackPlayerEventsTest {
|
|
|
395
437
|
@Test
|
|
396
438
|
fun `maybeEmitQueueEnd does nothing on non-ENDED states`() {
|
|
397
439
|
player.tracks = listOf(
|
|
398
|
-
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null)
|
|
440
|
+
TrackItem("t1", "https://example.com/t1.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
399
441
|
)
|
|
400
442
|
var queueEndFires = 0
|
|
401
443
|
player.onQueueEnd { queueEndFires += 1 }
|
|
@@ -425,7 +467,7 @@ class TrackPlayerEventsTest {
|
|
|
425
467
|
@Test
|
|
426
468
|
fun `maybeEmitQueueEnd fires queueEnd and ENDED state on STATE_ENDED with tracks`() {
|
|
427
469
|
player.tracks = (1..3).map {
|
|
428
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
470
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
429
471
|
}
|
|
430
472
|
|
|
431
473
|
var queueEndFires = 0
|
|
@@ -450,7 +492,7 @@ class TrackPlayerEventsTest {
|
|
|
450
492
|
fun `emitStateIfChanged defaults pending reason to SYSTEM on terminal state`() {
|
|
451
493
|
player.setQueueInternal(
|
|
452
494
|
(1..1).map {
|
|
453
|
-
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null)
|
|
495
|
+
TrackItem("t$it", "https://example.com/t$it.m4a", null, null, null, null, null, null, null, null, null, null)
|
|
454
496
|
}
|
|
455
497
|
)
|
|
456
498
|
// Manually reset dedup so the next call emits
|
|
@@ -479,7 +521,7 @@ class TrackPlayerEventsTest {
|
|
|
479
521
|
serviceHandle.destroy()
|
|
480
522
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
481
523
|
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()
|
|
524
|
+
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
525
|
)
|
|
484
526
|
// No listener is bound to the new ExoPlayer's actual STATE_ENDED
|
|
485
527
|
// path in a Robolectric test, but the callback slot itself is
|
|
@@ -493,4 +535,12 @@ class TrackPlayerEventsTest {
|
|
|
493
535
|
unsubscribe()
|
|
494
536
|
assertFalse(fired)
|
|
495
537
|
}
|
|
538
|
+
|
|
539
|
+
private fun networkFailure() = androidx.media3.common.PlaybackException(
|
|
540
|
+
"boom", null,
|
|
541
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED)
|
|
542
|
+
|
|
543
|
+
private fun decodingFailure() = androidx.media3.common.PlaybackException(
|
|
544
|
+
"bad frame", null,
|
|
545
|
+
androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED)
|
|
496
546
|
}
|
|
@@ -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,207 @@ 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
|
+
private fun seedThreeTrackQueue(autoRetries: Double? = null) {
|
|
236
|
+
player.configureInternal(config(autoRetries = autoRetries), context())
|
|
237
|
+
player.setQueueInternal((0 until 3).map { track("https://example.com/$it.mp3") })
|
|
238
|
+
player.currentTrackIndex = 0
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
@Test
|
|
242
|
+
fun `the automatic retry budget is one attempt by default`() {
|
|
243
|
+
assertEquals(1, player.effectiveAutoRetries())
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@Test
|
|
247
|
+
fun `the consumer can ask for none or more`() {
|
|
248
|
+
player.config = config(autoRetries = 0.0)
|
|
249
|
+
assertEquals(0, player.effectiveAutoRetries())
|
|
250
|
+
player.config = config(autoRetries = 4.0)
|
|
251
|
+
assertEquals(4, player.effectiveAutoRetries())
|
|
252
|
+
player.config = config(autoRetries = 99.0)
|
|
253
|
+
assertEquals(5, player.effectiveAutoRetries())
|
|
254
|
+
player.config = config()
|
|
255
|
+
assertEquals(1, player.effectiveAutoRetries())
|
|
256
|
+
}
|
|
257
|
+
|
|
61
258
|
@Test
|
|
62
259
|
fun `configureInternal builds an ExoPlayer and stores config`() {
|
|
63
260
|
assertNull("player not yet built before configure", player.player)
|
|
64
261
|
|
|
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
|
-
)
|
|
262
|
+
val config = config(httpHeaders = mapOf("X-Foo" to "bar"), userAgent = "rnqp-test/0.0")
|
|
73
263
|
player.configureInternal(config, context())
|
|
74
264
|
|
|
75
265
|
assertNotNull("configure must build ExoPlayer", player.player)
|
|
@@ -87,12 +277,12 @@ class TrackPlayerLifecycleTest {
|
|
|
87
277
|
fun `configure destroys then rebuilds — second call yields a fresh ExoPlayer`() {
|
|
88
278
|
// Universal `configure()` auto-destroy contract: every call to
|
|
89
279
|
// the public configure tears down any prior state before
|
|
90
|
-
// reinitialising. The internal `configureInternal`
|
|
280
|
+
// reinitialising. The internal `configureInternal` entry point is
|
|
91
281
|
// purely constructive (assumes `player == null` on entry); the
|
|
92
282
|
// public path owns the destroy step. This test drives the
|
|
93
283
|
// destroy + reconstruct shape directly to mirror what
|
|
94
284
|
// `configure()` does end-to-end.
|
|
95
|
-
val empty =
|
|
285
|
+
val empty = config()
|
|
96
286
|
|
|
97
287
|
player.configureInternal(empty, context())
|
|
98
288
|
val firstInstance = player.player
|
|
@@ -122,16 +312,8 @@ class TrackPlayerLifecycleTest {
|
|
|
122
312
|
|
|
123
313
|
@Test
|
|
124
314
|
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
|
-
)
|
|
315
|
+
val first = config(httpHeaders = mapOf("A" to "1"), userAgent = "ua-1")
|
|
316
|
+
val second = config(httpHeaders = mapOf("B" to "2"), userAgent = "ua-2")
|
|
135
317
|
|
|
136
318
|
player.configureInternal(first, context())
|
|
137
319
|
player.configureInternal(second, context())
|
|
@@ -141,11 +323,7 @@ class TrackPlayerLifecycleTest {
|
|
|
141
323
|
|
|
142
324
|
@Test
|
|
143
325
|
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
|
-
)
|
|
326
|
+
val configured = config(httpHeaders = mapOf("h" to "v"), userAgent = "ua")
|
|
149
327
|
player.configureInternal(configured, context())
|
|
150
328
|
// Non-default playback state the consumer set post-configure.
|
|
151
329
|
player.repeatModeState = RepeatMode.QUEUE
|
|
@@ -180,7 +358,7 @@ class TrackPlayerLifecycleTest {
|
|
|
180
358
|
@Test
|
|
181
359
|
fun `destroyInternal is safe to call twice`() {
|
|
182
360
|
player.configureInternal(
|
|
183
|
-
|
|
361
|
+
config(), context()
|
|
184
362
|
)
|
|
185
363
|
player.destroyInternal()
|
|
186
364
|
player.destroyInternal()
|
|
@@ -189,7 +367,7 @@ class TrackPlayerLifecycleTest {
|
|
|
189
367
|
|
|
190
368
|
@Test
|
|
191
369
|
fun `configureInternal after destroyInternal rebuilds the ExoPlayer`() {
|
|
192
|
-
val cfg =
|
|
370
|
+
val cfg = config()
|
|
193
371
|
|
|
194
372
|
player.configureInternal(cfg, context())
|
|
195
373
|
val first = player.player
|