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
@@ -10,7 +10,7 @@ struct SFSymbolTests {
10
10
  let action = await CarPlayArtworkResolver.resolve(
11
11
  artwork: "sf:heart.fill",
12
12
  artworkSourceUri: nil,
13
- artworkCarPlayTinted: nil,
13
+ stencil: false,
14
14
  targetWidth: 40, targetHeight: 40,
15
15
  displayScale: 2.0,
16
16
  urlResolver: nil,
@@ -22,7 +22,7 @@ struct SFSymbolTests {
22
22
  let action = await CarPlayArtworkResolver.resolve(
23
23
  artwork: "sf:play.circle?bg=#000&fg=#fff",
24
24
  artworkSourceUri: nil,
25
- artworkCarPlayTinted: nil,
25
+ stencil: false,
26
26
  targetWidth: 24, targetHeight: 24,
27
27
  displayScale: 3.0,
28
28
  urlResolver: nil,
@@ -35,7 +35,7 @@ struct SFSymbolTests {
35
35
  let action = await CarPlayArtworkResolver.resolve(
36
36
  artwork: "sf:music.note",
37
37
  artworkSourceUri: nil,
38
- artworkCarPlayTinted: nil,
38
+ stencil: false,
39
39
  targetWidth: 40, targetHeight: 40,
40
40
  displayScale: 2.0,
41
41
  urlResolver: { _, _ in
@@ -51,7 +51,7 @@ struct SFSymbolTests {
51
51
  let action = await CarPlayArtworkResolver.resolve(
52
52
  artwork: nil,
53
53
  artworkSourceUri: "sf:star.fill",
54
- artworkCarPlayTinted: nil,
54
+ stencil: false,
55
55
  targetWidth: 20, targetHeight: 20,
56
56
  displayScale: 2.0,
57
57
  urlResolver: nil,
@@ -68,7 +68,7 @@ struct NoArtworkTests {
68
68
  let action = await CarPlayArtworkResolver.resolve(
69
69
  artwork: nil,
70
70
  artworkSourceUri: nil,
71
- artworkCarPlayTinted: nil,
71
+ stencil: false,
72
72
  targetWidth: 40, targetHeight: 40,
73
73
  displayScale: 2.0,
74
74
  urlResolver: nil,
@@ -80,7 +80,7 @@ struct NoArtworkTests {
80
80
  let action = await CarPlayArtworkResolver.resolve(
81
81
  artwork: nil,
82
82
  artworkSourceUri: nil,
83
- artworkCarPlayTinted: nil,
83
+ stencil: false,
84
84
  targetWidth: 40, targetHeight: 40,
85
85
  displayScale: 2.0,
86
86
  urlResolver: { _, _ in nil },
@@ -97,7 +97,7 @@ struct URLResolutionTests {
97
97
  let action = await CarPlayArtworkResolver.resolve(
98
98
  artwork: "https://example.com/art.jpg",
99
99
  artworkSourceUri: nil,
100
- artworkCarPlayTinted: false,
100
+ stencil: false,
101
101
  targetWidth: 40, targetHeight: 40,
102
102
  displayScale: 2.0,
103
103
  urlResolver: { pixelWidth, pixelHeight in
@@ -121,7 +121,7 @@ struct URLResolutionTests {
121
121
  let action = await CarPlayArtworkResolver.resolve(
122
122
  artwork: "https://example.com/art.jpg",
123
123
  artworkSourceUri: nil,
124
- artworkCarPlayTinted: nil,
124
+ stencil: false,
125
125
  targetWidth: 24, targetHeight: 24,
126
126
  displayScale: 3.0,
127
127
  urlResolver: { _, _ in
@@ -140,7 +140,7 @@ struct URLResolutionTests {
140
140
  let action = await CarPlayArtworkResolver.resolve(
141
141
  artwork: "https://example.com/art.jpg",
142
142
  artworkSourceUri: nil,
143
- artworkCarPlayTinted: nil,
143
+ stencil: false,
144
144
  targetWidth: 40, targetHeight: 40,
145
145
  displayScale: 2.0,
146
146
  urlResolver: { _, _ in nil },
@@ -157,7 +157,7 @@ struct URLResolutionTests {
157
157
  let action = await CarPlayArtworkResolver.resolve(
158
158
  artwork: "https://example.com/fallback.jpg",
159
159
  artworkSourceUri: nil,
160
- artworkCarPlayTinted: nil,
160
+ stencil: false,
161
161
  targetWidth: 40, targetHeight: 40,
162
162
  displayScale: 2.0,
163
163
  urlResolver: { _, _ in
@@ -180,7 +180,7 @@ struct URLResolutionTests {
180
180
  _ = await CarPlayArtworkResolver.resolve(
181
181
  artwork: "https://example.com/art.jpg",
182
182
  artworkSourceUri: nil,
183
- artworkCarPlayTinted: nil,
183
+ stencil: false,
184
184
  targetWidth: 50, targetHeight: 30,
185
185
  displayScale: 2.5,
186
186
  urlResolver: { w, h in
@@ -202,7 +202,7 @@ struct DirectFallbackTests {
202
202
  let action = await CarPlayArtworkResolver.resolve(
203
203
  artwork: "https://example.com/artwork.png",
204
204
  artworkSourceUri: nil,
205
- artworkCarPlayTinted: nil,
205
+ stencil: false,
206
206
  targetWidth: 40, targetHeight: 40,
207
207
  displayScale: 2.0,
208
208
  urlResolver: nil,
@@ -219,7 +219,7 @@ struct DirectFallbackTests {
219
219
  let action = await CarPlayArtworkResolver.resolve(
220
220
  artwork: nil,
221
221
  artworkSourceUri: "https://example.com/source.jpg",
222
- artworkCarPlayTinted: nil,
222
+ stencil: false,
223
223
  targetWidth: 40, targetHeight: 40,
224
224
  displayScale: 2.0,
225
225
  urlResolver: nil,
@@ -236,7 +236,7 @@ struct DirectFallbackTests {
236
236
  let action = await CarPlayArtworkResolver.resolve(
237
237
  artwork: "https://example.com/artwork.jpg",
238
238
  artworkSourceUri: "https://example.com/source.jpg",
239
- artworkCarPlayTinted: nil,
239
+ stencil: false,
240
240
  targetWidth: 40, targetHeight: 40,
241
241
  displayScale: 2.0,
242
242
  urlResolver: nil,
@@ -253,7 +253,7 @@ struct DirectFallbackTests {
253
253
  let action = await CarPlayArtworkResolver.resolve(
254
254
  artwork: "",
255
255
  artworkSourceUri: nil,
256
- artworkCarPlayTinted: nil,
256
+ stencil: false,
257
257
  targetWidth: 40, targetHeight: 40,
258
258
  displayScale: 2.0,
259
259
  urlResolver: nil,
@@ -270,7 +270,7 @@ struct SVGDetectionTests {
270
270
  let action = await CarPlayArtworkResolver.resolve(
271
271
  artwork: "https://example.com/icon.svg",
272
272
  artworkSourceUri: nil,
273
- artworkCarPlayTinted: nil,
273
+ stencil: false,
274
274
  targetWidth: 40, targetHeight: 40,
275
275
  displayScale: 2.0,
276
276
  urlResolver: nil,
@@ -287,7 +287,7 @@ struct SVGDetectionTests {
287
287
  let action = await CarPlayArtworkResolver.resolve(
288
288
  artwork: "https://example.com/icon.SVG",
289
289
  artworkSourceUri: nil,
290
- artworkCarPlayTinted: nil,
290
+ stencil: false,
291
291
  targetWidth: 40, targetHeight: 40,
292
292
  displayScale: 2.0,
293
293
  urlResolver: nil,
@@ -304,7 +304,7 @@ struct SVGDetectionTests {
304
304
  let action = await CarPlayArtworkResolver.resolve(
305
305
  artwork: "https://example.com/art.jpg",
306
306
  artworkSourceUri: nil,
307
- artworkCarPlayTinted: nil,
307
+ stencil: false,
308
308
  targetWidth: 40, targetHeight: 40,
309
309
  displayScale: 2.0,
310
310
  urlResolver: { _, _ in
@@ -323,7 +323,7 @@ struct SVGDetectionTests {
323
323
  let action = await CarPlayArtworkResolver.resolve(
324
324
  artwork: "https://example.com/photo.jpg",
325
325
  artworkSourceUri: nil,
326
- artworkCarPlayTinted: nil,
326
+ stencil: false,
327
327
  targetWidth: 40, targetHeight: 40,
328
328
  displayScale: 2.0,
329
329
  urlResolver: nil,
@@ -341,11 +341,11 @@ struct SVGDetectionTests {
341
341
 
342
342
  @Suite("tinting")
343
343
  struct TintingTests {
344
- @Test func artworkCarPlayTintedTrue_setsShouldTint() async {
344
+ @Test func stencilTrue_setsShouldTint() async {
345
345
  let action = await CarPlayArtworkResolver.resolve(
346
346
  artwork: "https://example.com/icon.png",
347
347
  artworkSourceUri: nil,
348
- artworkCarPlayTinted: true,
348
+ stencil: true,
349
349
  targetWidth: 40, targetHeight: 40,
350
350
  displayScale: 2.0,
351
351
  urlResolver: nil,
@@ -358,28 +358,11 @@ struct TintingTests {
358
358
  ))
359
359
  }
360
360
 
361
- @Test func artworkCarPlayTintedFalse_doesNotTint() async {
361
+ @Test func stencilFalse_doesNotTint() async {
362
362
  let action = await CarPlayArtworkResolver.resolve(
363
363
  artwork: "https://example.com/photo.jpg",
364
364
  artworkSourceUri: nil,
365
- artworkCarPlayTinted: false,
366
- targetWidth: 40, targetHeight: 40,
367
- displayScale: 2.0,
368
- urlResolver: nil,
369
- )
370
- #expect(action == .fetch(
371
- uri: "https://example.com/photo.jpg",
372
- headers: nil,
373
- shouldTint: false,
374
- isSvg: false,
375
- ))
376
- }
377
-
378
- @Test func artworkCarPlayTintedNil_defaultsToFalse() async {
379
- let action = await CarPlayArtworkResolver.resolve(
380
- artwork: "https://example.com/photo.jpg",
381
- artworkSourceUri: nil,
382
- artworkCarPlayTinted: nil,
365
+ stencil: false,
383
366
  targetWidth: 40, targetHeight: 40,
384
367
  displayScale: 2.0,
385
368
  urlResolver: nil,
@@ -396,7 +379,7 @@ struct TintingTests {
396
379
  let action = await CarPlayArtworkResolver.resolve(
397
380
  artwork: "https://example.com/icon.png",
398
381
  artworkSourceUri: nil,
399
- artworkCarPlayTinted: true,
382
+ stencil: true,
400
383
  targetWidth: 40, targetHeight: 40,
401
384
  displayScale: 2.0,
402
385
  urlResolver: { _, _ in
@@ -0,0 +1,200 @@
1
+ import Testing
2
+
3
+ @testable import AudioBrowserTestable
4
+
5
+ // The dev diagnostic for declarations that can never render (ADR 0011): the
6
+ // recovery for the compile-time invalid-combination errors the block model
7
+ // deliberately gave up. Structural inertness only — a surface that can't draw
8
+ // a property it understands is intended usage, never a finding.
9
+
10
+ @Suite("InertStyleDiagnostic")
11
+ struct InertStyleDiagnosticTests {
12
+ private func findings(
13
+ _ sections: [Section], page: SectionStyle? = nil, path: String = "/home",
14
+ ) -> [String] {
15
+ InertStyleDiagnostic.findings(path: path, pageStyle: page, sections: sections)
16
+ }
17
+
18
+ private func playable(_ src: String = "https://s/a.mp3", style: TrackStyle? = nil) -> Track {
19
+ Track(src: src, title: "T", style: style)
20
+ }
21
+
22
+ private func browsable(_ path: String = "/albums/1", style: TrackStyle? = nil) -> Track {
23
+ Track(path: path, title: "T", style: style)
24
+ }
25
+
26
+ // MARK: - Container properties
27
+
28
+ @Test func gridWrapOnAListSectionIsInert() {
29
+ let found = findings([Section(title: "Recent", style: SectionStyle(gridWrap: false), children: [])])
30
+ #expect(found.count == 1)
31
+ #expect(found[0].contains("section 'Recent'"))
32
+ #expect(found[0].contains("style.gridWrap"))
33
+ #expect(found[0].contains("display 'list'"))
34
+ #expect(found[0].contains("/home"))
35
+ }
36
+
37
+ @Test func gridWrapInAGridIsLive() {
38
+ let style = SectionStyle(gridWrap: false, display: .grid)
39
+ #expect(findings([Section(style: style, children: [])]).isEmpty)
40
+ }
41
+
42
+ @Test func aSectionInheritsThePagesGridDisplay() {
43
+ // `display` resolves by scope override, so the page's grid makes the
44
+ // section's `gridWrap` live without the section restating it.
45
+ let found = findings(
46
+ [Section(style: SectionStyle(gridWrap: false), children: [])],
47
+ page: SectionStyle(display: .grid),
48
+ )
49
+ #expect(found.isEmpty)
50
+ }
51
+
52
+ @Test func gridTileOnAListSectionIsInert() {
53
+ let found = findings([Section(style: SectionStyle(gridTile: .card), children: [])])
54
+ #expect(found.count == 1)
55
+ #expect(found[0].contains("style.gridTile"))
56
+ #expect(found[0].contains("section #1"))
57
+ }
58
+
59
+ // MARK: - Item properties
60
+
61
+ @Test func cardPropertiesNeedTheCardTreatment() {
62
+ let plainGrid = SectionStyle(display: .grid, cardTint: "#1e3a8a", cardImage: .background)
63
+ let found = findings([Section(title: "Featured", style: plainGrid, children: [])])
64
+ #expect(found.count == 2)
65
+ #expect(found.contains { $0.contains("style.cardTint") })
66
+ #expect(found.contains { $0.contains("style.cardImage") })
67
+ #expect(found.allSatisfy { $0.contains("display 'grid', gridTile 'plain'") })
68
+ }
69
+
70
+ @Test func cardPropertiesInACardGridAreLive() {
71
+ let cards = SectionStyle(gridTile: .card, display: .grid, cardTint: "#1e3a8a", cardImage: .background)
72
+ #expect(findings([Section(style: cards, children: [])]).isEmpty)
73
+ }
74
+
75
+ @Test func imageShapeNeedsAShapedTile() {
76
+ let list = SectionStyle(imageShape: .circular)
77
+ let cards = SectionStyle(gridTile: .card, display: .grid, imageShape: .circular)
78
+ let condensed = SectionStyle(gridTile: .condensed, display: .grid, imageShape: .circular)
79
+ #expect(findings([Section(style: list, children: [])]).count == 1)
80
+ #expect(findings([Section(style: cards, children: [])]).count == 1)
81
+ // Plain and condensed tiles both take a shape.
82
+ #expect(findings([Section(style: condensed, children: [])]).isEmpty)
83
+ }
84
+
85
+ @Test func accessorySymbolIsInertOnlyOnCards() {
86
+ let rows = SectionStyle(accessorySymbol: "lock.fill")
87
+ let cards = SectionStyle(gridTile: .card, display: .grid, accessorySymbol: "lock.fill")
88
+ // List rows draw accessories; card elements have no slot for one.
89
+ #expect(findings([Section(style: rows, children: [])]).isEmpty)
90
+ let found = findings([Section(style: cards, children: [])])
91
+ #expect(found.count == 1)
92
+ #expect(found[0].contains("no accessory slot"))
93
+ }
94
+
95
+ @Test func artworkRenderingIsNeverInert() {
96
+ let stencilled = SectionStyle(gridTile: .card, display: .grid, artworkRendering: .stencil)
97
+ #expect(findings([Section(style: stencilled, children: [])]).isEmpty)
98
+ }
99
+
100
+ // MARK: - Track-level declarations
101
+
102
+ @Test func trackDeclarationsAreReportedPerSection() {
103
+ let section = Section(
104
+ title: "Results", style: SectionStyle(display: .grid),
105
+ children: [
106
+ playable(style: TrackStyle(cardTint: "#111")),
107
+ playable("https://s/b.mp3", style: TrackStyle(cardTint: "#222")),
108
+ playable("https://s/c.mp3"),
109
+ ],
110
+ )
111
+ let found = findings([section])
112
+ #expect(found.count == 1)
113
+ #expect(found[0].contains("2 tracks in section 'Results'"))
114
+ #expect(found[0].contains("style.cardTint"))
115
+ }
116
+
117
+ @Test func aTracksOwnShapeIsLiveInAPlainGrid() {
118
+ let section = Section(
119
+ style: SectionStyle(display: .grid),
120
+ children: [playable(style: TrackStyle(imageShape: .circular))],
121
+ )
122
+ #expect(findings([section]).isEmpty)
123
+ }
124
+
125
+ // MARK: - The page block
126
+
127
+ @Test func aPageDeclarationLiveInOneSectionIsNotReported() {
128
+ // Card tint reaches both sections; one renders cards. Live is live.
129
+ let found = findings(
130
+ [
131
+ Section(style: SectionStyle(gridTile: .card, display: .grid), children: []),
132
+ Section(style: SectionStyle(display: .grid), children: []),
133
+ ],
134
+ page: SectionStyle(cardTint: "#1e3a8a"),
135
+ )
136
+ #expect(found.isEmpty)
137
+ }
138
+
139
+ @Test func aPageDeclarationNoSectionRendersIsReportedOnce() {
140
+ let found = findings(
141
+ [
142
+ Section(title: "A", style: SectionStyle(display: .grid), children: []),
143
+ Section(title: "B", children: []),
144
+ ],
145
+ page: SectionStyle(cardTint: "#1e3a8a"),
146
+ )
147
+ #expect(found.count == 1)
148
+ #expect(found[0].contains("the page block"))
149
+ #expect(found[0].contains("no section that inherits it"))
150
+ }
151
+
152
+ @Test func aSectionOverrideShadowsThePageValue() {
153
+ // Every section declares its own tint, so the page's is never resolved —
154
+ // shadowed, not inert; the diagnostic reports what can't render, not what
155
+ // isn't reached.
156
+ let found = findings(
157
+ [Section(style: SectionStyle(gridTile: .card, display: .grid, cardTint: "#000"), children: [])],
158
+ page: SectionStyle(cardTint: "#1e3a8a"),
159
+ )
160
+ #expect(found.isEmpty)
161
+ }
162
+
163
+ @Test func aPageWideGridMakesSectionDeclarationsLive() {
164
+ let found = findings(
165
+ [Section(style: SectionStyle(cardTint: "#111"), children: [])],
166
+ page: SectionStyle(gridTile: .card, display: .grid),
167
+ )
168
+ #expect(found.isEmpty)
169
+ }
170
+
171
+ // MARK: - The positional `display`
172
+
173
+ @Test func displayOnAPlayableTrackIsInert() {
174
+ let section = Section(
175
+ title: "Tracks",
176
+ children: [
177
+ playable(style: TrackStyle(display: .grid)),
178
+ browsable(style: TrackStyle(display: .grid)),
179
+ ],
180
+ )
181
+ let found = findings([section])
182
+ #expect(found.count == 1)
183
+ #expect(found[0].contains("1 track in section 'Tracks'"))
184
+ #expect(found[0].contains("style.display"))
185
+ #expect(found[0].contains("opens none"))
186
+ }
187
+
188
+ @Test func displayOnASectionOrPageIsNeverInert() {
189
+ let found = findings(
190
+ [Section(style: SectionStyle(display: .list), children: [browsable()])],
191
+ page: SectionStyle(display: .grid),
192
+ )
193
+ #expect(found.isEmpty)
194
+ }
195
+
196
+ @Test func anUnstyledPageIsSilent() {
197
+ #expect(findings([Section(children: [playable(), browsable()])]).isEmpty)
198
+ #expect(findings([]).isEmpty)
199
+ }
200
+ }
@@ -40,7 +40,7 @@ struct JsonTrackArtworkTests {
40
40
  // The shape that shipped broken: a container decodes its children eagerly,
41
41
  // so one unparseable row rejected the whole page rather than losing an image.
42
42
  let json = """
43
- { "url": "/favorites", "title": "Favorites", "children": [
43
+ { "path": "/favorites", "title": "Favorites", "children": [
44
44
  { "title": "Playlist", "artwork": { "light": "https://e.example/l.png", "dark": "https://e.example/d.png" } }
45
45
  ] }
46
46
  """.data(using: .utf8)!
@@ -56,3 +56,152 @@ struct JsonTrackArtworkTests {
56
56
  }
57
57
  }
58
58
  }
59
+
60
+ // MARK: - Style blocks (ADR 0011)
61
+
62
+ @Suite("JsonTrack.style")
63
+ struct JsonStyleTests {
64
+ @Test func decodesABlockOntoTrackStyle() throws {
65
+ let json = Data(
66
+ """
67
+ {"title":"X","style":{"display":"grid","artworkRendering":"stencil"}}
68
+ """.utf8)
69
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
70
+ #expect(track.style?.display == .grid)
71
+ #expect(track.style?.artworkRendering == .stencil)
72
+ }
73
+
74
+ @Test func decodesABlockOntoSectionStyle() throws {
75
+ let json = Data(
76
+ """
77
+ {"path":"/home","title":"Home","sections":[
78
+ {"style":{"display":"grid","gridWrap":false},"children":[{"title":"C","src":"s"}]}
79
+ ]}
80
+ """.utf8)
81
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
82
+ let style = resolved.sections?.first?.style
83
+ #expect(style?.display == .grid)
84
+ #expect(style?.gridWrap == false)
85
+ }
86
+
87
+ @Test func staleStringStyleDecodesAsNoDeclaration() throws {
88
+ // The retired string vocabulary must never kill the page (tolerant
89
+ // decoding, ADR 0011) — it decodes as an empty block.
90
+ let json = Data(
91
+ """
92
+ {"path":"/home","title":"Home","style":"rail","children":[
93
+ {"title":"C","src":"s","style":"grid"}
94
+ ]}
95
+ """.utf8)
96
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
97
+ #expect(resolved.style == nil)
98
+ #expect(resolved.children?.first?.style == nil)
99
+ }
100
+
101
+ @Test func unknownEnumValuesDecodeAsNoDeclaration() throws {
102
+ let json = Data(
103
+ """
104
+ {"title":"X","style":{"display":"carousel","artworkRendering":"embossed","gridWrap":"yes"}}
105
+ """.utf8)
106
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
107
+ // All-unknown declarations collapse to "no block" — one shape for "no
108
+ // declaration" on every platform.
109
+ #expect(track.style == nil)
110
+ }
111
+
112
+ @Test func decodesDisabled() throws {
113
+ let json = Data("""
114
+ {"title":"X","src":"s","disabled":true}
115
+ """.utf8)
116
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
117
+ #expect(track.disabled == true)
118
+ }
119
+
120
+ @Test func decodesImageShapeAndAccessorySymbol() throws {
121
+ let json = Data(
122
+ """
123
+ {"title":"X","style":{"imageShape":"circular","accessorySymbol":"lock.fill"}}
124
+ """.utf8)
125
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
126
+ #expect(track.style?.imageShape == .circular)
127
+ #expect(track.style?.accessorySymbol == "lock.fill")
128
+ }
129
+
130
+ @Test func accessorySymbolNoneIsAValueNotEmptiness() throws {
131
+ // The inheritance-escape sentinel must survive the wire — collapsing it
132
+ // to "no declaration" would let the section value flow back in.
133
+ let json = Data(
134
+ """
135
+ {"title":"X","style":{"accessorySymbol":"none"}}
136
+ """.utf8)
137
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
138
+ #expect(track.style?.accessorySymbol == "none")
139
+ }
140
+
141
+ @Test func unknownImageShapeDecodesAsNoDeclaration() throws {
142
+ let json = Data(
143
+ """
144
+ {"title":"X","style":{"imageShape":"hexagonal"}}
145
+ """.utf8)
146
+ let track = try JSONDecoder().decode(JsonTrack.self, from: json).toNitro()
147
+ #expect(track.style == nil)
148
+ }
149
+
150
+ @Test func decodesTheCardFamily() throws {
151
+ let json = Data(
152
+ """
153
+ {"path":"/home","title":"Home","sections":[
154
+ {"style":{"display":"grid","gridTile":"card","cardTint":"#1e3a8a","cardImage":"background"},
155
+ "children":[{"title":"C","src":"s"}]}
156
+ ]}
157
+ """.utf8)
158
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
159
+ let style = resolved.sections?.first?.style
160
+ #expect(style?.gridTile == .card)
161
+ #expect(style?.cardTint == "#1e3a8a")
162
+ #expect(style?.cardImage == .background)
163
+ }
164
+
165
+ @Test func unknownGridTileDecodesAsNoDeclaration() throws {
166
+ let json = Data(
167
+ """
168
+ {"path":"/home","title":"Home","sections":[
169
+ {"style":{"gridTile":"hexagon"},"children":[{"title":"C","src":"s"}]}
170
+ ]}
171
+ """.utf8)
172
+ let resolved = try JSONDecoder().decode(JsonResolvedTrack.self, from: json).toNitro()
173
+ #expect(resolved.sections?.first?.style == nil)
174
+ }
175
+
176
+ @Test func styleAndDisabledSurviveThePersistenceRoundTrip() throws {
177
+ // Restored queues keep their visible fidelity across process death.
178
+ let original = JsonTrack(
179
+ title: "X", src: "s",
180
+ style: JsonStyle(
181
+ display: "grid", artworkRendering: "stencil",
182
+ imageShape: "circular", accessorySymbol: "lock.fill",
183
+ ),
184
+ disabled: true,
185
+ )
186
+ let encoded = try JSONEncoder().encode(original)
187
+ let decoded = try JSONDecoder().decode(JsonTrack.self, from: encoded)
188
+ #expect(decoded.style == original.style)
189
+ #expect(decoded.disabled == true)
190
+ }
191
+
192
+ @Test func liveBlockSnapshotsAllItemProperties() throws {
193
+ // The persistence snapshot (JsonStyle from a live TrackStyle) must not
194
+ // drop the newer item properties.
195
+ let snapshot = JsonStyle(
196
+ TrackStyle(
197
+ display: nil, artworkRendering: .stencil,
198
+ imageShape: .circular, accessorySymbol: "none",
199
+ cardTint: "#1e3a8a", cardImage: .background,
200
+ ))
201
+ #expect(snapshot?.artworkRendering == "stencil")
202
+ #expect(snapshot?.imageShape == "circular")
203
+ #expect(snapshot?.accessorySymbol == "none")
204
+ #expect(snapshot?.cardTint == "#1e3a8a")
205
+ #expect(snapshot?.cardImage == "background")
206
+ }
207
+ }
@@ -0,0 +1,111 @@
1
+ import Foundation
2
+ import Testing
3
+
4
+ @testable import AudioBrowserTestable
5
+
6
+ @Suite("LRUCache")
7
+ struct LRUCacheTests {
8
+ /// A class so the tests can observe release through a weak reference.
9
+ private final class Box {
10
+ let id: Int
11
+ init(_ id: Int) { self.id = id }
12
+ }
13
+
14
+ @Test func evictsTheLeastRecentlyUsed() {
15
+ let cache = LRUCache<String, Int>(maxSize: 2)
16
+ cache.set("a", value: 1)
17
+ cache.set("b", value: 2)
18
+ cache.set("c", value: 3)
19
+
20
+ #expect(cache.get("a") == nil)
21
+ #expect(cache.get("b") == 2)
22
+ #expect(cache.get("c") == 3)
23
+ #expect(cache.count == 2)
24
+ }
25
+
26
+ /// A read is a use: it must move the entry off the eviction end.
27
+ @Test func readingPromotesAnEntry() {
28
+ let cache = LRUCache<String, Int>(maxSize: 2)
29
+ cache.set("a", value: 1)
30
+ cache.set("b", value: 2)
31
+ _ = cache.get("a")
32
+ cache.set("c", value: 3)
33
+
34
+ #expect(cache.get("a") == 1)
35
+ #expect(cache.get("b") == nil)
36
+ }
37
+
38
+ @Test func removeDropsOnlyItsOwnEntry() {
39
+ let cache = LRUCache<String, Int>(maxSize: 3)
40
+ cache.set("a", value: 1)
41
+ cache.set("b", value: 2)
42
+ cache.set("c", value: 3)
43
+
44
+ cache.remove("b")
45
+
46
+ #expect(cache.get("b") == nil)
47
+ #expect(cache.get("a") == 1)
48
+ #expect(cache.get("c") == 3)
49
+ #expect(cache.count == 2)
50
+ }
51
+
52
+ /// The nodes form a doubly-linked list. If both directions were strong, adjacent nodes would
53
+ /// retain each other and clearing would drop only the dictionary and the two ends — the chain,
54
+ /// and everything it holds, would survive (#96).
55
+ @Test func clearReleasesEveryNode() {
56
+ let cache = LRUCache<String, Box>(maxSize: 10)
57
+ weak var first: Box?
58
+ weak var middle: Box?
59
+ weak var last: Box?
60
+
61
+ do {
62
+ let a = Box(1), b = Box(2), c = Box(3)
63
+ (first, middle, last) = (a, b, c)
64
+ cache.set("a", value: a)
65
+ cache.set("b", value: b)
66
+ cache.set("c", value: c)
67
+ }
68
+
69
+ #expect(first != nil, "the cache should still hold its entries")
70
+
71
+ cache.clear()
72
+
73
+ #expect(first == nil)
74
+ #expect(middle == nil)
75
+ #expect(last == nil)
76
+ #expect(cache.count == 0)
77
+ }
78
+
79
+ /// Same cycle, reached by dropping the cache itself rather than clearing it.
80
+ @Test func droppingTheCacheReleasesEveryNode() {
81
+ weak var first: Box?
82
+ weak var last: Box?
83
+
84
+ do {
85
+ let cache = LRUCache<String, Box>(maxSize: 10)
86
+ let a = Box(1), b = Box(2)
87
+ (first, last) = (a, b)
88
+ cache.set("a", value: a)
89
+ cache.set("b", value: b)
90
+ #expect(first != nil)
91
+ }
92
+
93
+ #expect(first == nil)
94
+ #expect(last == nil)
95
+ }
96
+
97
+ /// Eviction has always unlinked the node it drops; this pins that down alongside the rest.
98
+ @Test func evictionReleasesTheEvictedNode() {
99
+ let cache = LRUCache<String, Box>(maxSize: 1)
100
+ weak var evicted: Box?
101
+
102
+ do {
103
+ let a = Box(1)
104
+ evicted = a
105
+ cache.set("a", value: a)
106
+ cache.set("b", value: Box(2))
107
+ }
108
+
109
+ #expect(evicted == nil)
110
+ }
111
+ }