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.
Files changed (367) hide show
  1. package/QueuePlayer.podspec +14 -9
  2. package/README.md +3 -3
  3. package/android/build.gradle +7 -0
  4. package/android/consumer-rules.pro +72 -3
  5. package/android/src/main/cpp/airplay2_control.c +4 -4
  6. package/android/src/main/cpp/airplay2_control.h +4 -4
  7. package/android/src/main/cpp/airplay2_jni.cpp +26 -22
  8. package/android/src/main/cpp/airplay2_pair.c +7 -7
  9. package/android/src/main/cpp/airplay2_pair.h +2 -2
  10. package/android/src/main/cpp/airplay2_rtsp.c +8 -8
  11. package/android/src/main/cpp/airplay2_session.c +2 -2
  12. package/android/src/main/cpp/airplay_jni.cpp +20 -72
  13. package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
  14. package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
  15. package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
  16. package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
  17. package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
  18. package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
  19. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
  20. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
  21. package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
  22. package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
  23. package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
  24. package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
  25. package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
  26. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
  27. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
  28. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
  29. package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
  30. package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
  31. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
  32. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
  33. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
  34. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
  35. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
  36. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
  37. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
  38. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
  39. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
  40. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
  41. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
  42. package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
  43. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
  44. package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
  45. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
  46. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
  47. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
  48. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
  49. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
  50. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
  51. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
  52. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
  53. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
  54. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
  55. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
  56. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
  57. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
  58. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
  59. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
  60. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
  61. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
  62. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
  63. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
  64. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
  65. package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
  66. package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
  67. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
  68. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
  69. package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
  70. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
  71. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
  72. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
  73. package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
  74. package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
  75. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
  76. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
  77. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
  78. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
  79. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
  80. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
  81. package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
  82. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
  83. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
  84. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
  85. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
  86. package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
  87. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
  88. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
  89. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
  90. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
  91. package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
  92. package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
  93. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
  94. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
  95. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
  96. package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
  97. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
  98. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
  99. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
  100. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
  101. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
  102. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
  103. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
  104. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
  105. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
  106. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
  107. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
  108. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
  109. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
  110. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
  111. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
  112. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
  113. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
  114. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
  115. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
  116. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
  117. package/app.plugin.js +17 -1
  118. package/ios/AVPlayerItemQueueItemId.swift +26 -12
  119. package/ios/AVQueueBuilder.swift +120 -85
  120. package/ios/ArtworkLoader.swift +0 -7
  121. package/ios/ArtworkResolver.swift +22 -4
  122. package/ios/AssetReplayGainReader.swift +118 -0
  123. package/ios/AudioSession.swift +67 -16
  124. package/ios/AudioTapProvider.swift +284 -149
  125. package/ios/CarPlayBridge.swift +0 -12
  126. package/ios/CarPlayCoordinator.swift +18 -2
  127. package/ios/CarPlaySceneDelegate.swift +2 -2
  128. package/ios/Cast/AirPlayRouteState.swift +9 -8
  129. package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
  130. package/ios/Cast/Core/CastEventBridge.swift +66 -26
  131. package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
  132. package/ios/Cast/Core/CastSession.swift +11 -0
  133. package/ios/Cast/Core/CastTransportRouter.swift +21 -9
  134. package/ios/Cast/Core/LocalMediaServer.swift +41 -52
  135. package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
  136. package/ios/Cast/Core/MediaServerHandle.swift +0 -4
  137. package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
  138. package/ios/CastManager.swift +1 -1
  139. package/ios/CrossfadeEngine.swift +660 -226
  140. package/ios/EQTap.swift +20 -17
  141. package/ios/Equalizer.swift +1 -1
  142. package/ios/FLACStreamInfo.swift +65 -0
  143. package/ios/GaplessEngine.swift +57 -30
  144. package/ios/InputGuards.swift +35 -5
  145. package/ios/LookaheadCache.swift +202 -58
  146. package/ios/LookaheadCachePrefetcher.swift +48 -36
  147. package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
  148. package/ios/NetworkRecoveryPolicy.swift +32 -0
  149. package/ios/NowPlayingFormatExtractor.swift +25 -18
  150. package/ios/NowPlayingInfo.swift +84 -48
  151. package/ios/OutputRouteMonitor.swift +16 -3
  152. package/ios/PendingBrowseRequests.swift +1 -1
  153. package/ios/PlaceholderArtwork.swift +9 -5
  154. package/ios/PlaybackEngine.swift +165 -76
  155. package/ios/PlaybackErrorMapping.swift +3 -3
  156. package/ios/PlaybackModeStateMachine.swift +2 -2
  157. package/ios/PlaybackNetworkMonitor.swift +73 -0
  158. package/ios/PlayerStateDerivation.swift +6 -6
  159. package/ios/QueueMutationArithmetic.swift +25 -0
  160. package/ios/QueueSkipArithmetic.swift +5 -3
  161. package/ios/QueueWindowArithmetic.swift +152 -0
  162. package/ios/ReadThroughServer.swift +283 -0
  163. package/ios/RemoteCommands.swift +44 -4
  164. package/ios/ReplayGainData.swift +22 -3
  165. package/ios/ReplayGainExtractor.swift +70 -27
  166. package/ios/Siri/VoiceDonation.swift +14 -9
  167. package/ios/StreamingBitrateProbe.swift +22 -68
  168. package/ios/SuppliedReplayGain.swift +83 -0
  169. package/ios/Tests/AVQueueBuilderTests.swift +158 -221
  170. package/ios/Tests/ActiveItemEngineStub.swift +44 -0
  171. package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
  172. package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
  173. package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
  174. package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
  175. package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
  176. package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
  177. package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
  178. package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
  179. package/ios/Tests/CrossfadeEngineTests.swift +164 -0
  180. package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
  181. package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
  182. package/ios/Tests/EngineSwapTests.swift +91 -0
  183. package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
  184. package/ios/Tests/EqualizerHybridTests.swift +5 -2
  185. package/ios/Tests/FLACStreamInfoTests.swift +88 -0
  186. package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
  187. package/ios/Tests/InputGuardsTests.swift +41 -0
  188. package/ios/Tests/InterruptionIntentTests.swift +77 -0
  189. package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
  190. package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
  191. package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
  192. package/ios/Tests/LookaheadCacheTests.swift +44 -18
  193. package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
  194. package/ios/Tests/MutationDeferralTests.swift +220 -0
  195. package/ios/Tests/NowPlayingInfoTests.swift +86 -99
  196. package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
  197. package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
  198. package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
  199. package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
  200. package/ios/Tests/PlayerFixtures.swift +48 -0
  201. package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
  202. package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
  203. package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
  204. package/ios/Tests/QueueStateTests.swift +3 -1
  205. package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
  206. package/ios/Tests/QueueWindowSliceTests.swift +162 -0
  207. package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
  208. package/ios/Tests/ReadThroughServerTests.swift +345 -0
  209. package/ios/Tests/RemoteCommandsTests.swift +28 -0
  210. package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
  211. package/ios/Tests/ReplayGainMergeTests.swift +230 -0
  212. package/ios/Tests/RetryRecoveryTests.swift +303 -0
  213. package/ios/Tests/SkipCapabilityTests.swift +233 -8
  214. package/ios/Tests/SkipIndexTests.swift +32 -2
  215. package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
  216. package/ios/Tests/StallRecoveryTests.swift +97 -0
  217. package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
  218. package/ios/Tests/TopUpWindowGateTests.swift +394 -0
  219. package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
  220. package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
  221. package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
  222. package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
  223. package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
  224. package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
  225. package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
  226. package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
  227. package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
  228. package/ios/TrackPlayer+Automotive.swift +101 -0
  229. package/ios/TrackPlayer+Cache.swift +232 -0
  230. package/ios/TrackPlayer+Config.swift +291 -0
  231. package/ios/TrackPlayer+EngineDelegate.swift +209 -0
  232. package/ios/TrackPlayer+EventsAPI.swift +111 -0
  233. package/ios/TrackPlayer+EventsDispatch.swift +968 -0
  234. package/ios/TrackPlayer+EventsWiring.swift +173 -0
  235. package/ios/TrackPlayer+Lifecycle.swift +930 -0
  236. package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
  237. package/ios/TrackPlayer+Queue.swift +940 -0
  238. package/ios/TrackPlayer+Recovery.swift +196 -0
  239. package/ios/TrackPlayer+Skip.swift +455 -0
  240. package/ios/TrackPlayer+SleepTimer.swift +175 -0
  241. package/ios/TrackPlayer+State.swift +108 -0
  242. package/ios/TrackPlayer+Threading.swift +135 -0
  243. package/ios/TrackPlayer+Transport.swift +267 -0
  244. package/ios/TrackPlayer+Window.swift +195 -0
  245. package/ios/TrackPlayer.swift +305 -4610
  246. package/ios/Visualizer.swift +6 -5
  247. package/ios/tests-harness/Podfile +1 -1
  248. package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
  249. package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
  250. package/lib/module/hooks/useActiveTrack.js +29 -22
  251. package/lib/module/hooks/useActiveTrack.js.map +1 -1
  252. package/lib/module/hooks/useCast.js +8 -26
  253. package/lib/module/hooks/useCast.js.map +1 -1
  254. package/lib/module/hooks/useEqualizer.js +19 -12
  255. package/lib/module/hooks/useEqualizer.js.map +1 -1
  256. package/lib/module/hooks/useLookaheadCache.js +3 -7
  257. package/lib/module/hooks/useLookaheadCache.js.map +1 -1
  258. package/lib/module/hooks/useQueue.js +66 -19
  259. package/lib/module/hooks/useQueue.js.map +1 -1
  260. package/lib/module/index.js +6 -3
  261. package/lib/module/index.js.map +1 -1
  262. package/lib/module/queueDelta.js +41 -0
  263. package/lib/module/queueDelta.js.map +1 -0
  264. package/lib/module/types.js +28 -5
  265. package/lib/module/types.js.map +1 -1
  266. package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
  267. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  268. package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
  269. package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
  270. package/lib/typescript/hooks/useCast.d.ts +6 -1
  271. package/lib/typescript/hooks/useCast.d.ts.map +1 -1
  272. package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
  273. package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
  274. package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
  275. package/lib/typescript/hooks/useQueue.d.ts +4 -4
  276. package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
  277. package/lib/typescript/index.d.ts +2 -1
  278. package/lib/typescript/index.d.ts.map +1 -1
  279. package/lib/typescript/queueDelta.d.ts +10 -0
  280. package/lib/typescript/queueDelta.d.ts.map +1 -0
  281. package/lib/typescript/types.d.ts +168 -12
  282. package/lib/typescript/types.d.ts.map +1 -1
  283. package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
  284. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
  285. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
  286. package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
  287. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
  288. package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
  289. package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
  290. 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
  291. 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
  292. 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
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
  297. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
  298. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
  299. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
  300. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
  301. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
  302. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
  303. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
  304. 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
  305. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
  306. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
  307. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
  308. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
  309. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
  310. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
  311. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
  312. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
  313. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
  314. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
  315. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
  316. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
  317. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
  318. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
  319. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
  320. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
  321. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
  322. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
  323. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
  324. package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
  325. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
  326. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
  327. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
  328. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
  329. package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
  330. package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
  331. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
  332. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
  333. package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
  334. package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
  335. package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
  336. package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
  337. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
  338. package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
  339. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
  340. package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
  341. package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
  342. package/package.json +8 -8
  343. package/src/TrackPlayer.nitro.ts +53 -27
  344. package/src/hooks/useActiveTrack.ts +29 -22
  345. package/src/hooks/useCast.ts +14 -15
  346. package/src/hooks/useEqualizer.ts +19 -12
  347. package/src/hooks/useLookaheadCache.ts +3 -7
  348. package/src/hooks/useQueue.ts +66 -17
  349. package/src/index.ts +13 -3
  350. package/src/queueDelta.ts +41 -0
  351. package/src/types.ts +169 -12
  352. package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
  353. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
  354. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
  355. package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
  356. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
  357. package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
  358. package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
  359. package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
  360. package/ios/MetadataReader.swift +0 -555
  361. package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
  362. package/ios/Tests/MetadataReaderTests.swift +0 -603
  363. package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
  364. package/ios/Tests/VoiceDonationTests.swift +0 -26
  365. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
  366. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
  367. /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
