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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (660) hide show
  1. package/README.md +35 -21
  2. package/android/build.gradle +35 -12
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
  11. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  12. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
  13. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
  14. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  15. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
  16. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  17. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  18. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  19. package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
  20. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
  21. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
  22. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  23. package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
  24. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
  25. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
  26. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  27. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
  28. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
  29. package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
  30. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  31. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
  32. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  33. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  34. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  35. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  36. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  37. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  38. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  39. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  40. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
  41. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  42. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  43. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
  44. package/android/src/main/res/values/strings.xml +25 -5
  45. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  46. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
  47. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  48. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
  49. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
  50. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  51. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  52. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
  53. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  54. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
  55. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  56. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  57. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  58. package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
  59. package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
  60. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
  61. package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
  62. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  63. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  64. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  65. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  66. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  67. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  68. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  69. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  70. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  71. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  72. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  73. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
  74. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  75. package/ios/Browser/BrowserConfig.swift +102 -5
  76. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  77. package/ios/Browser/BrowserManager.swift +230 -205
  78. package/ios/Browser/BrowserPathHelper.swift +44 -44
  79. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  80. package/ios/Browser/JsonModels.swift +264 -106
  81. package/ios/Browser/SearchDrillIn.swift +7 -7
  82. package/ios/Browser/SectionPresentation.swift +64 -0
  83. package/ios/Browser/SectionScope.swift +58 -24
  84. package/ios/Browser/StyleResolver.swift +58 -0
  85. package/ios/Browser/TabBarEntries.swift +9 -3
  86. package/ios/Browser/TrackIdTemplate.swift +37 -0
  87. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  88. package/ios/CarPlay/CarPlayController.swift +227 -85
  89. package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
  90. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  91. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  92. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  93. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  94. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  95. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  96. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  97. package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
  98. package/ios/Extension/Section+Copying.swift +29 -0
  99. package/ios/Extension/String+IcyCharset.swift +21 -0
  100. package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
  101. package/ios/Extension/Track+Copying.swift +8 -12
  102. package/ios/Extension/Track+Identity.swift +18 -0
  103. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  104. package/ios/Http/HttpClient.swift +8 -2
  105. package/ios/HybridAudioBrowser.swift +253 -100
  106. package/ios/Model/NitroTypeStubs.swift +125 -23
  107. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  108. package/ios/Model/RemoteCommand.swift +18 -0
  109. package/ios/Model/TrackPlayerError.swift +2 -2
  110. package/ios/Option/SessionCategories.swift +3 -2
  111. package/ios/Player/MediaIntentCriteria.swift +17 -9
  112. package/ios/Player/MediaLoader.swift +33 -9
  113. package/ios/Player/NowPlayingUpdater.swift +54 -6
  114. package/ios/Player/PlaybackCoordinator.swift +13 -6
  115. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  116. package/ios/Player/PlaybackTimer.swift +8 -2
  117. package/ios/Player/QueueManager.swift +46 -13
  118. package/ios/Player/RetryManager.swift +80 -18
  119. package/ios/Player/SeekCompletionHandler.swift +3 -1
  120. package/ios/Player/ShuffleOrder.swift +33 -0
  121. package/ios/Player/SleepTimerHandling.swift +13 -0
  122. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  123. package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
  124. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  125. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  126. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  127. package/ios/Tests/JsonModelsTests.swift +150 -1
  128. package/ios/Tests/LRUCacheTests.swift +111 -0
  129. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  130. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  131. package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
  132. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  133. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  134. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  135. package/ios/Tests/QueueManagerTests.swift +122 -10
  136. package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
  137. package/ios/Tests/RemoteCommandTests.swift +67 -0
  138. package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
  139. package/ios/Tests/RetryManagerTests.swift +89 -0
  140. package/ios/Tests/SearchDrillInTests.swift +16 -16
  141. package/ios/Tests/SectionPresentationTests.swift +90 -0
  142. package/ios/Tests/SectionScopeTests.swift +87 -55
  143. package/ios/Tests/StyleResolverTests.swift +127 -0
  144. package/ios/Tests/Support/TestTypes.swift +8 -1
  145. package/ios/Tests/TabBarEntriesTests.swift +19 -19
  146. package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
  147. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  148. package/ios/Tests/TrackIdentityTests.swift +20 -0
  149. package/ios/Tests/TrackSelectorTests.swift +168 -33
  150. package/ios/TrackPlayer.swift +63 -35
  151. package/ios/TrackPlayerCallbacks.swift +5 -38
  152. package/ios/TrackSelector.swift +45 -30
  153. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  154. package/ios/Util/LRUCache.swift +4 -1
  155. package/ios/Util/NetworkMonitor.swift +43 -26
  156. package/ios/Util/SFSymbolRenderer.swift +24 -2
  157. package/ios/Util/SVGProcessor.swift +9 -2
  158. package/lib/commonjs/AudioBrowser.js +8 -8
  159. package/lib/commonjs/AudioBrowser.js.map +1 -1
  160. package/lib/commonjs/CLAUDE.md +52 -0
  161. package/lib/commonjs/features/browser-config.js +12 -8
  162. package/lib/commonjs/features/browser-config.js.map +1 -1
  163. package/lib/commonjs/features/browser.js +11 -3
  164. package/lib/commonjs/features/browser.js.map +1 -1
  165. package/lib/commonjs/features/carConnection.js +1 -1
  166. package/lib/commonjs/features/equalizer.js +1 -1
  167. package/lib/commonjs/features/errors.js +3 -3
  168. package/lib/commonjs/features/favorites.js +14 -11
  169. package/lib/commonjs/features/favorites.js.map +1 -1
  170. package/lib/commonjs/features/gate.js +16 -1
  171. package/lib/commonjs/features/gate.js.map +1 -1
  172. package/lib/commonjs/features/metadata.js +20 -3
  173. package/lib/commonjs/features/metadata.js.map +1 -1
  174. package/lib/commonjs/features/network.js +1 -1
  175. package/lib/commonjs/features/nowPlaying.js +1 -1
  176. package/lib/commonjs/features/output.js +1 -1
  177. package/lib/commonjs/features/playback/controls.js +4 -2
  178. package/lib/commonjs/features/playback/controls.js.map +1 -1
  179. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  180. package/lib/commonjs/features/playback/playing.js +1 -1
  181. package/lib/commonjs/features/playback/progress.js +3 -2
  182. package/lib/commonjs/features/playback/progress.js.map +1 -1
  183. package/lib/commonjs/features/playback/rate.js +8 -1
  184. package/lib/commonjs/features/playback/rate.js.map +1 -1
  185. package/lib/commonjs/features/playback/state.js +3 -4
  186. package/lib/commonjs/features/playback/state.js.map +1 -1
  187. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  188. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  189. package/lib/commonjs/features/playback/volume.js +1 -1
  190. package/lib/commonjs/features/player/options.js +48 -34
  191. package/lib/commonjs/features/player/options.js.map +1 -1
  192. package/lib/commonjs/features/player/setup.js +18 -9
  193. package/lib/commonjs/features/player/setup.js.map +1 -1
  194. package/lib/commonjs/features/player/validateOptions.js +23 -0
  195. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  196. package/lib/commonjs/features/queue/activeTrack.js +15 -4
  197. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  198. package/lib/commonjs/features/queue/queue.js +13 -6
  199. package/lib/commonjs/features/queue/queue.js.map +1 -1
  200. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  201. package/lib/commonjs/features/queue/shuffle.js +1 -1
  202. package/lib/commonjs/features/remoteControls.js +13 -92
  203. package/lib/commonjs/features/remoteControls.js.map +1 -1
  204. package/lib/commonjs/features/sleepTimer.js +13 -1
  205. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  206. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  207. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  208. package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
  209. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  210. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  211. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  212. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  213. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  214. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  215. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  216. package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
  217. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  218. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
  219. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
  220. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  221. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  222. package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
  223. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  224. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  225. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  226. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
  227. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
  228. package/lib/commonjs/web/browser/BrowserManager.js +175 -100
  229. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  230. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  231. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  232. package/lib/commonjs/web/browser/SearchManager.js +18 -23
  233. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  234. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  235. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  236. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  237. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  238. package/lib/commonjs/web/browser/sections.js +89 -0
  239. package/lib/commonjs/web/browser/sections.js.map +1 -0
  240. package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
  241. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  242. package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
  243. package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
  244. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  245. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  246. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  247. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  248. package/lib/module/AudioBrowser.js +2 -1
  249. package/lib/module/AudioBrowser.js.map +1 -1
  250. package/lib/module/CLAUDE.md +52 -0
  251. package/lib/module/features/browser-config.js +12 -8
  252. package/lib/module/features/browser-config.js.map +1 -1
  253. package/lib/module/features/browser.js +11 -3
  254. package/lib/module/features/browser.js.map +1 -1
  255. package/lib/module/features/carConnection.js +1 -1
  256. package/lib/module/features/equalizer.js +1 -1
  257. package/lib/module/features/errors.js +3 -3
  258. package/lib/module/features/favorites.js +14 -11
  259. package/lib/module/features/favorites.js.map +1 -1
  260. package/lib/module/features/gate.js +16 -1
  261. package/lib/module/features/gate.js.map +1 -1
  262. package/lib/module/features/metadata.js +20 -3
  263. package/lib/module/features/metadata.js.map +1 -1
  264. package/lib/module/features/network.js +1 -1
  265. package/lib/module/features/nowPlaying.js +1 -1
  266. package/lib/module/features/output.js +1 -1
  267. package/lib/module/features/playback/controls.js +4 -2
  268. package/lib/module/features/playback/controls.js.map +1 -1
  269. package/lib/module/features/playback/playWhenReady.js +1 -1
  270. package/lib/module/features/playback/playing.js +1 -1
  271. package/lib/module/features/playback/progress.js +3 -2
  272. package/lib/module/features/playback/progress.js.map +1 -1
  273. package/lib/module/features/playback/rate.js +8 -1
  274. package/lib/module/features/playback/rate.js.map +1 -1
  275. package/lib/module/features/playback/state.js +3 -4
  276. package/lib/module/features/playback/state.js.map +1 -1
  277. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  278. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  279. package/lib/module/features/playback/volume.js +1 -1
  280. package/lib/module/features/player/options.js +47 -32
  281. package/lib/module/features/player/options.js.map +1 -1
  282. package/lib/module/features/player/setup.js +17 -8
  283. package/lib/module/features/player/setup.js.map +1 -1
  284. package/lib/module/features/player/validateOptions.js +19 -0
  285. package/lib/module/features/player/validateOptions.js.map +1 -0
  286. package/lib/module/features/queue/activeTrack.js +15 -4
  287. package/lib/module/features/queue/activeTrack.js.map +1 -1
  288. package/lib/module/features/queue/queue.js +13 -6
  289. package/lib/module/features/queue/queue.js.map +1 -1
  290. package/lib/module/features/queue/repeatMode.js +1 -1
  291. package/lib/module/features/queue/shuffle.js +1 -1
  292. package/lib/module/features/remoteControls.js +13 -92
  293. package/lib/module/features/remoteControls.js.map +1 -1
  294. package/lib/module/features/sleepTimer.js +13 -1
  295. package/lib/module/features/sleepTimer.js.map +1 -1
  296. package/lib/module/test-utils/reactNativeStub.js +58 -0
  297. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  298. package/lib/module/utils/LazyNativeEmitter.js +28 -14
  299. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  300. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  301. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  302. package/lib/module/utils/getTrackIdentity.js +20 -0
  303. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  304. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  305. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  306. package/lib/module/web/NativeAudioBrowser.js +200 -75
  307. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  308. package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
  309. package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
  310. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  311. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  312. package/lib/module/web/TrackPlayer/Player.js +254 -39
  313. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  314. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  315. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  316. package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
  317. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
  318. package/lib/module/web/browser/BrowserManager.js +175 -100
  319. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  320. package/lib/module/web/browser/FavoriteManager.js +31 -21
  321. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  322. package/lib/module/web/browser/SearchManager.js +18 -23
  323. package/lib/module/web/browser/SearchManager.js.map +1 -1
  324. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  325. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  326. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  327. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  328. package/lib/module/web/browser/sections.js +83 -0
  329. package/lib/module/web/browser/sections.js.map +1 -0
  330. package/lib/module/web/http/RequestConfigBuilder.js +110 -27
  331. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  332. package/lib/module/web/http/followMediaRedirect.js +215 -0
  333. package/lib/module/web/http/followMediaRedirect.js.map +1 -0
  334. package/lib/module/web/player/NowPlayingManager.js +4 -3
  335. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  336. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  337. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  338. package/lib/typescript/package.json +1 -0
  339. package/lib/typescript/src/AudioBrowser.d.ts +1 -1
  340. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  341. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  342. package/lib/typescript/src/features/browser.d.ts +10 -2
  343. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  344. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  345. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  346. package/lib/typescript/src/features/errors.d.ts +3 -3
  347. package/lib/typescript/src/features/favorites.d.ts +14 -11
  348. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  349. package/lib/typescript/src/features/gate.d.ts +18 -2
  350. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  351. package/lib/typescript/src/features/metadata.d.ts +20 -3
  352. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  353. package/lib/typescript/src/features/network.d.ts +1 -1
  354. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  355. package/lib/typescript/src/features/output.d.ts +1 -1
  356. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  357. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  358. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  359. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  360. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  361. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  362. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  363. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  364. package/lib/typescript/src/features/playback/state.d.ts +3 -4
  365. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  366. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  367. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  368. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  369. package/lib/typescript/src/features/player/options.d.ts +108 -56
  370. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  371. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  372. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  373. package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
  374. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  375. package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
  376. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  377. package/lib/typescript/src/features/queue/queue.d.ts +7 -4
  378. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  379. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  380. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  381. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  382. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  383. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  384. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  385. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  386. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  387. package/lib/typescript/src/types/browser-native.d.ts +3 -2
  388. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  389. package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
  390. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  391. package/lib/typescript/src/types/browser.d.ts +82 -57
  392. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  393. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
  394. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  395. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  396. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  397. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  398. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  399. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  400. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  401. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
  402. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
  403. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  404. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  405. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  406. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  407. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  408. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  409. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  410. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  411. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  412. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  413. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  414. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  415. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  416. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  417. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
  418. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  419. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  420. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
  421. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  422. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  423. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  424. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  425. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
  426. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  427. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  428. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  429. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  430. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
  431. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  432. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
  433. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
  434. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  435. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  436. package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
  437. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  438. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  439. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  440. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  441. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
  442. package/nitrogen/generated/android/c++/JSection.hpp +116 -0
  443. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  444. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  445. package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
  446. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  447. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  448. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  449. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  450. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  451. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  452. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  453. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  454. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  455. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
  456. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  457. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  458. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  459. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  460. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
  461. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  462. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  463. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  464. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  465. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  466. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
  467. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
  468. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  469. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  470. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
  471. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  472. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  473. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
  474. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
  475. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
  476. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
  477. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  478. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  479. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  480. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  481. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
  482. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  483. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  484. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  485. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  486. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  487. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
  488. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  489. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  490. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  491. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  492. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  493. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
  494. package/nitrogen/generated/ios/swift/Section.swift +82 -0
  495. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  496. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  497. package/nitrogen/generated/ios/swift/Track.swift +28 -63
  498. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  499. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  500. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  501. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  502. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  503. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  504. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  505. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  506. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  507. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  508. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  509. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
  510. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  511. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  512. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  513. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  514. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  515. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
  516. package/nitrogen/generated/shared/c++/Section.hpp +102 -0
  517. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  518. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  519. package/nitrogen/generated/shared/c++/Track.hpp +18 -30
  520. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  521. package/package.json +24 -11
  522. package/src/AudioBrowser.ts +2 -1
  523. package/src/CLAUDE.md +52 -0
  524. package/src/features/battery.test.ts +79 -0
  525. package/src/features/browser-config.test.ts +21 -6
  526. package/src/features/browser-config.ts +12 -8
  527. package/src/features/browser.test.ts +1 -1
  528. package/src/features/browser.ts +11 -3
  529. package/src/features/carConnection.ts +1 -1
  530. package/src/features/equalizer.ts +1 -1
  531. package/src/features/errors.ts +3 -3
  532. package/src/features/favorites.ts +14 -11
  533. package/src/features/gate.ts +16 -1
  534. package/src/features/hook-wiring.test.ts +302 -0
  535. package/src/features/metadata.ts +20 -3
  536. package/src/features/network.ts +1 -1
  537. package/src/features/nowPlaying.ts +1 -1
  538. package/src/features/output.ts +1 -1
  539. package/src/features/playback/controls.ts +4 -2
  540. package/src/features/playback/playWhenReady.ts +1 -1
  541. package/src/features/playback/playing.ts +1 -1
  542. package/src/features/playback/progress.test.ts +169 -0
  543. package/src/features/playback/progress.ts +3 -2
  544. package/src/features/playback/rate.ts +8 -1
  545. package/src/features/playback/state.ts +3 -4
  546. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  547. package/src/features/playback/trackPlaybackTime.ts +1 -6
  548. package/src/features/playback/volume.ts +1 -1
  549. package/src/features/player/options.ts +126 -76
  550. package/src/features/player/setup.test.ts +24 -7
  551. package/src/features/player/setup.ts +22 -10
  552. package/src/features/player/validateOptions.ts +21 -0
  553. package/src/features/queue/activeTrack.ts +15 -4
  554. package/src/features/queue/queue.ts +13 -6
  555. package/src/features/queue/repeatMode.ts +1 -1
  556. package/src/features/queue/shuffle.ts +1 -1
  557. package/src/features/remoteControls.ts +13 -92
  558. package/src/features/sleepTimer.test.ts +172 -0
  559. package/src/features/sleepTimer.ts +9 -1
  560. package/src/nitro-stub-drift.test.ts +277 -0
  561. package/src/public-surface.test.ts +147 -0
  562. package/src/specs/audio-browser.nitro.ts +5 -4
  563. package/src/style-resolution.test.ts +210 -0
  564. package/src/test-utils/reactNativeStub.ts +60 -0
  565. package/src/types/browser-native.ts +7 -3
  566. package/src/types/browser-nodes.ts +396 -134
  567. package/src/types/browser.ts +85 -59
  568. package/src/utils/LazyNativeEmitter.ts +28 -15
  569. package/src/utils/NativeUpdatedValue.ts +29 -13
  570. package/src/utils/getTrackIdentity.ts +20 -0
  571. package/src/utils/useDebug.test.ts +182 -0
  572. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  573. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  574. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
  575. package/src/web/NativeAudioBrowser.ts +239 -82
  576. package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
  577. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
  578. package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
  579. package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
  580. package/src/web/TrackPlayer/Player.load.test.ts +137 -1
  581. package/src/web/TrackPlayer/Player.ts +284 -37
  582. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  583. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  584. package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
  585. package/src/web/__tests__/lifecycle.test.ts +345 -0
  586. package/src/web/__tests__/lifecycleHarness.ts +351 -0
  587. package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
  588. package/src/web/__tests__/loadSupersession.test.ts +175 -0
  589. package/src/web/__tests__/retry.test.ts +169 -0
  590. package/src/web/browser/BrowserManager.ts +209 -111
  591. package/src/web/browser/FavoriteManager.test.ts +84 -0
  592. package/src/web/browser/FavoriteManager.ts +28 -23
  593. package/src/web/browser/SearchManager.test.ts +75 -0
  594. package/src/web/browser/SearchManager.ts +25 -28
  595. package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
  596. package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
  597. package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
  598. package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
  599. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  600. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  601. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  602. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  603. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  604. package/src/web/browser/sections.test.ts +91 -0
  605. package/src/web/browser/sections.ts +72 -0
  606. package/src/web/http/RequestConfigBuilder.test.ts +241 -1
  607. package/src/web/http/RequestConfigBuilder.ts +140 -33
  608. package/src/web/http/followMediaRedirect.test.ts +289 -0
  609. package/src/web/http/followMediaRedirect.ts +253 -0
  610. package/src/web/player/NowPlayingManager.ts +4 -3
  611. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  612. package/src/web/util/BrowserPathHelper.ts +61 -13
  613. package/website/guide/android-auto.md +114 -0
  614. package/website/guide/android-certificates.md +409 -0
  615. package/website/guide/artwork.md +255 -0
  616. package/website/guide/audio-output.md +135 -0
  617. package/website/guide/automotive.md +86 -0
  618. package/website/guide/basic-usage.md +151 -0
  619. package/website/guide/battery.md +115 -0
  620. package/website/guide/browser.md +581 -0
  621. package/website/guide/carplay.md +620 -0
  622. package/website/guide/configuration.md +173 -0
  623. package/website/guide/equalizer.md +231 -0
  624. package/website/guide/errors.md +310 -0
  625. package/website/guide/favorites.md +154 -0
  626. package/website/guide/gate.md +143 -0
  627. package/website/guide/getting-started.md +46 -0
  628. package/website/guide/hooks.md +238 -0
  629. package/website/guide/live-streams.md +125 -0
  630. package/website/guide/metadata.md +239 -0
  631. package/website/guide/migrating-from-track-player.md +244 -0
  632. package/website/guide/native-callback-fetch.md +127 -0
  633. package/website/guide/network.md +86 -0
  634. package/website/guide/now-playing.md +175 -0
  635. package/website/guide/playback.md +328 -0
  636. package/website/guide/queue.md +300 -0
  637. package/website/guide/remote-controls.md +256 -0
  638. package/website/guide/search.md +255 -0
  639. package/website/guide/sleep-timer.md +268 -0
  640. package/website/guide/track.md +247 -0
  641. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  642. package/ios/Model/MediaURL.swift +0 -67
  643. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  644. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  645. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  646. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  647. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  648. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  649. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  653. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  654. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  655. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  656. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  657. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  658. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  659. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
  660. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -3,17 +3,18 @@ import Testing
