react-native-queue-player 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +3 -19
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +8 -69
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +111 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +13 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +985 -276
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +84 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaResponseCheckTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +122 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +672 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +229 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaResponseCheck.swift +122 -0
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MediaResponseCheckTests.swift +128 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +418 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +441 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +206 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +1042 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +982 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +951 -0
- package/ios/TrackPlayer+Recovery.swift +213 -0
- package/ios/TrackPlayer+Skip.swift +465 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +272 -0
- package/ios/TrackPlayer+Window.swift +227 -0
- package/ios/TrackPlayer.swift +331 -4619
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import QueuePlayer
|
|
3
|
+
|
|
4
|
+
/// What the cache will and will not believe is a track, judged from a
|
|
5
|
+
/// response's declared type and the first bytes of its body.
|
|
6
|
+
final class MediaResponseCheckTests: XCTestCase {
|
|
7
|
+
|
|
8
|
+
private let xmlEnvelope = Data("""
|
|
9
|
+
<api-response status="failed"><error code="70"/></api-response>
|
|
10
|
+
""".utf8)
|
|
11
|
+
private let mp3 = Data([0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00]) // "ID3"
|
|
12
|
+
|
|
13
|
+
private func rejection(
|
|
14
|
+
_ contentType: String?, _ body: Data, whole: Bool = true
|
|
15
|
+
) -> MediaResponseCheck.Rejection? {
|
|
16
|
+
MediaResponseCheck.rejection(
|
|
17
|
+
contentType: contentType, head: body, headIsStartOfResource: whole)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - A declared document is refused whatever its body
|
|
21
|
+
|
|
22
|
+
func testMarkupAndJsonContentTypesAreRefused() {
|
|
23
|
+
for type in [
|
|
24
|
+
"application/xml", "text/xml", "text/html",
|
|
25
|
+
"application/xhtml+xml", "application/json", "text/json",
|
|
26
|
+
] {
|
|
27
|
+
XCTAssertEqual(
|
|
28
|
+
rejection(type, xmlEnvelope), .markupContentType(type),
|
|
29
|
+
"\(type) is a document type")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func testTheDeclaredTypeIsReadWithoutItsParameters() {
|
|
34
|
+
XCTAssertEqual(
|
|
35
|
+
rejection("application/xml; charset=utf-8", xmlEnvelope),
|
|
36
|
+
.markupContentType("application/xml"))
|
|
37
|
+
XCTAssertEqual(rejection("APPLICATION/XML", xmlEnvelope), .markupContentType("application/xml"))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// A ranged request answers from an arbitrary offset, so only the declared
|
|
41
|
+
/// type can be judged — but that much still holds.
|
|
42
|
+
func testADeclaredDocumentIsRefusedOnAPartialBodyToo() {
|
|
43
|
+
XCTAssertEqual(
|
|
44
|
+
rejection("application/xml", Data([0x3C, 0x00]), whole: false),
|
|
45
|
+
.markupContentType("application/xml"))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func testAPartialBodyIsNotJudgedByItsLeadingBytes() {
|
|
49
|
+
// The same markup, under a type that is not itself a document, so only the
|
|
50
|
+
// leading-byte rule can decide: refused as a whole resource, accepted as a
|
|
51
|
+
// range, which is what separates the two inputs.
|
|
52
|
+
let markup = Data("<api-response/>".utf8)
|
|
53
|
+
XCTAssertEqual(
|
|
54
|
+
rejection("application/octet-stream", markup),
|
|
55
|
+
.markupBody)
|
|
56
|
+
XCTAssertNil(
|
|
57
|
+
rejection("application/octet-stream", markup, whole: false),
|
|
58
|
+
"a body that begins part way into a file says nothing about its first bytes")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// MARK: - Plain text has to prove itself
|
|
62
|
+
|
|
63
|
+
func testTextWithoutAContainerSignatureIsRefused() {
|
|
64
|
+
XCTAssertEqual(
|
|
65
|
+
rejection("text/plain", Data("not found".utf8)),
|
|
66
|
+
.textWithoutMediaSignature("text/plain"))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func testTextCarryingAContainerSignatureIsAccepted() {
|
|
70
|
+
XCTAssertNil(
|
|
71
|
+
rejection("text/plain", mp3),
|
|
72
|
+
"a server mislabelling audio as text does not make it a document")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func testAPlaylistDeclaredAsTextIsAccepted() {
|
|
76
|
+
XCTAssertNil(rejection("text/plain", Data("#EXTM3U\n#EXT-X-VERSION:3".utf8)))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MARK: - An unusable declared type falls to the body
|
|
80
|
+
|
|
81
|
+
func testAMarkupBodyIsRefusedWhenNothingUsableIsDeclared() {
|
|
82
|
+
XCTAssertEqual(rejection(nil, xmlEnvelope), .markupBody)
|
|
83
|
+
XCTAssertEqual(rejection("application/octet-stream", xmlEnvelope), .markupBody)
|
|
84
|
+
XCTAssertEqual(rejection(nil, Data("<!DOCTYPE html><html>".utf8)), .markupBody)
|
|
85
|
+
XCTAssertEqual(rejection(nil, Data("{\"error\":\"not found\"}".utf8)), .markupBody)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func testLeadingSpaceAndAByteOrderMarkDoNotHideAMarkupBody() {
|
|
89
|
+
XCTAssertEqual(rejection(nil, Data("\n <?xml version=\"1.0\"?>".utf8)), .markupBody)
|
|
90
|
+
XCTAssertEqual(
|
|
91
|
+
rejection(nil, Data([0xEF, 0xBB, 0xBF]) + Data("<error/>".utf8)), .markupBody)
|
|
92
|
+
// `FF FE` also satisfies the MPEG frame sync, so a UTF-16 document would
|
|
93
|
+
// read as audio if the mark were not dropped first.
|
|
94
|
+
XCTAssertEqual(
|
|
95
|
+
rejection(nil, Data([0xFF, 0xFE]) + Data("<e/>".utf8)), .markupBody)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// MARK: - Real media is accepted, however it is labelled
|
|
99
|
+
|
|
100
|
+
func testRecognisedContainersAreAccepted() {
|
|
101
|
+
let bodies: [String: [UInt8]] = [
|
|
102
|
+
"ID3": [0x49, 0x44, 0x33, 0x04, 0, 0, 0, 0],
|
|
103
|
+
"MPEG frame sync": [0xFF, 0xFB, 0x90, 0x00, 0, 0, 0, 0],
|
|
104
|
+
"ADTS AAC": [0xFF, 0xF1, 0x50, 0x80, 0, 0, 0, 0],
|
|
105
|
+
"FLAC": [0x66, 0x4C, 0x61, 0x43, 0, 0, 0, 0],
|
|
106
|
+
"Ogg": [0x4F, 0x67, 0x67, 0x53, 0, 0, 0, 0],
|
|
107
|
+
"WAV": [0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0],
|
|
108
|
+
"AIFF": [0x46, 0x4F, 0x52, 0x4D, 0, 0, 0, 0],
|
|
109
|
+
"CAF": [0x63, 0x61, 0x66, 0x66, 0, 0, 0, 0],
|
|
110
|
+
"MP4": [0, 0, 0, 0x20, 0x66, 0x74, 0x79, 0x70],
|
|
111
|
+
]
|
|
112
|
+
for (name, bytes) in bodies {
|
|
113
|
+
XCTAssertNil(rejection("text/plain", Data(bytes)), "\(name) is media")
|
|
114
|
+
XCTAssertNil(rejection(nil, Data(bytes)), "\(name) is media")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
func testAnUndeclaredOrOpaqueBinaryBodyIsAccepted() {
|
|
119
|
+
let binary = Data([0x00, 0x01, 0x02, 0x03, 0x04])
|
|
120
|
+
XCTAssertNil(rejection(nil, binary), "an unrecognised container is not a document")
|
|
121
|
+
XCTAssertNil(rejection("application/octet-stream", binary))
|
|
122
|
+
XCTAssertNil(rejection("audio/mpeg", binary))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func testAnEmptyBodyIsNotJudged() {
|
|
126
|
+
XCTAssertNil(rejection(nil, Data()))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -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
|
}
|