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
@@ -1,5 +1,7 @@
1
1
  package com.audiobrowser.tls
2
2
 
3
+ import java.io.ByteArrayInputStream
4
+ import java.io.InputStream
3
5
  import org.junit.Assert.assertArrayEquals
4
6
  import org.junit.Assert.assertEquals
5
7
  import org.junit.Assert.assertNull
@@ -72,4 +74,85 @@ class CachingCertificateFetcherTest {
72
74
  CachingCertificateFetcher.parseHttpResponse("garbage without a header break".toByteArray())
73
75
  )
74
76
  }
77
+
78
+ // -- response cap (the AIA URL, so the host serving it, comes from the presented cert) --
79
+
80
+ // -- URL validation (the AIA URL is attacker-supplied text from the presented cert) --
81
+
82
+ @Test
83
+ fun `accepts the URL form CAs actually serve`() {
84
+ assertTrue(CachingCertificateFetcher.isSafeUrl("http://r13.i.lencr.org/"))
85
+ assertTrue(CachingCertificateFetcher.isSafeUrl("http://ca.example:8080/issuer.crt?v=2"))
86
+ }
87
+
88
+ @Test
89
+ fun `rejects a URL carrying CRLF, which would inject request lines`() {
90
+ // java.net.URL keeps the CRLF in `file`, and the cleartext fetch writes `file` straight
91
+ // into "GET $path HTTP/1.0\r\n" on a raw socket.
92
+ assertTrue(!CachingCertificateFetcher.isSafeUrl("http://127.0.0.1:6379/\r\nSET foo bar\r\n"))
93
+ assertTrue(!CachingCertificateFetcher.isSafeUrl("http://ca.example/a\nX-Injected: 1"))
94
+ }
95
+
96
+ @Test
97
+ fun `rejects a URL carrying a space, which would split the request line`() {
98
+ assertTrue(!CachingCertificateFetcher.isSafeUrl("http://ca.example/a b"))
99
+ }
100
+
101
+ @Test
102
+ fun `reads a response that fits within the cap`() {
103
+ val body = ByteArray(64) { it.toByte() }
104
+
105
+ assertArrayEquals(
106
+ body,
107
+ CachingCertificateFetcher.readCapped(ByteArrayInputStream(body), limit = 128),
108
+ )
109
+ }
110
+
111
+ @Test
112
+ fun `returns null for a response that exceeds the cap`() {
113
+ val body = ByteArray(129)
114
+
115
+ assertNull(CachingCertificateFetcher.readCapped(ByteArrayInputStream(body), limit = 128))
116
+ }
117
+
118
+ @Test
119
+ fun `a stream that never ends does not read without bound`() {
120
+ // Endless: read() always fills the buffer and never signals EOF, the shape a slow-drip
121
+ // server takes — the per-read timeout never fires because every read succeeds.
122
+ val endless =
123
+ object : InputStream() {
124
+ override fun read() = 0
125
+
126
+ override fun read(b: ByteArray, off: Int, len: Int): Int = len
127
+ }
128
+
129
+ assertNull(CachingCertificateFetcher.readCapped(endless, limit = 1 shl 16))
130
+ }
131
+
132
+ @Test
133
+ fun `a slow drip is cut off by the deadline, not left to exhaust the byte cap`() {
134
+ // One byte per read, each one landing just inside the socket's read timeout: the byte cap
135
+ // alone would let this run for a million reads. The clock advances 1s per read, so the
136
+ // 10s budget must end it after ~10 — far short of the 1 MiB cap.
137
+ var reads = 0
138
+ val drip =
139
+ object : InputStream() {
140
+ override fun read() = 0
141
+
142
+ override fun read(b: ByteArray, off: Int, len: Int): Int {
143
+ reads++
144
+ return 1
145
+ }
146
+ }
147
+ var nanos = 0L
148
+
149
+ val result =
150
+ CachingCertificateFetcher.readCapped(drip, budgetMs = 10_000) {
151
+ nanos += 1_000_000_000
152
+ nanos
153
+ }
154
+
155
+ assertNull(result)
156
+ assertTrue("gave up after $reads reads", reads < 20)
157
+ }
75
158
  }
@@ -1,28 +1,53 @@
1
1
  package com.audiobrowser.util
2
2
 
