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
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import QueuePlayer
|
|
4
|
+
|
|
5
|
+
/// A queue mutation that touches neither leg waits for a crossfade to settle
|
|
6
|
+
/// rather than cutting it short; one that touches a leg cannot wait.
|
|
7
|
+
final class MutationDeferralTests: XCTestCase {
|
|
8
|
+
|
|
9
|
+
private var player: TrackPlayer!
|
|
10
|
+
|
|
11
|
+
override func setUp() async throws {
|
|
12
|
+
try await super.setUp()
|
|
13
|
+
player = TrackPlayer()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override func tearDown() async throws {
|
|
17
|
+
player = nil
|
|
18
|
+
try await super.tearDown()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func testAMutationClearOfBothLegsIsHeldBack() async throws {
|
|
22
|
+
try await seed(fading: true, attaching: [0, 1])
|
|
23
|
+
|
|
24
|
+
let deferred = try await onQueue { $0.deferEngineWork(touching: [7]) }
|
|
25
|
+
|
|
26
|
+
XCTAssertTrue(deferred)
|
|
27
|
+
let held = try await read { $0.engineReconcileDeferred }
|
|
28
|
+
XCTAssertTrue(held)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// The prerolled standby leg counts: removing it out from under the arm
|
|
32
|
+
/// chain leaves the fade running into a track the queue no longer has.
|
|
33
|
+
func testAMutationTouchingTheStandbyLegIsNotHeldBack() async throws {
|
|
34
|
+
_ = try await seed(fading: true, attaching: [0, 1])
|
|
35
|
+
|
|
36
|
+
let deferred = try await onQueue { $0.deferEngineWork(touching: [1]) }
|
|
37
|
+
|
|
38
|
+
XCTAssertFalse(deferred)
|
|
39
|
+
let held = try await read { $0.engineReconcileDeferred }
|
|
40
|
+
XCTAssertFalse(held)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testAMutationTouchingTheLeadingLegIsNotHeldBack() async throws {
|
|
44
|
+
_ = try await seed(fading: true, attaching: [0, 1])
|
|
45
|
+
|
|
46
|
+
let deferred = try await onQueue { $0.deferEngineWork(touching: [0]) }
|
|
47
|
+
|
|
48
|
+
XCTAssertFalse(deferred)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// With no fade to protect there is nothing to wait for.
|
|
52
|
+
func testNothingIsHeldBackWhenNoFadeIsPending() async throws {
|
|
53
|
+
_ = try await seed(fading: false, attaching: [0])
|
|
54
|
+
|
|
55
|
+
let deferred = try await onQueue { $0.deferEngineWork(touching: [7]) }
|
|
56
|
+
|
|
57
|
+
XCTAssertFalse(deferred)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// One flag, however many mutations: the reconcile re-derives what the
|
|
61
|
+
/// engine should hold from the model, so the order they arrived in and how
|
|
62
|
+
/// many there were do not matter.
|
|
63
|
+
func testSeveralHeldBackMutationsFlushAsOne() async throws {
|
|
64
|
+
let engine = try await seed(fading: true, attaching: [0, 1])
|
|
65
|
+
_ = try await onQueue { $0.deferEngineWork(touching: [6]) }
|
|
66
|
+
_ = try await onQueue { $0.deferEngineWork(touching: [7]) }
|
|
67
|
+
_ = try await onQueue { $0.deferEngineWork(touching: [8]) }
|
|
68
|
+
|
|
69
|
+
XCTAssertEqual(
|
|
70
|
+
engine.setItemsCount, 0, "nothing reaches the engine while the fade runs")
|
|
71
|
+
|
|
72
|
+
engine.fadePending = false
|
|
73
|
+
await onQueue { $0.flushDeferredEngineWork() }
|
|
74
|
+
|
|
75
|
+
let stillHeld = try await read { $0.engineReconcileDeferred }
|
|
76
|
+
XCTAssertFalse(stillHeld)
|
|
77
|
+
XCTAssertEqual(
|
|
78
|
+
engine.setItemsCount, 1, "the engine is reconciled once, not once per mutation")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func testFlushingWithNothingHeldBackLeavesTheEngineAlone() async throws {
|
|
82
|
+
let engine = try await seed(fading: false, attaching: [0])
|
|
83
|
+
|
|
84
|
+
await onQueue { $0.flushDeferredEngineWork() }
|
|
85
|
+
|
|
86
|
+
XCTAssertEqual(engine.setItemsCount, 0)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// The new engine is installed from the queue model, which already carries
|
|
90
|
+
/// every mutation, so nothing is owed to it.
|
|
91
|
+
func testAnEngineSwapDropsWhatWasHeldBack() async throws {
|
|
92
|
+
_ = try await seed(fading: true, attaching: [0, 1])
|
|
93
|
+
_ = try await onQueue { $0.deferEngineWork(touching: [7]) }
|
|
94
|
+
|
|
95
|
+
await onQueue {
|
|
96
|
+
$0.applyEngineSwapForMode(PlaybackMode(kind: .gapless, crossfadeDurationMs: nil))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let held = try await read { $0.engineReconcileDeferred }
|
|
100
|
+
XCTAssertFalse(held)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// MARK: - The model stays authoritative
|
|
104
|
+
|
|
105
|
+
/// Holding the engine back does not hold the queue back: the model is
|
|
106
|
+
/// updated and the consumer is told at once either way.
|
|
107
|
+
func testADeferredRemoveStillChangesTheQueueAndReportsIt() async throws {
|
|
108
|
+
let engine = try await seed(fading: true, attaching: [0, 1])
|
|
109
|
+
var removeReports = 0
|
|
110
|
+
let unsubscribe = try player.onQueueChange { _, _, reason in
|
|
111
|
+
if reason == .remove { removeReports += 1 }
|
|
112
|
+
}
|
|
113
|
+
defer { unsubscribe() }
|
|
114
|
+
|
|
115
|
+
_ = try await player.removeFromQueue(indices: [7]).await()
|
|
116
|
+
|
|
117
|
+
let state = try await read { ($0.tracks.count, $0.queueItemIds.count) }
|
|
118
|
+
XCTAssertEqual(state.0, 11, "the queue model drops the track at once")
|
|
119
|
+
XCTAssertEqual(state.1, 11, "ids stay parallel to it")
|
|
120
|
+
XCTAssertEqual(removeReports, 1, "the consumer is told once")
|
|
121
|
+
XCTAssertTrue(engine.removed.isEmpty, "the engine is left alone until the fade settles")
|
|
122
|
+
let held = try await read { $0.engineReconcileDeferred }
|
|
123
|
+
XCTAssertTrue(held)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// The same for a move, which reorders the model without touching a leg.
|
|
127
|
+
func testADeferredMoveStillReordersTheQueueAndReportsIt() async throws {
|
|
128
|
+
_ = try await seed(fading: true, attaching: [0, 1])
|
|
129
|
+
var moveReports = 0
|
|
130
|
+
let unsubscribe = try player.onQueueChange { _, _, reason in
|
|
131
|
+
if reason == .move { moveReports += 1 }
|
|
132
|
+
}
|
|
133
|
+
defer { unsubscribe() }
|
|
134
|
+
let idsBefore = try await read { $0.queueItemIds }
|
|
135
|
+
|
|
136
|
+
_ = try await player.moveInQueue(fromIndex: 8, toIndex: 6).await()
|
|
137
|
+
|
|
138
|
+
let idsAfter = try await read { $0.queueItemIds }
|
|
139
|
+
XCTAssertEqual(idsAfter[6], idsBefore[8], "the model is reordered at once")
|
|
140
|
+
XCTAssertEqual(moveReports, 1, "a deferred move is still reported")
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// MARK: - Helpers
|
|
144
|
+
|
|
145
|
+
@discardableResult
|
|
146
|
+
private func seed(fading: Bool, attaching: [Int]) async throws -> DeferralEngineStub {
|
|
147
|
+
let player = try XCTUnwrap(self.player)
|
|
148
|
+
let engine = DeferralEngineStub()
|
|
149
|
+
await player.onPlayerQueue {
|
|
150
|
+
let ids = player.queueState.replaceAll(
|
|
151
|
+
(0 ..< 12).map {
|
|
152
|
+
PlayerFixtures.track(id: "t\($0)", url: "http://127.0.0.1:1/\($0).m4a")
|
|
153
|
+
})
|
|
154
|
+
engine.fadePending = fading
|
|
155
|
+
engine.attached = attaching.map { ids[$0] }
|
|
156
|
+
player.currentTrackIndex = 0
|
|
157
|
+
player.engine = engine
|
|
158
|
+
}
|
|
159
|
+
return engine
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func onQueue<T>(_ body: @escaping (TrackPlayer) -> T) async throws -> T {
|
|
163
|
+
let player = try XCTUnwrap(self.player)
|
|
164
|
+
return await player.onPlayerQueueValue { body(player) }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private func onQueue(_ body: @escaping (TrackPlayer) -> Void) async {
|
|
168
|
+
guard let player else { return }
|
|
169
|
+
await player.onPlayerQueue { body(player) }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private func read<T>(_ body: @escaping (TrackPlayer) -> T) async throws -> T {
|
|
173
|
+
try await onQueue(body)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Reports a fade and a chosen set of attached identities, and records what
|
|
178
|
+
/// the queue asks of it.
|
|
179
|
+
private final class DeferralEngineStub: PlaybackEngine {
|
|
180
|
+
var fadePending = false
|
|
181
|
+
var attached: [String] = []
|
|
182
|
+
private(set) var removed: [AVPlayerItem] = []
|
|
183
|
+
private(set) var setItemsCount = 0
|
|
184
|
+
|
|
185
|
+
var isFadePendingOrActive: Bool { fadePending }
|
|
186
|
+
var attachedQueueItemIds: [String] { attached }
|
|
187
|
+
|
|
188
|
+
func setItems(_ items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) {
|
|
189
|
+
setItemsCount += 1
|
|
190
|
+
}
|
|
191
|
+
func remove(_ item: AVPlayerItem) { removed.append(item) }
|
|
192
|
+
@discardableResult
|
|
193
|
+
func insertItems(_ items: [AVPlayerItem], after: AVPlayerItem?) -> Bool { true }
|
|
194
|
+
func setAudioMixProvider(_ provider: AudioMixProvider?) {}
|
|
195
|
+
func refreshAllItemMixes() {}
|
|
196
|
+
func release() {}
|
|
197
|
+
func removeAllItems() {}
|
|
198
|
+
func canInsert(_ item: AVPlayerItem, after: AVPlayerItem?) -> Bool { true }
|
|
199
|
+
func play() {}
|
|
200
|
+
func pause() {}
|
|
201
|
+
func stop() {}
|
|
202
|
+
func seek(toIndex index: Int, position: TimeInterval) {}
|
|
203
|
+
func skipToNext() {}
|
|
204
|
+
func skipToPrevious() {}
|
|
205
|
+
func setVolume(_ volume: Float) {}
|
|
206
|
+
func setPlaybackSpeed(_ speed: Float) {}
|
|
207
|
+
func seedPlaybackSpeed(_ speed: Float) {}
|
|
208
|
+
func setRepeatMode(_ mode: RepeatMode) {}
|
|
209
|
+
func setPitchCorrectionMode(_ mode: PitchCorrectionMode) {}
|
|
210
|
+
|
|
211
|
+
var currentMediaItemIndex: Int { 0 }
|
|
212
|
+
var currentPositionSeconds: TimeInterval { 0 }
|
|
213
|
+
var currentDurationSeconds: TimeInterval { 0 }
|
|
214
|
+
var bufferedPositionSeconds: TimeInterval { 0 }
|
|
215
|
+
var isPlaying: Bool { false }
|
|
216
|
+
var timeControlStatus: AVPlayer.TimeControlStatus { .paused }
|
|
217
|
+
var currentMediaItem: AVPlayerItem? { nil }
|
|
218
|
+
var allMediaItems: [AVPlayerItem] { [] }
|
|
219
|
+
var delegate: PlaybackEngineDelegate?
|
|
220
|
+
}
|
|
@@ -22,6 +22,9 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
22
22
|
|
|
23
23
|
override func tearDown() {
|
|
24
24
|
nowPlaying.clear()
|
|
25
|
+
// Drain before dropping the subject: writes are enqueued, so one left in
|
|
26
|
+
// flight would land during the next test and clobber its seeded state.
|
|
27
|
+
_ = nowPlayingInfoAfterWritesLand()
|
|
25
28
|
nowPlaying = nil
|
|
26
29
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
27
30
|
super.tearDown()
|
|
@@ -29,31 +32,34 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
29
32
|
|
|
30
33
|
// MARK: - refreshAll
|
|
31
34
|
|
|
32
|
-
func
|
|
33
|
-
|
|
34
|
-
nowPlaying.
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
func testRefreshAllPublishesTheSnapshotSuppliedToEachCall() {
|
|
36
|
+
let track = makeTrack(title: "T")
|
|
37
|
+
nowPlaying.refreshAll(
|
|
38
|
+
NowPlayingInfo.Snapshot(
|
|
39
|
+
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1))
|
|
40
|
+
nowPlaying.refreshAll(
|
|
41
|
+
NowPlayingInfo.Snapshot(
|
|
42
|
+
track: track, elapsedSeconds: 40, durationSeconds: 90, rate: 0))
|
|
43
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
44
|
+
XCTAssertEqual(info?[MPNowPlayingInfoPropertyElapsedPlaybackTime] as? Double, 40)
|
|
45
|
+
XCTAssertEqual(info?[MPNowPlayingInfoPropertyPlaybackRate] as? Double, 0)
|
|
46
|
+
XCTAssertEqual(info?[MPMediaItemPropertyPlaybackDuration] as? Double, 90)
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
func testRefreshAllClearsDictWhenSnapshotHasNoTrack() {
|
|
40
50
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = ["pre": "existing"]
|
|
41
|
-
nowPlaying.
|
|
51
|
+
nowPlaying.refreshAll(
|
|
42
52
|
NowPlayingInfo.Snapshot(
|
|
43
|
-
track: nil, elapsedSeconds: 0, durationSeconds: 0, rate: 0)
|
|
44
|
-
|
|
45
|
-
nowPlaying.refreshAll()
|
|
46
|
-
XCTAssertNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
53
|
+
track: nil, elapsedSeconds: 0, durationSeconds: 0, rate: 0))
|
|
54
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
func testRefreshAllPopulatesTitleArtistAlbum() {
|
|
50
58
|
let track = makeTrack(title: "Song", artist: "Artist", album: "Album")
|
|
51
|
-
nowPlaying.
|
|
59
|
+
nowPlaying.refreshAll(
|
|
52
60
|
NowPlayingInfo.Snapshot(
|
|
53
|
-
track: track, elapsedSeconds: 12, durationSeconds: 180, rate: 1)
|
|
54
|
-
|
|
55
|
-
nowPlaying.refreshAll()
|
|
56
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
61
|
+
track: track, elapsedSeconds: 12, durationSeconds: 180, rate: 1))
|
|
62
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
57
63
|
XCTAssertEqual(info?[MPMediaItemPropertyTitle] as? String, "Song")
|
|
58
64
|
XCTAssertEqual(info?[MPMediaItemPropertyArtist] as? String, "Artist")
|
|
59
65
|
XCTAssertEqual(info?[MPMediaItemPropertyAlbumTitle] as? String, "Album")
|
|
@@ -61,12 +67,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
61
67
|
|
|
62
68
|
func testRefreshAllEmptyStringCoercesNilTitleArtistAlbum() {
|
|
63
69
|
let track = makeTrack(title: nil, artist: nil, album: nil)
|
|
64
|
-
nowPlaying.
|
|
70
|
+
nowPlaying.refreshAll(
|
|
65
71
|
NowPlayingInfo.Snapshot(
|
|
66
|
-
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0)
|
|
67
|
-
|
|
68
|
-
nowPlaying.refreshAll()
|
|
69
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
72
|
+
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0))
|
|
73
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
70
74
|
XCTAssertEqual(info?[MPMediaItemPropertyTitle] as? String, "")
|
|
71
75
|
XCTAssertEqual(info?[MPMediaItemPropertyArtist] as? String, "")
|
|
72
76
|
XCTAssertEqual(info?[MPMediaItemPropertyAlbumTitle] as? String, "")
|
|
@@ -77,12 +81,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
77
81
|
let omitted: [Double] = [0, -1, .nan, .infinity, -.infinity]
|
|
78
82
|
for d in omitted {
|
|
79
83
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
80
|
-
nowPlaying.
|
|
84
|
+
nowPlaying.refreshAll(
|
|
81
85
|
NowPlayingInfo.Snapshot(
|
|
82
|
-
track: track, elapsedSeconds: 0, durationSeconds: d, rate: 0)
|
|
83
|
-
|
|
84
|
-
nowPlaying.refreshAll()
|
|
85
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
86
|
+
track: track, elapsedSeconds: 0, durationSeconds: d, rate: 0))
|
|
87
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
86
88
|
XCTAssertNil(info?[MPMediaItemPropertyPlaybackDuration],
|
|
87
89
|
"duration must be omitted for value \(d)")
|
|
88
90
|
}
|
|
@@ -90,23 +92,19 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
90
92
|
|
|
91
93
|
func testRefreshAllSetsDurationWhenFinitePositive() {
|
|
92
94
|
let track = makeTrack(title: "T")
|
|
93
|
-
nowPlaying.
|
|
95
|
+
nowPlaying.refreshAll(
|
|
94
96
|
NowPlayingInfo.Snapshot(
|
|
95
|
-
track: track, elapsedSeconds: 0, durationSeconds: 240, rate: 1)
|
|
96
|
-
|
|
97
|
-
nowPlaying.refreshAll()
|
|
98
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
97
|
+
track: track, elapsedSeconds: 0, durationSeconds: 240, rate: 1))
|
|
98
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
99
99
|
XCTAssertEqual(info?[MPMediaItemPropertyPlaybackDuration] as? Double, 240)
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
func testRefreshAllSetsAudioMediaType() {
|
|
103
103
|
let track = makeTrack()
|
|
104
|
-
nowPlaying.
|
|
104
|
+
nowPlaying.refreshAll(
|
|
105
105
|
NowPlayingInfo.Snapshot(
|
|
106
|
-
track: track, elapsedSeconds: 0, durationSeconds: 0, rate: 0)
|
|
107
|
-
|
|
108
|
-
nowPlaying.refreshAll()
|
|
109
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
106
|
+
track: track, elapsedSeconds: 0, durationSeconds: 0, rate: 0))
|
|
107
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
110
108
|
XCTAssertEqual(
|
|
111
109
|
info?[MPNowPlayingInfoPropertyMediaType] as? UInt,
|
|
112
110
|
MPNowPlayingInfoMediaType.audio.rawValue
|
|
@@ -115,12 +113,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
115
113
|
|
|
116
114
|
func testRefreshAllPlacesArtworkPlaceholder() {
|
|
117
115
|
let track = makeTrack(title: "T")
|
|
118
|
-
nowPlaying.
|
|
116
|
+
nowPlaying.refreshAll(
|
|
119
117
|
NowPlayingInfo.Snapshot(
|
|
120
|
-
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0)
|
|
121
|
-
|
|
122
|
-
nowPlaying.refreshAll()
|
|
123
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
118
|
+
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0))
|
|
119
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
124
120
|
XCTAssertNotNil(info?[MPMediaItemPropertyArtwork])
|
|
125
121
|
XCTAssertTrue(info?[MPMediaItemPropertyArtwork] is MPMediaItemArtwork)
|
|
126
122
|
}
|
|
@@ -129,12 +125,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
129
125
|
let track = makeTrack(title: "T")
|
|
130
126
|
let bad: [Double] = [.nan, .infinity, -.infinity, -5]
|
|
131
127
|
for e in bad {
|
|
132
|
-
nowPlaying.
|
|
128
|
+
nowPlaying.refreshAll(
|
|
133
129
|
NowPlayingInfo.Snapshot(
|
|
134
|
-
track: track, elapsedSeconds: e, durationSeconds: 60, rate: 1)
|
|
135
|
-
|
|
136
|
-
nowPlaying.refreshAll()
|
|
137
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
130
|
+
track: track, elapsedSeconds: e, durationSeconds: 60, rate: 1))
|
|
131
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
138
132
|
XCTAssertEqual(
|
|
139
133
|
info?[MPNowPlayingInfoPropertyElapsedPlaybackTime] as? Double, 0,
|
|
140
134
|
"elapsed must be 0 for value \(e)")
|
|
@@ -145,12 +139,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
145
139
|
let track = makeTrack(title: "T")
|
|
146
140
|
let bad: [Float] = [.nan, .infinity, -.infinity]
|
|
147
141
|
for r in bad {
|
|
148
|
-
nowPlaying.
|
|
142
|
+
nowPlaying.refreshAll(
|
|
149
143
|
NowPlayingInfo.Snapshot(
|
|
150
|
-
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: r)
|
|
151
|
-
|
|
152
|
-
nowPlaying.refreshAll()
|
|
153
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
144
|
+
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: r))
|
|
145
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
154
146
|
XCTAssertEqual(
|
|
155
147
|
info?[MPNowPlayingInfoPropertyPlaybackRate] as? Double, 0,
|
|
156
148
|
"rate must be 0 for value \(r)")
|
|
@@ -161,12 +153,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
161
153
|
|
|
162
154
|
func testRefreshPositionAndRateNoOpWhenSnapshotHasNoTrack() {
|
|
163
155
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = ["pre": "value"]
|
|
164
|
-
nowPlaying.
|
|
156
|
+
nowPlaying.refreshPositionAndRate(
|
|
165
157
|
NowPlayingInfo.Snapshot(
|
|
166
|
-
track: nil, elapsedSeconds: 5, durationSeconds: 60, rate: 1)
|
|
167
|
-
|
|
168
|
-
nowPlaying.refreshPositionAndRate()
|
|
169
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
158
|
+
track: nil, elapsedSeconds: 5, durationSeconds: 60, rate: 1))
|
|
159
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
170
160
|
XCTAssertEqual(info?["pre"] as? String, "value")
|
|
171
161
|
XCTAssertNil(info?[MPNowPlayingInfoPropertyElapsedPlaybackTime])
|
|
172
162
|
}
|
|
@@ -174,51 +164,39 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
174
164
|
func testRefreshPositionAndRateNoOpWhenDictNeverPopulated() {
|
|
175
165
|
let track = makeTrack(title: "T")
|
|
176
166
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
177
|
-
nowPlaying.
|
|
167
|
+
nowPlaying.refreshPositionAndRate(
|
|
178
168
|
NowPlayingInfo.Snapshot(
|
|
179
|
-
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1)
|
|
180
|
-
|
|
181
|
-
nowPlaying.refreshPositionAndRate()
|
|
182
|
-
XCTAssertNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
169
|
+
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1))
|
|
170
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
func testRefreshPositionAndRateNoOpAfterRefreshAllCleared() {
|
|
186
174
|
let track = makeTrack(title: "T")
|
|
187
|
-
nowPlaying.
|
|
175
|
+
nowPlaying.refreshAll(
|
|
188
176
|
NowPlayingInfo.Snapshot(
|
|
189
|
-
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1)
|
|
190
|
-
|
|
191
|
-
nowPlaying.refreshAll()
|
|
192
|
-
XCTAssertNotNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
177
|
+
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1))
|
|
178
|
+
XCTAssertNotNil(nowPlayingInfoAfterWritesLand())
|
|
193
179
|
|
|
194
|
-
nowPlaying.
|
|
180
|
+
nowPlaying.refreshAll(
|
|
195
181
|
NowPlayingInfo.Snapshot(
|
|
196
|
-
track: nil, elapsedSeconds: 0, durationSeconds: 0, rate: 0)
|
|
197
|
-
|
|
198
|
-
nowPlaying.refreshAll()
|
|
199
|
-
XCTAssertNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
182
|
+
track: nil, elapsedSeconds: 0, durationSeconds: 0, rate: 0))
|
|
183
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
200
184
|
|
|
201
|
-
nowPlaying.
|
|
185
|
+
nowPlaying.refreshPositionAndRate(
|
|
202
186
|
NowPlayingInfo.Snapshot(
|
|
203
|
-
track: track, elapsedSeconds: 30, durationSeconds: 60, rate: 1)
|
|
204
|
-
|
|
205
|
-
nowPlaying.refreshPositionAndRate()
|
|
206
|
-
XCTAssertNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
187
|
+
track: track, elapsedSeconds: 30, durationSeconds: 60, rate: 1))
|
|
188
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
207
189
|
}
|
|
208
190
|
|
|
209
191
|
func testRefreshPositionAndRateUpdatesElapsedAndRate() {
|
|
210
192
|
let track = makeTrack(title: "T")
|
|
211
|
-
nowPlaying.
|
|
193
|
+
nowPlaying.refreshAll(
|
|
212
194
|
NowPlayingInfo.Snapshot(
|
|
213
|
-
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1)
|
|
214
|
-
|
|
215
|
-
nowPlaying.refreshAll()
|
|
216
|
-
nowPlaying.snapshotProvider = {
|
|
195
|
+
track: track, elapsedSeconds: 5, durationSeconds: 60, rate: 1))
|
|
196
|
+
nowPlaying.refreshPositionAndRate(
|
|
217
197
|
NowPlayingInfo.Snapshot(
|
|
218
|
-
track: track, elapsedSeconds: 30, durationSeconds: 60, rate: 0)
|
|
219
|
-
|
|
220
|
-
nowPlaying.refreshPositionAndRate()
|
|
221
|
-
let info = MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
198
|
+
track: track, elapsedSeconds: 30, durationSeconds: 60, rate: 0))
|
|
199
|
+
let info = nowPlayingInfoAfterWritesLand()
|
|
222
200
|
XCTAssertEqual(info?[MPNowPlayingInfoPropertyElapsedPlaybackTime] as? Double, 30)
|
|
223
201
|
XCTAssertEqual(info?[MPNowPlayingInfoPropertyPlaybackRate] as? Double, 0)
|
|
224
202
|
XCTAssertEqual(info?[MPMediaItemPropertyTitle] as? String, "T")
|
|
@@ -228,12 +206,10 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
228
206
|
|
|
229
207
|
func testApplyArtworkUpdatesDictWhenPopulated() {
|
|
230
208
|
let track = makeTrack(title: "T")
|
|
231
|
-
nowPlaying.
|
|
209
|
+
nowPlaying.refreshAll(
|
|
232
210
|
NowPlayingInfo.Snapshot(
|
|
233
|
-
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0)
|
|
234
|
-
|
|
235
|
-
nowPlaying.refreshAll()
|
|
236
|
-
let placeholder = MPNowPlayingInfoCenter.default().nowPlayingInfo?[MPMediaItemPropertyArtwork]
|
|
211
|
+
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0))
|
|
212
|
+
let placeholder = nowPlayingInfoAfterWritesLand()?[MPMediaItemPropertyArtwork]
|
|
237
213
|
let size = CGSize(width: 8, height: 8)
|
|
238
214
|
let renderer = UIGraphicsImageRenderer(size: size)
|
|
239
215
|
let custom = renderer.image { ctx in
|
|
@@ -241,8 +217,8 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
241
217
|
ctx.fill(CGRect(x: 0, y: 0, width: 8, height: 8))
|
|
242
218
|
}
|
|
243
219
|
nowPlaying.applyArtwork(custom)
|
|
244
|
-
let after =
|
|
245
|
-
|
|
220
|
+
let after = nowPlayingInfoAfterWritesLand()?[
|
|
221
|
+
MPMediaItemPropertyArtwork] as? MPMediaItemArtwork
|
|
246
222
|
XCTAssertNotNil(after)
|
|
247
223
|
XCTAssertFalse((after as AnyObject) === (placeholder as AnyObject))
|
|
248
224
|
let resolvedImage = after?.image(at: size)
|
|
@@ -256,21 +232,19 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
256
232
|
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 8, height: 8))
|
|
257
233
|
let custom = renderer.image { ctx in UIColor.red.setFill(); ctx.fill(CGRect(x: 0, y: 0, width: 8, height: 8)) }
|
|
258
234
|
nowPlaying.applyArtwork(custom)
|
|
259
|
-
XCTAssertNil(
|
|
235
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
260
236
|
}
|
|
261
237
|
|
|
262
238
|
// MARK: - clear
|
|
263
239
|
|
|
264
240
|
func testClearNilsTheDict() {
|
|
265
241
|
let track = makeTrack(title: "T")
|
|
266
|
-
nowPlaying.
|
|
242
|
+
nowPlaying.refreshAll(
|
|
267
243
|
NowPlayingInfo.Snapshot(
|
|
268
|
-
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0)
|
|
269
|
-
|
|
270
|
-
nowPlaying.refreshAll()
|
|
271
|
-
XCTAssertNotNil(MPNowPlayingInfoCenter.default().nowPlayingInfo)
|
|
244
|
+
track: track, elapsedSeconds: 0, durationSeconds: 60, rate: 0))
|
|
245
|
+
XCTAssertNotNil(nowPlayingInfoAfterWritesLand())
|
|
272
246
|
nowPlaying.clear()
|
|
273
|
-
XCTAssertNil(
|
|
247
|
+
XCTAssertNil(nowPlayingInfoAfterWritesLand())
|
|
274
248
|
}
|
|
275
249
|
|
|
276
250
|
// MARK: - Helpers
|
|
@@ -286,7 +260,20 @@ final class NowPlayingInfoTests: XCTestCase {
|
|
|
286
260
|
) -> TrackItem {
|
|
287
261
|
return TrackItem(
|
|
288
262
|
id: id, url: url, title: title, artist: artist, album: album,
|
|
289
|
-
duration: duration, artworkUrl: artworkUrl, extras: nil
|
|
263
|
+
duration: duration, artworkUrl: artworkUrl, extras: nil,
|
|
264
|
+
replayGainTrackGain: nil, replayGainTrackPeak: nil,
|
|
265
|
+
replayGainAlbumGain: nil, replayGainAlbumPeak: nil
|
|
290
266
|
)
|
|
291
267
|
}
|
|
268
|
+
|
|
269
|
+
/// The Now Playing dictionary once the writes handed to main have landed.
|
|
270
|
+
/// `NowPlayingInfo` enqueues its writes rather than performing them inline,
|
|
271
|
+
/// so a read taken in the same turn still sees the previous value.
|
|
272
|
+
private func nowPlayingInfoAfterWritesLand() -> [String: Any]? {
|
|
273
|
+
let drained = expectation(description: "main queue drained")
|
|
274
|
+
DispatchQueue.main.async { drained.fulfill() }
|
|
275
|
+
wait(for: [drained], timeout: 2.0)
|
|
276
|
+
return MPNowPlayingInfoCenter.default().nowPlayingInfo
|
|
277
|
+
}
|
|
278
|
+
|
|
292
279
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import XCTest
|
|
3
|
+
@testable import QueuePlayer
|
|
4
|
+
|
|
5
|
+
/// The lock-screen snapshot reports the engine's position, duration and
|
|
6
|
+
/// effective rate — the configured speed while playing, zero otherwise — for
|
|
7
|
+
/// whichever engine is active, including one with no gapless queue player.
|
|
8
|
+
final class NowPlayingSnapshotTests: XCTestCase {
|
|
9
|
+
|
|
10
|
+
func testSnapshotReportsTheEnginesPositionAndDurationWithNoQueuePlayer() {
|
|
11
|
+
let player = TrackPlayer()
|
|
12
|
+
seedOneTrack(player)
|
|
13
|
+
let engine = SnapshotEngineStub()
|
|
14
|
+
engine.position = 42.5
|
|
15
|
+
engine.duration = 180
|
|
16
|
+
engine.playing = true
|
|
17
|
+
player.engine = engine
|
|
18
|
+
XCTAssertNil(player.player, "precondition: no gapless queue player to read")
|
|
19
|
+
|
|
20
|
+
let snapshot = player.nowPlayingSnapshot()
|
|
21
|
+
|
|
22
|
+
XCTAssertEqual(snapshot.elapsedSeconds, 42.5)
|
|
23
|
+
XCTAssertEqual(snapshot.durationSeconds, 180)
|
|
24
|
+
XCTAssertEqual(
|
|
25
|
+
snapshot.rate, 1.0,
|
|
26
|
+
"a playing engine must report a non-zero rate, or the lock screen renders paused")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func testRateIsZeroBecauseTheEngineIsNotPlayingRatherThanAlways() {
|
|
30
|
+
let player = TrackPlayer()
|
|
31
|
+
seedOneTrack(player)
|
|
32
|
+
let engine = SnapshotEngineStub()
|
|
33
|
+
engine.position = 10
|
|
34
|
+
engine.duration = 60
|
|
35
|
+
engine.playing = false
|
|
36
|
+
player.engine = engine
|
|
37
|
+
player.playbackSpeedState = 1.5
|
|
38
|
+
|
|
39
|
+
XCTAssertEqual(player.nowPlayingSnapshot().rate, 0)
|
|
40
|
+
XCTAssertEqual(
|
|
41
|
+
player.nowPlayingSnapshot().elapsedSeconds, 10,
|
|
42
|
+
"position still reports while paused")
|
|
43
|
+
|
|
44
|
+
// Same fixture, same configured speed — only the play state changes.
|
|
45
|
+
engine.playing = true
|
|
46
|
+
XCTAssertEqual(player.nowPlayingSnapshot().rate, 1.5)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func testSnapshotCarriesTheConfiguredSpeedAsTheRate() {
|
|
50
|
+
let player = TrackPlayer()
|
|
51
|
+
seedOneTrack(player)
|
|
52
|
+
let engine = SnapshotEngineStub()
|
|
53
|
+
engine.playing = true
|
|
54
|
+
player.engine = engine
|
|
55
|
+
player.playbackSpeedState = 1.5
|
|
56
|
+
|
|
57
|
+
XCTAssertEqual(player.nowPlayingSnapshot().rate, 1.5)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func testSnapshotHasNoTrackForAnEmptyQueue() {
|
|
61
|
+
let player = TrackPlayer()
|
|
62
|
+
player.engine = SnapshotEngineStub()
|
|
63
|
+
|
|
64
|
+
XCTAssertNil(player.nowPlayingSnapshot().track)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func seedOneTrack(_ player: TrackPlayer) {
|
|
68
|
+
_ = player.queueState.replaceAll([
|
|
69
|
+
TrackItem(
|
|
70
|
+
id: "t0", url: "https://example.com/0.m4a", title: "Song", artist: nil,
|
|
71
|
+
album: nil, duration: nil, artworkUrl: nil, extras: nil,
|
|
72
|
+
replayGainTrackGain: nil, replayGainTrackPeak: nil,
|
|
73
|
+
replayGainAlbumGain: nil, replayGainAlbumPeak: nil)
|
|
74
|
+
])
|
|
75
|
+
player.currentTrackIndex = 0
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// Reports whatever the test sets. Every other `PlaybackEngine` member is an
|
|
80
|
+
/// inert stub — the snapshot only reads position, duration and
|
|
81
|
+
/// `timeControlStatus`.
|
|
82
|
+
private final class SnapshotEngineStub: PlaybackEngine {
|
|
83
|
+
var position: TimeInterval = 0
|
|
84
|
+
var duration: TimeInterval = 0
|
|
85
|
+
var playing = false
|
|
86
|
+
|
|
87
|
+
var currentPositionSeconds: TimeInterval { position }
|
|
88
|
+
var currentDurationSeconds: TimeInterval { duration }
|
|
89
|
+
/// The snapshot reads `timeControlStatus`; `isPlaying` is here only to
|
|
90
|
+
/// satisfy the protocol, and the two agree in this stub.
|
|
91
|
+
var isPlaying: Bool { playing }
|
|
92
|
+
|
|
93
|
+
func setAudioMixProvider(_ provider: AudioMixProvider?) {}
|
|
94
|
+
func refreshAllItemMixes() {}
|
|
95
|
+
func release() {}
|
|
96
|
+
func setItems(_ items: [AVPlayerItem], startIndex: Int, startPositionSeconds: TimeInterval) {}
|
|
97
|
+
@discardableResult
|
|
98
|
+
func insertItems(_ items: [AVPlayerItem], after: AVPlayerItem?) -> Bool { true }
|
|
99
|
+
func remove(_ item: AVPlayerItem) {}
|
|
100
|
+
func removeAllItems() {}
|
|
101
|
+
func canInsert(_ item: AVPlayerItem, after: AVPlayerItem?) -> Bool { true }
|
|
102
|
+
func play() {}
|
|
103
|
+
func pause() {}
|
|
104
|
+
func stop() {}
|
|
105
|
+
func seek(toIndex index: Int, position: TimeInterval) {}
|
|
106
|
+
func skipToNext() {}
|
|
107
|
+
func skipToPrevious() {}
|
|
108
|
+
func setVolume(_ volume: Float) {}
|
|
109
|
+
func setPlaybackSpeed(_ speed: Float) {}
|
|
110
|
+
func seedPlaybackSpeed(_ speed: Float) {}
|
|
111
|
+
func setRepeatMode(_ mode: RepeatMode) {}
|
|
112
|
+
func setPitchCorrectionMode(_ mode: PitchCorrectionMode) {}
|
|
113
|
+
|
|
114
|
+
var currentMediaItemIndex: Int { -1 }
|
|
115
|
+
var bufferedPositionSeconds: TimeInterval { 0 }
|
|
116
|
+
var timeControlStatus: AVPlayer.TimeControlStatus { playing ? .playing : .paused }
|
|
117
|
+
var currentMediaItem: AVPlayerItem? { nil }
|
|
118
|
+
var allMediaItems: [AVPlayerItem] { [] }
|
|
119
|
+
|
|
120
|
+
/// No fade to schedule — this stub exercises the rest of the surface.
|
|
121
|
+
var isFadePendingOrActive: Bool { false }
|
|
122
|
+
|
|
123
|
+
/// Nothing attached — this stub exercises the rest of the surface.
|
|
124
|
+
var attachedQueueItemIds: [String] { [] }
|
|
125
|
+
|
|
126
|
+
var delegate: PlaybackEngineDelegate?
|
|
127
|
+
}
|