react-native-audio-browser 0.2.0-next.11 → 0.2.0-next.13

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 (416) hide show
  1. package/README.md +14 -4
  2. package/android/build.gradle +20 -1
  3. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
  5. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  6. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  7. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -58
  8. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  9. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  10. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -7
  11. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  12. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  13. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
  14. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  15. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  16. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +109 -74
  17. package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
  18. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  19. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  20. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  21. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  22. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  23. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
  24. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
  25. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  26. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
  27. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  28. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  29. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  30. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  31. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  32. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  33. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  34. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  36. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  38. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  39. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  40. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  41. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  42. package/ios/Browser/BrowserConfig.swift +102 -5
  43. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  44. package/ios/Browser/BrowserManager.swift +229 -180
  45. package/ios/Browser/BrowserPathHelper.swift +44 -44
  46. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  47. package/ios/Browser/JsonModels.swift +258 -106
  48. package/ios/Browser/SearchDrillIn.swift +6 -6
  49. package/ios/Browser/SectionPresentation.swift +64 -0
  50. package/ios/Browser/SectionScope.swift +58 -24
  51. package/ios/Browser/StyleResolver.swift +58 -0
  52. package/ios/Browser/TabBarEntries.swift +9 -3
  53. package/ios/Browser/TrackIdTemplate.swift +37 -0
  54. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  55. package/ios/CarPlay/CarPlayController.swift +227 -85
  56. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  57. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  58. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  59. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  60. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  61. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  62. package/ios/Extension/Section+Copying.swift +31 -0
  63. package/ios/Extension/Track+Copying.swift +6 -12
  64. package/ios/Extension/Track+Identity.swift +18 -0
  65. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  66. package/ios/Http/HttpClient.swift +8 -2
  67. package/ios/HybridAudioBrowser.swift +166 -45
  68. package/ios/Model/NitroTypeStubs.swift +106 -21
  69. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  70. package/ios/Player/MediaIntentCriteria.swift +13 -5
  71. package/ios/Player/MediaLoader.swift +33 -9
  72. package/ios/Player/NowPlayingUpdater.swift +54 -6
  73. package/ios/Player/PlaybackCoordinator.swift +13 -6
  74. package/ios/Player/PlaybackTimer.swift +8 -2
  75. package/ios/Player/QueueManager.swift +36 -10
  76. package/ios/Player/RetryManager.swift +4 -3
  77. package/ios/Player/ShuffleOrder.swift +23 -0
  78. package/ios/Player/SleepTimerHandling.swift +13 -0
  79. package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
  80. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  81. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  82. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  83. package/ios/Tests/JsonModelsTests.swift +150 -1
  84. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  85. package/ios/Tests/QueueManagerTests.swift +72 -6
  86. package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
  87. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  88. package/ios/Tests/SearchDrillInTests.swift +16 -16
  89. package/ios/Tests/SectionPresentationTests.swift +90 -0
  90. package/ios/Tests/SectionScopeTests.swift +87 -55
  91. package/ios/Tests/StyleResolverTests.swift +127 -0
  92. package/ios/Tests/Support/TestTypes.swift +7 -0
  93. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  94. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  95. package/ios/Tests/TrackIdentityTests.swift +20 -0
  96. package/ios/Tests/TrackSelectorTests.swift +168 -33
  97. package/ios/TrackPlayer.swift +57 -34
  98. package/ios/TrackSelector.swift +45 -30
  99. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  100. package/ios/Util/NetworkMonitor.swift +43 -26
  101. package/ios/Util/SFSymbolRenderer.swift +24 -2
  102. package/ios/Util/SVGProcessor.swift +9 -2
  103. package/lib/commonjs/AudioBrowser.js +11 -0
  104. package/lib/commonjs/AudioBrowser.js.map +1 -1
  105. package/lib/commonjs/features/browser-config.js +11 -8
  106. package/lib/commonjs/features/browser-config.js.map +1 -1
  107. package/lib/commonjs/features/browser.js +2 -2
  108. package/lib/commonjs/features/favorites.js +13 -10
  109. package/lib/commonjs/features/favorites.js.map +1 -1
  110. package/lib/commonjs/features/playback/controls.js +4 -2
  111. package/lib/commonjs/features/playback/controls.js.map +1 -1
  112. package/lib/commonjs/features/playback/progress.js +2 -1
  113. package/lib/commonjs/features/playback/progress.js.map +1 -1
  114. package/lib/commonjs/features/playback/rate.js +8 -1
  115. package/lib/commonjs/features/playback/rate.js.map +1 -1
  116. package/lib/commonjs/features/playback/state.js +2 -3
  117. package/lib/commonjs/features/playback/state.js.map +1 -1
  118. package/lib/commonjs/features/player/options.js.map +1 -1
  119. package/lib/commonjs/features/player/setup.js +3 -1
  120. package/lib/commonjs/features/player/setup.js.map +1 -1
  121. package/lib/commonjs/features/player/validateOptions.js +2 -0
  122. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  123. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  124. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  125. package/lib/commonjs/features/queue/queue.js +11 -4
  126. package/lib/commonjs/features/queue/queue.js.map +1 -1
  127. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  128. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  129. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  130. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  131. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  132. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  133. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  134. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  135. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  136. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  137. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  138. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  139. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  140. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  141. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  142. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  143. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  144. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  145. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  146. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  147. package/lib/commonjs/web/browser/sections.js +89 -0
  148. package/lib/commonjs/web/browser/sections.js.map +1 -0
  149. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  150. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  151. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  152. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  153. package/lib/module/AudioBrowser.js +1 -0
  154. package/lib/module/AudioBrowser.js.map +1 -1
  155. package/lib/module/features/browser-config.js +11 -8
  156. package/lib/module/features/browser-config.js.map +1 -1
  157. package/lib/module/features/browser.js +2 -2
  158. package/lib/module/features/favorites.js +13 -10
  159. package/lib/module/features/favorites.js.map +1 -1
  160. package/lib/module/features/playback/controls.js +4 -2
  161. package/lib/module/features/playback/controls.js.map +1 -1
  162. package/lib/module/features/playback/progress.js +2 -1
  163. package/lib/module/features/playback/progress.js.map +1 -1
  164. package/lib/module/features/playback/rate.js +8 -1
  165. package/lib/module/features/playback/rate.js.map +1 -1
  166. package/lib/module/features/playback/state.js +2 -3
  167. package/lib/module/features/playback/state.js.map +1 -1
  168. package/lib/module/features/player/options.js.map +1 -1
  169. package/lib/module/features/player/setup.js +3 -1
  170. package/lib/module/features/player/setup.js.map +1 -1
  171. package/lib/module/features/player/validateOptions.js +2 -0
  172. package/lib/module/features/player/validateOptions.js.map +1 -1
  173. package/lib/module/features/queue/activeTrack.js +14 -3
  174. package/lib/module/features/queue/activeTrack.js.map +1 -1
  175. package/lib/module/features/queue/queue.js +11 -4
  176. package/lib/module/features/queue/queue.js.map +1 -1
  177. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  178. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  179. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  180. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  181. package/lib/module/utils/getTrackIdentity.js +20 -0
  182. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  183. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  184. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  185. package/lib/module/web/NativeAudioBrowser.js +32 -24
  186. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  187. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  188. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  189. package/lib/module/web/browser/BrowserManager.js +144 -87
  190. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  191. package/lib/module/web/browser/FavoriteManager.js +31 -21
  192. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  193. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  194. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  195. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  196. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  197. package/lib/module/web/browser/sections.js +83 -0
  198. package/lib/module/web/browser/sections.js.map +1 -0
  199. package/lib/module/web/player/NowPlayingManager.js +4 -3
  200. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  201. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  202. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  203. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  204. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  205. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  206. package/lib/typescript/src/features/browser.d.ts +2 -2
  207. package/lib/typescript/src/features/favorites.d.ts +13 -10
  208. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  209. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  210. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  211. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  212. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  213. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  214. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  215. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  216. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  217. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  218. package/lib/typescript/src/features/player/options.d.ts +21 -19
  219. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  220. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  221. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  222. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  223. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  224. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  225. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  226. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  227. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  228. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  229. package/lib/typescript/src/types/browser-nodes.d.ts +361 -126
  230. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  231. package/lib/typescript/src/types/browser.d.ts +33 -53
  232. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  233. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  234. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  235. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  236. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  237. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  238. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  239. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  240. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  241. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  242. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  243. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  244. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  245. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  246. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  247. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  248. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  249. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  250. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  251. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  252. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  253. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  254. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  255. 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
  256. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  257. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  258. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  259. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  260. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  261. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  262. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
  263. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  264. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  265. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  266. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
  267. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  268. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -7
  269. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  270. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  271. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  272. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  273. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  274. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
  275. package/nitrogen/generated/android/c++/JSection.hpp +120 -0
  276. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  277. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  278. package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
  279. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  280. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  281. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  282. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  283. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  284. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  285. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  286. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  287. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +18 -6
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  297. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
  298. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
  299. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
  300. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  301. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  302. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  303. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  304. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  305. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  306. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  307. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  308. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  309. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  310. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  311. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  312. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  313. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  314. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  315. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  316. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  317. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  318. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  319. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  320. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  321. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  322. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  323. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  324. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  325. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  326. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  327. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  328. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  329. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  330. package/package.json +7 -2
  331. package/src/AudioBrowser.ts +1 -0
  332. package/src/features/browser-config.test.ts +21 -6
  333. package/src/features/browser-config.ts +11 -8
  334. package/src/features/browser.test.ts +1 -1
  335. package/src/features/browser.ts +2 -2
  336. package/src/features/favorites.ts +13 -10
  337. package/src/features/hook-wiring.test.ts +63 -15
  338. package/src/features/playback/controls.ts +4 -2
  339. package/src/features/playback/progress.ts +2 -1
  340. package/src/features/playback/rate.ts +8 -1
  341. package/src/features/playback/state.ts +2 -3
  342. package/src/features/playback/trackPlaybackTime.ts +1 -1
  343. package/src/features/player/options.ts +27 -19
  344. package/src/features/player/setup.test.ts +12 -3
  345. package/src/features/player/setup.ts +3 -1
  346. package/src/features/player/validateOptions.ts +2 -0
  347. package/src/features/queue/activeTrack.ts +14 -3
  348. package/src/features/queue/queue.ts +11 -4
  349. package/src/nitro-stub-drift.test.ts +40 -0
  350. package/src/public-surface.test.ts +40 -0
  351. package/src/specs/audio-browser.nitro.ts +4 -3
  352. package/src/style-resolution.test.ts +210 -0
  353. package/src/types/browser-native.ts +2 -2
  354. package/src/types/browser-nodes.ts +377 -135
  355. package/src/types/browser.ts +34 -55
  356. package/src/utils/LazyNativeEmitter.ts +20 -10
  357. package/src/utils/NativeUpdatedValue.ts +29 -13
  358. package/src/utils/getTrackIdentity.ts +20 -0
  359. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  360. package/src/web/NativeAudioBrowser.ts +37 -22
  361. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  362. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  363. package/src/web/browser/BrowserManager.ts +176 -95
  364. package/src/web/browser/FavoriteManager.test.ts +84 -0
  365. package/src/web/browser/FavoriteManager.ts +28 -23
  366. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  367. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  368. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  369. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  370. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  371. package/src/web/browser/sections.test.ts +91 -0
  372. package/src/web/browser/sections.ts +72 -0
  373. package/src/web/player/NowPlayingManager.ts +4 -3
  374. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  375. package/src/web/util/BrowserPathHelper.ts +61 -13
  376. package/website/guide/android-auto.md +114 -0
  377. package/website/guide/android-certificates.md +409 -0
  378. package/website/guide/artwork.md +255 -0
  379. package/website/guide/audio-output.md +135 -0
  380. package/website/guide/automotive.md +86 -0
  381. package/website/guide/basic-usage.md +151 -0
  382. package/website/guide/battery.md +115 -0
  383. package/website/guide/browser.md +581 -0
  384. package/website/guide/carplay.md +620 -0
  385. package/website/guide/configuration.md +173 -0
  386. package/website/guide/equalizer.md +231 -0
  387. package/website/guide/errors.md +310 -0
  388. package/website/guide/favorites.md +154 -0
  389. package/website/guide/gate.md +143 -0
  390. package/website/guide/getting-started.md +46 -0
  391. package/website/guide/hooks.md +238 -0
  392. package/website/guide/live-streams.md +125 -0
  393. package/website/guide/metadata.md +239 -0
  394. package/website/guide/migrating-from-track-player.md +244 -0
  395. package/website/guide/native-callback-fetch.md +127 -0
  396. package/website/guide/network.md +86 -0
  397. package/website/guide/now-playing.md +175 -0
  398. package/website/guide/playback.md +328 -0
  399. package/website/guide/queue.md +300 -0
  400. package/website/guide/remote-controls.md +256 -0
  401. package/website/guide/search.md +255 -0
  402. package/website/guide/sleep-timer.md +268 -0
  403. package/website/guide/track.md +246 -0
  404. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  405. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  406. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  407. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  408. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  409. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  410. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  411. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  412. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  413. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  414. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  415. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  416. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -58,8 +58,10 @@ export function seekTo(position: number): void {
58
58
  }