3
- import org.junit.Assert.assertFalse
4
- import org.junit.Assert.assertTrue
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertNull
5
5
  import org.junit.Test
6
+ import org.junit.runner.RunWith
7
+ import org.robolectric.RobolectricTestRunner
6
8
 
9
+ @RunWith(RobolectricTestRunner::class)
7
10
  class BrowserPathHelperTest {
8
11
 
9
12
  @Test
10
- fun `containsSegment matches a uid as a full path segment`() {
11
- val path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
12
- assertTrue(BrowserPathHelper.containsSegment(path, "Gw0LGB8j"))
13
- assertTrue(BrowserPathHelper.containsSegment(path, "amsterdam-funk-channel"))
14
- assertTrue(BrowserPathHelper.containsSegment(path, "listen"))
15
- assertTrue(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j?hl=en", "Gw0LGB8j"))
16
- assertTrue(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j#frag", "Gw0LGB8j"))
13
+ fun `build round-trips a src carrying its own query params`() {
14
+ // A src carrying its own query string (signed CDN URL) must survive the
15
+ // build → extract/strip round-trip: an unescaped `&` would split the src
16
+ // into stray query params, truncating the trackId and polluting the
17
+ // parent path.
18
+ val src = "https://cdn.example.com/stream.mp3?token=abc&exp=1699999999"
19
+ val url = BrowserPathHelper.build("/library", src)
20
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
21
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
17
22
  }
18
23
 
19
24
  @Test
20
- fun `containsSegment rejects partial-segment and missing matches`() {
21
- val path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
22
- assertFalse(BrowserPathHelper.containsSegment(path, "Gw0LGB8"))
23
- assertFalse(BrowserPathHelper.containsSegment(path, "funk"))
24
- assertFalse(BrowserPathHelper.containsSegment(path, "msterdam-funk-channe"))
25
- assertFalse(BrowserPathHelper.containsSegment(path, "NopeNope"))
26
- assertFalse(BrowserPathHelper.containsSegment(path, ""))
25
+ fun `build round-trips a src with equals and plus`() {
26
+ val src = "https://cdn.example.com/a+b.mp3?sig=x=y"
27
+ val url = BrowserPathHelper.build("/library", src)
28
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
29
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
30
+ }
31
+
32
+ @Test
33
+ fun `build stamps the index and round-trips it`() {
34
+ val src = "https://cdn.example.com/stream.mp3?token=abc&exp=1"
35
+ val url = BrowserPathHelper.build("/library", src, 3)
36
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
37
+ assertEquals(3, BrowserPathHelper.extractIndex(url))
38
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
39
+ }
40
+
41
+ @Test
42
+ fun `extractIndex returns null without a stamped index`() {
43
+ val url = BrowserPathHelper.build("/library", "song.mp3")
44
+ assertNull(BrowserPathHelper.extractIndex(url))
45
+ }
46
+
47
+ @Test
48
+ fun `extractIndex returns null for non-contextual or malformed-index URLs`() {
49
+ assertNull(BrowserPathHelper.extractIndex("/library"))
50
+ assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=x"))
51
+ assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=-1"))
27
52
  }
28
53
  }
@@ -0,0 +1,162 @@
1
+ package com.audiobrowser.util
2
+
3
+ import androidx.media.utils.MediaConstants
4
+ import com.audiobrowser.TestFixtures
5
+ import com.audiobrowser.TestFixtures.sectionStyle
6
+ import com.audiobrowser.TestFixtures.trackStyle
7
+ import com.audiobrowser.browser.styleResolvedSections
8
+ import com.margelo.nitro.audiobrowser.StyleDisplay
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertFalse
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import org.robolectric.RobolectricTestRunner
14
+
15
+ /**
16
+ * The extras are where a section survives the flat MediaBrowser protocol (ADR 0010): the owning
17
+ * section stamps the Android Auto group-title header hint and the per-item display hint. Per-item
18
+ * hints derive from the section resolution; a track's own `display` is positional — the page
19
+ * promise a browsable track emits as the parent-level hint, never its own tile shape (ADR 0011).
20
+ */
21
+ @RunWith(RobolectricTestRunner::class)
22
+ class MediaExtrasBuilderTest {
23
+ private val track = TestFixtures.track()
24
+
25
+ private fun sectionStyle(display: StyleDisplay?) =
26
+ sectionStyle(display = display, artworkRendering = null, gridWrap = null)
27
+
28
+ @Test
29
+ fun `owning section title stamps the group-title hint`() {
30
+ val section = TestFixtures.section(arrayOf(track), title = "Popular")
31
+ val extras = MediaExtrasBuilder.build(track, section)
32
+ assertEquals(
33
+ "Popular",
34
+ extras.getString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE),
35
+ )
36
+ }
37
+
38
+ @Test
39
+ fun `an untitled section stamps no group-title hint`() {
40
+ val section = TestFixtures.section(arrayOf(track))
41
+ val extras = MediaExtrasBuilder.build(track, section)
42
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE))
43
+ }
44
+
45
+ @Test
46
+ fun `grid section display stamps the grid item hint`() {
47
+ val section = TestFixtures.section(arrayOf(track), style = sectionStyle(StyleDisplay.GRID))
48
+ val extras = MediaExtrasBuilder.build(track, section)
49
+ assertEquals(
50
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
51
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
52
+ )
53
+ }
54
+
55
+ @Test
56
+ fun `list section display stamps the list item hint`() {
57
+ val section = TestFixtures.section(arrayOf(track), style = sectionStyle(StyleDisplay.LIST))
58
+ val extras = MediaExtrasBuilder.build(track, section)
59
+ assertEquals(
60
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM,
61
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
62
+ )
63
+ }
64
+
65
+ @Test
66
+ fun `a track's own display never sets its item hint - display is positional`() {
67
+ val gridPromiseTrack =
68
+ track.copy(style = trackStyle(display = StyleDisplay.GRID, artworkRendering = null))
69
+ val section = TestFixtures.section(arrayOf(gridPromiseTrack), style = null)
70
+ val extras = MediaExtrasBuilder.build(gridPromiseTrack, section)
71
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
72
+ }
73
+
74
+ @Test
75
+ fun `a browsable track's declared display emits the parent-level promise`() {
76
+ val handle =
77
+ TestFixtures.browseTrack()
78
+ .copy(style = trackStyle(display = StyleDisplay.GRID, artworkRendering = null))
79
+ val extras = MediaExtrasBuilder.build(handle)
80
+ val expected = MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
81
+ assertEquals(
82
+ expected,
83
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE),
84
+ )
85
+ assertEquals(
86
+ expected,
87
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE),
88
+ )
89
+ }
90
+
91
+ @Test
92
+ fun `an undeclared promise emits no parent-level hint - never derived`() {
93
+ val handle = TestFixtures.browseTrack()
94
+ val extras = MediaExtrasBuilder.build(handle)
95
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE))
96
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE))
97
+ }
98
+
99
+ @Test
100
+ fun `a playable track's display is inert - playables open no page`() {
101
+ val playable =
102
+ track.copy(style = trackStyle(display = StyleDisplay.GRID, artworkRendering = null))
103
+ val extras = MediaExtrasBuilder.build(playable)
104
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE))
105
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE))
106
+ }
107
+
108
+ @Test
109
+ fun `no style anywhere stamps no style hint`() {
110
+ val extras = MediaExtrasBuilder.build(track, TestFixtures.section(arrayOf(track)))
111
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
112
+ }
113
+
114
+ @Test
115
+ fun `no section (queue and now-playing surfaces) stamps neither section hint`() {
116
+ val extras = MediaExtrasBuilder.build(track)
117
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE))
118
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
119
+ }
120
+
121
+ @Test
122
+ fun `the view-all navigation track carries the section's declared display as its promise`() {
123
+ // A "view all" page has no consumer-authored handle — the synthetic
124
+ // navigation track projects the section's declared block, the only
125
+ // declared promise that page can ever have.
126
+ val section =
127
+ TestFixtures.section(
128
+ arrayOf(track),
129
+ title = "Popular",
130
+ path = "/popular",
131
+ style = sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = false),
132
+ )
133
+ val navigation = TrackFactory.navigationTrack(section, "View more")
134
+ assertEquals(StyleDisplay.GRID, navigation.style?.display)
135
+ assertEquals("View more", navigation.title)
136
+
137
+ val extras = MediaExtrasBuilder.build(navigation)
138
+ assertEquals(
139
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
140
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE),
141
+ )
142
+ }
143
+
144
+ @Test
145
+ fun `an unstyled section under a grid page stamps the grid hint end to end`() {
146
+ // The wire that makes ResolvedTrack.style real: page block ->
147
+ // styleResolvedSections fold -> per-item hint. Without the fold, every
148
+ // narrower unit test stays green while the page tier goes dark.
149
+ val page =
150
+ TestFixtures.resolvedTrack(
151
+ style = sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = null),
152
+ sections = arrayOf(TestFixtures.section(arrayOf(track))),
153
+ )
154
+
155
+ val folded = page.styleResolvedSections()!!.single()
156
+ val extras = MediaExtrasBuilder.build(track, folded)
157
+ assertEquals(
158
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
159
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
160
+ )
161
+ }
162
+ }
@@ -22,27 +22,27 @@ class TrackFactoryMediaIdTest {
22
22
  }