@@ -10,7 +10,10 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.authors = package["author"]
12
12
 
13
- s.platforms = { :ios => min_ios_version_supported }
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'`. iOS 15.0 floor matches
68
- # Google Cast SDK 4.8.x's supported deployment target.
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 = '15.0'
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
- # Cast serves consumer-supplied `file://` URLs to pull-only receivers
91
- # (Chromecast / future DLNA) via a native `Network.framework` listener
92
- # (`LocalMediaServer` / `MediaHTTPConnection`) no third-party HTTP
93
- # server dependency, no `:modular_headers` requirement on the consumer.
94
- # Orthogonal to the streaming-only lookahead cache.
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 15+ (the Chromecast subspec requires iOS 15).
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** — add `audio` to `UIBackgroundModes` in your app config (`ios.infoPlist`). This is the one core background-playback step the plugin does not set for you:
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"] } } } }
@@ -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 published AAR via `consumerProguardFiles` and applied
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 Android AirPlay 2 receiver) ships
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:2409-2415:
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:2499 — adds RTP-Info header. */
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:2507-2548 — raw image body with format-specific CT. */
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:2481-2504 — "progress: start/current/end". */
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:2402-2418.
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:2551-2575.
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:2507-2548.
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:2481-2504. Converts ms to RTP timestamps.
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:1485-1539 — socket reused across auth. */
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:2456-2478 — SET_PARAMETER text/parameters. */
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:3660-3662 — FLUSH after stopping audio. */
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
- __android_log_print(ANDROID_LOG_WARN, TAG,
550
- "AirPlay2JNI class not found RegisterNatives deferred");
551
- return JNI_VERSION_1_6;
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:181-190 and 3304-3345. */
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:3304-3345 and pyatv:
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:3325-3327. */
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:3670 — AIRPLAY_SEQ_PIN_START is a separate
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:3304-3345 decision tree. */
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:3304-3312 — when ONE_TIME_PAIRING is set,
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:3434-3443. */
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:3670 — separate from pair-setup.
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:1184 — strdup(pin) as SRP password.
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:684-779) ---- */
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:805-878.
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:684-779 — HA1=MD5("":realm:password), HA2=MD5(method:uri).
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:687 */
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:3814 — session_url for SETUP/RECORD/etc,
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:908-912 — auth_header_add on every request. */
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:919-926.
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:3183-3197. Must happen before freeing resp_buf. */
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:919-926. */
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:1535.
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),