react-native-queue-player 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/QueuePlayer.podspec +14 -9
  2. package/README.md +3 -3
  3. package/android/build.gradle +7 -0
  4. package/android/consumer-rules.pro +72 -3
  5. package/android/src/main/cpp/airplay2_control.c +4 -4
  6. package/android/src/main/cpp/airplay2_control.h +4 -4
  7. package/android/src/main/cpp/airplay2_jni.cpp +26 -22
  8. package/android/src/main/cpp/airplay2_pair.c +7 -7
  9. package/android/src/main/cpp/airplay2_pair.h +2 -2
  10. package/android/src/main/cpp/airplay2_rtsp.c +8 -8
  11. package/android/src/main/cpp/airplay2_session.c +2 -2
  12. package/android/src/main/cpp/airplay_jni.cpp +20 -72
  13. package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
  14. package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
  15. package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
  16. package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
  17. package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
  18. package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
  19. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
  20. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
  21. package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
  22. package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
  23. package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
  24. package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
  25. package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
  26. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
  27. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
  28. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
  29. package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
  30. package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
  31. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
  32. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
  33. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
  34. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
  35. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
  36. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
  37. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
  38. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
  39. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
  40. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
  41. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
  42. package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
  43. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
  44. package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
  45. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
  46. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
  47. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
  48. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
  49. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
  50. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
  51. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
  52. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
  53. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
  54. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
  55. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
  56. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
  57. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
  58. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
  59. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
  60. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
  61. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
  62. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
  63. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
  64. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
  65. package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
  66. package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
  67. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
  68. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
  69. package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
  70. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
  71. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
  72. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
  73. package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
  74. package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
  75. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
  76. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
  77. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
  78. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
  79. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
  80. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
  81. package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
  82. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
  83. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
  84. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
  85. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
  86. package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
  87. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
  88. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
  89. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
  90. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
  91. package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
  92. package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
  93. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
  94. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
  95. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
  96. package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
  97. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
  98. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
  99. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
  100. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
  101. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
  102. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
  103. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
  104. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
  105. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
  106. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
  107. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
  108. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
  109. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
  110. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
  111. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
  112. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
  113. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
  114. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
  115. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
  116. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
  117. package/app.plugin.js +17 -1
  118. package/ios/AVPlayerItemQueueItemId.swift +26 -12
  119. package/ios/AVQueueBuilder.swift +120 -85
  120. package/ios/ArtworkLoader.swift +0 -7
  121. package/ios/ArtworkResolver.swift +22 -4
  122. package/ios/AssetReplayGainReader.swift +118 -0
  123. package/ios/AudioSession.swift +67 -16
  124. package/ios/AudioTapProvider.swift +284 -149
  125. package/ios/CarPlayBridge.swift +0 -12
  126. package/ios/CarPlayCoordinator.swift +18 -2
  127. package/ios/CarPlaySceneDelegate.swift +2 -2
  128. package/ios/Cast/AirPlayRouteState.swift +9 -8
  129. package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
  130. package/ios/Cast/Core/CastEventBridge.swift +66 -26
  131. package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
  132. package/ios/Cast/Core/CastSession.swift +11 -0
  133. package/ios/Cast/Core/CastTransportRouter.swift +21 -9
  134. package/ios/Cast/Core/LocalMediaServer.swift +41 -52
  135. package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
  136. package/ios/Cast/Core/MediaServerHandle.swift +0 -4
  137. package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
  138. package/ios/CastManager.swift +1 -1
  139. package/ios/CrossfadeEngine.swift +660 -226
  140. package/ios/EQTap.swift +20 -17
  141. package/ios/Equalizer.swift +1 -1
  142. package/ios/FLACStreamInfo.swift +65 -0
  143. package/ios/GaplessEngine.swift +57 -30
  144. package/ios/InputGuards.swift +35 -5
  145. package/ios/LookaheadCache.swift +202 -58
  146. package/ios/LookaheadCachePrefetcher.swift +48 -36
  147. package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
  148. package/ios/NetworkRecoveryPolicy.swift +32 -0
  149. package/ios/NowPlayingFormatExtractor.swift +25 -18
  150. package/ios/NowPlayingInfo.swift +84 -48
  151. package/ios/OutputRouteMonitor.swift +16 -3
  152. package/ios/PendingBrowseRequests.swift +1 -1
  153. package/ios/PlaceholderArtwork.swift +9 -5
  154. package/ios/PlaybackEngine.swift +165 -76
  155. package/ios/PlaybackErrorMapping.swift +3 -3
  156. package/ios/PlaybackModeStateMachine.swift +2 -2
  157. package/ios/PlaybackNetworkMonitor.swift +73 -0
  158. package/ios/PlayerStateDerivation.swift +6 -6
  159. package/ios/QueueMutationArithmetic.swift +25 -0
  160. package/ios/QueueSkipArithmetic.swift +5 -3
  161. package/ios/QueueWindowArithmetic.swift +152 -0
  162. package/ios/ReadThroughServer.swift +283 -0
  163. package/ios/RemoteCommands.swift +44 -4
  164. package/ios/ReplayGainData.swift +22 -3
  165. package/ios/ReplayGainExtractor.swift +70 -27
  166. package/ios/Siri/VoiceDonation.swift +14 -9
  167. package/ios/StreamingBitrateProbe.swift +22 -68
  168. package/ios/SuppliedReplayGain.swift +83 -0
  169. package/ios/Tests/AVQueueBuilderTests.swift +158 -221
  170. package/ios/Tests/ActiveItemEngineStub.swift +44 -0
  171. package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
  172. package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
  173. package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
  174. package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
  175. package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
  176. package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
  177. package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
  178. package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
  179. package/ios/Tests/CrossfadeEngineTests.swift +164 -0
  180. package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
  181. package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
  182. package/ios/Tests/EngineSwapTests.swift +91 -0
  183. package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
  184. package/ios/Tests/EqualizerHybridTests.swift +5 -2
  185. package/ios/Tests/FLACStreamInfoTests.swift +88 -0
  186. package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
  187. package/ios/Tests/InputGuardsTests.swift +41 -0
  188. package/ios/Tests/InterruptionIntentTests.swift +77 -0
  189. package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
  190. package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
  191. package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
  192. package/ios/Tests/LookaheadCacheTests.swift +44 -18
  193. package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
  194. package/ios/Tests/MutationDeferralTests.swift +220 -0
  195. package/ios/Tests/NowPlayingInfoTests.swift +86 -99
  196. package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
  197. package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
  198. package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
  199. package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
  200. package/ios/Tests/PlayerFixtures.swift +48 -0
  201. package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
  202. package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
  203. package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
  204. package/ios/Tests/QueueStateTests.swift +3 -1
  205. package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
  206. package/ios/Tests/QueueWindowSliceTests.swift +162 -0
  207. package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
  208. package/ios/Tests/ReadThroughServerTests.swift +345 -0
  209. package/ios/Tests/RemoteCommandsTests.swift +28 -0
  210. package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
  211. package/ios/Tests/ReplayGainMergeTests.swift +230 -0
  212. package/ios/Tests/RetryRecoveryTests.swift +303 -0
  213. package/ios/Tests/SkipCapabilityTests.swift +233 -8
  214. package/ios/Tests/SkipIndexTests.swift +32 -2
  215. package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
  216. package/ios/Tests/StallRecoveryTests.swift +97 -0
  217. package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
  218. package/ios/Tests/TopUpWindowGateTests.swift +394 -0
  219. package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
  220. package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
  221. package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
  222. package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
  223. package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
  224. package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
  225. package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
  226. package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
  227. package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
  228. package/ios/TrackPlayer+Automotive.swift +101 -0
  229. package/ios/TrackPlayer+Cache.swift +232 -0
  230. package/ios/TrackPlayer+Config.swift +291 -0
  231. package/ios/TrackPlayer+EngineDelegate.swift +209 -0
  232. package/ios/TrackPlayer+EventsAPI.swift +111 -0
  233. package/ios/TrackPlayer+EventsDispatch.swift +968 -0
  234. package/ios/TrackPlayer+EventsWiring.swift +173 -0
  235. package/ios/TrackPlayer+Lifecycle.swift +930 -0
  236. package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
  237. package/ios/TrackPlayer+Queue.swift +940 -0
  238. package/ios/TrackPlayer+Recovery.swift +196 -0
  239. package/ios/TrackPlayer+Skip.swift +455 -0
  240. package/ios/TrackPlayer+SleepTimer.swift +175 -0
  241. package/ios/TrackPlayer+State.swift +108 -0
  242. package/ios/TrackPlayer+Threading.swift +135 -0
  243. package/ios/TrackPlayer+Transport.swift +267 -0
  244. package/ios/TrackPlayer+Window.swift +195 -0
  245. package/ios/TrackPlayer.swift +305 -4610
  246. package/ios/Visualizer.swift +6 -5
  247. package/ios/tests-harness/Podfile +1 -1
  248. package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
  249. package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
  250. package/lib/module/hooks/useActiveTrack.js +29 -22
  251. package/lib/module/hooks/useActiveTrack.js.map +1 -1
  252. package/lib/module/hooks/useCast.js +8 -26
  253. package/lib/module/hooks/useCast.js.map +1 -1
  254. package/lib/module/hooks/useEqualizer.js +19 -12
  255. package/lib/module/hooks/useEqualizer.js.map +1 -1
  256. package/lib/module/hooks/useLookaheadCache.js +3 -7
  257. package/lib/module/hooks/useLookaheadCache.js.map +1 -1
  258. package/lib/module/hooks/useQueue.js +66 -19
  259. package/lib/module/hooks/useQueue.js.map +1 -1
  260. package/lib/module/index.js +6 -3
  261. package/lib/module/index.js.map +1 -1
  262. package/lib/module/queueDelta.js +41 -0
  263. package/lib/module/queueDelta.js.map +1 -0
  264. package/lib/module/types.js +28 -5
  265. package/lib/module/types.js.map +1 -1
  266. package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
  267. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  268. package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
  269. package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
  270. package/lib/typescript/hooks/useCast.d.ts +6 -1
  271. package/lib/typescript/hooks/useCast.d.ts.map +1 -1
  272. package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
  273. package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
  274. package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
  275. package/lib/typescript/hooks/useQueue.d.ts +4 -4
  276. package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
  277. package/lib/typescript/index.d.ts +2 -1
  278. package/lib/typescript/index.d.ts.map +1 -1
  279. package/lib/typescript/queueDelta.d.ts +10 -0
  280. package/lib/typescript/queueDelta.d.ts.map +1 -0
  281. package/lib/typescript/types.d.ts +168 -12
  282. package/lib/typescript/types.d.ts.map +1 -1
  283. package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
  284. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
  285. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
  286. package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
  287. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
  288. package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
  289. package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
  297. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
  298. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
  299. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
  300. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
  301. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
  302. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
  303. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
  304. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
  305. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
  306. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
  307. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
  308. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
  309. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
  310. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
  311. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
  312. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
  313. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
  314. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
  315. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
  316. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
  317. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
  318. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
  319. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
  320. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
  321. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
  322. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
  323. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
  324. package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
  325. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
  326. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
  327. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
  328. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
  329. package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
  330. package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
  331. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
  332. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
  333. package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
  334. package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
  335. package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
  336. package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
  337. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
  338. package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
  339. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
  340. package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
  341. package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
  342. package/package.json +8 -8
  343. package/src/TrackPlayer.nitro.ts +53 -27
  344. package/src/hooks/useActiveTrack.ts +29 -22
  345. package/src/hooks/useCast.ts +14 -15
  346. package/src/hooks/useEqualizer.ts +19 -12
  347. package/src/hooks/useLookaheadCache.ts +3 -7
  348. package/src/hooks/useQueue.ts +66 -17
  349. package/src/index.ts +13 -3
  350. package/src/queueDelta.ts +41 -0
  351. package/src/types.ts +169 -12
  352. package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
  353. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
  354. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
  355. package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
  356. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
  357. package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
  358. package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
  359. package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
  360. package/ios/MetadataReader.swift +0 -555
  361. package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
  362. package/ios/Tests/MetadataReaderTests.swift +0 -603
  363. package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
  364. package/ios/Tests/VoiceDonationTests.swift +0 -26
  365. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
  366. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
  367. /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
