react-native-queue-player 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -1,603 +0,0 @@
|
|
|
1
|
-
import XCTest
|
|
2
|
-
@testable import QueuePlayer
|
|
3
|
-
|
|
4
|
-
/// Byte-level unit tests for `MetadataReader`'s container parsers.
|
|
5
|
-
/// Builds synthetic ID3v2 / FLAC / MP4 byte sequences in-memory and
|
|
6
|
-
/// verifies tag extraction surfaces the expected `[key: value]` map.
|
|
7
|
-
final class MetadataReaderTests: XCTestCase {
|
|
8
|
-
|
|
9
|
-
// MARK: - ID3v2 TXXX
|
|
10
|
-
|
|
11
|
-
func testID3v23ExtractsSingleTXXXReplayGainTrackGain() {
|
|
12
|
-
// ID3v2.3 with one TXXX frame:
|
|
13
|
-
// description="REPLAYGAIN_TRACK_GAIN", value="-7.85 dB"
|
|
14
|
-
let data = makeID3v23(frames: [
|
|
15
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-7.85 dB")
|
|
16
|
-
])
|
|
17
|
-
let tags = MetadataReader.parseID3v2(data)
|
|
18
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-7.85 dB")
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func testID3v23ExtractsAllFourReplayGainTags() {
|
|
22
|
-
let data = makeID3v23(frames: [
|
|
23
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-7.85 dB"),
|
|
24
|
-
.txxx(description: "REPLAYGAIN_TRACK_PEAK", value: "1.160082"),
|
|
25
|
-
.txxx(description: "REPLAYGAIN_ALBUM_GAIN", value: "-6.05 dB"),
|
|
26
|
-
.txxx(description: "REPLAYGAIN_ALBUM_PEAK", value: "1.160082"),
|
|
27
|
-
])
|
|
28
|
-
let tags = MetadataReader.parseID3v2(data)
|
|
29
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-7.85 dB")
|
|
30
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_PEAK"], "1.160082")
|
|
31
|
-
XCTAssertEqual(tags["REPLAYGAIN_ALBUM_GAIN"], "-6.05 dB")
|
|
32
|
-
XCTAssertEqual(tags["REPLAYGAIN_ALBUM_PEAK"], "1.160082")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func testID3v24ExtractsTXXXReplayGainTags() {
|
|
36
|
-
// v2.4 syncsafe frame size — verify the 28-bit decode path.
|
|
37
|
-
let data = makeID3v24(frames: [
|
|
38
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-7.85 dB"),
|
|
39
|
-
])
|
|
40
|
-
let tags = MetadataReader.parseID3v2(data)
|
|
41
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-7.85 dB")
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func testID3v23ReturnsEmptyForNonReplayGainTags() {
|
|
45
|
-
let data = makeID3v23(frames: [
|
|
46
|
-
.txxx(description: "TITLE", value: "A Song"),
|
|
47
|
-
.txxx(description: "ARTIST", value: "Some Band"),
|
|
48
|
-
])
|
|
49
|
-
let tags = MetadataReader.parseID3v2(data)
|
|
50
|
-
// Parser returns ALL TXXX descriptions; consumer filters for RG.
|
|
51
|
-
// Confirm RG keys absent + non-RG present.
|
|
52
|
-
XCTAssertNil(tags["REPLAYGAIN_TRACK_GAIN"])
|
|
53
|
-
XCTAssertEqual(tags["TITLE"], "A Song")
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
func testID3v23ReturnsEmptyOnNonID3Bytes() {
|
|
57
|
-
let data = Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
58
|
-
let tags = MetadataReader.parseID3v2(data)
|
|
59
|
-
XCTAssertTrue(tags.isEmpty)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/// foobar2000 emits TXXX values with a trailing `\0` even though
|
|
63
|
-
/// the spec leaves the final field unterminated. Decoded values
|
|
64
|
-
/// must be trimmed of the trailing null so downstream `Double.init`
|
|
65
|
-
/// parses the gain string cleanly.
|
|
66
|
-
func testID3v23TXXXWithEncodingZeroAndTrailingNullValue() {
|
|
67
|
-
// encoding=0 ISO-8859-1, description "REPLAYGAIN_TRACK_GAIN" + 00,
|
|
68
|
-
// value "-7.85 dB" + trailing 00 (foobar2000-style).
|
|
69
|
-
var payload = Data([0x00])
|
|
70
|
-
payload.append("REPLAYGAIN_TRACK_GAIN".data(using: .isoLatin1)!)
|
|
71
|
-
payload.append(0x00)
|
|
72
|
-
payload.append("-7.85 dB".data(using: .isoLatin1)!)
|
|
73
|
-
payload.append(0x00)
|
|
74
|
-
var tag = Data()
|
|
75
|
-
tag.append("TXXX".data(using: .ascii)!)
|
|
76
|
-
tag.append(beUInt32(UInt32(payload.count)))
|
|
77
|
-
tag.append(Data([0x00, 0x00]))
|
|
78
|
-
tag.append(payload)
|
|
79
|
-
var full = Data()
|
|
80
|
-
full.append("ID3".data(using: .ascii)!)
|
|
81
|
-
full.append(0x03) // major version
|
|
82
|
-
full.append(0x00) // minor
|
|
83
|
-
full.append(0x00) // flags
|
|
84
|
-
full.append(syncsafeUInt32(UInt32(tag.count)))
|
|
85
|
-
full.append(tag)
|
|
86
|
-
let tags = MetadataReader.parseID3v2(full)
|
|
87
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-7.85 dB",
|
|
88
|
-
"trailing-null TXXX value should round-trip clean — no \\0 in decoded value")
|
|
89
|
-
let rg = MetadataReader.rgDataFromTags(tags)
|
|
90
|
-
XCTAssertNotNil(rg)
|
|
91
|
-
XCTAssertEqual(rg?.trackGainDb, -7.85)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// foobar2000 + most ID3v2.3 taggers write TXXX with encoding=01
|
|
95
|
-
/// (UTF-16 with BOM). Description and value each carry their own
|
|
96
|
-
/// `0xFF 0xFE` BOM. Decoded strings must NOT retain the BOM as a
|
|
97
|
-
/// leading U+FEFF — that would prevent `REPLAYGAIN_*` lookups.
|
|
98
|
-
func testID3v23TXXXWithUTF16BOMStripsBOMFromDescriptionAndValue() {
|
|
99
|
-
let payload = utf16BOMTXXXFramePayload(
|
|
100
|
-
description: "REPLAYGAIN_TRACK_GAIN",
|
|
101
|
-
value: "-7.85 dB"
|
|
102
|
-
)
|
|
103
|
-
var tag = Data()
|
|
104
|
-
tag.append("TXXX".data(using: .ascii)!)
|
|
105
|
-
tag.append(beUInt32(UInt32(payload.count)))
|
|
106
|
-
tag.append(Data([0x00, 0x00]))
|
|
107
|
-
tag.append(payload)
|
|
108
|
-
var full = Data()
|
|
109
|
-
full.append("ID3".data(using: .ascii)!)
|
|
110
|
-
full.append(0x03) // major version
|
|
111
|
-
full.append(0x00) // minor
|
|
112
|
-
full.append(0x00) // flags
|
|
113
|
-
full.append(syncsafeUInt32(UInt32(tag.count)))
|
|
114
|
-
full.append(tag)
|
|
115
|
-
let tags = MetadataReader.parseID3v2(full)
|
|
116
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-7.85 dB",
|
|
117
|
-
"BOM-prefixed UTF-16 description should map to bare REPLAYGAIN_TRACK_GAIN key")
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// MARK: - FLAC Vorbis comments
|
|
121
|
-
|
|
122
|
-
func testFLACExtractsVorbisReplayGainTags() {
|
|
123
|
-
let data = makeFLAC(comments: [
|
|
124
|
-
"REPLAYGAIN_TRACK_GAIN=4.43 dB",
|
|
125
|
-
"REPLAYGAIN_TRACK_PEAK=0.621351",
|
|
126
|
-
"REPLAYGAIN_ALBUM_GAIN=0.30 dB",
|
|
127
|
-
"REPLAYGAIN_ALBUM_PEAK=0.923366",
|
|
128
|
-
])
|
|
129
|
-
let tags = MetadataReader.parseFLAC(data)
|
|
130
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "4.43 dB")
|
|
131
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_PEAK"], "0.621351")
|
|
132
|
-
XCTAssertEqual(tags["REPLAYGAIN_ALBUM_GAIN"], "0.30 dB")
|
|
133
|
-
XCTAssertEqual(tags["REPLAYGAIN_ALBUM_PEAK"], "0.923366")
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
func testFLACReturnsEmptyOnNonFLACBytes() {
|
|
137
|
-
let data = Data([0x00, 0x01, 0x02, 0x03])
|
|
138
|
-
let tags = MetadataReader.parseFLAC(data)
|
|
139
|
-
XCTAssertTrue(tags.isEmpty)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
func testFLACStreamInfoExtractsSampleRateAndTotalSamples() throws {
|
|
143
|
-
// Build a STREAMINFO-only FLAC header.
|
|
144
|
-
// sampleRate=44100, channels=2, bitsPerSample=16, totalSamples=1234567
|
|
145
|
-
let info = makeFLACStreamInfo(
|
|
146
|
-
sampleRate: 44100,
|
|
147
|
-
channels: 2,
|
|
148
|
-
bitsPerSample: 16,
|
|
149
|
-
totalSamples: 1_234_567
|
|
150
|
-
)
|
|
151
|
-
let parsed = try XCTUnwrap(MetadataReader.parseFLACStreamInfo(info))
|
|
152
|
-
XCTAssertEqual(parsed.sampleRate, 44100)
|
|
153
|
-
XCTAssertEqual(parsed.channelCount, 2)
|
|
154
|
-
XCTAssertEqual(parsed.bitsPerSample, 16)
|
|
155
|
-
XCTAssertEqual(parsed.totalSamples, 1_234_567)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
func testFLACStreamInfoHighSampleRate() throws {
|
|
159
|
-
// foobar2000 + some lossless masters use 96 kHz / 24-bit. Verify
|
|
160
|
-
// the 20-bit sampleRate field isn't truncated.
|
|
161
|
-
let info = makeFLACStreamInfo(
|
|
162
|
-
sampleRate: 96000,
|
|
163
|
-
channels: 2,
|
|
164
|
-
bitsPerSample: 24,
|
|
165
|
-
totalSamples: 9_876_543
|
|
166
|
-
)
|
|
167
|
-
let parsed = try XCTUnwrap(MetadataReader.parseFLACStreamInfo(info))
|
|
168
|
-
XCTAssertEqual(parsed.sampleRate, 96000)
|
|
169
|
-
XCTAssertEqual(parsed.bitsPerSample, 24)
|
|
170
|
-
XCTAssertEqual(parsed.totalSamples, 9_876_543)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
func testFLACStreamInfoReturnsNilOnNonFLACBytes() {
|
|
174
|
-
XCTAssertNil(MetadataReader.parseFLACStreamInfo(
|
|
175
|
-
Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
|
176
|
-
))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
func testFLACSkipsStreamInfoAndReachesVorbisComment() {
|
|
180
|
-
// STREAMINFO (type 0, 34 bytes), then VORBIS_COMMENT (type 4).
|
|
181
|
-
// Verify the block walker advances past the first block.
|
|
182
|
-
let data = makeFLAC(
|
|
183
|
-
includeStreamInfo: true,
|
|
184
|
-
comments: ["REPLAYGAIN_TRACK_GAIN=-1.00 dB"]
|
|
185
|
-
)
|
|
186
|
-
let tags = MetadataReader.parseFLAC(data)
|
|
187
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-1.00 dB")
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// MARK: - MP4 freeform iTunes-style atoms
|
|
191
|
-
|
|
192
|
-
func testMP4ExtractsFreeformReplayGainTags() {
|
|
193
|
-
let data = makeMP4(freeformTags: [
|
|
194
|
-
"REPLAYGAIN_TRACK_GAIN": "-3.21 dB",
|
|
195
|
-
"REPLAYGAIN_ALBUM_GAIN": "-2.10 dB",
|
|
196
|
-
])
|
|
197
|
-
let tags = MetadataReader.parseMP4(data)
|
|
198
|
-
XCTAssertEqual(tags["REPLAYGAIN_TRACK_GAIN"], "-3.21 dB")
|
|
199
|
-
XCTAssertEqual(tags["REPLAYGAIN_ALBUM_GAIN"], "-2.10 dB")
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
func testMP4ReturnsEmptyWhenNoIlstAtom() {
|
|
203
|
-
// Just an ftyp atom with no moov.
|
|
204
|
-
var data = Data()
|
|
205
|
-
data.append(makeAtom(type: "ftyp", body: Data(repeating: 0, count: 16)))
|
|
206
|
-
let tags = MetadataReader.parseMP4(data)
|
|
207
|
-
XCTAssertTrue(tags.isEmpty)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// MARK: - Format dispatch (parseTags)
|
|
211
|
-
|
|
212
|
-
func testParseTagsDispatchesByMagicBytes() {
|
|
213
|
-
let mp3 = makeID3v23(frames: [
|
|
214
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-1.0 dB")
|
|
215
|
-
])
|
|
216
|
-
XCTAssertEqual(
|
|
217
|
-
MetadataReader.parseTags(from: mp3)["REPLAYGAIN_TRACK_GAIN"], "-1.0 dB"
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
let flac = makeFLAC(comments: ["REPLAYGAIN_TRACK_GAIN=-2.0 dB"])
|
|
221
|
-
XCTAssertEqual(
|
|
222
|
-
MetadataReader.parseTags(from: flac)["REPLAYGAIN_TRACK_GAIN"], "-2.0 dB"
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
let mp4 = makeMP4(freeformTags: ["REPLAYGAIN_TRACK_GAIN": "-3.0 dB"])
|
|
226
|
-
XCTAssertEqual(
|
|
227
|
-
MetadataReader.parseTags(from: mp4)["REPLAYGAIN_TRACK_GAIN"], "-3.0 dB"
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// MARK: - rgDataFromTags integration
|
|
232
|
-
|
|
233
|
-
func testRgDataFromTagsParsesAllFourFields() throws {
|
|
234
|
-
let data = try XCTUnwrap(MetadataReader.rgDataFromTags([
|
|
235
|
-
"REPLAYGAIN_TRACK_GAIN": "-12.0 dB",
|
|
236
|
-
"REPLAYGAIN_TRACK_PEAK": "0.95",
|
|
237
|
-
"REPLAYGAIN_ALBUM_GAIN": "-8.0 dB",
|
|
238
|
-
"REPLAYGAIN_ALBUM_PEAK": "0.90",
|
|
239
|
-
]))
|
|
240
|
-
XCTAssertEqual(try XCTUnwrap(data.trackGainDb), -12.0, accuracy: 0.001)
|
|
241
|
-
XCTAssertEqual(try XCTUnwrap(data.trackPeak), 0.95, accuracy: 0.001)
|
|
242
|
-
XCTAssertEqual(try XCTUnwrap(data.albumGainDb), -8.0, accuracy: 0.001)
|
|
243
|
-
XCTAssertEqual(try XCTUnwrap(data.albumPeak), 0.90, accuracy: 0.001)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
func testRgDataFromTagsAcceptsLowercaseKeys() throws {
|
|
247
|
-
let data = try XCTUnwrap(MetadataReader.rgDataFromTags([
|
|
248
|
-
"replaygain_track_gain": "-6.5 dB",
|
|
249
|
-
]))
|
|
250
|
-
XCTAssertEqual(try XCTUnwrap(data.trackGainDb), -6.5, accuracy: 0.001)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
func testRgDataFromTagsReturnsNilForEmptyDict() {
|
|
254
|
-
XCTAssertNil(MetadataReader.rgDataFromTags([:]))
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
func testRgDataFromTagsReturnsNilWhenAllValuesUnparseable() {
|
|
258
|
-
XCTAssertNil(MetadataReader.rgDataFromTags([
|
|
259
|
-
"REPLAYGAIN_TRACK_GAIN": "garbage",
|
|
260
|
-
"REPLAYGAIN_TRACK_PEAK": "1.0", // encoder default → rejected
|
|
261
|
-
]))
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// MARK: - Opus container guard
|
|
265
|
-
|
|
266
|
-
func testIsOpusContainerRecognisesOpusExtension() {
|
|
267
|
-
XCTAssertTrue(
|
|
268
|
-
MetadataReader.isOpusContainer(
|
|
269
|
-
url: URL(string: "https://example.test/track.opus")!
|
|
270
|
-
)
|
|
271
|
-
)
|
|
272
|
-
XCTAssertTrue(
|
|
273
|
-
MetadataReader.isOpusContainer(
|
|
274
|
-
url: URL(string: "https://example.test/track.ogg")!
|
|
275
|
-
)
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
func testIsOpusContainerReturnsFalseForOtherFormats() {
|
|
280
|
-
XCTAssertFalse(
|
|
281
|
-
MetadataReader.isOpusContainer(
|
|
282
|
-
url: URL(string: "https://example.test/track.mp3")!
|
|
283
|
-
)
|
|
284
|
-
)
|
|
285
|
-
XCTAssertFalse(
|
|
286
|
-
MetadataReader.isOpusContainer(
|
|
287
|
-
url: URL(string: "https://example.test/track.m4a")!
|
|
288
|
-
)
|
|
289
|
-
)
|
|
290
|
-
XCTAssertFalse(
|
|
291
|
-
MetadataReader.isOpusContainer(
|
|
292
|
-
url: URL(string: "https://example.test/track.flac")!
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// MARK: - End-to-end with file bytes
|
|
298
|
-
|
|
299
|
-
func testReadReplayGainTagsFromTempFileMP3() async throws {
|
|
300
|
-
let bytes = makeID3v23(frames: [
|
|
301
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-7.85 dB"),
|
|
302
|
-
.txxx(description: "REPLAYGAIN_TRACK_PEAK", value: "1.160082"),
|
|
303
|
-
])
|
|
304
|
-
let url = try writeTempFile(bytes: bytes, suffix: ".mp3")
|
|
305
|
-
defer { try? FileManager.default.removeItem(at: url) }
|
|
306
|
-
let result = await MetadataReader.readReplayGainTags(url: url)
|
|
307
|
-
let data = try XCTUnwrap(result)
|
|
308
|
-
XCTAssertEqual(try XCTUnwrap(data.trackGainDb), -7.85, accuracy: 0.001)
|
|
309
|
-
XCTAssertEqual(try XCTUnwrap(data.trackPeak), 1.160082, accuracy: 0.001)
|
|
310
|
-
XCTAssertNil(data.albumGainDb)
|
|
311
|
-
XCTAssertNil(data.albumPeak)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
func testReadReplayGainTagsFromTempFileFLAC() async throws {
|
|
315
|
-
let bytes = makeFLAC(comments: [
|
|
316
|
-
"REPLAYGAIN_TRACK_GAIN=4.43 dB",
|
|
317
|
-
"REPLAYGAIN_ALBUM_GAIN=0.30 dB",
|
|
318
|
-
])
|
|
319
|
-
let url = try writeTempFile(bytes: bytes, suffix: ".flac")
|
|
320
|
-
defer { try? FileManager.default.removeItem(at: url) }
|
|
321
|
-
let result = await MetadataReader.readReplayGainTags(url: url)
|
|
322
|
-
let data = try XCTUnwrap(result)
|
|
323
|
-
XCTAssertEqual(try XCTUnwrap(data.trackGainDb), 4.43, accuracy: 0.001)
|
|
324
|
-
XCTAssertEqual(try XCTUnwrap(data.albumGainDb), 0.30, accuracy: 0.001)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
func testReadReplayGainTagsReturnsNilForOpusURL() async {
|
|
328
|
-
let url = URL(string: "https://example.test/track.opus")!
|
|
329
|
-
let result = await MetadataReader.readReplayGainTags(url: url)
|
|
330
|
-
XCTAssertNil(result)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
func testReadReplayGainTagsCompletesSubSecond() async throws {
|
|
334
|
-
// Latency budget: a local-file extract has no excuse to be slow.
|
|
335
|
-
// ID3v2 / FLAC / MP4 metadata sits at file start; the parser
|
|
336
|
-
// walks ≤256 KB of bytes in pure Swift. 100 ms is generous on
|
|
337
|
-
// any host; missing this budget is a regression.
|
|
338
|
-
let bytes = makeID3v23(frames: [
|
|
339
|
-
.txxx(description: "REPLAYGAIN_TRACK_GAIN", value: "-7.85 dB"),
|
|
340
|
-
])
|
|
341
|
-
let url = try writeTempFile(bytes: bytes, suffix: ".mp3")
|
|
342
|
-
defer { try? FileManager.default.removeItem(at: url) }
|
|
343
|
-
let started = Date()
|
|
344
|
-
_ = await MetadataReader.readReplayGainTags(url: url)
|
|
345
|
-
let elapsed = Date().timeIntervalSince(started)
|
|
346
|
-
XCTAssertLessThan(elapsed, 0.1,
|
|
347
|
-
"extract took \(elapsed)s for a small local file — should be <100 ms")
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// MARK: - Test helpers — byte-level fixture builders
|
|
351
|
-
|
|
352
|
-
private func writeTempFile(bytes: Data, suffix: String) throws -> URL {
|
|
353
|
-
let url = FileManager.default.temporaryDirectory
|
|
354
|
-
.appendingPathComponent(UUID().uuidString + suffix)
|
|
355
|
-
try bytes.write(to: url)
|
|
356
|
-
return url
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
private enum Frame {
|
|
360
|
-
case txxx(description: String, value: String)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/// Build a TXXX frame body with encoding=01 (UTF-16 with BOM).
|
|
364
|
-
/// Description gets `0xFF 0xFE` BOM + UTF-16LE encoded chars + `00 00`
|
|
365
|
-
/// terminator. Value follows with its own BOM + chars (no trailing
|
|
366
|
-
/// terminator).
|
|
367
|
-
private func utf16BOMTXXXFramePayload(
|
|
368
|
-
description: String,
|
|
369
|
-
value: String
|
|
370
|
-
) -> Data {
|
|
371
|
-
var payload = Data([0x01]) // encoding byte: UTF-16 with BOM
|
|
372
|
-
payload.append(contentsOf: [0xFF, 0xFE])
|
|
373
|
-
payload.append(description.data(using: .utf16LittleEndian) ?? Data())
|
|
374
|
-
payload.append(contentsOf: [0x00, 0x00]) // null terminator
|
|
375
|
-
payload.append(contentsOf: [0xFF, 0xFE])
|
|
376
|
-
payload.append(value.data(using: .utf16LittleEndian) ?? Data())
|
|
377
|
-
return payload
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/// Build an ID3v2.3 tag containing the given TXXX frames.
|
|
381
|
-
/// Returns the tag bytes only — no MP3 audio frames follow.
|
|
382
|
-
private func makeID3v23(frames: [Frame]) -> Data {
|
|
383
|
-
return makeID3v2(majorVersion: 3, frames: frames)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/// Build an ID3v2.4 tag containing the given TXXX frames.
|
|
387
|
-
private func makeID3v24(frames: [Frame]) -> Data {
|
|
388
|
-
return makeID3v2(majorVersion: 4, frames: frames)
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
private func makeID3v2(majorVersion: UInt8, frames: [Frame]) -> Data {
|
|
392
|
-
var frameBlock = Data()
|
|
393
|
-
for frame in frames {
|
|
394
|
-
switch frame {
|
|
395
|
-
case .txxx(let desc, let value):
|
|
396
|
-
// TXXX payload: encoding (0x03 UTF-8) + description + 0x00 + value
|
|
397
|
-
var payload = Data([0x03])
|
|
398
|
-
payload.append(desc.data(using: .utf8) ?? Data())
|
|
399
|
-
payload.append(0x00)
|
|
400
|
-
payload.append(value.data(using: .utf8) ?? Data())
|
|
401
|
-
// Frame header: 4-byte ID, 4-byte size, 2-byte flags.
|
|
402
|
-
frameBlock.append("TXXX".data(using: .ascii)!)
|
|
403
|
-
if majorVersion >= 4 {
|
|
404
|
-
frameBlock.append(syncsafeUInt32(UInt32(payload.count)))
|
|
405
|
-
} else {
|
|
406
|
-
frameBlock.append(beUInt32(UInt32(payload.count)))
|
|
407
|
-
}
|
|
408
|
-
frameBlock.append(Data([0x00, 0x00]))
|
|
409
|
-
frameBlock.append(payload)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
var tag = Data()
|
|
413
|
-
tag.append("ID3".data(using: .ascii)!)
|
|
414
|
-
tag.append(majorVersion)
|
|
415
|
-
tag.append(0x00) // minor version
|
|
416
|
-
tag.append(0x00) // flags
|
|
417
|
-
tag.append(syncsafeUInt32(UInt32(frameBlock.count)))
|
|
418
|
-
tag.append(frameBlock)
|
|
419
|
-
return tag
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/// 28-bit syncsafe encode (used by ID3v2 tag size + v2.4 frame size).
|
|
423
|
-
private func syncsafeUInt32(_ v: UInt32) -> Data {
|
|
424
|
-
return Data([
|
|
425
|
-
UInt8((v >> 21) & 0x7f),
|
|
426
|
-
UInt8((v >> 14) & 0x7f),
|
|
427
|
-
UInt8((v >> 7) & 0x7f),
|
|
428
|
-
UInt8(v & 0x7f),
|
|
429
|
-
])
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
private func beUInt32(_ v: UInt32) -> Data {
|
|
433
|
-
return Data([
|
|
434
|
-
UInt8((v >> 24) & 0xff),
|
|
435
|
-
UInt8((v >> 16) & 0xff),
|
|
436
|
-
UInt8((v >> 8) & 0xff),
|
|
437
|
-
UInt8(v & 0xff),
|
|
438
|
-
])
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
private func leUInt32(_ v: UInt32) -> Data {
|
|
442
|
-
return Data([
|
|
443
|
-
UInt8(v & 0xff),
|
|
444
|
-
UInt8((v >> 8) & 0xff),
|
|
445
|
-
UInt8((v >> 16) & 0xff),
|
|
446
|
-
UInt8((v >> 24) & 0xff),
|
|
447
|
-
])
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/// Build a minimal FLAC stream — `fLaC` magic + optional STREAMINFO
|
|
451
|
-
/// + VORBIS_COMMENT block. The VORBIS_COMMENT is marked last when
|
|
452
|
-
/// it's the only block (or the last block if STREAMINFO precedes).
|
|
453
|
-
private func makeFLAC(
|
|
454
|
-
includeStreamInfo: Bool = false,
|
|
455
|
-
comments: [String]
|
|
456
|
-
) -> Data {
|
|
457
|
-
var data = Data()
|
|
458
|
-
data.append("fLaC".data(using: .ascii)!)
|
|
459
|
-
if includeStreamInfo {
|
|
460
|
-
// STREAMINFO is type 0, 34-byte body. Not last (VORBIS_COMMENT
|
|
461
|
-
// follows). Body content doesn't matter for the parser test.
|
|
462
|
-
data.append(0x00)
|
|
463
|
-
data.append(beUInt24(34))
|
|
464
|
-
data.append(Data(repeating: 0, count: 34))
|
|
465
|
-
}
|
|
466
|
-
// Build VORBIS_COMMENT block body.
|
|
467
|
-
var body = Data()
|
|
468
|
-
let vendor = "test-vendor".data(using: .utf8)!
|
|
469
|
-
body.append(leUInt32(UInt32(vendor.count)))
|
|
470
|
-
body.append(vendor)
|
|
471
|
-
body.append(leUInt32(UInt32(comments.count)))
|
|
472
|
-
for c in comments {
|
|
473
|
-
let bytes = c.data(using: .utf8)!
|
|
474
|
-
body.append(leUInt32(UInt32(bytes.count)))
|
|
475
|
-
body.append(bytes)
|
|
476
|
-
}
|
|
477
|
-
// Header: last-block flag (0x80) + type (4)
|
|
478
|
-
data.append(0x84)
|
|
479
|
-
data.append(beUInt24(UInt32(body.count)))
|
|
480
|
-
data.append(body)
|
|
481
|
-
return data
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/// Build a FLAC header with only a STREAMINFO block (last-block
|
|
485
|
-
/// flag set), populated with the given values. Layout per FLAC
|
|
486
|
-
/// spec — 16+16+24+24 bits of block sizes are zeroed; the bits
|
|
487
|
-
/// the parser actually reads (sampleRate, channels, bitsPerSample,
|
|
488
|
-
/// totalSamples) are packed in.
|
|
489
|
-
private func makeFLACStreamInfo(
|
|
490
|
-
sampleRate: UInt32,
|
|
491
|
-
channels: Int,
|
|
492
|
-
bitsPerSample: Int,
|
|
493
|
-
totalSamples: Int64
|
|
494
|
-
) -> Data {
|
|
495
|
-
var data = Data()
|
|
496
|
-
data.append("fLaC".data(using: .ascii)!)
|
|
497
|
-
// Block header: last-block flag (0x80) + type 0 (STREAMINFO) +
|
|
498
|
-
// 24-bit size = 34 bytes.
|
|
499
|
-
data.append(0x80)
|
|
500
|
-
data.append(beUInt24(34))
|
|
501
|
-
// Body. 34 bytes total.
|
|
502
|
-
var body = Data(repeating: 0, count: 34)
|
|
503
|
-
// minBlockSize, maxBlockSize, minFrameSize, maxFrameSize: bytes
|
|
504
|
-
// 0..9, leave zero. Bytes 10..12 contain the 20-bit sampleRate
|
|
505
|
-
// followed by 3-bit channels-1 and 5-bit bitsPerSample-1 split
|
|
506
|
-
// across bytes 12..13. totalSamples is 36 bits at bytes 13..17.
|
|
507
|
-
let sr = UInt32(sampleRate) & 0x000FFFFF
|
|
508
|
-
let chMinusOne = UInt8((channels - 1) & 0x07)
|
|
509
|
-
let bpsMinusOne = UInt8((bitsPerSample - 1) & 0x1F)
|
|
510
|
-
body[10] = UInt8((sr >> 12) & 0xFF)
|
|
511
|
-
body[11] = UInt8((sr >> 4) & 0xFF)
|
|
512
|
-
body[12] = UInt8(((sr & 0x0F) << 4) | (UInt32(chMinusOne) << 1) | UInt32(bpsMinusOne >> 4))
|
|
513
|
-
body[13] = UInt8(((bpsMinusOne & 0x0F) << 4) | UInt8((totalSamples >> 32) & 0x0F))
|
|
514
|
-
body[14] = UInt8((totalSamples >> 24) & 0xFF)
|
|
515
|
-
body[15] = UInt8((totalSamples >> 16) & 0xFF)
|
|
516
|
-
body[16] = UInt8((totalSamples >> 8) & 0xFF)
|
|
517
|
-
body[17] = UInt8(totalSamples & 0xFF)
|
|
518
|
-
// Bytes 18..33: MD5, leave zero.
|
|
519
|
-
data.append(body)
|
|
520
|
-
return data
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private func beUInt24(_ v: UInt32) -> Data {
|
|
524
|
-
return Data([
|
|
525
|
-
UInt8((v >> 16) & 0xff),
|
|
526
|
-
UInt8((v >> 8) & 0xff),
|
|
527
|
-
UInt8(v & 0xff),
|
|
528
|
-
])
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/// Wrap a body in an MP4 atom (`size` BE u32 + 4-byte type + body).
|
|
532
|
-
private func makeAtom(type: String, body: Data) -> Data {
|
|
533
|
-
var data = Data()
|
|
534
|
-
let total = UInt32(8 + body.count)
|
|
535
|
-
data.append(beUInt32(total))
|
|
536
|
-
data.append(type.data(using: .ascii)!)
|
|
537
|
-
data.append(body)
|
|
538
|
-
return data
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/// Build a minimal MP4 with `moov.udta.meta.ilst` containing
|
|
542
|
-
/// freeform `----` atoms for each (key, value) pair.
|
|
543
|
-
private func makeMP4(freeformTags: [String: String]) -> Data {
|
|
544
|
-
// Build ilst body — sequence of `----` atoms.
|
|
545
|
-
var ilstBody = Data()
|
|
546
|
-
for (key, value) in freeformTags {
|
|
547
|
-
var freeformBody = Data()
|
|
548
|
-
// mean atom: 4-byte version+flags + UTF-8 issuer.
|
|
549
|
-
var meanInner = Data([0x00, 0x00, 0x00, 0x00])
|
|
550
|
-
meanInner.append("com.apple.iTunes".data(using: .utf8)!)
|
|
551
|
-
freeformBody.append(makeAtom(type: "mean", body: meanInner))
|
|
552
|
-
// name atom: 4-byte version+flags + UTF-8 key.
|
|
553
|
-
var nameInner = Data([0x00, 0x00, 0x00, 0x00])
|
|
554
|
-
nameInner.append(key.data(using: .utf8)!)
|
|
555
|
-
freeformBody.append(makeAtom(type: "name", body: nameInner))
|
|
556
|
-
// data atom: 4-byte type indicator (1=UTF-8) + 4-byte locale + value.
|
|
557
|
-
var dataInner = Data([0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00])
|
|
558
|
-
dataInner.append(value.data(using: .utf8)!)
|
|
559
|
-
freeformBody.append(makeAtom(type: "data", body: dataInner))
|
|
560
|
-
ilstBody.append(makeAtom(type: "----", body: freeformBody))
|
|
561
|
-
}
|
|
562
|
-
let ilst = makeAtom(type: "ilst", body: ilstBody)
|
|
563
|
-
// meta atom: 4-byte version+flags prefix + ilst.
|
|
564
|
-
var metaBody = Data([0x00, 0x00, 0x00, 0x00])
|
|
565
|
-
metaBody.append(ilst)
|
|
566
|
-
let meta = makeAtom(type: "meta", body: metaBody)
|
|
567
|
-
let udta = makeAtom(type: "udta", body: meta)
|
|
568
|
-
let moov = makeAtom(type: "moov", body: udta)
|
|
569
|
-
// ftyp box at the start.
|
|
570
|
-
let ftyp = makeAtom(type: "ftyp", body: Data([
|
|
571
|
-
0x4d, 0x34, 0x41, 0x20, // M4A_
|
|
572
|
-
0x00, 0x00, 0x00, 0x00, // minor version
|
|
573
|
-
0x4d, 0x34, 0x41, 0x20, // compatible brand
|
|
574
|
-
]))
|
|
575
|
-
var data = Data()
|
|
576
|
-
data.append(ftyp)
|
|
577
|
-
data.append(moov)
|
|
578
|
-
return data
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// MARK: - Remote fetch uses the injected session (no real network)
|
|
582
|
-
|
|
583
|
-
func testFetchPrefixBytesUsesInjectedSessionForRemoteUrl() async {
|
|
584
|
-
let config = URLSessionConfiguration.ephemeral
|
|
585
|
-
config.protocolClasses = [StubURLProtocol.self]
|
|
586
|
-
let session = URLSession(configuration: config)
|
|
587
|
-
StubURLProtocol.reset()
|
|
588
|
-
defer { StubURLProtocol.reset() }
|
|
589
|
-
// `.test` is a reserved non-resolvable TLD — if the read ever hit the
|
|
590
|
-
// real network instead of the injected session it would fail with
|
|
591
|
-
// -1003 and never reach the stub.
|
|
592
|
-
let url = URL(string: "https://example.test/meta.mp3")!
|
|
593
|
-
let blob = Data("ID3-mock-prefix-bytes".utf8)
|
|
594
|
-
StubURLProtocol.stub(url: url, data: blob, statusCode: 200)
|
|
595
|
-
|
|
596
|
-
let bytes = await MetadataReader.fetchPrefixBytes(url: url, urlSession: session)
|
|
597
|
-
|
|
598
|
-
XCTAssertEqual(bytes, blob, "remote prefix read must resolve via the injected session")
|
|
599
|
-
XCTAssertEqual(
|
|
600
|
-
StubURLProtocol.requestCount(for: url), 1,
|
|
601
|
-
"must fetch through the injected session, never URLSession.shared")
|
|
602
|
-
}
|
|
603
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import AVFoundation
|
|
2
|
-
import XCTest
|
|
3
|
-
@testable import QueuePlayer
|
|
4
|
-
|
|
5
|
-
/// Type-check coverage for `PlaybackEngine` + `PlaybackEngineDelegate`
|
|
6
|
-
/// + `AudioMixProvider`. The protocols have no runtime; this suite
|
|
7
|
-
/// exercises the type surface so a regression that drops a method
|
|
8
|
-
/// or rotates a parameter shape surfaces immediately. Goes away once
|
|
9
|
-
/// the real `GaplessEngine` impl pins the type surface from a
|
|
10
|
-
/// runtime perspective.
|
|
11
|
-
final class PlaybackEngineProtocolTests: XCTestCase {
|
|
12
|
-
|
|
13
|
-
func testProtocolSurfaceCompiles() {
|
|
14
|
-
let stub = NoopPlaybackEngine()
|
|
15
|
-
XCTAssertEqual(stub.currentMediaItemIndex, -1)
|
|
16
|
-
XCTAssertEqual(stub.currentPositionSeconds, 0)
|
|
17
|
-
XCTAssertEqual(stub.currentDurationSeconds, 0)
|
|
18
|
-
XCTAssertEqual(stub.bufferedPositionSeconds, 0)
|
|
19
|
-
XCTAssertFalse(stub.isPlaying)
|
|
20
|
-
XCTAssertNil(stub.currentMediaItem)
|
|
21
|
-
XCTAssertEqual(stub.allMediaItems.count, 0)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func testDelegateSurfaceCompiles() {
|
|
25
|
-
let delegate = NoopDelegate()
|
|
26
|
-
let stub = NoopPlaybackEngine()
|
|
27
|
-
stub.delegate = delegate
|
|
28
|
-
XCTAssertTrue(stub.delegate === delegate)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
func testAudioMixProviderRegistration() {
|
|
32
|
-
let stub = NoopPlaybackEngine()
|
|
33
|
-
let provider = NoopMixProvider()
|
|
34
|
-
stub.setAudioMixProvider(provider)
|
|
35
|
-
stub.refreshAllItemMixes()
|
|
36
|
-
stub.setAudioMixProvider(nil)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private final class NoopPlaybackEngine: PlaybackEngine {
|
|
41
|
-
func prepare(items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) async {}
|
|
42
|
-
func release() {}
|
|
43
|
-
func setItems(_ items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) {}
|
|
44
|
-
@discardableResult
|
|
45
|
-
func insertItems(_ items: [AVPlayerItem], after: AVPlayerItem?) -> Bool { true }
|
|
46
|
-
func remove(_ item: AVPlayerItem) {}
|
|
47
|
-
func removeAllItems() {}
|
|
48
|
-
func canInsert(_ item: AVPlayerItem, after: AVPlayerItem?) -> Bool { true }
|
|
49
|
-
func play() {}
|
|
50
|
-
func pause() {}
|
|
51
|
-
func stop() {}
|
|
52
|
-
func seek(toIndex index: Int, position: TimeInterval) {}
|
|
53
|
-
func skipToNext() {}
|
|
54
|
-
func skipToPrevious() {}
|
|
55
|
-
func setVolume(_ volume: Float) {}
|
|
56
|
-
func setPlaybackSpeed(_ speed: Float) {}
|
|
57
|
-
func seedPlaybackSpeed(_ speed: Float) {}
|
|
58
|
-
func setRepeatMode(_ mode: RepeatMode) {}
|
|
59
|
-
func setPitchCorrectionMode(_ mode: PitchCorrectionMode) {}
|
|
60
|
-
|
|
61
|
-
var currentMediaItemIndex: Int { -1 }
|
|
62
|
-
var currentPositionSeconds: TimeInterval { 0 }
|
|
63
|
-
var currentDurationSeconds: TimeInterval { 0 }
|
|
64
|
-
var bufferedPositionSeconds: TimeInterval { 0 }
|
|
65
|
-
var isPlaying: Bool { false }
|
|
66
|
-
var timeControlStatus: AVPlayer.TimeControlStatus { .paused }
|
|
67
|
-
var currentMediaItem: AVPlayerItem? { nil }
|
|
68
|
-
var allMediaItems: [AVPlayerItem] { [] }
|
|
69
|
-
|
|
70
|
-
func setAudioMixProvider(_ provider: AudioMixProvider?) {}
|
|
71
|
-
func refreshAllItemMixes() {}
|
|
72
|
-
|
|
73
|
-
func handoff(to nextEngine: PlaybackEngine, atTrackBoundary: Bool) async {}
|
|
74
|
-
|
|
75
|
-
var delegate: PlaybackEngineDelegate?
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private final class NoopDelegate: PlaybackEngineDelegate {
|
|
79
|
-
func engineStateMaybeChanged(_ engine: PlaybackEngine, reason: StateChangeReason?) {}
|
|
80
|
-
func engineIsPlayingChanged(_ engine: PlaybackEngine, isPlaying: Bool) {}
|
|
81
|
-
func engineDidTransitionTrack(_ engine: PlaybackEngine) {}
|
|
82
|
-
func engineActiveItemFormatChanged(_ engine: PlaybackEngine) {}
|
|
83
|
-
func engineDidFailWithError(_ engine: PlaybackEngine, error: Error) {}
|
|
84
|
-
func engineDidPlayItemToEnd(_ engine: PlaybackEngine) {}
|
|
85
|
-
func enginePlaybackEnded(_ engine: PlaybackEngine) {}
|
|
86
|
-
func engineCrossfadeDidBegin(_ engine: PlaybackEngine, incomingIndex: Int) {}
|
|
87
|
-
func engineCrossfadeDidCancel(_ engine: PlaybackEngine, revertedIndex: Int) {}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private final class NoopMixProvider: AudioMixProvider {
|
|
91
|
-
func audioMix(for item: AVPlayerItem) -> AVAudioMix? { nil }
|
|
92
|
-
}
|