23
23
 
24
24
  @Test
25
- fun `stable id wins over the contextual url for playable tracks`() {
25
+ fun `stable id wins over the contextual path for playable tracks`() {
26
26
  val track =
27
27
  TestFixtures.track(id = "abc123", src = "/listen/abc123/channel.mp3")
28
- .copy(url = "/home?__trackId=%2Flisten%2Fabc123%2Fchannel.mp3")
28
+ .copy(path = "/home?__trackId=%2Flisten%2Fabc123%2Fchannel.mp3")
29
29
  assertEquals("abc123", TrackFactory.toMedia3(track).mediaId)
30
30
  }
31
31
 
32
32
  @Test
33
- fun `playable track without id falls back to url then src`() {
34
- val withUrl = TestFixtures.track(src = "/a.mp3").copy(url = "/home?__trackId=%2Fa.mp3")
35
- assertEquals("/home?__trackId=%2Fa.mp3", TrackFactory.toMedia3(withUrl).mediaId)
33
+ fun `playable track without id falls back to path then src`() {
34
+ val withPath = TestFixtures.track(src = "/a.mp3").copy(path = "/home?__trackId=%2Fa.mp3")
35
+ assertEquals("/home?__trackId=%2Fa.mp3", TrackFactory.toMedia3(withPath).mediaId)
36
36
 
37
37
  val srcOnly = TestFixtures.track(src = "/a.mp3")
38
38
  assertEquals("/a.mp3", TrackFactory.toMedia3(srcOnly).mediaId)
39
39
  }
40
40
 
41
41
  @Test
42
- fun `browsable-only track keeps url as mediaId even with an id`() {
42
+ fun `browsable-only track keeps path as mediaId even with an id`() {
43
43
  // Navigation parentIds must stay resolvable paths, so a container's id is
44
44
  // never its mediaId.
45
- val track = TestFixtures.browseTrack(url = "/stations").copy(id = "abc123")
45
+ val track = TestFixtures.browseTrack(path = "/stations").copy(id = "abc123")
46
46
  assertEquals("/stations", TrackFactory.toMedia3(track).mediaId)
47
47
  }
48
48
 
@@ -48,9 +48,9 @@ struct BrowserConfig {
48
48
  let carPlayLoadingTitle: String?
49
49
 
50
50
  /// Resolves a browse path for the CarPlay Now Playing album line when the
51
- /// active track has no albumUrl. Invoked on track changes (not at tap) so
51
+ /// active track has no albumPath. Invoked on track changes (not at tap) so
52
52
  /// the album line only becomes tappable when a destination exists.
53
- let resolveAlbumUrl: ((_ track: Track) -> Promise<String?>)?
53
+ let resolveAlbumPath: ((_ track: Track) -> Promise<String?>)?
54
54
 
55
55
  /// Custom handler for track load events (overrides default load behavior)
56
56
  let handleTrackLoad: ((_ event: TrackLoadEvent) -> Promise<Promise<Void>>)?
@@ -72,7 +72,7 @@ struct BrowserConfig {
72
72
  handleTrackLoad: ((_ event: TrackLoadEvent) -> Promise<Promise<Void>>)? = nil,
73
73
  androidControllerOfflineError: Bool = true,
74
74
  carPlayLoadingTitle: String? = nil,
75
- resolveAlbumUrl: ((_ track: Track) -> Promise<String?>)? = nil,
75
+ resolveAlbumPath: ((_ track: Track) -> Promise<String?>)? = nil,
76
76
  formatNavigationError: ((_ params: FormatNavigationErrorParams) -> Promise<FormattedNavigationError?>)? = nil,
77
77
  ) {
78
78
  self.request = request
@@ -87,7 +87,7 @@ struct BrowserConfig {
87
87
  self.handleTrackLoad = handleTrackLoad
88
88
  self.androidControllerOfflineError = androidControllerOfflineError
89
89
  self.carPlayLoadingTitle = carPlayLoadingTitle
90
- self.resolveAlbumUrl = resolveAlbumUrl
90
+ self.resolveAlbumPath = resolveAlbumPath
91
91
  self.formatNavigationError = formatNavigationError
92
92
  }
93
93
 
@@ -105,10 +105,31 @@ struct BrowserConfig {
105
105
  handleTrackLoad = config.handleTrackLoad
106
106
  androidControllerOfflineError = config.androidControllerOfflineError ?? true
107
107
  carPlayLoadingTitle = config.carPlayLoadingTitle
108
- resolveAlbumUrl = config.resolveAlbumUrl
108
+ resolveAlbumPath = config.resolveAlbumPath
109
109
  formatNavigationError = config.formatNavigationError
110
110
  }
111
111
 
112
+ /// A copy of this config with every JS-provided function dropped — at every
113
+ /// level, not just the top; each nested type's strip below nils its own —
114
+ /// keeping the static fields (URLs, headers, routes' shape). Makes the config
115
+ /// safe to hold after its JS runtime is gone: callers take their
116
+ /// static/built-in fallback paths (a callback-only route degrades to its
117
+ /// no-content path). See `HybridAudioBrowser.dropStaleJSCallbacks` for why
118
+ /// invoking a stale callback is fatal.
119
+ func strippingJSCallbacks() -> BrowserConfig {
120
+ BrowserConfig(
121
+ request: request?.strippingJSCallbacks(),
122
+ browse: browse?.strippingJSCallbacks(),
123
+ media: media?.strippingJSCallbacks(),
124
+ artwork: artwork?.strippingJSCallbacks(),
125
+ nowPlayingArtwork: nowPlayingArtwork?.strippingJSCallbacks(),
126
+ routes: routes?.map { $0.strippingJSCallbacks() },
127
+ singleTrack: singleTrack,
128
+ androidControllerOfflineError: androidControllerOfflineError,
129
+ carPlayLoadingTitle: carPlayLoadingTitle,
130
+ )
131
+ }
132
+
112
133
  // MARK: - Track Load Handler
113
134
 
114
135
  private static let logger = Logger(subsystem: "com.audiobrowser", category: "BrowserConfig")
@@ -139,3 +160,79 @@ struct BrowserConfig {
139
160
  // MARK: - NativeRouteEntry + RouteEntry conformance
140
161
 
141
162
  extension NativeRouteEntry: RouteEntry {}
163
+
164
+ // MARK: - Deep JS-callback stripping
165
+
166
+ // The generated structs are get-only from Swift, so each strip rebuilds the
167
+ // value through the memberwise initializer with the function fields nil'd.
168
+
169
+ private extension TransformableRequestConfig {
170
+ func strippingJSCallbacks() -> TransformableRequestConfig {
171
+ TransformableRequestConfig(
172
+ transform: nil,
173
+ transformSync: nil,
174
+ method: method,
175
+ path: path,
176
+ baseUrl: baseUrl,
177
+ headers: headers,
178
+ query: query,
179
+ body: body,
180
+ contentType: contentType,
181
+ userAgent: userAgent,
182
+ )
183
+ }
184
+ }
185
+
186
+ private extension MediaRequestConfig {
187
+ func strippingJSCallbacks() -> MediaRequestConfig {
188
+ MediaRequestConfig(
189
+ resolve: nil,
190
+ resolveSync: nil,
191
+ transform: nil,
192
+ transformSync: nil,
193
+ method: method,
194
+ path: path,
195
+ baseUrl: baseUrl,
196
+ headers: headers,
197
+ query: query,
198
+ body: body,
199
+ contentType: contentType,
200
+ userAgent: userAgent,
201
+ )
202
+ }
203
+ }
204
+
205
+ private extension ArtworkRequestConfig {
206
+ func strippingJSCallbacks() -> ArtworkRequestConfig {
207
+ ArtworkRequestConfig(
208
+ resolve: nil,
209
+ resolveSync: nil,
210
+ transform: nil,
211
+ transformSync: nil,
212
+ imageQueryParams: imageQueryParams,
213
+ method: method,
214
+ path: path,
215
+ baseUrl: baseUrl,
216
+ headers: headers,
217
+ query: query,
218
+ body: body,
219
+ contentType: contentType,
220
+ userAgent: userAgent,
221
+ )
222
+ }
223
+ }
224
+
225
+ private extension NativeRouteEntry {
226
+ func strippingJSCallbacks() -> NativeRouteEntry {
227
+ NativeRouteEntry(
228
+ path: path,
229
+ browseCallback: nil,
230
+ browseConfig: browseConfig?.strippingJSCallbacks(),
231
+ browseStatic: browseStatic,
232
+ searchCallback: nil,
233
+ searchConfig: searchConfig?.strippingJSCallbacks(),
234
+ media: media?.strippingJSCallbacks(),
235
+ artwork: artwork?.strippingJSCallbacks(),
236
+ )
237
+ }
238
+ }
@@ -262,10 +262,14 @@ extension BrowserManager {
262
262
  }
263
263
 
264
264
  // Substitute the `{id}` template token with the track's id across path/query/header values.
265
- // (Configs without the token are unaffected — e.g. browse artwork.) Only when the track has an id.
266
- if let id = track.id, !id.isEmpty {
267
- mergedConfig = substituteTrackId(in: mergedConfig, id: id)
268
- }
265
+ // (Configs without the token are unaffected — e.g. browse artwork.) A track without a
266
+ // non-blank id leaves the token LITERALLY in place — the request then visibly 404s, which
267
+ // is intended (self-describing error trail) — with a single warning logged below once the
268
+ // URL is built.
269
+ mergedConfig = substituteTrackId(in: mergedConfig, id: track.id)
270
+ let hasUnfilledIdToken = TrackIdTemplate.containsToken(mergedConfig.path)
271
+ || TrackIdTemplate.containsToken(mergedConfig.query)
272
+ || TrackIdTemplate.containsToken(mergedConfig.headers)
269
273
 
270
274
  // Build final URL - if no path after merging, no artwork to transform
271
275
  guard mergedConfig.path != nil else {
@@ -280,6 +284,10 @@ extension BrowserManager {
280
284
  headers["User-Agent"] = userAgent
281
285
  }
282
286
 
287
+ if hasUnfilledIdToken {
288
+ logger.warning("nowPlayingArtwork: track has no id — '{id}' left unfilled in \(uri)")
289
+ }
290
+
283
291
  logger.debug("Artwork URL transformed: \(track.artwork?.url ?? "nil") -> \(uri)")
284
292
 
285
293
  return ImageSource(
@@ -315,18 +323,14 @@ extension BrowserManager {
315
323
 
316
324
  /// Replaces the `{id}` token with the track id in a request config's path, query values, and
317
325
  /// header values. Used so a `nowPlayingArtwork` like `{ path: "/artwork/{id}" }` resolves.
318
- private func substituteTrackId(in config: RequestConfig, id: String) -> RequestConfig {
319
- func sub(_ s: String?) -> String? { s?.replacingOccurrences(of: "{id}", with: id) }
320
- func subDict(_ d: [String: String]?) -> [String: String]? {
321
- guard let d else { return nil }
322
- return d.mapValues { $0.replacingOccurrences(of: "{id}", with: id) }
323
- }
324
- return RequestConfig(
326
+ /// A nil/blank id leaves the values unchanged (token left literal — see TrackIdTemplate).
327
+ private func substituteTrackId(in config: RequestConfig, id: String?) -> RequestConfig {
328
+ RequestConfig(
325
329
  method: config.method,
326
- path: sub(config.path),
330
+ path: TrackIdTemplate.substitute(config.path, id: id),
327
331
  baseUrl: config.baseUrl,
328
- headers: subDict(config.headers),
329
- query: subDict(config.query),
332
+ headers: TrackIdTemplate.substitute(config.headers, id: id),
333
+ query: TrackIdTemplate.substitute(config.query, id: id),
330
334
  body: config.body,
331
335
  contentType: config.contentType,
332
336
  userAgent: config.userAgent,