@@ -1,189 +0,0 @@
1
- package com.margelo.nitro.queueplayer.cast.airplay
2
-
3
- import android.content.Context
4
- import android.graphics.Bitmap
5
- import android.graphics.BitmapFactory
6
- import android.net.Uri
7
- import android.os.Handler
8
- import android.os.Looper
9
- import android.util.Log
10
- import androidx.media3.common.MediaItem
11
- import androidx.media3.common.Player
12
- import kotlinx.coroutines.*
13
- import java.io.ByteArrayOutputStream
14
- import java.net.URL
15
-
16
- /**
17
- * Syncs ExoPlayer track metadata and playback progress to an [AirPlay2Session].
18
- * Identical logic to [AirPlayMetadataSync] but targets the AP2 session.
19
- */
20
- internal class AirPlay2MetadataSync(
21
- private val context: Context,
22
- private val session: AirPlay2Session,
23
- private val player: Player,
24
- private val scope: CoroutineScope,
25
- ) {
26
- companion object {
27
- private const val TAG = "AP2MetadataSync"
28
- private const val PROGRESS_INTERVAL_MS = 5000L
29
- private const val MAX_ARTWORK_DIM = 600
30
- private const val JPEG_QUALITY = 80
31
- }
32
-
33
- private var progressJob: Job? = null
34
- private var artworkJob: Job? = null
35
- private var lastArtworkUri: String? = null
36
- private var started = false
37
-
38
- // ExoPlayer rejects add/removeListener + state reads off its application
39
- // looper. start()/stop() are driven by session activation/close, and
40
- // close() runs on whichever thread disconnected (the JS thread for a
41
- // user disconnect, the native drain thread on receiver death) — so the
42
- // player touches are marshalled onto the player's looper, run inline when
43
- // already on it.
44
- private val playerHandler = Handler(player.applicationLooper)
45
-
46
- private fun onPlayerLooper(block: () -> Unit) {
47
- if (Looper.myLooper() == player.applicationLooper) block()
48
- else playerHandler.post(block)
49
- }
50
-
51
- private val listener = object : Player.Listener {
52
- override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
53
- sendCurrentMetadata()
54
- }
55
-
56
- override fun onPositionDiscontinuity(
57
- oldPosition: Player.PositionInfo,
58
- newPosition: Player.PositionInfo,
59
- reason: Int,
60
- ) {
61
- if (reason == Player.DISCONTINUITY_REASON_SEEK) {
62
- sendProgress()
63
- }
64
- }
65
-
66
- override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
67
- if (playWhenReady) {
68
- startProgressTimer()
69
- } else {
70
- progressJob?.cancel()
71
- progressJob = null
72
- }
73
- }
74
- }
75
-
76
- fun start() {
77
- if (started) return
78
- started = true
79
- onPlayerLooper {
80
- player.addListener(listener)
81
- sendCurrentMetadata()
82
- if (player.playWhenReady) {
83
- startProgressTimer()
84
- }
85
- }
86
- Log.i(TAG, "Started metadata sync")
87
- }
88
-
89
- fun stop() {
90
- if (!started) return
91
- started = false
92
- progressJob?.cancel()
93
- progressJob = null
94
- artworkJob?.cancel()
95
- artworkJob = null
96
- onPlayerLooper { player.removeListener(listener) }
97
- Log.i(TAG, "Stopped metadata sync")
98
- }
99
-
100
- private fun sendCurrentMetadata() {
101
- val meta = player.currentMediaItem?.mediaMetadata ?: return
102
- val title = meta.title?.toString()
103
- val artist = meta.artist?.toString()
104
- val album = meta.albumTitle?.toString()
105
-
106
- Log.i(TAG, "Sending metadata: title=$title, artist=$artist, album=$album")
107
- session.setMetadata(title, artist, album)
108
- sendProgress()
109
-
110
- val artUri = meta.artworkUri?.toString()
111
- if (artUri != null && artUri != lastArtworkUri) {
112
- lastArtworkUri = artUri
113
- fetchAndSendArtwork(artUri)
114
- }
115
- }
116
-
117
- private fun sendProgress() {
118
- val position = player.currentPosition.toInt()
119
- val duration = player.duration.let { if (it > 0) it.toInt() else 0 }
120
- if (duration > 0) {
121
- session.setProgress(position, duration)
122
- }
123
- }
124
-
125
- private fun startProgressTimer() {
126
- progressJob?.cancel()
127
- progressJob = scope.launch {
128
- while (isActive) {
129
- delay(PROGRESS_INTERVAL_MS)
130
- if (player.isPlaying) {
131
- sendProgress()
132
- }
133
- }
134
- }
135
- }
136
-
137
- private fun fetchAndSendArtwork(uriString: String) {
138
- artworkJob?.cancel()
139
- artworkJob = scope.launch(Dispatchers.IO) {
140
- try {
141
- val uri = Uri.parse(uriString)
142
- val scheme = uri.scheme ?: return@launch
143
-
144
- // Read raw bytes — use ContentResolver for content:// and
145
- // android.resource://, URL for http(s), file for file://
146
- val rawBytes: ByteArray = when (scheme) {
147
- "content", "android.resource" ->
148
- context.contentResolver.openInputStream(uri)
149
- ?.use { it.readBytes() } ?: return@launch
150
- "file" ->
151
- java.io.File(uri.path ?: return@launch).readBytes()
152
- "http", "https" ->
153
- URL(uriString).openStream().use { it.readBytes() }
154
- else -> {
155
- Log.w(TAG, "Unsupported artwork scheme: $scheme")
156
- return@launch
157
- }
158
- }
159
-
160
- val boundsOpts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
161
- BitmapFactory.decodeByteArray(rawBytes, 0, rawBytes.size, boundsOpts)
162
-
163
- var sampleSize = 1
164
- while (boundsOpts.outWidth / sampleSize > MAX_ARTWORK_DIM ||
165
- boundsOpts.outHeight / sampleSize > MAX_ARTWORK_DIM) {
166
- sampleSize *= 2
167
- }
168
-
169
- val decodeOpts = BitmapFactory.Options().apply { inSampleSize = sampleSize }
170
- val bitmap = BitmapFactory.decodeByteArray(rawBytes, 0, rawBytes.size, decodeOpts)
171
- ?: return@launch
172
-
173
- val baos = ByteArrayOutputStream()
174
- bitmap.compress(Bitmap.CompressFormat.JPEG, JPEG_QUALITY, baos)
175
- bitmap.recycle()
176
-
177
- val bytes = baos.toByteArray()
178
- if (isActive) {
179
- session.setArtwork(bytes, "image/jpeg")
180
- Log.i(TAG, "Sent artwork: ${bytes.size} bytes from $uriString")
181
- }
182
- } catch (e: Exception) {
183
- if (e !is CancellationException) {
184
- Log.w(TAG, "Artwork fetch failed for $uriString", e)
185
- }
186
- }
187
- }
188
- }
189
- }
@@ -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
- }
@@ -1,192 +0,0 @@
1
- package com.margelo.nitro.queueplayer
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Assert.assertFalse
5
- import org.junit.Assert.assertNull
6
- import org.junit.Assert.assertTrue
7
- import org.junit.Test
8
- import java.util.concurrent.CountDownLatch
9
- import java.util.concurrent.Executors
10
- import java.util.concurrent.TimeUnit
11
- import java.util.concurrent.atomic.AtomicLong
12
-
13
- /**
14
- * Coverage for the single-slot pending intent buffer.
15
- * Drives push / drain / peek / clear / isEmpty directly with a
16
- * synthetic payload so the generic shape is exercised without
17
- * dragging Nitrogen-bridged request types into the test target.
18
- */
19
- class PendingIntentBufferTest {
20
-
21
- private data class Sample(val payload: String)
22
-
23
- private class FakeClock(start: Long) {
24
- private val now = AtomicLong(start)
25
- fun nowMillis(): Long = now.get()
26
- fun advance(byMillis: Long) {
27
- now.addAndGet(byMillis)
28
- }
29
- }
30
-
31
- private fun makeBuffer(
32
- ttlMillis: Long = 20_000L,
33
- clock: FakeClock,
34
- ): PendingIntentBuffer<Sample> = PendingIntentBuffer(ttlMillis = ttlMillis, clock = clock::nowMillis)
35
-
36
- @Test
37
- fun `empty buffer drains null`() {
38
- val buffer = PendingIntentBuffer<Sample>()
39
- assertNull(buffer.drain())
40
- assertNull(buffer.peek())
41
- assertTrue(buffer.isEmpty())
42
- }
43
-
44
- @Test
45
- fun `push then drain returns the request`() {
46
- val buffer = PendingIntentBuffer<Sample>()
47
- buffer.push(Sample("first"))
48
- assertEquals(Sample("first"), buffer.drain())
49
- }
50
-
51
- @Test
52
- fun `drain empties the buffer`() {
53
- val buffer = PendingIntentBuffer<Sample>()
54
- buffer.push(Sample("first"))
55
- buffer.drain()
56
- assertNull(buffer.drain())
57
- assertTrue(buffer.isEmpty())
58
- }
59
-
60
- @Test
61
- fun `second push replaces the first`() {
62
- val buffer = PendingIntentBuffer<Sample>()
63
- buffer.push(Sample("first"))
64
- buffer.push(Sample("second"))
65
- assertEquals(Sample("second"), buffer.drain())
66
- assertNull(buffer.drain())
67
- }
68
-
69
- @Test
70
- fun `peek does not empty the buffer`() {
71
- val buffer = PendingIntentBuffer<Sample>()
72
- buffer.push(Sample("first"))
73
- assertEquals(Sample("first"), buffer.peek())
74
- assertEquals(Sample("first"), buffer.peek())
75
- assertEquals(Sample("first"), buffer.drain())
76
- }
77
-
78
- @Test
79
- fun `expired entry drains null`() {
80
- val clock = FakeClock(start = 1_000L)
81
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
82
- buffer.push(Sample("stale"))
83
- clock.advance(byMillis = 21_000L)
84
- assertNull(buffer.drain())
85
- }
86
-
87
- @Test
88
- fun `expired entry peeks null and clears`() {
89
- val clock = FakeClock(start = 1_000L)
90
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
91
- buffer.push(Sample("stale"))
92
- clock.advance(byMillis = 25_000L)
93
- assertNull(buffer.peek())
94
- assertTrue(buffer.isEmpty())
95
- assertNull(buffer.drain())
96
- }
97
-
98
- @Test
99
- fun `just before TTL boundary still drains`() {
100
- val clock = FakeClock(start = 1_000L)
101
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
102
- buffer.push(Sample("fresh"))
103
- clock.advance(byMillis = 19_900L)
104
- assertEquals(Sample("fresh"), buffer.drain())
105
- }
106
-
107
- @Test
108
- fun `zero TTL same instant drains`() {
109
- val clock = FakeClock(start = 1_000L)
110
- val buffer = makeBuffer(ttlMillis = 0L, clock = clock)
111
- buffer.push(Sample("now"))
112
- assertEquals(Sample("now"), buffer.drain())
113
- }
114
-
115
- @Test
116
- fun `zero TTL after advance drops entry`() {
117
- val clock = FakeClock(start = 1_000L)
118
- val buffer = makeBuffer(ttlMillis = 0L, clock = clock)
119
- buffer.push(Sample("now"))
120
- clock.advance(byMillis = 1L)
121
- assertNull(buffer.drain())
122
- }
123
-
124
- @Test
125
- fun `negative clock skew clamps to zero elapsed`() {
126
- val clock = FakeClock(start = 1_000L)
127
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
128
- buffer.push(Sample("fresh"))
129
- clock.advance(byMillis = -10_000L)
130
- assertEquals(Sample("fresh"), buffer.drain())
131
- }
132
-
133
- @Test
134
- fun `push after peek cleared expired slot returns fresh entry`() {
135
- val clock = FakeClock(start = 1_000L)
136
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
137
- buffer.push(Sample("stale"))
138
- clock.advance(byMillis = 25_000L)
139
- assertNull(buffer.peek())
140
- assertTrue(buffer.isEmpty())
141
- buffer.push(Sample("fresh"))
142
- assertEquals(Sample("fresh"), buffer.drain())
143
- }
144
-
145
- @Test
146
- fun `clear empties without return`() {
147
- val buffer = PendingIntentBuffer<Sample>()
148
- buffer.push(Sample("first"))
149
- buffer.clear()
150
- assertNull(buffer.drain())
151
- assertTrue(buffer.isEmpty())
152
- }
153
-
154
- @Test
155
- fun `interleaved push and drain serialise safely`() {
156
- val buffer = PendingIntentBuffer<Sample>()
157
- val executor = Executors.newFixedThreadPool(4)
158
- val latch = CountDownLatch(400)
159
- try {
160
- repeat(200) { i ->
161
- executor.submit {
162
- buffer.push(Sample("p-$i"))
163
- latch.countDown()
164
- }
165
- executor.submit {
166
- buffer.drain()
167
- latch.countDown()
168
- }
169
- }
170
- val finished = latch.await(2, TimeUnit.SECONDS)
171
- assertTrue("interleaved push/drain did not finish within 2 s", finished)
172
- } finally {
173
- executor.shutdownNow()
174
- }
175
- }
176
-
177
- @Test
178
- fun `isEmpty reports true after expiry without explicit drain`() {
179
- val clock = FakeClock(start = 1_000L)
180
- val buffer = makeBuffer(ttlMillis = 20_000L, clock = clock)
181
- buffer.push(Sample("stale"))
182
- clock.advance(byMillis = 21_000L)
183
- assertTrue(buffer.isEmpty())
184
- }
185
-
186
- @Test
187
- fun `isEmpty reports false when a fresh entry exists`() {
188
- val buffer = PendingIntentBuffer<Sample>()
189
- buffer.push(Sample("fresh"))
190
- assertFalse(buffer.isEmpty())
191
- }
192
- }
@@ -1,135 +0,0 @@
1
- package com.margelo.nitro.queueplayer
2
-
3
- import androidx.media3.common.MediaItem
4
- import androidx.media3.common.PlaybackException
5
- import androidx.media3.common.util.UnstableApi
6
- import androidx.media3.exoplayer.ExoPlayer
7
- import androidx.media3.exoplayer.audio.TeeAudioProcessor
8
- import androidx.test.core.app.ApplicationProvider
9
- import org.junit.runner.RunWith
10
- import org.robolectric.RobolectricTestRunner
11
- import org.robolectric.annotation.Config
12
- import org.junit.Assert.assertEquals
13
- import org.junit.Assert.assertNotNull
14
- import org.junit.Test
15
-
16
- /**
17
- * Type-check coverage for [PlaybackEngine] + [PlaybackEngineDelegate].
18
- * The interface has no runtime; this suite exercises the type
19
- * surface so a regression that drops a method or rotates a parameter
20
- * shape surfaces immediately. Goes away once the real `GaplessEngine`
21
- * impl pins the type surface from a runtime perspective.
22
- */
23
- @RunWith(RobolectricTestRunner::class)
24
- @Config(sdk = [34])
25
- @UnstableApi
26
- class PlaybackEngineInterfaceTest {
27
-
28
- @Test
29
- fun `PlaybackEngine surface compiles with all method signatures reachable`() {
30
- val stub = NoopPlaybackEngine()
31
- assertNotNull(stub)
32
- // Empty-queue sentinel: -1 (matches TrackPlayer.currentTrackIndex).
33
- assertEquals(-1, stub.currentMediaItemIndex)
34
- assertEquals(0L, stub.currentPositionMs)
35
- assertEquals(0L, stub.currentDurationMs)
36
- assertEquals(0L, stub.bufferedPositionMs)
37
- assertEquals(false, stub.isPlaying)
38
- assertEquals(null, stub.currentMediaItem)
39
- assertEquals(0, stub.allMediaItems.size)
40
- assertEquals(0, stub.currentAudioSessionIds().size)
41
- }
42
-
43
- @Test
44
- fun `audio-session-id listener add returns a disposer`() {
45
- val stub = NoopPlaybackEngine()
46
- val dispose = stub.addAudioSessionIdListener { /* noop */ }
47
- dispose()
48
- }
49
-
50
- @Test
51
- fun `delegate surface compiles with all callbacks`() {
52
- val delegate = NoopPlaybackEngineDelegate()
53
- val stub = NoopPlaybackEngine()
54
- stub.delegate = delegate
55
- assertEquals(delegate, stub.delegate)
56
- }
57
-
58
- /**
59
- * Minimal compile-only stub. Every method is a no-op; the goal is
60
- * to surface a type-system change in [PlaybackEngine] as a Kotlin
61
- * compile error here, not as a silent contract drift.
62
- *
63
- * `mediaSessionPlayer` requires a real ExoPlayer instance for
64
- * type compatibility — the stub builds one against the
65
- * Robolectric application context but never plays anything.
66
- */
67
- private class NoopPlaybackEngine : PlaybackEngine {
68
- override val mediaSessionPlayer: ExoPlayer = ExoPlayer.Builder(
69
- ApplicationProvider.getApplicationContext()
70
- ).build()
71
-
72
- override suspend fun prepare(
73
- mediaItems: List<MediaItem>,
74
- startIndex: Int,
75
- startPositionMs: Long,
76
- ) = Unit
77
-
78
- override fun release() = Unit
79
-
80
- override fun setMediaItems(
81
- items: List<MediaItem>,
82
- startIndex: Int,
83
- startPositionMs: Long,
84
- ) = Unit
85
-
86
- override fun addMediaItems(items: List<MediaItem>, insertBefore: Int) = Unit
87
- override fun removeMediaItems(indices: IntArray) = Unit
88
- override fun moveMediaItem(fromIndex: Int, toIndex: Int) = Unit
89
-
90
- override fun play() = Unit
91
- override fun pause() = Unit
92
- override fun stop() = Unit
93
- override fun seekTo(index: Int, positionMs: Long) = Unit
94
- override fun skipToNext() = Unit
95
- override fun skipToPrevious() = Unit
96
-
97
- override fun setVolume(volume: Float) = Unit
98
- override fun setPlaybackSpeed(speed: Float) = Unit
99
- override fun setRepeatMode(mode: RepeatMode) = Unit
100
- override fun setPitchCorrectionMode(mode: PitchCorrectionMode) = Unit
101
- override fun setWakeMode(mode: Int) = Unit
102
-
103
- override val currentMediaItemIndex: Int = -1
104
- override val currentPositionMs: Long = 0
105
- override val currentDurationMs: Long = 0
106
- override val bufferedPositionMs: Long = 0
107
- override val isPlaying: Boolean = false
108
- override val currentMediaItem: MediaItem? = null
109
- override val isCrossfadeActive: Boolean = false
110
- override val allMediaItems: List<MediaItem> = emptyList()
111
-
112
- override fun currentAudioSessionIds(): IntArray = IntArray(0)
113
-
114
- override fun addAudioSessionIdListener(listener: (IntArray) -> Unit): () -> Unit = { /* noop */ }
115
-
116
- override fun attachAudioBufferSink(sink: TeeAudioProcessor.AudioBufferSink) = Unit
117
- override fun detachAudioBufferSink(sink: TeeAudioProcessor.AudioBufferSink) = Unit
118
-
119
- override suspend fun handoff(to: PlaybackEngine, atTrackBoundary: Boolean) = Unit
120
-
121
- override var delegate: PlaybackEngineDelegate? = null
122
- }
123
-
124
- private class NoopPlaybackEngineDelegate : PlaybackEngineDelegate {
125
- override fun onStateMaybeChanged(engine: PlaybackEngine, playWhenReadyReason: Int?) = Unit
126
- override fun onIsPlayingChanged(engine: PlaybackEngine, isPlaying: Boolean) = Unit
127
- override fun onTrackTransition(engine: PlaybackEngine, platformReason: Int) = Unit
128
- override fun onActiveItemFormatChanged(engine: PlaybackEngine) = Unit
129
- override fun onError(engine: PlaybackEngine, exception: PlaybackException) = Unit
130
- override fun onPlaybackEnded(engine: PlaybackEngine) = Unit
131
- override fun onAutoTransitionDiscontinuity(engine: PlaybackEngine) = Unit
132
- override fun onCrossfadeBegin(engine: PlaybackEngine, incomingIndex: Int) = Unit
133
- override fun onCrossfadeCancel(engine: PlaybackEngine, revertedIndex: Int) = Unit
134
- }
135
- }
@@ -1,27 +0,0 @@
1
- package com.margelo.nitro.queueplayer
2
-
3
- import org.junit.Assert.assertTrue
4
- import org.junit.Test
5
- import org.junit.runner.RunWith
6
- import org.robolectric.RobolectricTestRunner
7
- import org.robolectric.annotation.Config
8
-
9
- /**
10
- * Proves the JUnit + Robolectric test target is
11
- * wired + `./gradlew :react-native-queue-player:testDebugUnitTest`
12
- * executes. Real test suites for the Kotlin audio wrappers
13
- * (MediaItemBuilder, AudioFocus, PlaybackService, LookaheadCache,
14
- * etc.) live alongside their wrapper sources.
15
- *
16
- * @Config sdk=34 because Robolectric 4.11.1 supports up to API 34,
17
- * while our library's compileSdkVersion is 36. When Robolectric bumps
18
- * its max-supported SDK to 36 we drop the annotation.
19
- */
20
- @RunWith(RobolectricTestRunner::class)
21
- @Config(sdk = [34])
22
- class SmokeTest {
23
- @Test
24
- fun smoke_sanity() {
25
- assertTrue("smoke", true)
26
- }
27
- }
@@ -1,110 +0,0 @@
1
- import Foundation
2
- import Network
3
-
4
- /// Wraps `NWPathMonitor` to publish the local IPv4 address over the
5
- /// `.wifi` interface. Cellular addresses can't reach a LAN receiver,
6
- /// so they are intentionally ignored — Chromecast / DLNA / AirPlay
7
- /// targets are all wifi-local by design.
8
- final class LocalAddressMonitor {
9
-
10
- typealias Listener = (String?) -> Void
11
-
12
- final class Subscription {
13
- fileprivate let id: UUID
14
- fileprivate weak var owner: LocalAddressMonitor?
15
- fileprivate init(id: UUID, owner: LocalAddressMonitor) {
16
- self.id = id
17
- self.owner = owner
18
- }
19
- func remove() { owner?.removeListener(id: id) }
20
- }
21
-
22
- private let monitor = NWPathMonitor(requiredInterfaceType: .wifi)
23
- private let queue = DispatchQueue(label: "LocalAddressMonitor")
24
- private let lock = NSLock()
25
- private var listeners: [UUID: Listener] = [:]
26
- private var running = false
27
-
28
- private let addressLock = NSLock()
29
- private var _currentAddress: String?
30
- var currentAddress: String? {
31
- addressLock.lock(); defer { addressLock.unlock() }
32
- return _currentAddress
33
- }
34
-
35
- func start() {
36
- lock.lock()
37
- if running { lock.unlock(); return }
38
- running = true
39
- lock.unlock()
40
- monitor.pathUpdateHandler = { [weak self] path in
41
- guard let self = self else { return }
42
- let addr: String? = (path.status == .satisfied) ? Self.firstWifiIPv4() : nil
43
- self.addressLock.lock()
44
- let changed: Bool = (addr != self._currentAddress)
45
- self._currentAddress = addr
46
- self.addressLock.unlock()
47
- if changed {
48
- self.dispatch(addr)
49
- }
50
- }
51
- monitor.start(queue: queue)
52
- }
53
-
54
- func stop() {
55
- lock.lock()
56
- if !running { lock.unlock(); return }
57
- running = false
58
- lock.unlock()
59
- monitor.cancel()
60
- }
61
-
62
- func addListener(_ listener: @escaping Listener) -> Subscription {
63
- let id = UUID()
64
- lock.lock()
65
- listeners[id] = listener
66
- lock.unlock()
67
- return Subscription(id: id, owner: self)
68
- }
69
-
70
- fileprivate func removeListener(id: UUID) {
71
- lock.lock()
72
- listeners.removeValue(forKey: id)
73
- lock.unlock()
74
- }
75
-
76
- private func dispatch(_ addr: String?) {
77
- lock.lock()
78
- let snapshot = Array(listeners.values)
79
- lock.unlock()
80
- for l in snapshot { l(addr) }
81
- }
82
-
83
- /// Enumerate BSD interfaces and return the first IPv4 address on an
84
- /// `en*` interface (Wi-Fi). Synchronous so callers can probe without
85
- /// waiting for the `NWPathMonitor` first-update callback.
86
- static func firstWifiIPv4() -> String? {
87
- var ifaddrPtr: UnsafeMutablePointer<ifaddrs>?
88
- guard getifaddrs(&ifaddrPtr) == 0, let first = ifaddrPtr else { return nil }
89
- defer { freeifaddrs(ifaddrPtr) }
90
- var ptr: UnsafeMutablePointer<ifaddrs>? = first
91
- while let current = ptr {
92
- defer { ptr = current.pointee.ifa_next }
93
- let name = String(cString: current.pointee.ifa_name)
94
- guard name.hasPrefix("en") else { continue }
95
- guard let sa = current.pointee.ifa_addr else { continue }
96
- guard sa.pointee.sa_family == UInt8(AF_INET) else { continue }
97
- var hostBuf = [CChar](repeating: 0, count: Int(NI_MAXHOST))
98
- let result = getnameinfo(
99
- sa, socklen_t(sa.pointee.sa_len),
100
- &hostBuf, socklen_t(hostBuf.count),
101
- nil, 0, NI_NUMERICHOST
102
- )
103
- if result == 0 {
104
- let host = String(cString: hostBuf)
105
- if !host.isEmpty && host != "0.0.0.0" { return host }
106
- }
107
- }
108
- return nil
109
- }
110
- }