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
@@ -0,0 +1,175 @@
1
+ import Foundation
2
+ import NitroModules
3
+
4
+ extension TrackPlayer {
5
+ func setSleepTimer(seconds: Double) throws -> Promise<Void> {
6
+ return enqueue { self.setSleepTimerInternal(seconds: seconds) }
7
+ }
8
+ func setSleepTimerToTrackEnd() throws -> Promise<Void> {
9
+ return enqueue { self.armSleepTimerToTrackEndInternal() }
10
+ }
11
+ func clearSleepTimer() throws -> Promise<Void> {
12
+ return enqueue { self.clearSleepTimerInternal() }
13
+ }
14
+ func getSleepTimer() throws -> SleepTimerState {
15
+ return onPlayerQueueSync { self.sleepTimerSnapshot() }
16
+ }
17
+ func onSleepTimerChange(
18
+ callback: @escaping (_ state: SleepTimerState) -> Void
19
+ ) throws -> () -> Void {
20
+ let id = sleepTimerListeners.add(callback)
21
+ playerQueue.async { [weak self] in
22
+ guard let self else { return }
23
+ guard self.sleepTimerListeners.contains(id: id) else { return }
24
+ callback(self.sleepTimerSnapshot())
25
+ }
26
+ return { [weak self] in _ = self?.sleepTimerListeners.remove(id: id) }
27
+ }
28
+
29
+ // MARK: - Sleep timer — internal
30
+
31
+ /// Arm a wall-clock duration timer; a non-positive value clears any timer.
32
+ private func setSleepTimerInternal(seconds: Double) {
33
+ guard seconds.isFinite, seconds > 0 else {
34
+ clearSleepTimerInternal()
35
+ return
36
+ }
37
+ restoreSleepTimerVolume()
38
+ sleepTimerCore.armDuration(seconds: seconds, nowMs: Self.nowEpochMs())
39
+ startSleepTimerTick()
40
+ emitSleepTimerChanged()
41
+ }
42
+
43
+ private func armSleepTimerToTrackEndInternal() {
44
+ restoreSleepTimerVolume()
45
+ sleepTimerCore.armEndOfTrack()
46
+ startSleepTimerTick()
47
+ emitSleepTimerChanged()
48
+ }
49
+
50
+ private func clearSleepTimerInternal() {
51
+ let wasActive = sleepTimerCore.isActive
52
+ restoreSleepTimerVolume()
53
+ sleepTimerCore.clear()
54
+ stopSleepTimerTick()
55
+ if wasActive { emitSleepTimerChanged() }
56
+ }
57
+
58
+ private func sleepTimerSnapshot() -> SleepTimerState {
59
+ let now = Self.nowEpochMs()
60
+ return SleepTimerState(
61
+ active: sleepTimerCore.isActive,
62
+ endsAtEpochMs: sleepTimerCore.deadlineEpochMs.map { Double($0) },
63
+ remainingSeconds: sleepTimerCore.remainingSeconds(nowMs: now).map { Double($0) },
64
+ endOfTrack: sleepTimerCore.isEndOfTrack)
65
+ }
66
+
67
+ private func emitSleepTimerChanged() {
68
+ let snapshot = sleepTimerSnapshot()
69
+ sleepTimerListeners.forEach { $0(snapshot) }
70
+ }
71
+
72
+ private func startSleepTimerTick() {
73
+ guard sleepTimerTimer == nil else { return }
74
+ let timer = DispatchSource.makeTimerSource(queue: playerQueue)
75
+ timer.schedule(
76
+ deadline: .now() + Self.sleepTimerTickInterval,
77
+ repeating: Self.sleepTimerTickInterval)
78
+ timer.setEventHandler { [weak self] in
79
+ self?.onSleepTimerTick()
80
+ }
81
+ sleepTimerTimer = timer
82
+ // The source is resumed exactly once and never suspended — a suspended
83
+ // source traps libdispatch on release.
84
+ timer.resume()
85
+ }
86
+
87
+ /// Caller must be on `playerQueue` — cancelling from the source's own
88
+ /// queue is what guarantees no further tick.
89
+ internal func stopSleepTimerTick() {
90
+ sleepTimerTimer?.cancel()
91
+ sleepTimerTimer = nil
92
+ }
93
+
94
+ private func onSleepTimerTick() {
95
+ // While casting, the local engine is intentionally frozen (its position
96
+ // never advances), so feed the state machine the receiver's position /
97
+ // duration instead — otherwise the end-of-track tail rule never trips and
98
+ // `.endOfTrack` stays idle forever.
99
+ let durationSec: Double
100
+ let positionSec: Double
101
+ if let remote = PlaybackStateRouter.shared.activeRemote() {
102
+ let d = Double(remote.lastDurationMs) / 1000.0
103
+ let p = Double(remote.lastPositionMs) / 1000.0
104
+ durationSec = (d.isFinite && d > 0) ? d : 0
105
+ positionSec = (p.isFinite && p > 0) ? p : 0
106
+ } else {
107
+ let duration = self.engine?.currentDurationSeconds ?? 0
108
+ let position = self.engine?.currentPositionSeconds ?? 0
109
+ durationSec = (duration.isFinite && duration > 0) ? duration : 0
110
+ positionSec = (position.isFinite && position > 0) ? position : 0
111
+ }
112
+ let result = sleepTimerCore.tick(
113
+ nowMs: Self.nowEpochMs(),
114
+ trackDurationSec: durationSec,
115
+ trackPositionSec: positionSec)
116
+ applySleepTimerResult(result)
117
+ }
118
+
119
+ /// Apply a `SleepTimerCore.Tick` decision to the engine — fade, state emit,
120
+ /// and the boundary pause. Shared by the polling tick and the real track-end
121
+ /// signal (`handlePlayerItemDidPlayToEndTime`).
122
+ internal func applySleepTimerResult(_ result: SleepTimerCore.Tick) {
123
+ applySleepTimerFade(result.fadeFraction)
124
+ if result.stateChanged { emitSleepTimerChanged() }
125
+ if result.pauseNow {
126
+ // Route the boundary pause to the receiver when casting (the receiver
127
+ // emits its own paused state); only the local-engine pause needs the
128
+ // sleep-timer reason tag.
129
+ if !CastTransportRouter.routePause() {
130
+ self.pendingStateChangeReason = .sleepTimer
131
+ // The timer's pause is a transport intent like the user's: without
132
+ // clearing these, a drain that follows (the timer firing at a track
133
+ // end on the last installed item) or a buffer refill would restart
134
+ // playback the timer just stopped.
135
+ self.wantsToPlay = false
136
+ self.isRecoveringFromStall = false
137
+ self.engine?.pause()
138
+ }
139
+ }
140
+ if !sleepTimerCore.isActive { stopSleepTimerTick() }
141
+ }
142
+
143
+ /// Fade multiplies off `volumeState` without mutating it, so `getVolume`
144
+ /// stays truthful and the pre-fade level is restored on clear/fire.
145
+ private func applySleepTimerFade(_ fraction: Double) {
146
+ // Fade is local-only: the receiver's volume is user/system-owned and
147
+ // `volumeState` tracks the local level, so ramping it on the receiver would
148
+ // clobber the cast volume. When casting the boundary pause still routes to
149
+ // the receiver; the pre-pause fade is skipped. Undo any in-progress local
150
+ // fade first so a cast session appearing mid-fade can't strand the local
151
+ // engine at a ramped-down volume for a later cast→local return.
152
+ if PlaybackStateRouter.shared.activeRemote() != nil {
153
+ restoreSleepTimerVolume()
154
+ return
155
+ }
156
+ if fraction < 1.0 {
157
+ self.engine?.setVolume(Float(Double(self.volumeState) * fraction))
158
+ sleepTimerFading = true
159
+ } else if sleepTimerFading {
160
+ self.engine?.setVolume(self.volumeState)
161
+ sleepTimerFading = false
162
+ }
163
+ }
164
+
165
+ private func restoreSleepTimerVolume() {
166
+ if sleepTimerFading {
167
+ self.engine?.setVolume(self.volumeState)
168
+ sleepTimerFading = false
169
+ }
170
+ }
171
+
172
+ private static func nowEpochMs() -> Int64 {
173
+ Int64(Date().timeIntervalSince1970 * 1000)
174
+ }
175
+ }
@@ -0,0 +1,108 @@
1
+ import Foundation
2
+
3
+ extension TrackPlayer {
4
+ // MARK: - State snapshots
5
+ //
6
+ // Nitro calls these getters on a JS worker thread. Player state lives
7
+ // on `playerQueue`, so every read hops there with `onPlayerQueueSync`,
8
+ // which runs the body inline when the caller is already on that queue
9
+ // (a `sync` from the queue onto itself would deadlock). The cached
10
+ // `canSkip*` Bools are written on `playerQueue` by
11
+ // `recomputeCapabilities()`, so the same hop reads them consistently.
12
+ func getState() throws -> PlayerState {
13
+ // The receiver owns playback during a cast session — the local engine is
14
+ // parked and paused for its duration, so its derived state describes
15
+ // nothing the user can hear. Until the receiver has reported (session
16
+ // still activating, holding its initial `.idle`) the local read is the
17
+ // better answer; after that the receiver is authoritative, including a
18
+ // later `.idle`, which means it genuinely has nothing loaded.
19
+ if let remote = PlaybackStateRouter.shared.activeRemote(), remote.hasReportedState {
20
+ return remote.lastState.asPlayerState
21
+ }
22
+ return onPlayerQueueSync {
23
+ // Both engines derive PlayerState from the same inputs via
24
+ // `engine.timeControlStatus` (gapless returns its AVQueuePlayer's, which
25
+ // is what `self.player` also points at; crossfade returns the active
26
+ // leg's), so gapless + crossfade report identical states. `.none` when no
27
+ // engine is configured.
28
+ guard let engine = self.engine else { return .none }
29
+ return PlayerStateDerivation.translate(
30
+ status: engine.timeControlStatus,
31
+ currentIndex: self.currentTrackIndex,
32
+ hasCurrentItem: engine.currentMediaItem != nil,
33
+ reachedQueueEnd: self.reachedQueueEnd
34
+ )
35
+ }
36
+ }
37
+ func getCurrentTrackIndex() throws -> Double {
38
+ // Nitrogen spec types this return as Double; bridge the internal Int.
39
+ return onPlayerQueueSync { Double(self.currentTrackIndex) }
40
+ }
41
+ func getCurrentTrackSource() throws -> TrackSource? {
42
+ return onPlayerQueueSync { self.currentTrackSource }
43
+ }
44
+ func getCanSkipNext() throws -> Bool {
45
+ return onPlayerQueueSync { self.cachedSkipCapability.canSkipNext }
46
+ }
47
+ func getSkipCapability() throws -> SkipCapability {
48
+ return onPlayerQueueSync { self.cachedSkipCapability }
49
+ }
50
+ func getCanSkipPrevious() throws -> Bool {
51
+ return onPlayerQueueSync { self.cachedSkipCapability.canSkipPrevious }
52
+ }
53
+ func getPlaybackSpeed() throws -> Double {
54
+ return onPlayerQueueSync { Double(self.playbackSpeedState) }
55
+ }
56
+ func getVolume() throws -> Double {
57
+ return onPlayerQueueSync { Double(self.volumeState) }
58
+ }
59
+ func getRemoteControls() throws -> RemoteControlOptions {
60
+ return onPlayerQueueSync {
61
+ let options: RemoteControlOptions = .init(
62
+ skipMode: self.remoteSkipMode,
63
+ forwardJumpInterval: self.remoteForwardInterval,
64
+ backwardJumpInterval: self.remoteBackwardInterval)
65
+ return options
66
+ }
67
+ }
68
+
69
+ func getBufferState() throws -> BufferState {
70
+ return onPlayerQueueSync { self.currentBufferState }
71
+ }
72
+
73
+ func getIsSeekable() throws -> Bool {
74
+ return onPlayerQueueSync {
75
+ guard let item = self.engine?.currentMediaItem else { return true }
76
+ return !item.seekableTimeRanges.isEmpty
77
+ }
78
+ }
79
+
80
+ func isFullyBuffered() throws -> Bool {
81
+ return onPlayerQueueSync { self.currentFullyBuffered }
82
+ }
83
+
84
+ func onBufferStateChange(
85
+ callback: @escaping (_ state: BufferState) -> Void
86
+ ) throws -> () -> Void {
87
+ let id = bufferStateListeners.add(callback)
88
+ // Fire once on subscribe with the current state so a UI renders immediately.
89
+ playerQueue.async { [weak self] in
90
+ guard let self else { return }
91
+ guard self.bufferStateListeners.contains(id: id) else { return }
92
+ callback(self.currentBufferState)
93
+ }
94
+ return { [weak self] in self?.bufferStateListeners.remove(id: id) }
95
+ }
96
+
97
+ func onFullyBufferedChange(
98
+ callback: @escaping (_ fullyBuffered: Bool) -> Void
99
+ ) throws -> () -> Void {
100
+ let id = fullyBufferedListeners.add(callback)
101
+ playerQueue.async { [weak self] in
102
+ guard let self else { return }
103
+ guard self.fullyBufferedListeners.contains(id: id) else { return }
104
+ callback(self.currentFullyBuffered)
105
+ }
106
+ return { [weak self] in self?.fullyBufferedListeners.remove(id: id) }
107
+ }
108
+ }
@@ -0,0 +1,135 @@
1
+ import Foundation
2
+ import NitroModules
3
+
4
+ extension TrackPlayer {
5
+ /// True when the caller is already executing on `playerQueue`.
6
+ ///
7
+ /// `DispatchQueue.sync` runs its body on the calling thread, so a thread
8
+ /// identity test answers the wrong question — it reports "main" for work
9
+ /// serialised on the queue and would let a nested `sync` deadlock. The
10
+ /// queue's own specific travels with the execution context instead.
11
+ internal var isOnPlayerQueue: Bool {
12
+ DispatchQueue.getSpecific(key: playerQueueKey) == ObjectIdentifier(self)
13
+ }
14
+
15
+ /// Claim the audio session, then start the engine.
16
+ ///
17
+ /// Every path that starts playback goes through here so activation cannot be
18
+ /// forgotten at a new one. `activateIfNeeded` is idempotent, so the cost
19
+ /// after the first play is a bool check.
20
+ ///
21
+ /// Activation is deliberately not done at `configure()`: that would stop
22
+ /// whatever else the user is listening to the moment a consumer app
23
+ /// launches, before they have pressed anything.
24
+ internal func beginPlayback() {
25
+ audioSession.activateIfNeeded()
26
+ engine?.play()
27
+ }
28
+
29
+ /// Enqueue `body` on `playerQueue` **at call time**, on the calling thread,
30
+ /// and resolve the returned promise when it completes.
31
+ ///
32
+ /// This is what makes the queue's FIFO order equal the JS call order.
33
+ /// `Promise.async` starts an unstructured `Task`, and two Tasks created back
34
+ /// to back have no ordering relationship with each other — so a body that
35
+ /// hops onto the queue *inside* the Task reaches the player at an arbitrary
36
+ /// later point. Two calls issued in order from JS could then arrive
37
+ /// reversed: `play()` then `pause()` landing as pause-then-play, leaving
38
+ /// audio running after the user paused it.
39
+ ///
40
+ /// A serial queue gives mutual exclusion, not ordering. Ordering comes from
41
+ /// when the work is enqueued, which is why this runs before returning.
42
+ internal func enqueue(_ body: @escaping () -> Void) -> Promise<Void> {
43
+ let promise = Promise<Void>()
44
+ playerQueue.async {
45
+ body()
46
+ promise.resolve()
47
+ }
48
+ return promise
49
+ }
50
+
51
+ /// Enqueue a mutation at call time and hand its result to a receiver mirror
52
+ /// that runs off the queue.
53
+ ///
54
+ /// The enqueue is what fixes ordering against other player work — a later
55
+ /// call cannot overtake this one. Doing it with `async` rather than `sync`
56
+ /// matters just as much: the mutation can rebuild the whole player queue,
57
+ /// and `sync` would run that on the JS thread.
58
+ ///
59
+ /// `mirror` awaits the receiver afterwards, holding neither the queue nor
60
+ /// the calling thread, and is chained behind the previous mutation's mirror
61
+ /// so the receiver sees mutations in the order they were made. The promise
62
+ /// resolves once the mirror returns, so a consumer awaiting the call knows
63
+ /// local and remote are both settled.
64
+ internal func enqueueThenMirror<T>(
65
+ _ body: @escaping () -> T,
66
+ mirror: @escaping (T, _ generation: UInt64) async -> Void
67
+ ) -> Promise<Void> {
68
+ let promise = Promise<Void>()
69
+ playerQueue.async {
70
+ self.queueMutationGeneration &+= 1
71
+ let generation = self.queueMutationGeneration
72
+ let value = body()
73
+ let previous = self.castMirrorChain
74
+ self.castMirrorChain = Task {
75
+ // Deltas are only correct against a receiver that has applied
76
+ // everything before them, so this waits rather than racing. A failed
77
+ // mirror still completes its Task, so one bad round-trip cannot wedge
78
+ // the chain.
79
+ await previous?.value
80
+ await mirror(value, generation)
81
+ promise.resolve()
82
+ }
83
+ }
84
+ return promise
85
+ }
86
+
87
+ /// Value-returning variant of `enqueue`.
88
+ internal func enqueueValue<T>(_ body: @escaping () -> T) -> Promise<T> {
89
+ let promise = Promise<T>()
90
+ playerQueue.async {
91
+ promise.resolve(withResult: body())
92
+ }
93
+ return promise
94
+ }
95
+
96
+ /// Throwing variant of `enqueue` — a thrown error rejects the JS promise
97
+ /// with its message, as `Promise.async` does for a throwing body.
98
+ internal func enqueueThrowing(_ body: @escaping () throws -> Void) -> Promise<Void> {
99
+ let promise = Promise<Void>()
100
+ playerQueue.async {
101
+ do {
102
+ try body()
103
+ promise.resolve()
104
+ } catch {
105
+ promise.reject(withError: error)
106
+ }
107
+ }
108
+ return promise
109
+ }
110
+
111
+ /// Serialise a mutation body on `playerQueue` and return a
112
+ /// Promise-compatible awaitable. Centralising here keeps every access to
113
+ /// player state on one queue, which is what makes the state safe to read
114
+ /// without locks inside these bodies.
115
+ internal func onPlayerQueue(_ body: @escaping () -> Void) async {
116
+ await withCheckedContinuation { (cont: CheckedContinuation<Void, Never>) in
117
+ playerQueue.async {
118
+ body()
119
+ cont.resume()
120
+ }
121
+ }
122
+ }
123
+
124
+ /// Run `body` on `playerQueue` synchronously and return its value. Used by
125
+ /// the Nitro getters and the listener register / dispose paths, which are
126
+ /// not Promise-wrapped and run on whatever thread the JS bridge uses.
127
+ ///
128
+ /// Running inline when already on the queue is what makes re-entrant calls
129
+ /// safe: `playerQueue.sync` from a block already on `playerQueue` is an
130
+ /// immediate deadlock.
131
+ internal func onPlayerQueueSync<T>(_ body: () -> T) -> T {
132
+ if isOnPlayerQueue { return body() }
133
+ return playerQueue.sync(execute: body)
134
+ }
135
+ }