react-native-queue-player 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -29,6 +29,33 @@ final class AudioTapProviderTests: XCTestCase {
|
|
|
29
29
|
)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
func testConsumerWantingTapGetsAWorkingMixForALoadedLocalAsset() async throws {
|
|
33
|
+
let provider = AudioTapProvider()
|
|
34
|
+
provider.register(makeConsumer(wantsTap: { true }))
|
|
35
|
+
|
|
36
|
+
let url = try requireFixture(name: "silence-cbr-128", ext: "mp3")
|
|
37
|
+
let asset = AVURLAsset(url: url)
|
|
38
|
+
// The mix keys its input parameters to an `AVAssetTrack`, so the tracks
|
|
39
|
+
// key has to be loaded before there is anything to key them to.
|
|
40
|
+
let tracks = try await asset.load(.tracks)
|
|
41
|
+
let item = AVPlayerItem(asset: asset)
|
|
42
|
+
|
|
43
|
+
let mix = try XCTUnwrap(
|
|
44
|
+
provider.audioMix(for: item),
|
|
45
|
+
"a registered consumer wanting the tap must receive a mix")
|
|
46
|
+
let params = try XCTUnwrap(
|
|
47
|
+
mix.inputParameters.first as? AVMutableAudioMixInputParameters,
|
|
48
|
+
"the mix must carry input parameters for the asset's audio track")
|
|
49
|
+
XCTAssertNotNil(
|
|
50
|
+
params.audioTapProcessor,
|
|
51
|
+
"the input parameters must carry the processing tap — a mix without one "
|
|
52
|
+
+ "silently drops ReplayGain, the equalizer and the visualizer")
|
|
53
|
+
|
|
54
|
+
let audioTrackID = try XCTUnwrap(
|
|
55
|
+
tracks.first { $0.mediaType == .audio }?.trackID)
|
|
56
|
+
XCTAssertEqual(params.trackID, audioTrackID)
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
func testNoConsumersWantingTapReturnsNilEvenForLocalAssets() {
|
|
33
60
|
let provider = AudioTapProvider()
|
|
34
61
|
XCTAssertFalse(provider.isEnabledSnapshot())
|
|
@@ -106,4 +133,23 @@ final class AudioTapProviderTests: XCTestCase {
|
|
|
106
133
|
_ = provider.audioMix(for: item)
|
|
107
134
|
XCTAssertEqual(1, fires, "unregistered consumer must not see further events")
|
|
108
135
|
}
|
|
136
|
+
|
|
137
|
+
private func requireFixture(name: String, ext: String) throws -> URL {
|
|
138
|
+
let bundle = Bundle(for: type(of: self))
|
|
139
|
+
if let url = bundle.url(forResource: name, withExtension: ext) {
|
|
140
|
+
return url
|
|
141
|
+
}
|
|
142
|
+
let candidates = bundle.urls(forResourcesWithExtension: ext, subdirectory: nil) ?? []
|
|
143
|
+
if let match = candidates.first(where: { $0.deletingPathExtension().lastPathComponent == name }) {
|
|
144
|
+
return match
|
|
145
|
+
}
|
|
146
|
+
// Fixtures are bundled via `test_spec.resources` in QueuePlayer.podspec.
|
|
147
|
+
// A miss means the test-harness pod install regressed — that's a real
|
|
148
|
+
// infrastructure failure, not a conditionally-inapplicable test.
|
|
149
|
+
return try XCTUnwrap(
|
|
150
|
+
nil as URL?,
|
|
151
|
+
"fixture \(name).\(ext) not bundled — pod install regression in tests-harness"
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
109
155
|
}
|
|
@@ -257,7 +257,6 @@ private final class RecordingEngineStub: PlaybackEngine {
|
|
|
257
257
|
var refreshCount: Int = 0
|
|
258
258
|
var onRefresh: (() -> Void)?
|
|
259
259
|
|
|
260
|
-
func prepare(items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) async {}
|
|
261
260
|
func release() {}
|
|
262
261
|
func setItems(_ items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) {}
|
|
263
262
|
@discardableResult
|
|
@@ -295,7 +294,11 @@ private final class RecordingEngineStub: PlaybackEngine {
|
|
|
295
294
|
onRefresh?()
|
|
296
295
|
}
|
|
297
296
|
|
|
298
|
-
|
|
297
|
+
/// No fade to schedule — this stub exercises the rest of the surface.
|
|
298
|
+
var isFadePendingOrActive: Bool { false }
|
|
299
|
+
|
|
300
|
+
/// Nothing attached — this stub exercises the rest of the surface.
|
|
301
|
+
var attachedQueueItemIds: [String] { [] }
|
|
299
302
|
|
|
300
303
|
var delegate: PlaybackEngineDelegate?
|
|
301
304
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// The FLAC STREAMINFO parse, the only source of a local FLAC bitrate.
|
|
5
|
+
final class FLACStreamInfoTests: XCTestCase {
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
func testFLACStreamInfoExtractsSampleRateAndTotalSamples() throws {
|
|
9
|
+
// Build a STREAMINFO-only FLAC header.
|
|
10
|
+
// sampleRate=44100, channels=2, bitsPerSample=16, totalSamples=1234567
|
|
11
|
+
let info = makeFLACStreamInfo(
|
|
12
|
+
sampleRate: 44100,
|
|
13
|
+
channels: 2,
|
|
14
|
+
bitsPerSample: 16,
|
|
15
|
+
totalSamples: 1_234_567
|
|
16
|
+
)
|
|
17
|
+
let parsed = try XCTUnwrap(FLACStreamInfo.parse(info))
|
|
18
|
+
XCTAssertEqual(parsed.sampleRate, 44100)
|
|
19
|
+
XCTAssertEqual(parsed.channelCount, 2)
|
|
20
|
+
XCTAssertEqual(parsed.bitsPerSample, 16)
|
|
21
|
+
XCTAssertEqual(parsed.totalSamples, 1_234_567)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func testFLACStreamInfoHighSampleRate() throws {
|
|
25
|
+
// foobar2000 + some lossless masters use 96 kHz / 24-bit. Verify
|
|
26
|
+
// the 20-bit sampleRate field isn't truncated.
|
|
27
|
+
let info = makeFLACStreamInfo(
|
|
28
|
+
sampleRate: 96000,
|
|
29
|
+
channels: 2,
|
|
30
|
+
bitsPerSample: 24,
|
|
31
|
+
totalSamples: 9_876_543
|
|
32
|
+
)
|
|
33
|
+
let parsed = try XCTUnwrap(FLACStreamInfo.parse(info))
|
|
34
|
+
XCTAssertEqual(parsed.sampleRate, 96000)
|
|
35
|
+
XCTAssertEqual(parsed.bitsPerSample, 24)
|
|
36
|
+
XCTAssertEqual(parsed.totalSamples, 9_876_543)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func testFLACStreamInfoReturnsNilOnNonFLACBytes() {
|
|
40
|
+
XCTAssertNil(FLACStreamInfo.parse(
|
|
41
|
+
Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
42
|
+
))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MARK: - Test helpers — byte-level fixture builders
|
|
46
|
+
|
|
47
|
+
private func makeFLACStreamInfo(
|
|
48
|
+
sampleRate: UInt32,
|
|
49
|
+
channels: Int,
|
|
50
|
+
bitsPerSample: Int,
|
|
51
|
+
totalSamples: Int64
|
|
52
|
+
) -> Data {
|
|
53
|
+
var data = Data()
|
|
54
|
+
data.append("fLaC".data(using: .ascii)!)
|
|
55
|
+
// Block header: last-block flag (0x80) + type 0 (STREAMINFO) +
|
|
56
|
+
// 24-bit size = 34 bytes.
|
|
57
|
+
data.append(0x80)
|
|
58
|
+
data.append(beUInt24(34))
|
|
59
|
+
// Body. 34 bytes total.
|
|
60
|
+
var body = Data(repeating: 0, count: 34)
|
|
61
|
+
// minBlockSize, maxBlockSize, minFrameSize, maxFrameSize: bytes
|
|
62
|
+
// 0..9, leave zero. Bytes 10..12 contain the 20-bit sampleRate
|
|
63
|
+
// followed by 3-bit channels-1 and 5-bit bitsPerSample-1 split
|
|
64
|
+
// across bytes 12..13. totalSamples is 36 bits at bytes 13..17.
|
|
65
|
+
let sr = UInt32(sampleRate) & 0x000FFFFF
|
|
66
|
+
let chMinusOne = UInt8((channels - 1) & 0x07)
|
|
67
|
+
let bpsMinusOne = UInt8((bitsPerSample - 1) & 0x1F)
|
|
68
|
+
body[10] = UInt8((sr >> 12) & 0xFF)
|
|
69
|
+
body[11] = UInt8((sr >> 4) & 0xFF)
|
|
70
|
+
body[12] = UInt8(((sr & 0x0F) << 4) | (UInt32(chMinusOne) << 1) | UInt32(bpsMinusOne >> 4))
|
|
71
|
+
body[13] = UInt8(((bpsMinusOne & 0x0F) << 4) | UInt8((totalSamples >> 32) & 0x0F))
|
|
72
|
+
body[14] = UInt8((totalSamples >> 24) & 0xFF)
|
|
73
|
+
body[15] = UInt8((totalSamples >> 16) & 0xFF)
|
|
74
|
+
body[16] = UInt8((totalSamples >> 8) & 0xFF)
|
|
75
|
+
body[17] = UInt8(totalSamples & 0xFF)
|
|
76
|
+
// Bytes 18..33: MD5, leave zero.
|
|
77
|
+
data.append(body)
|
|
78
|
+
return data
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private func beUInt24(_ v: UInt32) -> Data {
|
|
82
|
+
return Data([
|
|
83
|
+
UInt8((v >> 16) & 0xff),
|
|
84
|
+
UInt8((v >> 8) & 0xff),
|
|
85
|
+
UInt8(v & 0xff),
|
|
86
|
+
])
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -173,12 +173,12 @@ final class GaplessEngineLifecycleTests: XCTestCase {
|
|
|
173
173
|
|
|
174
174
|
private final class NoopDelegate: PlaybackEngineDelegate {
|
|
175
175
|
func engineStateMaybeChanged(_ engine: PlaybackEngine, reason: StateChangeReason?) {}
|
|
176
|
-
func engineIsPlayingChanged(_ engine: PlaybackEngine, isPlaying: Bool) {}
|
|
177
176
|
func engineDidTransitionTrack(_ engine: PlaybackEngine) {}
|
|
178
177
|
func engineActiveItemFormatChanged(_ engine: PlaybackEngine) {}
|
|
179
|
-
func engineDidFailWithError(
|
|
178
|
+
func engineDidFailWithError(
|
|
179
|
+
_ engine: PlaybackEngine, error: Error, item: AVPlayerItem?) {}
|
|
180
180
|
func engineDidPlayItemToEnd(_ engine: PlaybackEngine) {}
|
|
181
181
|
func enginePlaybackEnded(_ engine: PlaybackEngine) {}
|
|
182
|
-
func engineCrossfadeDidBegin(_ engine: PlaybackEngine
|
|
183
|
-
func engineCrossfadeDidCancel(_ engine: PlaybackEngine
|
|
182
|
+
func engineCrossfadeDidBegin(_ engine: PlaybackEngine) {}
|
|
183
|
+
func engineCrossfadeDidCancel(_ engine: PlaybackEngine) {}
|
|
184
184
|
}
|
|
@@ -8,8 +8,49 @@ import XCTest
|
|
|
8
8
|
/// are covered without a live AVPlayer or AudioSession.
|
|
9
9
|
final class InputGuardsTests: XCTestCase {
|
|
10
10
|
|
|
11
|
+
/// A finite value too large for `Int64` is as fatal as an infinite one once
|
|
12
|
+
/// it is scaled to milliseconds, so the guard bounds magnitude, not just
|
|
13
|
+
/// finiteness.
|
|
14
|
+
func testSeekPositionIsBoundedToAConvertibleMagnitude() throws {
|
|
15
|
+
let huge = try XCTUnwrap(InputGuards.sanitisedSeekPosition(1e300))
|
|
16
|
+
XCTAssertLessThan(huge * 1000.0, Double(Int64.max))
|
|
17
|
+
let negative = try XCTUnwrap(InputGuards.sanitisedSeekPosition(-1e300))
|
|
18
|
+
XCTAssertGreaterThan(negative * 1000.0, Double(Int64.min))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func testSeekPositionPassesOrdinaryValuesThrough() {
|
|
22
|
+
XCTAssertEqual(InputGuards.sanitisedSeekPosition(42.5), 42.5)
|
|
23
|
+
XCTAssertNil(InputGuards.sanitisedSeekPosition(.nan))
|
|
24
|
+
XCTAssertNil(InputGuards.sanitisedSeekPosition(.infinity))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// The conversion has to be bounded in `Double` space; `Int(_:)` traps on a
|
|
28
|
+
/// finite value larger than `Int` before any clamp on the result can apply.
|
|
29
|
+
func testLookaheadCountIsBoundedBeforeConversion() {
|
|
30
|
+
XCTAssertEqual(InputGuards.validLookaheadCount(.infinity), 10_000)
|
|
31
|
+
XCTAssertEqual(InputGuards.validLookaheadCount(1e300), 10_000)
|
|
32
|
+
XCTAssertEqual(InputGuards.validLookaheadCount(.nan), 0)
|
|
33
|
+
XCTAssertEqual(InputGuards.validLookaheadCount(-5), 0)
|
|
34
|
+
XCTAssertEqual(InputGuards.validLookaheadCount(7), 7)
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
// MARK: - validQueueIndex
|
|
12
38
|
|
|
39
|
+
/// A finite value past what `Int` holds means "append", the same as any
|
|
40
|
+
/// other position past the end; converting it first would trap.
|
|
41
|
+
func testInsertPositionClampsAMagnitudeIntCannotHold() {
|
|
42
|
+
XCTAssertEqual(InputGuards.insertPosition(1e300, count: 5), 5)
|
|
43
|
+
XCTAssertEqual(InputGuards.insertPosition(-1e300, count: 5), 0)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func testInsertPositionPassesOrdinaryValuesThroughAndDropsNonFinite() {
|
|
47
|
+
XCTAssertEqual(InputGuards.insertPosition(2, count: 5), 2)
|
|
48
|
+
XCTAssertEqual(InputGuards.insertPosition(7, count: 5), 5)
|
|
49
|
+
XCTAssertNil(InputGuards.insertPosition(nil, count: 5))
|
|
50
|
+
XCTAssertNil(InputGuards.insertPosition(.nan, count: 5))
|
|
51
|
+
XCTAssertNil(InputGuards.insertPosition(.infinity, count: 5))
|
|
52
|
+
}
|
|
53
|
+
|
|
13
54
|
func testValidQueueIndexReturnsIntForInRangeValue() {
|
|
14
55
|
XCTAssertEqual(InputGuards.validQueueIndex(0, count: 5), 0)
|
|
15
56
|
XCTAssertEqual(InputGuards.validQueueIndex(2, count: 5), 2)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// `isInterrupted` disables retries, window refills and network recovery
|
|
5
|
+
/// while the system holds the session. The system does not always deliver
|
|
6
|
+
/// the interruption's end, so the flag has user-side exits too.
|
|
7
|
+
final class InterruptionIntentTests: XCTestCase {
|
|
8
|
+
|
|
9
|
+
private var player: TrackPlayer!
|
|
10
|
+
|
|
11
|
+
override func setUp() async throws {
|
|
12
|
+
try await super.setUp()
|
|
13
|
+
player = TrackPlayer()
|
|
14
|
+
_ = try await player.configure(config: PlayerFixtures.config()).await()
|
|
15
|
+
_ = try await player.setQueue(
|
|
16
|
+
tracks: PlayerFixtures.localTracks(count: 2, for: self), startAtIndex: 0
|
|
17
|
+
).await()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override func tearDown() async throws {
|
|
21
|
+
if let player {
|
|
22
|
+
_ = try await player.destroy().await()
|
|
23
|
+
}
|
|
24
|
+
player = nil
|
|
25
|
+
try await super.tearDown()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// An explicit play is the user ending the interruption from their side.
|
|
29
|
+
func testAnExplicitPlayEndsTheInterruption() async throws {
|
|
30
|
+
let player = try XCTUnwrap(self.player)
|
|
31
|
+
await player.onPlayerQueue { player.isInterrupted = true }
|
|
32
|
+
|
|
33
|
+
_ = try await player.play().await()
|
|
34
|
+
|
|
35
|
+
let interrupted = await player.onPlayerQueueValue { player.isInterrupted }
|
|
36
|
+
XCTAssertFalse(interrupted)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// At the end of the queue play() does nothing: the engine is not asked to
|
|
40
|
+
/// play and the intent is not recorded, so a track a later skip seats waits
|
|
41
|
+
/// for its own play().
|
|
42
|
+
func testPlayAtTheEndOfTheQueueRestartsNothing() async throws {
|
|
43
|
+
let player = try XCTUnwrap(self.player)
|
|
44
|
+
let engine = ActiveItemEngineStub()
|
|
45
|
+
await player.onPlayerQueue {
|
|
46
|
+
player.engine = engine
|
|
47
|
+
player.reachedQueueEnd = true
|
|
48
|
+
player.wantsToPlay = false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_ = try await player.play().await()
|
|
52
|
+
|
|
53
|
+
XCTAssertEqual(engine.playCalls, 0, "the parked engine is left alone")
|
|
54
|
+
let state = await player.onPlayerQueueValue { (player.wantsToPlay, player.reachedQueueEnd) }
|
|
55
|
+
XCTAssertFalse(state.0, "no intent is recorded for a queue that is over")
|
|
56
|
+
XCTAssertTrue(state.1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Nothing about the torn-down session survives into the next configure.
|
|
60
|
+
func testDestroyClearsTheInterruptionAndRecoveryFlags() async throws {
|
|
61
|
+
let player = try XCTUnwrap(self.player)
|
|
62
|
+
await player.onPlayerQueue {
|
|
63
|
+
player.isInterrupted = true
|
|
64
|
+
player.isRecoveringFromStall = true
|
|
65
|
+
player.engineReconcileDeferred = true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_ = try await player.destroy().await()
|
|
69
|
+
|
|
70
|
+
let flags = await player.onPlayerQueueValue {
|
|
71
|
+
(player.isInterrupted, player.isRecoveringFromStall, player.engineReconcileDeferred)
|
|
72
|
+
}
|
|
73
|
+
XCTAssertFalse(flags.0)
|
|
74
|
+
XCTAssertFalse(flags.1)
|
|
75
|
+
XCTAssertFalse(flags.2)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// Prefetch requests carry `allowsCellularAccess`, and `setLookaheadCache`
|
|
5
|
+
/// changes it live — the cache instance and everything it holds on disk stay
|
|
6
|
+
/// as they are.
|
|
7
|
+
final class LookaheadCacheCellularAccessTests: XCTestCase {
|
|
8
|
+
|
|
9
|
+
private var tempDir: URL!
|
|
10
|
+
private var session: URLSession!
|
|
11
|
+
|
|
12
|
+
override func setUp() {
|
|
13
|
+
super.setUp()
|
|
14
|
+
tempDir = FileManager.default.temporaryDirectory
|
|
15
|
+
.appendingPathComponent("CellularAccess-\(UUID().uuidString)", isDirectory: true)
|
|
16
|
+
let config = URLSessionConfiguration.ephemeral
|
|
17
|
+
config.protocolClasses = [StubURLProtocol.self] + (config.protocolClasses ?? [])
|
|
18
|
+
session = URLSession(configuration: config)
|
|
19
|
+
StubURLProtocol.reset()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override func tearDown() {
|
|
23
|
+
session?.invalidateAndCancel()
|
|
24
|
+
try? FileManager.default.removeItem(at: tempDir)
|
|
25
|
+
StubURLProtocol.reset()
|
|
26
|
+
super.tearDown()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - The request carries the policy
|
|
30
|
+
|
|
31
|
+
func testAPrefetchRequestIsAllowedOverCellularByDefault() async throws {
|
|
32
|
+
let cache = makeCache()
|
|
33
|
+
let url = URL(string: "https://example.com/default.m4a")!
|
|
34
|
+
StubURLProtocol.stub(url: url, data: Data("payload".utf8), statusCode: 200)
|
|
35
|
+
|
|
36
|
+
try await cache.download(track: makeTrack(id: "t", url: url.absoluteString), headers: nil)
|
|
37
|
+
|
|
38
|
+
XCTAssertEqual(StubURLProtocol.lastRequest(for: url)?.allowsCellularAccess, true)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func testAPrefetchRequestRefusesCellularWhenTheCacheIsToldTo() async throws {
|
|
42
|
+
let cache = makeCache(allowsCellularAccess: false)
|
|
43
|
+
let url = URL(string: "https://example.com/denied.m4a")!
|
|
44
|
+
StubURLProtocol.stub(url: url, data: Data("payload".utf8), statusCode: 200)
|
|
45
|
+
|
|
46
|
+
try await cache.download(track: makeTrack(id: "t", url: url.absoluteString), headers: nil)
|
|
47
|
+
|
|
48
|
+
XCTAssertEqual(StubURLProtocol.lastRequest(for: url)?.allowsCellularAccess, false)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
func testTheNextRequestPicksUpAChangedPolicy() async throws {
|
|
52
|
+
let cache = makeCache()
|
|
53
|
+
let first = URL(string: "https://example.com/before.m4a")!
|
|
54
|
+
let second = URL(string: "https://example.com/after.m4a")!
|
|
55
|
+
StubURLProtocol.stub(url: first, data: Data("a".utf8), statusCode: 200)
|
|
56
|
+
StubURLProtocol.stub(url: second, data: Data("b".utf8), statusCode: 200)
|
|
57
|
+
try await cache.download(track: makeTrack(id: "a", url: first.absoluteString), headers: nil)
|
|
58
|
+
|
|
59
|
+
cache.allowsCellularAccess = false
|
|
60
|
+
try await cache.download(track: makeTrack(id: "b", url: second.absoluteString), headers: nil)
|
|
61
|
+
|
|
62
|
+
XCTAssertEqual(StubURLProtocol.lastRequest(for: first)?.allowsCellularAccess, true)
|
|
63
|
+
XCTAssertEqual(StubURLProtocol.lastRequest(for: second)?.allowsCellularAccess, false)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - The runtime switch
|
|
67
|
+
|
|
68
|
+
/// `applyLookaheadConfig` is player-queue confined — the prefetcher asserts
|
|
69
|
+
/// it — so the tests reach it the way `setLookaheadCache` does.
|
|
70
|
+
private func apply(
|
|
71
|
+
_ player: TrackPlayer, enabled: Bool, count: Double, cellular: Bool?
|
|
72
|
+
) throws {
|
|
73
|
+
var caught: Error?
|
|
74
|
+
player.playerQueue.sync {
|
|
75
|
+
do {
|
|
76
|
+
try player.applyLookaheadConfig(
|
|
77
|
+
LookaheadCacheConfig(
|
|
78
|
+
enabled: enabled, lookaheadCount: count, allowsCellularAccess: cellular))
|
|
79
|
+
} catch {
|
|
80
|
+
caught = error
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if let caught { throw caught }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// `enabled` starts true, so the cache only exists once the flag has been
|
|
87
|
+
/// cycled — the construction path a consumer hits at `configure()`.
|
|
88
|
+
private func playerWithCache() throws -> TrackPlayer {
|
|
89
|
+
let player = TrackPlayer()
|
|
90
|
+
try apply(player, enabled: false, count: 1, cellular: true)
|
|
91
|
+
try apply(player, enabled: true, count: 1, cellular: true)
|
|
92
|
+
return player
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func testTurningCellularOffKeepsTheCacheAndOnlyChangesThePolicy() throws {
|
|
96
|
+
let player = try playerWithCache()
|
|
97
|
+
defer { _ = try? player.destroy() }
|
|
98
|
+
let cache = try XCTUnwrap(player.lookaheadCache)
|
|
99
|
+
XCTAssertTrue(cache.allowsCellularAccess)
|
|
100
|
+
|
|
101
|
+
try apply(player, enabled: true, count: 1, cellular: false)
|
|
102
|
+
|
|
103
|
+
XCTAssertTrue(cache === player.lookaheadCache, "nothing is rebuilt — the policy is live")
|
|
104
|
+
XCTAssertFalse(cache.allowsCellularAccess)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func testTurningCellularBackOnRestoresIt() throws {
|
|
108
|
+
let player = try playerWithCache()
|
|
109
|
+
defer { _ = try? player.destroy() }
|
|
110
|
+
try apply(player, enabled: true, count: 1, cellular: false)
|
|
111
|
+
|
|
112
|
+
try apply(player, enabled: true, count: 1, cellular: true)
|
|
113
|
+
|
|
114
|
+
XCTAssertTrue(try XCTUnwrap(player.lookaheadCache).allowsCellularAccess)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// The cache is constructed lazily, so a policy set while it does not exist
|
|
118
|
+
/// has to reach the instance the next construction produces.
|
|
119
|
+
func testACacheBuiltAfterThePolicyWasSetHonoursIt() throws {
|
|
120
|
+
let player = TrackPlayer()
|
|
121
|
+
defer { _ = try? player.destroy() }
|
|
122
|
+
try apply(player, enabled: false, count: 1, cellular: false)
|
|
123
|
+
XCTAssertNil(player.lookaheadCache, "precondition: nothing built yet")
|
|
124
|
+
|
|
125
|
+
try apply(player, enabled: true, count: 1, cellular: false)
|
|
126
|
+
|
|
127
|
+
XCTAssertFalse(try XCTUnwrap(player.lookaheadCache).allowsCellularAccess)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// An omitted `allowsCellularAccess` reads as permitted, so a consumer that
|
|
131
|
+
/// never sets it is not silently cut off from prefetching.
|
|
132
|
+
func testOmittingTheFlagLeavesCellularPermitted() throws {
|
|
133
|
+
let player = TrackPlayer()
|
|
134
|
+
defer { _ = try? player.destroy() }
|
|
135
|
+
try apply(player, enabled: false, count: 1, cellular: nil)
|
|
136
|
+
try apply(player, enabled: true, count: 1, cellular: nil)
|
|
137
|
+
|
|
138
|
+
XCTAssertTrue(try XCTUnwrap(player.lookaheadCache).allowsCellularAccess)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// `configure()` builds the cache itself rather than going through
|
|
142
|
+
/// `applyLookaheadConfig`, so it has to read the stored policy too.
|
|
143
|
+
func testConfigureBuildsTheCacheUnderTheStoredPolicy() async throws {
|
|
144
|
+
let player = TrackPlayer()
|
|
145
|
+
// Prefetch stays enabled — configure only builds the cache when it is —
|
|
146
|
+
// and with no cache yet the policy lives in the stored config alone.
|
|
147
|
+
try apply(player, enabled: true, count: 1, cellular: false)
|
|
148
|
+
XCTAssertNil(player.lookaheadCache, "precondition: configure does the building")
|
|
149
|
+
|
|
150
|
+
_ = try await player.configure(config: Self.playerConfig()).await()
|
|
151
|
+
defer { _ = try? player.destroy() }
|
|
152
|
+
|
|
153
|
+
let cache = try XCTUnwrap(player.lookaheadCache)
|
|
154
|
+
XCTAssertFalse(cache.allowsCellularAccess)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static func playerConfig() -> PlayerConfig {
|
|
158
|
+
return PlayerConfig(
|
|
159
|
+
httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil,
|
|
160
|
+
autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil,
|
|
161
|
+
audioContentType: nil, audioCategoryOptions: nil,
|
|
162
|
+
voiceVocabularyDonationEnabled: nil,
|
|
163
|
+
visualizationEnabled: nil, clampSeekToBuffered: nil,
|
|
164
|
+
skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil,
|
|
165
|
+
lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil,
|
|
166
|
+
backgroundProgressUpdateIntervalMs: nil
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func makeTrack(id: String, url: String) -> TrackItem {
|
|
171
|
+
return TrackItem(
|
|
172
|
+
id: id, url: url, title: nil, artist: nil, album: nil,
|
|
173
|
+
duration: nil, artworkUrl: nil, extras: nil,
|
|
174
|
+
replayGainTrackGain: nil, replayGainTrackPeak: nil,
|
|
175
|
+
replayGainAlbumGain: nil, replayGainAlbumPeak: nil
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private func makeCache(allowsCellularAccess: Bool = true) -> LookaheadCache {
|
|
180
|
+
LookaheadCache(
|
|
181
|
+
directory: tempDir, maxSizeBytes: 10 * 1024 * 1024, urlSession: session,
|
|
182
|
+
allowsCellularAccess: allowsCellularAccess
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
}
|