react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.21

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 (660) hide show
  1. package/README.md +35 -21
  2. package/android/build.gradle +35 -12
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
  11. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  12. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
  13. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
  14. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  15. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
  16. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  17. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  18. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  19. package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
  20. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
  21. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
  22. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  23. package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
  24. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
  25. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
  26. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  27. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
  28. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
  29. package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
  30. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  31. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
  32. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  33. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  34. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  35. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  36. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  37. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  38. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  39. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  40. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
  41. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  42. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  43. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
  44. package/android/src/main/res/values/strings.xml +25 -5
  45. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  46. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
  47. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  48. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
  49. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
  50. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  51. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  52. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
  53. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  54. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
  55. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  56. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  57. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  58. package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
  59. package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
  60. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
  61. package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
  62. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  63. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  64. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  65. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  66. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  67. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  68. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  69. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  70. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  71. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  72. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  73. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
  74. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  75. package/ios/Browser/BrowserConfig.swift +102 -5
  76. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  77. package/ios/Browser/BrowserManager.swift +230 -205
  78. package/ios/Browser/BrowserPathHelper.swift +44 -44
  79. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  80. package/ios/Browser/JsonModels.swift +264 -106
  81. package/ios/Browser/SearchDrillIn.swift +7 -7
  82. package/ios/Browser/SectionPresentation.swift +64 -0
  83. package/ios/Browser/SectionScope.swift +58 -24
  84. package/ios/Browser/StyleResolver.swift +58 -0
  85. package/ios/Browser/TabBarEntries.swift +9 -3
  86. package/ios/Browser/TrackIdTemplate.swift +37 -0
  87. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  88. package/ios/CarPlay/CarPlayController.swift +227 -85
  89. package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
  90. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  91. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  92. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  93. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  94. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  95. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  96. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  97. package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
  98. package/ios/Extension/Section+Copying.swift +29 -0
  99. package/ios/Extension/String+IcyCharset.swift +21 -0
  100. package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
  101. package/ios/Extension/Track+Copying.swift +8 -12
  102. package/ios/Extension/Track+Identity.swift +18 -0
  103. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  104. package/ios/Http/HttpClient.swift +8 -2
  105. package/ios/HybridAudioBrowser.swift +253 -100
  106. package/ios/Model/NitroTypeStubs.swift +125 -23
  107. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  108. package/ios/Model/RemoteCommand.swift +18 -0
  109. package/ios/Model/TrackPlayerError.swift +2 -2
  110. package/ios/Option/SessionCategories.swift +3 -2
  111. package/ios/Player/MediaIntentCriteria.swift +17 -9
  112. package/ios/Player/MediaLoader.swift +33 -9
  113. package/ios/Player/NowPlayingUpdater.swift +54 -6
  114. package/ios/Player/PlaybackCoordinator.swift +13 -6
  115. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  116. package/ios/Player/PlaybackTimer.swift +8 -2
  117. package/ios/Player/QueueManager.swift +46 -13
  118. package/ios/Player/RetryManager.swift +80 -18
  119. package/ios/Player/SeekCompletionHandler.swift +3 -1
  120. package/ios/Player/ShuffleOrder.swift +33 -0
  121. package/ios/Player/SleepTimerHandling.swift +13 -0
  122. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  123. package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
  124. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  125. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  126. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  127. package/ios/Tests/JsonModelsTests.swift +150 -1
  128. package/ios/Tests/LRUCacheTests.swift +111 -0
  129. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  130. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  131. package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
  132. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  133. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  134. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  135. package/ios/Tests/QueueManagerTests.swift +122 -10
  136. package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
  137. package/ios/Tests/RemoteCommandTests.swift +67 -0
  138. package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
  139. package/ios/Tests/RetryManagerTests.swift +89 -0
  140. package/ios/Tests/SearchDrillInTests.swift +16 -16
  141. package/ios/Tests/SectionPresentationTests.swift +90 -0
  142. package/ios/Tests/SectionScopeTests.swift +87 -55
  143. package/ios/Tests/StyleResolverTests.swift +127 -0
  144. package/ios/Tests/Support/TestTypes.swift +8 -1
  145. package/ios/Tests/TabBarEntriesTests.swift +19 -19
  146. package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
  147. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  148. package/ios/Tests/TrackIdentityTests.swift +20 -0
  149. package/ios/Tests/TrackSelectorTests.swift +168 -33
  150. package/ios/TrackPlayer.swift +63 -35
  151. package/ios/TrackPlayerCallbacks.swift +5 -38
  152. package/ios/TrackSelector.swift +45 -30
  153. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  154. package/ios/Util/LRUCache.swift +4 -1
  155. package/ios/Util/NetworkMonitor.swift +43 -26
  156. package/ios/Util/SFSymbolRenderer.swift +24 -2
  157. package/ios/Util/SVGProcessor.swift +9 -2
  158. package/lib/commonjs/AudioBrowser.js +8 -8
  159. package/lib/commonjs/AudioBrowser.js.map +1 -1
  160. package/lib/commonjs/CLAUDE.md +52 -0
  161. package/lib/commonjs/features/browser-config.js +12 -8
  162. package/lib/commonjs/features/browser-config.js.map +1 -1
  163. package/lib/commonjs/features/browser.js +11 -3
  164. package/lib/commonjs/features/browser.js.map +1 -1
  165. package/lib/commonjs/features/carConnection.js +1 -1
  166. package/lib/commonjs/features/equalizer.js +1 -1
  167. package/lib/commonjs/features/errors.js +3 -3
  168. package/lib/commonjs/features/favorites.js +14 -11
  169. package/lib/commonjs/features/favorites.js.map +1 -1
  170. package/lib/commonjs/features/gate.js +16 -1
  171. package/lib/commonjs/features/gate.js.map +1 -1
  172. package/lib/commonjs/features/metadata.js +20 -3
  173. package/lib/commonjs/features/metadata.js.map +1 -1
  174. package/lib/commonjs/features/network.js +1 -1
  175. package/lib/commonjs/features/nowPlaying.js +1 -1
  176. package/lib/commonjs/features/output.js +1 -1
  177. package/lib/commonjs/features/playback/controls.js +4 -2
  178. package/lib/commonjs/features/playback/controls.js.map +1 -1
  179. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  180. package/lib/commonjs/features/playback/playing.js +1 -1
  181. package/lib/commonjs/features/playback/progress.js +3 -2
  182. package/lib/commonjs/features/playback/progress.js.map +1 -1
  183. package/lib/commonjs/features/playback/rate.js +8 -1
  184. package/lib/commonjs/features/playback/rate.js.map +1 -1
  185. package/lib/commonjs/features/playback/state.js +3 -4
  186. package/lib/commonjs/features/playback/state.js.map +1 -1
  187. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  188. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  189. package/lib/commonjs/features/playback/volume.js +1 -1
  190. package/lib/commonjs/features/player/options.js +48 -34
  191. package/lib/commonjs/features/player/options.js.map +1 -1
  192. package/lib/commonjs/features/player/setup.js +18 -9
  193. package/lib/commonjs/features/player/setup.js.map +1 -1
  194. package/lib/commonjs/features/player/validateOptions.js +23 -0
  195. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  196. package/lib/commonjs/features/queue/activeTrack.js +15 -4
  197. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  198. package/lib/commonjs/features/queue/queue.js +13 -6
  199. package/lib/commonjs/features/queue/queue.js.map +1 -1
  200. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  201. package/lib/commonjs/features/queue/shuffle.js +1 -1
  202. package/lib/commonjs/features/remoteControls.js +13 -92
  203. package/lib/commonjs/features/remoteControls.js.map +1 -1
  204. package/lib/commonjs/features/sleepTimer.js +13 -1
  205. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  206. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  207. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  208. package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
  209. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  210. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  211. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  212. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  213. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  214. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  215. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  216. package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
  217. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  218. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
  219. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
  220. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  221. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  222. package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
  223. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  224. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  225. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  226. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
  227. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
  228. package/lib/commonjs/web/browser/BrowserManager.js +175 -100
  229. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  230. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  231. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  232. package/lib/commonjs/web/browser/SearchManager.js +18 -23
  233. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  234. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  235. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  236. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  237. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  238. package/lib/commonjs/web/browser/sections.js +89 -0
  239. package/lib/commonjs/web/browser/sections.js.map +1 -0
  240. package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
  241. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  242. package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
  243. package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
  244. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  245. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  246. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  247. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  248. package/lib/module/AudioBrowser.js +2 -1
  249. package/lib/module/AudioBrowser.js.map +1 -1
  250. package/lib/module/CLAUDE.md +52 -0
  251. package/lib/module/features/browser-config.js +12 -8
  252. package/lib/module/features/browser-config.js.map +1 -1
  253. package/lib/module/features/browser.js +11 -3
  254. package/lib/module/features/browser.js.map +1 -1
  255. package/lib/module/features/carConnection.js +1 -1
  256. package/lib/module/features/equalizer.js +1 -1
  257. package/lib/module/features/errors.js +3 -3
  258. package/lib/module/features/favorites.js +14 -11
  259. package/lib/module/features/favorites.js.map +1 -1
  260. package/lib/module/features/gate.js +16 -1
  261. package/lib/module/features/gate.js.map +1 -1
  262. package/lib/module/features/metadata.js +20 -3
  263. package/lib/module/features/metadata.js.map +1 -1
  264. package/lib/module/features/network.js +1 -1
  265. package/lib/module/features/nowPlaying.js +1 -1
  266. package/lib/module/features/output.js +1 -1
  267. package/lib/module/features/playback/controls.js +4 -2
  268. package/lib/module/features/playback/controls.js.map +1 -1
  269. package/lib/module/features/playback/playWhenReady.js +1 -1
  270. package/lib/module/features/playback/playing.js +1 -1
  271. package/lib/module/features/playback/progress.js +3 -2
  272. package/lib/module/features/playback/progress.js.map +1 -1
  273. package/lib/module/features/playback/rate.js +8 -1
  274. package/lib/module/features/playback/rate.js.map +1 -1
  275. package/lib/module/features/playback/state.js +3 -4
  276. package/lib/module/features/playback/state.js.map +1 -1
  277. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  278. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  279. package/lib/module/features/playback/volume.js +1 -1
  280. package/lib/module/features/player/options.js +47 -32
  281. package/lib/module/features/player/options.js.map +1 -1
  282. package/lib/module/features/player/setup.js +17 -8
  283. package/lib/module/features/player/setup.js.map +1 -1
  284. package/lib/module/features/player/validateOptions.js +19 -0
  285. package/lib/module/features/player/validateOptions.js.map +1 -0
  286. package/lib/module/features/queue/activeTrack.js +15 -4
  287. package/lib/module/features/queue/activeTrack.js.map +1 -1
  288. package/lib/module/features/queue/queue.js +13 -6
  289. package/lib/module/features/queue/queue.js.map +1 -1
  290. package/lib/module/features/queue/repeatMode.js +1 -1
  291. package/lib/module/features/queue/shuffle.js +1 -1
  292. package/lib/module/features/remoteControls.js +13 -92
  293. package/lib/module/features/remoteControls.js.map +1 -1
  294. package/lib/module/features/sleepTimer.js +13 -1
  295. package/lib/module/features/sleepTimer.js.map +1 -1
  296. package/lib/module/test-utils/reactNativeStub.js +58 -0
  297. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  298. package/lib/module/utils/LazyNativeEmitter.js +28 -14
  299. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  300. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  301. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  302. package/lib/module/utils/getTrackIdentity.js +20 -0
  303. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  304. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  305. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  306. package/lib/module/web/NativeAudioBrowser.js +200 -75
  307. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  308. package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
  309. package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
  310. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  311. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  312. package/lib/module/web/TrackPlayer/Player.js +254 -39
  313. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  314. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  315. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  316. package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
  317. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
  318. package/lib/module/web/browser/BrowserManager.js +175 -100
  319. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  320. package/lib/module/web/browser/FavoriteManager.js +31 -21
  321. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  322. package/lib/module/web/browser/SearchManager.js +18 -23
  323. package/lib/module/web/browser/SearchManager.js.map +1 -1
  324. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  325. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  326. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  327. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  328. package/lib/module/web/browser/sections.js +83 -0
  329. package/lib/module/web/browser/sections.js.map +1 -0
  330. package/lib/module/web/http/RequestConfigBuilder.js +110 -27
  331. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  332. package/lib/module/web/http/followMediaRedirect.js +215 -0
  333. package/lib/module/web/http/followMediaRedirect.js.map +1 -0
  334. package/lib/module/web/player/NowPlayingManager.js +4 -3
  335. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  336. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  337. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  338. package/lib/typescript/package.json +1 -0
  339. package/lib/typescript/src/AudioBrowser.d.ts +1 -1
  340. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  341. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  342. package/lib/typescript/src/features/browser.d.ts +10 -2
  343. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  344. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  345. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  346. package/lib/typescript/src/features/errors.d.ts +3 -3
  347. package/lib/typescript/src/features/favorites.d.ts +14 -11
  348. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  349. package/lib/typescript/src/features/gate.d.ts +18 -2
  350. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  351. package/lib/typescript/src/features/metadata.d.ts +20 -3
  352. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  353. package/lib/typescript/src/features/network.d.ts +1 -1
  354. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  355. package/lib/typescript/src/features/output.d.ts +1 -1
  356. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  357. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  358. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  359. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  360. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  361. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  362. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  363. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  364. package/lib/typescript/src/features/playback/state.d.ts +3 -4
  365. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  366. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  367. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  368. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  369. package/lib/typescript/src/features/player/options.d.ts +108 -56
  370. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  371. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  372. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  373. package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
  374. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  375. package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
  376. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  377. package/lib/typescript/src/features/queue/queue.d.ts +7 -4
  378. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  379. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  380. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  381. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  382. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  383. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  384. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  385. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  386. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  387. package/lib/typescript/src/types/browser-native.d.ts +3 -2
  388. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  389. package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
  390. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  391. package/lib/typescript/src/types/browser.d.ts +82 -57
  392. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  393. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
  394. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  395. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  396. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  397. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  398. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  399. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  400. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  401. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
  402. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
  403. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  404. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  405. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  406. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  407. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  408. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  409. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  410. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  411. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  412. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  413. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  414. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  415. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  416. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  417. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
  418. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  419. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  420. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
  421. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  422. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  423. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  424. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  425. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
  426. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  427. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  428. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  429. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  430. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
  431. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  432. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
  433. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
  434. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  435. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  436. package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
  437. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  438. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  439. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  440. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  441. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
  442. package/nitrogen/generated/android/c++/JSection.hpp +116 -0
  443. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  444. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  445. package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
  446. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  447. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  448. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  449. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  450. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  451. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  452. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  453. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  454. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  455. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
  456. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  457. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  458. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  459. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  460. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
  461. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  462. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  463. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  464. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  465. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  466. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
  467. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
  468. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  469. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  470. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
  471. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  472. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  473. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
  474. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
  475. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
  476. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
  477. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  478. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  479. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  480. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  481. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
  482. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  483. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  484. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  485. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  486. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  487. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
  488. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  489. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  490. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  491. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  492. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  493. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
  494. package/nitrogen/generated/ios/swift/Section.swift +82 -0
  495. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  496. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  497. package/nitrogen/generated/ios/swift/Track.swift +28 -63
  498. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  499. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  500. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  501. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  502. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  503. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  504. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  505. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  506. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  507. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  508. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  509. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
  510. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  511. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  512. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  513. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  514. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  515. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
  516. package/nitrogen/generated/shared/c++/Section.hpp +102 -0
  517. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  518. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  519. package/nitrogen/generated/shared/c++/Track.hpp +18 -30
  520. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  521. package/package.json +24 -11
  522. package/src/AudioBrowser.ts +2 -1
  523. package/src/CLAUDE.md +52 -0
  524. package/src/features/battery.test.ts +79 -0
  525. package/src/features/browser-config.test.ts +21 -6
  526. package/src/features/browser-config.ts +12 -8
  527. package/src/features/browser.test.ts +1 -1
  528. package/src/features/browser.ts +11 -3
  529. package/src/features/carConnection.ts +1 -1
  530. package/src/features/equalizer.ts +1 -1
  531. package/src/features/errors.ts +3 -3
  532. package/src/features/favorites.ts +14 -11
  533. package/src/features/gate.ts +16 -1
  534. package/src/features/hook-wiring.test.ts +302 -0
  535. package/src/features/metadata.ts +20 -3
  536. package/src/features/network.ts +1 -1
  537. package/src/features/nowPlaying.ts +1 -1
  538. package/src/features/output.ts +1 -1
  539. package/src/features/playback/controls.ts +4 -2
  540. package/src/features/playback/playWhenReady.ts +1 -1
  541. package/src/features/playback/playing.ts +1 -1
  542. package/src/features/playback/progress.test.ts +169 -0
  543. package/src/features/playback/progress.ts +3 -2
  544. package/src/features/playback/rate.ts +8 -1
  545. package/src/features/playback/state.ts +3 -4
  546. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  547. package/src/features/playback/trackPlaybackTime.ts +1 -6
  548. package/src/features/playback/volume.ts +1 -1
  549. package/src/features/player/options.ts +126 -76
  550. package/src/features/player/setup.test.ts +24 -7
  551. package/src/features/player/setup.ts +22 -10
  552. package/src/features/player/validateOptions.ts +21 -0
  553. package/src/features/queue/activeTrack.ts +15 -4
  554. package/src/features/queue/queue.ts +13 -6
  555. package/src/features/queue/repeatMode.ts +1 -1
  556. package/src/features/queue/shuffle.ts +1 -1
  557. package/src/features/remoteControls.ts +13 -92
  558. package/src/features/sleepTimer.test.ts +172 -0
  559. package/src/features/sleepTimer.ts +9 -1
  560. package/src/nitro-stub-drift.test.ts +277 -0
  561. package/src/public-surface.test.ts +147 -0
  562. package/src/specs/audio-browser.nitro.ts +5 -4
  563. package/src/style-resolution.test.ts +210 -0
  564. package/src/test-utils/reactNativeStub.ts +60 -0
  565. package/src/types/browser-native.ts +7 -3
  566. package/src/types/browser-nodes.ts +396 -134
  567. package/src/types/browser.ts +85 -59
  568. package/src/utils/LazyNativeEmitter.ts +28 -15
  569. package/src/utils/NativeUpdatedValue.ts +29 -13
  570. package/src/utils/getTrackIdentity.ts +20 -0
  571. package/src/utils/useDebug.test.ts +182 -0
  572. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  573. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  574. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
  575. package/src/web/NativeAudioBrowser.ts +239 -82
  576. package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
  577. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
  578. package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
  579. package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
  580. package/src/web/TrackPlayer/Player.load.test.ts +137 -1
  581. package/src/web/TrackPlayer/Player.ts +284 -37
  582. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  583. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  584. package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
  585. package/src/web/__tests__/lifecycle.test.ts +345 -0
  586. package/src/web/__tests__/lifecycleHarness.ts +351 -0
  587. package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
  588. package/src/web/__tests__/loadSupersession.test.ts +175 -0
  589. package/src/web/__tests__/retry.test.ts +169 -0
  590. package/src/web/browser/BrowserManager.ts +209 -111
  591. package/src/web/browser/FavoriteManager.test.ts +84 -0
  592. package/src/web/browser/FavoriteManager.ts +28 -23
  593. package/src/web/browser/SearchManager.test.ts +75 -0
  594. package/src/web/browser/SearchManager.ts +25 -28
  595. package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
  596. package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
  597. package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
  598. package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
  599. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  600. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  601. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  602. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  603. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  604. package/src/web/browser/sections.test.ts +91 -0
  605. package/src/web/browser/sections.ts +72 -0
  606. package/src/web/http/RequestConfigBuilder.test.ts +241 -1
  607. package/src/web/http/RequestConfigBuilder.ts +140 -33
  608. package/src/web/http/followMediaRedirect.test.ts +289 -0
  609. package/src/web/http/followMediaRedirect.ts +253 -0
  610. package/src/web/player/NowPlayingManager.ts +4 -3
  611. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  612. package/src/web/util/BrowserPathHelper.ts +61 -13
  613. package/website/guide/android-auto.md +114 -0
  614. package/website/guide/android-certificates.md +409 -0
  615. package/website/guide/artwork.md +255 -0
  616. package/website/guide/audio-output.md +135 -0
  617. package/website/guide/automotive.md +86 -0
  618. package/website/guide/basic-usage.md +151 -0
  619. package/website/guide/battery.md +115 -0
  620. package/website/guide/browser.md +581 -0
  621. package/website/guide/carplay.md +620 -0
  622. package/website/guide/configuration.md +173 -0
  623. package/website/guide/equalizer.md +231 -0
  624. package/website/guide/errors.md +310 -0
  625. package/website/guide/favorites.md +154 -0
  626. package/website/guide/gate.md +143 -0
  627. package/website/guide/getting-started.md +46 -0
  628. package/website/guide/hooks.md +238 -0
  629. package/website/guide/live-streams.md +125 -0
  630. package/website/guide/metadata.md +239 -0
  631. package/website/guide/migrating-from-track-player.md +244 -0
  632. package/website/guide/native-callback-fetch.md +127 -0
  633. package/website/guide/network.md +86 -0
  634. package/website/guide/now-playing.md +175 -0
  635. package/website/guide/playback.md +328 -0
  636. package/website/guide/queue.md +300 -0
  637. package/website/guide/remote-controls.md +256 -0
  638. package/website/guide/search.md +255 -0
  639. package/website/guide/sleep-timer.md +268 -0
  640. package/website/guide/track.md +247 -0
  641. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  642. package/ios/Model/MediaURL.swift +0 -67
  643. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  644. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  645. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  646. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  647. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  648. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  649. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  653. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  654. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  655. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  656. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  657. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  658. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  659. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
  660. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -45,8 +45,15 @@ class QueueManager {
45
45
  /// The repeat mode for the queue. Plain property — no side effects.
46
46
  var repeatMode: RepeatMode = .off
47
47
 
48
- /// Whether shuffle mode is enabled. Plain property — no side effects.
49
- var shuffleEnabled: Bool = false
48
+ /// Whether shuffle mode is enabled.
49
+ var shuffleEnabled: Bool = false {
50
+ didSet {
51
+ // Re-pin on enable: the existing order has the current track wherever it happened to land,
52
+ // so without this, turning shuffle on near the end of the queue ends it right away.
53
+ guard shuffleEnabled, shuffleEnabled != oldValue else { return }
54
+ shuffleOrder.reshuffle(keepingFirst: currentIndex)
55
+ }
56
+ }
50
57
 
51
58
  // MARK: - Computed Properties
52
59
 
@@ -206,7 +213,7 @@ class QueueManager {
206
213
  currentIndex = clampedIndex
207
214
  queueSourcePath = sourcePath
208
215
  tracks = newTracks
209
- shuffleOrder = ShuffleOrder(length: newTracks.count)
216
+ shuffleOrder = ShuffleOrder(length: newTracks.count, firstIndex: clampedIndex)
210
217
  return true
211
218
  }
212
219
 
@@ -218,11 +225,8 @@ class QueueManager {
218
225
  let wasEmpty = tracks.isEmpty
219
226
  let insertIndex = tracks.count
220
227
  tracks.append(contentsOf: newTracks)
228
+ if wasEmpty { return adoptInitialCurrent(initialIndex) }
221
229
  shuffleOrder.insert(at: insertIndex, count: newTracks.count)
222
- if wasEmpty {
223
- currentIndex = max(0, min(initialIndex, tracks.count - 1))
224
- return true
225
- }
226
230
  return false
227
231
  }
228
232
 
@@ -243,14 +247,31 @@ class QueueManager {
243
247
  currentIndex += newTracks.count
244
248
  }
245
249
  tracks.insert(contentsOf: newTracks, at: index)
250
+ if wasEmpty { return adoptInitialCurrent(0) }
246
251
  shuffleOrder.insert(at: index, count: newTracks.count)
247
- if wasEmpty {
248
- currentIndex = 0
249
- return true
250
- }
251
252
  return false
252
253
  }
