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
package/QueuePlayer.podspec
CHANGED
|
@@ -10,7 +10,10 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.license = package["license"]
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# Declared explicitly rather than inherited from
|
|
14
|
+
# `min_ios_version_supported`, which resolves lower: AVPlayer and
|
|
15
|
+
# AVPlayerItem only drop their main-thread requirement at iOS 16.
|
|
16
|
+
s.platforms = { :ios => '16.0' }
|
|
14
17
|
s.source = { :git => "https://github.com/ghenry22/react-native-queue-player.git", :tag => "#{s.version}" }
|
|
15
18
|
|
|
16
19
|
# NOTE on cross-pod consumer-Swift access:
|
|
@@ -64,11 +67,11 @@ Pod::Spec.new do |s|
|
|
|
64
67
|
|
|
65
68
|
# Opt-in Chromecast subspec. Consumer Podfile adds:
|
|
66
69
|
# pod 'QueuePlayer/Chromecast', :path => '...'
|
|
67
|
-
# alongside the default `pod 'QueuePlayer'`.
|
|
68
|
-
# Google Cast SDK 4.8.x's
|
|
70
|
+
# alongside the default `pod 'QueuePlayer'`. The 16.0 floor tracks the
|
|
71
|
+
# library's own and sits above Google Cast SDK 4.8.x's iOS 15.0 minimum.
|
|
69
72
|
s.subspec 'Chromecast' do |ss|
|
|
70
73
|
ss.source_files = 'ios/Cast/Chromecast/**/*.swift'
|
|
71
|
-
ss.ios.deployment_target = '
|
|
74
|
+
ss.ios.deployment_target = '16.0'
|
|
72
75
|
ss.dependency 'google-cast-sdk', '~> 4.8.4'
|
|
73
76
|
end
|
|
74
77
|
|
|
@@ -87,11 +90,13 @@ Pod::Spec.new do |s|
|
|
|
87
90
|
s.dependency 'React-jsi'
|
|
88
91
|
s.dependency 'React-callinvoker'
|
|
89
92
|
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
93
|
+
# The shared local-HTTP media server (`ios/MediaServer/MediaHTTPConnection`)
|
|
94
|
+
# serves media over a native `Network.framework` listener — no third-party
|
|
95
|
+
# HTTP server dependency, no `:modular_headers` requirement on the consumer.
|
|
96
|
+
# Two servers use it: Cast's `LocalMediaServer` serves consumer-supplied
|
|
97
|
+
# `file://` URLs to pull-only receivers (Chromecast / future DLNA), and the
|
|
98
|
+
# lookahead cache's `ReadThroughServer` serves the local player from the
|
|
99
|
+
# cache or, on a miss, the origin.
|
|
95
100
|
|
|
96
101
|
load 'nitrogen/generated/ios/QueuePlayer+autolinking.rb'
|
|
97
102
|
add_nitrogen_files(s)
|
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ A complete audio playback library for React Native — gapless and crossfade eng
|
|
|
24
24
|
- **Remote-control skip mode** — choose track skip or interval skip (jump ±N seconds) for the lock screen / notification, adjustable at runtime.
|
|
25
25
|
- **Sleep timer** — pause after a duration or at the end of the current track, with a volume fade; runs on a native wall-clock deadline.
|
|
26
26
|
- **Now Playing format** — codec, container, sample rate, bitrate, and ReplayGain metadata for the active track.
|
|
27
|
-
- **React hooks** — `useProgress`, `usePlaybackState`, `useActiveTrack`, `useCanSkip`, `useSleepTimer`, `useEqualizer`, `useVisualizerState`, `useLookaheadCache`, `useNowPlayingFormat`, `useCast`, `useAudioRoute`.
|
|
27
|
+
- **React hooks** — `useProgress`, `usePlaybackState`, `useActiveTrack`, `useQueue`, `useCanSkip`, `useSleepTimer`, `useBufferState`, `useFullyBuffered`, `useEqualizer`, `useVisualizerState`, `useLookaheadCache`, `useNowPlayingFormat`, `useCast`, `useAudioRoute`.
|
|
28
28
|
|
|
29
29
|
## Requirements
|
|
30
30
|
|
|
31
31
|
- React Native with the New Architecture enabled.
|
|
32
32
|
- [`react-native-nitro-modules`](https://nitro.margelo.com/) (peer dependency).
|
|
33
|
-
- iOS
|
|
33
|
+
- iOS 16+ (Chromecast subspec included).
|
|
34
34
|
- Android `minSdk` 24+.
|
|
35
35
|
- Expo config-plugin support (the native setup is driven by the bundled Expo plugin).
|
|
36
36
|
|
|
@@ -107,7 +107,7 @@ The player is a singleton — `getTrackPlayer()` always returns the same instanc
|
|
|
107
107
|
|
|
108
108
|
Most native setup is automatic (Nitro autolinking + the Android manifest merge + the Expo plugin). A few things you must do by hand — see the [Setup & manual configuration guide](https://ghenry22.github.io/react-native-queue-player/docs/guides/manual-setup) for full detail:
|
|
109
109
|
|
|
110
|
-
- **iOS background audio** —
|
|
110
|
+
- **iOS background audio** — set automatically by the Expo plugin: it adds `audio` to `UIBackgroundModes` (on by default; pass `{ enableIosBGAudio: false }` only if your app declares `UIBackgroundModes` elsewhere). If you opt out, or use bare React Native (no plugin), set it yourself:
|
|
111
111
|
|
|
112
112
|
```json
|
|
113
113
|
{ "expo": { "ios": { "infoPlist": { "UIBackgroundModes": ["audio"] } } } }
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
# libraop's JNI_OnLoad binds this class by literal descriptor: src/main/cpp/
|
|
12
12
|
# airplay_jni.cpp calls FindClass on the string
|
|
13
|
-
# "com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI", then resolves all
|
|
13
|
+
# "com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI", then resolves all 19
|
|
14
14
|
# entries of its JNINativeMethod table by name and signature. React Native's own
|
|
15
15
|
# rules pin the native method names but let the class be renamed, and the rule
|
|
16
16
|
# that pins the class name ships only in AGP's optional default ProGuard file.
|
|
@@ -458,7 +458,7 @@ int ap2_record(ap2_session_t *session) {
|
|
|
458
458
|
/* ---- FLUSH ---- */
|
|
459
459
|
|
|
460
460
|
int ap2_flush(ap2_session_t *session) {
|
|
461
|
-
/* Build RTP-Info header per OwnTone airplay.c:
|
|
461
|
+
/* Build RTP-Info header per OwnTone airplay.c:
|
|
462
462
|
* "RTP-Info: seq=<seqnum>;rtptime=<rtptime>" */
|
|
463
463
|
char rtp_info[128];
|
|
464
464
|
snprintf(rtp_info, sizeof(rtp_info),
|
|
@@ -524,7 +524,7 @@ int ap2_set_metadata(ap2_session_t *session,
|
|
|
524
524
|
return -1;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
-
/* OwnTone airplay.c
|
|
527
|
+
/* OwnTone airplay.c — adds RTP-Info header. */
|
|
528
528
|
char rtp_info[128];
|
|
529
529
|
snprintf(rtp_info, sizeof(rtp_info),
|
|
530
530
|
"RTP-Info: rtptime=%u\r\n",
|
|
@@ -556,7 +556,7 @@ int ap2_set_artwork(ap2_session_t *session,
|
|
|
556
556
|
const char *content_type) {
|
|
557
557
|
if (!data || len == 0) return 0;
|
|
558
558
|
|
|
559
|
-
/* OwnTone airplay.c
|
|
559
|
+
/* OwnTone airplay.c — raw image body with format-specific CT. */
|
|
560
560
|
char rtp_info[128];
|
|
561
561
|
snprintf(rtp_info, sizeof(rtp_info),
|
|
562
562
|
"RTP-Info: rtptime=%u\r\n",
|
|
@@ -581,7 +581,7 @@ int ap2_set_artwork(ap2_session_t *session,
|
|
|
581
581
|
int ap2_set_progress(ap2_session_t *session,
|
|
582
582
|
uint32_t elapsed_ms, uint32_t duration_ms) {
|
|
583
583
|
/* Convert ms to RTP timestamps.
|
|
584
|
-
* Per OwnTone airplay.c
|
|
584
|
+
* Per OwnTone airplay.c — "progress: start/current/end". */
|
|
585
585
|
uint32_t rtp_start = session->rtp_timestamp;
|
|
586
586
|
uint32_t rtp_pos = rtp_start + (uint32_t)((uint64_t)elapsed_ms * session->sample_rate / 1000);
|
|
587
587
|
uint32_t rtp_end = rtp_start + (uint32_t)((uint64_t)duration_ms * session->sample_rate / 1000);
|
|
@@ -41,7 +41,7 @@ int ap2_record(ap2_session_t *session);
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* FLUSH: stop audio playback on the receiver.
|
|
44
|
-
* Sends RTP-Info header with current seq/rtptime per OwnTone airplay.c
|
|
44
|
+
* Sends RTP-Info header with current seq/rtptime per OwnTone airplay.c.
|
|
45
45
|
* @return 0 on success, -1 on failure
|
|
46
46
|
*/
|
|
47
47
|
int ap2_flush(ap2_session_t *session);
|
|
@@ -55,7 +55,7 @@ int ap2_set_volume(ap2_session_t *session, float vol_db);
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* SET_PARAMETER metadata (DMAP-encoded title/artist/album).
|
|
58
|
-
* Per OwnTone airplay.c
|
|
58
|
+
* Per OwnTone airplay.c.
|
|
59
59
|
* @return 0 on success, -1 on failure
|
|
60
60
|
*/
|
|
61
61
|
int ap2_set_metadata(ap2_session_t *session,
|
|
@@ -63,7 +63,7 @@ int ap2_set_metadata(ap2_session_t *session,
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* SET_PARAMETER artwork (raw JPEG/PNG image bytes).
|
|
66
|
-
* Per OwnTone airplay.c
|
|
66
|
+
* Per OwnTone airplay.c.
|
|
67
67
|
* @param content_type "image/jpeg" or "image/png"
|
|
68
68
|
* @return 0 on success, -1 on failure
|
|
69
69
|
*/
|
|
@@ -73,7 +73,7 @@ int ap2_set_artwork(ap2_session_t *session,
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* SET_PARAMETER progress (elapsed/duration in milliseconds).
|
|
76
|
-
* Per OwnTone airplay.c
|
|
76
|
+
* Per OwnTone airplay.c. Converts ms to RTP timestamps.
|
|
77
77
|
* @return 0 on success, -1 on failure
|
|
78
78
|
*/
|
|
79
79
|
int ap2_set_progress(ap2_session_t *session,
|
|
@@ -130,7 +130,7 @@ static jboolean nativeAp2Connect(JNIEnv *, jclass, jlong handle) {
|
|
|
130
130
|
* ap2_connect() calls ap2_rtsp_connect() internally. */
|
|
131
131
|
if (ap2_connect(s) != 0) {
|
|
132
132
|
/* If pairing required, keep TCP alive for subsequent pair-setup.
|
|
133
|
-
* Per OwnTone airplay.c
|
|
133
|
+
* Per OwnTone airplay.c — socket reused across auth. */
|
|
134
134
|
if (s->pairing_required) {
|
|
135
135
|
__android_log_print(ANDROID_LOG_INFO, TAG,
|
|
136
136
|
"Connect: pairing required (auth_mode=%d), keeping TCP alive",
|
|
@@ -204,11 +204,6 @@ static void nativeAp2Destroy(JNIEnv *, jclass, jlong handle) {
|
|
|
204
204
|
// Pairing
|
|
205
205
|
// ---------------------------------------------------------------------------
|
|
206
206
|
|
|
207
|
-
static jboolean nativeAp2NeedsPairing(JNIEnv *, jclass, jlong handle) {
|
|
208
|
-
auto *s = toSession(handle);
|
|
209
|
-
return (s && s->pairing_required && !s->has_auth_key) ? JNI_TRUE : JNI_FALSE;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
207
|
static jboolean nativeAp2PairPinStart(JNIEnv *, jclass, jlong handle) {
|
|
213
208
|
auto *s = toSession(handle);
|
|
214
209
|
if (!s) return JNI_FALSE;
|
|
@@ -270,11 +265,6 @@ static jstring nativeAp2PairSetup(JNIEnv *env, jclass,
|
|
|
270
265
|
return result;
|
|
271
266
|
}
|
|
272
267
|
|
|
273
|
-
static jint nativeAp2GetPairingState(JNIEnv *, jclass, jlong handle) {
|
|
274
|
-
auto *s = toSession(handle);
|
|
275
|
-
return s ? static_cast<jint>(s->state) : 0;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
268
|
static jint nativeAp2GetAuthMode(JNIEnv *, jclass, jlong handle) {
|
|
279
269
|
auto *s = toSession(handle);
|
|
280
270
|
return s ? static_cast<jint>(s->auth_mode) : 0;
|
|
@@ -341,7 +331,7 @@ static void nativeAp2SetVolume(JNIEnv *, jclass, jlong handle, jfloat vol) {
|
|
|
341
331
|
s->volume = vol;
|
|
342
332
|
/* Send volume over RTSP if streaming.
|
|
343
333
|
* Convert 0.0-1.0 linear to dB: 0→-144 (mute), 1→0 (full).
|
|
344
|
-
* Per OwnTone airplay.c
|
|
334
|
+
* Per OwnTone airplay.c — SET_PARAMETER text/parameters. */
|
|
345
335
|
if (s->state >= AP2_STATE_RECORD) {
|
|
346
336
|
float vol_db;
|
|
347
337
|
if (vol <= 0.0f) {
|
|
@@ -403,7 +393,7 @@ static void nativeAp2Pause(JNIEnv *, jclass, jlong handle) {
|
|
|
403
393
|
if (!s) return;
|
|
404
394
|
ap2_audio_pause(s);
|
|
405
395
|
/* Send FLUSH so receiver knows audio stopped.
|
|
406
|
-
* Per OwnTone airplay.c
|
|
396
|
+
* Per OwnTone airplay.c — FLUSH after stopping audio. */
|
|
407
397
|
if (s->state >= AP2_STATE_RECORD) {
|
|
408
398
|
ap2_flush(s);
|
|
409
399
|
}
|
|
@@ -466,18 +456,12 @@ static const JNINativeMethod sMethods[] = {
|
|
|
466
456
|
"(J)V",
|
|
467
457
|
reinterpret_cast<void *>(nativeAp2Destroy)},
|
|
468
458
|
/* Pairing */
|
|
469
|
-
{"nativeAp2NeedsPairing",
|
|
470
|
-
"(J)Z",
|
|
471
|
-
reinterpret_cast<void *>(nativeAp2NeedsPairing)},
|
|
472
459
|
{"nativeAp2PairPinStart",
|
|
473
460
|
"(J)Z",
|
|
474
461
|
reinterpret_cast<void *>(nativeAp2PairPinStart)},
|
|
475
462
|
{"nativeAp2PairSetup",
|
|
476
463
|
"(JLjava/lang/String;)Ljava/lang/String;",
|
|
477
464
|
reinterpret_cast<void *>(nativeAp2PairSetup)},
|
|
478
|
-
{"nativeAp2GetPairingState",
|
|
479
|
-
"(J)I",
|
|
480
|
-
reinterpret_cast<void *>(nativeAp2GetPairingState)},
|
|
481
465
|
{"nativeAp2GetAuthMode",
|
|
482
466
|
"(J)I",
|
|
483
467
|
reinterpret_cast<void *>(nativeAp2GetAuthMode)},
|
|
@@ -79,14 +79,14 @@ int ap2_probe_info(ap2_session_t *session) {
|
|
|
79
79
|
LOGI("/info: statusFlags=0x%llx", (unsigned long long)status_flags);
|
|
80
80
|
|
|
81
81
|
/* Store individual statusFlags bits for the auth decision tree.
|
|
82
|
-
* Per OwnTone airplay.c
|
|
82
|
+
* Per OwnTone airplay.c. */
|
|
83
83
|
session->one_time_pairing = (status_flags & ((uint64_t)1 << 9)) ? 1 : 0;
|
|
84
84
|
session->pin_required = (status_flags & ((uint64_t)1 << 3)) ? 1 : 0;
|
|
85
85
|
session->password_required = (status_flags & ((uint64_t)1 << 7)) ? 1 : 0;
|
|
86
86
|
LOGI("/info: one_time_pairing=%d pin_required=%d password_required=%d",
|
|
87
87
|
session->one_time_pairing, session->pin_required, session->password_required);
|
|
88
88
|
|
|
89
|
-
/* Decide auth mode. Per OwnTone airplay.c
|
|
89
|
+
/* Decide auth mode. Per OwnTone airplay.c and pyatv:
|
|
90
90
|
* ONE_TIME_PAIRING ALWAYS means PIN mode, even when PASSWORD_REQUIRED
|
|
91
91
|
* is also set. The PIN is displayed on the receiver; the password is
|
|
92
92
|
* a separate concern used for HTTP Digest auth on encrypted RTSP.
|
|
@@ -100,7 +100,7 @@ int ap2_probe_info(ap2_session_t *session) {
|
|
|
100
100
|
session->auth_mode = AP2_AUTH_PIN;
|
|
101
101
|
session->pairing_required = 1;
|
|
102
102
|
session->transient_ok = 0;
|
|
103
|
-
/* PIN_REQUIRED always clears auth_key — per OwnTone airplay.c
|
|
103
|
+
/* PIN_REQUIRED always clears auth_key — per OwnTone airplay.c. */
|
|
104
104
|
session->has_auth_key = 0;
|
|
105
105
|
} else if (session->password_required) {
|
|
106
106
|
session->auth_mode = AP2_AUTH_PASSWORD;
|
|
@@ -394,7 +394,7 @@ int ap2_pair_transient(ap2_session_t *session) {
|
|
|
394
394
|
|
|
395
395
|
int ap2_pair_pin_start(ap2_session_t *session) {
|
|
396
396
|
/* POST /pair-pin-start — triggers PIN display on receiver.
|
|
397
|
-
* Per OwnTone airplay.c
|
|
397
|
+
* Per OwnTone airplay.c — AIRPLAY_SEQ_PIN_START is a separate
|
|
398
398
|
* sequence from AIRPLAY_SEQ_PAIR_SETUP. */
|
|
399
399
|
ap2_rtsp_response_t resp;
|
|
400
400
|
if (ap2_rtsp_request(session, "POST", "/pair-pin-start",
|
|
@@ -563,7 +563,7 @@ int ap2_connect(ap2_session_t *session) {
|
|
|
563
563
|
/* Step 2: Probe /info — sets auth_mode, pairing_required, etc. */
|
|
564
564
|
if (ap2_probe_info(session) != 0) return -1;
|
|
565
565
|
|
|
566
|
-
/* Step 3: Pairing — per OwnTone airplay.c
|
|
566
|
+
/* Step 3: Pairing — per OwnTone airplay.c decision tree. */
|
|
567
567
|
|
|
568
568
|
if (session->auth_mode == AP2_AUTH_PIN) {
|
|
569
569
|
/* ONE_TIME_PAIRING or PIN_REQUIRED. */
|
|
@@ -588,7 +588,7 @@ int ap2_connect(ap2_session_t *session) {
|
|
|
588
588
|
session->has_auth_key = 0;
|
|
589
589
|
}
|
|
590
590
|
if (session->password[0]) {
|
|
591
|
-
/* Per OwnTone airplay.c
|
|
591
|
+
/* Per OwnTone airplay.c — when ONE_TIME_PAIRING is set,
|
|
592
592
|
* always call /pair-pin-start first to put the receiver in pairing
|
|
593
593
|
* mode before SRP. This is required even for password-protected
|
|
594
594
|
* receivers: without it the Mac may process the SRP exchange but
|
|
@@ -636,7 +636,7 @@ int ap2_connect(ap2_session_t *session) {
|
|
|
636
636
|
if (ret == 0) return 0;
|
|
637
637
|
|
|
638
638
|
/* Transient→normal fallback: if receiver rejected transient with 470,
|
|
639
|
-
* switch to PIN-based auth. Per OwnTone airplay.c
|
|
639
|
+
* switch to PIN-based auth. Per OwnTone airplay.c. */
|
|
640
640
|
if (session->state == AP2_STATE_PAIR) {
|
|
641
641
|
LOGI("Transient rejected — receiver requires PIN");
|
|
642
642
|
session->auth_mode = AP2_AUTH_PIN;
|
|
@@ -52,7 +52,7 @@ int ap2_pair_transient(ap2_session_t *session);
|
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* POST /pair-pin-start — trigger PIN display on receiver.
|
|
55
|
-
* Per OwnTone airplay.c
|
|
55
|
+
* Per OwnTone airplay.c — separate from pair-setup.
|
|
56
56
|
* Call this for PIN-based auth BEFORE ap2_pair_setup().
|
|
57
57
|
* Do NOT call this for password-based auth (password is known, not displayed).
|
|
58
58
|
*
|
|
@@ -65,7 +65,7 @@ int ap2_pair_pin_start(ap2_session_t *session);
|
|
|
65
65
|
* Generates and stores Ed25519 identity keys.
|
|
66
66
|
* The credential can be a 4-digit PIN (displayed on device) or an
|
|
67
67
|
* arbitrary password string — both use the same SRP mechanism.
|
|
68
|
-
* Per OwnTone pair_homekit.c
|
|
68
|
+
* Per OwnTone pair_homekit.c — strdup(pin) as SRP password.
|
|
69
69
|
*
|
|
70
70
|
* @param pin PIN or password string
|
|
71
71
|
* @param auth_key OUT: hex-encoded auth key for persistence (192 chars + NUL)
|
|
@@ -202,7 +202,7 @@ static int recv_decrypt_frame(ap2_session_t *s, uint8_t *out, size_t out_cap) {
|
|
|
202
202
|
return ret == 0 ? (int)frame_len : -1;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
/* ---- HTTP Digest auth (OwnTone airplay.c
|
|
205
|
+
/* ---- HTTP Digest auth (OwnTone airplay.c) ---- */
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
208
|
* Compute MD5 hex digest of the concatenated inputs separated by ':'.
|
|
@@ -229,7 +229,7 @@ static void md5_hex(const char *a, const char *b, const char *c, char out[33]) {
|
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
231
|
* Parse WWW-Authenticate: Digest realm="...", nonce="..." from response.
|
|
232
|
-
* OwnTone airplay.c
|
|
232
|
+
* OwnTone airplay.c.
|
|
233
233
|
*/
|
|
234
234
|
static int parse_www_authenticate(ap2_session_t *s, const char *resp_buf) {
|
|
235
235
|
const char *hdr = strstr(resp_buf, "WWW-Authenticate:");
|
|
@@ -279,13 +279,13 @@ static int parse_www_authenticate(ap2_session_t *s, const char *resp_buf) {
|
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
281
|
* Build Authorization: Digest ... header string.
|
|
282
|
-
* OwnTone airplay.c
|
|
282
|
+
* OwnTone airplay.c — HA1=MD5("":realm:password), HA2=MD5(method:uri).
|
|
283
283
|
* Returns heap-allocated string like "Authorization: Digest ...\r\n", or NULL.
|
|
284
284
|
*/
|
|
285
285
|
static char *build_digest_header(ap2_session_t *s, const char *method, const char *uri) {
|
|
286
286
|
if (!s->has_digest_auth || !s->password[0]) return NULL;
|
|
287
287
|
|
|
288
|
-
/* HA1 = MD5(username:realm:password) — username is empty per OwnTone
|
|
288
|
+
/* HA1 = MD5(username:realm:password) — username is empty per OwnTone */
|
|
289
289
|
char ha1[33];
|
|
290
290
|
md5_hex("", s->realm, s->password, ha1);
|
|
291
291
|
|
|
@@ -372,7 +372,7 @@ int ap2_rtsp_request_ex(ap2_session_t *session,
|
|
|
372
372
|
const char *dn = session->device_name[0] ? session->device_name : NULL;
|
|
373
373
|
|
|
374
374
|
/* Use session URL for RTSP methods, raw path for HTTP-style endpoints.
|
|
375
|
-
* Per OwnTone airplay.c
|
|
375
|
+
* Per OwnTone airplay.c — session_url for SETUP/RECORD/etc,
|
|
376
376
|
* raw path for /info, /pair-*, /feedback, /peer-list-changed. */
|
|
377
377
|
const char *uri = path;
|
|
378
378
|
if (strcmp(method, "SETUP") == 0 || strcmp(method, "RECORD") == 0 ||
|
|
@@ -382,7 +382,7 @@ int ap2_rtsp_request_ex(ap2_session_t *session,
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
/* Auto-include Digest Authorization on all requests after first 401.
|
|
385
|
-
* Per OwnTone airplay.c
|
|
385
|
+
* Per OwnTone airplay.c — auth_header_add on every request. */
|
|
386
386
|
char *digest_hdr = NULL;
|
|
387
387
|
char *merged_headers = NULL;
|
|
388
388
|
if (session->has_digest_auth) {
|
|
@@ -399,7 +399,7 @@ int ap2_rtsp_request_ex(ap2_session_t *session,
|
|
|
399
399
|
}
|
|
400
400
|
const char *all_extra = merged_headers ? merged_headers : "";
|
|
401
401
|
|
|
402
|
-
/* DACP-ID / Client-Instance / Active-Remote — per OwnTone airplay.c
|
|
402
|
+
/* DACP-ID / Client-Instance / Active-Remote — per OwnTone airplay.c.
|
|
403
403
|
* These let the receiver persistently identify this client. */
|
|
404
404
|
char dacp_hdr[192];
|
|
405
405
|
if (session->dacp_id[0]) {
|
|
@@ -562,7 +562,7 @@ int ap2_rtsp_request_ex(ap2_session_t *session,
|
|
|
562
562
|
}
|
|
563
563
|
|
|
564
564
|
/* 401 Unauthorized → parse Digest challenge and retry once.
|
|
565
|
-
* Per OwnTone airplay.c
|
|
565
|
+
* Per OwnTone airplay.c. Must happen before freeing resp_buf. */
|
|
566
566
|
if (resp->status == 401 && session->password[0] && !session->has_digest_auth) {
|
|
567
567
|
if (parse_www_authenticate(session, (char *)resp_buf) == 0) {
|
|
568
568
|
LOGI("Digest auth: realm=%s — retrying %s %s",
|
|
@@ -80,7 +80,7 @@ ap2_session_t *ap2_session_create(const char *host, int port,
|
|
|
80
80
|
pthread_mutex_init(&s->ring_mutex, NULL);
|
|
81
81
|
|
|
82
82
|
/* DACP-ID / Client-Instance — 16-char uppercase hex.
|
|
83
|
-
* Active-Remote — 32-bit decimal. Per OwnTone airplay.c
|
|
83
|
+
* Active-Remote — 32-bit decimal. Per OwnTone airplay.c. */
|
|
84
84
|
{
|
|
85
85
|
uint8_t id_bytes[8];
|
|
86
86
|
randombytes_buf(id_bytes, sizeof(id_bytes));
|
|
@@ -105,7 +105,7 @@ ap2_session_t *ap2_session_create(const char *host, int port,
|
|
|
105
105
|
/* PTP clock ID — random 64-bit identifier for timing sync. */
|
|
106
106
|
s->ptp_clock_id = ((uint64_t)rand() << 32) | (uint64_t)rand();
|
|
107
107
|
|
|
108
|
-
/* Random RTSP session ID — per OwnTone airplay.c
|
|
108
|
+
/* Random RTSP session ID — per OwnTone airplay.c.
|
|
109
109
|
* Used in the RTSP URL: rtsp://<host>/<session_id>. */
|
|
110
110
|
s->session_id = (uint32_t)rand();
|
|
111
111
|
snprintf(s->session_url, sizeof(s->session_url),
|
|
@@ -174,14 +174,6 @@ static struct raopcl_s *toRaop(jlong handle) {
|
|
|
174
174
|
// JNI methods
|
|
175
175
|
// ---------------------------------------------------------------------------
|
|
176
176
|
|
|
177
|
-
static jstring nativeVersion(JNIEnv *env, jclass) {
|
|
178
|
-
return env->NewStringUTF("libraop-0.2.0");
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
static jboolean nativeInitSsl(JNIEnv *, jclass) {
|
|
182
|
-
return cross_ssl_load() ? JNI_TRUE : JNI_FALSE;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
177
|
/**
|
|
186
178
|
* Open a RAOP session. Returns a handle (pointer-as-long) or 0 on failure.
|
|
187
179
|
*
|
|
@@ -356,34 +348,7 @@ static jint nativeRingAvailable(JNIEnv *, jclass, jlong handle) {
|
|
|
356
348
|
return static_cast<jint>(avail);
|
|
357
349
|
}
|
|
358
350
|
|
|
359
|
-
// --
|
|
360
|
-
|
|
361
|
-
static jboolean nativeAcceptFrames(JNIEnv *, jclass, jlong handle) {
|
|
362
|
-
auto *r = toRaop(handle);
|
|
363
|
-
return (r && raopcl_accept_frames(r)) ? JNI_TRUE : JNI_FALSE;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
static jboolean nativeSendChunk(JNIEnv *env, jclass,
|
|
367
|
-
jlong handle,
|
|
368
|
-
jbyteArray data, jint offset, jint length) {
|
|
369
|
-
auto *r = toRaop(handle);
|
|
370
|
-
if (!r) return JNI_FALSE;
|
|
371
|
-
|
|
372
|
-
jbyte *buf = env->GetByteArrayElements(data, nullptr);
|
|
373
|
-
if (!buf) return JNI_FALSE;
|
|
374
|
-
|
|
375
|
-
uint64_t playtime = 0;
|
|
376
|
-
// Fix: length is bytes, send_chunk expects frames (bytes / 4 for stereo S16)
|
|
377
|
-
bool ok = raopcl_send_chunk(
|
|
378
|
-
r,
|
|
379
|
-
reinterpret_cast<uint8_t *>(buf + offset),
|
|
380
|
-
static_cast<int>(length / 4),
|
|
381
|
-
&playtime
|
|
382
|
-
);
|
|
383
|
-
|
|
384
|
-
env->ReleaseByteArrayElements(data, buf, JNI_ABORT);
|
|
385
|
-
return ok ? JNI_TRUE : JNI_FALSE;
|
|
386
|
-
}
|
|
351
|
+
// -- Transport control --
|
|
387
352
|
|
|
388
353
|
static void nativePause(JNIEnv *, jclass, jlong handle) {
|
|
389
354
|
auto *r = toRaop(handle);
|
|
@@ -409,6 +374,8 @@ static jboolean nativeFlush(JNIEnv *, jclass, jlong handle) {
|
|
|
409
374
|
return (r && raopcl_flush(r)) ? JNI_TRUE : JNI_FALSE;
|
|
410
375
|
}
|
|
411
376
|
|
|
377
|
+
// -- Volume & progress --
|
|
378
|
+
|
|
412
379
|
static jboolean nativeSetVolume(JNIEnv *, jclass, jlong handle, jfloat vol) {
|
|
413
380
|
auto *r = toRaop(handle);
|
|
414
381
|
return (r && raopcl_set_volume(r, vol)) ? JNI_TRUE : JNI_FALSE;
|
|
@@ -422,41 +389,20 @@ static jboolean nativeSetProgressMs(JNIEnv *, jclass, jlong handle,
|
|
|
422
389
|
static_cast<uint32_t>(duration))) ? JNI_TRUE : JNI_FALSE;
|
|
423
390
|
}
|
|
424
391
|
|
|
425
|
-
|
|
426
|
-
auto *r = toRaop(handle);
|
|
427
|
-
return r ? static_cast<jlong>(raopcl_get_progress_ms(r)) : 0;
|
|
428
|
-
}
|
|
392
|
+
// -- State queries --
|
|
429
393
|
|
|
430
394
|
static jboolean nativeIsConnected(JNIEnv *, jclass, jlong handle) {
|
|
431
395
|
auto *r = toRaop(handle);
|
|
432
396
|
return (r && raopcl_is_connected(r)) ? JNI_TRUE : JNI_FALSE;
|
|
433
397
|
}
|
|
434
398
|
|
|
435
|
-
static jboolean nativeIsPlaying(JNIEnv *, jclass, jlong handle) {
|
|
436
|
-
auto *r = toRaop(handle);
|
|
437
|
-
return (r && raopcl_is_playing(r)) ? JNI_TRUE : JNI_FALSE;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
static jint nativeGetState(JNIEnv *, jclass, jlong handle) {
|
|
441
|
-
auto *r = toRaop(handle);
|
|
442
|
-
return r ? static_cast<jint>(raopcl_state(r)) : 0;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
static jint nativeGetLatency(JNIEnv *, jclass, jlong handle) {
|
|
446
|
-
auto *r = toRaop(handle);
|
|
447
|
-
return r ? static_cast<jint>(raopcl_latency(r)) : 0;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
static jint nativeGetSampleRate(JNIEnv *, jclass, jlong handle) {
|
|
451
|
-
auto *r = toRaop(handle);
|
|
452
|
-
return r ? static_cast<jint>(raopcl_sample_rate(r)) : 0;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
399
|
static jboolean nativeKeepalive(JNIEnv *, jclass, jlong handle) {
|
|
456
400
|
auto *r = toRaop(handle);
|
|
457
401
|
return (r && raopcl_keepalive(r)) ? JNI_TRUE : JNI_FALSE;
|
|
458
402
|
}
|
|
459
403
|
|
|
404
|
+
// -- Configuration --
|
|
405
|
+
|
|
460
406
|
static jint nativeGetLastRtspStatus(JNIEnv *, jclass, jlong handle) {
|
|
461
407
|
auto *r = toRaop(handle);
|
|
462
408
|
return r ? static_cast<jint>(raopcl_last_rtsp_status(r)) : 0;
|
|
@@ -484,6 +430,8 @@ static void nativeSetPassword(JNIEnv *env, jclass, jlong handle, jstring jPasswd
|
|
|
484
430
|
}
|
|
485
431
|
}
|
|
486
432
|
|
|
433
|
+
// -- Metadata --
|
|
434
|
+
|
|
487
435
|
static jboolean nativeSetDaap(JNIEnv *env, jclass, jlong handle,
|
|
488
436
|
jstring jTitle, jstring jArtist, jstring jAlbum) {
|
|
489
437
|
auto *r = toRaop(handle);
|
|
@@ -533,8 +481,6 @@ static jboolean nativeSetArtwork(JNIEnv *env, jclass, jlong handle,
|
|
|
533
481
|
// RegisterNatives table
|
|
534
482
|
// ---------------------------------------------------------------------------
|
|
535
483
|
static const JNINativeMethod sMethods[] = {
|
|
536
|
-
{"nativeVersion", "()Ljava/lang/String;", reinterpret_cast<void *>(nativeVersion)},
|
|
537
|
-
{"nativeInitSsl", "()Z", reinterpret_cast<void *>(nativeInitSsl)},
|
|
538
484
|
{"nativeOpen", "(Ljava/lang/String;IIIIIZIIIF)J", reinterpret_cast<void *>(nativeOpen)},
|
|
539
485
|
{"nativeConnect", "(JLjava/lang/String;I)Z", reinterpret_cast<void *>(nativeConnect)},
|
|
540
486
|
{"nativeDisconnect", "(J)Z", reinterpret_cast<void *>(nativeDisconnect)},
|
|
@@ -542,19 +488,12 @@ static const JNINativeMethod sMethods[] = {
|
|
|
542
488
|
{"nativeFeedPcm", "(J[BII)I", reinterpret_cast<void *>(nativeFeedPcm)},
|
|
543
489
|
{"nativeBytesSent", "(J)J", reinterpret_cast<void *>(nativeBytesSent)},
|
|
544
490
|
{"nativeRingAvailable", "(J)I", reinterpret_cast<void *>(nativeRingAvailable)},
|
|
545
|
-
{"nativeAcceptFrames", "(J)Z", reinterpret_cast<void *>(nativeAcceptFrames)},
|
|
546
|
-
{"nativeSendChunk", "(J[BII)Z", reinterpret_cast<void *>(nativeSendChunk)},
|
|
547
491
|
{"nativePause", "(J)V", reinterpret_cast<void *>(nativePause)},
|
|
548
492
|
{"nativeStop", "(J)V", reinterpret_cast<void *>(nativeStop)},
|
|
549
493
|
{"nativeFlush", "(J)Z", reinterpret_cast<void *>(nativeFlush)},
|
|
550
494
|
{"nativeSetVolume", "(JF)Z", reinterpret_cast<void *>(nativeSetVolume)},
|
|
551
495
|
{"nativeSetProgressMs", "(JII)Z", reinterpret_cast<void *>(nativeSetProgressMs)},
|
|
552
|
-
{"nativeGetProgressMs", "(J)J", reinterpret_cast<void *>(nativeGetProgressMs)},
|
|
553
496
|
{"nativeIsConnected", "(J)Z", reinterpret_cast<void *>(nativeIsConnected)},
|
|
554
|
-
{"nativeIsPlaying", "(J)Z", reinterpret_cast<void *>(nativeIsPlaying)},
|
|
555
|
-
{"nativeGetState", "(J)I", reinterpret_cast<void *>(nativeGetState)},
|
|
556
|
-
{"nativeGetLatency", "(J)I", reinterpret_cast<void *>(nativeGetLatency)},
|
|
557
|
-
{"nativeGetSampleRate", "(J)I", reinterpret_cast<void *>(nativeGetSampleRate)},
|
|
558
497
|
{"nativeKeepalive", "(J)Z", reinterpret_cast<void *>(nativeKeepalive)},
|
|
559
498
|
{"nativeGetLastRtspStatus", "(J)I", reinterpret_cast<void *>(nativeGetLastRtspStatus)},
|
|
560
499
|
{"nativeSetPassword", "(JLjava/lang/String;)V", reinterpret_cast<void *>(nativeSetPassword)},
|
|
@@ -66,10 +66,11 @@ internal class AirPlayEngine(
|
|
|
66
66
|
|
|
67
67
|
private val playerListener = object : Player.Listener {
|
|
68
68
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
69
|
-
|
|
69
|
+
// End signal before the generic state callback, as in GaplessEngine.
|
|
70
70
|
if (playbackState == Player.STATE_ENDED) {
|
|
71
71
|
delegate?.onPlaybackEnded(this@AirPlayEngine)
|
|
72
72
|
}
|
|
73
|
+
delegate?.onStateMaybeChanged(this@AirPlayEngine, playWhenReadyReason = null)
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
|
@@ -129,15 +130,6 @@ internal class AirPlayEngine(
|
|
|
129
130
|
|
|
130
131
|
// --- PlaybackEngine — lifecycle ---
|
|
131
132
|
|
|
132
|
-
override suspend fun prepare(
|
|
133
|
-
mediaItems: List<MediaItem>,
|
|
134
|
-
startIndex: Int,
|
|
135
|
-
startPositionMs: Long,
|
|
136
|
-
) {
|
|
137
|
-
exoPlayer.setMediaItems(mediaItems, startIndex, startPositionMs)
|
|
138
|
-
exoPlayer.prepare()
|
|
139
|
-
}
|
|
140
|
-
|
|
141
133
|
override fun release() {
|
|
142
134
|
if (released) return
|
|
143
135
|
released = true
|
|
@@ -168,6 +160,11 @@ internal class AirPlayEngine(
|
|
|
168
160
|
startIndex: Int,
|
|
169
161
|
startPositionMs: Long,
|
|
170
162
|
) {
|
|
163
|
+
if (items.isEmpty()) {
|
|
164
|
+
// Draining is not pausing: `playWhenReady` survives an empty playlist,
|
|
165
|
+
// so the next queue seated on this player would start on contact.
|
|
166
|
+
exoPlayer.pause()
|
|
167
|
+
}
|
|
171
168
|
exoPlayer.setMediaItems(items, startIndex, startPositionMs)
|
|
172
169
|
// ExoPlayer is IDLE after construction; prepare() is required before any
|
|
173
170
|
// subsequent play() can take effect (mirrors GaplessEngine).
|
|
@@ -272,12 +269,6 @@ internal class AirPlayEngine(
|
|
|
272
269
|
|
|
273
270
|
// ReplayGain absent on the AirPlay engine — interface defaults
|
|
274
271
|
// (currentReplayGainData() = null, currentReplayGainActive() = false) apply.
|
|
275
|
-
|
|
276
|
-
// --- PlaybackEngine — handoff ---
|
|
277
|
-
|
|
278
|
-
override suspend fun handoff(to: PlaybackEngine, atTrackBoundary: Boolean) {
|
|
279
|
-
throw NotImplementedError("PlaybackEngine.handoff is not supported by AirPlayEngine")
|
|
280
|
-
}
|
|
281
272
|
}
|
|
282
273
|
|
|
283
274
|
/**
|
|
@@ -3,7 +3,7 @@ package com.margelo.nitro.queueplayer
|
|
|
3
3
|
import java.net.URI
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* MIME-type
|
|
6
|
+
* MIME-type handling for the lookahead cache. Mirrors iOS's
|
|
7
7
|
* `LookaheadCache.extensionFromMimeType` / `extensionFromUrl` —
|
|
8
8
|
* lets the Android cache record the same Content-Type signal so
|
|
9
9
|
* Media3 can be told the format up front via
|
|
@@ -526,12 +526,8 @@ class CastManager : HybridCastManagerSpec() {
|
|
|
526
526
|
}
|
|
527
527
|
airplayScope.launch {
|
|
528
528
|
try {
|
|
529
|
-
val
|
|
530
|
-
|
|
531
|
-
val receiverId = pendingId ?: lastConnectedAirplayReceiverId() ?: ""
|
|
532
|
-
if (receiverId.isNotEmpty()) {
|
|
533
|
-
installAirplaySession(session, receiverId)
|
|
534
|
-
}
|
|
529
|
+
val paired = backend.submitPairingPin(pin)
|
|
530
|
+
installAirplaySession(paired.session, paired.receiverId)
|
|
535
531
|
promise.resolve(Unit)
|
|
536
532
|
} catch (t: Throwable) {
|
|
537
533
|
promise.reject(t)
|
|
@@ -540,14 +536,6 @@ class CastManager : HybridCastManagerSpec() {
|
|
|
540
536
|
return promise
|
|
541
537
|
}
|
|
542
538
|
|
|
543
|
-
/// Look up the most recently visible AirPlay receiver that pair-setup
|
|
544
|
-
/// would have stashed against. The pending pair's receiverId isn't
|
|
545
|
-
/// directly accessible from the public backend surface; lookup via
|
|
546
|
-
/// the discovered snapshot is good enough — the user pairs the device
|
|
547
|
-
/// they just saw in the picker.
|
|
548
|
-
private fun lastConnectedAirplayReceiverId(): String? =
|
|
549
|
-
airplayReceivers.firstOrNull()?.id
|
|
550
|
-
|
|
551
539
|
override fun getLocalNetworkPermissionState(): CastLocalNetworkPermission =
|
|
552
540
|
localNetworkPermissionState()
|
|
553
541
|
|