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
|
@@ -137,176 +137,129 @@ final class AVQueueBuilderTests: XCTestCase {
|
|
|
137
137
|
XCTAssertEqual(item?.preferredForwardBufferDuration, 0.0)
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// MARK: -
|
|
140
|
+
// MARK: - Read-through routing
|
|
141
141
|
|
|
142
|
-
func
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// makeItem(for:config:cache:) and assert the AVPlayerItem is
|
|
146
|
-
// backed by the local file URL (not the remote one).
|
|
147
|
-
let tempDir = FileManager.default.temporaryDirectory
|
|
148
|
-
.appendingPathComponent("AVQueueBuilderTests-cache-\(UUID().uuidString)", isDirectory: true)
|
|
149
|
-
defer { try? FileManager.default.removeItem(at: tempDir) }
|
|
150
|
-
|
|
151
|
-
StubURLProtocol.reset()
|
|
152
|
-
defer { StubURLProtocol.reset() }
|
|
153
|
-
let sessionConfig = URLSessionConfiguration.ephemeral
|
|
154
|
-
sessionConfig.protocolClasses = [StubURLProtocol.self] + (sessionConfig.protocolClasses ?? [])
|
|
155
|
-
let session = URLSession(configuration: sessionConfig)
|
|
156
|
-
defer { session.invalidateAndCancel() }
|
|
142
|
+
private func routableConfig() -> PlayerConfig {
|
|
143
|
+
PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, voiceVocabularyDonationEnabled: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil)
|
|
144
|
+
}
|
|
157
145
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
|
|
146
|
+
private func startedServer() async -> ReadThroughServer {
|
|
147
|
+
let server = ReadThroughServer(cachedFileForOrigin: { _ in nil }, onFirstDiskHit: { _ in })
|
|
148
|
+
server.start()
|
|
149
|
+
XCTAssertTrue(server.waitUntilBound(), "the loopback listener binds within the wait")
|
|
150
|
+
return server
|
|
151
|
+
}
|
|
162
152
|
|
|
163
|
-
|
|
164
|
-
|
|
153
|
+
/// The routing decision is stamped on the item at build time, which is what
|
|
154
|
+
/// source classification reads: a routed item carries the stamp and an
|
|
155
|
+
/// item built with no server does not.
|
|
156
|
+
func testTheItemCarriesWhetherItWasRouted() async throws {
|
|
157
|
+
let server = await startedServer()
|
|
158
|
+
defer { server.stop() }
|
|
159
|
+
let track = makeTrack(id: "remote-stamp", url: "https://example.com/stamp.mp3")
|
|
160
|
+
|
|
161
|
+
let routed = try XCTUnwrap(
|
|
162
|
+
AVQueueBuilder.makeItem(for: track, queueItemId: "q1", config: routableConfig(), server: server))
|
|
163
|
+
let direct = try XCTUnwrap(
|
|
164
|
+
AVQueueBuilder.makeItem(for: track, queueItemId: "q2", config: routableConfig(), server: nil))
|
|
165
|
+
|
|
166
|
+
XCTAssertTrue(routed.isRoutedThroughReadThroughServer)
|
|
167
|
+
XCTAssertFalse(direct.isRoutedThroughReadThroughServer)
|
|
168
|
+
}
|
|
165
169
|
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
/// A remote track binds the local server, not the origin — whether or not
|
|
171
|
+
/// the cache holds it yet. That is the point: the URL is chosen once and the
|
|
172
|
+
/// cache question is answered per request, later, when it can be answered.
|
|
173
|
+
func testARemoteTrackBindsTheReadThroughServer() async throws {
|
|
174
|
+
let server = await startedServer()
|
|
175
|
+
defer { server.stop() }
|
|
176
|
+
let track = makeTrack(id: "remote-1", url: "https://example.com/track.mp3")
|
|
168
177
|
|
|
169
178
|
let item = AVQueueBuilder.makeItem(
|
|
170
|
-
for: track,
|
|
171
|
-
queueItemId: UUID().uuidString,
|
|
172
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
173
|
-
cache: cache
|
|
174
|
-
)
|
|
175
|
-
XCTAssertNotNil(item)
|
|
176
|
-
let urlAsset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
177
|
-
XCTAssertTrue(urlAsset.url.isFileURL,
|
|
178
|
-
"fully-cached track must resolve to a local file URL, got \(urlAsset.url)")
|
|
179
|
-
XCTAssertEqual(
|
|
180
|
-
urlAsset.url.deletingLastPathComponent().path,
|
|
181
|
-
cache.cacheDirectory.path,
|
|
182
|
-
"local file URL must point inside the cache directory"
|
|
183
|
-
)
|
|
184
|
-
}
|
|
179
|
+
for: track, queueItemId: UUID().uuidString, config: routableConfig(), server: server)
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
let track = makeTrack(id: "t1", url: "https://example.com/track.m4a")
|
|
189
|
-
let config = PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil)
|
|
190
|
-
let item = AVQueueBuilder.makeItem(
|
|
191
|
-
for: track, queueItemId: UUID().uuidString, config: config, cache: nil)
|
|
192
|
-
XCTAssertNotNil(item)
|
|
193
|
-
if let urlAsset = item?.asset as? AVURLAsset {
|
|
194
|
-
XCTAssertEqual(urlAsset.url.absoluteString, "https://example.com/track.m4a",
|
|
195
|
-
"no cache → remote URL")
|
|
196
|
-
} else {
|
|
197
|
-
XCTFail("expected AVURLAsset")
|
|
198
|
-
}
|
|
181
|
+
let asset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
182
|
+
XCTAssertEqual(asset.url.host, "127.0.0.1", "a remote track must read through the server")
|
|
199
183
|
}
|
|
200
184
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
185
|
+
/// The media's own URL survives on the item. Everything that reads a
|
|
186
|
+
/// container, a codec or a track identity out of a URL reads this one — the
|
|
187
|
+
/// asset URL now names the transport.
|
|
188
|
+
func testTheSourceURLRemainsTheOriginAfterRouting() async throws {
|
|
189
|
+
let server = await startedServer()
|
|
190
|
+
defer { server.stop() }
|
|
191
|
+
let track = makeTrack(id: "remote-2", url: "https://example.com/track.flac")
|
|
205
192
|
|
|
206
|
-
let cache = LookaheadCache(
|
|
207
|
-
directory: tempDir,
|
|
208
|
-
maxSizeBytes: 1024 * 1024,
|
|
209
|
-
urlSession: URLSession(configuration: .ephemeral)
|
|
210
|
-
)
|
|
211
|
-
defer { cache.tearDown() }
|
|
212
|
-
|
|
213
|
-
let track = makeTrack(id: "uncached", url: "https://example.com/uncached.m4a")
|
|
214
193
|
let item = AVQueueBuilder.makeItem(
|
|
215
|
-
for: track,
|
|
216
|
-
queueItemId: UUID().uuidString,
|
|
217
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
218
|
-
cache: cache
|
|
219
|
-
)
|
|
220
|
-
XCTAssertNotNil(item)
|
|
221
|
-
if let urlAsset = item?.asset as? AVURLAsset {
|
|
222
|
-
XCTAssertEqual(urlAsset.url.absoluteString, "https://example.com/uncached.m4a",
|
|
223
|
-
"cache miss → remote URL")
|
|
224
|
-
} else {
|
|
225
|
-
XCTFail("expected AVURLAsset")
|
|
226
|
-
}
|
|
227
|
-
}
|
|
194
|
+
for: track, queueItemId: UUID().uuidString, config: routableConfig(), server: server)
|
|
228
195
|
|
|
229
|
-
|
|
196
|
+
XCTAssertEqual(item?.sourceURL, "https://example.com/track.flac")
|
|
197
|
+
}
|
|
230
198
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
let
|
|
235
|
-
|
|
236
|
-
|
|
199
|
+
/// One URL per track for the session, so a window rebuild does not re-point
|
|
200
|
+
/// an asset the player is already reading.
|
|
201
|
+
func testRebuildingTheSameTrackBindsTheSameURL() async throws {
|
|
202
|
+
let server = await startedServer()
|
|
203
|
+
defer { server.stop() }
|
|
204
|
+
let track = makeTrack(id: "remote-3", url: "https://example.com/stable.mp3")
|
|
205
|
+
let config = routableConfig()
|
|
206
|
+
|
|
207
|
+
let first = AVQueueBuilder.makeItem(
|
|
208
|
+
for: track, queueItemId: UUID().uuidString, config: config, server: server)
|
|
209
|
+
let second = AVQueueBuilder.makeItem(
|
|
210
|
+
for: track, queueItemId: UUID().uuidString, config: config, server: server)
|
|
211
|
+
|
|
212
|
+
let a = try XCTUnwrap(first?.asset as? AVURLAsset).url
|
|
213
|
+
let b = try XCTUnwrap(second?.asset as? AVURLAsset).url
|
|
214
|
+
XCTAssertEqual(a, b)
|
|
215
|
+
}
|
|
237
216
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
let session = URLSession(configuration: sessionConfig)
|
|
243
|
-
defer { session.invalidateAndCancel() }
|
|
217
|
+
func testAFileTrackIsNeverRouted() async throws {
|
|
218
|
+
let server = await startedServer()
|
|
219
|
+
defer { server.stop() }
|
|
220
|
+
let track = makeTrack(id: "local-1", url: "file:///tmp/song.mp3")
|
|
244
221
|
|
|
245
|
-
let
|
|
246
|
-
|
|
247
|
-
)
|
|
248
|
-
defer { cache.tearDown() }
|
|
222
|
+
let item = AVQueueBuilder.makeItem(
|
|
223
|
+
for: track, queueItemId: UUID().uuidString, config: routableConfig(), server: server)
|
|
249
224
|
|
|
250
|
-
let
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
try await cache.download(track: track, headers: nil)
|
|
225
|
+
let asset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
226
|
+
XCTAssertTrue(asset.url.isFileURL, "a local file has nothing to read through")
|
|
227
|
+
}
|
|
254
228
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
229
|
+
/// AVFoundation resolves a playlist's relative segment paths against the URL
|
|
230
|
+
/// it fetched the playlist from, so a proxied playlist would point every
|
|
231
|
+
/// segment at a token that names none of them.
|
|
232
|
+
func testAnHLSPlaylistIsNeverRouted() async throws {
|
|
233
|
+
let server = await startedServer()
|
|
234
|
+
defer { server.stop() }
|
|
235
|
+
let track = makeTrack(id: "hls-1", url: "https://example.com/stream.m3u8")
|
|
259
236
|
|
|
260
237
|
let item = AVQueueBuilder.makeItem(
|
|
261
|
-
for: track,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
)
|
|
266
|
-
XCTAssertNotNil(item)
|
|
267
|
-
let urlAsset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
268
|
-
XCTAssertEqual(urlAsset.url.absoluteString, remoteURL.absoluteString,
|
|
269
|
-
"deleted-file path must fall back to remote URL via cachedURL's stale-entry prune")
|
|
238
|
+
for: track, queueItemId: UUID().uuidString, config: routableConfig(), server: server)
|
|
239
|
+
|
|
240
|
+
let asset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
241
|
+
XCTAssertEqual(asset.url.absoluteString, "https://example.com/stream.m3u8")
|
|
270
242
|
}
|
|
271
243
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
244
|
+
/// A consumer running without the lookahead cache has no server, and gets
|
|
245
|
+
/// exactly today's behaviour rather than a local hop that could never hit.
|
|
246
|
+
func testWithNoServerTheOriginIsBoundDirectly() throws {
|
|
247
|
+
let track = makeTrack(id: "remote-4", url: "https://example.com/direct.mp3")
|
|
276
248
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
let sessionConfig = URLSessionConfiguration.ephemeral
|
|
280
|
-
sessionConfig.protocolClasses = [StubURLProtocol.self] + (sessionConfig.protocolClasses ?? [])
|
|
281
|
-
let session = URLSession(configuration: sessionConfig)
|
|
282
|
-
defer { session.invalidateAndCancel() }
|
|
249
|
+
let item = AVQueueBuilder.makeItem(
|
|
250
|
+
for: track, queueItemId: UUID().uuidString, config: routableConfig(), server: nil)
|
|
283
251
|
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
defer { cache.tearDown() }
|
|
252
|
+
let asset = try XCTUnwrap(item?.asset as? AVURLAsset)
|
|
253
|
+
XCTAssertEqual(asset.url.absoluteString, "https://example.com/direct.mp3")
|
|
254
|
+
}
|
|
288
255
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
let items = AVQueueBuilder.buildPlayerItems(
|
|
297
|
-
tracks: [trackA, trackB],
|
|
298
|
-
queueItemIds: [UUID().uuidString, UUID().uuidString],
|
|
299
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
300
|
-
cache: cache
|
|
301
|
-
)
|
|
302
|
-
XCTAssertEqual(items.count, 2)
|
|
303
|
-
let assetA = try XCTUnwrap(items[0].asset as? AVURLAsset)
|
|
304
|
-
let assetB = try XCTUnwrap(items[1].asset as? AVURLAsset)
|
|
305
|
-
XCTAssertTrue(assetA.url.isFileURL,
|
|
306
|
-
"A is cached → file URL")
|
|
307
|
-
XCTAssertFalse(assetB.url.isFileURL,
|
|
308
|
-
"B is uncached → remote URL")
|
|
309
|
-
XCTAssertEqual(assetB.url.absoluteString, "https://example.com/b.m4a")
|
|
256
|
+
func testRoutabilityIsSchemeAndManifestAware() {
|
|
257
|
+
XCTAssertTrue(AVQueueBuilder.isRoutable("https://example.com/a.mp3"))
|
|
258
|
+
XCTAssertTrue(AVQueueBuilder.isRoutable("http://example.com/a.mp3"))
|
|
259
|
+
XCTAssertTrue(AVQueueBuilder.isRoutable("https://example.com/no-extension"))
|
|
260
|
+
XCTAssertFalse(AVQueueBuilder.isRoutable("file:///tmp/a.mp3"))
|
|
261
|
+
XCTAssertFalse(AVQueueBuilder.isRoutable("https://example.com/stream.m3u8"))
|
|
262
|
+
XCTAssertFalse(AVQueueBuilder.isRoutable("HTTPS://example.com/stream.M3U8"))
|
|
310
263
|
}
|
|
311
264
|
|
|
312
265
|
func testSetMaxSizeBytesShrinkProtectsCurrentlyPlayingTrack() async throws {
|
|
@@ -371,13 +324,13 @@ final class AVQueueBuilderTests: XCTestCase {
|
|
|
371
324
|
let queueItemIdB = UUID().uuidString
|
|
372
325
|
let itemA = AVQueueBuilder.makeItem(
|
|
373
326
|
for: track, queueItemId: queueItemIdA,
|
|
374
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
375
|
-
|
|
327
|
+
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, voiceVocabularyDonationEnabled: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
328
|
+
server: nil
|
|
376
329
|
)
|
|
377
330
|
let itemB = AVQueueBuilder.makeItem(
|
|
378
331
|
for: track, queueItemId: queueItemIdB,
|
|
379
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
380
|
-
|
|
332
|
+
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, voiceVocabularyDonationEnabled: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
333
|
+
server: nil
|
|
381
334
|
)
|
|
382
335
|
XCTAssertNotNil(itemA)
|
|
383
336
|
XCTAssertNotNil(itemB)
|
|
@@ -396,13 +349,15 @@ final class AVQueueBuilderTests: XCTestCase {
|
|
|
396
349
|
let track = TrackItem(
|
|
397
350
|
id: nil, url: "https://example.com/nil-id.m4a",
|
|
398
351
|
title: nil, artist: nil, album: nil,
|
|
399
|
-
duration: nil, artworkUrl: nil, extras: nil
|
|
352
|
+
duration: nil, artworkUrl: nil, extras: nil,
|
|
353
|
+
replayGainTrackGain: nil, replayGainTrackPeak: nil,
|
|
354
|
+
replayGainAlbumGain: nil, replayGainAlbumPeak: nil
|
|
400
355
|
)
|
|
401
356
|
let queueItemId = UUID().uuidString
|
|
402
357
|
let item = AVQueueBuilder.makeItem(
|
|
403
358
|
for: track, queueItemId: queueItemId,
|
|
404
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
405
|
-
|
|
359
|
+
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, voiceVocabularyDonationEnabled: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
360
|
+
server: nil
|
|
406
361
|
)
|
|
407
362
|
XCTAssertNotNil(item)
|
|
408
363
|
XCTAssertEqual(item?.queueItemId, queueItemId,
|
|
@@ -417,88 +372,70 @@ final class AVQueueBuilderTests: XCTestCase {
|
|
|
417
372
|
let queueItemId = UUID().uuidString
|
|
418
373
|
let item = AVQueueBuilder.makeItem(
|
|
419
374
|
for: track, queueItemId: queueItemId,
|
|
420
|
-
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
421
|
-
|
|
375
|
+
config: PlayerConfig(httpHeaders: nil, userAgent: nil, placeholderArtworkUri: nil, autoRetries: nil, retryBackoffMs: nil, networkTimeoutMs: nil, audioContentType: nil, audioCategoryOptions: nil, voiceVocabularyDonationEnabled: nil, visualizationEnabled: nil, clampSeekToBuffered: nil, skipToPreviousBehavior: nil, lookaheadCacheMaxSizeMb: nil, lookaheadCacheEvictionPolicy: nil, progressUpdateIntervalMs: nil, backgroundProgressUpdateIntervalMs: nil),
|
|
376
|
+
server: nil
|
|
422
377
|
)
|
|
423
378
|
XCTAssertNotNil(item)
|
|
424
379
|
XCTAssertEqual(item?.queueItemId, queueItemId,
|
|
425
380
|
"queueItemId must be set even when track.id is empty string")
|
|
426
381
|
}
|
|
427
382
|
|
|
428
|
-
// MARK: - preloadAssetForInsert
|
|
429
383
|
|
|
430
|
-
func
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
384
|
+
private func makeTrack(id: String, url: String) -> TrackItem {
|
|
385
|
+
return TrackItem(
|
|
386
|
+
id: id, url: url, title: nil, artist: nil, album: nil,
|
|
387
|
+
duration: nil, artworkUrl: nil, extras: nil,
|
|
388
|
+
replayGainTrackGain: nil, replayGainTrackPeak: nil,
|
|
389
|
+
replayGainAlbumGain: nil, replayGainAlbumPeak: nil
|
|
390
|
+
)
|
|
391
|
+
}
|
|
435
392
|
|
|
436
|
-
await AVQueueBuilder.preloadAssetForInsert(item)
|
|
437
393
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
394
|
+
// MARK: - Cancelling loads on discarded items
|
|
395
|
+
|
|
396
|
+
/// A cancelled asset can never become playable, so cancellation may only
|
|
397
|
+
/// touch assets that no surviving item reuses.
|
|
398
|
+
func testEveryOutgoingItemIsDroppedWhenNothingSurvives() {
|
|
399
|
+
let outgoing = [makeItem("a"), makeItem("b")]
|
|
400
|
+
let dropped = AVQueueBuilder.itemsBeingDropped(outgoing: outgoing, keeping: [])
|
|
401
|
+
XCTAssertEqual(dropped.count, 2)
|
|
444
402
|
}
|
|
445
403
|
|
|
446
|
-
func
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"
|
|
461
|
-
let urlAsset = try XCTUnwrap(item.asset as? AVURLAsset)
|
|
462
|
-
XCTAssertEqual(urlAsset.url, missing,
|
|
463
|
-
"a failed preload must leave the item's asset unchanged and insertable")
|
|
404
|
+
func testAnItemWhoseAssetIsReusedIsNotDropped() {
|
|
405
|
+
let shared = AVURLAsset(url: URL(string: "https://example.com/shared.m4a")!)
|
|
406
|
+
let outgoing = [AVPlayerItem(asset: shared), makeItem("other")]
|
|
407
|
+
let rebuiltOnTheSameAsset = AVPlayerItem(asset: shared)
|
|
408
|
+
|
|
409
|
+
let dropped = AVQueueBuilder.itemsBeingDropped(
|
|
410
|
+
outgoing: outgoing, keeping: [rebuiltOnTheSameAsset])
|
|
411
|
+
|
|
412
|
+
XCTAssertEqual(
|
|
413
|
+
dropped.count, 1,
|
|
414
|
+
"an asset a surviving item rebuilt on must keep its loads — cancelling it "
|
|
415
|
+
+ "leaves that item permanently unplayable")
|
|
416
|
+
XCTAssertFalse(
|
|
417
|
+
dropped.contains { $0.asset === shared },
|
|
418
|
+
"the reused asset must not be handed to cancellation")
|
|
464
419
|
}
|
|
465
420
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
421
|
+
func testKeepingIsComparedByAssetIdentityNotByItem() {
|
|
422
|
+
let shared = AVURLAsset(url: URL(string: "https://example.com/shared.m4a")!)
|
|
423
|
+
let outgoing = [AVPlayerItem(asset: shared)]
|
|
424
|
+
// A different item object wrapping the same asset still protects it.
|
|
425
|
+
let dropped = AVQueueBuilder.itemsBeingDropped(
|
|
426
|
+
outgoing: outgoing, keeping: [AVPlayerItem(asset: shared)])
|
|
427
|
+
XCTAssertTrue(dropped.isEmpty)
|
|
471
428
|
}
|
|
472
429
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let bitsPerSample: UInt16 = 16
|
|
479
|
-
let frameCount = 1024
|
|
480
|
-
let dataSize = UInt32(frameCount) * UInt32(channels) * UInt32(bitsPerSample / 8)
|
|
481
|
-
var data = Data()
|
|
482
|
-
func appendLE<T: FixedWidthInteger>(_ value: T) {
|
|
483
|
-
withUnsafeBytes(of: value.littleEndian) { data.append(contentsOf: $0) }
|
|
484
|
-
}
|
|
485
|
-
data.append(contentsOf: Array("RIFF".utf8))
|
|
486
|
-
appendLE(UInt32(36) + dataSize)
|
|
487
|
-
data.append(contentsOf: Array("WAVE".utf8))
|
|
488
|
-
data.append(contentsOf: Array("fmt ".utf8))
|
|
489
|
-
appendLE(UInt32(16))
|
|
490
|
-
appendLE(UInt16(1))
|
|
491
|
-
appendLE(channels)
|
|
492
|
-
appendLE(sampleRate)
|
|
493
|
-
appendLE(sampleRate * UInt32(channels) * UInt32(bitsPerSample / 8))
|
|
494
|
-
appendLE(UInt16(channels * bitsPerSample / 8))
|
|
495
|
-
appendLE(bitsPerSample)
|
|
496
|
-
data.append(contentsOf: Array("data".utf8))
|
|
497
|
-
appendLE(dataSize)
|
|
498
|
-
data.append(Data(count: Int(dataSize)))
|
|
499
|
-
let url = FileManager.default.temporaryDirectory
|
|
500
|
-
.appendingPathComponent("preload-\(UUID().uuidString).wav")
|
|
501
|
-
try data.write(to: url)
|
|
502
|
-
return url
|
|
430
|
+
func testAnUnrelatedSurvivorDoesNotProtectOtherAssets() {
|
|
431
|
+
let outgoing = [makeItem("a"), makeItem("b")]
|
|
432
|
+
let dropped = AVQueueBuilder.itemsBeingDropped(
|
|
433
|
+
outgoing: outgoing, keeping: [makeItem("c")])
|
|
434
|
+
XCTAssertEqual(dropped.count, 2)
|
|
503
435
|
}
|
|
436
|
+
|
|
437
|
+
private func makeItem(_ name: String) -> AVPlayerItem {
|
|
438
|
+
AVPlayerItem(asset: AVURLAsset(url: URL(string: "https://example.com/\(name).m4a")!))
|
|
439
|
+
}
|
|
440
|
+
|
|
504
441
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// A `PlaybackEngine` that reports one active item and nothing else; for
|
|
5
|
+
/// tests whose subject reads `activeMediaItem` and needs a real
|
|
6
|
+
/// `AVPlayerItem` identity behind it.
|
|
7
|
+
final class ActiveItemEngineStub: PlaybackEngine {
|
|
8
|
+
var item: AVPlayerItem?
|
|
9
|
+
private(set) var released = false
|
|
10
|
+
private(set) var playCalls = 0
|
|
11
|
+
|
|
12
|
+
var currentMediaItem: AVPlayerItem? { item }
|
|
13
|
+
var allMediaItems: [AVPlayerItem] { item.map { [$0] } ?? [] }
|
|
14
|
+
var attachedQueueItemIds: [String] { [] }
|
|
15
|
+
var isFadePendingOrActive: Bool { false }
|
|
16
|
+
var currentMediaItemIndex: Int { item == nil ? -1 : 0 }
|
|
17
|
+
var currentPositionSeconds: TimeInterval { 0 }
|
|
18
|
+
var currentDurationSeconds: TimeInterval { 0 }
|
|
19
|
+
var bufferedPositionSeconds: TimeInterval { 0 }
|
|
20
|
+
var isPlaying: Bool { false }
|
|
21
|
+
var timeControlStatus: AVPlayer.TimeControlStatus { .paused }
|
|
22
|
+
var delegate: PlaybackEngineDelegate?
|
|
23
|
+
|
|
24
|
+
func release() { released = true }
|
|
25
|
+
func setItems(_ items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) {}
|
|
26
|
+
@discardableResult
|
|
27
|
+
func insertItems(_ items: [AVPlayerItem], after: AVPlayerItem?) -> Bool { true }
|
|
28
|
+
func remove(_ item: AVPlayerItem) {}
|
|
29
|
+
func removeAllItems() {}
|
|
30
|
+
func canInsert(_ item: AVPlayerItem, after: AVPlayerItem?) -> Bool { true }
|
|
31
|
+
func play() { playCalls += 1 }
|
|
32
|
+
func pause() {}
|
|
33
|
+
func stop() {}
|
|
34
|
+
func seek(toIndex index: Int, position: TimeInterval) {}
|
|
35
|
+
func skipToNext() {}
|
|
36
|
+
func skipToPrevious() {}
|
|
37
|
+
func setVolume(_ volume: Float) {}
|
|
38
|
+
func setPlaybackSpeed(_ speed: Float) {}
|
|
39
|
+
func seedPlaybackSpeed(_ speed: Float) {}
|
|
40
|
+
func setRepeatMode(_ mode: RepeatMode) {}
|
|
41
|
+
func setPitchCorrectionMode(_ mode: PitchCorrectionMode) {}
|
|
42
|
+
func setAudioMixProvider(_ provider: AudioMixProvider?) {}
|
|
43
|
+
func refreshAllItemMixes() {}
|
|
44
|
+
}
|