253
254
 
255
+ /// The queue was empty: `index` becomes the current track AND leads the
256
+ /// shuffle order. One operation because the two must not be separable —
257
+ /// forgetting the second half is the bug this exists to prevent.
258
+ ///
259
+ /// `insert(at:count:)` drops new indices at *random* shuffle positions, so
260
+ /// without the pin the starting track can land last in the order, which reads
261
+ /// as `isLastInPlaybackOrder`: the queue "ends" (and fires
262
+ /// `playerDidEndQueue`) the moment the first track finishes. Pins
263
+ /// unconditionally, like `setQueue` — the order has to be sane whether or not
264
+ /// shuffle is on right now, since `shuffleEnabled`'s didSet only re-pins on
265
+ /// the off→on edge.
266
+ ///
267
+ /// Returns true, so callers can `return adoptInitialCurrent(…)` to report the
268
+ /// current track changed.
269
+ private func adoptInitialCurrent(_ index: Int) -> Bool {
270
+ currentIndex = max(0, min(index, tracks.count - 1))
271
+ shuffleOrder = ShuffleOrder(length: tracks.count, firstIndex: currentIndex)
272
+ return true
273
+ }
274
+
254
275
  /// Remove a track from the queue.
255
276
  /// Returns `true` if the current track changed.
