react-native-queue-player 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
package/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"] } } } }
|
package/android/build.gradle
CHANGED
|
@@ -129,6 +129,13 @@ dependencies {
|
|
|
129
129
|
implementation "com.facebook.react:react-android"
|
|
130
130
|
implementation project(":react-native-nitro-modules")
|
|
131
131
|
|
|
132
|
+
// @Keep drives R8 retention of the Nitrogen-generated bridge classes, and
|
|
133
|
+
// this artifact carries the consumer rule that honours it. Resolved
|
|
134
|
+
// transitively via Media3 / androidx.core today; declared explicitly at the
|
|
135
|
+
// version that already resolves so the dependency surface is honest rather
|
|
136
|
+
// than relying on another artifact keeping it in the graph.
|
|
137
|
+
implementation "androidx.annotation:annotation:1.8.1"
|
|
138
|
+
|
|
132
139
|
// Media3 ExoPlayer — Android audio engine. Pinned to 1.10.0 (current
|
|
133
140
|
// stable, Mar 2026). 1.10 keeps cache + audio/wake-lock APIs stable
|
|
134
141
|
// and only deprecates MetadataRetriever (we don't use it).
|
|
@@ -1,10 +1,79 @@
|
|
|
1
1
|
# React Native Queue Player — consumer R8 / ProGuard rules.
|
|
2
2
|
#
|
|
3
|
-
# Packaged into the
|
|
3
|
+
# Packaged into the AAR via `consumerProguardFiles` and applied
|
|
4
4
|
# automatically to a consuming app's R8 run, so consumers need no manual rules
|
|
5
|
-
# for this library's transitive dependencies.
|
|
5
|
+
# for this library's native bindings or its transitive dependencies.
|
|
6
|
+
#
|
|
7
|
+
# The keeps below are deliberately whole-class rather than member-scoped. The
|
|
8
|
+
# saving from narrowing them is a few kilobytes; the cost of getting one wrong
|
|
9
|
+
# is a silent runtime failure in someone else's shipped app.
|
|
10
|
+
|
|
11
|
+
# libraop's JNI_OnLoad binds this class by literal descriptor: src/main/cpp/
|
|
12
|
+
# airplay_jni.cpp calls FindClass on the string
|
|
13
|
+
# "com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI", then resolves all 19
|
|
14
|
+
# entries of its JNINativeMethod table by name and signature. React Native's own
|
|
15
|
+
# rules pin the native method names but let the class be renamed, and the rule
|
|
16
|
+
# that pins the class name ships only in AGP's optional default ProGuard file.
|
|
17
|
+
# When the class is renamed, FindClass returns null, RegisterNatives never runs,
|
|
18
|
+
# and AirPlay 1 reports itself unavailable rather than failing loudly.
|
|
19
|
+
-keep class com.margelo.nitro.queueplayer.cast.airplay.AirplayJNI { *; }
|
|
20
|
+
|
|
21
|
+
# libairplay2 binds this class the same way (src/main/cpp/airplay2_jni.cpp) and
|
|
22
|
+
# additionally calls back into Kotlin: the audio drain thread invokes
|
|
23
|
+
# onNativeSessionDied(long, int), resolved through GetStaticMethodID on that
|
|
24
|
+
# literal name. Nothing in Kotlin calls that method, so without this rule R8
|
|
25
|
+
# deletes it as unreachable — the native side then logs that upcalls are
|
|
26
|
+
# disabled and carries on. Connect and streaming still work; only the receiver's
|
|
27
|
+
# feedback timeout stops arriving, so a dead receiver never tears its session
|
|
28
|
+
# down and never reverts routing to local. Playback stops with the UI still
|
|
29
|
+
# showing the remote device.
|
|
30
|
+
-keep class com.margelo.nitro.queueplayer.cast.airplay.AirPlay2JNI { *; }
|
|
31
|
+
|
|
32
|
+
# libsignalsmith_stretch has no JNI_OnLoad; src/main/cpp/stretch_jni.cpp relies
|
|
33
|
+
# on implicit JNI name mangling, so its exported symbols spell out both the
|
|
34
|
+
# package path and the method name (Java_com_margelo_nitro_queueplayer_
|
|
35
|
+
# SignalsmithStretchNative_nativeCreate). Renaming either half breaks the lookup
|
|
36
|
+
# and the `music` pitch-correction mode throws UnsatisfiedLinkError on first use.
|
|
37
|
+
# The native methods live on this class, not on its Companion.
|
|
38
|
+
-keep class com.margelo.nitro.queueplayer.SignalsmithStretchNative { *; }
|
|
39
|
+
|
|
40
|
+
# Named as a string in this library's own AndroidManifest.xml, through the cast
|
|
41
|
+
# OPTIONS_PROVIDER_CLASS_NAME meta-data. aapt2 generates keep rules for manifest
|
|
42
|
+
# components but does not descend into meta-data values, so nothing automatic
|
|
43
|
+
# protects this one. The Play Services Cast AAR happens to keep it today via
|
|
44
|
+
# `-keep public class * implements OptionsProvider`; this rule means correctness
|
|
45
|
+
# does not depend on another artifact's rule surviving.
|
|
46
|
+
-keep class com.margelo.nitro.queueplayer.cast.chromecast.ChromecastOptionsProvider { *; }
|
|
47
|
+
|
|
48
|
+
# Named as a string in this library's own AndroidManifest.xml, as the
|
|
49
|
+
# androidx.startup meta-data entry that registers it with
|
|
50
|
+
# InitializationProvider. Same shape as the cast options provider above and
|
|
51
|
+
# equally invisible to aapt2, which generates keep rules for manifest
|
|
52
|
+
# components but not for meta-data values. androidx.startup keeps it today via
|
|
53
|
+
# `-keepnames class * extends androidx.startup.Initializer`; this rule means
|
|
54
|
+
# correctness does not depend on that.
|
|
55
|
+
-keep class com.margelo.nitro.queueplayer.cast.airplay.AirPlayBackendInitializer { *; }
|
|
56
|
+
|
|
57
|
+
# The Nitrogen-generated enums are read from C++ by constant name — e.g.
|
|
58
|
+
# getStaticField<JCastProtocol>("AIRPLAY") — so losing a constant's name breaks
|
|
59
|
+
# every conversion across the bridge. They survive today only because
|
|
60
|
+
# androidx.annotation ships a keep rule for @Keep-annotated classes and Nitrogen
|
|
61
|
+
# annotates what it generates, which makes correctness depend on that artifact
|
|
62
|
+
# staying in the resolution graph. `**` also matches the enums internal to this
|
|
63
|
+
# library that native code never touches; keeping them is accepted collateral.
|
|
64
|
+
-keep enum com.margelo.nitro.queueplayer.** { *; }
|
|
65
|
+
|
|
66
|
+
# Constructed from C++ during Nitro's onLoad via getConstructor<javaobject()>().
|
|
67
|
+
# R8's compatibility FAQ states that full mode does not implicitly keep a kept
|
|
68
|
+
# class's no-arg constructor; AGP's keep-rule documentation states that it does.
|
|
69
|
+
# Current R8 keeps it. These rules make the library correct under either.
|
|
70
|
+
-keep class com.margelo.nitro.queueplayer.TrackPlayer { <init>(); }
|
|
71
|
+
-keep class com.margelo.nitro.queueplayer.Equalizer { <init>(); }
|
|
72
|
+
-keep class com.margelo.nitro.queueplayer.Visualizer { <init>(); }
|
|
73
|
+
-keep class com.margelo.nitro.queueplayer.CastManager { <init>(); }
|
|
6
74
|
|
|
7
|
-
# The embedded Ktor CIO HTTP server (the
|
|
75
|
+
# The embedded Ktor CIO HTTP server (the Chromecast local media server, which
|
|
76
|
+
# serves local files to a receiver) ships
|
|
8
77
|
# io.ktor.util.debug.IntellijIdeaDebugDetector, which references
|
|
9
78
|
# java.lang.management.ManagementFactory and RuntimeMXBean to detect an attached
|
|
10
79
|
# IntelliJ debugger. Those JMX classes are JVM-only and absent on Android, so a
|
|
@@ -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)},
|
|
@@ -546,19 +530,39 @@ jint ap2_register_natives(JavaVM *vm) {
|
|
|
546
530
|
|
|
547
531
|
jclass clazz = env->FindClass(sClassName);
|
|
548
532
|
if (!clazz) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
533
|
+
/* Leaves a pending NoClassDefFoundError. Clear it and fail the load
|
|
534
|
+
* outright: nothing retries registration, so reporting success here
|
|
535
|
+
* would leave the Kotlin object reporting AirPlay 2 as available with
|
|
536
|
+
* no natives bound, and the first call would throw
|
|
537
|
+
* UnsatisfiedLinkError. Failing makes System.loadLibrary throw, and
|
|
538
|
+
* AirPlay2JNI's initialiser catches that and sets isAvailable false. */
|
|
539
|
+
env->ExceptionClear();
|
|
540
|
+
__android_log_print(ANDROID_LOG_ERROR, TAG,
|
|
541
|
+
"AirPlay2JNI class not found — AirPlay 2 unavailable");
|
|
542
|
+
return JNI_ERR;
|
|
552
543
|
}
|
|
553
544
|
|
|
554
545
|
if (env->RegisterNatives(clazz, sMethods,
|
|
555
546
|
sizeof(sMethods) / sizeof(sMethods[0])) < 0) {
|
|
547
|
+
/* An unresolved table entry leaves a pending NoSuchMethodError. */
|
|
548
|
+
env->ExceptionClear();
|
|
556
549
|
__android_log_print(ANDROID_LOG_ERROR, TAG, "RegisterNatives failed");
|
|
557
550
|
return JNI_ERR;
|
|
558
551
|
}
|
|
559
552
|
|
|
560
553
|
/* Cache the session-death upcall target (global ref + static method id). */
|
|
561
554
|
sAp2JniClass = (jclass)env->NewGlobalRef(clazz);
|
|
555
|
+
if (!sAp2JniClass) {
|
|
556
|
+
/* Null only when the global reference table is exhausted, which can
|
|
557
|
+
* leave a pending OutOfMemoryError; clear it so the load reports
|
|
558
|
+
* success with upcalls disabled, matching the missing-method path
|
|
559
|
+
* below. The check has to precede GetStaticMethodID, which aborts
|
|
560
|
+
* under CheckJNI when handed a null class. */
|
|
561
|
+
env->ExceptionClear();
|
|
562
|
+
__android_log_print(ANDROID_LOG_WARN, TAG,
|
|
563
|
+
"could not retain AirPlay2JNI — upcalls disabled");
|
|
564
|
+
return JNI_VERSION_1_6;
|
|
565
|
+
}
|
|
562
566
|
sOnNativeSessionDied = env->GetStaticMethodID(
|
|
563
567
|
sAp2JniClass, "onNativeSessionDied", "(JI)V");
|
|
564
568
|
if (!sOnNativeSessionDied) {
|
|
@@ -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),
|