3
3
  @testable import AudioBrowserTestable
4
4
 
5
5
  private func makeResolvedTrack(
6
- url: String = "/test",
6
+ path: String = "/test",
7
7
  title: String = "Test Track",
8
8
  ) -> ResolvedTrack {
9
9
  ResolvedTrack(
10
- url: url,
10
+ path: path,
11
+ style: nil,
12
+ sections: nil,
11
13
  children: nil,
12
14
  carPlaySiriListButton: nil,
13
15
  src: nil,
14
16
  artwork: nil,
15
17
  artworkSource: nil,
16
- artworkCarPlayTinted: nil,
17
18
  title: title,
18
19
  subtitle: nil,
19
20
  artist: nil,
@@ -21,25 +22,24 @@ private func makeResolvedTrack(
21
22
  description: nil,
22
23
  genre: nil,
23
24
  duration: nil,
24
- style: nil,
25
- childrenStyle: nil,
25
+ disabled: nil,
26
26
  favorited: nil,
27
- groupTitle: nil,
28
27
  live: nil,
29
- imageRow: nil,
28
+ icyCharset: nil,
30
29
  )
31
30
  }
32
31
 
33
32
  private func makeFullResolvedTrack() -> ResolvedTrack {
34
33
  ResolvedTrack(
35
- url: "/original",
36
- children: [Track(id: "t1", url: "/t1")],
34
+ path: "/original",
35
+ style: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
36
+ sections: [Section(title: "Group A", style: nil, path: nil, children: [])],
37
+ children: [Track(id: "t1", path: "/t1")],
37
38
  carPlaySiriListButton: .top,
38
39
  id: "original-id",
39
40
  src: "src.mp3",
40
41
  artwork: .first("art.jpg"),
41
42
  artworkSource: ImageSource(uri: "resolved-art.jpg"),
42
- artworkCarPlayTinted: true,
43
43
  title: "Original Title",
44
44
  subtitle: "Original Subtitle",
45
45
  artist: "Original Artist",
@@ -47,12 +47,10 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
47
47
  description: "Original Description",
48
48
  genre: "Rock",
49
49
  duration: 180.0,
50
- style: .list,
51
- childrenStyle: .grid,
50
+ disabled: false,
52
51
  favorited: true,
53
- groupTitle: "Group A",
54
52
  live: false,
55
- imageRow: [ImageRowItem(title: "Row 1")],
53
+ icyCharset: nil,
56
54
  )
57
55
  }
58
56
 
@@ -64,12 +62,12 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
64
62
  #expect(copy == original)
65
63
  }
