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,258 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Coverage for [ReplayGainData.merge] — the all-or-nothing, per-item
|
|
8
|
+
* choice between consumer-supplied ReplayGain and values detected from
|
|
9
|
+
* the file. Mirrors the iOS `ReplayGainMergeTests` matrix row for row,
|
|
10
|
+
* with the same fixtures, so both platforms hold the same rule.
|
|
11
|
+
*
|
|
12
|
+
* The rule is one predicate on the item, not two pair decisions and not
|
|
13
|
+
* four field decisions: a claimed track's ReplayGain comes solely from
|
|
14
|
+
* the consumer and detection is discarded in full — including for the
|
|
15
|
+
* mode the consumer said nothing about. `merge` takes the claim and the
|
|
16
|
+
* surviving values as two separate inputs; which fields make a claim and
|
|
17
|
+
* which values survive is [SuppliedReplayGain]'s job, covered in
|
|
18
|
+
* `SuppliedReplayGainTest`.
|
|
19
|
+
*
|
|
20
|
+
* Supplied and detected fixtures carry different values in every field,
|
|
21
|
+
* so a row that returned the wrong source cannot pass.
|
|
22
|
+
*/
|
|
23
|
+
class ReplayGainMergeTest {
|
|
24
|
+
|
|
25
|
+
// region Fixtures
|
|
26
|
+
|
|
27
|
+
private val detectedAll = ReplayGainData(
|
|
28
|
+
trackGainDb = -20.0f, trackPeak = 0.5f,
|
|
29
|
+
albumGainDb = -6.0f, albumPeak = 0.6f,
|
|
30
|
+
)
|
|
31
|
+
private val detectedTrackOnly = ReplayGainData(
|
|
32
|
+
trackGainDb = -20.0f, trackPeak = 0.5f,
|
|
33
|
+
albumGainDb = null, albumPeak = null,
|
|
34
|
+
)
|
|
35
|
+
private val detectedAlbumOnly = ReplayGainData(
|
|
36
|
+
trackGainDb = null, trackPeak = null,
|
|
37
|
+
albumGainDb = -6.0f, albumPeak = 0.6f,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
private val suppliedAll = ReplayGainData(
|
|
41
|
+
trackGainDb = -3.0f, trackPeak = 0.25f,
|
|
42
|
+
albumGainDb = -9.0f, albumPeak = 0.3f,
|
|
43
|
+
)
|
|
44
|
+
private val suppliedTrackGain = ReplayGainData(
|
|
45
|
+
trackGainDb = -3.0f, trackPeak = null,
|
|
46
|
+
albumGainDb = null, albumPeak = null,
|
|
47
|
+
)
|
|
48
|
+
private val suppliedAlbumGain = ReplayGainData(
|
|
49
|
+
trackGainDb = null, trackPeak = null,
|
|
50
|
+
albumGainDb = -9.0f, albumPeak = null,
|
|
51
|
+
)
|
|
52
|
+
private val suppliedTrackPeak = ReplayGainData(
|
|
53
|
+
trackGainDb = null, trackPeak = 0.25f,
|
|
54
|
+
albumGainDb = null, albumPeak = null,
|
|
55
|
+
)
|
|
56
|
+
private val suppliedAlbumPeak = ReplayGainData(
|
|
57
|
+
trackGainDb = null, trackPeak = null,
|
|
58
|
+
albumGainDb = null, albumPeak = 0.3f,
|
|
59
|
+
)
|
|
60
|
+
private val suppliedTrackPair = ReplayGainData(
|
|
61
|
+
trackGainDb = -3.0f, trackPeak = 0.25f,
|
|
62
|
+
albumGainDb = null, albumPeak = null,
|
|
63
|
+
)
|
|
64
|
+
private val suppliedAlbumPair = ReplayGainData(
|
|
65
|
+
trackGainDb = null, trackPeak = null,
|
|
66
|
+
albumGainDb = -9.0f, albumPeak = 0.3f,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
// endregion
|
|
70
|
+
|
|
71
|
+
// region No claim — detection is used verbatim
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
fun `an unclaimed track uses every detected field`() {
|
|
75
|
+
assertMerge(
|
|
76
|
+
claimed = false, supplied = null, detected = detectedAll,
|
|
77
|
+
expected = detectedAll,
|
|
78
|
+
message = "no supplied field means detection is used, all four",
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Test
|
|
83
|
+
fun `an unclaimed track with no detection has no ReplayGain`() {
|
|
84
|
+
assertMerge(
|
|
85
|
+
claimed = false, supplied = null, detected = null,
|
|
86
|
+
expected = null,
|
|
87
|
+
message = "nothing supplied and nothing detected is no ReplayGain",
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
fun `an unclaimed track keeps a track-only detection`() {
|
|
93
|
+
assertMerge(
|
|
94
|
+
claimed = false, supplied = null, detected = detectedTrackOnly,
|
|
95
|
+
expected = detectedTrackOnly,
|
|
96
|
+
message = "a partial detection is used as-is, not padded",
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@Test
|
|
101
|
+
fun `an unclaimed track keeps an album-only detection`() {
|
|
102
|
+
assertMerge(
|
|
103
|
+
claimed = false, supplied = null, detected = detectedAlbumOnly,
|
|
104
|
+
expected = detectedAlbumOnly,
|
|
105
|
+
message = "the mirrored partial-detection case",
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// endregion
|
|
110
|
+
|
|
111
|
+
// region Claimed — one supplied field discards all detection
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
fun `a supplied track gain discards every detected field`() {
|
|
115
|
+
assertMerge(
|
|
116
|
+
claimed = true, supplied = suppliedTrackGain, detected = detectedAll,
|
|
117
|
+
expected = suppliedTrackGain,
|
|
118
|
+
message = "supplied trackGain applies; detected peak and album pair are dropped",
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Test
|
|
123
|
+
fun `a supplied album gain discards every detected field`() {
|
|
124
|
+
assertMerge(
|
|
125
|
+
claimed = true, supplied = suppliedAlbumGain, detected = detectedAll,
|
|
126
|
+
expected = suppliedAlbumGain,
|
|
127
|
+
message = "supplied albumGain applies; detected track pair and album peak are dropped",
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Test
|
|
132
|
+
fun `a supplied track peak alone leaves no gain anywhere`() {
|
|
133
|
+
assertMerge(
|
|
134
|
+
claimed = true, supplied = suppliedTrackPeak, detected = detectedAll,
|
|
135
|
+
expected = suppliedTrackPeak,
|
|
136
|
+
message = "a peak with no gain is ownership with nothing to apply",
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
fun `a supplied album peak alone leaves no gain anywhere`() {
|
|
142
|
+
assertMerge(
|
|
143
|
+
claimed = true, supplied = suppliedAlbumPeak, detected = detectedAll,
|
|
144
|
+
expected = suppliedAlbumPeak,
|
|
145
|
+
message = "the mirrored peak-only case",
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
fun `a supplied track pair discards the detected album pair`() {
|
|
151
|
+
assertMerge(
|
|
152
|
+
claimed = true, supplied = suppliedTrackPair, detected = detectedAll,
|
|
153
|
+
expected = suppliedTrackPair,
|
|
154
|
+
message = "album mode plays at unity even though the file carries album tags",
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Test
|
|
159
|
+
fun `a supplied album pair discards the detected track pair`() {
|
|
160
|
+
assertMerge(
|
|
161
|
+
claimed = true, supplied = suppliedAlbumPair, detected = detectedAll,
|
|
162
|
+
expected = suppliedAlbumPair,
|
|
163
|
+
message = "track mode plays at unity even though the file carries track tags",
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Test
|
|
168
|
+
fun `a fully supplied set replaces a fully detected set`() {
|
|
169
|
+
assertMerge(
|
|
170
|
+
claimed = true, supplied = suppliedAll, detected = detectedAll,
|
|
171
|
+
expected = suppliedAll,
|
|
172
|
+
message = "all four supplied values win over all four detected values",
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun `a supplied track gain discards an album-only detection`() {
|
|
178
|
+
assertMerge(
|
|
179
|
+
claimed = true, supplied = suppliedTrackGain, detected = detectedAlbumOnly,
|
|
180
|
+
expected = suppliedTrackGain,
|
|
181
|
+
message = "detection is discarded even where supplied and detected do not overlap",
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// endregion
|
|
186
|
+
|
|
187
|
+
// region Claimed with no detection to discard
|
|
188
|
+
|
|
189
|
+
@Test
|
|
190
|
+
fun `a fully supplied set applies to an untagged file`() {
|
|
191
|
+
assertMerge(
|
|
192
|
+
claimed = true, supplied = suppliedAll, detected = null,
|
|
193
|
+
expected = suppliedAll,
|
|
194
|
+
message = "a supplied set is applied where the file carries no tags",
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Test
|
|
199
|
+
fun `a supplied track gain applies to an untagged file`() {
|
|
200
|
+
assertMerge(
|
|
201
|
+
claimed = true, supplied = suppliedTrackGain, detected = null,
|
|
202
|
+
expected = suppliedTrackGain,
|
|
203
|
+
message = "a single supplied field on an untagged file applies alone",
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// endregion
|
|
208
|
+
|
|
209
|
+
// region Claimed but nothing usable — ownership without a value
|
|
210
|
+
|
|
211
|
+
@Test
|
|
212
|
+
fun `a claim with no usable value discards a full detection`() {
|
|
213
|
+
assertMerge(
|
|
214
|
+
claimed = true, supplied = null, detected = detectedAll,
|
|
215
|
+
expected = null,
|
|
216
|
+
message = "a claim discards detection even when it carries no value",
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Test
|
|
221
|
+
fun `a claim with no usable value discards a partial detection`() {
|
|
222
|
+
assertMerge(
|
|
223
|
+
claimed = true, supplied = null, detected = detectedTrackOnly,
|
|
224
|
+
expected = null,
|
|
225
|
+
message = "a partial detection is discarded by a claim just as a full one is",
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@Test
|
|
230
|
+
fun `a claim with no usable value and no detection has no ReplayGain`() {
|
|
231
|
+
assertMerge(
|
|
232
|
+
claimed = true, supplied = null, detected = null,
|
|
233
|
+
expected = null,
|
|
234
|
+
message = "ownership claimed, nothing supplied, nothing detected",
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// endregion
|
|
239
|
+
|
|
240
|
+
// region Helper
|
|
241
|
+
|
|
242
|
+
private fun assertMerge(
|
|
243
|
+
claimed: Boolean,
|
|
244
|
+
supplied: ReplayGainData?,
|
|
245
|
+
detected: ReplayGainData?,
|
|
246
|
+
expected: ReplayGainData?,
|
|
247
|
+
message: String,
|
|
248
|
+
) {
|
|
249
|
+
val actual = ReplayGainData.merge(
|
|
250
|
+
suppliedClaimed = claimed,
|
|
251
|
+
supplied = supplied,
|
|
252
|
+
detected = detected,
|
|
253
|
+
)
|
|
254
|
+
assertEquals(message, expected, actual)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// endregion
|
|
258
|
+
}
|
|
@@ -41,13 +41,17 @@ import org.robolectric.android.controller.ServiceController
|
|
|
41
41
|
* enforces a unique session ID per JVM and a leaked session
|
|
42
42
|
* blocks the next [PlaybackService.onCreate] in the same run.
|
|
43
43
|
*
|
|
44
|
-
* Standard Robolectric service-lifecycle
|
|
45
|
-
* injection
|
|
44
|
+
* Standard Robolectric service-lifecycle driving — no test
|
|
45
|
+
* injection point on the lib side.
|
|
46
46
|
*/
|
|
47
47
|
@UnstableApi
|
|
48
48
|
internal class PlaybackServiceTestHandle(
|
|
49
|
-
private val controller: ServiceController<PlaybackService
|
|
49
|
+
private val controller: ServiceController<PlaybackService>,
|
|
50
|
+
private val binder: PlaybackService.LocalBinder,
|
|
50
51
|
) {
|
|
52
|
+
/** The bound service's live engine, for asserting local-path transport. */
|
|
53
|
+
val engine: PlaybackEngine get() = binder.engine
|
|
54
|
+
|
|
51
55
|
fun destroy() {
|
|
52
56
|
controller.destroy()
|
|
53
57
|
}
|
|
@@ -79,5 +83,5 @@ internal fun bindPlaybackServiceForTest(
|
|
|
79
83
|
deferred.await()
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
return PlaybackServiceTestHandle(controller)
|
|
86
|
+
return PlaybackServiceTestHandle(controller, binder as PlaybackService.LocalBinder)
|
|
83
87
|
}
|
package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt
CHANGED
|
@@ -27,7 +27,7 @@ import org.robolectric.annotation.Config
|
|
|
27
27
|
*
|
|
28
28
|
* The wrapper's `handle*` overrides are `protected` on a `final`
|
|
29
29
|
* Kotlin class, so the suite drives them through [HandleDriver] — a
|
|
30
|
-
* thin reflection
|
|
30
|
+
* thin reflection helper into the exact overridden methods — to assert
|
|
31
31
|
* the routing directly without standing up a live `MediaController`.
|
|
32
32
|
*
|
|
33
33
|
* Controller-vs-internal gating turns on whether
|
|
@@ -422,7 +422,7 @@ class SessionCommandForwardingPlayerTest {
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
|
-
* Reflection
|
|
425
|
+
* Reflection helper into a [SessionCommandForwardingPlayer]'s
|
|
426
426
|
* `protected` `handle*` overrides. The wrapper is a `final` Kotlin
|
|
427
427
|
* class so it cannot be subclassed; reflecting straight onto the
|
|
428
428
|
* declared overrides invokes the exact production methods without
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.media.audiofx.DynamicsProcessing
|
|
4
|
+
import org.robolectric.annotation.Implements
|
|
5
|
+
import org.robolectric.shadows.ShadowDynamicsProcessing
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A [DynamicsProcessing] whose construction and band writes succeed and
|
|
9
|
+
* whose `setEnabled` throws. The framework constructor never calls
|
|
10
|
+
* `setEnabled`; the engine does, after applying the bands and before
|
|
11
|
+
* recording the attach — so this is the one call that fails an attach
|
|
12
|
+
* inside its configure step rather than at construction.
|
|
13
|
+
*/
|
|
14
|
+
@Implements(DynamicsProcessing::class)
|
|
15
|
+
class ShadowDynamicsProcessingRejectingEnable : ShadowDynamicsProcessing() {
|
|
16
|
+
|
|
17
|
+
override fun native_setEnabled(enabled: Boolean): Int =
|
|
18
|
+
throw IllegalStateException("setEnabled() called on uninitialized AudioEffect")
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.media.audiofx.Equalizer
|
|
4
|
+
import org.robolectric.annotation.Implements
|
|
5
|
+
import org.robolectric.shadows.ShadowAudioEffect
|
|
6
|
+
import java.nio.ByteBuffer
|
|
7
|
+
import java.nio.ByteOrder
|
|
8
|
+
import java.util.Optional
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Models a five-band hardware equalizer so the legacy attach path resolves
|
|
12
|
+
* the same way on every run.
|
|
13
|
+
*
|
|
14
|
+
* [ShadowAudioEffect] answers the AudioEffect surface but falls back to a
|
|
15
|
+
* four-byte zero for any parameter it does not know, which the constructor's
|
|
16
|
+
* `short` reads cannot hold — so constructing an [Equalizer] throws before
|
|
17
|
+
* any band is queried. Supplying the parameters it reads makes a real attach
|
|
18
|
+
* distinguishable from a failed one. Levels written with `setBandLevel` land
|
|
19
|
+
* in the base shadow's parameter map, so they read back through the ordinary
|
|
20
|
+
* [Equalizer] API.
|
|
21
|
+
*/
|
|
22
|
+
@Implements(Equalizer::class)
|
|
23
|
+
open class ShadowEqualizer : ShadowAudioEffect() {
|
|
24
|
+
|
|
25
|
+
override fun getDefaultParameter(parameter: ByteBuffer): Optional<ByteBuffer> {
|
|
26
|
+
val key = parameter.duplicate().order(ByteOrder.nativeOrder())
|
|
27
|
+
if (key.remaining() < Int.SIZE_BYTES) return Optional.empty()
|
|
28
|
+
return when (key.int) {
|
|
29
|
+
PARAM_NUM_BANDS -> Optional.of(shorts(BAND_COUNT))
|
|
30
|
+
PARAM_LEVEL_RANGE -> Optional.of(shorts(MIN_LEVEL_MB, MAX_LEVEL_MB))
|
|
31
|
+
PARAM_BAND_LEVEL -> Optional.of(shorts(0))
|
|
32
|
+
PARAM_GET_NUM_OF_PRESETS -> Optional.of(shorts(0))
|
|
33
|
+
PARAM_CENTER_FREQ -> {
|
|
34
|
+
val band = if (key.remaining() >= Int.SIZE_BYTES) key.int else 0
|
|
35
|
+
Optional.of(ints(CENTER_FREQS_MILLI_HZ[band.coerceIn(0, BAND_COUNT - 1)]))
|
|
36
|
+
}
|
|
37
|
+
else -> Optional.empty()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private fun shorts(vararg values: Int): ByteBuffer =
|
|
42
|
+
ByteBuffer.allocate(values.size * Short.SIZE_BYTES).order(ByteOrder.nativeOrder()).apply {
|
|
43
|
+
values.forEach { putShort(it.toShort()) }
|
|
44
|
+
flip()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun ints(vararg values: Int): ByteBuffer =
|
|
48
|
+
ByteBuffer.allocate(values.size * Int.SIZE_BYTES).order(ByteOrder.nativeOrder()).apply {
|
|
49
|
+
values.forEach { putInt(it) }
|
|
50
|
+
flip()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
companion object {
|
|
54
|
+
const val BAND_COUNT = 5
|
|
55
|
+
const val MIN_LEVEL_MB = -1500
|
|
56
|
+
const val MAX_LEVEL_MB = 1500
|
|
57
|
+
|
|
58
|
+
/** Centre frequencies in milliHz, matching a typical OEM five-band set. */
|
|
59
|
+
private val CENTER_FREQS_MILLI_HZ =
|
|
60
|
+
intArrayOf(60_000, 230_000, 910_000, 3_600_000, 14_000_000)
|
|
61
|
+
|
|
62
|
+
private const val PARAM_NUM_BANDS = 0
|
|
63
|
+
private const val PARAM_LEVEL_RANGE = 1
|
|
64
|
+
private const val PARAM_BAND_LEVEL = 2
|
|
65
|
+
private const val PARAM_CENTER_FREQ = 3
|
|
66
|
+
private const val PARAM_GET_NUM_OF_PRESETS = 7
|
|
67
|
+
}
|
|
68
|
+
}
|
package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import android.media.audiofx.AudioEffect
|
|
4
|
+
import android.media.audiofx.Equalizer
|
|
5
|
+
import org.robolectric.annotation.Implements
|
|
6
|
+
import java.nio.ByteBuffer
|
|
7
|
+
import java.nio.ByteOrder
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A five-band equalizer whose construction and parameter reads succeed and
|
|
11
|
+
* whose first band-level write fails, so an attach reaches its configure
|
|
12
|
+
* step and throws there. `Equalizer.setBandLevel` routes the returned error
|
|
13
|
+
* through `checkStatus`, which raises `RuntimeException`.
|
|
14
|
+
*/
|
|
15
|
+
@Implements(Equalizer::class)
|
|
16
|
+
class ShadowEqualizerRejectingBandWrites : ShadowEqualizer() {
|
|
17
|
+
|
|
18
|
+
override fun native_setParameter(psize: Int, param: ByteArray, vsize: Int, value: ByteArray): Int {
|
|
19
|
+
val id = ByteBuffer.wrap(param).order(ByteOrder.nativeOrder()).int
|
|
20
|
+
if (id == PARAM_BAND_LEVEL) return AudioEffect.ERROR_INVALID_OPERATION
|
|
21
|
+
return super.native_setParameter(psize, param, vsize, value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private companion object {
|
|
25
|
+
const val PARAM_BAND_LEVEL = 2
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.util.UnstableApi
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Assert.assertNotNull
|
|
6
|
+
import org.junit.Assert.assertNull
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* [SuppliedReplayGain.from] separates the consumer's claim over a track
|
|
11
|
+
* from the values that survive validation: any present field claims the
|
|
12
|
+
* track, and only accepted values are carried.
|
|
13
|
+
*/
|
|
14
|
+
@UnstableApi
|
|
15
|
+
class SuppliedReplayGainTest {
|
|
16
|
+
|
|
17
|
+
@Test
|
|
18
|
+
fun `a track with no ReplayGain field makes no claim`() {
|
|
19
|
+
assertNull(SuppliedReplayGain.from(track()))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
fun `one valid field claims the track and is carried`() {
|
|
24
|
+
val claim = SuppliedReplayGain.from(track(trackGain = -3.0))
|
|
25
|
+
|
|
26
|
+
assertNotNull(claim)
|
|
27
|
+
assertEquals(
|
|
28
|
+
ReplayGainData(trackGainDb = -3.0f, trackPeak = null, albumGainDb = null, albumPeak = null),
|
|
29
|
+
claim?.data)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun `only invalid fields still claim the track but carry nothing`() {
|
|
34
|
+
val claim = SuppliedReplayGain.from(
|
|
35
|
+
track(trackGain = Double.NaN, trackPeak = 5.0, albumGain = Double.POSITIVE_INFINITY, albumPeak = 0.0))
|
|
36
|
+
|
|
37
|
+
assertNotNull("presence claims the track, whatever the values", claim)
|
|
38
|
+
assertNull("no value passed acceptance, so there is nothing to apply", claim?.data)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test
|
|
42
|
+
fun `a mix carries the accepted fields and drops the rest`() {
|
|
43
|
+
val claim = SuppliedReplayGain.from(
|
|
44
|
+
track(trackGain = -3.0, trackPeak = 5.0, albumGain = -9.0, albumPeak = 0.3))
|
|
45
|
+
|
|
46
|
+
assertEquals(
|
|
47
|
+
ReplayGainData(trackGainDb = -3.0f, trackPeak = null, albumGainDb = -9.0f, albumPeak = 0.3f),
|
|
48
|
+
claim?.data)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private fun track(
|
|
52
|
+
trackGain: Double? = null,
|
|
53
|
+
trackPeak: Double? = null,
|
|
54
|
+
albumGain: Double? = null,
|
|
55
|
+
albumPeak: Double? = null,
|
|
56
|
+
) = TrackItem(
|
|
57
|
+
id = null, url = "https://example.com/a.mp3", title = null, artist = null, album = null,
|
|
58
|
+
duration = null, artworkUrl = null, extras = null,
|
|
59
|
+
replayGainTrackGain = trackGain, replayGainTrackPeak = trackPeak,
|
|
60
|
+
replayGainAlbumGain = albumGain, replayGainAlbumPeak = albumPeak)
|
|
61
|
+
}
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt
CHANGED
|
@@ -38,7 +38,7 @@ import java.nio.ByteBuffer
|
|
|
38
38
|
* stopped when the AirPlay engine is released or the session closes
|
|
39
39
|
* (`MetadataAwareSession.onDeactivated`).
|
|
40
40
|
*
|
|
41
|
-
* The route flip is driven through the production
|
|
41
|
+
* The route flip is driven through the production path — `CastSinkRouter`'s
|
|
42
42
|
* activate/deactivate transitions, which `TrackPlayer`'s route listener turns
|
|
43
43
|
* into a posted `applyEngineSwapForMode`. The sync itself is observed through a
|
|
44
44
|
* fake [CastSession.PcmStream] that also implements [MetadataAwareSession] and
|
|
@@ -64,7 +64,7 @@ class TrackPlayerAirPlayMetadataWiringTest {
|
|
|
64
64
|
PlayerConfig(
|
|
65
65
|
httpHeaders = null, userAgent = null, autoRetries = null,
|
|
66
66
|
retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null,
|
|
67
|
-
audioCategoryOptions = null, visualizationEnabled = null,
|
|
67
|
+
audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null,
|
|
68
68
|
clampSeekToBuffered = null,
|
|
69
69
|
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null,
|
|
70
70
|
),
|
|
@@ -74,8 +74,8 @@ class TrackPlayerAirPlayMetadataWiringTest {
|
|
|
74
74
|
// matching the production path where `onActivated` is fired after the queue
|
|
75
75
|
// install reads a populated player.
|
|
76
76
|
player.tracks = listOf(
|
|
77
|
-
TrackItem("t1", "https://example.com/1.m4a", null, null, null, null, null, null),
|
|
78
|
-
TrackItem("t2", "https://example.com/2.m4a", null, null, null, null, null, null),
|
|
77
|
+
TrackItem("t1", "https://example.com/1.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
78
|
+
TrackItem("t2", "https://example.com/2.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
79
79
|
)
|
|
80
80
|
player.currentTrackIndex = 0
|
|
81
81
|
}
|
|
@@ -44,7 +44,7 @@ class TrackPlayerAudioFocusTest {
|
|
|
44
44
|
player = TrackPlayer()
|
|
45
45
|
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
46
46
|
player.configureInternal(
|
|
47
|
-
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
47
|
+
PlayerConfig(httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null, networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null, voiceVocabularyDonationEnabled = null, visualizationEnabled = null, clampSeekToBuffered = null, lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null), context()
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package com.margelo.nitro.queueplayer
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.util.UnstableApi
|
|
4
|
+
import androidx.test.core.app.ApplicationProvider
|
|
5
|
+
import com.margelo.nitro.queueplayer.cast.FakeRemotePlayer
|
|
6
|
+
import com.margelo.nitro.queueplayer.cast.PlaybackStateRouter
|
|
7
|
+
import com.margelo.nitro.queueplayer.cast.RemotePlaybackState
|
|
8
|
+
import org.junit.After
|
|
9
|
+
import org.junit.Assert.assertEquals
|
|
10
|
+
import org.junit.Before
|
|
11
|
+
import org.junit.Test
|
|
12
|
+
import org.junit.runner.RunWith
|
|
13
|
+
import org.robolectric.RobolectricTestRunner
|
|
14
|
+
import org.robolectric.annotation.Config
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* OS-surface transport commands — hardware and Bluetooth media keys, and the
|
|
18
|
+
* media-session skip callbacks — drive the receiver while a cast session owns
|
|
19
|
+
* playback. Routing them to the local engine would play the phone speaker over
|
|
20
|
+
* the receiver, or skip a queue nobody is listening to.
|
|
21
|
+
*/
|
|
22
|
+
@RunWith(RobolectricTestRunner::class)
|
|
23
|
+
@Config(sdk = [34])
|
|
24
|
+
@UnstableApi
|
|
25
|
+
class TrackPlayerCastCommandRoutingTest {
|
|
26
|
+
|
|
27
|
+
private lateinit var player: TrackPlayer
|
|
28
|
+
private lateinit var serviceHandle: PlaybackServiceTestHandle
|
|
29
|
+
private val remote = FakeRemotePlayer()
|
|
30
|
+
|
|
31
|
+
private fun context() = ApplicationProvider.getApplicationContext<android.content.Context>()
|
|
32
|
+
|
|
33
|
+
private fun handler() = player.mediaSessionCommandHandler
|
|
34
|
+
|
|
35
|
+
@Before
|
|
36
|
+
fun setUp() {
|
|
37
|
+
PlaybackStateRouter.setActive(null)
|
|
38
|
+
player = TrackPlayer()
|
|
39
|
+
serviceHandle = bindPlaybackServiceForTest(player, context())
|
|
40
|
+
player.configureInternal(
|
|
41
|
+
PlayerConfig(
|
|
42
|
+
httpHeaders = null, userAgent = null, autoRetries = null, retryBackoffMs = null,
|
|
43
|
+
networkTimeoutMs = null, audioContentType = null, audioCategoryOptions = null,
|
|
44
|
+
voiceVocabularyDonationEnabled = null,
|
|
45
|
+
visualizationEnabled = null, clampSeekToBuffered = null,
|
|
46
|
+
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null,
|
|
47
|
+
progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null,
|
|
48
|
+
placeholderArtworkUri = null, skipToPreviousBehavior = null
|
|
49
|
+
),
|
|
50
|
+
context()
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@After
|
|
55
|
+
fun tearDown() {
|
|
56
|
+
PlaybackStateRouter.setActive(null)
|
|
57
|
+
player.destroyInternal()
|
|
58
|
+
serviceHandle.destroy()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
fun `media play key drives the receiver`() {
|
|
63
|
+
PlaybackStateRouter.setActive(remote)
|
|
64
|
+
|
|
65
|
+
handler().onMediaPlay()
|
|
66
|
+
|
|
67
|
+
assertEquals(listOf("play"), remote.transportCalls)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Test
|
|
71
|
+
fun `media pause key drives the receiver`() {
|
|
72
|
+
PlaybackStateRouter.setActive(remote)
|
|
73
|
+
|
|
74
|
+
handler().onMediaPause()
|
|
75
|
+
|
|
76
|
+
assertEquals(listOf("pause"), remote.transportCalls)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
fun `toggle key pauses a playing receiver`() {
|
|
81
|
+
remote.reportState(RemotePlaybackState.PLAYING)
|
|
82
|
+
PlaybackStateRouter.setActive(remote)
|
|
83
|
+
|
|
84
|
+
handler().onMediaTogglePlayPause()
|
|
85
|
+
|
|
86
|
+
assertEquals(listOf("pause"), remote.transportCalls)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test
|
|
90
|
+
fun `toggle key pauses a buffering receiver`() {
|
|
91
|
+
remote.reportState(RemotePlaybackState.BUFFERING)
|
|
92
|
+
PlaybackStateRouter.setActive(remote)
|
|
93
|
+
|
|
94
|
+
handler().onMediaTogglePlayPause()
|
|
95
|
+
|
|
96
|
+
assertEquals(listOf("pause"), remote.transportCalls)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
fun `toggle key pauses a loading receiver`() {
|
|
101
|
+
remote.reportState(RemotePlaybackState.LOADING)
|
|
102
|
+
PlaybackStateRouter.setActive(remote)
|
|
103
|
+
|
|
104
|
+
handler().onMediaTogglePlayPause()
|
|
105
|
+
|
|
106
|
+
assertEquals(listOf("pause"), remote.transportCalls)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Test
|
|
110
|
+
fun `toggle key resumes a paused receiver`() {
|
|
111
|
+
remote.reportState(RemotePlaybackState.PAUSED)
|
|
112
|
+
PlaybackStateRouter.setActive(remote)
|
|
113
|
+
|
|
114
|
+
handler().onMediaTogglePlayPause()
|
|
115
|
+
|
|
116
|
+
assertEquals(listOf("play"), remote.transportCalls)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@Test
|
|
120
|
+
fun `media skip keys drive the receiver queue`() {
|
|
121
|
+
PlaybackStateRouter.setActive(remote)
|
|
122
|
+
|
|
123
|
+
handler().onMediaSkipNext()
|
|
124
|
+
handler().onMediaSkipPrevious()
|
|
125
|
+
|
|
126
|
+
assertEquals(listOf("queueNext", "queuePrev"), remote.transportCalls)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@Test
|
|
130
|
+
fun `session skip commands drive the receiver queue and stay claimed`() {
|
|
131
|
+
PlaybackStateRouter.setActive(remote)
|
|
132
|
+
|
|
133
|
+
assertEquals(true, handler().onSkipToNextCommand())
|
|
134
|
+
assertEquals(true, handler().onSkipToPreviousCommand())
|
|
135
|
+
assertEquals(listOf("queueNext", "queuePrev"), remote.transportCalls)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Test
|
|
139
|
+
fun `play key drives the local engine when no cast session is active`() {
|
|
140
|
+
handler().onMediaPlay()
|
|
141
|
+
|
|
142
|
+
assertEquals(true, serviceHandle.engine.mediaSessionPlayer.playWhenReady)
|
|
143
|
+
assertEquals(emptyList<String>(), remote.transportCalls)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Test
|
|
147
|
+
fun `pause key drives the local engine when no cast session is active`() {
|
|
148
|
+
handler().onMediaPlay()
|
|
149
|
+
handler().onMediaPause()
|
|
150
|
+
|
|
151
|
+
assertEquals(false, serviceHandle.engine.mediaSessionPlayer.playWhenReady)
|
|
152
|
+
assertEquals(emptyList<String>(), remote.transportCalls)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
fun `toggle key drives the local engine when no cast session is active`() {
|
|
157
|
+
handler().onMediaPlay()
|
|
158
|
+
handler().onMediaTogglePlayPause()
|
|
159
|
+
|
|
160
|
+
assertEquals(false, serviceHandle.engine.mediaSessionPlayer.playWhenReady)
|
|
161
|
+
assertEquals(emptyList<String>(), remote.transportCalls)
|
|
162
|
+
}
|
|
163
|
+
}
|