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
|
@@ -57,6 +57,18 @@ class LookaheadCacheWriterTest {
|
|
|
57
57
|
assertTrue(w.computeTargets(emptyList(), currentIndex = 0, lookaheadCount = 3).isEmpty())
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
@Test
|
|
61
|
+
fun `computeTargets clamps a lookaheadCount that would overflow the range`() {
|
|
62
|
+
// The count arrives as a JS Double, so Infinity reaches Int.MAX_VALUE here
|
|
63
|
+
// and `start + count` wraps negative — a range subList rejects outright.
|
|
64
|
+
val w = newWriter()
|
|
65
|
+
val tracks = (1..3).map { httpTrack("t$it") }
|
|
66
|
+
assertEquals(
|
|
67
|
+
2,
|
|
68
|
+
w.computeTargets(tracks, currentIndex = 0, lookaheadCount = Int.MAX_VALUE).size
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
60
72
|
@Test
|
|
61
73
|
fun `computeTargets returns empty for non-positive lookaheadCount`() {
|
|
62
74
|
val w = newWriter()
|
|
@@ -632,6 +644,103 @@ class LookaheadCacheWriterTest {
|
|
|
632
644
|
|
|
633
645
|
// --- Helpers
|
|
634
646
|
|
|
647
|
+
// --- allowsCellularAccess
|
|
648
|
+
|
|
649
|
+
@Test
|
|
650
|
+
fun `prefetch is skipped while cellular is not permitted and the network is cellular`() = runTest {
|
|
651
|
+
val downloads = mutableListOf<String>()
|
|
652
|
+
val writer = LookaheadCacheWriter(
|
|
653
|
+
downloader = { track -> downloads.add(track.id ?: "") },
|
|
654
|
+
scope = CoroutineScope(StandardTestDispatcher(testScheduler) + SupervisorJob()),
|
|
655
|
+
isOnCellular = { true }
|
|
656
|
+
)
|
|
657
|
+
writer.allowsCellularAccess = false
|
|
658
|
+
|
|
659
|
+
writer.reschedule((0..4).map { httpTrack("t$it") }, currentIndex = 0, lookaheadCount = 3)
|
|
660
|
+
advanceUntilIdle()
|
|
661
|
+
|
|
662
|
+
assertTrue(downloads.isEmpty())
|
|
663
|
+
assertNull(writer.orchestratorJob)
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
@Test
|
|
667
|
+
fun `prefetch runs on a non-cellular network even when cellular is not permitted`() = runTest {
|
|
668
|
+
val downloads = mutableListOf<String>()
|
|
669
|
+
val writer = LookaheadCacheWriter(
|
|
670
|
+
downloader = { track -> downloads.add(track.id ?: "") },
|
|
671
|
+
scope = CoroutineScope(StandardTestDispatcher(testScheduler) + SupervisorJob()),
|
|
672
|
+
isOnCellular = { false }
|
|
673
|
+
)
|
|
674
|
+
writer.allowsCellularAccess = false
|
|
675
|
+
|
|
676
|
+
writer.reschedule((0..4).map { httpTrack("t$it") }, currentIndex = 0, lookaheadCount = 3)
|
|
677
|
+
advanceUntilIdle()
|
|
678
|
+
|
|
679
|
+
assertEquals(listOf("t1", "t2", "t3"), downloads)
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@Test
|
|
683
|
+
fun `prefetch runs on cellular while it is permitted`() = runTest {
|
|
684
|
+
val downloads = mutableListOf<String>()
|
|
685
|
+
val writer = LookaheadCacheWriter(
|
|
686
|
+
downloader = { track -> downloads.add(track.id ?: "") },
|
|
687
|
+
scope = CoroutineScope(StandardTestDispatcher(testScheduler) + SupervisorJob()),
|
|
688
|
+
isOnCellular = { true }
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
writer.reschedule((0..4).map { httpTrack("t$it") }, currentIndex = 0, lookaheadCount = 3)
|
|
692
|
+
advanceUntilIdle()
|
|
693
|
+
|
|
694
|
+
assertEquals(listOf("t1", "t2", "t3"), downloads)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
@Test
|
|
698
|
+
fun `handing off to cellular cancels the download in flight`() = runBlocking<Unit> {
|
|
699
|
+
val started = CompletableDeferred<Unit>()
|
|
700
|
+
val release = CompletableDeferred<Unit>()
|
|
701
|
+
var onCellular = false
|
|
702
|
+
val writer = LookaheadCacheWriter(
|
|
703
|
+
downloader = { started.complete(Unit); release.await() },
|
|
704
|
+
scope = CoroutineScope(SupervisorJob()),
|
|
705
|
+
isOnCellular = { onCellular }
|
|
706
|
+
)
|
|
707
|
+
writer.allowsCellularAccess = false
|
|
708
|
+
val queue = (0..4).map { httpTrack("t$it") }
|
|
709
|
+
writer.reschedule(queue, currentIndex = 0, lookaheadCount = 3)
|
|
710
|
+
started.await()
|
|
711
|
+
// Captured before the handoff: the orchestrator clears `inFlightJob` on its
|
|
712
|
+
// own thread once the cancelled download resumes it, so reading the slot
|
|
713
|
+
// afterwards races that clear.
|
|
714
|
+
val inFlightBefore = writer.inFlightJob
|
|
715
|
+
|
|
716
|
+
onCellular = true
|
|
717
|
+
writer.reschedule(queue, currentIndex = 0, lookaheadCount = 3)
|
|
718
|
+
|
|
719
|
+
assertTrue("the download in flight must be cancelled", inFlightBefore?.isCancelled == true)
|
|
720
|
+
release.complete(Unit)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
@Test
|
|
724
|
+
fun `turning cellular back on refills the window on the next reschedule`() = runTest {
|
|
725
|
+
val downloads = mutableListOf<String>()
|
|
726
|
+
val writer = LookaheadCacheWriter(
|
|
727
|
+
downloader = { track -> downloads.add(track.id ?: "") },
|
|
728
|
+
scope = CoroutineScope(StandardTestDispatcher(testScheduler) + SupervisorJob()),
|
|
729
|
+
isOnCellular = { true }
|
|
730
|
+
)
|
|
731
|
+
val queue = (0..4).map { httpTrack("t$it") }
|
|
732
|
+
writer.allowsCellularAccess = false
|
|
733
|
+
writer.reschedule(queue, currentIndex = 0, lookaheadCount = 3)
|
|
734
|
+
advanceUntilIdle()
|
|
735
|
+
assertTrue(downloads.isEmpty())
|
|
736
|
+
|
|
737
|
+
writer.allowsCellularAccess = true
|
|
738
|
+
writer.reschedule(queue, currentIndex = 0, lookaheadCount = 3)
|
|
739
|
+
advanceUntilIdle()
|
|
740
|
+
|
|
741
|
+
assertEquals(listOf("t1", "t2", "t3"), downloads)
|
|
742
|
+
}
|
|
743
|
+
|
|
635
744
|
private fun httpTrack(id: String): TrackItem =
|
|
636
745
|
makeTrack(id, "https://example.com/$id.m4a")
|
|
637
746
|
|
|
@@ -644,6 +753,8 @@ class LookaheadCacheWriterTest {
|
|
|
644
753
|
album = null,
|
|
645
754
|
duration = null,
|
|
646
755
|
artworkUrl = null,
|
|
647
|
-
extras = null
|
|
756
|
+
extras = null,
|
|
757
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
758
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
648
759
|
)
|
|
649
760
|
}
|
|
@@ -171,7 +171,7 @@ class MediaItemBuilderTest {
|
|
|
171
171
|
// Parity with iOS `AVQueueBuilderTests.testMakeItemForFileURL
|
|
172
172
|
// SkipsHeaderDictionary`: the test asserts the URI scheme is
|
|
173
173
|
// preserved, but does NOT assert the file/remote routing
|
|
174
|
-
// downstream.
|
|
174
|
+
// downstream. What the factory built is unreadable post-build on
|
|
175
175
|
// both platforms (AVURLAsset options can't be read back on
|
|
176
176
|
// iOS; DefaultDataSource.Factory wiring is internal on
|
|
177
177
|
// Android), so the documented contract is the test.
|
|
@@ -184,7 +184,7 @@ class MediaItemBuilderTest {
|
|
|
184
184
|
assertEquals("file", uri?.scheme)
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
// --- buildMediaItems MIME
|
|
187
|
+
// --- buildMediaItems MIME handling
|
|
188
188
|
|
|
189
189
|
@Test
|
|
190
190
|
fun `buildMediaItems sets MIME from URL extension for http urls`() {
|
|
@@ -474,6 +474,8 @@ class MediaItemBuilderTest {
|
|
|
474
474
|
album = album,
|
|
475
475
|
duration = duration,
|
|
476
476
|
artworkUrl = artworkUrl,
|
|
477
|
-
extras = null
|
|
477
|
+
extras = null,
|
|
478
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
479
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null
|
|
478
480
|
)
|
|
479
481
|
}
|
package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt
CHANGED
|
@@ -4,7 +4,10 @@ import androidx.media3.common.Format
|
|
|
4
4
|
import androidx.media3.common.MimeTypes
|
|
5
5
|
import androidx.media3.common.util.UnstableApi
|
|
6
6
|
import org.junit.Assert.assertEquals
|
|
7
|
+
import org.junit.Assert.assertNull
|
|
8
|
+
import org.junit.Rule
|
|
7
9
|
import org.junit.Test
|
|
10
|
+
import org.junit.rules.TemporaryFolder
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Pure-mapping coverage for [NowPlayingFormatExtractor]. These
|
|
@@ -14,6 +17,48 @@ import org.junit.Test
|
|
|
14
17
|
@UnstableApi
|
|
15
18
|
class NowPlayingFormatExtractorTest {
|
|
16
19
|
|
|
20
|
+
@get:Rule
|
|
21
|
+
val tempFolder = TemporaryFolder()
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* FLAC declares no bitrate, so Media3 reports NO_VALUE for it on local files
|
|
25
|
+
* as well as remote. For a file on disk the average is arithmetic — encoded
|
|
26
|
+
* bytes over playing time — which is the same derivation iOS uses.
|
|
27
|
+
*/
|
|
28
|
+
@Test
|
|
29
|
+
fun averageBitrateIsBytesOverPlayingTime() {
|
|
30
|
+
val file = tempFolder.newFile("track.flac")
|
|
31
|
+
file.writeBytes(ByteArray(1_000_000))
|
|
32
|
+
|
|
33
|
+
// 1,000,000 bytes over 40 s = 8,000,000 bits / 40 = 200 kbps.
|
|
34
|
+
assertEquals(
|
|
35
|
+
200_000.0,
|
|
36
|
+
NowPlayingFormatExtractor.averageBitrateOf(file.absolutePath, 40.0)!!,
|
|
37
|
+
0.001
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Every input that would make the answer invented rather than measured
|
|
43
|
+
* yields null: the player has not resolved a duration yet, the duration is
|
|
44
|
+
* nonsense, the path is absent, or the file has no bytes.
|
|
45
|
+
*/
|
|
46
|
+
@Test
|
|
47
|
+
fun averageBitrateIsNullWhenItCannotBeMeasured() {
|
|
48
|
+
val file = tempFolder.newFile("track.flac")
|
|
49
|
+
file.writeBytes(ByteArray(1_000))
|
|
50
|
+
|
|
51
|
+
assertNull(NowPlayingFormatExtractor.averageBitrateOf(file.absolutePath, null))
|
|
52
|
+
assertNull(NowPlayingFormatExtractor.averageBitrateOf(file.absolutePath, 0.0))
|
|
53
|
+
assertNull(NowPlayingFormatExtractor.averageBitrateOf(file.absolutePath, -5.0))
|
|
54
|
+
assertNull(NowPlayingFormatExtractor.averageBitrateOf(null, 40.0))
|
|
55
|
+
assertNull(
|
|
56
|
+
NowPlayingFormatExtractor.averageBitrateOf(
|
|
57
|
+
tempFolder.newFile("empty.flac").absolutePath, 40.0))
|
|
58
|
+
assertNull(
|
|
59
|
+
NowPlayingFormatExtractor.averageBitrateOf("/nonexistent/nope.flac", 40.0))
|
|
60
|
+
}
|
|
61
|
+
|
|
17
62
|
@Test
|
|
18
63
|
fun mapCodecCoversAACAliases() {
|
|
19
64
|
assertEquals(AudioCodec.AAC, NowPlayingFormatExtractor.mapCodec(MimeTypes.AUDIO_AAC))
|
package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt
CHANGED
|
@@ -60,7 +60,7 @@ class PitchAwareAudioProcessorChainTest {
|
|
|
60
60
|
|
|
61
61
|
@Test
|
|
62
62
|
fun `signalsmith stays active on the float path even when not stretching`() {
|
|
63
|
-
//
|
|
63
|
+
// Media3's pipeline is static: Signalsmith must remain in the
|
|
64
64
|
// active set regardless of mode/speed so it can stretch when music kicks in.
|
|
65
65
|
val chain = newConfiguredChain()
|
|
66
66
|
chain.setMode(PitchCorrectionMode.VOICE)
|
package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt
CHANGED
|
@@ -7,8 +7,16 @@ import androidx.media3.session.LibraryResult
|
|
|
7
7
|
import androidx.media3.session.MediaLibraryService
|
|
8
8
|
import androidx.media3.session.MediaSession
|
|
9
9
|
import androidx.test.core.app.ApplicationProvider
|
|
10
|
+
import java.util.concurrent.CountDownLatch
|
|
11
|
+
import java.util.concurrent.TimeUnit
|
|
12
|
+
import java.util.concurrent.atomic.AtomicInteger
|
|
13
|
+
import kotlinx.coroutines.Dispatchers
|
|
14
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
15
|
+
import kotlinx.coroutines.test.resetMain
|
|
16
|
+
import kotlinx.coroutines.test.setMain
|
|
10
17
|
import org.junit.After
|
|
11
18
|
import org.junit.Assert.assertEquals
|
|
19
|
+
import org.junit.Assert.assertFalse
|
|
12
20
|
import org.junit.Assert.assertNull
|
|
13
21
|
import org.junit.Assert.assertTrue
|
|
14
22
|
import org.junit.Before
|
|
@@ -167,4 +175,43 @@ class PlaybackServiceCallbackSearchTest {
|
|
|
167
175
|
result.params
|
|
168
176
|
)
|
|
169
177
|
}
|
|
178
|
+
|
|
179
|
+
// --- dispatchSearchToJs
|
|
180
|
+
|
|
181
|
+
// `Dispatchers.setMain` is the only way to see which dispatcher the notify
|
|
182
|
+
// lands on; `notifySearchResultChanged` has no other observable effect with
|
|
183
|
+
// no controller connected.
|
|
184
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
185
|
+
@Test
|
|
186
|
+
fun `dispatchSearchToJs caches the answer then notifies on main`() {
|
|
187
|
+
// A Nitro Promise is JNI-constructed and cannot exist on the JVM, so
|
|
188
|
+
// the only JS answer reachable here is a throwing handler; that path
|
|
189
|
+
// still writes the cache and still notifies, which is the contract a
|
|
190
|
+
// connected controller depends on to stop waiting.
|
|
191
|
+
val mainDispatches = AtomicInteger(0)
|
|
192
|
+
val dispatched = CountDownLatch(1)
|
|
193
|
+
val recording = object : kotlinx.coroutines.CoroutineDispatcher() {
|
|
194
|
+
override fun dispatch(context: kotlin.coroutines.CoroutineContext, block: Runnable) {
|
|
195
|
+
mainDispatches.incrementAndGet()
|
|
196
|
+
dispatched.countDown()
|
|
197
|
+
android.os.Handler(android.os.Looper.getMainLooper()).post(block)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
Dispatchers.setMain(recording)
|
|
201
|
+
try {
|
|
202
|
+
service.browseCallbacks.registerOnSearchRequest { _ ->
|
|
203
|
+
throw IllegalStateException("no Nitro runtime under Robolectric")
|
|
204
|
+
}
|
|
205
|
+
assertFalse(service.searchResultCache.hasResults("rock"))
|
|
206
|
+
|
|
207
|
+
service.dispatchSearchToJs("rock")
|
|
208
|
+
|
|
209
|
+
assertTrue("the notify reached a dispatcher", dispatched.await(2, TimeUnit.SECONDS))
|
|
210
|
+
org.robolectric.Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
|
211
|
+
assertEquals("notify is dispatched to main", 1, mainDispatches.get())
|
|
212
|
+
assertEquals(emptyList<BrowseItem>(), service.searchResultCache.getResults("rock"))
|
|
213
|
+
} finally {
|
|
214
|
+
Dispatchers.resetMain()
|
|
215
|
+
}
|
|
216
|
+
}
|
|
170
217
|
}
|
|
@@ -259,19 +259,6 @@ class PlaybackServiceCallbackTest {
|
|
|
259
259
|
assertTrue(commands.contains(Player.COMMAND_SET_SPEED_AND_PITCH))
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
@Test
|
|
263
|
-
fun `refreshAvailableCommands is a no-op when no handler is registered`() {
|
|
264
|
-
// No handler, no controllers — exercising the early return.
|
|
265
|
-
binder.refreshAvailableCommands()
|
|
266
|
-
// Re-issue with a handler but no connected controllers — also a no-op.
|
|
267
|
-
binder.setCommandHandler(RecordingHandler())
|
|
268
|
-
binder.refreshAvailableCommands()
|
|
269
|
-
// The per-controller iteration body (session.setAvailableCommands)
|
|
270
|
-
// requires a live MediaController that has completed handshake,
|
|
271
|
-
// which is not reachable from Robolectric without standing up a
|
|
272
|
-
// full MediaController.Builder.buildAsync() flow.
|
|
273
|
-
}
|
|
274
|
-
|
|
275
262
|
// --- TrackPlayer-side handler implementation
|
|
276
263
|
|
|
277
264
|
@Test
|
|
@@ -291,8 +278,8 @@ class PlaybackServiceCallbackTest {
|
|
|
291
278
|
val player = newConfiguredPlayer()
|
|
292
279
|
try {
|
|
293
280
|
player.tracks = listOf(
|
|
294
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
295
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
281
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
282
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
296
283
|
)
|
|
297
284
|
player.currentTrackIndex = 0
|
|
298
285
|
|
|
@@ -326,8 +313,8 @@ class PlaybackServiceCallbackTest {
|
|
|
326
313
|
|
|
327
314
|
// Two-track queue at index 0 — skip-next now allowed.
|
|
328
315
|
player.tracks = listOf(
|
|
329
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
330
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
316
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
317
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
331
318
|
)
|
|
332
319
|
player.currentTrackIndex = 0
|
|
333
320
|
player.recomputeCapabilities()
|
|
@@ -352,8 +339,8 @@ class PlaybackServiceCallbackTest {
|
|
|
352
339
|
val player = newConfiguredPlayer()
|
|
353
340
|
try {
|
|
354
341
|
player.tracks = listOf(
|
|
355
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
356
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
342
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
343
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
357
344
|
)
|
|
358
345
|
player.currentTrackIndex = 1
|
|
359
346
|
|
|
@@ -377,9 +364,9 @@ class PlaybackServiceCallbackTest {
|
|
|
377
364
|
val player = newConfiguredPlayer()
|
|
378
365
|
try {
|
|
379
366
|
player.tracks = listOf(
|
|
380
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
381
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
382
|
-
TrackItem("c", "https://example.com/c.m4a", null, null, null, null, null, null),
|
|
367
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
368
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
369
|
+
TrackItem("c", "https://example.com/c.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
383
370
|
)
|
|
384
371
|
player.currentTrackIndex = 0
|
|
385
372
|
|
|
@@ -404,8 +391,8 @@ class PlaybackServiceCallbackTest {
|
|
|
404
391
|
val player = newConfiguredPlayer()
|
|
405
392
|
try {
|
|
406
393
|
player.tracks = listOf(
|
|
407
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
408
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
394
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
395
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
409
396
|
)
|
|
410
397
|
player.currentTrackIndex = 1
|
|
411
398
|
|
|
@@ -426,8 +413,8 @@ class PlaybackServiceCallbackTest {
|
|
|
426
413
|
val player = newConfiguredPlayer()
|
|
427
414
|
try {
|
|
428
415
|
player.tracks = listOf(
|
|
429
|
-
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null),
|
|
430
|
-
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null),
|
|
416
|
+
TrackItem("a", "https://example.com/a.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
417
|
+
TrackItem("b", "https://example.com/b.m4a", null, null, null, null, null, null, null, null, null, null),
|
|
431
418
|
)
|
|
432
419
|
player.currentTrackIndex = 1
|
|
433
420
|
|
|
@@ -444,6 +431,35 @@ class PlaybackServiceCallbackTest {
|
|
|
444
431
|
|
|
445
432
|
// --- Voice / browse-leaf routing through onAddMediaItems / onSetMediaItems
|
|
446
433
|
|
|
434
|
+
@Test
|
|
435
|
+
fun `onAddMediaItems asks for append and onSetMediaItems does not`() {
|
|
436
|
+
val fakeEnv = RecordingEnvironment(service)
|
|
437
|
+
val routingCallback = PlaybackServiceCallback(fakeEnv)
|
|
438
|
+
fakeEnv.idResolution = listOf(
|
|
439
|
+
MediaItem.Builder().setMediaId("resolved")
|
|
440
|
+
.setUri("https://example.com/resolved.m4a".toUri()).build()
|
|
441
|
+
)
|
|
442
|
+
fun idOnlyItem() = MediaItem.Builder().setMediaId("album:1").build()
|
|
443
|
+
|
|
444
|
+
routingCallback.onAddMediaItems(
|
|
445
|
+
session, controllerInfo, mutableListOf(idOnlyItem())
|
|
446
|
+
).get()
|
|
447
|
+
// "Queue up X" grows the queue; Media3 appends what the resolution
|
|
448
|
+
// returns, so the lib's own queue has to grow by the same amount rather
|
|
449
|
+
// than being replaced by the new tracks alone.
|
|
450
|
+
assertEquals(listOf(true), fakeEnv.idAppendFlags)
|
|
451
|
+
|
|
452
|
+
fakeEnv.idAppendFlags.clear()
|
|
453
|
+
routingCallback.onSetMediaItems(
|
|
454
|
+
session, controllerInfo, mutableListOf(idOnlyItem()), 0, 0L
|
|
455
|
+
).get()
|
|
456
|
+
// A browse-leaf tap is a genuine replace: it takes the no-sync route,
|
|
457
|
+
// where the JS handler's own `setQueue` is the only writer, so no
|
|
458
|
+
// append-flagged request reaches the environment at all.
|
|
459
|
+
assertEquals(listOf("album:1"), fakeEnv.idRequestsNoSync)
|
|
460
|
+
assertEquals(emptyList<Boolean>(), fakeEnv.idAppendFlags)
|
|
461
|
+
}
|
|
462
|
+
|
|
447
463
|
@Test
|
|
448
464
|
fun `onAddMediaItems with searchQuery dispatches to onPlayFromSearchRequest`() {
|
|
449
465
|
val fakeEnv = RecordingEnvironment(service)
|
|
@@ -783,7 +799,8 @@ class PlaybackServiceCallbackTest {
|
|
|
783
799
|
networkTimeoutMs = null,
|
|
784
800
|
audioContentType = null,
|
|
785
801
|
audioCategoryOptions = null,
|
|
786
|
-
|
|
802
|
+
voiceVocabularyDonationEnabled = null,
|
|
803
|
+
visualizationEnabled = null,
|
|
787
804
|
clampSeekToBuffered = null,
|
|
788
805
|
lookaheadCacheMaxSizeMb = null, lookaheadCacheEvictionPolicy = null, progressUpdateIntervalMs = null, backgroundProgressUpdateIntervalMs = null, placeholderArtworkUri = null, skipToPreviousBehavior = null,
|
|
789
806
|
),
|
|
@@ -878,19 +895,26 @@ class PlaybackServiceCallbackTest {
|
|
|
878
895
|
service.playerCommandsFor(capability)
|
|
879
896
|
|
|
880
897
|
override fun onPlayFromSearchRequest(
|
|
881
|
-
request: MediaSearchRequest
|
|
898
|
+
request: MediaSearchRequest,
|
|
899
|
+
append: Boolean
|
|
882
900
|
): ListenableFuture<List<MediaItem>> {
|
|
883
901
|
searchRequests += request
|
|
902
|
+
searchAppendFlags += append
|
|
884
903
|
return Futures.immediateFuture(searchResolution)
|
|
885
904
|
}
|
|
886
905
|
|
|
887
906
|
override fun onPlayFromIdRequest(
|
|
888
|
-
mediaId: String
|
|
907
|
+
mediaId: String,
|
|
908
|
+
append: Boolean
|
|
889
909
|
): ListenableFuture<List<MediaItem>> {
|
|
890
910
|
idRequests += mediaId
|
|
911
|
+
idAppendFlags += append
|
|
891
912
|
return Futures.immediateFuture(idResolution)
|
|
892
913
|
}
|
|
893
914
|
|
|
915
|
+
val searchAppendFlags = mutableListOf<Boolean>()
|
|
916
|
+
val idAppendFlags = mutableListOf<Boolean>()
|
|
917
|
+
|
|
894
918
|
override fun onPlayFromIdRequestNoSync(mediaId: String) {
|
|
895
919
|
idRequestsNoSync += mediaId
|
|
896
920
|
}
|
|
@@ -49,19 +49,19 @@ class PlaybackServiceLifecycleTest {
|
|
|
49
49
|
val controller = Robolectric.buildService(PlaybackService::class.java).create()
|
|
50
50
|
try {
|
|
51
51
|
val binder = bindLocal(controller.get())
|
|
52
|
-
assertNotNull("
|
|
52
|
+
assertNotNull("the engine's player is initialised in onCreate", binder.engine.mediaSessionPlayer)
|
|
53
53
|
assertNotNull("binder.session is initialised in onCreate", binder.session)
|
|
54
54
|
// The session binds to the stable SessionCommandForwardingPlayer
|
|
55
55
|
// wrapper for its lifetime; the wrapper forwards to the engine's
|
|
56
|
-
// ExoPlayer
|
|
56
|
+
// own ExoPlayer.
|
|
57
57
|
val sessionPlayer = binder.session.player
|
|
58
58
|
assertTrue(
|
|
59
59
|
"binder.session.player is the stable forwarding wrapper",
|
|
60
60
|
sessionPlayer is SessionCommandForwardingPlayer,
|
|
61
61
|
)
|
|
62
62
|
assertSame(
|
|
63
|
-
"the wrapper forwards to the ExoPlayer instance
|
|
64
|
-
binder.
|
|
63
|
+
"the wrapper forwards to the engine's ExoPlayer instance",
|
|
64
|
+
binder.engine.mediaSessionPlayer,
|
|
65
65
|
(sessionPlayer as SessionCommandForwardingPlayer).wrappedPlayer,
|
|
66
66
|
)
|
|
67
67
|
assertEquals(
|
|
@@ -173,8 +173,8 @@ class PlaybackServiceLifecycleTest {
|
|
|
173
173
|
val binder = bindLocal(service)
|
|
174
174
|
// A loaded + play-ready session: the previous behaviour kept the service
|
|
175
175
|
// alive here. Swipe-away is now an unconditional kill.
|
|
176
|
-
binder.
|
|
177
|
-
binder.
|
|
176
|
+
binder.engine.mediaSessionPlayer.addMediaItem(MediaItem.fromUri("http://example.com/a.mp3"))
|
|
177
|
+
binder.engine.mediaSessionPlayer.playWhenReady = true
|
|
178
178
|
shadowOf(Looper.getMainLooper()).idle()
|
|
179
179
|
|
|
180
180
|
service.onTaskRemoved(null)
|
|
@@ -183,7 +183,7 @@ class PlaybackServiceLifecycleTest {
|
|
|
183
183
|
assertEquals(
|
|
184
184
|
"the player is stopped (idle) on swipe-away",
|
|
185
185
|
Player.STATE_IDLE,
|
|
186
|
-
binder.
|
|
186
|
+
binder.engine.mediaSessionPlayer.playbackState,
|
|
187
187
|
)
|
|
188
188
|
assertTrue(
|
|
189
189
|
"the service stops even with a queued, play-ready session",
|
|
@@ -198,7 +198,7 @@ class PlaybackServiceLifecycleTest {
|
|
|
198
198
|
fun `onDestroy releases the player and clears the session`() {
|
|
199
199
|
val controller = Robolectric.buildService(PlaybackService::class.java).create()
|
|
200
200
|
val binder = bindLocal(controller.get())
|
|
201
|
-
val player = binder.
|
|
201
|
+
val player = binder.engine.mediaSessionPlayer
|
|
202
202
|
val session: MediaSession = binder.session
|
|
203
203
|
assertFalse("player is live before destroy", player.isReleased)
|
|
204
204
|
|
|
@@ -134,7 +134,7 @@ class QueueSkipArithmeticTest {
|
|
|
134
134
|
@Test
|
|
135
135
|
fun `computePrevious OFF — at index 0 returns null (no wrap)`() {
|
|
136
136
|
// The "tap Previous at track 0 restarts current" UX behaviour
|
|
137
|
-
// is applied by
|
|
137
|
+
// is applied by `skipToPrevious`, not here.
|
|
138
138
|
assertNull(
|
|
139
139
|
QueueSkipArithmetic.computePrevious(5, 0, QueueSkipRepeatMode.OFF)
|
|
140
140
|
)
|
|
@@ -443,4 +443,36 @@ class QueueSkipArithmeticTest {
|
|
|
443
443
|
assertFalse(QueueSkipArithmetic.canSkipPrevious(3, 0, QueueSkipRepeatMode.OFF, restartEnabled = false))
|
|
444
444
|
assertTrue(QueueSkipArithmetic.canSkipPrevious(3, 1, QueueSkipRepeatMode.OFF, restartEnabled = false))
|
|
445
445
|
}
|
|
446
|
+
|
|
447
|
+
@Test
|
|
448
|
+
fun `canSkipPrevious restartEnabled — nothing playing is false in any mode`() {
|
|
449
|
+
// Nothing is loaded, so there is no track to restart and no previous
|
|
450
|
+
// track to reach: resolveSkipToPrevious returns NoOp for this state.
|
|
451
|
+
for (mode in QueueSkipRepeatMode.entries) {
|
|
452
|
+
assertFalse(QueueSkipArithmetic.canSkipPrevious(5, -1, mode, restartEnabled = true))
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
@Test
|
|
457
|
+
fun `canSkipPrevious agrees with resolveSkipToPrevious`() {
|
|
458
|
+
// canSkipPrevious is the enabled-state of the same decision
|
|
459
|
+
// resolveSkipToPrevious makes; the two must never disagree.
|
|
460
|
+
for (count in listOf(0, 1, 3)) {
|
|
461
|
+
for (index in listOf(-1, 0, 1, 2)) {
|
|
462
|
+
for (mode in QueueSkipRepeatMode.entries) {
|
|
463
|
+
for (restart in listOf(false, true)) {
|
|
464
|
+
val can = QueueSkipArithmetic.canSkipPrevious(count, index, mode, restart)
|
|
465
|
+
val resolved = QueueSkipArithmetic.resolveSkipToPrevious(
|
|
466
|
+
count, index, mode, restart, positionMs = 0
|
|
467
|
+
)
|
|
468
|
+
assertEquals(
|
|
469
|
+
"count=$count index=$index mode=$mode restart=$restart",
|
|
470
|
+
can,
|
|
471
|
+
resolved != QueueSkipArithmetic.SkipToPreviousAction.NoOp
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
446
478
|
}
|