66
64
 
67
- // MARK: - non-optional fields (url, title)
65
+ // MARK: - non-optional fields (path, title)
68
66
 
69
- @Test func copyingOverridesUrl() {
70
- let original = makeResolvedTrack(url: "/old")
71
- let copy = original.copying(url: "/new")
72
- #expect(copy.url == "/new")
67
+ @Test func copyingOverridesPath() {
68
+ let original = makeResolvedTrack(path: "/old")
69
+ let copy = original.copying(path: "/new")
70
+ #expect(copy.path == "/new")
73
71
  #expect(copy.title == "Test Track")
74
72
  }
75
73
 
@@ -77,14 +75,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
77
75
  let original = makeResolvedTrack(title: "Old")
78
76
  let copy = original.copying(title: "New")
79
77
  #expect(copy.title == "New")
80
- #expect(copy.url == "/test")
78
+ #expect(copy.path == "/test")
81
79
  }
82
80
 
83
81
  // MARK: - optional fields: set value
84
82
 
85
83
  @Test func copyingOverridesChildren() {
86
84
  let original = makeResolvedTrack()
87
- let children = [Track(id: "c1", url: "/c1"), Track(id: "c2", url: "/c2")]
85
+ let children = [Track(id: "c1", path: "/c1"), Track(id: "c2", path: "/c2")]
88
86
  let copy = original.copying(children: children)
89
87
  #expect(copy.children?.count == 2)
90
88
  #expect(copy.children?[0].id == "c1")
@@ -135,8 +133,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
135
133
 
136
134
  @Test func copyingOverridesStyle() {
137
135
  let original = makeResolvedTrack()
138
- let copy = original.copying(style: .grid)
139
- #expect(copy.style == .grid)
136
+ let copy = original.copying(style: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil))
137
+ #expect(copy.style?.display == .grid)
138
+ }
139
+
140
+ @Test func copyingOverridesDisabled() {
141
+ let original = makeResolvedTrack()
142
+ let copy = original.copying(disabled: true)
143
+ #expect(copy.disabled == true)
140
144
  }
