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

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
@@ -1,14 +1,16 @@
1
1
  import type { Track, ResolvedTrack } from '../../types'
2
+ import { getTrackIdentity } from '../../utils/getTrackIdentity'
2
3
 
3
4
  /**
4
- * Manages favorite state for tracks.
5
+ * Manages favorite state for tracks, keyed by track identity (`id` when set,
6
+ * falling back to `src` — see getTrackIdentity / ADR 0008).
5
7
  * Matches Android's favorite hydration behavior.
6
8
  */
7
9
  export class FavoriteManager {
8
10
  private favorites = new Set<string>()
9
11
 
10
12
  /**
11
- * Sets the favorites cache from an array of track src values.
13
+ * Sets the favorites cache from an array of track identities.
12
14
  * This is typically called when favorites are loaded from storage.
13
15
  */
14
16
  setFavorites(favorites: string[]): void {
@@ -16,24 +18,27 @@ export class FavoriteManager {
16
18
  }
17
19
 
18
20
  /**
19
- * Checks if a track is favorited based on its src.
21
+ * Checks if a track is favorited based on its identity.
20
22
  */
21
- isFavorited(src: string): boolean {
22
- return this.favorites.has(src)
23
+ isFavorited(track: Track): boolean {
24
+ const identity = getTrackIdentity(track)
25
+ return identity !== undefined && this.favorites.has(identity)
23
26
  }
24
27
 
25
28
  /**
26
- * Adds a track src to the favorites cache.
29
+ * Adds a track to the favorites cache under its identity.
27
30
  */
28
- addFavorite(src: string): void {
29
- this.favorites.add(src)
31
+ addFavorite(track: Track): void {
32
+ const identity = getTrackIdentity(track)
33
+ if (identity !== undefined) this.favorites.add(identity)
30
34
  }
31
35
 
32
36
  /**
33
- * Removes a track src from the favorites cache.
37
+ * Removes a track from the favorites cache under its identity.
34
38
  */
35
- removeFavorite(src: string): void {
36
- this.favorites.delete(src)
39
+ removeFavorite(track: Track): void {
40
+ const identity = getTrackIdentity(track)
41
+ if (identity !== undefined) this.favorites.delete(identity)
37
42
  }
38
43
 
39
44
  /**
@@ -48,8 +53,7 @@ export class FavoriteManager {
48
53
  if (track.favorited !== undefined && track.favorited !== null) return track
49
54
  if (this.favorites.size === 0) return track
50
55
 
51
- const isFavorited = track.src ? this.favorites.has(track.src) : false
52
- if (!isFavorited) return track
56
+ if (!this.isFavorited(track)) return track
53
57
 
54
58
  return {
55
59
  ...track,
@@ -58,22 +62,23 @@ export class FavoriteManager {
58
62
  }
59
63
 
60
64
  /**
61
- * Hydrates favorites on all children of a ResolvedTrack.
62
- * Matches Android's hydrateChildren behavior.
65
+ * Hydrates favorites on all tracks of a resolved page.
66
+ * Matches Android's hydrateChildren behavior. Pages reach hydration
67
+ * normalized (ADR 0010), so `sections` is the only structure to walk.
63
68
  *
64
- * @param resolvedTrack ResolvedTrack with children to hydrate
65
- * @returns ResolvedTrack with favorited properties set on children
69
+ * @param resolvedTrack Resolved page to hydrate
70
+ * @returns Resolved page with favorited properties set on its tracks
66
71
  */
67
72
  hydrateChildren(resolvedTrack: ResolvedTrack): ResolvedTrack {
68
- const children = resolvedTrack.children
69
- if (!children) return resolvedTrack
73
+ const { sections } = resolvedTrack
74
+ if (!sections) return resolvedTrack
70
75
 
71
- const hydratedChildren = children.map((track) =>
72
- this.hydrateFavorite(track)
73
- )
74
76
  return {
75
77
  ...resolvedTrack,
76
- children: hydratedChildren
78
+ sections: sections.map((section) => ({
79
+ ...section,
80
+ children: section.children.map((track) => this.hydrateFavorite(track))
81
+ }))
77
82
  }
78
83
  }
79
84
  }
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import type { NativeBrowserConfiguration } from '../../types/browser-native'
3
+ import type { HttpClient } from '../http/HttpClient'
4
+ import { BrowserManager } from './BrowserManager'
5
+ import { FavoriteManager } from './FavoriteManager'
6
+ import { NavigationErrorManager } from './NavigationErrorManager'
7
+ import { SearchManager } from './SearchManager'
8
+
9
+ /**
10
+ * Builds a SearchManager over a BrowserManager whose HTTP client answers every
11
+ * request with `respond` — a resolved body or a rejection.
12
+ */
13
+ function makeSearchManager(
14
+ respond: () => Promise<unknown>,
15
+ route: NonNullable<NativeBrowserConfiguration['routes']>[number] = {
16
+ path: '__search__',
17
+ searchConfig: { baseUrl: 'https://api.example.com' }
18
+ }
19
+ ): SearchManager {
20
+ const httpClient = {
21
+ executeRequest: vi.fn().mockImplementation(respond)
22
+ } as unknown as HttpClient
23
+ const browserManager = new BrowserManager(
24
+ httpClient,
25
+ new FavoriteManager(),
26
+ new NavigationErrorManager()
27
+ )
28
+ browserManager.configuration = { routes: [route] }
29
+ return new SearchManager(browserManager)
30
+ }
31
+
32
+ const httpError = (): Error => {
33
+ const error = new Error('HTTP 500: Internal Server Error') as Error & {
34
+ code: string
35
+ statusCode: number
36
+ }
37
+ error.code = 'http-error'
38
+ error.statusCode = 500
39
+ return error
40
+ }
41
+
42
+ describe('SearchManager', () => {
43
+ it('rejects when the search request fails', async () => {
44
+ const manager = makeSearchManager(() => Promise.reject(httpError()))
45
+ await expect(
46
+ manager.search({ query: 'jazz', reference: 'unknown' })
47
+ ).rejects.toThrow('HTTP 500: Internal Server Error')
48
+ })
49
+
50
+ it('resolves [] when the search found nothing', async () => {
51
+ const manager = makeSearchManager(() => Promise.resolve({ children: [] }))
52
+ await expect(
53
+ manager.search({ query: 'jazz', reference: 'unknown' })
54
+ ).resolves.toEqual([])
55
+ })
56
+
57
+ it('resolves the matching tracks', async () => {
58
+ const manager = makeSearchManager(() =>
59
+ Promise.resolve({ children: [{ title: 'Jazz FM', src: 'https://s/1' }] })
60
+ )
61
+ await expect(
62
+ manager.search({ query: 'jazz', reference: 'unknown' })
63
+ ).resolves.toEqual([{ title: 'Jazz FM', src: 'https://s/1' }])
64
+ })
65
+
66
+ it('propagates a rejecting search callback', async () => {
67
+ const manager = makeSearchManager(() => Promise.resolve([]), {
68
+ path: '__search__',
69
+ searchCallback: () => Promise.reject(new Error('callback exploded'))
70
+ })
71
+ await expect(
72
+ manager.search({ query: 'jazz', reference: 'unknown' })
73
+ ).rejects.toThrow('callback exploded')
74
+ })
75
+ })
@@ -15,7 +15,9 @@ export class SearchManager {
15
15
  * Matches Android's BrowserManager.search(params: SearchParams) behavior.
16
16
  *
17
17
  * @param params Search parameters (query is required, other fields optional)
18
- * @returns Array of matching tracks
18
+ * @returns Array of matching tracks — empty only when the source found none
19
+ * @throws The failure itself: an `Error` carrying a `NavigationErrorType`
20
+ * `code` from `HttpClient`, or whatever a `searchCallback` rejected with
19
21
  */
20
22
  async search(params: SearchParams): Promise<Track[]> {
21
23
  // Find __search__ route entry
@@ -48,36 +50,31 @@ export class SearchManager {
48
50
 
49
51
  // Delegate to the shared layered fetch (request → search) on BrowserManager
50
52
  // so the ladder lives in one place.
51
- try {
52
- results = await this.browserManager.fetchSearchResults(
53
- searchRoute.searchConfig,
54
- searchQueryParams
55
- )
56
- } catch (error) {
57
- console.error('Search failed:', error)
58
- return []
59
- }
53
+ results = await this.browserManager.fetchSearchResults(
54
+ searchRoute.searchConfig,
55
+ searchQueryParams
56
+ )
60
57
  }
61
58
 
62
59
  // Transform artwork URLs on search results using async method with full Track access
63
- const { request: requestConfig, artwork: artworkConfig } =
64
- this.browserManager.configuration
65
- if (artworkConfig) {
66
- results = await Promise.all(
67
- results.map(async (track) => {
68
- const artworkSource =
69
- await RequestConfigBuilder.resolveArtworkSourceAsync(
70
- track,
71
- requestConfig,
72
- artworkConfig
73
- )
74
- if (artworkSource && !track.artworkSource) {
75
- return { ...track, artworkSource }
76
- }
77
- return track
78
- })
79
- )
80
- }
60
+ // Gated per track inside the resolver, as on Android — a result carrying a
61
+ // plain `artwork` URL resolves even with no `artwork` config.
62
+ const requestConfig = await this.browserManager.resolvedRequestConfig()
63
+ const artworkConfig = this.browserManager.configuration.artwork
64
+ results = await Promise.all(
65
+ results.map(async (track) => {
66
+ const artworkSource =
67
+ await RequestConfigBuilder.resolveArtworkSourceAsync(
68
+ track,
69
+ requestConfig,
70
+ artworkConfig
71
+ )
72
+ if (artworkSource && !track.artworkSource) {
73
+ return { ...track, artworkSource }
74
+ }
75
+ return track
76
+ })
77
+ )
81
78
 
82
79
  return results
83
80
  }
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import type { HttpClient } from '../../http/HttpClient'
3
+ import { BrowserManager } from '../BrowserManager'
4
+ import { FavoriteManager } from '../FavoriteManager'
5
+ import { NavigationErrorManager } from '../NavigationErrorManager'
6
+
7
+ function makeManager(): BrowserManager {
8
+ const httpClient = {
9
+ executeRequest: vi.fn().mockRejectedValue(new Error('no network in test'))
10
+ } as unknown as HttpClient
11
+ return new BrowserManager(
12
+ httpClient,
13
+ new FavoriteManager(),
14
+ new NavigationErrorManager()
15
+ )
16
+ }
17
+
18
+ /**
19
+ * Who gets an `artworkSource`, and on whose say-so.
20
+ *
21
+ * Android gates this per *track* — `resolveArtworkUrl` returns early only when
22
+ * `artworkConfig == null && track.artwork == null`, so a track carrying a plain
23
+ * URL resolves to `{ uri }` with no `artwork` block configured at all. Web used
24
+ * to gate a level up, bailing out of the whole page when `configuration.artwork`
25
+ * was unset, which left every child's `artworkSource` undefined in any app that
26
+ * never declared one — the example app among them.
27
+ */
28
+ describe('artwork resolution without an artwork config', () => {
29
+ it('populates artworkSource for a child carrying a plain artwork url', async () => {
30
+ const manager = makeManager()
31
+ manager.configuration = {
32
+ path: '/',
33
+ routes: [
34
+ {
35
+ path: '/library',
36
+ browseStatic: {
37
+ path: '/library',
38
+ title: 'Library',
39
+ sections: [
40
+ {
41
+ title: 'Collections',
42
+ children: [
43
+ {
44
+ title: 'Cratediggers',
45
+ path: '/collection/cratediggers',
46
+ artwork: 'https://cdn.example.com/cratediggers.jpg'
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ }
53
+ ]
54
+ }
55
+
56
+ await manager.navigatePath('/library')
57
+
58
+ const tile = manager.content?.sections?.[0]?.children[0]
59
+ expect(tile?.artworkSource?.uri).toBe(
60
+ 'https://cdn.example.com/cratediggers.jpg'
61
+ )
62
+ })
63
+
64
+ it('leaves artworkSource undefined for a child with no artwork', async () => {
65
+ const manager = makeManager()
66
+ manager.configuration = {
67
+ path: '/',
68
+ routes: [
69
+ {
70
+ path: '/library',
71
+ browseStatic: {
72
+ path: '/library',
73
+ title: 'Library',
74
+ sections: [
75
+ {
76
+ title: 'Collections',
77
+ children: [{ title: 'Bare', path: '/collection/bare' }]
78
+ }
79
+ ]
80
+ }
81
+ }
82
+ ]
83
+ }
84
+
85
+ await manager.navigatePath('/library')
86
+
87
+ const tile = manager.content?.sections?.[0]?.children[0]
88
+ expect(tile?.artworkSource).toBeUndefined()
89
+ })
90
+
91
+ it('treats an empty artwork string as no artwork, as Android does', async () => {
92
+ const manager = makeManager()
93
+ manager.configuration = {
94
+ path: '/',
95
+ routes: [
96
+ {
97
+ path: '/library',
98
+ browseStatic: {
99
+ path: '/library',
100
+ title: 'Library',
101
+ sections: [
102
+ {
103
+ title: 'Collections',
104
+ children: [{ title: 'Empty', path: '/x', artwork: '' }]
105
+ }
106
+ ]
107
+ }
108
+ }
109
+ ]
110
+ }
111
+
112
+ await manager.navigatePath('/library')
113
+
114
+ const tile = manager.content?.sections?.[0]?.children[0]
115
+ expect(tile?.artworkSource).toBeUndefined()
116
+ })
117
+ })
@@ -66,4 +66,39 @@ describe('BrowserManager layer resolver', () => {
66
66
  await expect(manager.navigatePath('/a')).resolves.not.toThrow()
67
67
  expect(calls).toBe(1)
68
68
  })
69
+
70
+ /**
71
+ * The media and artwork paths sit outside browse and so must reach the
72
+ * request layer through this accessor. Reading `configuration.request`
73
+ * instead hands a resolver-only config no baseUrl and no headers — on
74
+ * exactly the requests that carry a credential. Android's
75
+ * `resolvedRequestConfig()` exists for the same reason.
76
+ */
77
+ it('exposes the resolver result to consumers outside the browse path', async () => {
78
+ const manager = makeManager()
79
+ manager.configuration = {
80
+ path: '/',
81
+ requestResolver: async () => ({
82
+ baseUrl: 'https://api.example.com',
83
+ headers: { Authorization: 'Bearer token' }
84
+ })
85
+ }
86
+
87
+ expect(await manager.resolvedRequestConfig()).toEqual({
88
+ baseUrl: 'https://api.example.com',
89
+ headers: { Authorization: 'Bearer token' }
90
+ })
91
+ })
92
+
93
+ it('falls back to the static request config when no resolver is set', async () => {
94
+ const manager = makeManager()
95
+ manager.configuration = {
96
+ path: '/',
97
+ request: { baseUrl: 'https://static.example.com' }
98
+ }
99
+
100
+ expect(await manager.resolvedRequestConfig()).toEqual({
101
+ baseUrl: 'https://static.example.com'
102
+ })
103
+ })
69
104
  })
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import type { HttpClient } from '../../http/HttpClient'
3
+ import { BrowserManager } from '../BrowserManager'
4
+ import { FavoriteManager } from '../FavoriteManager'
5
+ import { NavigationErrorManager } from '../NavigationErrorManager'
6
+ import { SearchManager } from '../SearchManager'
7
+
8
+ /**
9
+ * Search-result artwork reaches the request layer through
10
+ * `resolvedRequestConfig()`, as content and media artwork do. Reading
11
+ * `configuration.request` handed a resolver-only config no baseUrl and no
12
+ * headers, so a relative artwork path on a search result never resolved.
13
+ */
14
+ describe('search result artwork', () => {
15
+ it('applies a resolver-only request layer', async () => {
16
+ const httpClient = {
17
+ executeRequest: vi
18
+ .fn()
19
+ .mockResolvedValue([
20
+ { title: 'Hit', src: '/hit.mp3', artwork: '/art/hit.jpg' }
21
+ ])
22
+ } as unknown as HttpClient
23
+ const manager = new BrowserManager(
24
+ httpClient,
25
+ new FavoriteManager(),
26
+ new NavigationErrorManager()
27
+ )
28
+ manager.configuration = {
29
+ path: '/',
30
+ requestResolver: async () => ({
31
+ baseUrl: 'https://api.example.com',
32
+ headers: { Authorization: 'Bearer t' }
33
+ }),
34
+ artwork: {},
35
+ routes: [{ path: '__search__', searchConfig: { path: '/search' } }]
36
+ }
37
+
38
+ const [hit] = await new SearchManager(manager).search({
39
+ query: 'hit',
40
+ reference: 'unknown'
41
+ })
42
+
43
+ expect(hit?.artworkSource?.uri).toBe('https://api.example.com/art/hit.jpg')
44
+ expect(hit?.artworkSource?.headers).toEqual({ Authorization: 'Bearer t' })
45
+ })
46
+ })
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import type { FormattedNavigationError } from '../../../features'
3
+ import type { HttpClient } from '../../http/HttpClient'
4
+ import { BrowserManager } from '../BrowserManager'
5
+ import { FavoriteManager } from '../FavoriteManager'
6
+ import { NavigationErrorManager } from '../NavigationErrorManager'
7
+
8
+ /**
9
+ * A BrowserManager whose only route is a `searchConfig`, with its HTTP client
10
+ * answering every request with `respond`.
11
+ */
12
+ function makeManager(respond: () => Promise<unknown>): {
13
+ manager: BrowserManager
14
+ errors: NavigationErrorManager
15
+ formattedPaths: string[]
16
+ } {
17
+ const httpClient = {
18
+ executeRequest: vi.fn().mockImplementation(respond)
19
+ } as unknown as HttpClient
20
+ const errors = new NavigationErrorManager()
21
+ const manager = new BrowserManager(httpClient, new FavoriteManager(), errors)
22
+ const formattedPaths: string[] = []
23
+ manager.configuration = {
24
+ routes: [
25
+ {
26
+ path: '__search__',
27
+ searchConfig: { baseUrl: 'https://api.example.com' }
28
+ }
29
+ ],
30
+ formatNavigationError: ({ path }): FormattedNavigationError => {
31
+ formattedPaths.push(path)
32
+ return { title: 'Search unavailable' }
33
+ }
34
+ }
35
+ return { manager, errors, formattedPaths }
36
+ }
37
+
38
+ const httpError = (): Error => {
39
+ const error = new Error('HTTP 500: Internal Server Error') as Error & {
40
+ code: string
41
+ statusCode: number
42
+ }
43
+ error.code = 'http-error'
44
+ error.statusCode = 500
45
+ return error
46
+ }
47
+
48
+ describe('navigating to a search path', () => {
49
+ it('surfaces a navigation error when the search fails', async () => {
50
+ const { manager, errors, formattedPaths } = makeManager(() =>
51
+ Promise.reject(httpError())
52
+ )
53
+
54
+ await manager.navigatePath('/__search?q=jazz')
55
+
56
+ expect(errors.getNavigationError()).toEqual({
57
+ code: 'http-error',
58
+ message: 'HTTP 500: Internal Server Error',
59
+ statusCode: 500,
60
+ statusCodeSuccess: undefined
61
+ })
62
+ expect(formattedPaths).toEqual(['/__search?q=jazz'])
63
+ expect(manager.content).toBeUndefined()
64
+ })
65
+
66
+ it('resolves an empty page without an error when the search found nothing', async () => {
67
+ const { manager, errors } = makeManager(() =>
68
+ Promise.resolve({ children: [] })
69
+ )
70
+
71
+ await manager.navigatePath('/__search?q=jazz')
72
+
73
+ expect(errors.getNavigationError()).toBeUndefined()
74
+ expect(manager.content?.sections).toEqual([{ children: [] }])
75
+ })
76
+ })
@@ -3,7 +3,7 @@ import { assertBrowsePageShape } from './assertBrowsePageShape'
3
3
 
4
4
  describe('assertBrowsePageShape', () => {
5
5
  it('passes a page object through unchanged', () => {
6
- const page = { url: '/x', title: 'X', children: [] }
6
+ const page = { path: '/x', title: 'X', children: [] }
7
7
  expect(assertBrowsePageShape(page, '/x')).toBe(page)
8
8
  })
9
9
 
@@ -13,14 +13,16 @@ export function assertBrowsePageShape(
13
13
  if (Array.isArray(response)) {
14
14
  throw new Error(
15
15
  `Browse endpoint for "${path}" returned a JSON array; expected a ` +
16
- `page object { title, children?: Track[] }. Wrap the rows in a ` +
17
- `\`children\` array.`
16
+ `page object { title, sections?: Section[], children?: Track[] }. ` +
17
+ `Wrap the rows in a \`children\` array (one untitled section) or ` +
18
+ `group them into \`sections\`.`
18
19
  )
19
20
  }
20
21
  if (response === null || typeof response !== 'object') {
21
22
  throw new Error(
22
23
  `Browse endpoint for "${path}" returned ${JSON.stringify(response)}; ` +
23
- `expected a page object { title, children?: Track[] }.`
24
+ `expected a page object { title, sections?: Section[], ` +
25
+ `children?: Track[] }.`
24
26
  )
25
27
  }
26
28
  return response as ResolvedTrack
@@ -5,10 +5,10 @@ import { classifyTrackNavigation } from './classifyTrackNavigation'
5
5
  const track = (fields: Partial<Track>): Track => fields as Track
6
6
 
7
7
  describe('classifyTrackNavigation', () => {
8
- it('classifies a contextual url, exposing parent path and track id', () => {
8
+ it('classifies a contextual path, exposing parent path and track id', () => {
9
9
  expect(
10
10
  classifyTrackNavigation(
11
- track({ url: '/library/radio?__trackId=song.mp3' })
11
+ track({ path: '/library/radio?__trackId=song.mp3' })
12
12
  )
13
13
  ).toEqual({
14
14
  kind: 'contextual',
@@ -17,25 +17,27 @@ describe('classifyTrackNavigation', () => {
17
17
  })
18
18
  })
19
19
 
20
- it('classifies a non-contextual url as a browse navigation', () => {
21
- expect(classifyTrackNavigation(track({ url: '/library/radio' }))).toEqual({
20
+ it('classifies a non-contextual path as a browse navigation', () => {
21
+ expect(classifyTrackNavigation(track({ path: '/library/radio' }))).toEqual({
22
22
  kind: 'browse'
23
23
  })
24
24
  })
25
25
 
26
- it('classifies a track with src but no url as playable', () => {
26
+ it('classifies a track with src but no path as playable', () => {
27
27
  expect(classifyTrackNavigation(track({ src: 'song.mp3' }))).toEqual({
28
28
  kind: 'playable'
29
29
  })
30
30
  })
31
31
 
32
- it('prefers browse over playable when a url is present', () => {
32
+ it('prefers browse over playable when a path is present', () => {
33
33
  expect(
34
- classifyTrackNavigation(track({ url: '/library/radio', src: 'song.mp3' }))
34
+ classifyTrackNavigation(
35
+ track({ path: '/library/radio', src: 'song.mp3' })
36
+ )
35
37
  ).toEqual({ kind: 'browse' })
36
38
  })
37
39
 
38
- it('classifies a track with neither url nor src as invalid', () => {
40
+ it('classifies a track with neither path nor src as invalid', () => {
39
41
  expect(classifyTrackNavigation(track({}))).toEqual({ kind: 'invalid' })
40
42
  })
41
43
  })
@@ -4,10 +4,10 @@ import { BrowserPathHelper } from '../util/BrowserPathHelper'
4
4
  /**
5
5
  * How a selected track should be navigated, decided purely from its shape:
6
6
  * - `contextual` — a playable track carrying its parent queue context in a
7
- * contextual URL (`{parentPath}?__trackId={src}`); expand/skip within that queue.
8
- * - `browse` — a browsable track (has a `url` that isn't contextual); drill in.
9
- * - `playable` — a bare playable track (`src`, no `url`); load and play it alone.
10
- * - `invalid` — neither `url` nor `src`; nothing to do.
7
+ * contextual path (`{parentPath}?__trackId={src}`); expand/skip within that queue.
8
+ * - `browse` — a browsable track (has a `path` that isn't contextual); drill in.
9
+ * - `playable` — a bare playable track (`src`, no `path`); load and play it alone.
10
+ * - `invalid` — neither `path` nor `src`; nothing to do.
11
11
  */
12
12
  export type TrackNavigation =
13
13
  | { kind: 'contextual'; parentPath: string; trackId: string | undefined }
@@ -21,15 +21,15 @@ export type TrackNavigation =
21
21
  * caller; this only decides which action applies.
22
22
  */
23
23
  export function classifyTrackNavigation(track: Track): TrackNavigation {
24
- const url = track.url
25
- if (url && BrowserPathHelper.isContextual(url)) {
24
+ const path = track.path
25
+ if (path && BrowserPathHelper.isContextual(path)) {
26
26
  return {
27
27
  kind: 'contextual',
28
- parentPath: BrowserPathHelper.stripTrackId(url),
29
- trackId: BrowserPathHelper.extractTrackId(url)
28
+ parentPath: BrowserPathHelper.stripTrackId(path),
29
+ trackId: BrowserPathHelper.extractTrackId(path)
30
30
  }
31
31
  }
32
- if (url) return { kind: 'browse' }
32
+ if (path) return { kind: 'browse' }
33
33
  if (track.src) return { kind: 'playable' }
34
34
  return { kind: 'invalid' }
35
35
  }
@@ -5,7 +5,11 @@ describe('parseSearchResponse', () => {
5
5
  it('extracts children from a page-object response', () => {
6
6
  const tracks = [{ src: '/a', title: 'A' }]
7
7
  expect(
8
- parseSearchResponse({ url: '/search', title: 'Search', children: tracks })
8
+ parseSearchResponse({
9
+ path: '/search',
10
+ title: 'Search',
11
+ children: tracks
12
+ })
9
13
  ).toEqual(tracks)
10
14
  })
11
15
 
@@ -15,7 +19,9 @@ describe('parseSearchResponse', () => {
15
19
  })
16
20
 
17
21
  it('returns [] for a page object with no children', () => {
18
- expect(parseSearchResponse({ url: '/search', title: 'Search' })).toEqual([])
22
+ expect(parseSearchResponse({ path: '/search', title: 'Search' })).toEqual(
23
+ []
24
+ )
19
25
  })
20
26
 
21
27
  it('returns [] for null / non-object responses', () => {