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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.media.AudioManager
|
|
5
|
+
import androidx.media3.common.MediaItem
|
|
6
|
+
import androidx.media3.common.PlaybackException
|
|
7
|
+
import androidx.media3.common.Player
|
|
8
|
+
import androidx.media3.common.util.UnstableApi
|
|
9
|
+
import androidx.test.core.app.ApplicationProvider
|
|
10
|
+
import org.junit.Assert.assertEquals
|
|
11
|
+
import org.junit.Assert.assertNotNull
|
|
12
|
+
import org.junit.Test
|
|
13
|
+
import org.junit.runner.RunWith
|
|
14
|
+
import org.robolectric.RobolectricTestRunner
|
|
15
|
+
import org.robolectric.Shadows.shadowOf
|
|
16
|
+
import org.robolectric.annotation.Config
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A crossfade engine owns audio focus itself, so the pause a focus loss
|
|
20
|
+
* makes reaches the delegate carrying the audio-focus reason, not the
|
|
21
|
+
* user-request reason `pause()` stamps by default.
|
|
22
|
+
*/
|
|
23
|
+
@RunWith(RobolectricTestRunner::class)
|
|
24
|
+
@Config(sdk = [34])
|
|
25
|
+
@UnstableApi
|
|
26
|
+
class CrossfadeEngineFocusLossTest {
|
|
27
|
+
|
|
28
|
+
private val context: Context = ApplicationProvider.getApplicationContext()
|
|
29
|
+
|
|
30
|
+
/** Records every play-when-ready reason the engine reports. */
|
|
31
|
+
private class RecordingDelegate : PlaybackEngineDelegate {
|
|
32
|
+
val reasons = mutableListOf<Int?>()
|
|
33
|
+
override fun onStateMaybeChanged(engine: PlaybackEngine, playWhenReadyReason: Int?) {
|
|
34
|
+
reasons.add(playWhenReadyReason)
|
|
35
|
+
}
|
|
36
|
+
override fun onIsPlayingChanged(engine: PlaybackEngine, isPlaying: Boolean) {}
|
|
37
|
+
override fun onTrackTransition(engine: PlaybackEngine, platformReason: Int) {}
|
|
38
|
+
override fun onActiveItemFormatChanged(engine: PlaybackEngine) {}
|
|
39
|
+
override fun onError(engine: PlaybackEngine, exception: PlaybackException, failedIndex: Int) {}
|
|
40
|
+
override fun onPlaybackEnded(engine: PlaybackEngine) {}
|
|
41
|
+
override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) {}
|
|
42
|
+
override fun onCrossfadeBegin(engine: PlaybackEngine, incomingIndex: Int) {}
|
|
43
|
+
override fun onCrossfadeCancel(engine: PlaybackEngine, revertedIndex: Int) {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private fun playingEngine(delegate: RecordingDelegate): CrossfadeEngine {
|
|
47
|
+
val engine = CrossfadeEngine(context = context, crossfadeDurationMs = 3_000L)
|
|
48
|
+
engine.delegate = delegate
|
|
49
|
+
engine.setMediaItems(listOf(MediaItem.fromUri("https://example.com/a.mp3")), 0, 0L)
|
|
50
|
+
engine.play()
|
|
51
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
52
|
+
return engine
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun focusListener(): AudioManager.OnAudioFocusChangeListener {
|
|
56
|
+
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
57
|
+
val request = shadowOf(audioManager).lastAudioFocusRequest
|
|
58
|
+
assertNotNull("play() must request focus", request)
|
|
59
|
+
return request.listener
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
fun `a focus loss pauses with the audio-focus reason`() {
|
|
64
|
+
val delegate = RecordingDelegate()
|
|
65
|
+
val engine = playingEngine(delegate)
|
|
66
|
+
delegate.reasons.clear()
|
|
67
|
+
|
|
68
|
+
focusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS)
|
|
69
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
70
|
+
|
|
71
|
+
assertEquals(
|
|
72
|
+
"the delegate stamps the last reason it is handed; a USER_REQUEST after the loss would label the pause a user pause",
|
|
73
|
+
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
|
|
74
|
+
delegate.reasons.last { it != null },
|
|
75
|
+
)
|
|
76
|
+
engine.release()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
fun `a transient focus loss pauses with the audio-focus reason too`() {
|
|
81
|
+
val delegate = RecordingDelegate()
|
|
82
|
+
val engine = playingEngine(delegate)
|
|
83
|
+
delegate.reasons.clear()
|
|
84
|
+
|
|
85
|
+
focusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)
|
|
86
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
87
|
+
|
|
88
|
+
assertEquals(
|
|
89
|
+
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
|
|
90
|
+
delegate.reasons.last { it != null },
|
|
91
|
+
)
|
|
92
|
+
engine.release()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun `a user pause after the loss keeps its own reason`() {
|
|
97
|
+
val delegate = RecordingDelegate()
|
|
98
|
+
val engine = playingEngine(delegate)
|
|
99
|
+
focusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)
|
|
100
|
+
focusListener().onAudioFocusChange(AudioManager.AUDIOFOCUS_GAIN)
|
|
101
|
+
engine.play()
|
|
102
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
103
|
+
delegate.reasons.clear()
|
|
104
|
+
|
|
105
|
+
engine.pause()
|
|
106
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
107
|
+
|
|
108
|
+
assertEquals(
|
|
109
|
+
"the substitution lasts only for the loss's own pauses",
|
|
110
|
+
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
|
111
|
+
delegate.reasons.last { it != null },
|
|
112
|
+
)
|
|
113
|
+
engine.release()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.margelo.nitro.queueplayer
|
|
2
2
|
|
|
3
|
+
import androidx.media3.common.MediaItem
|
|
3
4
|
import androidx.media3.common.util.UnstableApi
|
|
4
5
|
import androidx.test.core.app.ApplicationProvider
|
|
5
6
|
import org.junit.Assert.assertEquals
|
|
@@ -31,6 +32,49 @@ class CrossfadeEngineLifecycleTest {
|
|
|
31
32
|
crossfadeDurationMs = crossfadeDurationMs,
|
|
32
33
|
)
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Settling leaves the standby leg holding nothing.
|
|
37
|
+
*
|
|
38
|
+
* An armed standby carries the queue as it stood when it was armed. A caller
|
|
39
|
+
* that mutates the queue through a path the engine does not see — Media3
|
|
40
|
+
* setting resolved assistant items on the session player — needs that
|
|
41
|
+
* snapshot gone, or promoting it drops whatever the caller added.
|
|
42
|
+
*/
|
|
43
|
+
@Test
|
|
44
|
+
fun `settling a pending fade leaves nothing staged on the standby leg`() {
|
|
45
|
+
val engine = makeEngine()
|
|
46
|
+
engine.standbyPlayer.setMediaItems(
|
|
47
|
+
listOf(MediaItem.fromUri("http://example.com/next.mp3")))
|
|
48
|
+
engine.standbyPlayer.prepare()
|
|
49
|
+
assertEquals(1, engine.standbyPlayer.mediaItemCount)
|
|
50
|
+
|
|
51
|
+
engine.settlePendingFade()
|
|
52
|
+
|
|
53
|
+
assertEquals(0, engine.standbyPlayer.mediaItemCount)
|
|
54
|
+
engine.release()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A settle with nothing to settle leaves the legs as they are: a caller
|
|
59
|
+
* settling defensively must not swap them. Promotion itself needs a live
|
|
60
|
+
* fade, which needs real playback with duration metadata, so that half is
|
|
61
|
+
* covered by the crossfade demo suites.
|
|
62
|
+
*/
|
|
63
|
+
@Test
|
|
64
|
+
fun `settling by promotion leaves the roles alone when no fade is in flight`() {
|
|
65
|
+
val engine = makeEngine()
|
|
66
|
+
val leading = engine.leadingPlayer
|
|
67
|
+
val standby = engine.standbyPlayer
|
|
68
|
+
|
|
69
|
+
engine.settlePendingFadeByPromoting()
|
|
70
|
+
engine.settlePendingFadeByPromoting()
|
|
71
|
+
|
|
72
|
+
assertSame(leading, engine.leadingPlayer)
|
|
73
|
+
assertSame(standby, engine.standbyPlayer)
|
|
74
|
+
assertTrue(engine.currentRoleIsA)
|
|
75
|
+
engine.release()
|
|
76
|
+
}
|
|
77
|
+
|
|
34
78
|
@Test
|
|
35
79
|
fun `constructor accepts a non-negative crossfade duration`() {
|
|
36
80
|
val engine = makeEngine()
|
|
@@ -204,6 +248,7 @@ class CrossfadeEngineLifecycleTest {
|
|
|
204
248
|
override fun onError(
|
|
205
249
|
engine: PlaybackEngine,
|
|
206
250
|
exception: androidx.media3.common.PlaybackException,
|
|
251
|
+
failedIndex: Int,
|
|
207
252
|
) = Unit
|
|
208
253
|
override fun onPlaybackEnded(engine: PlaybackEngine) = Unit
|
|
209
254
|
override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) = Unit
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.media3.common.PlaybackException
|
|
5
|
+
import androidx.media3.common.util.UnstableApi
|
|
6
|
+
import androidx.test.core.app.ApplicationProvider
|
|
7
|
+
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Test
|
|
9
|
+
import org.junit.runner.RunWith
|
|
10
|
+
import org.robolectric.RobolectricTestRunner
|
|
11
|
+
import org.robolectric.Shadows.shadowOf
|
|
12
|
+
import org.robolectric.annotation.Config
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* When a player reaches STATE_ENDED the engines hand the delegate the end
|
|
16
|
+
* signal before the generic state callback, so the queue-end path owns the
|
|
17
|
+
* ENDED emit and its reason.
|
|
18
|
+
*/
|
|
19
|
+
@RunWith(RobolectricTestRunner::class)
|
|
20
|
+
@Config(sdk = [34])
|
|
21
|
+
@UnstableApi
|
|
22
|
+
class EngineEndSignalOrderTest {
|
|
23
|
+
|
|
24
|
+
private val context: Context = ApplicationProvider.getApplicationContext()
|
|
25
|
+
|
|
26
|
+
private class OrderingDelegate : PlaybackEngineDelegate {
|
|
27
|
+
val calls = mutableListOf<String>()
|
|
28
|
+
override fun onStateMaybeChanged(engine: PlaybackEngine, playWhenReadyReason: Int?) { calls.add("state") }
|
|
29
|
+
override fun onPlaybackEnded(engine: PlaybackEngine) { calls.add("ended") }
|
|
30
|
+
override fun onIsPlayingChanged(engine: PlaybackEngine, isPlaying: Boolean) {}
|
|
31
|
+
override fun onTrackTransition(engine: PlaybackEngine, platformReason: Int) {}
|
|
32
|
+
override fun onActiveItemFormatChanged(engine: PlaybackEngine) {}
|
|
33
|
+
override fun onError(engine: PlaybackEngine, exception: PlaybackException, failedIndex: Int) {}
|
|
34
|
+
override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) {}
|
|
35
|
+
override fun onCrossfadeBegin(engine: PlaybackEngine, incomingIndex: Int) {}
|
|
36
|
+
override fun onCrossfadeCancel(engine: PlaybackEngine, revertedIndex: Int) {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** An empty playlist prepared is STATE_ENDED at once. */
|
|
40
|
+
private fun assertEndedBeforeState(engine: PlaybackEngine, delegate: OrderingDelegate) {
|
|
41
|
+
engine.setMediaItems(emptyList(), 0, 0L)
|
|
42
|
+
shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
43
|
+
val ended = delegate.calls.indexOf("ended")
|
|
44
|
+
val stateAfter = delegate.calls.withIndex().any { it.value == "state" && it.index > ended }
|
|
45
|
+
assertEquals("the end signal must be delivered", true, ended >= 0)
|
|
46
|
+
assertEquals(
|
|
47
|
+
"the generic state callback follows the end signal: ${delegate.calls}",
|
|
48
|
+
true, stateAfter)
|
|
49
|
+
assertEquals(
|
|
50
|
+
"no state callback precedes the end signal for this transition: ${delegate.calls}",
|
|
51
|
+
-1, delegate.calls.subList(0, ended).lastIndexOf("state"))
|
|
52
|
+
engine.release()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun `gapless reports the end before the state`() {
|
|
57
|
+
val delegate = OrderingDelegate()
|
|
58
|
+
val engine = GaplessEngine(context = context)
|
|
59
|
+
engine.delegate = delegate
|
|
60
|
+
assertEndedBeforeState(engine, delegate)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
fun `crossfade reports the end before the state`() {
|
|
65
|
+
val delegate = OrderingDelegate()
|
|
66
|
+
val engine = CrossfadeEngine(context = context, crossfadeDurationMs = 3_000L)
|
|
67
|
+
engine.delegate = delegate
|
|
68
|
+
assertEndedBeforeState(engine, delegate)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -213,4 +213,21 @@ class EqualizerEngineTest {
|
|
|
213
213
|
engine.setBandGain(3, 4f)
|
|
214
214
|
assertEquals(4f, engine.getBandGains()[3])
|
|
215
215
|
}
|
|
216
|
+
|
|
217
|
+
/** A failed attach leaves no trace: nothing attached, and a retry is possible. */
|
|
218
|
+
@Test
|
|
219
|
+
@Config(sdk = [34], shadows = [ShadowDynamicsProcessingRejectingEnable::class])
|
|
220
|
+
fun `attach that fails while configuring leaves the engine detached`() {
|
|
221
|
+
val engine = EqualizerEngine()
|
|
222
|
+
|
|
223
|
+
val attached = engine.attach(audioSessionId = 801, channelCount = 2)
|
|
224
|
+
|
|
225
|
+
assertFalse("the attach reports its failure", attached)
|
|
226
|
+
assertFalse("nothing is attached after a failed configure", engine.isAttached())
|
|
227
|
+
assertEquals(
|
|
228
|
+
"the failed session is not carried as attached on the next diff",
|
|
229
|
+
mapOf(801 to false),
|
|
230
|
+
engine.attachAll(intArrayOf(801))
|
|
231
|
+
)
|
|
232
|
+
}
|
|
216
233
|
}
|
|
@@ -232,11 +232,6 @@ private class FakePlaybackEngine : PlaybackEngine {
|
|
|
232
232
|
override val mediaSessionPlayer: androidx.media3.exoplayer.ExoPlayer
|
|
233
233
|
get() = error("not used in test")
|
|
234
234
|
|
|
235
|
-
override suspend fun prepare(
|
|
236
|
-
mediaItems: List<androidx.media3.common.MediaItem>,
|
|
237
|
-
startIndex: Int,
|
|
238
|
-
startPositionMs: Long,
|
|
239
|
-
) = error("not used in test")
|
|
240
235
|
override fun release() = error("not used in test")
|
|
241
236
|
override fun setMediaItems(items: List<androidx.media3.common.MediaItem>, startIndex: Int, startPositionMs: Long) = error("not used in test")
|
|
242
237
|
override fun addMediaItems(items: List<androidx.media3.common.MediaItem>, insertBefore: Int) = error("not used in test")
|
|
@@ -268,6 +263,5 @@ private class FakePlaybackEngine : PlaybackEngine {
|
|
|
268
263
|
}
|
|
269
264
|
override fun attachAudioBufferSink(sink: androidx.media3.exoplayer.audio.TeeAudioProcessor.AudioBufferSink) = error("not used in test")
|
|
270
265
|
override fun detachAudioBufferSink(sink: androidx.media3.exoplayer.audio.TeeAudioProcessor.AudioBufferSink) = error("not used in test")
|
|
271
|
-
override suspend fun handoff(to: PlaybackEngine, atTrackBoundary: Boolean) = error("not used in test")
|
|
272
266
|
override var delegate: PlaybackEngineDelegate? = null
|
|
273
267
|
}
|
|
@@ -221,4 +221,21 @@ class EqualizerLegacyEngineTest {
|
|
|
221
221
|
} ?: error("no equalizer attached for session $sessionId")
|
|
222
222
|
return (effect as Equalizer).getBandLevel(band).toInt()
|
|
223
223
|
}
|
|
224
|
+
|
|
225
|
+
/** A failed attach leaves no trace: nothing attached, and a retry is possible. */
|
|
226
|
+
@Test
|
|
227
|
+
@Config(sdk = [27], shadows = [ShadowEqualizerRejectingBandWrites::class])
|
|
228
|
+
fun `attach that fails while configuring leaves the engine detached`() {
|
|
229
|
+
val engine = EqualizerLegacyEngine()
|
|
230
|
+
|
|
231
|
+
val attached = engine.attach(audioSessionId = 801, channelCount = 2)
|
|
232
|
+
|
|
233
|
+
assertFalse("the attach reports its failure", attached)
|
|
234
|
+
assertFalse("nothing is attached after a failed configure", engine.isAttached())
|
|
235
|
+
assertEquals(
|
|
236
|
+
"the failed session is not carried as attached on the next diff",
|
|
237
|
+
mapOf(801 to false),
|
|
238
|
+
engine.attachAll(intArrayOf(801))
|
|
239
|
+
)
|
|
240
|
+
}
|
|
224
241
|
}
|
|
@@ -181,4 +181,19 @@ class FFTProcessorTeeTest {
|
|
|
181
181
|
assertEquals(expectedPosition, original.position())
|
|
182
182
|
assertEquals(expectedOrder, original.order())
|
|
183
183
|
}
|
|
184
|
+
|
|
185
|
+
/** A flush resets the emit throttle, so the first frame of a new stream is not delayed. */
|
|
186
|
+
@Test
|
|
187
|
+
fun `flush resets the emit throttle`() {
|
|
188
|
+
val reported = mutableListOf<Int>()
|
|
189
|
+
val tee = makeTee(fftSize = 256, intervalMs = 1000) { _, _, sampleRate -> reported += sampleRate }
|
|
190
|
+
tee.flush(48000, 1, C.ENCODING_PCM_16BIT)
|
|
191
|
+
tee.handleBuffer(pcm16Buffer(ShortArray(256) { 1000 }))
|
|
192
|
+
assertEquals(listOf(48000), reported)
|
|
193
|
+
|
|
194
|
+
// Well inside the 1000 ms window: without the reset this emit is throttled.
|
|
195
|
+
tee.flush(96000, 1, C.ENCODING_PCM_16BIT)
|
|
196
|
+
tee.handleBuffer(pcm16Buffer(ShortArray(256) { 1000 }))
|
|
197
|
+
assertEquals(listOf(48000, 96000), reported)
|
|
198
|
+
}
|
|
184
199
|
}
|
|
@@ -13,8 +13,10 @@ import java.io.File
|
|
|
13
13
|
/**
|
|
14
14
|
* Unit tests for [FifoCacheEvictor]'s eviction ordering. Drives the evictor's
|
|
15
15
|
* [CacheEvictor] callbacks directly against a minimal fake [Cache] whose only
|
|
16
|
-
* live method is `removeSpan
|
|
17
|
-
*
|
|
16
|
+
* live method is `removeSpan`, so the tests assert insertion-order eviction
|
|
17
|
+
* without a real SimpleCache or filesystem. The fake normally delegates back to
|
|
18
|
+
* `onSpanRemoved`; `callsBack = false` models a span the cache index has already
|
|
19
|
+
* dropped, which notifies nothing.
|
|
18
20
|
*/
|
|
19
21
|
@UnstableApi
|
|
20
22
|
class FifoCacheEvictorTest {
|
|
@@ -72,11 +74,140 @@ class FifoCacheEvictorTest {
|
|
|
72
74
|
assertEquals(listOf("b"), removed.map { it.key })
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
@Test
|
|
78
|
+
fun `a url retires its insertion order once its last span is gone`() {
|
|
79
|
+
// The cache notifies an add even when the evictor's own set rejects the
|
|
80
|
+
// span, so a count taken from the callbacks over-counts. Here both adds are
|
|
81
|
+
// the same span, so one entry exists and one removal empties the url.
|
|
82
|
+
val evictor = FifoCacheEvictor(maxBytes = 1000)
|
|
83
|
+
val cache = FakeCache(evictor)
|
|
84
|
+
val only = CacheSpan("a", 0, 100, 1L, File("/tmp/a"))
|
|
85
|
+
evictor.onSpanAdded(cache, only)
|
|
86
|
+
evictor.onSpanAdded(cache, only)
|
|
87
|
+
evictor.onSpanAdded(cache, span("b", length = 100))
|
|
88
|
+
evictor.onSpanRemoved(cache, only)
|
|
89
|
+
removed.clear()
|
|
90
|
+
|
|
91
|
+
// `a` is empty, so re-adding it stamps a fresh newest order and `b` — now
|
|
92
|
+
// the oldest — is the victim. An over-count leaves `a` on its old order and
|
|
93
|
+
// evicts `a` instead.
|
|
94
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
95
|
+
evictor.onSpanAdded(cache, span("c", length = 900)) // 1100 > maxBytes
|
|
96
|
+
assertEquals(listOf("b"), removed.map { it.key })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
fun `a url keeps its insertion order while any of its spans remain`() {
|
|
101
|
+
val evictor = FifoCacheEvictor(maxBytes = 1000)
|
|
102
|
+
val cache = FakeCache(evictor)
|
|
103
|
+
val first = CacheSpan("a", 0, 100, 1L, File("/tmp/a-1"))
|
|
104
|
+
val second = CacheSpan("a", 100, 100, 1L, File("/tmp/a-2"))
|
|
105
|
+
evictor.onSpanAdded(cache, first)
|
|
106
|
+
evictor.onSpanAdded(cache, second)
|
|
107
|
+
evictor.onSpanAdded(cache, second) // rejected by the set, still notified
|
|
108
|
+
evictor.onSpanAdded(cache, span("b", length = 100))
|
|
109
|
+
evictor.onSpanRemoved(cache, first)
|
|
110
|
+
removed.clear()
|
|
111
|
+
|
|
112
|
+
// `a` still holds `second`, so it keeps its original (oldest) order and is
|
|
113
|
+
// still the first victim. A retired order would sort it newest and evict b.
|
|
114
|
+
evictor.onSpanAdded(cache, span("c", length = 900))
|
|
115
|
+
assertEquals("a", removed.first().key)
|
|
116
|
+
removed.clear()
|
|
117
|
+
|
|
118
|
+
// With `a` now empty, a re-add stamps a fresh newest order. An over-count
|
|
119
|
+
// from the rejected add leaves `a` on its old order and evicts it again.
|
|
120
|
+
evictor.onSpanRemoved(cache, second)
|
|
121
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
122
|
+
evictor.onSpanAdded(cache, span("d", length = 900))
|
|
123
|
+
assertEquals("b", removed.first().key)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@Test(timeout = 5_000)
|
|
127
|
+
fun `eviction advances past a span the cache no longer holds`() {
|
|
128
|
+
// removeSpan is a no-op here, modelling a span the cache index has already
|
|
129
|
+
// dropped: no onSpanRemoved callback, so the head does not move on its own.
|
|
130
|
+
// The evictor must retire its own record and carry on — spinning wedges the
|
|
131
|
+
// cache's lock, and giving up makes that span the victim of every later
|
|
132
|
+
// eviction, which stops eviction permanently.
|
|
133
|
+
val evictor = FifoCacheEvictor(maxBytes = 150)
|
|
134
|
+
val deaf = FakeCache(evictor, callsBack = false)
|
|
135
|
+
evictor.onSpanAdded(deaf, span("a", length = 100))
|
|
136
|
+
evictor.onSpanAdded(deaf, span("b", length = 100))
|
|
137
|
+
evictor.onSpanAdded(deaf, span("c", length = 100))
|
|
138
|
+
assertEquals(listOf("a", "b"), removed.map { it.key })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
fun `a protected url survives eviction and the next-oldest goes instead`() {
|
|
143
|
+
val evictor = FifoCacheEvictor(maxBytes = 250)
|
|
144
|
+
val cache = FakeCache(evictor)
|
|
145
|
+
evictor.setProtectedKeys(setOf("a"))
|
|
146
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
147
|
+
evictor.onSpanAdded(cache, span("b", length = 100))
|
|
148
|
+
evictor.onSpanAdded(cache, span("c", length = 100)) // 300 > 250
|
|
149
|
+
assertEquals(listOf("b"), removed.map { it.key })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Test(timeout = 5_000)
|
|
153
|
+
fun `eviction gives up when every remaining url is protected`() {
|
|
154
|
+
// Over budget with nothing evictable is a real state: the protected window
|
|
155
|
+
// can outgrow the budget. Staying over budget is the outcome; spinning
|
|
156
|
+
// under the cache's lock is not.
|
|
157
|
+
val evictor = FifoCacheEvictor(maxBytes = 150)
|
|
158
|
+
val cache = FakeCache(evictor)
|
|
159
|
+
evictor.setProtectedKeys(setOf("a", "b"))
|
|
160
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
161
|
+
evictor.onSpanAdded(cache, span("b", length = 100)) // 200 > 150, both shielded
|
|
162
|
+
assertEquals(emptyList<String>(), removed.map { it.key })
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@Test(timeout = 5_000)
|
|
166
|
+
fun `a protected span ahead of the victim does not stall the self-heal`() {
|
|
167
|
+
// The victim is not the head here: `a` is protected and sorts first, so a
|
|
168
|
+
// progress check that compared the head would never see the removal fail
|
|
169
|
+
// and would spin under the cache's lock.
|
|
170
|
+
val evictor = FifoCacheEvictor(maxBytes = 150)
|
|
171
|
+
val deaf = FakeCache(evictor, callsBack = false)
|
|
172
|
+
evictor.setProtectedKeys(setOf("a"))
|
|
173
|
+
evictor.onSpanAdded(deaf, span("a", length = 100))
|
|
174
|
+
evictor.onSpanAdded(deaf, span("b", length = 100)) // 200 > 150; a shielded
|
|
175
|
+
assertEquals(listOf("b"), removed.map { it.key })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@Test
|
|
179
|
+
fun `dropping protection makes the url evictable again`() {
|
|
180
|
+
val evictor = FifoCacheEvictor(maxBytes = 250)
|
|
181
|
+
val cache = FakeCache(evictor)
|
|
182
|
+
evictor.setProtectedKeys(setOf("a"))
|
|
183
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
184
|
+
evictor.onSpanAdded(cache, span("b", length = 100))
|
|
185
|
+
evictor.setProtectedKeys(emptySet())
|
|
186
|
+
evictor.onSpanAdded(cache, span("c", length = 100)) // 300 > 250
|
|
187
|
+
assertEquals(listOf("a"), removed.map { it.key })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@Test
|
|
191
|
+
fun `the protected set is copied so a caller's later mutation cannot leak in`() {
|
|
192
|
+
val evictor = FifoCacheEvictor(maxBytes = 250)
|
|
193
|
+
val cache = FakeCache(evictor)
|
|
194
|
+
val live = mutableSetOf("a")
|
|
195
|
+
evictor.setProtectedKeys(live)
|
|
196
|
+
live.add("b") // must not reach the evictor
|
|
197
|
+
evictor.onSpanAdded(cache, span("a", length = 100))
|
|
198
|
+
evictor.onSpanAdded(cache, span("b", length = 100))
|
|
199
|
+
evictor.onSpanAdded(cache, span("c", length = 100)) // 300 > 250
|
|
200
|
+
assertEquals(listOf("b"), removed.map { it.key })
|
|
201
|
+
}
|
|
202
|
+
|
|
75
203
|
/** Minimal [Cache]: only `removeSpan` is live (records + calls back the evictor). */
|
|
76
|
-
private inner class FakeCache(
|
|
204
|
+
private inner class FakeCache(
|
|
205
|
+
private val evictor: FifoCacheEvictor,
|
|
206
|
+
private val callsBack: Boolean = true
|
|
207
|
+
) : Cache {
|
|
77
208
|
override fun removeSpan(span: CacheSpan) {
|
|
78
209
|
removed.add(span)
|
|
79
|
-
evictor.onSpanRemoved(this, span)
|
|
210
|
+
if (callsBack) evictor.onSpanRemoved(this, span)
|
|
80
211
|
}
|
|
81
212
|
|
|
82
213
|
override fun getUid(): Long = throw UnsupportedOperationException()
|
|
@@ -45,6 +45,7 @@ class GaplessEngineLifecycleTest {
|
|
|
45
45
|
override fun onError(
|
|
46
46
|
engine: PlaybackEngine,
|
|
47
47
|
exception: androidx.media3.common.PlaybackException,
|
|
48
|
+
failedIndex: Int,
|
|
48
49
|
) = Unit
|
|
49
50
|
override fun onPlaybackEnded(engine: PlaybackEngine) = Unit
|
|
50
51
|
override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) = Unit
|
|
@@ -21,8 +21,9 @@ import org.robolectric.annotation.Config
|
|
|
21
21
|
* the engine-internal `handleReplayGainParse` helper which these
|
|
22
22
|
* tests drive directly with synthetic [ReplayGainData].
|
|
23
23
|
* This file covers the no-data baseline,
|
|
24
|
-
* mode-change-without-data behaviour,
|
|
25
|
-
* `handleReplayGainParse` cache + recompute contract
|
|
24
|
+
* mode-change-without-data behaviour, the
|
|
25
|
+
* `handleReplayGainParse` cache + recompute contract, and the
|
|
26
|
+
* consumer's supplied claim outranking the parsed tags.
|
|
26
27
|
*/
|
|
27
28
|
@RunWith(RobolectricTestRunner::class)
|
|
28
29
|
@Config(sdk = [34])
|
|
@@ -81,6 +82,39 @@ class GaplessEngineReplayGainTest {
|
|
|
81
82
|
engine.release()
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The consumer's claim over the active item outranks the file's tags: a
|
|
87
|
+
* supplied value replaces detection in full, and a claim whose values were
|
|
88
|
+
* all rejected still discards detection and plays at unity.
|
|
89
|
+
*/
|
|
90
|
+
@Test
|
|
91
|
+
fun `a supplied claim on the active item replaces the detected tags`() {
|
|
92
|
+
val engine = makeEngine()
|
|
93
|
+
ReplayGainConfig.setMode(ReplayGainMode.TRACK)
|
|
94
|
+
engine.setMediaItems(listOf(androidx.media3.common.MediaItem.fromUri("https://example.com/a.mp3")), 0, 0L)
|
|
95
|
+
val detected = ReplayGainData(trackGainDb = -6.0f, trackPeak = 0.5f, albumGainDb = null, albumPeak = null)
|
|
96
|
+
engine.handleReplayGainParse(detected)
|
|
97
|
+
assertEquals(detected, engine.currentReplayGainData())
|
|
98
|
+
|
|
99
|
+
var claim: TrackItem = suppliedTrack(trackGain = -3.0)
|
|
100
|
+
engine.suppliedReplayGainResolver = { SuppliedReplayGain.from(claim) }
|
|
101
|
+
assertEquals(
|
|
102
|
+
ReplayGainData(trackGainDb = -3.0f, trackPeak = null, albumGainDb = null, albumPeak = null),
|
|
103
|
+
engine.currentReplayGainData())
|
|
104
|
+
assertTrue(engine.currentReplayGainActive())
|
|
105
|
+
|
|
106
|
+
claim = suppliedTrack(trackGain = Double.NaN)
|
|
107
|
+
assertNull("a claim with no usable value discards the detection too", engine.currentReplayGainData())
|
|
108
|
+
assertFalse(engine.currentReplayGainActive())
|
|
109
|
+
engine.release()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun suppliedTrack(trackGain: Double) = TrackItem(
|
|
113
|
+
id = null, url = "https://example.com/a.mp3", title = null, artist = null, album = null,
|
|
114
|
+
duration = null, artworkUrl = null, extras = null,
|
|
115
|
+
replayGainTrackGain = trackGain, replayGainTrackPeak = null,
|
|
116
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null)
|
|
117
|
+
|
|
84
118
|
@Test
|
|
85
119
|
fun `handleReplayGainParse with album-only data is inactive in track mode`() {
|
|
86
120
|
val engine = makeEngine()
|