141
145
 
142
146
  @Test func copyingOverridesFavorited() {
@@ -157,11 +161,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
157
161
  #expect(copy.carPlaySiriListButton == .bottom)
158
162
  }
159
163
 
160
- @Test func copyingOverridesImageRow() {
164
+ @Test func copyingOverridesSections() {
161
165
  let original = makeResolvedTrack()
162
- let items = [ImageRowItem(title: "Item 1"), ImageRowItem(title: "Item 2")]
163
- let copy = original.copying(imageRow: items)
164
- #expect(copy.imageRow == items)
166
+ let sections = [
167
+ Section(title: "A", style: nil, path: nil, children: [Track(id: "c1", src: "c1")]),
168
+ ]
169
+ let copy = original.copying(sections: sections)
170
+ #expect(copy.sections == sections)
165
171
  }
166
172
 
167
173
  // MARK: - optional fields: set to nil via .some(nil)
@@ -206,13 +212,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
206
212
  @Test func copyingOverridesMultipleFields() {
207
213
  let original = makeResolvedTrack()
208
214
  let copy = original.copying(
209
- url: "/updated",
215
+ path: "/updated",
210
216
  title: "Updated Title",
211
217
  artist: "New Artist",
212
218
  duration: 240.0,
213
219
  favorited: true,
214
220
  )
215
- #expect(copy.url == "/updated")
221
+ #expect(copy.path == "/updated")
216
222
  #expect(copy.title == "Updated Title")
217
223
  #expect(copy.artist == "New Artist")
218
224
  #expect(copy.duration == 240.0)
@@ -230,23 +236,21 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
230
236
  let copy = original.copying(title: "Changed Only Title")
231
237
  #expect(copy.title == "Changed Only Title")
232
238
  // All other fields unchanged
233
- #expect(copy.url == "/original")
239
+ #expect(copy.path == "/original")
234
240
  #expect(copy.children?.count == 1)
235
241
  #expect(copy.carPlaySiriListButton == .top)
236
242
  #expect(copy.src == "src.mp3")
237
243
  #expect(copy.artwork?.url == "art.jpg")
238
244
  #expect(copy.artworkSource == ImageSource(uri: "resolved-art.jpg"))
239
- #expect(copy.artworkCarPlayTinted == true)
240
245
  #expect(copy.subtitle == "Original Subtitle")
241
246
  #expect(copy.artist == "Original Artist")
242
247
  #expect(copy.album == "Original Album")
243
248
  #expect(copy.description == "Original Description")
244
249
  #expect(copy.genre == "Rock")
245
250
  #expect(copy.duration == 180.0)
246
- #expect(copy.style == .list)
247
- #expect(copy.childrenStyle == .grid)
251
+ #expect(copy.style == SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil))
252
+ #expect(copy.disabled == false)
248
253
  #expect(copy.favorited == true)
249
- #expect(copy.groupTitle == "Group A")
254
+ #expect(copy.sections?.first?.title == "Group A")
250
255
  #expect(copy.live == false)
251
- #expect(copy.imageRow == [ImageRowItem(title: "Row 1")])
252
256
  }
@@ -1,3 +1,4 @@
1
+ import AVFoundation
1
2
  import Foundation
2
3
  import Testing
3
4
 
@@ -87,3 +88,91 @@ struct RetryManagerBudgetTests {
87
88
  #expect(manager.hasPlayed == true)
88
89
  }
89
90
  }
