react-native-queue-player 1.2.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/consumer-rules.pro +1 -1
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +3 -19
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +8 -69
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +111 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +13 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +985 -276
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +84 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaResponseCheckTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +122 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +672 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +229 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaResponseCheck.swift +122 -0
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MediaResponseCheckTests.swift +128 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +418 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +441 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +206 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +1042 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +982 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +951 -0
- package/ios/TrackPlayer+Recovery.swift +213 -0
- package/ios/TrackPlayer+Skip.swift +465 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +272 -0
- package/ios/TrackPlayer+Window.swift +227 -0
- package/ios/TrackPlayer.swift +331 -4619
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -222,14 +222,10 @@ class Visualizer : HybridVisualizerSpec() {
|
|
|
222
222
|
* fftSize=2048 should expect proportionally higher GC pressure on
|
|
223
223
|
* the audio render thread.
|
|
224
224
|
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* emulator backend — observed empirically: post-FFT magnitudes
|
|
230
|
-
* surface as all-zero on the JS side, even though `bb.getFloat(0)`
|
|
231
|
-
* reads back the right value on the Kotlin side. The explicit
|
|
232
|
-
* putFloat loop sidesteps the whole class of issue.
|
|
225
|
+
* Writes with `putFloat` rather than `bb.asFloatBuffer().put(values)`:
|
|
226
|
+
* the view form delivered all-zero buffers to JS through
|
|
227
|
+
* `ArrayBuffer.wrap` while `bb.getFloat(0)` read back correctly in
|
|
228
|
+
* Kotlin. Cause not established.
|
|
233
229
|
*/
|
|
234
230
|
private fun floatArrayToArrayBuffer(values: FloatArray): ArrayBuffer {
|
|
235
231
|
val bb = ByteBuffer.allocateDirect(values.size * 4).order(ByteOrder.nativeOrder())
|
|
@@ -6,9 +6,10 @@ package com.margelo.nitro.queueplayer.cast
|
|
|
6
6
|
* `ChromecastBackend` for Cast, future DLNA / Sonos similarly. The
|
|
7
7
|
* core module never knows which backends are present.
|
|
8
8
|
*
|
|
9
|
-
* The interface is intentionally tiny. Anything richer
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* The interface is intentionally tiny. Anything richer lives on the
|
|
10
|
+
* concrete backend or on the [CastSession] subtype that [connect]
|
|
11
|
+
* returns — volume on the session, the AirPlay 2 pairing-PIN flow on
|
|
12
|
+
* `AirPlayBackend.submitPairingPin`. Keeps the registry pluggable + the
|
|
12
13
|
* routing layer protocol-agnostic.
|
|
13
14
|
*/
|
|
14
15
|
interface CastBackend {
|
|
@@ -61,9 +61,10 @@ sealed class CastSession {
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Optional `Player` implementation for `MediaSession`. When
|
|
64
|
-
* non-null, `
|
|
65
|
-
* cast connect and back
|
|
66
|
-
* `RemoteCastPlayer` from
|
|
64
|
+
* non-null, `PlaybackService` retargets the session's forwarding
|
|
65
|
+
* player to this on cast connect and back to the local engine on
|
|
66
|
+
* disconnect. Chromecast returns a `RemoteCastPlayer` from
|
|
67
|
+
* `media3-cast`. Default null.
|
|
67
68
|
*/
|
|
68
69
|
open val mediaSessionPlayer: Player? = null
|
|
69
70
|
|
|
@@ -126,7 +127,7 @@ sealed class CastSession {
|
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* `true` once the receiver has reported any state other than
|
|
129
|
-
* [RemotePlaybackState.IDLE].
|
|
130
|
+
* [RemotePlaybackState.IDLE]. Set by the first such report and stays
|
|
130
131
|
* set for the session's lifetime.
|
|
131
132
|
*
|
|
132
133
|
* State reads use this to decide whether the receiver can answer yet: a
|
|
@@ -91,10 +91,8 @@ object AirPlay2JNI {
|
|
|
91
91
|
|
|
92
92
|
// -- Pairing --
|
|
93
93
|
|
|
94
|
-
@JvmStatic external fun nativeAp2NeedsPairing(handle: Long): Boolean
|
|
95
94
|
@JvmStatic external fun nativeAp2PairPinStart(handle: Long): Boolean
|
|
96
95
|
@JvmStatic external fun nativeAp2PairSetup(handle: Long, credential: String): String?
|
|
97
|
-
@JvmStatic external fun nativeAp2GetPairingState(handle: Long): Int
|
|
98
96
|
@JvmStatic external fun nativeAp2GetAuthMode(handle: Long): Int
|
|
99
97
|
@JvmStatic external fun nativeAp2GetDeviceId(handle: Long): String?
|
|
100
98
|
@JvmStatic external fun nativeAp2SetPassword(handle: Long, password: String)
|
|
@@ -77,7 +77,7 @@ class AirPlay2Session(
|
|
|
77
77
|
/**
|
|
78
78
|
* Invoked from the native drain thread (via [AirPlay2JNI.onNativeSessionDied])
|
|
79
79
|
* when the receiver has stopped responding. Notifies any [deathListener]
|
|
80
|
-
* so
|
|
80
|
+
* so `CastManager` can revert routing to local, then runs the normal close
|
|
81
81
|
* path — native TEARDOWN is skipped because the socket is dead.
|
|
82
82
|
*/
|
|
83
83
|
private fun onRemoteDied(reason: Int) {
|
|
@@ -100,7 +100,9 @@ class AirPlay2Session(
|
|
|
100
100
|
AirPlay2JNI.unregisterSession(handle)
|
|
101
101
|
|
|
102
102
|
/* Native TEARDOWN can block on a dead socket — never run it on the
|
|
103
|
-
* caller's thread (
|
|
103
|
+
* caller's thread (`CastManager.disconnect()` on the JS thread, a
|
|
104
|
+
* replacing `connect()` or `submitPairingPin()` on `Dispatchers.IO`,
|
|
105
|
+
* `onRemoteDied` on the native drain thread). */
|
|
104
106
|
val h = handle
|
|
105
107
|
teardownScope.launch {
|
|
106
108
|
try {
|
|
@@ -97,22 +97,28 @@ internal class AirPlayBackend(private val context: Context) : CastBackend {
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Complete a deferred AP2 pairing by supplying the PIN / password the
|
|
100
|
-
* receiver demanded. Returns the
|
|
101
|
-
* pairing failure or when no pending pair
|
|
100
|
+
* receiver demanded. Returns the session and the receiver it was
|
|
101
|
+
* paired with; throws on pairing failure or when no pending pair
|
|
102
|
+
* exists.
|
|
102
103
|
*
|
|
103
104
|
* Callers use this when `CastManager.submitPairingPin(pin)` is invoked
|
|
104
105
|
* out-of-band of a follow-up `connect(receiverId, {password})` call.
|
|
105
106
|
*/
|
|
106
|
-
suspend fun submitPairingPin(pin: String):
|
|
107
|
+
suspend fun submitPairingPin(pin: String): PairedSession = withContext(Dispatchers.IO) {
|
|
107
108
|
val pending = synchronized(this@AirPlayBackend) { pendingPairState }
|
|
108
109
|
?: throw IllegalStateException("No pending AirPlay 2 pairing")
|
|
109
110
|
val receiver = discovery.findReceiver(pending.receiverId)
|
|
110
111
|
?: throw IllegalStateException(
|
|
111
112
|
"Receiver ${pending.receiverId} no longer visible — restart the connect flow"
|
|
112
113
|
)
|
|
114
|
+
// resumeAp2Pairing reports the id of the pair it consumed and
|
|
115
|
+
// validated against `receiver`; the read above only locates it.
|
|
113
116
|
resumeAp2Pairing(receiver, pin)
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
/** A session established by [submitPairingPin] and the receiver it was paired with. */
|
|
120
|
+
data class PairedSession(val session: CastSession, val receiverId: String)
|
|
121
|
+
|
|
116
122
|
/**
|
|
117
123
|
* Persistent store for AirPlay 2 pair-setup auth keys + passwords,
|
|
118
124
|
* keyed by the receiver's mDNS `deviceid` (MAC address).
|
|
@@ -306,7 +312,7 @@ internal class AirPlayBackend(private val context: Context) : CastBackend {
|
|
|
306
312
|
|
|
307
313
|
// --- Phase 2: credential supplied → resume pending pairing ---
|
|
308
314
|
if (credential != null) {
|
|
309
|
-
return resumeAp2Pairing(receiver, credential)
|
|
315
|
+
return resumeAp2Pairing(receiver, credential).session
|
|
310
316
|
}
|
|
311
317
|
|
|
312
318
|
// --- Phase 1: initial connection attempt ---
|
|
@@ -423,11 +429,12 @@ internal class AirPlayBackend(private val context: Context) : CastBackend {
|
|
|
423
429
|
}
|
|
424
430
|
|
|
425
431
|
/**
|
|
426
|
-
* Resume a pending AP2 pairing with user-provided credential
|
|
427
|
-
*
|
|
428
|
-
* [
|
|
432
|
+
* Resume a pending AP2 pairing with user-provided credential, from
|
|
433
|
+
* either `connect()` re-invoked with a password or [submitPairingPin].
|
|
434
|
+
* Consumes the pending pair, validates it is for [receiver], and
|
|
435
|
+
* returns the session together with that receiver's id.
|
|
429
436
|
*/
|
|
430
|
-
private fun resumeAp2Pairing(receiver: Receiver, credential: String):
|
|
437
|
+
private fun resumeAp2Pairing(receiver: Receiver, credential: String): PairedSession {
|
|
431
438
|
val pending = synchronized(this) {
|
|
432
439
|
val p = pendingPairState
|
|
433
440
|
pendingPairState = null
|
|
@@ -506,7 +513,7 @@ internal class AirPlayBackend(private val context: Context) : CastBackend {
|
|
|
506
513
|
}
|
|
507
514
|
|
|
508
515
|
Log.i(TAG, "Connected AP2 to ${receiver.name} via pair-verify")
|
|
509
|
-
return AirPlay2Session(context, newHandle, host, port)
|
|
516
|
+
return PairedSession(AirPlay2Session(context, newHandle, host, port), pending.receiverId)
|
|
510
517
|
}
|
|
511
518
|
|
|
512
519
|
/** Destroy any stale pending pairing handle. */
|
package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt
CHANGED
|
@@ -59,7 +59,7 @@ internal class AirPlayMetadataSync(
|
|
|
59
59
|
@Volatile
|
|
60
60
|
private var started = false
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// Set on stop and never cleared, so a restart cannot half-revive a spent instance: the
|
|
63
63
|
// scope is already cancelled, so the listener would still push metadata
|
|
64
64
|
// while the progress loop and artwork fetch stay dead.
|
|
65
65
|
@Volatile
|
package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt
CHANGED
|
@@ -10,12 +10,12 @@ import androidx.media3.exoplayer.audio.AudioSink
|
|
|
10
10
|
* [AudioSink] IS the AirPlay session's sink ([AirPlay2Sink] / [AirPlaySink]).
|
|
11
11
|
*
|
|
12
12
|
* Deliberately a bare [DefaultRenderersFactory]: no `setEnableAudioFloatOutput`,
|
|
13
|
-
* no audio processors. The AirPlay sinks
|
|
14
|
-
* `ENCODING_PCM_16BIT`, and the default factory
|
|
15
|
-
* renderer negotiates a format the sink accepts
|
|
16
|
-
* receiver clean.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* no audio processors, no extension-renderer preference. The AirPlay sinks
|
|
14
|
+
* only accept `AUDIO_RAW` + `ENCODING_PCM_16BIT`, and the default factory
|
|
15
|
+
* decodes to Int16 PCM — so the renderer negotiates a format the sink accepts
|
|
16
|
+
* and the PCM reaches the receiver clean. Float output or a processor chain
|
|
17
|
+
* in front of the sink corrupts the AirPlay stream, which is why this engine
|
|
18
|
+
* has its own factory instead of sharing the local pipeline's.
|
|
19
19
|
*
|
|
20
20
|
* One sink per factory instance — the AirPlay engine is built fresh for each
|
|
21
21
|
* session and released when the session ends, so the sink is never swapped at
|
|
@@ -27,14 +27,6 @@ internal class AirPlayRenderersFactory(
|
|
|
27
27
|
private val airplaySink: AudioSink,
|
|
28
28
|
) : DefaultRenderersFactory(context) {
|
|
29
29
|
|
|
30
|
-
init {
|
|
31
|
-
// Match the known-good fork AirPlay config: prefer extension decoders.
|
|
32
|
-
// No-op when no extension renderers are bundled, but it keeps the
|
|
33
|
-
// decoder-selection behaviour identical to the reference that streams
|
|
34
|
-
// clean PCM to these sinks.
|
|
35
|
-
setExtensionRendererMode(EXTENSION_RENDERER_MODE_PREFER)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
30
|
override fun buildAudioSink(
|
|
39
31
|
context: Context,
|
|
40
32
|
enableFloatOutput: Boolean,
|
|
@@ -40,11 +40,6 @@ object AirplayJNI {
|
|
|
40
40
|
false
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// -- Library info --
|
|
44
|
-
|
|
45
|
-
@JvmStatic external fun nativeVersion(): String
|
|
46
|
-
@JvmStatic external fun nativeInitSsl(): Boolean
|
|
47
|
-
|
|
48
43
|
// -- Session lifecycle --
|
|
49
44
|
|
|
50
45
|
/**
|
|
@@ -73,10 +68,8 @@ object AirplayJNI {
|
|
|
73
68
|
/** Bytes waiting in the ring buffer, not yet sent. Used for hasPendingData(). */
|
|
74
69
|
@JvmStatic external fun nativeRingAvailable(handle: Long): Int
|
|
75
70
|
|
|
76
|
-
// --
|
|
71
|
+
// -- Transport control --
|
|
77
72
|
|
|
78
|
-
@JvmStatic external fun nativeAcceptFrames(handle: Long): Boolean
|
|
79
|
-
@JvmStatic external fun nativeSendChunk(handle: Long, data: ByteArray, offset: Int, length: Int): Boolean
|
|
80
73
|
@JvmStatic external fun nativePause(handle: Long)
|
|
81
74
|
@JvmStatic external fun nativeStop(handle: Long)
|
|
82
75
|
@JvmStatic external fun nativeFlush(handle: Long): Boolean
|
|
@@ -91,16 +84,11 @@ object AirplayJNI {
|
|
|
91
84
|
|
|
92
85
|
@JvmStatic external fun nativeSetVolume(handle: Long, volume: Float): Boolean
|
|
93
86
|
@JvmStatic external fun nativeSetProgressMs(handle: Long, elapsed: Int, duration: Int): Boolean
|
|
94
|
-
@JvmStatic external fun nativeGetProgressMs(handle: Long): Long
|
|
95
87
|
@JvmStatic external fun nativeSetDaap(handle: Long, title: String?, artist: String?, album: String?): Boolean
|
|
96
88
|
@JvmStatic external fun nativeSetArtwork(handle: Long, imageData: ByteArray, length: Int, contentType: String): Boolean
|
|
97
89
|
|
|
98
90
|
// -- State queries --
|
|
99
91
|
|
|
100
92
|
@JvmStatic external fun nativeIsConnected(handle: Long): Boolean
|
|
101
|
-
@JvmStatic external fun nativeIsPlaying(handle: Long): Boolean
|
|
102
|
-
@JvmStatic external fun nativeGetState(handle: Long): Int
|
|
103
|
-
@JvmStatic external fun nativeGetLatency(handle: Long): Int
|
|
104
|
-
@JvmStatic external fun nativeGetSampleRate(handle: Long): Int
|
|
105
93
|
@JvmStatic external fun nativeKeepalive(handle: Long): Boolean
|
|
106
94
|
}
|
package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt
CHANGED
|
@@ -14,9 +14,9 @@ import com.margelo.nitro.queueplayer.CastReceiver
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Chromecast backend — owns discovery via MediaRouter and session
|
|
17
|
-
* lifecycle via GMS SessionManager
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* lifecycle via GMS SessionManager, plus the transport calls that
|
|
18
|
+
* address the SDK session directly. Queue load and transport-state
|
|
19
|
+
* translation live in [ChromecastSession] and [RemoteMediaCallbacks].
|
|
20
20
|
*/
|
|
21
21
|
internal class ChromecastBackend(
|
|
22
22
|
private val context: Context,
|
|
@@ -32,7 +32,7 @@ import kotlinx.coroutines.runBlocking
|
|
|
32
32
|
*
|
|
33
33
|
* Binds to `0.0.0.0` on an OS-assigned port. Range / HEAD /
|
|
34
34
|
* Content-Length fall out of Ktor's `LocalFileContent` + `PartialContent`
|
|
35
|
-
* plugin. Refcounted via `acquire(sessionId)` / `release()`.
|
|
35
|
+
* plugin. Refcounted via `acquire(sessionId, context)` / `release(sessionId)`.
|
|
36
36
|
*/
|
|
37
37
|
class LocalMediaServer {
|
|
38
38
|
private val lock = Any()
|
|
@@ -51,38 +51,26 @@ class LocalMediaServer {
|
|
|
51
51
|
var serverPort: Int = -1
|
|
52
52
|
private set
|
|
53
53
|
|
|
54
|
-
@Volatile
|
|
55
|
-
var lifecycleListener: LocalMediaServerLifecycle? = null
|
|
56
|
-
|
|
57
54
|
@Volatile
|
|
58
55
|
private var addressMonitor: LocalAddressMonitor? = null
|
|
59
56
|
|
|
60
57
|
val currentLanAddress: String?
|
|
61
58
|
get() = addressMonitor?.currentAddress
|
|
62
59
|
|
|
63
|
-
fun attachAddressMonitor(monitor: LocalAddressMonitor) {
|
|
64
|
-
addressMonitor = monitor
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fun addAddressChangeListener(
|
|
68
|
-
listener: LocalAddressMonitor.Listener,
|
|
69
|
-
): LocalAddressMonitor.Subscription? = addressMonitor?.addListener(listener)
|
|
70
|
-
|
|
71
60
|
fun acquire(sessionId: String, context: Context): MediaServerHandle {
|
|
72
61
|
val first = refCount.getAndIncrement() == 0
|
|
73
62
|
if (first) {
|
|
74
63
|
ensureStarted()
|
|
75
64
|
ensureAddressMonitor(context)
|
|
76
|
-
lifecycleListener?.onActiveStateChanged(true)
|
|
77
65
|
}
|
|
78
66
|
return MediaServerHandle(this, sessionId)
|
|
79
67
|
}
|
|
80
68
|
|
|
81
69
|
// Lazily create + start the LAN-address monitor the first time the
|
|
82
70
|
// server is acquired. `start()` seeds `currentAddress` synchronously
|
|
83
|
-
//
|
|
84
|
-
// first `currentLanAddress` read (the cast handoff loads
|
|
85
|
-
// on connect, before any async network callback
|
|
71
|
+
// when the active network is Wi-Fi, so the address is available to
|
|
72
|
+
// the very first `currentLanAddress` read (the cast handoff loads
|
|
73
|
+
// immediately on connect, before any async network callback lands).
|
|
86
74
|
private fun ensureAddressMonitor(context: Context) {
|
|
87
75
|
synchronized(lock) {
|
|
88
76
|
if (addressMonitor != null) return
|
|
@@ -93,29 +81,21 @@ class LocalMediaServer {
|
|
|
93
81
|
internal fun release(sessionId: String) {
|
|
94
82
|
registry.revokeAll(sessionId)
|
|
95
83
|
val last = refCount.decrementAndGet() == 0
|
|
96
|
-
if (last)
|
|
97
|
-
stop()
|
|
98
|
-
lifecycleListener?.onActiveStateChanged(false)
|
|
99
|
-
}
|
|
84
|
+
if (last) stop()
|
|
100
85
|
}
|
|
101
86
|
|
|
102
87
|
internal fun register(sessionId: String, file: File, contentType: String?): String =
|
|
103
88
|
registry.register(sessionId, file, contentType)
|
|
104
89
|
|
|
105
|
-
internal fun revoke(token: String) = registry.revoke(token)
|
|
106
|
-
|
|
107
|
-
internal fun resolve(token: String): RegisteredMedia? = registry.resolve(token)
|
|
108
|
-
|
|
109
90
|
private fun ensureStarted() {
|
|
110
91
|
synchronized(lock) {
|
|
111
92
|
if (engine != null) return
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// unauthorized callers cannot enumerate served media.
|
|
93
|
+
// Listen on every interface; the URL host selects the Wi-Fi one.
|
|
94
|
+
// URLs handed back to receivers go through
|
|
95
|
+
// `MediaServerHandle.register(file, lanAddress, contentType)` with the
|
|
96
|
+
// host from `currentLanAddress` (`LocalAddressMonitor.currentAddress`,
|
|
97
|
+
// the Wi-Fi network's address). Tokens are 32 hex chars (high
|
|
98
|
+
// entropy); unauthorized callers cannot enumerate served media.
|
|
119
99
|
val started = embeddedServer(CIO, port = 0, host = "0.0.0.0") {
|
|
120
100
|
install(PartialContent)
|
|
121
101
|
install(AutoHeadResponse)
|
|
@@ -153,8 +133,4 @@ class LocalMediaServer {
|
|
|
153
133
|
addressMonitor = null
|
|
154
134
|
}
|
|
155
135
|
}
|
|
156
|
-
|
|
157
|
-
internal fun stopForTest() {
|
|
158
|
-
stop()
|
|
159
|
-
}
|
|
160
136
|
}
|
|
@@ -31,11 +31,6 @@ class MediaServerHandle internal constructor(
|
|
|
31
31
|
return "http://$lanAddress:$port/media/$token"
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
fun revoke(token: String) {
|
|
35
|
-
if (closed.get()) return
|
|
36
|
-
server.revoke(token)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
34
|
fun close() {
|
|
40
35
|
if (closed.compareAndSet(false, true)) {
|
|
41
36
|
server.release(sessionId)
|
|
@@ -22,16 +22,10 @@ internal class MediaTokenRegistry {
|
|
|
22
22
|
|
|
23
23
|
fun resolve(token: String): RegisteredMedia? = tokens[token]
|
|
24
24
|
|
|
25
|
-
fun revoke(token: String) {
|
|
26
|
-
tokens.remove(token)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
25
|
fun revokeAll(sessionId: String) {
|
|
30
26
|
val iter = tokens.entries.iterator()
|
|
31
27
|
while (iter.hasNext()) {
|
|
32
28
|
if (iter.next().value.sessionId == sessionId) iter.remove()
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
|
-
|
|
36
|
-
fun size(): Int = tokens.size
|
|
37
31
|
}
|
|
@@ -7,37 +7,38 @@ import android.net.Network
|
|
|
7
7
|
import android.net.NetworkCapabilities
|
|
8
8
|
import android.net.NetworkRequest
|
|
9
9
|
import java.net.Inet4Address
|
|
10
|
-
import java.util.concurrent.CopyOnWriteArrayList
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Surfaces the host's
|
|
14
|
-
*
|
|
15
|
-
* server's `addAddressChangeListener`.
|
|
12
|
+
* Surfaces the host's Wi-Fi IPv4 address. Owned by `LocalMediaServer`,
|
|
13
|
+
* which exposes it to backends as `currentLanAddress`.
|
|
16
14
|
*
|
|
17
15
|
* Detection via [ConnectivityManager.NetworkCallback] on a request for
|
|
18
|
-
* `TRANSPORT_WIFI
|
|
19
|
-
*
|
|
16
|
+
* `TRANSPORT_WIFI` (the request's default capabilities exclude VPNs), so
|
|
17
|
+
* only a Wi-Fi network's address is ever served, and a cellular network
|
|
18
|
+
* coming or going neither replaces nor clears it.
|
|
20
19
|
*/
|
|
21
20
|
class LocalAddressMonitor(private val context: Context) {
|
|
22
21
|
|
|
23
|
-
fun interface Listener {
|
|
24
|
-
fun onAddressChanged(newAddress: String?)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
private val listeners = CopyOnWriteArrayList<Listener>()
|
|
28
|
-
|
|
29
22
|
@Volatile
|
|
30
23
|
var currentAddress: String? = null
|
|
31
24
|
private set
|
|
32
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The network whose link properties supplied [currentAddress]. Two Wi-Fi
|
|
28
|
+
* networks overlap during a handover — the new one reports its link before
|
|
29
|
+
* the old one is lost — so a loss clears the address only when it is the
|
|
30
|
+
* network the address came from.
|
|
31
|
+
*/
|
|
32
|
+
private var addressNetwork: Network? = null
|
|
33
|
+
|
|
33
34
|
private var registered = false
|
|
34
35
|
private val callback = object : ConnectivityManager.NetworkCallback() {
|
|
35
36
|
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
|
|
36
|
-
updateAddress(linkProperties)
|
|
37
|
+
updateAddress(network, linkProperties)
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
override fun onLost(network: Network) {
|
|
40
|
-
updateAddress(null)
|
|
41
|
+
if (network == addressNetwork) updateAddress(null, null)
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -47,14 +48,23 @@ class LocalAddressMonitor(private val context: Context) {
|
|
|
47
48
|
?: return
|
|
48
49
|
val request = NetworkRequest.Builder()
|
|
49
50
|
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
|
50
|
-
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
|
51
51
|
.build()
|
|
52
52
|
cm.registerNetworkCallback(request, callback)
|
|
53
53
|
registered = true
|
|
54
|
-
// Seed synchronously
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
54
|
+
// Seed synchronously so `currentAddress` is populated immediately —
|
|
55
|
+
// the cast handoff reads it right after connect, before the first
|
|
56
|
+
// async onLinkPropertiesChanged lands. The active network is used
|
|
57
|
+
// only when it passes the request's own filter (Wi-Fi, not a VPN:
|
|
58
|
+
// a VPN over Wi-Fi carries the Wi-Fi transport but a tunnel address).
|
|
59
|
+
// When Wi-Fi is up but not the default network, the address arrives
|
|
60
|
+
// with that network's first onLinkPropertiesChanged instead.
|
|
61
|
+
val active = cm.activeNetwork ?: return
|
|
62
|
+
val caps = cm.getNetworkCapabilities(active) ?: return
|
|
63
|
+
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
|
|
64
|
+
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
|
65
|
+
) {
|
|
66
|
+
updateAddress(active, cm.getLinkProperties(active))
|
|
67
|
+
}
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
fun stop() {
|
|
@@ -65,22 +75,11 @@ class LocalAddressMonitor(private val context: Context) {
|
|
|
65
75
|
registered = false
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
fun
|
|
69
|
-
listeners.add(listener)
|
|
70
|
-
return Subscription { listeners.remove(listener) }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private fun updateAddress(linkProperties: LinkProperties?) {
|
|
78
|
+
private fun updateAddress(network: Network?, linkProperties: LinkProperties?) {
|
|
74
79
|
val ipv4 = linkProperties?.linkAddresses
|
|
75
80
|
?.firstOrNull { it.address is Inet4Address && !it.address.isLoopbackAddress }
|
|
76
81
|
?.address?.hostAddress
|
|
77
|
-
if (ipv4 !=
|
|
78
|
-
|
|
79
|
-
listeners.forEach { it.onAddressChanged(ipv4) }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
fun interface Subscription {
|
|
84
|
-
fun remove()
|
|
82
|
+
addressNetwork = if (ipv4 != null) network else null
|
|
83
|
+
currentAddress = ipv4
|
|
85
84
|
}
|
|
86
85
|
}
|
|
@@ -3,7 +3,7 @@ package androidx.media3.session
|
|
|
3
3
|
import androidx.media3.common.util.UnstableApi
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Test-only
|
|
6
|
+
* Test-only access to Media3's controller-request scope.
|
|
7
7
|
*
|
|
8
8
|
* `MediaSession.getControllerForCurrentRequest()` is non-null only for
|
|
9
9
|
* the duration of a controller-originated `Player` call, set by Media3
|
|
@@ -80,6 +80,8 @@ class AvrcpMetadataTest {
|
|
|
80
80
|
duration = null,
|
|
81
81
|
artworkUrl = null,
|
|
82
82
|
extras = null,
|
|
83
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
84
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
83
85
|
)
|
|
84
86
|
setQueueOnSession(listOf(track))
|
|
85
87
|
|
|
@@ -100,6 +102,8 @@ class AvrcpMetadataTest {
|
|
|
100
102
|
duration = null,
|
|
101
103
|
artworkUrl = "https://example.com/cover.jpg",
|
|
102
104
|
extras = null,
|
|
105
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
106
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
103
107
|
)
|
|
104
108
|
setQueueOnSession(listOf(track))
|
|
105
109
|
|
|
@@ -120,6 +124,8 @@ class AvrcpMetadataTest {
|
|
|
120
124
|
duration = 42.0,
|
|
121
125
|
artworkUrl = null,
|
|
122
126
|
extras = null,
|
|
127
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
128
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
123
129
|
)
|
|
124
130
|
setQueueOnSession(listOf(track))
|
|
125
131
|
|
|
@@ -137,6 +143,8 @@ class AvrcpMetadataTest {
|
|
|
137
143
|
duration = null,
|
|
138
144
|
artworkUrl = null,
|
|
139
145
|
extras = null,
|
|
146
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
147
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
140
148
|
)
|
|
141
149
|
val second = TrackItem(
|
|
142
150
|
id = "second",
|
|
@@ -147,6 +155,8 @@ class AvrcpMetadataTest {
|
|
|
147
155
|
duration = null,
|
|
148
156
|
artworkUrl = null,
|
|
149
157
|
extras = null,
|
|
158
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
159
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
150
160
|
)
|
|
151
161
|
|
|
152
162
|
setQueueOnSession(listOf(first))
|
|
@@ -170,6 +180,8 @@ class AvrcpMetadataTest {
|
|
|
170
180
|
duration = null,
|
|
171
181
|
artworkUrl = null,
|
|
172
182
|
extras = null,
|
|
183
|
+
replayGainTrackGain = null, replayGainTrackPeak = null,
|
|
184
|
+
replayGainAlbumGain = null, replayGainAlbumPeak = null,
|
|
173
185
|
)
|
|
174
186
|
setQueueOnSession(listOf(track))
|
|
175
187
|
assertEquals(1, session.player.mediaItemCount)
|