256
277
  @discardableResult
@@ -281,8 +302,18 @@ class QueueManager {
281
302
  let insertion = min(newTracks.count, toIndex)
282
303
  newTracks.insert(track, at: insertion)
283
304
  tracks = newTracks
305
+ // Carry the moved track's shuffle position across the move. `insert(at:count:)`
306
+ // would drop it at a RANDOM position instead — reordering the queue would then
307
+ // silently reorder shuffled playback, and moving the playing track could land it
308
+ // last in the order, ending the queue at the next track boundary with tracks
309
+ // still unplayed (the failure `adoptInitialCurrent` guards against elsewhere).
310
+ // `remove` shifts every later position down by one, so re-inserting at the
311
+ // captured position restores the exact prior order.
312
+ let shufflePosition = shuffleOrder.shufflePosition(of: fromIndex)
284
313
  shuffleOrder.remove(from: fromIndex, to: fromIndex + 1)
285
- shuffleOrder.insert(at: insertion, count: 1)
314
+ shuffleOrder.insert(
315
+ at: insertion, atShufflePosition: shufflePosition ?? shuffleOrder.count,
316
+ )
286
317
  // The pointer follows the playing track; its identity never changes on a
287
318
  // move, so no caller needs to reload (returning true reloads).
288
319
  if currentIndex == fromIndex {
@@ -310,7 +341,9 @@ class QueueManager {
310
341
  // MARK: - Other Mutations
311
342
 
312
343
  /// Replace the track at a specific index.
313
- func replace(_ index: Int, _ track: Track) {
344
+ /// Throws like every other index-taking mutation here, rather than trapping.
345
+ func replace(_ index: Int, _ track: Track) throws {
346
+ try throwIfIndexInvalid(index: index)
314
347
  tracks[index] = track
315
348
  }
316
349
 
@@ -1,3 +1,4 @@
1
+ import AVFoundation
1
2
  import Foundation
2
3
  #if canImport(NitroModules)
3
4
  import NitroModules
@@ -122,30 +123,90 @@ class RetryManager {
122
123
 
123
124
  // MARK: - Error Classification
124
125
 
125
- func isRetryable(_ error: Error?) -> Bool {
126
+ /// HTTP statuses worth another attempt, beyond the 500–599 range checked in
127
+ /// `isRetryable`. Android's `RetryLoadErrorHandlingPolicy` shares 408/429 but
128
+ /// enumerates only 500/502/503/504 of the 5xx range, so uncommon 5xx codes
129
+ /// (501, 505…) retry here and not there.
130
+ private static let retryableHTTPStatusCodes: Set<Int> = [408, 429]
131
+
132
+ /// AVFoundation failures a retry cannot fix: the media is unusable rather
133
+ /// than the transport unreliable. Everything not listed falls through to
134
+ /// retry — `AVErrorUnknown` especially, which is the wrapper AVFoundation
135
+ /// puts around opaque CoreMedia transport failures.
136
+ private static let fatalAVErrorCodes: Set<Int> = [
137
+ AVError.fileFormatNotRecognized.rawValue,
138
+ AVError.failedToParse.rawValue,
139
+ AVError.decodeFailed.rawValue,
140
+ AVError.contentIsProtected.rawValue,
141
+ AVError.contentIsNotAuthorized.rawValue,
142
+ ]
143
+
144
+ /// Whether another attempt could plausibly succeed.
145
+ ///
146
+ /// Mirrors Android's `classifyError`, including its default: an unrecognized
147
+ /// failure is treated as *transient* and retried, bounded by the retry
148
+ /// budgets, rather than being terminal. Defaulting the other way made
149
+ /// `retry: true` largely inert on iOS, because AVFoundation reports most
150
+ /// stream failures as opaque `CoreMediaErrorDomain` / `AVFoundationErrorDomain`
151
+ /// errors rather than `URLError`.
152
+ ///
153
+ /// - Parameter httpStatusCode: The status from the item's error log, when the
154
+ /// caller has one. `AVPlayerItem.error` never carries it — a 404 arrives as
155
+ /// an opaque CoreMedia error — so this is the only reliable HTTP signal and
156
+ /// it outranks the error itself.
157
+ func isRetryable(_ error: Error?, httpStatusCode: Int?) -> Bool {
158
+ if let status = httpStatusCode {
159
+ let retryable = Self.retryableHTTPStatusCodes.contains(status) || (500 ... 599).contains(status)
160
+ logger.debug("HTTP \(status) \(retryable ? "is" : "not") retryable")
161
+ return retryable
162
+ }
163
+
126
164
  guard let error else { return false }
127
165
 
128
- if let urlError = error as? URLError {
129
- switch urlError.code {
130
- // Transient network errors - safe to retry
131
- case .timedOut,
132
- .networkConnectionLost,
133
- .notConnectedToInternet,
134
- .cannotConnectToHost,
135
- .cannotFindHost,
136
- .dnsLookupFailed:
137
- logger.debug("URLError is retryable: code=\(urlError.code.rawValue)")
138
- return true
139
- default:
140
- logger.debug("URLError not retryable: code=\(urlError.code.rawValue)")
166
+ // Walk the underlying-error chain: AVFoundation routinely wraps the real
167
+ // cause (a URLError, or a CoreMedia code) in an AVError.
168
+ for candidate in Self.errorChain(error) {
169
+ if let urlError = candidate as? URLError {
170
+ switch urlError.code {
171
+ // Transient network errors - safe to retry
172
+ case .timedOut,
173
+ .networkConnectionLost,
174
+ .notConnectedToInternet,
175
+ .cannotConnectToHost,
176
+ .cannotFindHost,
177
+ .dnsLookupFailed:
178
+ logger.debug("URLError is retryable: code=\(urlError.code.rawValue)")
179
+ return true
180
+ default:
181
+ logger.debug("URLError not retryable: code=\(urlError.code.rawValue)")
182
+ return false
183
+ }
184
+ }
185
+
186
+ let nsError = candidate as NSError
187
+ if nsError.domain == AVFoundationErrorDomain,
188
+ Self.fatalAVErrorCodes.contains(nsError.code)
189
+ {
190
+ logger.debug("AVError not retryable: code=\(nsError.code)")
141
191
  return false
142
192
  }
143
193
  }
144
194
 
145
- // Unknown errors - don't retry
146
195
  let nsError = error as NSError
147
- logger.debug("Error not retryable: domain=\(nsError.domain), code=\(nsError.code)")
148
- return false
196
+ logger.debug("Treating as transient: domain=\(nsError.domain), code=\(nsError.code)")
197
+ return true
198
+ }
199
+
200
+ /// The error and its `NSUnderlyingError` ancestors, outermost first.
201
+ private static func errorChain(_ error: Error) -> [Error] {
202
+ var chain: [Error] = []
203
+ var current: Error? = error
204
+ // Bounded: a malformed cycle must not spin here.
205
+ while let error = current, chain.count < 8 {
206
+ chain.append(error)
207
+ current = (error as NSError).userInfo[NSUnderlyingErrorKey] as? Error
208
+ }
209
+ return chain
149
210
  }
150
211
 
151
212
  // MARK: - Retry Management
@@ -170,7 +231,8 @@ class RetryManager {
170
231
  return "max retry duration (\(maxRetryDuration)s)"
171
232
  }
172
233
  if !hasPlayed, isOnline, let start = firstOnlineRetryTime,
173
- now.timeIntervalSince(start) >= firstConnectMaxRetryDuration {
234
+ now.timeIntervalSince(start) >= firstConnectMaxRetryDuration
235
+ {
174
236
  return "first-connect retry duration (\(firstConnectMaxRetryDuration)s)"
175
237
  }
176
238
  return nil
@@ -1,3 +1,5 @@
1
- @MainActor protocol SeekCompletionHandler: AnyObject {
1
+ // Sendable so LoadSeekCoordinator can carry a delegate into AVPlayer's
2
+ // @Sendable seek completion closure; @MainActor is what keeps that sound.
3
+ @MainActor protocol SeekCompletionHandler: AnyObject, Sendable {
2
4
  func handleSeekCompleted(to seconds: Double, didFinish: Bool)
3
5
  }
@@ -39,6 +39,16 @@ struct ShuffleOrder {
39
39
  indexInShuffled = Self.buildIndexInShuffled(from: shuffled)
40
40
  }
41
41
 
42
+ /// Creates a shuffle order for the given length with `firstIndex` pinned to shuffle position 0.
43
+ ///
44
+ /// Playback starts on that track, so leaving it wherever the permutation put it would end the
45
+ /// queue as soon as it is reached — on average after half the queue, and immediately whenever it
46
+ /// lands last.
47
+ init(length: Int, firstIndex: Int) {
48
+ self.init(length: length)
49
+ reshuffle(keepingFirst: firstIndex)
50
+ }
51
+
42
52
  /// Creates a shuffle order with pre-defined indices
43
53
  init(shuffledIndices: [Int]) {
44
54
  shuffled = shuffledIndices
@@ -91,6 +101,13 @@ struct ShuffleOrder {
91
101
  return indexInShuffled[index] == 0
92
102
  }
93
103
 
104
+ /// The item's position in the shuffle order, or nil when it has none.
105
+ func shufflePosition(of index: Int) -> Int? {
106
+ guard index >= 0, index < indexInShuffled.count else { return nil }
107
+ let position = indexInShuffled[index]
108
+ return position >= 0 ? position : nil
109
+ }
110
+
94
111
  // MARK: - Mutation
95
112
 
96
113
  /// Inserts items at random positions in the shuffle order
@@ -115,6 +132,22 @@ struct ShuffleOrder {
115
132
  indexInShuffled = Self.buildIndexInShuffled(from: shuffled)
116
133
  }
117
134
 
135
+ /// Inserts one item at `insertionIndex`, placing it at an explicit shuffle
136
+ /// position rather than a random one.
137
+ ///
138
+ /// Used by a queue `move`, which permutes positions but not identities: the
139
+ /// item already had a place in the playback order, and re-randomising it
140
+ /// silently reorders playback. For the *playing* track it can drop it at the
141
+ /// end of the order, which then reads as `isLastInPlaybackOrder` — the queue
142
+ /// reports itself over at the next track end, with tracks still unplayed.
143
+ mutating func insert(at insertionIndex: Int, atShufflePosition position: Int) {
144
+ for i in 0 ..< shuffled.count where shuffled[i] >= insertionIndex {
145
+ shuffled[i] += 1
146
+ }
147
+ shuffled.insert(insertionIndex, at: min(max(0, position), shuffled.count))
148
+ indexInShuffled = Self.buildIndexInShuffled(from: shuffled)
149
+ }
150
+
118
151
  /// Removes items in the specified range from the shuffle order
119
152
  mutating func remove(from indexFrom: Int, to indexToExclusive: Int) {
120
153
  let removeCount = indexToExclusive - indexFrom
@@ -12,4 +12,17 @@ import Foundation
12
12
  @discardableResult func clear() -> Bool
13
13
  func onTrackChanged()
14
14
  func onTrackPlayedToEnd()
15
+
16
+ // Consumer-facing surface — HybridAudioBrowser drives these through
17
+ // TrackPlayer, so they belong here rather than behind a concrete downcast.
18
+
19
+ /// Invoked when the timer state changes (set, cleared, completed).
20
+ var onChanged: ((SleepTimerState) -> Void)? { get set }
21
+ /// The current timer state, or nil when no timer is active.
22
+ func get() -> SleepTimerState?
23
+ /// Sets a time-based timer, optionally fading out over the last
24
+ /// `fadeDuration` seconds.
25
+ func set(seconds: TimeInterval, fadeDuration: TimeInterval?)
26
+ /// Sets the timer to stop playback when the current track ends.
27
+ func setToEndOfTrack()
15
28
  }
@@ -0,0 +1,47 @@
1
+ #if canImport(NitroModules)
2
+ import NitroModules
3
+ #endif
4
+
5
+ /// The callbacks `RemoteCommandController` invokes when the system delivers a
6
+ /// remote command (lock screen, Control Center, CarPlay, headset).
7
+ ///
8
+ /// Split out of `TrackPlayerCallbacks` — which the controller does not otherwise
9
+ /// use — so the controller depends only on what it calls. `TrackPlayerCallbacks`
10
+ /// extends this protocol, so conformers implement both at once.
11
+ @MainActor protocol RemoteCommandCallbacks: AnyObject {
12
+ /// Called when play is triggered remotely.
13
+ func remotePlay()
14
+
15
+ /// Called when pause is triggered remotely.
16
+ func remotePause()
17
+
18
+ /// Called when stop is triggered remotely.
19
+ func remoteStop()
20
+
21
+ /// Called when toggle play/pause is triggered remotely.
22
+ func remotePlayPause()
23
+
24
+ /// Called when next is triggered remotely.
25
+ func remoteNext()
26
+
27
+ /// Called when previous is triggered remotely.
28
+ func remotePrevious()
29
+
30
+ /// Called when jump forward is triggered remotely.
31
+ func remoteJumpForward(interval: Double)
32
+
33
+ /// Called when jump backward is triggered remotely.
34
+ func remoteJumpBackward(interval: Double)
35
+
36
+ /// Called when seek is triggered remotely.
37
+ func remoteSeek(position: Double)
38
+
39
+ /// Called when repeat mode change is triggered remotely (CarPlay/lock screen).
40
+ func remoteChangeRepeatMode(mode: RepeatMode)
41
+
42
+ /// Called when shuffle mode change is triggered remotely (CarPlay/lock screen).
43
+ func remoteChangeShuffleMode(enabled: Bool)
44
+
45
+ /// Called when playback rate change is triggered remotely (CarPlay/lock screen).
46
+ func remoteChangePlaybackRate(rate: Float)
47
+ }
@@ -46,7 +46,7 @@ class RemoteCommandController {
46
46
  private let logger = Logger(subsystem: "com.audiobrowser", category: "RemoteCommandController")
47
47
  private var center: MPRemoteCommandCenter
48
48
 
49
- weak var callbacks: TrackPlayerCallbacks?
49
+ weak var callbacks: RemoteCommandCallbacks?
50
50
 
51
51
  var commandTargetPointers: [String: Any] = [:]
52
52
  private var enabledCommands: [RemoteCommand] = []
@@ -72,7 +72,7 @@ class RemoteCommandController {
72
72
  */
73
73
  init(
74
74
  remoteCommandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared(),
75
- callbacks: TrackPlayerCallbacks? = nil,
75
+ callbacks: RemoteCommandCallbacks? = nil,
76
76
  ) {
77
77
  center = remoteCommandCenter
78
78
  self.callbacks = callbacks
@@ -112,13 +112,16 @@ class RemoteCommandController {
112
112
  }
113
113
 
114
114
  func enable(commands: [RemoteCommand]) {
115
- let commandsToDisable = enabledCommands.filter { command in
116
- !commands.contains(command)
117
- }
115
+ let commandsToDisable = RemoteCommand.commandsToDisable(
116
+ enabled: enabledCommands, replacedBy: commands,
117
+ )
118
118
 
119
119
  enabledCommands = commands
120
- commands.forEach { self.enable(command: $0) }
120
+ // Disable first. The key diff already makes the two sets disjoint, so this
121
+ // is belt-and-braces: it keeps a disable from tearing down a command the
122
+ // enable pass just configured should the two ever overlap again.
121
123
  disable(commands: commandsToDisable)
124
+ commands.forEach { self.enable(command: $0) }
122
125
  }
123
126
 
124
127
  func disable(commands: [RemoteCommand]) {
@@ -278,24 +281,59 @@ class RemoteCommandController {
278
281
 
279
282
  // MARK: - Handlers
280
283
 
281
- lazy var handlePlayCommand: RemoteCommandHandler = handlePlayCommandDefault
282
- lazy var handlePauseCommand: RemoteCommandHandler = handlePauseCommandDefault
283
- lazy var handleStopCommand: RemoteCommandHandler = handleStopCommandDefault
284
- lazy var handleTogglePlayPauseCommand: RemoteCommandHandler =
285
- handleTogglePlayPauseCommandDefault
286
- lazy var handleSkipForwardCommand: RemoteCommandHandler = handleSkipForwardCommandDefault
287
- lazy var handleSkipBackwardCommand: RemoteCommandHandler = handleSkipBackwardDefault
288
- lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler =
289
- handleChangePlaybackPositionCommandDefault
290
- lazy var handleNextTrackCommand: RemoteCommandHandler = handleNextTrackCommandDefault
291
- lazy var handlePreviousTrackCommand: RemoteCommandHandler =
292
- handlePreviousTrackCommandDefault
293
- lazy var handleChangeRepeatModeCommand: RemoteCommandHandler =
294
- handleChangeRepeatModeCommandDefault
295
- lazy var handleChangeShuffleModeCommand: RemoteCommandHandler =
296
- handleChangeShuffleModeCommandDefault
297
- lazy var handleChangePlaybackRateCommand: RemoteCommandHandler =
298
- handleChangePlaybackRateCommandDefault
284
+ // Each default is wrapped in a `[weak self]` closure rather than assigned as a
285
+ // bare method reference (`= handlePlayCommandDefault`): an unapplied instance
286
+ // method curries to `{ [self] in self.method($0) }`, which strongly captures
287
+ // self. Stored back onto self, that is a permanent cycle — `enable(command:)`
288
+ // forces every lazy init, so the controller would never deallocate, leaking one
289
+ // per player instance across JS reloads and re-setups.
290
+ lazy var handlePlayCommand: RemoteCommandHandler = { [weak self] event in
291
+ self?.handlePlayCommandDefault(event: event) ?? .commandFailed
292
+ }
293
+
294
+ lazy var handlePauseCommand: RemoteCommandHandler = { [weak self] event in
295
+ self?.handlePauseCommandDefault(event: event) ?? .commandFailed
296
+ }
297
+
298
+ lazy var handleStopCommand: RemoteCommandHandler = { [weak self] event in
299
+ self?.handleStopCommandDefault(event: event) ?? .commandFailed
300
+ }
301
+
302
+ lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { [weak self] event in
303
+ self?.handleTogglePlayPauseCommandDefault(event: event) ?? .commandFailed
304
+ }
305
+
306
+ lazy var handleSkipForwardCommand: RemoteCommandHandler = { [weak self] event in
307
+ self?.handleSkipForwardCommandDefault(event: event) ?? .commandFailed
308
+ }
309
+
310
+ lazy var handleSkipBackwardCommand: RemoteCommandHandler = { [weak self] event in
311
+ self?.handleSkipBackwardDefault(event: event) ?? .commandFailed
312
+ }
313
+
314
+ lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { [weak self] event in
315
+ self?.handleChangePlaybackPositionCommandDefault(event: event) ?? .commandFailed
316
+ }
317
+
318
+ lazy var handleNextTrackCommand: RemoteCommandHandler = { [weak self] event in
319
+ self?.handleNextTrackCommandDefault(event: event) ?? .commandFailed
320
+ }
321
+
322
+ lazy var handlePreviousTrackCommand: RemoteCommandHandler = { [weak self] event in
323
+ self?.handlePreviousTrackCommandDefault(event: event) ?? .commandFailed
324
+ }
325
+
326
+ lazy var handleChangeRepeatModeCommand: RemoteCommandHandler = { [weak self] event in
327
+ self?.handleChangeRepeatModeCommandDefault(event: event) ?? .commandFailed
328
+ }
329
+
330
+ lazy var handleChangeShuffleModeCommand: RemoteCommandHandler = { [weak self] event in
331
+ self?.handleChangeShuffleModeCommandDefault(event: event) ?? .commandFailed
332
+ }
333
+
334
+ lazy var handleChangePlaybackRateCommand: RemoteCommandHandler = { [weak self] event in
335
+ self?.handleChangePlaybackRateCommandDefault(event: event) ?? .commandFailed
336
+ }
299
337
 
300
338
  private func handlePlayCommandDefault(event _: MPRemoteCommandEvent)
301
339
  -> MPRemoteCommandHandlerStatus
@@ -74,6 +74,56 @@ import Testing
74
74
  #expect(result == "/items?a=1&m=2&z=3")
75
75
  }
76
76
 
77
+ // MARK: - build
78
+
79
+ @Test func buildAppendsTrackIdWithQuestionMark() {
80
+ let result = BrowserPathHelper.build(parentPath: "/library", trackId: "song.mp3")
81
+ #expect(result == "/library?__trackId=song.mp3")
82
+ }
83
+
84
+ @Test func buildAppendsTrackIdWithAmpersandWhenParentHasQuery() {
85
+ let result = BrowserPathHelper.build(parentPath: "/search?q=jazz", trackId: "song.mp3")
86
+ #expect(result == "/search?q=jazz&__trackId=song.mp3")
87
+ }
88
+
89
+ @Test func buildRoundTripsSrcWithQueryParams() {
90
+ // A src carrying its own query string (signed CDN URL) must survive the
91
+ // build → extract/strip round-trip: an unescaped `&` splits the src into
92
+ // stray query params, truncating the trackId and polluting the parent path.
93
+ let src = "https://cdn.example.com/stream.mp3?token=abc&exp=1699999999"
94
+ let url = BrowserPathHelper.build(parentPath: "/library", trackId: src)
95
+ #expect(BrowserPathHelper.extractTrackId(url) == src)
96
+ #expect(BrowserPathHelper.stripTrackId(url) == "/library")
97
+ }
98
+
99
+ @Test func buildRoundTripsSrcWithEqualsAndPlus() {
100
+ let src = "https://cdn.example.com/a+b.mp3?sig=x=y"
101
+ let url = BrowserPathHelper.build(parentPath: "/library", trackId: src)
102
+ #expect(BrowserPathHelper.extractTrackId(url) == src)
103
+ #expect(BrowserPathHelper.stripTrackId(url) == "/library")
104
+ }
105
+
106
+ @Test func buildStampsTheIndexAndRoundTripsIt() {
107
+ let src = "https://cdn.example.com/stream.mp3?token=abc&exp=1"
108
+ let url = BrowserPathHelper.build(parentPath: "/library", trackId: src, index: 3)
109
+ #expect(BrowserPathHelper.extractTrackId(url) == src)
110
+ #expect(BrowserPathHelper.extractIndex(url) == 3)
111
+ #expect(BrowserPathHelper.stripTrackId(url) == "/library")
112
+ }
113
+
114
+ // MARK: - extractIndex
115
+
116
+ @Test func extractIndexReturnsNilWithoutAStampedIndex() {
117
+ let url = BrowserPathHelper.build(parentPath: "/library", trackId: "song.mp3")
118
+ #expect(BrowserPathHelper.extractIndex(url) == nil)
119
+ }
120
+
121
+ @Test func extractIndexReturnsNilForNonContextualOrMalformedIndex() {
122
+ #expect(BrowserPathHelper.extractIndex("/library") == nil)
123
+ #expect(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=x") == nil)
124
+ #expect(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=-1") == nil)
125
+ }
126
+
77
127
  // MARK: - stripTrackId
78
128
 
79
129
  @Test func stripTrackIdRemovesTrackParam() {
@@ -116,23 +166,3 @@ import Testing
116
166
  @Test func isContextualReturnsTrueWithAmpersandSeparator() {
117
167
  #expect(BrowserPathHelper.isContextual("/lib?q=1&__trackId=x") == true)
118
168
  }
119
-
120
- // MARK: - containsSegment
121
-
122
- @Test func containsSegmentMatchesFullSegments() {
123
- let path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
124
- #expect(BrowserPathHelper.containsSegment(path, "Gw0LGB8j"))
125
- #expect(BrowserPathHelper.containsSegment(path, "amsterdam-funk-channel"))
126
- #expect(BrowserPathHelper.containsSegment(path, "listen"))
127
- #expect(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j?hl=en", "Gw0LGB8j"))
128
- #expect(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j#frag", "Gw0LGB8j"))
129
- }
130
-
131
- @Test func containsSegmentRejectsPartialAndMissing() {
132
- let path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
133
- #expect(!BrowserPathHelper.containsSegment(path, "Gw0LGB8"))
134
- #expect(!BrowserPathHelper.containsSegment(path, "funk"))
135
- #expect(!BrowserPathHelper.containsSegment(path, "msterdam-funk-channe"))
136
- #expect(!BrowserPathHelper.containsSegment(path, "NopeNope"))
137
- #expect(!BrowserPathHelper.containsSegment(path, ""))
138
- }