91
+
92
+ /// Which failures earn another attempt. The reference is Android's
93
+ /// `RetryLoadErrorHandlingPolicy.classifyError`: same HTTP set, and the same
94
+ /// default of treating an unrecognized failure as transient. Defaulting the
95
+ /// other way made `retry: true` largely inert on iOS, since AVFoundation
96
+ /// reports most stream failures as opaque CoreMedia errors, not `URLError`.
97
+ @Suite("RetryManager error classification")
98
+ @MainActor
99
+ struct RetryManagerClassificationTests {
100
+ private func makeManager() -> RetryManager {
101
+ let manager = RetryManager()
102
+ manager.updatePolicy(from: .first(true))
103
+ return manager
104
+ }
105
+
106
+ /// `AVPlayerItem.error` never carries the status, so the caller reads it off
107
+ /// the item's error log and passes it in. It outranks the opaque error.
108
+ @Test(arguments: [408, 429, 500, 502, 503, 504])
109
+ func retryableHTTPStatus_retries(status: Int) {
110
+ let opaque = NSError(domain: "CoreMediaErrorDomain", code: -12660)
111
+ #expect(makeManager().isRetryable(opaque, httpStatusCode: status) == true)
112
+ }
113
+
114
+ /// A retry cannot conjure content that isn't there, whatever the error says.
115
+ @Test(arguments: [400, 401, 403, 404, 410, 451])
116
+ func permanentHTTPStatus_doesNotRetry(status: Int) {
117
+ let transient = URLError(.timedOut)
118
+ #expect(makeManager().isRetryable(transient, httpStatusCode: status) == false)
119
+ }
120
+
121
+ @Test func transientURLError_retries() {
122
+ #expect(makeManager().isRetryable(URLError(.networkConnectionLost), httpStatusCode: nil) == true)
123
+ }
124
+
125
+ @Test func fatalURLError_doesNotRetry() {
126
+ #expect(makeManager().isRetryable(URLError(.badURL), httpStatusCode: nil) == false)
127
+ }
128
+
129
+ /// The regression this suite exists for: an opaque CoreMedia failure with no
130
+ /// status to read used to be terminal, so a station that dropped mid-song
131
+ /// never came back on iOS while Android reconnected.
132
+ @Test func opaqueCoreMediaError_retries() {
133
+ let error = NSError(domain: "CoreMediaErrorDomain", code: -12939)
134
+ #expect(makeManager().isRetryable(error, httpStatusCode: nil) == true)
135
+ }
136
+
137
+ /// `AVErrorUnknown` is the wrapper AVFoundation puts around transport
138
+ /// failures, so it must stay retryable even though it is an AVError.
139
+ @Test func avErrorUnknown_retries() {
140
+ let error = NSError(domain: AVFoundationErrorDomain, code: AVError.unknown.rawValue)
141
+ #expect(makeManager().isRetryable(error, httpStatusCode: nil) == true)
142
+ }
143
+
144
+ @Test(arguments: [
145
+ AVError.fileFormatNotRecognized.rawValue,
146
+ AVError.failedToParse.rawValue,
147
+ AVError.decodeFailed.rawValue,
148
+ ])
149
+ func unusableMedia_doesNotRetry(code: Int) {
150
+ let error = NSError(domain: AVFoundationErrorDomain, code: code)
151
+ #expect(makeManager().isRetryable(error, httpStatusCode: nil) == false)
152
+ }
153
+
154
+ /// AVFoundation wraps the real cause rather than surfacing it, which is why
155
+ /// the classifier walks `NSUnderlyingError` instead of inspecting only the
156
+ /// outermost error.
157
+ @Test func underlyingURLError_isFound() {
158
+ let manager = makeManager()
159
+ let wrapped = NSError(
160
+ domain: AVFoundationErrorDomain,
161
+ code: AVError.unknown.rawValue,
162
+ userInfo: [NSUnderlyingErrorKey: URLError(.badURL)],
163
+ )
164
+ #expect(manager.isRetryable(wrapped, httpStatusCode: nil) == false)
165
+
166
+ let transient = NSError(
167
+ domain: AVFoundationErrorDomain,
168
+ code: AVError.unknown.rawValue,
169
+ userInfo: [NSUnderlyingErrorKey: URLError(.dnsLookupFailed)],
170
+ )
171
+ #expect(manager.isRetryable(transient, httpStatusCode: nil) == true)
172
+ }
173
+
174
+ /// Nothing to classify: no error and no status stays terminal.
175
+ @Test func nilError_doesNotRetry() {
176
+ #expect(makeManager().isRetryable(nil, httpStatusCode: nil) == false)
177
+ }
178
+ }
@@ -4,14 +4,14 @@ import Testing
4
4
 
