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,8 +1,7 @@
1
1
  package com.audiobrowser.browser
2
2
 
3
- import com.audiobrowser.util.url
4
-
5
3
  import com.audiobrowser.http.RequestConfigBuilder
4
+ import com.audiobrowser.util.url
6
5
  import com.margelo.nitro.audiobrowser.ArtworkRequestConfig
7
6
  import com.margelo.nitro.audiobrowser.ImageContext
8
7
  import com.margelo.nitro.audiobrowser.ImageQueryParams
@@ -177,15 +176,22 @@ suspend fun BrowserManager.resolveArtworkUrl(
177
176
  )
178
177
  }
179
178
 
180
- // `{id}` token substitution, only for a non-empty id. Mirrors iOS substituteTrackId.
179
+ // `{id}` token substitution. A track without a non-blank id leaves the token LITERALLY in
180
+ // place — the request then visibly 404s, which is intended (self-describing error trail) —
181
+ // with a single warning logged below once the URL is built. Mirrors iOS substituteTrackId.
181
182
  track.id
182
183
  ?.takeIf { it.isNotEmpty() }
183
184
  ?.let { transformedConfig = substituteTrackId(transformedConfig, it) }
185
+ val hasUnfilledIdToken = configContainsIdToken(transformedConfig)
184
186
 
185
187
  val uri = RequestConfigBuilder.buildUrl(transformedConfig)
186
188
  // If URI is empty, there's no valid artwork path
187
189
  if (uri.isEmpty()) return null
188
190
 
