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
package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.queueplayer.cast.airplay
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.graphics.Bitmap
|
|
5
|
-
import android.graphics.BitmapFactory
|
|
6
|
-
import android.net.Uri
|
|
7
|
-
import android.os.Handler
|
|
8
|
-
import android.os.Looper
|
|
9
|
-
import android.util.Log
|
|
10
|
-
import androidx.media3.common.MediaItem
|
|
11
|
-
import androidx.media3.common.Player
|
|
12
|
-
import kotlinx.coroutines.*
|
|
13
|
-
import java.io.ByteArrayOutputStream
|
|
14
|
-
import java.net.URL
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Syncs ExoPlayer track metadata and playback progress to an [AirPlay2Session].
|
|
18
|
-
* Identical logic to [AirPlayMetadataSync] but targets the AP2 session.
|
|
19
|
-
*/
|
|
20
|
-
internal class AirPlay2MetadataSync(
|
|
21
|
-
private val context: Context,
|
|
22
|
-
private val session: AirPlay2Session,
|
|
23
|
-
private val player: Player,
|
|
24
|
-
private val scope: CoroutineScope,
|
|
25
|
-
) {
|
|
26
|
-
companion object {
|
|
27
|
-
private const val TAG = "AP2MetadataSync"
|
|
28
|
-
private const val PROGRESS_INTERVAL_MS = 5000L
|
|
29
|
-
private const val MAX_ARTWORK_DIM = 600
|
|
30
|
-
private const val JPEG_QUALITY = 80
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private var progressJob: Job? = null
|
|
34
|
-
private var artworkJob: Job? = null
|
|
35
|
-
private var lastArtworkUri: String? = null
|
|
36
|
-
private var started = false
|
|
37
|
-
|
|
38
|
-
// ExoPlayer rejects add/removeListener + state reads off its application
|
|
39
|
-
// looper. start()/stop() are driven by session activation/close, and
|
|
40
|
-
// close() runs on whichever thread disconnected (the JS thread for a
|
|
41
|
-
// user disconnect, the native drain thread on receiver death) — so the
|
|
42
|
-
// player touches are marshalled onto the player's looper, run inline when
|
|
43
|
-
// already on it.
|
|
44
|
-
private val playerHandler = Handler(player.applicationLooper)
|
|
45
|
-
|
|
46
|
-
private fun onPlayerLooper(block: () -> Unit) {
|
|
47
|
-
if (Looper.myLooper() == player.applicationLooper) block()
|
|
48
|
-
else playerHandler.post(block)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private val listener = object : Player.Listener {
|
|
52
|
-
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
53
|
-
sendCurrentMetadata()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
override fun onPositionDiscontinuity(
|
|
57
|
-
oldPosition: Player.PositionInfo,
|
|
58
|
-
newPosition: Player.PositionInfo,
|
|
59
|
-
reason: Int,
|
|
60
|
-
) {
|
|
61
|
-
if (reason == Player.DISCONTINUITY_REASON_SEEK) {
|
|
62
|
-
sendProgress()
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
67
|
-
if (playWhenReady) {
|
|
68
|
-
startProgressTimer()
|
|
69
|
-
} else {
|
|
70
|
-
progressJob?.cancel()
|
|
71
|
-
progressJob = null
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
fun start() {
|
|
77
|
-
if (started) return
|
|
78
|
-
started = true
|
|
79
|
-
onPlayerLooper {
|
|
80
|
-
player.addListener(listener)
|
|
81
|
-
sendCurrentMetadata()
|
|
82
|
-
if (player.playWhenReady) {
|
|
83
|
-
startProgressTimer()
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
Log.i(TAG, "Started metadata sync")
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
fun stop() {
|
|
90
|
-
if (!started) return
|
|
91
|
-
started = false
|
|
92
|
-
progressJob?.cancel()
|
|
93
|
-
progressJob = null
|
|
94
|
-
artworkJob?.cancel()
|
|
95
|
-
artworkJob = null
|
|
96
|
-
onPlayerLooper { player.removeListener(listener) }
|
|
97
|
-
Log.i(TAG, "Stopped metadata sync")
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private fun sendCurrentMetadata() {
|
|
101
|
-
val meta = player.currentMediaItem?.mediaMetadata ?: return
|
|
102
|
-
val title = meta.title?.toString()
|
|
103
|
-
val artist = meta.artist?.toString()
|
|
104
|
-
val album = meta.albumTitle?.toString()
|
|
105
|
-
|
|
106
|
-
Log.i(TAG, "Sending metadata: title=$title, artist=$artist, album=$album")
|
|
107
|
-
session.setMetadata(title, artist, album)
|
|
108
|
-
sendProgress()
|
|
109
|
-
|
|
110
|
-
val artUri = meta.artworkUri?.toString()
|
|
111
|
-
if (artUri != null && artUri != lastArtworkUri) {
|
|
112
|
-
lastArtworkUri = artUri
|
|
113
|
-
fetchAndSendArtwork(artUri)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private fun sendProgress() {
|
|
118
|
-
val position = player.currentPosition.toInt()
|
|
119
|
-
val duration = player.duration.let { if (it > 0) it.toInt() else 0 }
|
|
120
|
-
if (duration > 0) {
|
|
121
|
-
session.setProgress(position, duration)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private fun startProgressTimer() {
|
|
126
|
-
progressJob?.cancel()
|
|
127
|
-
progressJob = scope.launch {
|
|
128
|
-
while (isActive) {
|
|
129
|
-
delay(PROGRESS_INTERVAL_MS)
|
|
130
|
-
if (player.isPlaying) {
|
|
131
|
-
sendProgress()
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private fun fetchAndSendArtwork(uriString: String) {
|
|
138
|
-
artworkJob?.cancel()
|
|
139
|
-
artworkJob = scope.launch(Dispatchers.IO) {
|
|
140
|
-
try {
|
|
141
|
-
val uri = Uri.parse(uriString)
|
|
142
|
-
val scheme = uri.scheme ?: return@launch
|
|
143
|
-
|
|
144
|
-
// Read raw bytes — use ContentResolver for content:// and
|
|
145
|
-
// android.resource://, URL for http(s), file for file://
|
|
146
|
-
val rawBytes: ByteArray = when (scheme) {
|
|
147
|
-
"content", "android.resource" ->
|
|
148
|
-
context.contentResolver.openInputStream(uri)
|
|
149
|
-
?.use { it.readBytes() } ?: return@launch
|
|
150
|
-
"file" ->
|
|
151
|
-
java.io.File(uri.path ?: return@launch).readBytes()
|
|
152
|
-
"http", "https" ->
|
|
153
|
-
URL(uriString).openStream().use { it.readBytes() }
|
|
154
|
-
else -> {
|
|
155
|
-
Log.w(TAG, "Unsupported artwork scheme: $scheme")
|
|
156
|
-
return@launch
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
val boundsOpts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
|
161
|
-
BitmapFactory.decodeByteArray(rawBytes, 0, rawBytes.size, boundsOpts)
|
|
162
|
-
|
|
163
|
-
var sampleSize = 1
|
|
164
|
-
while (boundsOpts.outWidth / sampleSize > MAX_ARTWORK_DIM ||
|
|
165
|
-
boundsOpts.outHeight / sampleSize > MAX_ARTWORK_DIM) {
|
|
166
|
-
sampleSize *= 2
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
val decodeOpts = BitmapFactory.Options().apply { inSampleSize = sampleSize }
|
|
170
|
-
val bitmap = BitmapFactory.decodeByteArray(rawBytes, 0, rawBytes.size, decodeOpts)
|
|
171
|
-
?: return@launch
|
|
172
|
-
|
|
173
|
-
val baos = ByteArrayOutputStream()
|
|
174
|
-
bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos)
|
|
175
|
-
bitmap.recycle()
|
|
176
|
-
|
|
177
|
-
val bytes = baos.toByteArray()
|
|
178
|
-
if (isActive) {
|
|
179
|
-
session.setArtwork(bytes, "image/jpeg")
|
|
180
|
-
Log.i(TAG, "Sent artwork: ${bytes.size} bytes from $uriString")
|
|
181
|
-
}
|
|
182
|
-
} catch (e: Exception) {
|
|
183
|
-
if (e !is CancellationException) {
|
|
184
|
-
Log.w(TAG, "Artwork fetch failed for $uriString", e)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.queueplayer.cast.http
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Single-callback contract by which the host service learns whether
|
|
5
|
-
* the local HTTP server has any live sessions. While `active` is true
|
|
6
|
-
* the host must keep itself foregrounded so Android doesn't kill the
|
|
7
|
-
* process out from under the server.
|
|
8
|
-
*
|
|
9
|
-
* Cast-core stays unaware of foreground-service mechanics; the
|
|
10
|
-
* implementer translates this signal into `startForeground` /
|
|
11
|
-
* `stopForeground` etc.
|
|
12
|
-
*/
|
|
13
|
-
fun interface LocalMediaServerLifecycle {
|
|
14
|
-
fun onActiveStateChanged(active: Boolean)
|
|
15
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.queueplayer
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertEquals
|
|
4
|
-
import org.junit.Assert.assertFalse
|
|
5
|
-
import org.junit.Assert.assertNull
|
|
6
|
-
import org.junit.Assert.assertTrue
|
|
7
|
-
import org.junit.Test
|
|
8
|
-
import java.util.concurrent.CountDownLatch
|
|
9
|
-
import java.util.concurrent.Executors
|
|
10
|
-
import java.util.concurrent.TimeUnit
|
|
11
|
-
import java.util.concurrent.atomic.AtomicLong
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Coverage for the single-slot pending intent buffer.
|
|
15
|
-
* Drives push / drain / peek / clear / isEmpty directly with a
|
|
16
|
-
* synthetic payload so the generic shape is exercised without
|
|
17
|
-
* dragging Nitrogen-bridged request types into the test target.
|
|
18
|
-
*/
|
|
19
|
-
class PendingIntentBufferTest {
|
|
20
|
-
|
|
21
|
-
private data class Sample(val payload: String)
|
|
22
|
-
|
|
23
|
-
private class FakeClock(start: Long) {
|
|
24
|
-
private val now = AtomicLong(start)
|
|
25
|
-
fun nowMillis(): Long = now.get()
|
|
26
|
-
fun advance(byMillis: Long) {
|
|
27
|
-
now.addAndGet(byMillis)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private fun makeBuffer(
|
|
32
|
-
ttlMillis: Long = 20_000L,
|
|
33
|
-
clock: FakeClock,
|
|
34
|
-
): PendingIntentBuffer<Sample> = PendingIntentBuffer(ttlMillis = ttlMillis, clock = clock::nowMillis)
|
|
35
|
-
|
|
36
|
-
@Test
|
|
37
|
-
fun `empty buffer drains null`() {
|
|
38
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
39
|
-
assertNull(buffer.drain())
|
|
40
|
-
assertNull(buffer.peek())
|
|
41
|
-
assertTrue(buffer.isEmpty())
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
@Test
|
|
45
|
-
fun `push then drain returns the request`() {
|
|
46
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
47
|
-
buffer.push(Sample("first"))
|
|
48
|
-
assertEquals(Sample("first"), buffer.drain())
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@Test
|
|
52
|
-
fun `drain empties the buffer`() {
|
|
53
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
54
|
-
buffer.push(Sample("first"))
|
|
55
|
-
buffer.drain()
|
|
56
|
-
assertNull(buffer.drain())
|
|
57
|
-
assertTrue(buffer.isEmpty())
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@Test
|
|
61
|
-
fun `second push replaces the first`() {
|
|
62
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
63
|
-
buffer.push(Sample("first"))
|
|
64
|
-
buffer.push(Sample("second"))
|
|
65
|
-
assertEquals(Sample("second"), buffer.drain())
|
|
66
|
-
assertNull(buffer.drain())
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@Test
|
|
70
|
-
fun `peek does not empty the buffer`() {
|
|
71
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
72
|
-
buffer.push(Sample("first"))
|
|
73
|
-
assertEquals(Sample("first"), buffer.peek())
|
|
74
|
-
assertEquals(Sample("first"), buffer.peek())
|
|
75
|
-
assertEquals(Sample("first"), buffer.drain())
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
@Test
|
|
79
|
-
fun `expired entry drains null`() {
|
|
80
|
-
val clock = FakeClock(start = 1_000L)
|
|
81
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
82
|
-
buffer.push(Sample("stale"))
|
|
83
|
-
clock.advance(byMillis = 21_000L)
|
|
84
|
-
assertNull(buffer.drain())
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@Test
|
|
88
|
-
fun `expired entry peeks null and clears`() {
|
|
89
|
-
val clock = FakeClock(start = 1_000L)
|
|
90
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
91
|
-
buffer.push(Sample("stale"))
|
|
92
|
-
clock.advance(byMillis = 25_000L)
|
|
93
|
-
assertNull(buffer.peek())
|
|
94
|
-
assertTrue(buffer.isEmpty())
|
|
95
|
-
assertNull(buffer.drain())
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
@Test
|
|
99
|
-
fun `just before TTL boundary still drains`() {
|
|
100
|
-
val clock = FakeClock(start = 1_000L)
|
|
101
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
102
|
-
buffer.push(Sample("fresh"))
|
|
103
|
-
clock.advance(byMillis = 19_900L)
|
|
104
|
-
assertEquals(Sample("fresh"), buffer.drain())
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
@Test
|
|
108
|
-
fun `zero TTL same instant drains`() {
|
|
109
|
-
val clock = FakeClock(start = 1_000L)
|
|
110
|
-
val buffer = makeBuffer(ttlMillis = 0L, clock = clock)
|
|
111
|
-
buffer.push(Sample("now"))
|
|
112
|
-
assertEquals(Sample("now"), buffer.drain())
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@Test
|
|
116
|
-
fun `zero TTL after advance drops entry`() {
|
|
117
|
-
val clock = FakeClock(start = 1_000L)
|
|
118
|
-
val buffer = makeBuffer(ttlMillis = 0L, clock = clock)
|
|
119
|
-
buffer.push(Sample("now"))
|
|
120
|
-
clock.advance(byMillis = 1L)
|
|
121
|
-
assertNull(buffer.drain())
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@Test
|
|
125
|
-
fun `negative clock skew clamps to zero elapsed`() {
|
|
126
|
-
val clock = FakeClock(start = 1_000L)
|
|
127
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
128
|
-
buffer.push(Sample("fresh"))
|
|
129
|
-
clock.advance(byMillis = -10_000L)
|
|
130
|
-
assertEquals(Sample("fresh"), buffer.drain())
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@Test
|
|
134
|
-
fun `push after peek cleared expired slot returns fresh entry`() {
|
|
135
|
-
val clock = FakeClock(start = 1_000L)
|
|
136
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
137
|
-
buffer.push(Sample("stale"))
|
|
138
|
-
clock.advance(byMillis = 25_000L)
|
|
139
|
-
assertNull(buffer.peek())
|
|
140
|
-
assertTrue(buffer.isEmpty())
|
|
141
|
-
buffer.push(Sample("fresh"))
|
|
142
|
-
assertEquals(Sample("fresh"), buffer.drain())
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
@Test
|
|
146
|
-
fun `clear empties without return`() {
|
|
147
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
148
|
-
buffer.push(Sample("first"))
|
|
149
|
-
buffer.clear()
|
|
150
|
-
assertNull(buffer.drain())
|
|
151
|
-
assertTrue(buffer.isEmpty())
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
@Test
|
|
155
|
-
fun `interleaved push and drain serialise safely`() {
|
|
156
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
157
|
-
val executor = Executors.newFixedThreadPool(4)
|
|
158
|
-
val latch = CountDownLatch(400)
|
|
159
|
-
try {
|
|
160
|
-
repeat(200) { i ->
|
|
161
|
-
executor.submit {
|
|
162
|
-
buffer.push(Sample("p-$i"))
|
|
163
|
-
latch.countDown()
|
|
164
|
-
}
|
|
165
|
-
executor.submit {
|
|
166
|
-
buffer.drain()
|
|
167
|
-
latch.countDown()
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
val finished = latch.await(2, TimeUnit.SECONDS)
|
|
171
|
-
assertTrue("interleaved push/drain did not finish within 2 s", finished)
|
|
172
|
-
} finally {
|
|
173
|
-
executor.shutdownNow()
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
@Test
|
|
178
|
-
fun `isEmpty reports true after expiry without explicit drain`() {
|
|
179
|
-
val clock = FakeClock(start = 1_000L)
|
|
180
|
-
val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
|
|
181
|
-
buffer.push(Sample("stale"))
|
|
182
|
-
clock.advance(byMillis = 21_000L)
|
|
183
|
-
assertTrue(buffer.isEmpty())
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@Test
|
|
187
|
-
fun `isEmpty reports false when a fresh entry exists`() {
|
|
188
|
-
val buffer = PendingIntentBuffer<Sample>()
|
|
189
|
-
buffer.push(Sample("fresh"))
|
|
190
|
-
assertFalse(buffer.isEmpty())
|
|
191
|
-
}
|
|
192
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.queueplayer
|
|
2
|
-
|
|
3
|
-
import androidx.media3.common.MediaItem
|
|
4
|
-
import androidx.media3.common.PlaybackException
|
|
5
|
-
import androidx.media3.common.util.UnstableApi
|
|
6
|
-
import androidx.media3.exoplayer.ExoPlayer
|
|
7
|
-
import androidx.media3.exoplayer.audio.TeeAudioProcessor
|
|
8
|
-
import androidx.test.core.app.ApplicationProvider
|
|
9
|
-
import org.junit.runner.RunWith
|
|
10
|
-
import org.robolectric.RobolectricTestRunner
|
|
11
|
-
import org.robolectric.annotation.Config
|
|
12
|
-
import org.junit.Assert.assertEquals
|
|
13
|
-
import org.junit.Assert.assertNotNull
|
|
14
|
-
import org.junit.Test
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Type-check coverage for [PlaybackEngine] + [PlaybackEngineDelegate].
|
|
18
|
-
* The interface has no runtime; this suite exercises the type
|
|
19
|
-
* surface so a regression that drops a method or rotates a parameter
|
|
20
|
-
* shape surfaces immediately. Goes away once the real `GaplessEngine`
|
|
21
|
-
* impl pins the type surface from a runtime perspective.
|
|
22
|
-
*/
|
|
23
|
-
@RunWith(RobolectricTestRunner::class)
|
|
24
|
-
@Config(sdk = [34])
|
|
25
|
-
@UnstableApi
|
|
26
|
-
class PlaybackEngineInterfaceTest {
|
|
27
|
-
|
|
28
|
-
@Test
|
|
29
|
-
fun `PlaybackEngine surface compiles with all method signatures reachable`() {
|
|
30
|
-
val stub = NoopPlaybackEngine()
|
|
31
|
-
assertNotNull(stub)
|
|
32
|
-
// Empty-queue sentinel: -1 (matches TrackPlayer.currentTrackIndex).
|
|
33
|
-
assertEquals(-1, stub.currentMediaItemIndex)
|
|
34
|
-
assertEquals(0L, stub.currentPositionMs)
|
|
35
|
-
assertEquals(0L, stub.currentDurationMs)
|
|
36
|
-
assertEquals(0L, stub.bufferedPositionMs)
|
|
37
|
-
assertEquals(false, stub.isPlaying)
|
|
38
|
-
assertEquals(null, stub.currentMediaItem)
|
|
39
|
-
assertEquals(0, stub.allMediaItems.size)
|
|
40
|
-
assertEquals(0, stub.currentAudioSessionIds().size)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@Test
|
|
44
|
-
fun `audio-session-id listener add returns a disposer`() {
|
|
45
|
-
val stub = NoopPlaybackEngine()
|
|
46
|
-
val dispose = stub.addAudioSessionIdListener { /* noop */ }
|
|
47
|
-
dispose()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@Test
|
|
51
|
-
fun `delegate surface compiles with all callbacks`() {
|
|
52
|
-
val delegate = NoopPlaybackEngineDelegate()
|
|
53
|
-
val stub = NoopPlaybackEngine()
|
|
54
|
-
stub.delegate = delegate
|
|
55
|
-
assertEquals(delegate, stub.delegate)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Minimal compile-only stub. Every method is a no-op; the goal is
|
|
60
|
-
* to surface a type-system change in [PlaybackEngine] as a Kotlin
|
|
61
|
-
* compile error here, not as a silent contract drift.
|
|
62
|
-
*
|
|
63
|
-
* `mediaSessionPlayer` requires a real ExoPlayer instance for
|
|
64
|
-
* type compatibility — the stub builds one against the
|
|
65
|
-
* Robolectric application context but never plays anything.
|
|
66
|
-
*/
|
|
67
|
-
private class NoopPlaybackEngine : PlaybackEngine {
|
|
68
|
-
override val mediaSessionPlayer: ExoPlayer = ExoPlayer.Builder(
|
|
69
|
-
ApplicationProvider.getApplicationContext()
|
|
70
|
-
).build()
|
|
71
|
-
|
|
72
|
-
override suspend fun prepare(
|
|
73
|
-
mediaItems: List<MediaItem>,
|
|
74
|
-
startIndex: Int,
|
|
75
|
-
startPositionMs: Long,
|
|
76
|
-
) = Unit
|
|
77
|
-
|
|
78
|
-
override fun release() = Unit
|
|
79
|
-
|
|
80
|
-
override fun setMediaItems(
|
|
81
|
-
items: List<MediaItem>,
|
|
82
|
-
startIndex: Int,
|
|
83
|
-
startPositionMs: Long,
|
|
84
|
-
) = Unit
|
|
85
|
-
|
|
86
|
-
override fun addMediaItems(items: List<MediaItem>, insertBefore: Int) = Unit
|
|
87
|
-
override fun removeMediaItems(indices: IntArray) = Unit
|
|
88
|
-
override fun moveMediaItem(fromIndex: Int, toIndex: Int) = Unit
|
|
89
|
-
|
|
90
|
-
override fun play() = Unit
|
|
91
|
-
override fun pause() = Unit
|
|
92
|
-
override fun stop() = Unit
|
|
93
|
-
override fun seekTo(index: Int, positionMs: Long) = Unit
|
|
94
|
-
override fun skipToNext() = Unit
|
|
95
|
-
override fun skipToPrevious() = Unit
|
|
96
|
-
|
|
97
|
-
override fun setVolume(volume: Float) = Unit
|
|
98
|
-
override fun setPlaybackSpeed(speed: Float) = Unit
|
|
99
|
-
override fun setRepeatMode(mode: RepeatMode) = Unit
|
|
100
|
-
override fun setPitchCorrectionMode(mode: PitchCorrectionMode) = Unit
|
|
101
|
-
override fun setWakeMode(mode: Int) = Unit
|
|
102
|
-
|
|
103
|
-
override val currentMediaItemIndex: Int = -1
|
|
104
|
-
override val currentPositionMs: Long = 0
|
|
105
|
-
override val currentDurationMs: Long = 0
|
|
106
|
-
override val bufferedPositionMs: Long = 0
|
|
107
|
-
override val isPlaying: Boolean = false
|
|
108
|
-
override val currentMediaItem: MediaItem? = null
|
|
109
|
-
override val isCrossfadeActive: Boolean = false
|
|
110
|
-
override val allMediaItems: List<MediaItem> = emptyList()
|
|
111
|
-
|
|
112
|
-
override fun currentAudioSessionIds(): IntArray = IntArray(0)
|
|
113
|
-
|
|
114
|
-
override fun addAudioSessionIdListener(listener: (IntArray) -> Unit): () -> Unit = { /* noop */ }
|
|
115
|
-
|
|
116
|
-
override fun attachAudioBufferSink(sink: TeeAudioProcessor.AudioBufferSink) = Unit
|
|
117
|
-
override fun detachAudioBufferSink(sink: TeeAudioProcessor.AudioBufferSink) = Unit
|
|
118
|
-
|
|
119
|
-
override suspend fun handoff(to: PlaybackEngine, atTrackBoundary: Boolean) = Unit
|
|
120
|
-
|
|
121
|
-
override var delegate: PlaybackEngineDelegate? = null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private class NoopPlaybackEngineDelegate : PlaybackEngineDelegate {
|
|
125
|
-
override fun onStateMaybeChanged(engine: PlaybackEngine, playWhenReadyReason: Int?) = Unit
|
|
126
|
-
override fun onIsPlayingChanged(engine: PlaybackEngine, isPlaying: Boolean) = Unit
|
|
127
|
-
override fun onTrackTransition(engine: PlaybackEngine, platformReason: Int) = Unit
|
|
128
|
-
override fun onActiveItemFormatChanged(engine: PlaybackEngine) = Unit
|
|
129
|
-
override fun onError(engine: PlaybackEngine, exception: PlaybackException) = Unit
|
|
130
|
-
override fun onPlaybackEnded(engine: PlaybackEngine) = Unit
|
|
131
|
-
override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) = Unit
|
|
132
|
-
override fun onCrossfadeBegin(engine: PlaybackEngine, incomingIndex: Int) = Unit
|
|
133
|
-
override fun onCrossfadeCancel(engine: PlaybackEngine, revertedIndex: Int) = Unit
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.queueplayer
|
|
2
|
-
|
|
3
|
-
import org.junit.Assert.assertTrue
|
|
4
|
-
import org.junit.Test
|
|
5
|
-
import org.junit.runner.RunWith
|
|
6
|
-
import org.robolectric.RobolectricTestRunner
|
|
7
|
-
import org.robolectric.annotation.Config
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Proves the JUnit + Robolectric test target is
|
|
11
|
-
* wired + `./gradlew :react-native-queue-player:testDebugUnitTest`
|
|
12
|
-
* executes. Real test suites for the Kotlin audio wrappers
|
|
13
|
-
* (MediaItemBuilder, AudioFocus, PlaybackService, LookaheadCache,
|
|
14
|
-
* etc.) live alongside their wrapper sources.
|
|
15
|
-
*
|
|
16
|
-
* @Config sdk=34 because Robolectric 4.11.1 supports up to API 34,
|
|
17
|
-
* while our library's compileSdkVersion is 36. When Robolectric bumps
|
|
18
|
-
* its max-supported SDK to 36 we drop the annotation.
|
|
19
|
-
*/
|
|
20
|
-
@RunWith(RobolectricTestRunner::class)
|
|
21
|
-
@Config(sdk = [34])
|
|
22
|
-
class SmokeTest {
|
|
23
|
-
@Test
|
|
24
|
-
fun smoke_sanity() {
|
|
25
|
-
assertTrue("smoke", true)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import Network
|
|
3
|
-
|
|
4
|
-
/// Wraps `NWPathMonitor` to publish the local IPv4 address over the
|
|
5
|
-
/// `.wifi` interface. Cellular addresses can't reach a LAN receiver,
|
|
6
|
-
/// so they are intentionally ignored — Chromecast / DLNA / AirPlay
|
|
7
|
-
/// targets are all wifi-local by design.
|
|
8
|
-
final class LocalAddressMonitor {
|
|
9
|
-
|
|
10
|
-
typealias Listener = (String?) -> Void
|
|
11
|
-
|
|
12
|
-
final class Subscription {
|
|
13
|
-
fileprivate let id: UUID
|
|
14
|
-
fileprivate weak var owner: LocalAddressMonitor?
|
|
15
|
-
fileprivate init(id: UUID, owner: LocalAddressMonitor) {
|
|
16
|
-
self.id = id
|
|
17
|
-
self.owner = owner
|
|
18
|
-
}
|
|
19
|
-
func remove() { owner?.removeListener(id: id) }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private let monitor = NWPathMonitor(requiredInterfaceType: .wifi)
|
|
23
|
-
private let queue = DispatchQueue(label: "LocalAddressMonitor")
|
|
24
|
-
private let lock = NSLock()
|
|
25
|
-
private var listeners: [UUID: Listener] = [:]
|
|
26
|
-
private var running = false
|
|
27
|
-
|
|
28
|
-
private let addressLock = NSLock()
|
|
29
|
-
private var _currentAddress: String?
|
|
30
|
-
var currentAddress: String? {
|
|
31
|
-
addressLock.lock(); defer { addressLock.unlock() }
|
|
32
|
-
return _currentAddress
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func start() {
|
|
36
|
-
lock.lock()
|
|
37
|
-
if running { lock.unlock(); return }
|
|
38
|
-
running = true
|
|
39
|
-
lock.unlock()
|
|
40
|
-
monitor.pathUpdateHandler = { [weak self] path in
|
|
41
|
-
guard let self = self else { return }
|
|
42
|
-
let addr: String? = (path.status == .satisfied) ? Self.firstWifiIPv4() : nil
|
|
43
|
-
self.addressLock.lock()
|
|
44
|
-
let changed: Bool = (addr != self._currentAddress)
|
|
45
|
-
self._currentAddress = addr
|
|
46
|
-
self.addressLock.unlock()
|
|
47
|
-
if changed {
|
|
48
|
-
self.dispatch(addr)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
monitor.start(queue: queue)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
func stop() {
|
|
55
|
-
lock.lock()
|
|
56
|
-
if !running { lock.unlock(); return }
|
|
57
|
-
running = false
|
|
58
|
-
lock.unlock()
|
|
59
|
-
monitor.cancel()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func addListener(_ listener: @escaping Listener) -> Subscription {
|
|
63
|
-
let id = UUID()
|
|
64
|
-
lock.lock()
|
|
65
|
-
listeners[id] = listener
|
|
66
|
-
lock.unlock()
|
|
67
|
-
return Subscription(id: id, owner: self)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
fileprivate func removeListener(id: UUID) {
|
|
71
|
-
lock.lock()
|
|
72
|
-
listeners.removeValue(forKey: id)
|
|
73
|
-
lock.unlock()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private func dispatch(_ addr: String?) {
|
|
77
|
-
lock.lock()
|
|
78
|
-
let snapshot = Array(listeners.values)
|
|
79
|
-
lock.unlock()
|
|
80
|
-
for l in snapshot { l(addr) }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/// Enumerate BSD interfaces and return the first IPv4 address on an
|
|
84
|
-
/// `en*` interface (Wi-Fi). Synchronous so callers can probe without
|
|
85
|
-
/// waiting for the `NWPathMonitor` first-update callback.
|
|
86
|
-
static func firstWifiIPv4() -> String? {
|
|
87
|
-
var ifaddrPtr: UnsafeMutablePointer<ifaddrs>?
|
|
88
|
-
guard getifaddrs(&ifaddrPtr) == 0, let first = ifaddrPtr else { return nil }
|
|
89
|
-
defer { freeifaddrs(ifaddrPtr) }
|
|
90
|
-
var ptr: UnsafeMutablePointer<ifaddrs>? = first
|
|
91
|
-
while let current = ptr {
|
|
92
|
-
defer { ptr = current.pointee.ifa_next }
|
|
93
|
-
let name = String(cString: current.pointee.ifa_name)
|
|
94
|
-
guard name.hasPrefix("en") else { continue }
|
|
95
|
-
guard let sa = current.pointee.ifa_addr else { continue }
|
|
96
|
-
guard sa.pointee.sa_family == UInt8(AF_INET) else { continue }
|
|
97
|
-
var hostBuf = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
|
98
|
-
let result = getnameinfo(
|
|
99
|
-
sa, socklen_t(sa.pointee.sa_len),
|
|
100
|
-
&hostBuf, socklen_t(hostBuf.count),
|
|
101
|
-
nil, 0, NI_NUMERICHOST
|
|
102
|
-
)
|
|
103
|
-
if result == 0 {
|
|
104
|
-
let host = String(cString: hostBuf)
|
|
105
|
-
if !host.isEmpty && host != "0.0.0.0" { return host }
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return nil
|
|
109
|
-
}
|
|
110
|
-
}
|