5
5
  private struct Item: PlayableSearchItem {
6
6
  let src: String?
7
- let url: String?
7
+ let path: String?
8
8
  }
9
9
 
10
10
  @Suite("SearchDrillIn.playable")
11
11
  struct SearchDrillInTests {
12
12
  /// A resolver that fails the test if the drill-in path calls it.
13
- private func unusedResolver(_ url: String) async throws -> [Item] {
14
- Issue.record("resolveChildren should not have been called (url: \(url))")
13
+ private func unusedResolver(_ path: String) async throws -> [Item] {
14
+ Issue.record("resolveChildren should not have been called (path: \(path))")
15
15
  return []
16
16
  }
17
17
 
@@ -22,9 +22,9 @@ struct SearchDrillInTests {
22
22
 
23
23
  @Test func firstPlayable_returnsFlatPlayable_inOrder_withoutResolving() async throws {
24
24
  let items = [
25
- Item(src: "/a.mp3", url: nil),
26
- Item(src: nil, url: "/page/x"), // browsable, filtered out
27
- Item(src: "/b.mp3", url: nil),
25
+ Item(src: "/a.mp3", path: nil),
26
+ Item(src: nil, path: "/page/x"), // browsable, filtered out
27
+ Item(src: "/b.mp3", path: nil),
28
28
  ]
29
29
  let result = try await SearchDrillIn.playable(from: items, resolveChildren: unusedResolver)
30
30
  #expect(result?.compactMap(\.src) == ["/a.mp3", "/b.mp3"])
@@ -32,31 +32,31 @@ struct SearchDrillInTests {
32
32
 
33
33
  @Test func firstBrowsable_drillsIntoIt_andPlaysItsChildren() async throws {
34
34
  let items = [
35
- Item(src: nil, url: "/page/place"), // browsable first result
36
- Item(src: "/later.mp3", url: nil), // would win without drill-in
35
+ Item(src: nil, path: "/page/place"), // browsable first result
36
+ Item(src: "/later.mp3", path: nil), // would win without drill-in
37
37
  ]
38
- let result = try await SearchDrillIn.playable(from: items) { url in
39
- #expect(url == "/page/place")
40
- return [Item(src: "/station1.mp3", url: nil), Item(src: "/station2.mp3", url: nil)]
38
+ let result = try await SearchDrillIn.playable(from: items) { path in
39
+ #expect(path == "/page/place")
40
+ return [Item(src: "/station1.mp3", path: nil), Item(src: "/station2.mp3", path: nil)]
41
41
  }
42
42
  #expect(result?.compactMap(\.src) == ["/station1.mp3", "/station2.mp3"])
43
43
  }
44
44
 
45
45
  @Test func browsableDrillYieldsNothing_fallsBackToFlat() async throws {
46
46
  let items = [
47
- Item(src: nil, url: "/page/empty"),
48
- Item(src: "/fallback.mp3", url: nil),
47
+ Item(src: nil, path: "/page/empty"),
48
+ Item(src: "/fallback.mp3", path: nil),
49
49
  ]
50
50
  let result = try await SearchDrillIn.playable(from: items) { _ in
51
- [Item(src: nil, url: "/page/nested")] // nothing playable inside
51
+ [Item(src: nil, path: "/page/nested")] // nothing playable inside
52
52
  }
53
53
  #expect(result?.compactMap(\.src) == ["/fallback.mp3"])
54
54
  }
55
55
 
56
56
  @Test func nothingPlayableAnywhere_returnsNil() async throws {
57
- let items = [Item(src: nil, url: "/page/a"), Item(src: nil, url: "/page/b")]
57
+ let items = [Item(src: nil, path: "/page/a"), Item(src: nil, path: "/page/b")]
58
58
  let result = try await SearchDrillIn.playable(from: items) { _ in
59
- [Item(src: nil, url: "/page/nested")]
59
+ [Item(src: nil, path: "/page/nested")]
60
60
  }
61
61
  #expect(result == nil)
62
62
  }
@@ -0,0 +1,90 @@
1
+ import Testing
2
+
3
+ @testable import AudioBrowserTestable
4
+
5
+ // The (resolved style, platform capability) → rendered-form mapping (ADR
6
+ // 0011): degradation drops decorations before layout, and the one layout drop
7
+ // is a wrapping grid on a platform without a wrapping tile container.
8
+
9
+ @Suite("SectionPresentation")
10
+ struct SectionPresentationTests {
11
+ private func style(display: StyleDisplay?, gridWrap: Bool? = nil) -> SectionStyle {
12
+ SectionStyle(gridWrap: gridWrap, display: display, artworkRendering: nil)
13
+ }
14
+
15
+ @Test func noStyleRendersAList() {
16
+ #expect(SectionPresentation(for: nil, supportsWrappingGrid: true) == .list)
17
+ #expect(SectionPresentation(for: nil, supportsWrappingGrid: false) == .list)
18
+ }
19
+
20
+ @Test func listDisplayRendersAList() {
21
+ #expect(SectionPresentation(for: style(display: .list), supportsWrappingGrid: true) == .list)
22
+ }
23
+
24
+ @Test func wrappingGridNeedsTheWrappingContainer() {
25
+ #expect(
26
+ SectionPresentation(for: style(display: .grid), supportsWrappingGrid: true) == .wrappingGrid)
27
+ // The one layout drop: a wrapping grid has nowhere to go but a list —
28
+ // every item stays reachable instead of truncating at an unknowable width.
29
+ #expect(SectionPresentation(for: style(display: .grid), supportsWrappingGrid: false) == .list)
30
+ }
31
+
32
+ @Test func singleLineGridRendersOnEveryOS() {
33
+ // gridWrap: false is the legacy image row's own shape, so it never
34
+ // degrades — declaring it INCREASES pre-26 fidelity.
35
+ #expect(
36
+ SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: true)
37
+ == .singleLineRow)
38
+ #expect(
39
+ SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: false)
40
+ == .singleLineRow)
41
+ }
42
+
43
+ @Test func explicitWrapTrueBehavesLikeTheDefault() {
44
+ #expect(
45
+ SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: true)
46
+ == .wrappingGrid)
47
+ #expect(
48
+ SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: false)
49
+ == .list)
50
+ }
51
+
52
+ @Test func gridWrapIsInertOnLists() {
53
+ // Declaring a decoration never changes the layout you'd get without it.
54
+ #expect(
55
+ SectionPresentation(for: style(display: .list, gridWrap: false), supportsWrappingGrid: true)
56
+ == .list)
57
+ #expect(
58
+ SectionPresentation(for: style(display: nil, gridWrap: false), supportsWrappingGrid: true)
59
+ == .list)
60
+ }
61
+
62
+ @Test func tileFamilyFollowsGridTileWithThePlainSplit() {
63
+ // Card/condensed follow the declaration in either wrap mode; the plain
64
+ // default splits on the wrap: single-line keeps row elements (the
65
+ // subtitle slot), wrapping renders the image grid.
66
+ #expect(
67
+ SectionPresentation.tileFamily(for: SectionStyle(gridTile: .card), singleLine: true)
68
+ == .cardElements)
69
+ #expect(
70
+ SectionPresentation.tileFamily(for: SectionStyle(gridTile: .condensed), singleLine: false)
71
+ == .condensedElements)
72
+ #expect(SectionPresentation.tileFamily(for: nil, singleLine: true) == .rowElements)
73
+ #expect(SectionPresentation.tileFamily(for: nil, singleLine: false) == .imageGridElements)
74
+ #expect(
75
+ SectionPresentation.tileFamily(for: SectionStyle(gridTile: .plain), singleLine: true)
76
+ == .rowElements)
77
+ }
78
+
79
+ @Test func effectiveAccessorySymbol_noneAndAbsenceDrawNothing() {
80
+ // A declared symbol draws; 'none' (the inheritance escape) and absence
81
+ // both fall back to the derived accessory.
82
+ #expect(
83
+ SectionPresentation.effectiveAccessorySymbol(TrackStyle(accessorySymbol: "lock.fill"))
84
+ == "lock.fill")
85
+ #expect(
86
+ SectionPresentation.effectiveAccessorySymbol(TrackStyle(accessorySymbol: "none")) == nil)
87
+ #expect(SectionPresentation.effectiveAccessorySymbol(TrackStyle()) == nil)
88
+ #expect(SectionPresentation.effectiveAccessorySymbol(nil) == nil)
89
+ }
90
+ }
@@ -2,81 +2,113 @@
2
2
  import Testing