191
+ if (hasUnfilledIdToken) {
192
+ Timber.w("nowPlayingArtwork: track has no id — '{id}' left unfilled in %s", uri)
193
+ }
194
+
189
195
  ImageSource(
190
196
  uri = uri,
191
197
  method = transformedConfig.method,
@@ -266,15 +272,18 @@ private fun applyImageQueryParams(
266
272
  return config.copy(query = (config.query ?: emptyMap()) + contextQuery)
267
273
  }
268
274
 
275
+ /** The `{id}` template token — see [substituteTrackId] / [configContainsIdToken]. */
276
+ private const val ID_TOKEN = "{id}"
277
+
269
278
  /**
270
279
  * Replaces the `{id}` token with the track id in a request config's path, query values, and header
271
280
  * values. Used so a `nowPlayingArtwork` like `{ path: "/artwork/{id}" }` resolves. Configs without
272
281
  * the token are returned unchanged. Mirrors the iOS `substituteTrackId` helper.
273
282
  */
274
283
  private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig {
275
- fun sub(s: String?): String? = s?.replace("{id}", id)
284
+ fun sub(s: String?): String? = s?.replace(ID_TOKEN, id)
276
285
  fun subMap(m: Map<String, String>?): Map<String, String>? =
277
- m?.mapValues { (_, value) -> value.replace("{id}", id) }
286
+ m?.mapValues { (_, value) -> value.replace(ID_TOKEN, id) }
278
287
  return config.copy(
279
288
  path = sub(config.path),
280
289
  headers = subMap(config.headers),
@@ -282,6 +291,16 @@ private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig
282
291
  )
283
292
  }
284
293
 
294
+ /**
295
+ * Whether the config still carries an unfilled `{id}` token in its path, query values, or header
296
+ * values (checked AFTER substitution, so it matches exactly when the track had no non-blank id and
297
+ * the config uses the token). Mirrors iOS `TrackIdTemplate.containsToken`.
298
+ */
299
+ private fun configContainsIdToken(config: RequestConfig): Boolean =
300
+ config.path?.contains(ID_TOKEN) == true ||
301
+ config.query?.values?.any { it.contains(ID_TOKEN) } == true ||
302
+ config.headers?.values?.any { it.contains(ID_TOKEN) } == true
303
+
285
304
  /** Builds a headers map, merging explicit headers with userAgent and contentType. */
286
305
  private fun buildHeadersMap(
287
306
  headers: Map<String, String>?,
@@ -0,0 +1,267 @@
1
+ package com.audiobrowser.browser
2
+
3
+ import com.audiobrowser.BuildConfig
4
+ import com.audiobrowser.util.toWireString
5
+ import com.margelo.nitro.audiobrowser.GridTile
6
+ import com.margelo.nitro.audiobrowser.Section
7
+ import com.margelo.nitro.audiobrowser.SectionStyle
8
+ import com.margelo.nitro.audiobrowser.StyleDisplay
9
+ import com.margelo.nitro.audiobrowser.TrackStyle
10
+ import timber.log.Timber
11
+
12
+ /**
13
+ * Dev diagnostic (ADR 0011): the declarations on a resolved page that can never render.
14
+ *
15
+ * The block model deliberately gave up compile-time invalid-combination errors — combinations are
16
+ * not invalid, they are *inert*, the way `grid-template` on a non-grid element is inert in CSS.
17
+ * This is the recovery: at page resolution every declaration its own block makes unreadable is
18
+ * reported once, with the effective context and the fix.
19
+ *
20
+ * The line it draws, and never crosses: only **structural** inertness — a declaration the resolved
21
+ * block itself renders unreadable, on every surface (`gridWrap` outside a grid, `cardTint` with no
22
+ * card treatment in scope). *Surface* inertness is never reported, because declarations are
23
+ * aspirational: `imageShape` on Android Auto, or a card treatment on a pre-26 CarPlay, is intended
24
+ * usage — the renderer drops what it can't draw.
25
+ *
26
+ * Runs where every page passes regardless of its source (static config, route callback, or fetched
27
+ * browse JSON), and only on a cache miss, so a mistake is reported once per resolution rather than
28
+ * once per serve. Mirrors `ios/Browser/InertStyleDiagnostic.swift`.
29
+ *
30
+ * Debug builds only: [warn] is the whole seam, so a release build never even walks the page (the
31
+ * trade is that a page served only to a release build — an internal track, production browse JSON —
32
+ * reports nothing; reproduce it against a debug build).
33
+ */
34
+ object InertStyleDiagnostic {
35
+ /**
36
+ * Reports every inert declaration on a resolved page. The one gate — the [BuildConfig] check is
37
+ * here rather than at the call sites so callers read unconditionally and the rule set stays free
38
+ * of build-configuration knowledge.
39
+ */
40
+ fun warn(path: String, pageStyle: SectionStyle?, sections: List<Section>) {
41
+ if (!BuildConfig.DEBUG) return
42
+ findings(path, pageStyle, sections).forEach { Timber.w(it) }
43
+ }
44
+
45
+ /**
46
+ * The declarations that carry a structural inertness condition.
47
+ *
48
+ * Two keys of the block are deliberately absent — `display` and `artworkRendering`, dispositioned
49
+ * below instead.
50
+ */
51
+ private enum class Property(val key: String, val isItemProperty: Boolean) {
52
+ // Container properties resolve by scope override (`section ?? page`) and never reach an item;
53
+ // item properties inherit (`track ?? section ?? page`), so they may also be declared per track.
54
+ GRID_WRAP("gridWrap", isItemProperty = false),
55
+ GRID_TILE("gridTile", isItemProperty = false),
56
+ IMAGE_SHAPE("imageShape", isItemProperty = true),
57
+ ACCESSORY_SYMBOL("accessorySymbol", isItemProperty = true),
58
+ CARD_TINT("cardTint", isItemProperty = true),
59
+ CARD_IMAGE("cardImage", isItemProperty = true);
60
+
61
+ /**
62
+ * Whether a value of this property, resolved into [context], has any rendering to affect. Each
63
+ * branch is the spec's own "inert unless" clause.
64
+ */
65
+ fun isInert(context: Context): Boolean =
66
+ when (this) {
67
+ GRID_WRAP,
68
+ GRID_TILE -> !context.isGrid
69
+ IMAGE_SHAPE -> !context.isGrid || context.isCard
70
+ ACCESSORY_SYMBOL -> context.isCard
71
+ CARD_TINT,
72
+ CARD_IMAGE -> !context.isCard
73
+ }
74
+
75
+ /** The rule the declaration falls foul of, stated positively. */
76
+ val rule: String
77
+ get() =
78
+ when (this) {
79
+ GRID_WRAP,
80
+ GRID_TILE -> "`$key` renders only in a grid"
81
+ IMAGE_SHAPE -> "only plain and condensed tiles have a shape"
82
+ ACCESSORY_SYMBOL -> "card tiles have no accessory slot"
83
+ CARD_TINT,
84
+ CARD_IMAGE -> "the card properties render only under `gridTile: 'card'`"
85
+ }
86
+
87
+ val hint: String
88
+ get() =
89
+ when (this) {
90
+ GRID_WRAP,
91
+ GRID_TILE -> "Declare `display: 'grid'` on the section or the page, or drop `$key`."
92
+ IMAGE_SHAPE -> "Render the items as plain or condensed grid tiles, or drop `imageShape`."
93
+ ACCESSORY_SYMBOL -> "Use `gridTile: 'plain'` or `'condensed'`, or drop `accessorySymbol`."
94
+ CARD_TINT,
95
+ CARD_IMAGE -> "Declare `display: 'grid'` with `gridTile: 'card'`, or drop `$key`."
96
+ }
97
+
98
+ fun declaredIn(style: SectionStyle?): Boolean =
99
+ when (this) {
100
+ GRID_WRAP -> style?.gridWrap != null
101
+ GRID_TILE -> style?.gridTile != null
102
+ IMAGE_SHAPE -> style?.imageShape != null
103
+ ACCESSORY_SYMBOL -> style?.accessorySymbol != null
104
+ CARD_TINT -> style?.cardTint != null
105
+ CARD_IMAGE -> style?.cardImage != null
106
+ }
107
+
108
+ // Nitro flattens the spec's `extends`, so the two blocks share no type — hence two lookups over
109
+ // the same key set rather than one interface.
110
+ fun declaredIn(style: TrackStyle?): Boolean =
111
+ when (this) {
112
+ // Container properties have no Track slot to be declared in.
113
+ GRID_WRAP,
114
+ GRID_TILE -> false
115
+ IMAGE_SHAPE -> style?.imageShape != null
116
+ ACCESSORY_SYMBOL -> style?.accessorySymbol != null
117
+ CARD_TINT -> style?.cardTint != null
118
+ CARD_IMAGE -> style?.cardImage != null
119
+ }
120
+ }
121
+
122
+ /**
123
+ * The rest of the block's keys, dispositioned rather than ruled on: `artworkRendering` has no
124
+ * structural inertness condition (artwork renders in every layout), and `display` is positional —
125
+ * inert only in the one position that opens no page (see [playableDisplayFinding]). Declared here
126
+ * so the spec-level completeness test can hold this file to the whole vocabulary: every key of
127
+ * the block is a rule, always renderable, or positional, and a newly added property is a decision
128
+ * rather than an omission.
129
+ */
130
+ val ALWAYS_RENDERABLE = listOf("artworkRendering")
131
+
132
+ val POSITIONAL = listOf("display")
133
+
134
+ /**
135
+ * A section's effective container context: the two container values every inertness condition is
136
+ * stated against.
137
+ *
138
+ * Folded through [StyleResolver] rather than re-deriving the scope override: the diagnostic
139
+ * predicts what a renderer will do with these declarations, so it has to resolve them the way the
140
+ * renderers do.
141
+ */
142
+ private class Context(section: SectionStyle?, page: SectionStyle?) {
143
+ private val folded = StyleResolver.sectionStyle(section, page)
144
+ val isGrid = folded.display == StyleDisplay.GRID
145
+ val tile = folded.gridTile ?: GridTile.PLAIN
146
+
147
+ /**
148
+ * Whether the card treatment is what this container renders — the `card*` family's condition.
149
+ */
150
+ val isCard: Boolean
151
+ get() = isGrid && tile == GridTile.CARD
152
+
153
+ /**
154
+ * The effective style, for the message — in the wire spelling the author types ([toWireString]
155
+ * is the decoder's own mapping). `gridTile` is quoted only where a grid gives it meaning:
156
+ * naming it on a list would be advice to fix the wrong property.
157
+ */
158
+ val described: String
159
+ get() = if (!isGrid) "display 'list'" else "display 'grid', gridTile '${tile.toWireString()}'"
160
+ }
161
+
162
+ /**
163
+ * Every inert declaration on a resolved page, as ready-to-log messages — pure, so the whole rule
164
+ * set is testable off-device; the caller logs.
165
+ *
166
+ * @param path the resolved path, named in every message.
167
+ * @param pageStyle the page's own block (`ResolvedTrack.style`).
168
+ * @param sections the page's normalized sections, blocks *unfolded* — the diagnostic attributes
169
+ * each finding to the level that declared it, so it must see the levels before [StyleResolver]
170
+ * merges them.
171
+ */
172
+ fun findings(path: String, pageStyle: SectionStyle?, sections: List<Section>): List<String> {
173
+ val findings = mutableListOf<String>()
174
+ // A page-level declaration is judged across the whole page: it is live if it renders in any
175
+ // section it reaches, however many others ignore it.
176
+ val pageReached = mutableSetOf<Property>()
177
+ val pageRendered = mutableSetOf<Property>()
178
+
179
+ sections.forEachIndexed { index, section ->
180
+ val context = Context(section.style, pageStyle)
181
+ val sectionLabel = label(section, index)
182
+
183
+ for (property in Property.entries) {
184
+ val inert = property.isInert(context)
185
+
186
+ if (property.declaredIn(section.style)) {
187
+ if (inert) {
188
+ findings +=
189
+ message(property, path, sectionLabel, "its effective style is ${context.described}")
190
+ }
191
+ } else if (property.declaredIn(pageStyle)) {
192
+ // The section takes the page's value (nothing closer overrode it).
193
+ pageReached += property
194
+ if (!inert) pageRendered += property
195
+ }
196
+
197
+ if (!property.isItemProperty || !inert) continue
198
+ val tracks = section.children.count { property.declaredIn(it.style) }
199
+ if (tracks > 0) {
200
+ findings +=
201
+ message(
202
+ property,
203
+ path,
204
+ "${trackCount(tracks)} in $sectionLabel",
205
+ "the section's effective style is ${context.described}",
206
+ )
207
+ }
208
+ }
209
+
210
+ playableDisplayFinding(section, path, sectionLabel)?.let { findings += it }
211
+ }
212
+
213
+ for (property in Property.entries) {
214
+ if (property in pageReached && property !in pageRendered) {
215
+ findings +=
216
+ message(property, path, "the page block", "no section that inherits it renders one")
217
+ }
218
+ }
219
+
220
+ return findings
221
+ }
222
+
223
+ /**
224
+ * `display` is positional, so it is never inert *in* a container — but on a track that renders
225
+ * playable (`src` wins the rendering) it promises the layout of a page that never opens.
226
+ */
227
+ private fun playableDisplayFinding(section: Section, path: String, label: String): String? {
228
+ val tracks = section.children.count { it.src != null && it.style?.display != null }
229
+ if (tracks == 0) return null
230
+ return message(
231
+ path = path,
232
+ owner = "${trackCount(tracks)} in $label",
233
+ key = "display",
234
+ reason =
235
+ "`display` is the layout promise for the page a track opens, and a track rendered playable " +
236
+ "opens none",
237
+ hint = "Drop `display`, or give the track a `path` if it should be browsable.",
238
+ )
239
+ }
240
+
241
+ private fun message(property: Property, path: String, owner: String, scope: String): String =
242
+ message(
243
+ path = path,
244
+ owner = owner,
245
+ key = property.key,
246
+ reason = "${property.rule}, but $scope",
247
+ hint = property.hint,
248
+ )
249
+
250
+ /**
251
+ * The one message skeleton — every finding names the page, the declaration, who declared it, why
252
+ * it can't render, and what to do about it.
253
+ */
254
+ private fun message(
255
+ path: String,
256
+ owner: String,
257
+ key: String,
258
+ reason: String,
259
+ hint: String,
260
+ ): String =
261
+ "Inert style declaration on page '$path': `style.$key` on $owner never renders — $reason. $hint"
262
+
263
+ private fun trackCount(count: Int): String = "$count ${if (count == 1) "track" else "tracks"}"
264
+
265
+ private fun label(section: Section, index: Int): String =
266
+ section.title?.takeIf { it.isNotEmpty() }?.let { "section '$it'" } ?: "section #${index + 1}"
267
+ }
@@ -1,30 +1,30 @@
1
1
  package com.audiobrowser.browser
2
2
 
3
+ import com.audiobrowser.util.EMPTY_SECTION_STYLE
4
+ import com.audiobrowser.util.EMPTY_TRACK_STYLE
5
+ import com.audiobrowser.util.toArtworkRendering
6
+ import com.audiobrowser.util.toCardImage
7
+ import com.audiobrowser.util.toGridTile
8
+ import com.audiobrowser.util.toImageShape
9
+ import com.audiobrowser.util.toStyleDisplay
3
10
  import com.margelo.nitro.audiobrowser.CarPlaySiriListButtonPosition
4
- import com.margelo.nitro.audiobrowser.ImageRowItem
5
11
  import com.margelo.nitro.audiobrowser.ResolvedTrack
12
+ import com.margelo.nitro.audiobrowser.Section
13
+ import com.margelo.nitro.audiobrowser.SectionStyle
6
14
  import com.margelo.nitro.audiobrowser.Track
7
15
  import com.margelo.nitro.audiobrowser.TrackRequest
8
16
  import com.margelo.nitro.audiobrowser.TrackStyle
9
17
  import kotlinx.serialization.Serializable
18
+ import kotlinx.serialization.json.JsonElement
19
+ import kotlinx.serialization.json.JsonObject
20
+ import kotlinx.serialization.json.JsonPrimitive
21
+ import kotlinx.serialization.json.booleanOrNull
10
22
 
11
23
  /**
12
24
  * JSON serializable models for parsing API responses. These will be converted to Nitro types after
13
- * parsing.
25
+ * parsing. Legacy `groupTitle`/`imageRow` keys in payloads are simply unknown to these models and
26
+ * decode as ignored dead weight (ADR 0010).
14
27
  */
15
- @Serializable
16
- data class JsonImageRowItem(
17
- val id: String? = null,
18
- val url: String? = null,
19
- val src: String? = null,
20
- val artwork: String? = null,
21
- val title: String,
22
- val artist: String? = null,
23
- val album: String? = null,
24
- val albumUrl: String? = null,
25
- val live: Boolean? = null,
26
- val request: JsonTrackRequest? = null,
27
- )
28
28
 
29
29
  /** JSON model for a track's per-request HTTP override (identity/auth/signed-url). */
30
30
  @Serializable
@@ -36,60 +36,109 @@ data class JsonTrackRequest(
36
36
  fun toNitro(): TrackRequest = TrackRequest(userAgent, headers, query)
37
37
  }
38
38
 
39
+ /** JSON model for a page section (ADR 0010). */
40
+ @Serializable
41
+ data class JsonSection(
42
+ val title: String? = null,
43
+ val style: JsonElement? = null,
44
+ val path: String? = null,
45
+ val children: List<JsonTrack>,
46
+ )
47
+
39
48
  @Serializable
40
49
  data class JsonResolvedTrack(
41
50
  val id: String? = null,
42
- val url: String,
51
+ val path: String,
43
52
  val title: String,
44
53
  val subtitle: String? = null,
45
54
  val icon: String? = null,
46
55
  val artwork: JsonArtwork? = null,
47
56
  val artist: String? = null,
48
- val albumUrl: String? = null,
57
+ val albumPath: String? = null,
49
58
  val album: String? = null,
50
59
  val description: String? = null,
51
60
  val genre: String? = null,
52
61
  val duration: Double? = null,
62
+ val sections: List<JsonSection>? = null,
53
63
  val children: List<JsonTrack>? = null,
54
64
  val src: String? = null,
55
- val style: String? = null,
56
- val childrenStyle: String? = null,
57
- val groupTitle: String? = null,
65
+ val style: JsonElement? = null,
66
+ val disabled: Boolean? = null,
58
67
  val live: Boolean? = null,
68
+ val icyCharset: String? = null,
59
69
  val carPlaySiriListButton: String? = null,
60
70
  )
61
71
 
62
72
  @Serializable
63
73
  data class JsonTrack(
64
74
  val id: String? = null,
65
- val url: String? = null,
75
+ val path: String? = null,
66
76
  val title: String,
67
77
  val subtitle: String? = null,
68
78
  val icon: String? = null,
69
79
  val artwork: JsonArtwork? = null,
70
80
  val artist: String? = null,
71
- val albumUrl: String? = null,
81
+ val albumPath: String? = null,
72
82
  val album: String? = null,
73
83
  val description: String? = null,
74
84
  val genre: String? = null,
75
85
  val duration: Double? = null,
76
86
  val src: String? = null,
77
87
  val request: JsonTrackRequest? = null,
78
- val style: String? = null,
79
- val childrenStyle: String? = null,
80
- val groupTitle: String? = null,
88
+ val style: JsonElement? = null,
89
+ val disabled: Boolean? = null,
81
90
  val live: Boolean? = null,
82
- val imageRow: List<JsonImageRowItem>? = null,
91
+ val icyCharset: String? = null,
83
92
  )
84
93
 
85
- /** Convert JSON models to Nitro types */
86
- private fun String?.toTrackStyle(): TrackStyle? {
87
- return when (this?.lowercase()) {
88
- "list" -> TrackStyle.LIST
89
- "grid" -> TrackStyle.GRID
90
- else -> null
94
+ /**
95
+ * Convert JSON models to Nitro types.
96
+ *
97
+ * `style` rides the models as a raw [JsonElement] and maps leniently here — tolerant decoding by
98
+ * design (ADR 0011): a stale value (the retired string vocabulary), a wrong-typed field, or an
99
+ * unknown enum value is "no declaration", never a parse failure. A typed field would fail the whole
100
+ * page, since this decoder is otherwise strict.
101
+ */
102
+ private fun JsonObject.stringField(key: String): String? =
103
+ (get(key) as? JsonPrimitive)?.takeIf { it.isString }?.content
104
+
105
+ /** A strict JSON boolean — a quoted "false" is a wrong-typed field, i.e. no declaration. */
106
+ private fun JsonObject.booleanField(key: String): Boolean? =
107
+ (get(key) as? JsonPrimitive)?.takeIf { !it.isString }?.booleanOrNull
108
+
109
+ /**
110
+ * A block that resolves to no declarations at all — `{}` or all-unknown values — maps to null, so
111
+ * "no declaration" has one shape on every platform (iOS makes the same collapse).
112
+ */
113
+ private fun JsonElement?.toTrackStyle(): TrackStyle? =
114
+ (this as? JsonObject)?.let { obj ->
115
+ TrackStyle(
116
+ display = obj.stringField("display").toStyleDisplay(),
117
+ artworkRendering = obj.stringField("artworkRendering").toArtworkRendering(),
118
+ imageShape = obj.stringField("imageShape").toImageShape(),
119
+ // 'none' is a legitimate accessory value (the inheritance escape);
120
+ // only emptiness collapses.
121
+ accessorySymbol = obj.stringField("accessorySymbol")?.takeIf { it.isNotEmpty() },
122
+ cardTint = obj.stringField("cardTint")?.takeIf { it.isNotEmpty() },
123
+ cardImage = obj.stringField("cardImage").toCardImage(),
124
+ )
125
+ .takeIf { it != EMPTY_TRACK_STYLE }
126
+ }
127
+
128
+ private fun JsonElement?.toSectionStyle(): SectionStyle? =
129
+ (this as? JsonObject)?.let { obj ->
130
+ SectionStyle(
131
+ display = obj.stringField("display").toStyleDisplay(),
132
+ artworkRendering = obj.stringField("artworkRendering").toArtworkRendering(),
133
+ imageShape = obj.stringField("imageShape").toImageShape(),
134
+ accessorySymbol = obj.stringField("accessorySymbol")?.takeIf { it.isNotEmpty() },
135
+ cardTint = obj.stringField("cardTint")?.takeIf { it.isNotEmpty() },
136
+ cardImage = obj.stringField("cardImage").toCardImage(),
137
+ gridWrap = obj.booleanField("gridWrap"),
138
+ gridTile = obj.stringField("gridTile").toGridTile(),
139
+ )
140
+ .takeIf { it != EMPTY_SECTION_STYLE }
91
141
  }
92
- }
93
142
 
94
143
  private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosition? {
95
144
  return when (this?.lowercase()) {
@@ -99,26 +148,20 @@ private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosi
99
148
  }
100
149
  }
101
150
 
102
- private fun JsonImageRowItem.toNitro(): ImageRowItem {
103
- return ImageRowItem(
104
- id = id,
105
- url = url,
106
- src = src,
107
- artwork = artwork,
108
- artworkSource = null,
151
+ fun JsonSection.toNitro(): Section {
152
+ return Section(
109
153
  title = title,
110
- artist = artist,
111
- album = album,
112
- albumUrl = albumUrl,
113
- live = live,
114
- request = request?.toNitro(),
154
+ style = style.toSectionStyle(),
155
+ path = path,
156
+ children = children.map { it.toNitro() }.toTypedArray(),
115
157
  )
116
158
  }
117
159
 
118
160
  fun JsonResolvedTrack.toNitro(): ResolvedTrack {
119
161
  return ResolvedTrack(
120
162
  id = id,
121
- url = url,
163
+ path = path,
164
+ sections = sections?.map { it.toNitro() }?.toTypedArray(),
122
165
  children = children?.map { it.toNitro() }?.toTypedArray(),
123
166
  carPlaySiriListButton = carPlaySiriListButton.toCarPlaySiriListButtonPosition(),
124
167
  title = title,
@@ -127,45 +170,41 @@ fun JsonResolvedTrack.toNitro(): ResolvedTrack {
127
170
  artworkSource = null,
128
171
  // Containers carry no per-track media-request override (mirrors iOS JsonResolvedTrack).
129
172
  request = null,
130
- artworkCarPlayTinted = null,
131
173
  artist = artist,
132
- albumUrl = albumUrl,
174
+ albumPath = albumPath,
133
175
  album = album,
134
176
  description = description,
135
177
  genre = genre,
136
178
  duration = duration,
137
179
  src = src,
138
- style = style.toTrackStyle(),
139
- childrenStyle = childrenStyle.toTrackStyle(),
180
+ style = style.toSectionStyle(),
181
+ disabled = disabled,
140
182
  favorited = null,
141
- groupTitle = groupTitle,
142
183
  live = live,
143
- imageRow = null,
184
+ icyCharset = icyCharset,
144
185
  )
145
186
  }
146
187
 
147
188
  fun JsonTrack.toNitro(): Track {
148
189
  return Track(
149
190
  id = id,
150
- url = url,
191
+ path = path,
151
192
  title = title,
152
193
  subtitle = subtitle,
153
194
  artwork = artwork?.toNitro(),
154
195
  artworkSource = null,
155
196
  request = request?.toNitro(),
156
- artworkCarPlayTinted = null,
157
197
  artist = artist,
158
- albumUrl = albumUrl,
198
+ albumPath = albumPath,
159
199
  album = album,
160
200
  description = description,
161
201
  genre = genre,
162
202
  duration = duration,
163
203
  src = src,
164
204
  style = style.toTrackStyle(),
165
- childrenStyle = childrenStyle.toTrackStyle(),
205
+ disabled = disabled,
166
206
  favorited = null,
167
- groupTitle = groupTitle,
168
207
  live = live,
169
- imageRow = imageRow?.map { it.toNitro() }?.toTypedArray(),
208
+ icyCharset = icyCharset,
170
209
  )
171
210
  }