react-native-queue-player 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +3 -19
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +8 -69
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +839 -271
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4619
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import Network
|
|
3
|
-
|
|
4
|
-
/// Resolution of an HTTP `Range` header against a known file size.
|
|
5
|
-
enum MediaByteRange: Equatable {
|
|
6
|
-
/// No Range header, or one the server ignores (malformed / invalid
|
|
7
|
-
/// bounds) — serve the whole file with `200`.
|
|
8
|
-
case full
|
|
9
|
-
/// A satisfiable single range with inclusive bounds — serve `206`.
|
|
10
|
-
case partial(start: Int64, end: Int64)
|
|
11
|
-
/// A syntactically-valid range that starts past EOF — serve `416`.
|
|
12
|
-
case unsatisfiable
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/// Parsed view of an incoming request: the method, the request target,
|
|
16
|
-
/// and the one header the file server acts on (`Range`). Built from the
|
|
17
|
-
/// raw header block (everything before the `\r\n\r\n` terminator).
|
|
18
|
-
struct MediaHTTPRequest: Equatable {
|
|
19
|
-
let method: String
|
|
20
|
-
let path: String
|
|
21
|
-
let rangeHeader: String?
|
|
22
|
-
|
|
23
|
-
/// The `<token>` from a `/media/<32-hex>` path, or `nil` when the path
|
|
24
|
-
/// is anything else. The token is both the routing key (which file) and
|
|
25
|
-
/// the access secret (high-entropy, unenumerable) for the LAN-reachable
|
|
26
|
-
/// server.
|
|
27
|
-
var mediaToken: String? {
|
|
28
|
-
let prefix = "/media/"
|
|
29
|
-
guard path.hasPrefix(prefix) else { return nil }
|
|
30
|
-
let token = String(path.dropFirst(prefix.count))
|
|
31
|
-
guard token.count == 32,
|
|
32
|
-
token.allSatisfy({ ($0 >= "0" && $0 <= "9") || ($0 >= "a" && $0 <= "f") })
|
|
33
|
-
else { return nil }
|
|
34
|
-
return token
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
init?(headerData: Data) {
|
|
38
|
-
guard let text = String(data: headerData, encoding: .utf8) else { return nil }
|
|
39
|
-
var lines = text.components(separatedBy: "\r\n")
|
|
40
|
-
guard !lines.isEmpty else { return nil }
|
|
41
|
-
let requestLine = lines.removeFirst().split(separator: " ")
|
|
42
|
-
guard requestLine.count >= 2 else { return nil }
|
|
43
|
-
self.method = String(requestLine[0]).uppercased()
|
|
44
|
-
// Strip any query string — the file path never carries one, and the
|
|
45
|
-
// token match below expects the bare path.
|
|
46
|
-
let target = String(requestLine[1])
|
|
47
|
-
self.path = target.split(separator: "?", maxSplits: 1).first.map(String.init) ?? target
|
|
48
|
-
|
|
49
|
-
var range: String?
|
|
50
|
-
for line in lines where !line.isEmpty {
|
|
51
|
-
guard let colon = line.firstIndex(of: ":") else { continue }
|
|
52
|
-
let name = line[line.startIndex..<colon].trimmingCharacters(in: .whitespaces).lowercased()
|
|
53
|
-
if name == "range" {
|
|
54
|
-
range = String(line[line.index(after: colon)...]).trimmingCharacters(in: .whitespaces)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
self.rangeHeader = range
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/// Serves one local file over a single `NWConnection` as HTTP/1.1:
|
|
62
|
-
/// `GET`/`HEAD` on `/media/<token>` with `Range` support (the Cast
|
|
63
|
-
/// receiver byte-range probes + seeks). One responder per accepted
|
|
64
|
-
/// connection; it owns the connection, runs all I/O on a dedicated serial
|
|
65
|
-
/// queue, streams the body in bounded chunks (no whole-file buffering),
|
|
66
|
-
/// and reports completion via `onFinished` so the owner can drop it.
|
|
67
|
-
final class MediaHTTPConnection {
|
|
68
|
-
|
|
69
|
-
private let connection: NWConnection
|
|
70
|
-
private let registry: MediaTokenRegistry
|
|
71
|
-
private let onFinished: () -> Void
|
|
72
|
-
private let queue: DispatchQueue
|
|
73
|
-
/// In-flight body-streaming task, cancelled when the client hangs up so
|
|
74
|
-
/// we stop reading a range the receiver has abandoned.
|
|
75
|
-
private var streamTask: Task<Void, Never>?
|
|
76
|
-
private let stateLock = NSLock()
|
|
77
|
-
private var finished = false
|
|
78
|
-
|
|
79
|
-
/// Bound on the accumulated request header so a stalled/malicious client
|
|
80
|
-
/// can't grow the buffer without limit.
|
|
81
|
-
private static let maxHeaderBytes = 64 * 1024
|
|
82
|
-
/// Body streaming chunk — keeps memory flat for large offline files.
|
|
83
|
-
private static let chunkBytes = 256 * 1024
|
|
84
|
-
private static let headerTerminator = Data("\r\n\r\n".utf8)
|
|
85
|
-
|
|
86
|
-
init(
|
|
87
|
-
connection: NWConnection,
|
|
88
|
-
registry: MediaTokenRegistry,
|
|
89
|
-
onFinished: @escaping () -> Void
|
|
90
|
-
) {
|
|
91
|
-
self.connection = connection
|
|
92
|
-
self.registry = registry
|
|
93
|
-
self.onFinished = onFinished
|
|
94
|
-
self.queue = DispatchQueue(label: "queueplayer.cast.mediaserver.conn")
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
func start() {
|
|
98
|
-
connection.stateUpdateHandler = { [weak self] state in
|
|
99
|
-
guard let self = self else { return }
|
|
100
|
-
switch state {
|
|
101
|
-
case .ready:
|
|
102
|
-
self.readRequest(buffer: Data())
|
|
103
|
-
case .failed(let error):
|
|
104
|
-
// A receiver cancelling a range connection it no longer needs
|
|
105
|
-
// arrives here as a reset — expected churn, not an error.
|
|
106
|
-
if !Self.isBenignDisconnect(error) {
|
|
107
|
-
NSLog("[LocalMediaServer] connection failed: \(error)")
|
|
108
|
-
}
|
|
109
|
-
self.cancel()
|
|
110
|
-
case .cancelled:
|
|
111
|
-
self.finish()
|
|
112
|
-
default:
|
|
113
|
-
break
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
connection.start(queue: queue)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/// Cancel the connection + any in-flight streaming. Idempotent.
|
|
120
|
-
func cancel() {
|
|
121
|
-
stateLock.lock()
|
|
122
|
-
let task = streamTask
|
|
123
|
-
streamTask = nil
|
|
124
|
-
stateLock.unlock()
|
|
125
|
-
task?.cancel()
|
|
126
|
-
connection.cancel()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// MARK: - Request
|
|
130
|
-
|
|
131
|
-
private func readRequest(buffer: Data) {
|
|
132
|
-
connection.receive(minimumIncompleteLength: 1, maximumLength: 16 * 1024) {
|
|
133
|
-
[weak self] data, _, isComplete, error in
|
|
134
|
-
guard let self = self else { return }
|
|
135
|
-
if error != nil {
|
|
136
|
-
self.cancel()
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
var buf = buffer
|
|
140
|
-
if let data = data { buf.append(data) }
|
|
141
|
-
// Bound the header accumulation right after the append so a client
|
|
142
|
-
// that never sends `\r\n\r\n` can't grow the buffer past the cap.
|
|
143
|
-
if buf.count > Self.maxHeaderBytes {
|
|
144
|
-
self.respondError(status: 431, reason: "Request Header Fields Too Large")
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
guard let terminator = Self.firstRange(of: Self.headerTerminator, in: buf) else {
|
|
148
|
-
if isComplete {
|
|
149
|
-
self.cancel()
|
|
150
|
-
} else {
|
|
151
|
-
self.readRequest(buffer: buf)
|
|
152
|
-
}
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
let headerData = buf.subdata(in: buf.startIndex..<terminator.lowerBound)
|
|
156
|
-
guard let request = MediaHTTPRequest(headerData: headerData) else {
|
|
157
|
-
self.respondError(status: 400, reason: "Bad Request")
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
self.serve(request)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private func serve(_ request: MediaHTTPRequest) {
|
|
165
|
-
guard request.method == "GET" || request.method == "HEAD" else {
|
|
166
|
-
respondError(status: 405, reason: "Method Not Allowed")
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
guard let token = request.mediaToken, let media = registry.resolve(token) else {
|
|
170
|
-
respondError(status: 404, reason: "Not Found")
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
let fm = FileManager.default
|
|
174
|
-
var isDir: ObjCBool = false
|
|
175
|
-
guard fm.fileExists(atPath: media.fileURL.path, isDirectory: &isDir), !isDir.boolValue,
|
|
176
|
-
let attrs = try? fm.attributesOfItem(atPath: media.fileURL.path),
|
|
177
|
-
let size = (attrs[.size] as? NSNumber)?.int64Value
|
|
178
|
-
else {
|
|
179
|
-
respondError(status: 404, reason: "Not Found")
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let isHead = request.method == "HEAD"
|
|
184
|
-
switch Self.resolveRange(request.rangeHeader, fileSize: size) {
|
|
185
|
-
case .full:
|
|
186
|
-
let head = Self.headFull(contentType: media.contentType, fileSize: size)
|
|
187
|
-
sendBodyAfterHead(head, fileURL: media.fileURL, start: 0, end: max(0, size - 1), include: size > 0 && !isHead)
|
|
188
|
-
case .partial(let start, let end):
|
|
189
|
-
let head = Self.headPartial(contentType: media.contentType, start: start, end: end, fileSize: size)
|
|
190
|
-
sendBodyAfterHead(head, fileURL: media.fileURL, start: start, end: end, include: !isHead)
|
|
191
|
-
case .unsatisfiable:
|
|
192
|
-
sendHeadOnly(Self.headUnsatisfiable(fileSize: size))
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// MARK: - Response
|
|
197
|
-
|
|
198
|
-
private func sendHeadOnly(_ head: String) {
|
|
199
|
-
connection.send(content: Data(head.utf8), completion: .contentProcessed { [weak self] _ in
|
|
200
|
-
self?.cancel()
|
|
201
|
-
})
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/// Send the header block, then (when `include`) stream the inclusive
|
|
205
|
-
/// `[start, end]` byte range from `fileURL` in bounded chunks. A `HEAD`
|
|
206
|
-
/// or an empty body sends the head and closes.
|
|
207
|
-
private func sendBodyAfterHead(_ head: String, fileURL: URL, start: Int64, end: Int64, include: Bool) {
|
|
208
|
-
let task = Task { [weak self] in
|
|
209
|
-
guard let self = self else { return }
|
|
210
|
-
do {
|
|
211
|
-
try await self.send(Data(head.utf8))
|
|
212
|
-
if include {
|
|
213
|
-
try await self.streamFile(fileURL, start: start, end: end)
|
|
214
|
-
}
|
|
215
|
-
self.cancel()
|
|
216
|
-
} catch {
|
|
217
|
-
// A client disconnect mid-stream surfaces as a send/read failure —
|
|
218
|
-
// normal churn for cancelled range requests, just clean up.
|
|
219
|
-
self.cancel()
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
stateLock.lock()
|
|
223
|
-
streamTask = task
|
|
224
|
-
stateLock.unlock()
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private func streamFile(_ fileURL: URL, start: Int64, end: Int64) async throws {
|
|
228
|
-
let handle = try FileHandle(forReadingFrom: fileURL)
|
|
229
|
-
defer { try? handle.close() }
|
|
230
|
-
try handle.seek(toOffset: UInt64(start))
|
|
231
|
-
var remaining = end - start + 1
|
|
232
|
-
while remaining > 0 {
|
|
233
|
-
if Task.isCancelled { return }
|
|
234
|
-
let want = Int(min(Int64(Self.chunkBytes), remaining))
|
|
235
|
-
guard let chunk = try handle.read(upToCount: want), !chunk.isEmpty else { return }
|
|
236
|
-
try await send(chunk)
|
|
237
|
-
remaining -= Int64(chunk.count)
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private func send(_ data: Data) async throws {
|
|
242
|
-
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
|
|
243
|
-
connection.send(content: data, completion: .contentProcessed { error in
|
|
244
|
-
if let error = error { cont.resume(throwing: error) } else { cont.resume(returning: ()) }
|
|
245
|
-
})
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private func respondError(status: Int, reason: String) {
|
|
250
|
-
sendHeadOnly(Self.headError(status: status, reason: reason))
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private func finish() {
|
|
254
|
-
stateLock.lock()
|
|
255
|
-
if finished { stateLock.unlock(); return }
|
|
256
|
-
finished = true
|
|
257
|
-
let task = streamTask
|
|
258
|
-
streamTask = nil
|
|
259
|
-
stateLock.unlock()
|
|
260
|
-
task?.cancel()
|
|
261
|
-
onFinished()
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// MARK: - Range resolution (pure)
|
|
265
|
-
|
|
266
|
-
/// Resolve an HTTP `Range` header against `fileSize`. Single-range only
|
|
267
|
-
/// (the Cast receiver never sends multi-range); a multi-range header
|
|
268
|
-
/// resolves to its first range. Per RFC 7233 a malformed / inverted
|
|
269
|
-
/// range is ignored (`.full`, i.e. `200`); a syntactically-valid range
|
|
270
|
-
/// starting at or past EOF is `.unsatisfiable` (`416`).
|
|
271
|
-
static func resolveRange(_ header: String?, fileSize: Int64) -> MediaByteRange {
|
|
272
|
-
guard fileSize > 0 else { return .full }
|
|
273
|
-
guard let raw = header?.lowercased(), raw.hasPrefix("bytes=") else { return .full }
|
|
274
|
-
let spec = raw.dropFirst("bytes=".count)
|
|
275
|
-
let firstSpec = spec.split(separator: ",").first.map(String.init) ?? ""
|
|
276
|
-
guard let dash = firstSpec.firstIndex(of: "-") else { return .full }
|
|
277
|
-
let startStr = firstSpec[firstSpec.startIndex..<dash].trimmingCharacters(in: .whitespaces)
|
|
278
|
-
let endStr = firstSpec[firstSpec.index(after: dash)...].trimmingCharacters(in: .whitespaces)
|
|
279
|
-
|
|
280
|
-
if startStr.isEmpty {
|
|
281
|
-
// Suffix form `-N`: the last N bytes.
|
|
282
|
-
guard let suffix = Int64(endStr), suffix > 0 else { return .unsatisfiable }
|
|
283
|
-
let start = max(0, fileSize - suffix)
|
|
284
|
-
return .partial(start: start, end: fileSize - 1)
|
|
285
|
-
}
|
|
286
|
-
guard let start = Int64(startStr), start >= 0 else { return .full }
|
|
287
|
-
if start >= fileSize { return .unsatisfiable }
|
|
288
|
-
if endStr.isEmpty {
|
|
289
|
-
return .partial(start: start, end: fileSize - 1)
|
|
290
|
-
}
|
|
291
|
-
guard let end = Int64(endStr) else { return .full }
|
|
292
|
-
if end < start { return .full }
|
|
293
|
-
return .partial(start: start, end: min(end, fileSize - 1))
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// MARK: - Header builders (pure)
|
|
297
|
-
|
|
298
|
-
static func headFull(contentType: String, fileSize: Int64) -> String {
|
|
299
|
-
"HTTP/1.1 200 OK\r\n"
|
|
300
|
-
+ "Content-Type: \(contentType)\r\n"
|
|
301
|
-
+ "Content-Length: \(fileSize)\r\n"
|
|
302
|
-
+ "Accept-Ranges: bytes\r\n"
|
|
303
|
-
+ "Connection: close\r\n\r\n"
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
static func headPartial(contentType: String, start: Int64, end: Int64, fileSize: Int64) -> String {
|
|
307
|
-
"HTTP/1.1 206 Partial Content\r\n"
|
|
308
|
-
+ "Content-Type: \(contentType)\r\n"
|
|
309
|
-
+ "Content-Range: bytes \(start)-\(end)/\(fileSize)\r\n"
|
|
310
|
-
+ "Content-Length: \(end - start + 1)\r\n"
|
|
311
|
-
+ "Accept-Ranges: bytes\r\n"
|
|
312
|
-
+ "Connection: close\r\n\r\n"
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
static func headUnsatisfiable(fileSize: Int64) -> String {
|
|
316
|
-
"HTTP/1.1 416 Range Not Satisfiable\r\n"
|
|
317
|
-
+ "Content-Range: bytes */\(fileSize)\r\n"
|
|
318
|
-
+ "Content-Length: 0\r\n"
|
|
319
|
-
+ "Connection: close\r\n\r\n"
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
static func headError(status: Int, reason: String) -> String {
|
|
323
|
-
"HTTP/1.1 \(status) \(reason)\r\n"
|
|
324
|
-
+ "Content-Length: 0\r\n"
|
|
325
|
-
+ "Connection: close\r\n\r\n"
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// MARK: - Helpers
|
|
329
|
-
|
|
330
|
-
static func firstRange(of needle: Data, in haystack: Data) -> Range<Int>? {
|
|
331
|
-
guard needle.count <= haystack.count, !needle.isEmpty else { return nil }
|
|
332
|
-
let upper = haystack.count - needle.count
|
|
333
|
-
var i = haystack.startIndex
|
|
334
|
-
while i <= upper {
|
|
335
|
-
if haystack[i..<(i + needle.count)].elementsEqual(needle) {
|
|
336
|
-
return i..<(i + needle.count)
|
|
337
|
-
}
|
|
338
|
-
i += 1
|
|
339
|
-
}
|
|
340
|
-
return nil
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
private static func isBenignDisconnect(_ error: NWError) -> Bool {
|
|
344
|
-
if case .posix(let code) = error {
|
|
345
|
-
return code == .ECONNRESET || code == .ENOTCONN || code == .EPIPE || code == .ECANCELED
|
|
346
|
-
}
|
|
347
|
-
return false
|
|
348
|
-
}
|
|
349
|
-
}
|