react-native-queue-player 1.2.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/QueuePlayer.podspec +14 -9
  2. package/README.md +3 -3
  3. package/android/consumer-rules.pro +1 -1
  4. package/android/src/main/cpp/airplay2_control.c +4 -4
  5. package/android/src/main/cpp/airplay2_control.h +4 -4
  6. package/android/src/main/cpp/airplay2_jni.cpp +3 -19
  7. package/android/src/main/cpp/airplay2_pair.c +7 -7
  8. package/android/src/main/cpp/airplay2_pair.h +2 -2
  9. package/android/src/main/cpp/airplay2_rtsp.c +8 -8
  10. package/android/src/main/cpp/airplay2_session.c +2 -2
  11. package/android/src/main/cpp/airplay_jni.cpp +8 -69
  12. package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
  13. package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
  14. package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
  15. package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
  16. package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
  17. package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
  18. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
  19. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
  20. package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
  21. package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
  22. package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
  23. package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
  24. package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
  25. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +111 -29
  26. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
  27. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
  28. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
  29. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
  30. package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
  31. package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
  32. package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
  33. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
  34. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +13 -5
  35. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
  36. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
  37. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
  38. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
  39. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
  40. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
  41. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
  42. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
  43. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
  44. package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
  45. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +985 -276
  46. package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
  47. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
  48. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
  49. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
  50. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
  51. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
  52. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
  53. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
  54. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
  55. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
  56. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
  57. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
  58. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
  59. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
  60. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
  61. package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
  62. package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
  63. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
  64. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
  65. package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
  66. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
  67. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
  68. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
  69. package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
  70. package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
  71. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
  72. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
  73. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +84 -18
  74. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
  75. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
  76. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
  77. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaResponseCheckTest.kt +163 -0
  78. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
  79. package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
  80. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
  81. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
  82. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
  83. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
  84. package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
  85. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
  86. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
  87. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
  88. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
  89. package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
  90. package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
  91. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
  92. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
  93. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
  94. package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
  95. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
  96. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
  97. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
  98. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
  99. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +122 -38
  100. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -27
  101. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
  102. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
  103. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
  104. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
  105. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
  106. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
  107. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
  108. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
  109. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
  110. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
  111. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
  112. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
  113. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
  114. package/app.plugin.js +17 -1
  115. package/ios/AVPlayerItemQueueItemId.swift +26 -12
  116. package/ios/AVQueueBuilder.swift +120 -85
  117. package/ios/ArtworkLoader.swift +0 -7
  118. package/ios/ArtworkResolver.swift +22 -4
  119. package/ios/AssetReplayGainReader.swift +118 -0
  120. package/ios/AudioSession.swift +67 -16
  121. package/ios/AudioTapProvider.swift +284 -149
  122. package/ios/CarPlayBridge.swift +0 -12
  123. package/ios/CarPlayCoordinator.swift +18 -2
  124. package/ios/CarPlaySceneDelegate.swift +2 -2
  125. package/ios/Cast/AirPlayRouteState.swift +9 -8
  126. package/ios/Cast/Core/CastEventBridge.swift +66 -26
  127. package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
  128. package/ios/Cast/Core/CastSession.swift +2 -2
  129. package/ios/Cast/Core/CastTransportRouter.swift +21 -9
  130. package/ios/Cast/Core/LocalMediaServer.swift +41 -52
  131. package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
  132. package/ios/Cast/Core/MediaServerHandle.swift +0 -4
  133. package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
  134. package/ios/CastManager.swift +1 -1
  135. package/ios/CrossfadeEngine.swift +672 -226
  136. package/ios/EQTap.swift +20 -17
  137. package/ios/Equalizer.swift +1 -1
  138. package/ios/FLACStreamInfo.swift +65 -0
  139. package/ios/GaplessEngine.swift +57 -30
  140. package/ios/InputGuards.swift +35 -5
  141. package/ios/LookaheadCache.swift +229 -58
  142. package/ios/LookaheadCachePrefetcher.swift +48 -36
  143. package/ios/MediaResponseCheck.swift +122 -0
  144. package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
  145. package/ios/NetworkRecoveryPolicy.swift +32 -0
  146. package/ios/NowPlayingFormatExtractor.swift +25 -18
  147. package/ios/NowPlayingInfo.swift +84 -48
  148. package/ios/OutputRouteMonitor.swift +16 -3
  149. package/ios/PendingBrowseRequests.swift +1 -1
  150. package/ios/PlaceholderArtwork.swift +9 -5
  151. package/ios/PlaybackEngine.swift +165 -76
  152. package/ios/PlaybackErrorMapping.swift +3 -3
  153. package/ios/PlaybackModeStateMachine.swift +2 -2
  154. package/ios/PlaybackNetworkMonitor.swift +73 -0
  155. package/ios/PlayerStateDerivation.swift +6 -6
  156. package/ios/QueueMutationArithmetic.swift +25 -0
  157. package/ios/QueueSkipArithmetic.swift +5 -3
  158. package/ios/QueueWindowArithmetic.swift +152 -0
  159. package/ios/ReadThroughServer.swift +283 -0
  160. package/ios/RemoteCommands.swift +44 -4
  161. package/ios/ReplayGainData.swift +22 -3
  162. package/ios/ReplayGainExtractor.swift +70 -27
  163. package/ios/Siri/VoiceDonation.swift +14 -9
  164. package/ios/StreamingBitrateProbe.swift +22 -68
  165. package/ios/SuppliedReplayGain.swift +83 -0
  166. package/ios/Tests/AVQueueBuilderTests.swift +158 -221
  167. package/ios/Tests/ActiveItemEngineStub.swift +44 -0
  168. package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
  169. package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
  170. package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
  171. package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
  172. package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
  173. package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
  174. package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
  175. package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
  176. package/ios/Tests/CrossfadeEngineTests.swift +164 -0
  177. package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
  178. package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
  179. package/ios/Tests/EngineSwapTests.swift +91 -0
  180. package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
  181. package/ios/Tests/EqualizerHybridTests.swift +5 -2
  182. package/ios/Tests/FLACStreamInfoTests.swift +88 -0
  183. package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
  184. package/ios/Tests/InputGuardsTests.swift +41 -0
  185. package/ios/Tests/InterruptionIntentTests.swift +77 -0
  186. package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
  187. package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
  188. package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
  189. package/ios/Tests/LookaheadCacheTests.swift +44 -18
  190. package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
  191. package/ios/Tests/MediaResponseCheckTests.swift +128 -0
  192. package/ios/Tests/MutationDeferralTests.swift +220 -0
  193. package/ios/Tests/NowPlayingInfoTests.swift +86 -99
  194. package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
  195. package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
  196. package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
  197. package/ios/Tests/PlayerFixtures.swift +48 -0
  198. package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
  199. package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
  200. package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
  201. package/ios/Tests/QueueStateTests.swift +3 -1
  202. package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
  203. package/ios/Tests/QueueWindowSliceTests.swift +162 -0
  204. package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
  205. package/ios/Tests/ReadThroughServerTests.swift +345 -0
  206. package/ios/Tests/RemoteCommandsTests.swift +28 -0
  207. package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
  208. package/ios/Tests/ReplayGainMergeTests.swift +230 -0
  209. package/ios/Tests/RetryRecoveryTests.swift +418 -0
  210. package/ios/Tests/SkipCapabilityTests.swift +233 -8
  211. package/ios/Tests/SkipIndexTests.swift +32 -2
  212. package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
  213. package/ios/Tests/StallRecoveryTests.swift +97 -0
  214. package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
  215. package/ios/Tests/TopUpWindowGateTests.swift +441 -0
  216. package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
  217. package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
  218. package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
  219. package/ios/Tests/TrackPlayerEndVerdictTests.swift +206 -0
  220. package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
  221. package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
  222. package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
  223. package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
  224. package/ios/TrackPlayer+Automotive.swift +101 -0
  225. package/ios/TrackPlayer+Cache.swift +232 -0
  226. package/ios/TrackPlayer+Config.swift +291 -0
  227. package/ios/TrackPlayer+EngineDelegate.swift +209 -0
  228. package/ios/TrackPlayer+EventsAPI.swift +111 -0
  229. package/ios/TrackPlayer+EventsDispatch.swift +1042 -0
  230. package/ios/TrackPlayer+EventsWiring.swift +173 -0
  231. package/ios/TrackPlayer+Lifecycle.swift +982 -0
  232. package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
  233. package/ios/TrackPlayer+Queue.swift +951 -0
  234. package/ios/TrackPlayer+Recovery.swift +213 -0
  235. package/ios/TrackPlayer+Skip.swift +465 -0
  236. package/ios/TrackPlayer+SleepTimer.swift +175 -0
  237. package/ios/TrackPlayer+State.swift +108 -0
  238. package/ios/TrackPlayer+Threading.swift +135 -0
  239. package/ios/TrackPlayer+Transport.swift +272 -0
  240. package/ios/TrackPlayer+Window.swift +227 -0
  241. package/ios/TrackPlayer.swift +331 -4619
  242. package/ios/Visualizer.swift +6 -5
  243. package/ios/tests-harness/Podfile +1 -1
  244. package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
  245. package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
  246. package/lib/module/hooks/useActiveTrack.js +29 -22
  247. package/lib/module/hooks/useActiveTrack.js.map +1 -1
  248. package/lib/module/hooks/useCast.js +8 -26
  249. package/lib/module/hooks/useCast.js.map +1 -1
  250. package/lib/module/hooks/useEqualizer.js +19 -12
  251. package/lib/module/hooks/useEqualizer.js.map +1 -1
  252. package/lib/module/hooks/useLookaheadCache.js +3 -7
  253. package/lib/module/hooks/useLookaheadCache.js.map +1 -1
  254. package/lib/module/hooks/useQueue.js +66 -19
  255. package/lib/module/hooks/useQueue.js.map +1 -1
  256. package/lib/module/index.js +6 -3
  257. package/lib/module/index.js.map +1 -1
  258. package/lib/module/queueDelta.js +41 -0
  259. package/lib/module/queueDelta.js.map +1 -0
  260. package/lib/module/types.js +28 -5
  261. package/lib/module/types.js.map +1 -1
  262. package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
  263. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  264. package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
  265. package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
  266. package/lib/typescript/hooks/useCast.d.ts +6 -1
  267. package/lib/typescript/hooks/useCast.d.ts.map +1 -1
  268. package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
  269. package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
  270. package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
  271. package/lib/typescript/hooks/useQueue.d.ts +4 -4
  272. package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
  273. package/lib/typescript/index.d.ts +2 -1
  274. package/lib/typescript/index.d.ts.map +1 -1
  275. package/lib/typescript/queueDelta.d.ts +10 -0
  276. package/lib/typescript/queueDelta.d.ts.map +1 -0
  277. package/lib/typescript/types.d.ts +168 -12
  278. package/lib/typescript/types.d.ts.map +1 -1
  279. package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
  280. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
  281. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
  282. package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
  283. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
  284. package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
  285. package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
  286. 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
  287. 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
  288. 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
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
  297. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
  298. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
  299. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
  300. 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
  301. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
  302. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
  303. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
  304. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
  305. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
  306. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
  307. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
  308. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
  309. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
  310. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
  311. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
  312. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
  313. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
  314. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
  315. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
  316. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
  317. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
  318. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
  319. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
  320. package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
  321. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
  322. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
  323. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
  324. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
  325. package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
  326. package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
  327. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
  328. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
  329. package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
  330. package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
  331. package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
  332. package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
  333. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
  334. package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
  335. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
  336. package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
  337. package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
  338. package/package.json +8 -8
  339. package/src/TrackPlayer.nitro.ts +53 -27
  340. package/src/hooks/useActiveTrack.ts +29 -22
  341. package/src/hooks/useCast.ts +14 -15
  342. package/src/hooks/useEqualizer.ts +19 -12
  343. package/src/hooks/useLookaheadCache.ts +3 -7
  344. package/src/hooks/useQueue.ts +66 -17
  345. package/src/index.ts +13 -3
  346. package/src/queueDelta.ts +41 -0
  347. package/src/types.ts +169 -12
  348. package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
  349. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
  350. package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
  351. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
  352. package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
  353. package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
  354. package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
  355. package/ios/MetadataReader.swift +0 -555
  356. package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
  357. package/ios/Tests/MetadataReaderTests.swift +0 -603
  358. package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
  359. package/ios/Tests/VoiceDonationTests.swift +0 -26
  360. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
  361. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
  362. /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