59
59
 
60
60
  /**
61
- * Jump to the live edge of the current track. No-op for non-live tracks; for
62
- * live, seeks to the window end (HLS) or reconnects (non-seekable streams).
61
+ * Jump to the live edge of the current track; no-op unless the track
62
+ * declares `live: true`. A stream with a seekable live window (HLS) seeks
63
+ * to the window's edge; a non-seekable stream (e.g. ICY radio) reconnects
64
+ * to rejoin live — an expired URL re-resolves through `media.resolve`.
63
65
  */
64
66
  export function seekToLiveEdge(): void {
65
67
  nativeBrowser.seekToLiveEdge()
@@ -118,7 +118,8 @@ export function usePolledProgress(updateInterval = 1000): Progress {
118
118
  }
119
119
 
120
120
  const stop = () => {
121
- clearTimeout(timer)
121
+ // Guarded: React Native types clearTimeout(handle: number) — no undefined.
122
+ if (timer !== undefined) clearTimeout(timer)
122
123
  timer = undefined
123
124
  unsubscribeState?.()
124
125
  unsubscribeState = undefined
@@ -14,8 +14,15 @@ export function getRate(): number {
14
14
 
15
15
  /**
16
16
  * Sets the playback rate.
17
+ *
18
+ * The rate is a speed multiplier, not a transport control — it is independent
19
+ * of play/pause, and setting it while paused never starts playback. Use
20
+ * {@link play} / {@link pause} for that.
21
+ *
17
22
  * @param rate - The playback rate to change to, where 0.5 would be half speed,
18
- * 1 would be regular speed, 2 would be double speed etc.
23
+ * 1 would be regular speed, 2 would be double speed etc. Must be greater than
24
+ * zero; platforms disagree on what a non-positive rate does (iOS ignores it,
25
+ * Android throws, web stalls the element), so treat it as unsupported.
19
26
  */
20
27
  export function setRate(rate: number): void {
21
28
  nativeBrowser.setRate(rate)
@@ -18,9 +18,8 @@ import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
18
18
  * - `'buffering'`: Indicates that the player is currently loading more data
19
19
  * before it can continue playing or is ready to start playing.
20
20
  * - `'error'`: Indicates that playback of the current item failed. Call
21
- * `AudioBrowser.getError()` to get more information on the type of error that
22
- * occurred. Call `AudioBrowser.retry()` or `AudioBrowser.play()` to try to play
23
- * the item again.
21
+ * `getPlaybackError()` (or subscribe via `usePlaybackError()`) for the
22
+ * error details. Call `retry()` or `play()` to try to play the item again.
24
23
  * - `'ended'`: Indicates that playback stopped due to the end of the queue
25
24
  * being reached.
26
25
  */
@@ -9,7 +9,7 @@ export interface PlaybackElapsed {
9
9
  total: number
10
10
  /** Seconds played since the previous callback (≈ `period`). */
11
11
  sinceLast: number
12
- /** The currently playing track (id / src / url / title …), if any. */
12
+ /** The currently playing track (id / src / path / title …), if any. */
13
13
  track?: Track
14
14
  }
15
15
 
@@ -1,5 +1,7 @@
1
- import type { FavoriteConfig } from '../../types'
2
- import type { CarPlayNowPlayingButton } from '../../types/browser'
1
+ import type {
2
+ CarPlayNowPlayingButton,
3
+ CarPlayPlayingIndicatorLocation
4
+ } from '../../types/browser'
3
5
  import { nativeBrowser } from '../../native'
4
6
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
5
7
  import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
@@ -75,31 +77,22 @@ export interface PlayerCapabilities {
75
77
  * Android Auto and the Android 13+ system media controls — and an
76
78
  * (empty or filled) heart on playable browse rows.
77
79
  *
78
- * `match` controls how the ids from `setFavorites` are
79
- * compared against a track's `src` to decide its `favorited` state
80
- * (see `FavoritesMatchMode`); `true` is shorthand for `'exact'`.
81
- *
82
- * - `false` / omitted: favoriting off everywhere.
83
- * - `true`: on, with `'exact'` id matching.
84
- * - `{ match }`: on, with the given match mode.
80
+ * The ids passed to `setFavorites` are matched exactly against a track's
81
+ * *identity* — its `id` when set, falling back to `src`. Store favorites as
82
+ * the same stable identifier you put in `Track.id` (or as the full `src`
83
+ * for tracks without an id).
85
84
  *
86
85
  * @example
87
86
  * ```ts
88
- * // 'exact': a favorite id must equal the track's src.
89
- * favorite: { match: 'exact' }
90
- * setFavorites(['https://cdn.example.com/audio/track-42.mp3'])
91
- * // → favorited when src === 'https://cdn.example.com/audio/track-42.mp3'
92
- *
93
- * // 'partial': a favorite id matches when it is a full path segment of src.
94
- * favorite: { match: 'partial' }
87
+ * favorite: true
95
88
  * setFavorites(['track-42'])
96
- * // → favorited when src is '/library/track-42' or '/stream/track-42?hq=1',
97
- * // but NOT '/library/track-420'
89
+ * // → favorited when track.id === 'track-42'
90
+ * // (or, for id-less tracks, when track.src === 'track-42')
98
91
  * ```
99
92
  *
100
93
  * @default false
101
94
  */
102
- favorite?: boolean | FavoriteConfig
95
+ favorite?: boolean
103
96
  /**
104
97
  * Enable shuffle mode toggle.
105
98
  * @default true
@@ -295,6 +288,13 @@ export interface IOSOptions {
295
288
  * @default []
296
289
  */
297
290
  carPlayNowPlayingButtons: CarPlayNowPlayingButton[]
291
+
292
+ /**
293
+ * Where the now-playing indicator draws on CarPlay list rows.
294
+ * See {@link CarPlayPlayingIndicatorLocation}.
295
+ * @default 'leading'
296
+ */
297
+ carPlayPlayingIndicatorLocation: CarPlayPlayingIndicatorLocation
298
298
  }
299
299
 
300
300
  export interface AndroidOptions {
@@ -391,6 +391,13 @@ export interface IOSUpdateOptions {
391
391
  * @default []
392
392
  */
393
393
  carPlayNowPlayingButtons?: CarPlayNowPlayingButton[]
394
+
395
+ /**
396
+ * Where the now-playing indicator draws on CarPlay list rows.
397
+ * See {@link CarPlayPlayingIndicatorLocation}.
398
+ * @default 'leading'
399
+ */
400
+ carPlayPlayingIndicatorLocation?: CarPlayPlayingIndicatorLocation
394
401
  }
395
402
 
396
403
  /**
@@ -401,6 +408,7 @@ export interface NitroIOSUpdateOptions {
401
408
  playbackRates?: number[]
402
409
  carPlayUpNextButton?: boolean
403
410
  carPlayNowPlayingButtons?: CarPlayNowPlayingButton[]
411
+ carPlayPlayingIndicatorLocation?: CarPlayPlayingIndicatorLocation
404
412
  }
405
413
 
406
414
  /**
@@ -74,14 +74,23 @@ describe('setupPlayer wire regrouping', () => {
74
74
  })
75
75
  })
76
76
 
77
- it('moves ios playbackRates into the runtime options', async () => {
77
+ it('moves ios runtime fields into the runtime options', async () => {
78
78
  await setupPlayer({
79
- ios: { category: 'playback', playbackRates: [0.5, 1, 2] }
79
+ ios: {
80
+ category: 'playback',
81
+ playbackRates: [0.5, 1, 2],
82
+ carPlayPlayingIndicatorLocation: 'trailing'
83
+ }
80
84
  })
81
85
 
82
86
  const sent = payload()
83
87
  expect(sent.ios).toEqual({ category: 'playback' })
84
- expect(sent.options).toEqual({ ios: { playbackRates: [0.5, 1, 2] } })
88
+ expect(sent.options).toEqual({
89
+ ios: {
90
+ playbackRates: [0.5, 1, 2],
91
+ carPlayPlayingIndicatorLocation: 'trailing'
92
+ }
93
+ })
85
94
  })
86
95
 
87
96
  it('omits a platform bag that only carried runtime fields', async () => {
@@ -741,12 +741,14 @@ export async function setupPlayer(
741
741
  playbackRates,
742
742
  carPlayUpNextButton,
743
743
  carPlayNowPlayingButtons,
744
+ carPlayPlayingIndicatorLocation,
744
745
  ...iosSetup
745
746
  } = ios
746
747
  const iosUpdate = definedFields({
747
748
  playbackRates,
748
749
  carPlayUpNextButton,
749
- carPlayNowPlayingButtons
750
+ carPlayNowPlayingButtons,
751
+ carPlayPlayingIndicatorLocation
750
752
  })
751
753
  validateIOSUpdateOptions(iosUpdate)
752
754
 
@@ -7,6 +7,8 @@ const MAX_CARPLAY_NOW_PLAYING_BUTTONS = 5
7
7
  * Shared by `updateOptions` and `setupPlayer` (both can carry `ios` options).
8
8
  * Kept out of `options.ts`, which the barrel re-exports — see
9
9
  * public-surface.test.ts.
10
+ *
11
+ * @internal
10
12
  */
11
13
  export function validateIOSUpdateOptions(ios?: IOSUpdateOptions): void {
12
14
  const buttons = ios?.carPlayNowPlayingButtons
@@ -1,7 +1,8 @@
1
1
  import type { Track } from '../../types'
2
2
  import { nativeBrowser } from '../../native'
3
3
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
4
- import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
4
+ import { useNativeValueRefreshedBy } from '../../utils/useNativeValueRefreshedBy'
5
+ import { onFavoriteChanged } from '../favorites'
5
6
 
6
7
  /**
7
8
  * Event data for when the active track changes.
@@ -40,6 +41,12 @@ export function getActiveTrackIndex(): number | undefined {
40
41
 
41
42
  /**
42
43
  * Subscribes to active track change events.
44
+ *
45
+ * Fires on **transitions only** — selection, queue advance, skip. In-place
46
+ * metadata mutations of the active track (a favorite toggle) emit
47
+ * {@link onFavoriteChanged} instead; {@link useActiveTrack} subscribes to
48
+ * both, so UI bound through the hook still re-renders on a heart toggle.
49
+ *
43
50
  * @param callback - Called when the active track changes
44
51
  * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
45
52
  */
@@ -50,10 +57,14 @@ export const onActiveTrackChanged =
50
57
 
51
58
  // MARK: - Hooks
52
59
 
60
+ // Module-level so the hook's subscription identity is stable across renders.
61
+ const activeTrackInvalidators = [onActiveTrackChanged, onFavoriteChanged]
62
+
53
63
  /**
54
- * Hook that returns the current active track and updates when it changes.
64
+ * Hook that returns the current active track, updating on transitions AND on
65
+ * in-place mutations of the active track (favorite toggles).
55
66
  * @returns The current active track or undefined
56
67
  */
57
68
  export function useActiveTrack(): Track | undefined {
58
- return useNativeUpdatedValue(getActiveTrack, onActiveTrackChanged, 'track')
69
+ return useNativeValueRefreshedBy(getActiveTrack, activeTrackInvalidators)
59
70
  }
@@ -1,7 +1,8 @@
1
1
  import type { Track } from '../../types'
2
2
  import { nativeBrowser } from '../../native'
3
3
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
4
- import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
4
+ import { useNativeValueRefreshedBy } from '../../utils/useNativeValueRefreshedBy'
5
+ import { onFavoriteChanged } from '../favorites'
5
6
 
6
7
  // MARK: - Types
7
8
 
@@ -165,7 +166,9 @@ export const onQueueEnded =
165
166
 
166
167
  /**
167
168
  * Subscribes to queue change events.
168
- * Called when tracks are added, removed, reordered, or when track metadata changes.
169
+ * Called when tracks are added, removed, or reordered. An in-place mutation of
170
+ * a queued track's metadata (a favorite toggle) emits `onFavoriteChanged`
171
+ * instead; {@link useQueue} subscribes to both.
169
172
  * @param callback - Called with the updated queue
170
173
  * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
171
174
  */
@@ -175,10 +178,14 @@ export const onQueueChanged = NativeUpdatedValue.emitterize<Track[]>(
175
178
 
176
179
  // MARK: - Hooks
177
180
 
181
+ // Module-level so the hook's subscription identity is stable across renders.
182
+ const queueInvalidators = [onQueueChanged, onFavoriteChanged]
183
+
178
184
  /**
179
- * Hook that returns the current queue and updates when it changes.
185
+ * Hook that returns the current queue, updating on queue changes AND on
186
+ * in-place mutations of queued tracks (favorite toggles).
180
187
  * @returns The current queue
181
188
  */
182
189
  export function useQueue(): Track[] {
183
- return useNativeUpdatedValue(getQueue, onQueueChanged)
190
+ return useNativeValueRefreshedBy(getQueue, queueInvalidators)
184
191
  }
@@ -234,4 +234,44 @@ describe('NitroTypeStubs mirror the generated Nitro types', () => {
234
234
  drift.join('\n')
235
235
  ).toEqual([])
236
236
  })
237
+
238
+ it('declares struct members in the generated initializer order', () => {
239
+ // Name/type parity is not enough: the stub struct's memberwise init must
240
+ // accept the same ARGUMENT ORDER as the generated labelled init, or shared
241
+ // sources compile under `swift test` and fail in the app build with
242
+ // "argument 'x' must precede argument 'y'" (nitrogen flattens `extends`
243
+ // own-properties-first, which is easy to get backwards — SectionStyle did).
244
+ // A stub may omit members; the ones it declares must be a subsequence of
245
+ // the generated order.
246
+ const misordered: string[] = []
247
+
248
+ for (const [name, stub] of stubs) {
249
+ if (name in NOT_GENERATED || stub.kind !== 'struct') continue
250
+ const real = parseGenerated(name)
251
+ if (real == null || real.kind !== 'struct') continue
252
+
253
+ const realOrder = [...real.members.keys()]
254
+ const stubOrder = [...stub.members.keys()].filter((member) =>
255
+ real.members.has(member)
256
+ )
257
+ let cursor = -1
258
+ for (const member of stubOrder) {
259
+ const index = realOrder.indexOf(member)
260
+ if (index < cursor) {
261
+ misordered.push(
262
+ `${name}: stub declares [${stubOrder.join(', ')}], ` +
263
+ `generated init order is [${realOrder.join(', ')}]`
264
+ )
265
+ break
266
+ }
267
+ cursor = index
268
+ }
269
+ }
270
+
271
+ expect(
272
+ misordered,
273
+ `Stub struct members are out of order relative to the generated init:\n` +
274
+ misordered.join('\n')
275
+ ).toEqual([])
276
+ })
237
277
  })
@@ -104,4 +104,44 @@ describe('public surface', () => {
104
104
  it('does not re-export the raw Nitro object', () => {
105
105
  expect(exports.map((e) => e.name)).not.toContain('nativeBrowser')
106
106
  })
107
+
108
+ it('every public module is covered by the typedoc entry points', () => {
109
+ // The API reference documents entryPoints, not the export graph — a public
110
+ // module missing from website/typedoc.json ships undocumented.
111
+ const config = JSON.parse(
112
+ readFileSync(join(process.cwd(), 'website', 'typedoc.json'), 'utf8')
113
+ ) as { entryPoints: string[] }
114
+ const entries = config.entryPoints.map(
115
+ (glob) =>
116
+ new RegExp(
117
+ '^' +
118
+ glob
119
+ .replace(/^\.\.\/src\//, '')
120
+ .replace(/[.]/g, '\\.')
121
+ .replace(/\*/g, '[^/]+') +
122
+ '$'
123
+ )
124
+ )
125
+ const covered = (file: string): boolean => {
126
+ if (entries.some((e) => e.test(file))) return true
127
+ // A directory barrel entry (…/index.ts) documents what it re-exports.
128
+ const dir = file.split('/').slice(0, -1)
129
+ while (dir.length > 0) {
130
+ if (entries.some((e) => e.test([...dir, 'index.ts'].join('/'))))
131
+ return true
132
+ dir.pop()
133
+ }
134
+ return false
135
+ }
136
+ const uncovered = [...new Set(exports.map((e) => e.file))].filter(
137
+ (file) => !covered(file)
138
+ )
139
+ expect(
140
+ uncovered,
141
+ 'Exported to consumers but absent from the API reference. Add each ' +
142
+ 'module (or its directory barrel) to website/typedoc.json ' +
143
+ 'entryPoints:\n' +
144
+ uncovered.join('\n')
145
+ ).toEqual([])
146
+ })
107
147
  })
@@ -229,9 +229,10 @@ export interface AudioBrowser extends HybridObject<{
229
229
  seekTo(position: number): void
230
230
  seekBy(offset: number): void
231
231
  /**
232
- * Jump to the live edge of the current track. No-op for non-live tracks.
233
- * Live with a seekable window (HLS): seeks to the window end. Live without a
234
- * window (non-seekable, e.g. ICY): reconnects to rejoin live.
232
+ * Jump to the live edge of the current track; no-op unless the track
233
+ * declares `live: true`. A stream with a seekable live window (HLS) seeks
234
+ * to the window's edge; a non-seekable stream (e.g. ICY radio) reconnects
235
+ * to rejoin live — an expired URL re-resolves through `media.resolve`.
235
236
  */
236
237
  seekToLiveEdge(): void
237
238
  setVolume(level: number): void
@@ -0,0 +1,210 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ /**
6
+ * Completeness guards for the style declaration block (ADR 0011) — the checks
7
+ * Nitro's `extends`-flattening took out of the type system, in two parts:
8
+ * every key must be *resolved* by each platform's `StyleResolver`, and every
9
+ * key must be *accounted for* by each platform's `InertStyleDiagnostic`.
10
+ *
11
+ * Part one — inheritance completeness.
12
+ *
13
+ * `SectionStyle extends TrackStyle` in the spec, but Nitro flattens `extends`
14
+ * into independent structs on every platform — so nothing in any type system
15
+ * forces a platform's `StyleResolver` to read a newly added style key. A key
16
+ * the resolver forgets is silently dead: declared by consumers, decoded on the
17
+ * wire, never rendered. This test is the compile-time check the flattening
18
+ * took away:
19
+ *
20
+ * - every `SectionStyle` key must appear in each platform's
21
+ * `sectionStyle(section, page)` merge;
22
+ * - every inherited `TrackStyle` key must appear in each platform's
23
+ * `trackStyle(track, section)` merge;
24
+ * - `display` is positional (the deny-list): the track merge must resolve it
25
+ * to nil/null and must not read it from any level.
26
+ */
27
+
28
+ const ROOT = process.cwd()
29
+
30
+ const RESOLVERS = [
31
+ {
32
+ file: 'ios/Browser/StyleResolver.swift',
33
+ sectionMarker: 'static func sectionStyle',
34
+ trackMarker: 'static func trackStyle',
35
+ positionalNil: 'display: nil'
36
+ },
37
+ {
38
+ file: 'android/src/main/java/com/audiobrowser/browser/StyleResolver.kt',
39
+ sectionMarker: 'fun sectionStyle',
40
+ trackMarker: 'fun trackStyle',
41
+ positionalNil: 'display = null'
42
+ }
43
+ ]
44
+
45
+ /** Keys the spec deliberately excludes from item inheritance. */
46
+ const POSITIONAL = ['display']
47
+
48
+ function interfaceKeys(source: string, name: string): string[] {
49
+ const start = source.indexOf(`export interface ${name} `)
50
+ expect(
51
+ start,
52
+ `interface ${name} not found in browser-nodes.ts`
53
+ ).toBeGreaterThan(-1)
54
+ const end = source.indexOf('\n}', start)
55
+ const body = source.slice(start, end)
56
+ return [...body.matchAll(/^ {2}(\w+)\??:/gm)].flatMap((match) =>
57
+ match[1] === undefined ? [] : [match[1]]
58
+ )
59
+ }
60
+
61
+ /**
62
+ * The function's text from `marker` to the next function declaration (or end
63
+ * of file) — brace-agnostic, since Kotlin expression bodies have no braces.
64
+ */
65
+ function functionBody(source: string, marker: string, file: string): string {
66
+ const start = source.indexOf(marker)
67
+ expect(start, `${marker} not found in ${file}`).toBeGreaterThan(-1)
68
+ const rest = source.slice(start + marker.length)
69
+ const next = rest.search(/\n\s*(?:static func|func|fun)\s/)
70
+ return marker + (next === -1 ? rest : rest.slice(0, next))
71
+ }
72
+
73
+ /** Comment-free view, so prose about a key doesn't count as reading it. */
74
+ function stripComments(code: string): string {
75
+ return code
76
+ .replace(/\/\*[\s\S]*?\*\//g, '')
77
+ .split('\n')
78
+ .map((line) => line.replace(/\/\/.*$/, ''))
79
+ .join('\n')
80
+ }
81
+
82
+ const nodes = readFileSync(
83
+ join(ROOT, 'src', 'types', 'browser-nodes.ts'),
84
+ 'utf8'
85
+ )
86
+ const trackKeys = interfaceKeys(nodes, 'TrackStyle')
87
+ const sectionKeys = [...trackKeys, ...interfaceKeys(nodes, 'SectionStyle')]
88
+ const inheritedKeys = trackKeys.filter((key) => !POSITIONAL.includes(key))
89
+
90
+ describe('style block shape', () => {
91
+ it('has the positional key and at least one inherited key', () => {
92
+ expect(trackKeys).toContain('display')
93
+ expect(inheritedKeys.length).toBeGreaterThan(0)
94
+ })
95
+ })
96
+
97
+ for (const resolver of RESOLVERS) {
98
+ describe(resolver.file, () => {
99
+ const source = readFileSync(join(ROOT, resolver.file), 'utf8')
100
+ const sectionBody = stripComments(
101
+ functionBody(source, resolver.sectionMarker, resolver.file)
102
+ )
103
+ const trackBody = stripComments(
104
+ functionBody(source, resolver.trackMarker, resolver.file)
105
+ )
106
+
107
+ // Containment of `holder?.key` for BOTH holders — `display =
108
+ // section?.display` with the page half dropped must fail — AND in
109
+ // precedence order: the narrower holder must be read first, or the
110
+ // scope-override/inheritance direction is inverted (`page ?? section`
111
+ // would pass a bare containment check).
112
+ it.each(sectionKeys)('sectionStyle merges %s (section ?? page)', (key) => {
113
+ const section = sectionBody.indexOf(`section?.${key}`)
114
+ const page = sectionBody.indexOf(`page?.${key}`)
115
+ expect(section, `section?.${key} not read`).toBeGreaterThan(-1)
116
+ expect(page, `page?.${key} not read`).toBeGreaterThan(-1)
117
+ expect(section, `section?.${key} must precede page?.${key}`).toBeLessThan(
118
+ page
119
+ )
120
+ })
121
+
122
+ it.each(inheritedKeys)(
123
+ 'trackStyle inherits %s (track ?? section)',
124
+ (key) => {
125
+ const track = trackBody.indexOf(`track?.${key}`)
126
+ const section = trackBody.indexOf(`section?.${key}`)
127
+ expect(track, `track?.${key} not read`).toBeGreaterThan(-1)
128
+ expect(section, `section?.${key} not read`).toBeGreaterThan(-1)
129
+ expect(
130
+ track,
131
+ `track?.${key} must precede section?.${key}`
132
+ ).toBeLessThan(section)
133
+ }
134
+ )
135
+
136
+ it('trackStyle excludes the positional display (deny-list)', () => {
137
+ expect(trackBody).toContain(resolver.positionalNil)
138
+ // The nil assignment must be the ONLY mention: reading display from any
139
+ // level here would item-inherit a positional key.
140
+ const mentions = trackBody.match(/display/g) ?? []
141
+ expect(mentions).toHaveLength(1)
142
+ })
143
+ })
144
+ }
145
+
146
+ /**
147
+ * Part two — diagnostic completeness.
148
+ *
149
+ * The block model traded compile-time invalid-combination errors for
150
+ * inertness, and the dev-mode `InertStyleDiagnostic` is the recovery. It is
151
+ * exposed to the same flattening problem as the resolvers, one level up: a
152
+ * key added to the spec has no type-system tie to the diagnostic's rule set,
153
+ * so a property could ship whose inert combinations nobody is warned about.
154
+ *
155
+ * Each platform therefore dispositions the whole vocabulary — every key is a
156
+ * `Property` with an inertness rule, `alwaysRenderable`, or `positional` —
157
+ * and this test reads those three declarations and asserts set equality with
158
+ * the spec, both directions: a new key must be dispositioned, and a key
159
+ * deleted from the spec must not linger in the rule set.
160
+ */
161
+ const DIAGNOSTICS = [
162
+ {
163
+ file: 'ios/Browser/InertStyleDiagnostic.swift',
164
+ // `case gridWrap`, up to the first member after the case list.
165
+ ruled: (source: string) =>
166
+ keysIn(
167
+ source,
168
+ /enum Property[\s\S]*?var isItemProperty/,
169
+ /^\s*case (\w+)$/gm
170
+ )
171
+ },
172
+ {
173
+ file: 'android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt',
174
+ // `GRID_WRAP("gridWrap", …)`, up to the `;` that ends the entry list.
175
+ ruled: (source: string) =>
176
+ keysIn(source, /enum class Property[\s\S]*?;/, /\w+\("(\w+)"/g)
177
+ }
178
+ ]
179
+
180
+ /** The capture group of every `pattern` match within the first `region`. */
181
+ function keysIn(source: string, region: RegExp, pattern: RegExp): string[] {
182
+ const found = region.exec(source)
183
+ expect(found, `region ${region} not found`).not.toBeNull()
184
+ return [...found![0].matchAll(pattern)].flatMap((match) =>
185
+ match[1] === undefined ? [] : [match[1]]
186
+ )
187
+ }
188
+
189
+ for (const diagnostic of DIAGNOSTICS) {
190
+ describe(diagnostic.file, () => {
191
+ // Comment-free, so prose naming a key doesn't pass for dispositioning it.
192
+ const source = stripComments(
193
+ readFileSync(join(ROOT, diagnostic.file), 'utf8')
194
+ )
195
+ // The two lists are spelled `alwaysRenderable`/`positional` in Swift and
196
+ // SCREAMING_CASE in Kotlin; both hold bare string literals.
197
+ const listed = [
198
+ ...source.matchAll(
199
+ /(?:alwaysRenderable|ALWAYS_RENDERABLE|positional|POSITIONAL)\s*=[^\n]*/gi
200
+ )
201
+ ].flatMap((match) =>
202
+ [...match[0].matchAll(/"(\w+)"/g)].map((key) => key[1])
203
+ )
204
+
205
+ it('dispositions every key of the block, and only those', () => {
206
+ const dispositioned = [...diagnostic.ruled(source), ...listed]
207
+ expect(new Set(dispositioned)).toEqual(new Set(sectionKeys))
208
+ })
209
+ })
210
+ }
@@ -9,7 +9,7 @@
9
9
  import type {
10
10
  ArtworkRequestConfig,
11
11
  BrowserSourceCallback,
12
- ResolveAlbumUrlCallback,
12
+ ResolveAlbumPathCallback,
13
13
  FormatNavigationErrorCallback,
14
14
  HandleTrackLoadCallback,
15
15
  MediaRequestConfig,
@@ -76,6 +76,6 @@ export interface NativeBrowserConfiguration {
76
76
 
77
77
  // CarPlay options
78
78
  carPlayLoadingTitle?: string
79
- resolveAlbumUrl?: ResolveAlbumUrlCallback
79
+ resolveAlbumPath?: ResolveAlbumPathCallback
80
80
  formatNavigationError?: FormatNavigationErrorCallback
81
81
  }