3
3
 
4
4
  struct SectionScopeTests {
5
- private func track(_ src: String, group: String? = nil) -> Track {
6
- Track(id: src, src: src, groupTitle: group)
5
+ private func track(_ src: String, id: String? = nil) -> Track {
6
+ Track(id: id ?? src, src: src)
7
7
  }
8
8
 
9
- private func runSrcs(_ section: SectionScope.Section?) -> [String?]? {
10
- guard case .run(let tracks) = section else { return nil }
11
- return tracks.map(\.src)
9
+ private func section(_ title: String? = nil, _ tracks: [Track]) -> Section {
10
+ Section(title: title, children: tracks)
12
11
  }
13
12
 
14
- private func imageRowSrcs(_ section: SectionScope.Section?) -> [String?]? {
15
- guard case .imageRow(let items) = section else { return nil }
16
- return items.map(\.src)
13
+ private func srcs(_ scoped: SectionScope.Scoped?) -> [String?]? {
14
+ scoped.map { $0.section.children.map(\.src) }
17
15
  }
18
16
 
19
- @Test func scopesToTheContiguousGroupTitleRun() {
20
- let children = [
21
- track("a", group: "First"),
22
- track("b", group: "First"),
23
- track("c", group: "Second"),
24
- track("d", group: "Second"),
25
- track("e"),
17
+ // Two sections sharing an identity — the duplicate-heavy shape the flat
18
+ // model had to disambiguate by precedence; sections + the stamped flat
19
+ // index make it exact.
20
+ private var sections: [Section] {
21
+ [
22
+ section("First", [track("dup"), track("x")]),
23
+ section("Second", [track("y"), track("dup"), track("z")]),
26
24
  ]
27
- #expect(runSrcs(SectionScope.section(of: children, containing: "c")) == ["c", "d"])
28
- #expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a", "b"])
29
25
  }
30
26
 
31
- @Test func ungroupedItemsFormTheirOwnRun() {
32
- let children = [
33
- track("a", group: "First"),
34
- track("b"),
35
- track("c"),
36
- track("d", group: "Second"),
37
- ]
38
- #expect(runSrcs(SectionScope.section(of: children, containing: "b")) == ["b", "c"])
27
+ @Test func fallsBackToTheFirstSectionContainingTheIdentity() {
28
+ let scoped = SectionScope.scoped(in: sections, containing: "dup")
29
+ #expect(srcs(scoped) == ["dup", "x"])
30
+ #expect(scoped?.tappedOffset == nil)
39
31
  }
40
32
 
41
- @Test func identicalTitlesInSeparateRunsStaySeparate() {
42
- let children = [
43
- track("a", group: "Same"),
44
- track("x", group: "Other"),
45
- track("b", group: "Same"),
46
- ]
47
- #expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a"])
33
+ @Test func flatIndexPinsTheTappedSection() {
34
+ // Flat index 3 = second section, offset 1.
35
+ let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 3)
36
+ #expect(srcs(scoped) == ["y", "dup", "z"])
37
+ #expect(scoped?.tappedOffset == 1)
48
38
  }
49
39
 
50
- @Test func findsTheIdInsideAnImageRow() {
51
- let items = [
52
- ImageRowItem(src: "s1", title: "One"),
53
- ImageRowItem(src: "s2", title: "Two"),
54
- ]
55
- var row = Track(id: "row", title: "Most Played")
56
- row.imageRow = items
57
- let children = [row, track("a")]
58
- #expect(imageRowSrcs(SectionScope.section(of: children, containing: "s2")) == ["s1", "s2"])
40
+ @Test func flatIndexPinsTheExactCopyWithinASection() {
41
+ let playlist = [section(nil, [track("a"), track("b"), track("a"), track("c")])]
42
+ let scoped = SectionScope.scoped(in: playlist, containing: "a", tappedIndex: 2)
43
+ #expect(scoped?.tappedOffset == 2)
44
+ }
45
+
46
+ @Test func staleIndexFallsBackToTheFirstIdentityMatch() {
47
+ // The child at the stamped index no longer carries the tapped identity
48
+ // (the list shifted) — resolution ignores the index and pins nothing.
49
+ let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 1)
50
+ #expect(srcs(scoped) == ["dup", "x"])
51
+ #expect(scoped?.tappedOffset == nil)
59
52
  }
60
53
 
61
- @Test func unknownIdReturnsNil() {
62
- #expect(SectionScope.section(of: [track("a")], containing: "zz") == nil)
54
+ @Test func outOfRangeIndexIsIgnored() {
55
+ let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 99)
56
+ #expect(srcs(scoped) == ["dup", "x"])
57
+ #expect(scoped?.tappedOffset == nil)
63
58
  }
64
59
 
65
- // Pins the documented precedence: sections are located by src, and an id
66
- // present in both an image row and the flat list resolves to the row.
67
- @Test func imageRowWinsOverAFlatListDuplicate() {
68
- var row = Track(id: "row", title: "Row")
69
- row.imageRow = [ImageRowItem(src: "dup", title: "Dup")]
70
- let children = [row, track("dup"), track("b")]
71
- #expect(imageRowSrcs(SectionScope.section(of: children, containing: "dup")) == ["dup"])
60
+ @Test func vanishedIdentityReturnsNil() {
61
+ #expect(SectionScope.scoped(in: sections, containing: "gone") == nil)
72
62
  }
73
63
 
74
- @Test func duplicateSrcAcrossRunsResolvesToTheEarlierRun() {
64
+ // MARK: - Identity (id when non-blank, else src)
65
+
66
+ @Test func matchesChildByIdWhenSrcDiffers() {
75
67
  let children = [
76
- track("dup", group: "First"),
77
- track("x", group: "Second"),
78
- track("dup", group: "Second"),
68
+ Track(id: "stable-a", src: "https://cdn.example/a?token=1"),
69
+ Track(id: "stable-b", src: "https://cdn.example/b?token=2"),
79
70
  ]
80
- #expect(runSrcs(SectionScope.section(of: children, containing: "dup")) == ["dup"])
71
+ let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "stable-b")
72
+ #expect(srcs(scoped) == ["https://cdn.example/a?token=1", "https://cdn.example/b?token=2"])
73
+ }
74
+
75
+ @Test func blankIdFallsBackToSrc() {
76
+ let children = [Track(id: "", src: "s1"), Track(id: "", src: "s2")]
77
+ let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "s2")
78
+ #expect(srcs(scoped) == ["s1", "s2"])
79
+ }
80
+
81
+ // MARK: - Page shape helpers
82
+
83
+ @Test func childrenSugarNormalizesToOneUntitledSection() {
84
+ let page = ResolvedTrack(path: "/p", children: [track("a"), track("b")], title: "P")
85
+ let sections = page.normalizedSections
86
+ #expect(sections?.count == 1)
87
+ #expect(sections?.first?.title == nil)
88
+ #expect(sections?.first?.children.map(\.src) == ["a", "b"])
89
+ }
90
+
91
+ @Test func sectionsWinOverChildren() {
92
+ let page = ResolvedTrack(
93
+ path: "/p",
94
+ sections: [section("S", [track("a")])],
95
+ children: [track("zzz")],
96
+ title: "P",
97
+ )
98
+ #expect(page.normalizedSections?.count == 1)
99
+ #expect(page.normalizedSections?.first?.title == "S")
100
+ }
101
+
102
+ @Test func flattenedChildrenConcatenateInSectionOrder() {
103
+ let page = ResolvedTrack(
104
+ path: "/p",
105
+ sections: [section("A", [track("a1"), track("a2")]), section(nil, [track("b1")])],
106
+ title: "P",
107
+ )
108
+ #expect(page.flattenedChildren?.map(\.src) == ["a1", "a2", "b1"])
109
+ }
110
+
111
+ @Test func childlessPageHasNoSections() {
112
+ #expect(ResolvedTrack(path: "/p", title: "P").normalizedSections == nil)
81
113
  }
82
114
  }