@@ -1,6 +1,7 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { getTrackPlayer } from '../clients';
3
3
  import type { TrackItem } from '../types';
4
+ import { applyQueueDelta } from '../queueDelta';
4
5
 
5
6
  export interface QueueSnapshot {
6
7
  /** The current queue, in play order. */
@@ -17,28 +18,75 @@ const INITIAL: QueueSnapshot = { queue: [], currentIndex: -1 };
17
18
  * `onTrackChange` so `currentIndex` stays live on auto-advance / skip,
18
19
  * which move the active position without changing the queue contents.
19
20
  *
20
- * The first-render seed reads `getQueue()` + `getCurrentTrackIndex()`
21
- * separately the pair is not atomic against a concurrent mutation, so
22
- * it can briefly skew. Self-healing: the first `onQueueChange` /
23
- * `onTrackChange` fire delivers a consistent snapshot.
21
+ * Renders empty for the first frame, then fills in: the seed reads
22
+ * `getQueue()`, which resolves asynchronously. A mutation landing in that gap
23
+ * is not lost: a delta that arrives before the seed lands cannot be applied —
24
+ * there is nothing correct to apply it to — so the queue is re-read instead.
24
25
  */
25
26
  export function useQueue(): QueueSnapshot {
26
- const [snapshot, setSnapshot] = useState<QueueSnapshot>(() => {
27
- try {
28
- const player = getTrackPlayer();
29
- return {
30
- queue: player.getQueue(),
31
- currentIndex: player.getCurrentTrackIndex(),
32
- };
33
- } catch {
34
- return INITIAL;
35
- }
36
- });
27
+ const [snapshot, setSnapshot] = useState<QueueSnapshot>(INITIAL);
37
28
 
38
29
  useEffect(() => {
39
30
  const player = getTrackPlayer();
40
- const offQueue = player.onQueueChange((queue, currentIndex) => {
41
- setSnapshot({ queue, currentIndex });
31
+ let disposed = false;
32
+ // `haveBase` is what makes a delta applicable: without a queue to apply it
33
+ // to, applying one silently produces a wrong mirror — and for `shuffle`, an
34
+ // array of holes. `seenEvent` separately stops a slow seed overwriting
35
+ // newer state.
36
+ let haveBase = false;
37
+ let seenEvent = false;
38
+ let revision = 0;
39
+ // Guards against two overlapping re-reads resolving out of order.
40
+ let readTicket = 0;
41
+
42
+ const reread = () => {
43
+ const ticket = ++readTicket;
44
+ // Until this read lands there is no base a delta can be applied to:
45
+ // an event arriving meanwhile re-reads too, and the ticket keeps the
46
+ // last read's answer.
47
+ haveBase = false;
48
+ void player
49
+ .getQueue()
50
+ .then((queue) => {
51
+ if (disposed || ticket !== readTicket) return;
52
+ haveBase = true;
53
+ // Re-read the index too: the queue just came from a fresh read, and
54
+ // the index captured when the event fired may already be stale.
55
+ setSnapshot({ queue, currentIndex: player.getCurrentTrackIndex() });
56
+ })
57
+ .catch(() => {
58
+ // Player not configured yet; a later event re-reads.
59
+ });
60
+ };
61
+
62
+ void (async () => {
63
+ try {
64
+ const ticket = ++readTicket;
65
+ const queue = await player.getQueue();
66
+ const currentIndex = player.getCurrentTrackIndex();
67
+ if (disposed || seenEvent || ticket !== readTicket) return;
68
+ haveBase = true;
69
+ setSnapshot({ queue, currentIndex });
70
+ } catch {
71
+ // Player not configured yet; the first event re-reads instead.
72
+ }
73
+ })();
74
+
75
+ // A delta only makes sense against the copy it was computed from, so a
76
+ // missed event — or one that beats the seed — is recovered by re-reading
77
+ // rather than patched over.
78
+ const offQueue = player.onQueueChange((delta, currentIndex, reason) => {
79
+ seenEvent = true;
80
+ const missed = revision !== 0 && delta.revision !== revision + 1;
81
+ revision = delta.revision;
82
+ if (!haveBase || missed) {
83
+ reread();
84
+ return;
85
+ }
86
+ setSnapshot((prev) => ({
87
+ queue: applyQueueDelta(prev.queue, delta, reason),
88
+ currentIndex,
89
+ }));
42
90
  });
43
91
  // Keep `currentIndex` live when the active track changes without a
44
92
  // queue-content change (auto-advance, skip). Preserves the queue from
@@ -47,6 +95,7 @@ export function useQueue(): QueueSnapshot {
47
95
  setSnapshot((prev) => ({ queue: prev.queue, currentIndex: index }));
48
96
  });
49
97
  return () => {
98
+ disposed = true;
50
99
  offQueue();
51
100
  offTrack();
52
101
  };
package/src/index.ts CHANGED
@@ -9,6 +9,9 @@ export type {
9
9
  // Player
10
10
  TrackItem,
11
11
  PlayerConfig,
12
+ AudioContentType,
13
+ AudioCategoryOptions,
14
+ VoiceVocabulary,
12
15
  PlayerState,
13
16
  BufferState,
14
17
  PlaybackMode,
@@ -20,6 +23,7 @@ export type {
20
23
  StateChangeReason,
21
24
  TrackChangeReason,
22
25
  QueueChangeReason,
26
+ QueueChangeDelta,
23
27
  TrackSource,
24
28
  PlayerProgress,
25
29
  PlaybackError,
@@ -28,6 +32,8 @@ export type {
28
32
  SkipCapability,
29
33
  SleepTimerState,
30
34
  NowPlayingFormat,
35
+ NowPlayingFormatIosExtras,
36
+ NowPlayingFormatAndroidExtras,
31
37
  AudioCodec,
32
38
  AudioContainer,
33
39
  // Lookahead cache
@@ -71,6 +77,7 @@ export type {
71
77
  CastSessionDiedEvent,
72
78
  CastLocalNetworkPermissionEvent,
73
79
  CastConnectOptions,
80
+ CastConfigureOptions,
74
81
  // Audio route (cast + on-device)
75
82
  AudioRouteKind,
76
83
  AudioRoute,
@@ -118,10 +125,13 @@ export {
118
125
  nearestBandIndex,
119
126
  } from './equalizer';
120
127
 
128
+ // Pure helper for applying an onQueueChange delta to a queue copy.
129
+ export { applyQueueDelta } from './queueDelta';
130
+
121
131
  // React hooks — thin observers over the HybridObject event surface.
122
132
  export * from './hooks';
123
133
 
124
- // Cast — Promise-based wrappers + listener subscriptions around the
125
- // `CastManager` HybridObject. Consumers can either reach in via
126
- // `getCastManager()` directly or import from `react-native-queue-player/cast`.
134
+ // Cast — `Cast.*`: thin functions over the `CastManager` HybridObject
135
+ // (optional-argument shapes, `CastSubscription` handles, `CastErrorCodes`,
136
+ // `localRoute()`); `getCastManager()` returns the HybridObject itself.
127
137
  export * as Cast from './cast';
@@ -0,0 +1,41 @@
1
+ import type { QueueChangeDelta, QueueChangeReason, TrackItem } from './types';
2
+
3
+ /**
4
+ * Rebuild the queue from the previous copy plus one change.
5
+ *
6
+ * Exported from the package root as the reference for what a consumer must do
7
+ * with a delta; `useQueue` applies it too. The shapes it reads are documented
8
+ * on [QueueChangeDelta].
9
+ */
10
+ export function applyQueueDelta(
11
+ previous: TrackItem[],
12
+ delta: QueueChangeDelta,
13
+ reason: QueueChangeReason
14
+ ): TrackItem[] {
15
+ switch (reason) {
16
+ case 'set-queue':
17
+ return delta.inserted.slice();
18
+ case 'clear':
19
+ return [];
20
+ case 'add': {
21
+ const next = previous.slice();
22
+ next.splice(delta.insertedAt, 0, ...delta.inserted);
23
+ return next;
24
+ }
25
+ case 'remove': {
26
+ const drop = new Set(delta.removed);
27
+ return previous.filter((_, i) => !drop.has(i));
28
+ }
29
+ case 'move': {
30
+ const next = previous.slice();
31
+ const [moved] = next.splice(delta.movedFrom, 1);
32
+ if (moved) next.splice(delta.movedTo, 0, moved);
33
+ return next;
34
+ }
35
+ case 'shuffle':
36
+ // `order[i]` is where the track now at `i` used to be.
37
+ return delta.order.map((from) => previous[from]!);
38
+ default:
39
+ return previous;
40
+ }
41
+ }
package/src/types.ts CHANGED
@@ -44,6 +44,32 @@ export interface TrackItem {
44
44
  artworkUrl?: string;
45
45
  /** Opaque passthrough the consuming app can attach; the module does not read it. */
46
46
  extras?: Record<string, string>;
47
+ /**
48
+ * Consumer-supplied `REPLAYGAIN_TRACK_GAIN` in dB.
49
+ *
50
+ * The four ReplayGain fields are all-or-nothing per track: supplying
51
+ * ANY of them makes this track's ReplayGain come solely from these
52
+ * values, and whatever the file itself carries is discarded in full —
53
+ * including for the mode nothing was supplied for. Supplying a value
54
+ * is the statement of intent; a track that should use the file's own
55
+ * tags carries none of these fields.
56
+ *
57
+ * Presence is what claims the track, not validity: a lone
58
+ * out-of-range peak still discards the file's tags and leaves the
59
+ * track playing at unity.
60
+ */
61
+ replayGainTrackGain?: number;
62
+ /**
63
+ * Consumer-supplied `REPLAYGAIN_TRACK_PEAK`, linear. Accepted in the
64
+ * `0.001 ... 2.0` band — `1.0` is digital full scale and caps a boost
65
+ * at unity. A gain with no usable peak has its boost clamped to unity
66
+ * and reports a `null` peak.
67
+ */
68
+ replayGainTrackPeak?: number;
69
+ /** Consumer-supplied `REPLAYGAIN_ALBUM_GAIN` in dB. */
70
+ replayGainAlbumGain?: number;
71
+ /** Consumer-supplied `REPLAYGAIN_ALBUM_PEAK`, linear. */
72
+ replayGainAlbumPeak?: number;
47
73
  }
48
74
 
49
75
  /**
@@ -89,7 +115,7 @@ export interface PlayerConfig {
89
115
  /**
90
116
  * Auto-retry attempts on transient errors (TIMEOUT / CONNECTION_LOST /
91
117
  * NETWORK_UNREACHABLE / SOURCE_UNREACHABLE / CANNOT_OPEN). Clamped
92
- * to [0, 5]; default 3. Fatal errors (codec / format / auth) are
118
+ * to [0, 5]; default 1. Fatal errors (codec / format / auth) are
93
119
  * never retried regardless of this setting.
94
120
  */
95
121
  autoRetries?: number;
@@ -133,6 +159,30 @@ export interface PlayerConfig {
133
159
  * configure rule as `audioContentType`.
134
160
  */
135
161
  audioCategoryOptions?: AudioCategoryOptions;
162
+ /**
163
+ * Opt in to donating the consumer's catalog to Siri's vocabulary, so voice
164
+ * queries like "Play Pearl Jam" resolve against the app's own artist and
165
+ * playlist names. Enables `donateVoiceVocabulary`, which is inert without
166
+ * it. Default `false`.
167
+ *
168
+ * **iOS only.** Android ignores this field: Google Assistant has no in-app
169
+ * vocabulary donation API, and `donateVoiceVocabulary` is already a no-op
170
+ * there.
171
+ *
172
+ * **Enabling it requires the `com.apple.developer.siri` entitlement** —
173
+ * Xcode's Siri capability — not just `NSSiriUsageDescription` in
174
+ * Info.plist. `INVocabulary` checks for the entitlement at runtime and
175
+ * raises an Objective-C exception without it, which cannot be caught from
176
+ * Swift and terminates the app. iOS gives a process no way to read its own
177
+ * entitlements, so the library cannot verify this on your behalf: leave the
178
+ * flag `false` unless the Siri capability is on the target.
179
+ *
180
+ * **Pinned at the first `configure()` for the life of the process.** The
181
+ * entitlement it depends on is fixed when the app is signed, so there is
182
+ * nothing for a later change to mean; a re-`configure()` with a different
183
+ * value is ignored rather than silently re-arming a crash path.
184
+ */
185
+ voiceVocabularyDonationEnabled?: boolean;
136
186
  /**
137
187
  * Disable the visualization pipeline at engine construction. When
138
188
  * `false`, the lib skips the viz-side audio chain entirely:
@@ -195,6 +245,16 @@ export interface PlayerConfig {
195
245
  * `100`. Control actual runtime usage via `setLookaheadCache`'s `enabled` +
196
246
  * `lookaheadCount`; read the active ceiling back from
197
247
  * `getLookaheadCacheStatus().maxSizeMb`.
248
+ *
249
+ * Where the playing track is protected from eviction, this is a budget rather
250
+ * than a hard cap — that is both policies on iOS, and `'fifo'` on Android
251
+ * (see `EvictionPolicy`). Eviction never takes the playing track or its
252
+ * immediate neighbours, so when those alone exceed the budget nothing is
253
+ * evictable and usage can exceed it —
254
+ * `getLookaheadCacheStatus().currentSizeMb` may read above `maxSizeMb` until
255
+ * the window moves. Reachable with lossless sources and a large
256
+ * `lookaheadCount`; size the budget for `2 + lookaheadCount` tracks to avoid
257
+ * it. The alternative would be evicting the track being played.
198
258
  */
199
259
  lookaheadCacheMaxSizeMb?: number;
200
260
  /**
@@ -471,6 +531,53 @@ export type QueueChangeReason =
471
531
  | 'clear' // clearQueue emptied the queue
472
532
  | 'shuffle'; // shuffleQueue reordered the queue
473
533
 
534
+ /**
535
+ * What a queue mutation did, delivered by `onQueueChange` alongside the reason.
536
+ *
537
+ * Describes the change rather than re-sending the queue, so a reorder costs two
538
+ * numbers instead of one `TrackItem` per entry. Which fields carry meaning
539
+ * depends on the reason; the rest are empty or `-1`.
540
+ *
541
+ * Apply it to your own copy of the queue, or call `getQueue()` if you would
542
+ * rather re-read than track it. `useQueue()` does the former for you.
543
+ *
544
+ * **Not every emptying of the queue is reported.** `destroy()` and `configure()`
545
+ * clear it without an event, and `configure()` runs that teardown on every call,
546
+ * so a mirror held across one is stale with nothing to signal it.
547
+ */
548
+ export interface QueueChangeDelta {
549
+ /**
550
+ * Increments once per emit. A jump of more than one means an event was missed
551
+ * and the mirror should be rebuilt from `getQueue()`. Length alone cannot
552
+ * detect that — `move`, `shuffle` and a same-size `set-queue` all preserve it.
553
+ */
554
+ revision: number;
555
+ /** Queue length after the mutation. */
556
+ length: number;
557
+ /**
558
+ * Tracks now occupying `[insertedAt, insertedAt + inserted.length)`.
559
+ * Populated for `add` and `set-queue`; empty otherwise.
560
+ *
561
+ * `set-queue` carries the whole queue because the subscriber is not
562
+ * necessarily whoever called `setQueue` — and on Android an assistant or
563
+ * Android Auto request replaces the queue with tracks the app never passed in.
564
+ */
565
+ inserted: TrackItem[];
566
+ /** Where `inserted` begins, or `-1` when nothing was inserted. */
567
+ insertedAt: number;
568
+ /** Positions removed, ascending, as they were before the removal. */
569
+ removed: number[];
570
+ /** Position a `move` came from, else `-1`. */
571
+ movedFrom: number;
572
+ /** Position a `move` went to, else `-1`. */
573
+ movedTo: number;
574
+ /**
575
+ * For `shuffle`: `order[i]` is the position the track now at `i` held before
576
+ * the shuffle. Empty for every other reason.
577
+ */
578
+ order: number[];
579
+ }
580
+
474
581
  /**
475
582
  * How the active track is being played:
476
583
  * - `'local'` — `track.url` is a `file://` URL.
@@ -556,10 +663,21 @@ export interface NowPlayingFormat {
556
663
  /** Channel count (1 = mono, 2 = stereo, 6 = 5.1, etc.), or `null`. */
557
664
  channelCount: number | null;
558
665
  /**
559
- * Average / nominal bitrate in bits-per-second. `null` when not
560
- * detectable. Reliable for `file://` + progressive HTTP MP3/M4A/FLAC;
561
- * for HLS, returns the variant's nominal bandwidth from the
562
- * manifest.
666
+ * Average encoded bitrate in bits-per-second, or `null` when the source
667
+ * does not declare one and it cannot be measured.
668
+ *
669
+ * Available for `file://` sources of any codec, and for progressive HTTP
670
+ * and HLS sources whose stream or manifest declares a rate — which MP3,
671
+ * M4A/AAC and HLS variants do.
672
+ *
673
+ * **Not available for remote FLAC.** The FLAC header carries no bitrate
674
+ * — the format does not declare one — so it can only be derived from the
675
+ * file's size over its duration. That works for a local file and needs a
676
+ * request the library does not make for a remote one.
677
+ *
678
+ * A derived figure is the whole-file average, so it includes container
679
+ * headers and embedded artwork: typically a few kbps above the audio-only
680
+ * rate.
563
681
  */
564
682
  bitrate: number | null;
565
683
  /** Track duration in seconds, or `null` when not yet known. */
@@ -685,8 +803,8 @@ export interface PlaybackError {
685
803
  /**
686
804
  * Raw native message — typically `localizedDescription` on iOS or
687
805
  * `PlaybackException.message` on Android. URL query strings are
688
- * stripped before emission to avoid leaking auth tokens (e.g.
689
- * Subsonic `s=…&t=…` salt+token in stream URLs).
806
+ * stripped before emission to avoid leaking auth tokens carried in
807
+ * query strings (e.g. `?token=…`).
690
808
  */
691
809
  nativeMessage: string;
692
810
  /**
@@ -718,9 +836,8 @@ export interface PlaybackError {
718
836
  * (no skip-next on the last track, no skip-previous on the first).
719
837
  *
720
838
  * Wrapped in a struct (rather than two separate boolean callback
721
- * args) so future capability flags can be added without a breaking
722
- * API change. Also works around a Nitrogen 0.35.4 codegen bug for
723
- * `(bool, bool) → void` callback signatures specifically.
839
+ * args) so further capability flags can be added without a breaking
840
+ * API change.
724
841
  */
725
842
  export interface SkipCapability {
726
843
  /** Reflects queue length + current index + repeat mode (symmetric with `canSkipNext`). */
@@ -739,8 +856,7 @@ export interface SkipCapability {
739
856
  * converts to a concrete deadline near the track's end.
740
857
  *
741
858
  * Wrapped in a struct (rather than positional callback args) so fields can be
742
- * added without a breaking change, and to avoid a Nitrogen 0.35.4 codegen bug
743
- * for multi-primitive callback signatures.
859
+ * added without a breaking change.
744
860
  */
745
861
  export interface SleepTimerState {
746
862
  /** `true` while a sleep timer is armed (duration or end-of-track). */
@@ -772,13 +888,54 @@ export interface SleepTimerState {
772
888
  *
773
889
  * Construction-fixed like the cache size: changing it at `configure()` clears
774
890
  * the cache and rebuilds with the new policy.
891
+ *
892
+ * **Protecting the playing track differs by platform.** Eviction runs on every
893
+ * download commit, so without protection the entry backing the track being
894
+ * played can be deleted while it plays.
895
+ *
896
+ * - **iOS** protects under both policies.
897
+ * - **Android** protects under `'fifo'` only. Media3's LRU evictor has no
898
+ * protected-key concept, and its recency bump is per byte-range span rather
899
+ * than per track, so a long track's already-played spans stay evictable while
900
+ * it is still playing. Choose `'fifo'` if that matters to you.
775
901
  */
776
902
  export type EvictionPolicy = 'lru' | 'fifo';
777
903
 
778
904
  export interface LookaheadCacheConfig {
905
+ /**
906
+ * Whether the cache is in use. Enabled, playback of a track the cache holds
907
+ * in full is served from disk with no request, a track it does not hold is
908
+ * fetched from the origin and written as it plays, and the tracks ahead are
909
+ * prefetched. Disabled, every read goes to the origin and nothing is written,
910
+ * whatever the disk already holds; the item already loading keeps the source
911
+ * it opened. The on-disk entries survive a disable and serve again on enable.
912
+ */
779
913
  enabled: boolean;
780
914
  /** Tracks ahead of the current index to proactively cache. 0 disables lookahead; eviction still runs. */
781
915
  lookaheadCount: number;
916
+ /**
917
+ * Whether prefetching may use cellular data. Defaults to `true`.
918
+ *
919
+ * The lookahead cache downloads whole upcoming tracks before playback
920
+ * reaches them, so on a metered connection it can spend a lot of data on
921
+ * tracks the listener skips past. Set `false` to back a "download over
922
+ * Wi-Fi only" setting.
923
+ *
924
+ * Applies at runtime — no `configure()` cycle — and gates prefetch only.
925
+ * Playback is never blocked by it, so the track being played carries on
926
+ * either way.
927
+ *
928
+ * The enforcement point differs, because the platforms offer different
929
+ * primitives:
930
+ * - **iOS** sets it on each prefetch request, and the OS refuses to run that
931
+ * request over cellular. A download already on the wire keeps the value it
932
+ * started with and runs to completion.
933
+ * - **Android** has no per-request equivalent, so prefetch stops being
934
+ * scheduled while the default network is cellular. That also cancels the
935
+ * download in flight, whose partial data stays in the cache and resumes
936
+ * when prefetch is permitted again.
937
+ */
938
+ allowsCellularAccess?: boolean;
782
939
  }
783
940
 
784
941
  export interface CacheStatus {
@@ -1,82 +0,0 @@
1
- package com.margelo.nitro.queueplayer
2
-
3
- import java.util.concurrent.atomic.AtomicReference
4
-
5
- /**
6
- * Single-slot buffer for a voice intent that arrived before the JS
7
- * resolver had registered. Holds at most one request per instance; a
8
- * subsequent [push] replaces any prior entry (latest-wins).
9
- *
10
- * Entries carry a `bufferedAtMillis` timestamp and expire after
11
- * [ttlMillis]. Expired entries are dropped on the next [peek] /
12
- * [drain] without producing a result, so a stale cold-launch intent
13
- * never reaches a JS resolver that registers minutes later.
14
- *
15
- * Cross-platform parity primitive: iOS's `CarPlayBridge` holds an
16
- * analogous `PendingIntentBuffer` for `INPlayMediaIntent` cold-start
17
- * dispatches because Swift Concurrency lacks Kotlin's
18
- * `MutableStateFlow.filterNotNull().first()` suspend-until-non-null
19
- * pattern. On Android the lib's existing `BrowseCallbackRegistry`
20
- * slots + `PlaybackService.dispatchAssistantPlay` already provide the
21
- * same cold-start-race coverage via coroutine suspension, so this
22
- * class is a reusable primitive without a load-bearing Android call
23
- * site today. Kept for symmetry + future single-flight use cases.
24
- *
25
- * Thread-safety: every method is safe to call from any thread —
26
- * [AtomicReference] guards the slot via CAS.
27
- *
28
- * Clock contract: callers must supply a monotonically non-decreasing
29
- * clock. Negative-skew elapsed values are clamped to zero so a
30
- * backwards-moving clock can't extend an entry's effective TTL.
31
- */
32
- internal class PendingIntentBuffer<Request : Any>(
33
- private val ttlMillis: Long = 20_000L,
34
- private val clock: () -> Long = System::currentTimeMillis,
35
- ) {
36
-
37
- private data class Entry<Request : Any>(val request: Request, val bufferedAtMillis: Long)
38
-
39
- private val cell = AtomicReference<Entry<Request>?>(null)
40
-
41
- /** Store [request], replacing any prior entry. */
42
- fun push(request: Request) {
43
- cell.set(Entry(request, clock()))
44
- }
45
-
46
- /**
47
- * Remove and return the buffered request, or `null` if empty or
48
- * expired. Drains the slot in both cases — an expired entry is not
49
- * retained for a later non-expired read.
50
- */
51
- fun drain(): Request? {
52
- val current = cell.getAndSet(null) ?: return null
53
- val elapsed = (clock() - current.bufferedAtMillis).coerceAtLeast(0L)
54
- if (elapsed > ttlMillis) return null
55
- return current.request
56
- }
57
-
58
- /**
59
- * Inspect the buffered request without removing it. Auto-clears an
60
- * expired entry as a side effect so a stale request can't be
61
- * inspected indefinitely.
62
- */
63
- fun peek(): Request? {
64
- while (true) {
65
- val current = cell.get() ?: return null
66
- val elapsed = (clock() - current.bufferedAtMillis).coerceAtLeast(0L)
67
- if (elapsed > ttlMillis) {
68
- if (cell.compareAndSet(current, null)) return null
69
- continue
70
- }
71
- return current.request
72
- }
73
- }
74
-
75
- /** Drop any buffered request without returning it. */
76
- fun clear() {
77
- cell.set(null)
78
- }
79
-
80
- /** True when the slot is empty (or holds only an expired entry). */
81
- fun isEmpty(): Boolean = peek() == null
82
- }
@@ -1,15 +0,0 @@
1
- package com.margelo.nitro.queueplayer.cast.http
2
-
3
- /**
4
- * Single-callback contract by which the host service learns whether
5
- * the local HTTP server has any live sessions. While `active` is true
6
- * the host must keep itself foregrounded so Android doesn't kill the
7
- * process out from under the server.
8
- *
9
- * Cast-core stays unaware of foreground-service mechanics; the
10
- * implementer translates this signal into `startForeground` /
11
- * `stopForeground` etc.
12
- */
13
- fun interface LocalMediaServerLifecycle {
14
- fun onActiveStateChanged(active: Boolean)
15
- }