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

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 (333) hide show
  1. package/README.md +4 -4
  2. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
  3. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  5. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
  6. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  7. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
  8. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
  9. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  10. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  11. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  12. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  13. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
  14. package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
  15. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  16. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  17. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  18. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
  19. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
  20. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +26 -39
  21. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  22. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +228 -50
  23. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  24. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  25. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
  26. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  27. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
  28. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  29. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  30. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  31. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  32. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  33. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  34. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  35. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  36. package/ios/Browser/BrowserConfig.swift +102 -5
  37. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  38. package/ios/Browser/BrowserManager.swift +131 -143
  39. package/ios/Browser/BrowserPathHelper.swift +44 -44
  40. package/ios/Browser/JsonModels.swift +187 -106
  41. package/ios/Browser/SearchDrillIn.swift +6 -6
  42. package/ios/Browser/SectionPresentation.swift +34 -0
  43. package/ios/Browser/SectionScope.swift +58 -24
  44. package/ios/Browser/StyleResolver.swift +47 -0
  45. package/ios/Browser/TabBarEntries.swift +2 -2
  46. package/ios/Browser/TrackIdTemplate.swift +37 -0
  47. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  48. package/ios/CarPlay/CarPlayController.swift +105 -67
  49. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  50. package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
  51. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  52. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  53. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  54. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  55. package/ios/Extension/Section+Copying.swift +31 -0
  56. package/ios/Extension/Track+Copying.swift +6 -12
  57. package/ios/Extension/Track+Identity.swift +18 -0
  58. package/ios/HybridAudioBrowser.swift +113 -35
  59. package/ios/Model/NitroTypeStubs.swift +46 -21
  60. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  61. package/ios/Player/NowPlayingUpdater.swift +1 -1
  62. package/ios/Player/PlaybackCoordinator.swift +2 -2
  63. package/ios/Player/RetryManager.swift +4 -3
  64. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  65. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  66. package/ios/Tests/JsonModelsTests.swift +75 -1
  67. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  68. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  69. package/ios/Tests/SearchDrillInTests.swift +16 -16
  70. package/ios/Tests/SectionPresentationTests.swift +61 -0
  71. package/ios/Tests/SectionScopeTests.swift +87 -55
  72. package/ios/Tests/StyleResolverTests.swift +67 -0
  73. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  74. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  75. package/ios/Tests/TrackIdentityTests.swift +20 -0
  76. package/ios/Tests/TrackSelectorTests.swift +168 -33
  77. package/ios/TrackSelector.swift +45 -30
  78. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  79. package/lib/commonjs/AudioBrowser.js +11 -0
  80. package/lib/commonjs/AudioBrowser.js.map +1 -1
  81. package/lib/commonjs/features/browser-config.js +11 -8
  82. package/lib/commonjs/features/browser-config.js.map +1 -1
  83. package/lib/commonjs/features/browser.js +2 -2
  84. package/lib/commonjs/features/favorites.js +13 -10
  85. package/lib/commonjs/features/favorites.js.map +1 -1
  86. package/lib/commonjs/features/playback/controls.js +4 -2
  87. package/lib/commonjs/features/playback/controls.js.map +1 -1
  88. package/lib/commonjs/features/playback/state.js +2 -3
  89. package/lib/commonjs/features/playback/state.js.map +1 -1
  90. package/lib/commonjs/features/player/options.js.map +1 -1
  91. package/lib/commonjs/features/player/setup.js +3 -1
  92. package/lib/commonjs/features/player/setup.js.map +1 -1
  93. package/lib/commonjs/features/player/validateOptions.js +2 -0
  94. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  95. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  96. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  97. package/lib/commonjs/features/queue/queue.js +11 -4
  98. package/lib/commonjs/features/queue/queue.js.map +1 -1
  99. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  100. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  101. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  102. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  103. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  104. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  105. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  106. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  107. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  108. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  109. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  110. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  111. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  112. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  113. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  114. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  115. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  116. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  117. package/lib/commonjs/web/browser/sections.js +89 -0
  118. package/lib/commonjs/web/browser/sections.js.map +1 -0
  119. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  120. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  121. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  122. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  123. package/lib/module/AudioBrowser.js +1 -0
  124. package/lib/module/AudioBrowser.js.map +1 -1
  125. package/lib/module/features/browser-config.js +11 -8
  126. package/lib/module/features/browser-config.js.map +1 -1
  127. package/lib/module/features/browser.js +2 -2
  128. package/lib/module/features/favorites.js +13 -10
  129. package/lib/module/features/favorites.js.map +1 -1
  130. package/lib/module/features/playback/controls.js +4 -2
  131. package/lib/module/features/playback/controls.js.map +1 -1
  132. package/lib/module/features/playback/state.js +2 -3
  133. package/lib/module/features/playback/state.js.map +1 -1
  134. package/lib/module/features/player/options.js.map +1 -1
  135. package/lib/module/features/player/setup.js +3 -1
  136. package/lib/module/features/player/setup.js.map +1 -1
  137. package/lib/module/features/player/validateOptions.js +2 -0
  138. package/lib/module/features/player/validateOptions.js.map +1 -1
  139. package/lib/module/features/queue/activeTrack.js +14 -3
  140. package/lib/module/features/queue/activeTrack.js.map +1 -1
  141. package/lib/module/features/queue/queue.js +11 -4
  142. package/lib/module/features/queue/queue.js.map +1 -1
  143. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  144. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  145. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  146. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  147. package/lib/module/utils/getTrackIdentity.js +20 -0
  148. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  149. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  150. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  151. package/lib/module/web/NativeAudioBrowser.js +32 -24
  152. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  153. package/lib/module/web/browser/BrowserManager.js +144 -87
  154. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  155. package/lib/module/web/browser/FavoriteManager.js +31 -21
  156. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  157. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  158. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  159. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  160. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  161. package/lib/module/web/browser/sections.js +83 -0
  162. package/lib/module/web/browser/sections.js.map +1 -0
  163. package/lib/module/web/player/NowPlayingManager.js +4 -3
  164. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  165. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  166. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  167. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  168. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  169. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  170. package/lib/typescript/src/features/browser.d.ts +2 -2
  171. package/lib/typescript/src/features/favorites.d.ts +13 -10
  172. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  173. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  174. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  175. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  176. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  177. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  178. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  179. package/lib/typescript/src/features/player/options.d.ts +21 -19
  180. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  181. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  182. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  183. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  184. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  185. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  186. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  187. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  188. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  189. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  190. package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
  191. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  192. package/lib/typescript/src/types/browser.d.ts +33 -53
  193. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  194. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  195. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  196. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  197. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  198. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  199. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  200. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  201. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  202. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  203. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  204. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
  205. package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
  206. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  207. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
  208. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
  209. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
  210. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
  211. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
  212. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
  213. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
  214. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
  215. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
  216. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
  217. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  218. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
  219. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
  220. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
  221. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
  222. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  223. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
  224. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
  225. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
  226. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
  227. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  228. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  229. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  230. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
  231. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  232. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
  233. package/nitrogen/generated/android/c++/JSection.hpp +114 -0
  234. package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
  235. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  236. package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
  237. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
  238. package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
  239. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
  240. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  241. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  242. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  243. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  244. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  245. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  246. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  247. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
  248. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
  249. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  250. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  251. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
  252. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
  253. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
  254. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
  255. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  256. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  257. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  258. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  259. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  260. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  261. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  262. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  263. package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
  264. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  265. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  266. package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
  267. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  268. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  269. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  270. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  271. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  272. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  273. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  274. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  275. package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
  276. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  277. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  278. package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
  279. package/package.json +1 -1
  280. package/src/AudioBrowser.ts +1 -0
  281. package/src/features/browser-config.test.ts +21 -6
  282. package/src/features/browser-config.ts +11 -8
  283. package/src/features/browser.test.ts +1 -1
  284. package/src/features/browser.ts +2 -2
  285. package/src/features/favorites.ts +13 -10
  286. package/src/features/hook-wiring.test.ts +63 -15
  287. package/src/features/playback/controls.ts +4 -2
  288. package/src/features/playback/state.ts +2 -3
  289. package/src/features/playback/trackPlaybackTime.ts +1 -1
  290. package/src/features/player/options.ts +27 -19
  291. package/src/features/player/setup.test.ts +12 -3
  292. package/src/features/player/setup.ts +3 -1
  293. package/src/features/player/validateOptions.ts +2 -0
  294. package/src/features/queue/activeTrack.ts +14 -3
  295. package/src/features/queue/queue.ts +11 -4
  296. package/src/nitro-stub-drift.test.ts +40 -0
  297. package/src/public-surface.test.ts +40 -0
  298. package/src/specs/audio-browser.nitro.ts +4 -3
  299. package/src/style-resolution.test.ts +139 -0
  300. package/src/types/browser-native.ts +2 -2
  301. package/src/types/browser-nodes.ts +268 -136
  302. package/src/types/browser.ts +34 -55
  303. package/src/utils/LazyNativeEmitter.ts +20 -10
  304. package/src/utils/NativeUpdatedValue.ts +29 -13
  305. package/src/utils/getTrackIdentity.ts +20 -0
  306. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  307. package/src/web/NativeAudioBrowser.ts +37 -22
  308. package/src/web/browser/BrowserManager.ts +176 -95
  309. package/src/web/browser/FavoriteManager.test.ts +84 -0
  310. package/src/web/browser/FavoriteManager.ts +28 -23
  311. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  312. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  313. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  314. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  315. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  316. package/src/web/browser/sections.test.ts +91 -0
  317. package/src/web/browser/sections.ts +72 -0
  318. package/src/web/player/NowPlayingManager.ts +4 -3
  319. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  320. package/src/web/util/BrowserPathHelper.ts +61 -13
  321. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  322. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  323. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  324. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  325. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  326. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  327. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  328. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  329. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  330. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  331. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  332. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  333. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -12,15 +12,15 @@ private final class BrowserMock: TrackSelectionBrowser {
12
12
 
13
13
  // Record calls for verification
14
14
  var trackLoadEvents: [TrackLoadEvent] = []
15
- var expandedUrls: [String] = []
15
+ var expandedPaths: [String] = []
16
16
 
17
17
  func awaitTrackLoadHandler(event: TrackLoadEvent) async -> Bool {
18
18
  trackLoadEvents.append(event)
19
19
  return trackLoadHandlerResult
20
20
  }
21
21
 
22
- func expandQueueFromContextualUrl(_ url: String) async throws -> (tracks: [Track], selectedIndex: Int)? {
23
- expandedUrls.append(url)
22
+ func expandQueueFromContextualPath(_ path: String) async throws -> (tracks: [Track], selectedIndex: Int)? {
23
+ expandedPaths.append(path)
24
24
  if let error = expandQueueError {
25
25
  throw error
26
26
  }
@@ -53,23 +53,23 @@ private func makeSelector() -> (TrackSelector, BrowserMock, PlayerMock) {
53
53
  @Suite("browsable track")
54
54
  @MainActor
55
55
  struct BrowsableTrackTests {
56
- @Test func urlOnly_returnsBrowse() async {
56
+ @Test func pathOnly_returnsBrowse() async {
57
57
  let (selector, _, player) = makeSelector()
58
- let track = Track(id: "t1", url: "/some/path")
58
+ let track = Track(id: "t1", path: "/some/path")
59
59
  let result = await selector.select(track: track, player: player)
60
- guard case let .browse(url) = result else {
60
+ guard case let .browse(path) = result else {
61
61
  Issue.record("expected .browse, got \(result)")
62
62
  return
63
63
  }
64
- #expect(url == "/some/path")
64
+ #expect(path == "/some/path")
65
65
  }
66
66
  }
67
67
 
68
- // MARK: - No src or url
68
+ // MARK: - No src or path
69
69
 
70
- @Suite("no src or url")
70
+ @Suite("no src or path")
71
71
  @MainActor
72
- struct NoSrcOrUrlTests {
72
+ struct NoSrcOrPathTests {
73
73
  @Test func returnsNone() async {
74
74
  let (selector, _, player) = makeSelector()
75
75
  let track = Track(id: "t1")
@@ -115,25 +115,56 @@ struct PlayableTrackTests {
115
115
  }
116
116
  }
117
117
 
118
- // MARK: - Contextual URL Queue Reuse
118
+ // MARK: - Contextual path Queue Reuse
119
119
 
120
- @Suite("contextual URL queue reuse")
120
+ @Suite("contextual path queue reuse")
121
121
  @MainActor
122
- struct ContextualUrlQueueReuseTests {
122
+ struct ContextualPathQueueReuseTests {
123
123
  @Test func matchingQueueSourcePath_returnsSkipTo() async {
124
124
  let (selector, browser, player) = makeSelector()
125
125
  let parentPath = "/library/radio"
126
126
  let trackSrc = "song.mp3"
127
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
127
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
128
+
129
+ // Id-less queue tracks: identity falls back to src.
130
+ player.queueSourcePath = parentPath
131
+ player.tracks = [
132
+ Track(src: "other.mp3"),
133
+ Track(src: "song.mp3"),
134
+ Track(src: "another.mp3"),
135
+ ]
136
+
137
+ let track = Track(path: contextualPath, src: trackSrc)
138
+ let result = await selector.select(track: track, player: player)
139
+ guard case let .play(intent) = result else {
140
+ Issue.record("expected .play, got \(result)")
141
+ return
142
+ }
143
+ guard case let .skipTo(index) = intent else {
144
+ Issue.record("expected .skipTo, got \(intent)")
145
+ return
146
+ }
147
+ #expect(index == 1)
148
+ #expect(browser.trackLoadEvents.count == 1)
149
+ #expect(browser.expandedPaths.isEmpty)
150
+ }
151
+
152
+ // Skip-in-place matches by id when queue tracks carry ids: the contextual
153
+ // trackId is the row's identity (its id), and the queue row is found even
154
+ // though its src differs textually from the tapped track's.
155
+ @Test func matchingQueueSourcePath_skipsToTrackByIdWhenSrcDiffers() async {
156
+ let (selector, browser, player) = makeSelector()
157
+ let parentPath = "/library/radio"
158
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: "b")
128
159
 
129
160
  player.queueSourcePath = parentPath
130
161
  player.tracks = [
131
162
  Track(id: "a", src: "other.mp3"),
132
- Track(id: "b", src: "song.mp3"),
163
+ Track(id: "b", src: "https://cdn.example/song.mp3?token=queue"),
133
164
  Track(id: "c", src: "another.mp3"),
134
165
  ]
135
166
 
136
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
167
+ let track = Track(id: "b", path: contextualPath, src: "song.mp3")
137
168
  let result = await selector.select(track: track, player: player)
138
169
  guard case let .play(intent) = result else {
139
170
  Issue.record("expected .play, got \(result)")
@@ -144,8 +175,7 @@ struct ContextualUrlQueueReuseTests {
144
175
  return
145
176
  }
146
177
  #expect(index == 1)
147
- #expect(browser.trackLoadEvents.count == 1)
148
- #expect(browser.expandedUrls.isEmpty)
178
+ #expect(browser.expandedPaths.isEmpty)
149
179
  }
150
180
 
151
181
  @Test func handlerIntercepts_returnsIntercepted() async {
@@ -153,32 +183,93 @@ struct ContextualUrlQueueReuseTests {
153
183
  browser.trackLoadHandlerResult = true
154
184
  let parentPath = "/library/radio"
155
185
  let trackSrc = "song.mp3"
156
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
186
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
157
187
 
158
188
  player.queueSourcePath = parentPath
159
189
  player.tracks = [
160
- Track(id: "a", src: "song.mp3"),
190
+ Track(src: "song.mp3"),
161
191
  ]
162
192
 
163
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
193
+ let track = Track(path: contextualPath, src: trackSrc)
164
194
  let result = await selector.select(track: track, player: player)
165
195
  guard case .intercepted = result else {
166
196
  Issue.record("expected .intercepted, got \(result)")
167
197
  return
168
198
  }
199
+ #expect(browser.expandedPaths.isEmpty)
200
+ }
201
+
202
+ // A queue holding the same identity twice (normal for music playlists):
203
+ // exact contextual-path equality — which carries the stamped page index —
204
+ // pins the tapped copy instead of the first one.
205
+ @Test func duplicateIdentityInQueue_pathMatchPinsTheTappedCopy() async {
206
+ let (selector, browser, player) = makeSelector()
207
+ let parentPath = "/playlist"
208
+ func stamped(_ id: String, _ index: Int) -> String {
209
+ BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
210
+ }
211
+
212
+ player.queueSourcePath = parentPath
213
+ player.tracks = [
214
+ Track(path: stamped("a", 0), src: "a"),
215
+ Track(path: stamped("b", 1), src: "b"),
216
+ Track(path: stamped("a", 2), src: "a"),
217
+ ]
218
+
219
+ let track = Track(path: stamped("a", 2), src: "a")
220
+ let result = await selector.select(track: track, player: player)
221
+ guard case let .play(.skipTo(index)) = result else {
222
+ Issue.record("expected .play(.skipTo), got \(result)")
223
+ return
224
+ }
225
+ #expect(index == 2)
226
+ #expect(browser.expandedPaths.isEmpty)
227
+ }
228
+
229
+ // A tap in section B whose identity also sits in the active section-A
230
+ // queue: the exact surface is not in the queue, so the selection
231
+ // re-expands (re-scoping to the tapped section) instead of silently
232
+ // skipping inside the old section's queue.
233
+ @Test func indexStampedPathNotInQueue_reExpandsInsteadOfIdentitySkip() async {
234
+ let (selector, browser, player) = makeSelector()
235
+ let parentPath = "/city"
236
+ func stamped(_ id: String, _ index: Int) -> String {
237
+ BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
238
+ }
239
+
240
+ player.queueSourcePath = parentPath
241
+ player.tracks = [
242
+ Track(path: stamped("dup", 0), src: "dup"),
243
+ Track(path: stamped("x", 1), src: "x"),
244
+ ]
245
+ browser.expandQueueResult = (
246
+ tracks: [Track(path: stamped("y", 4), src: "y"), Track(path: stamped("dup", 5), src: "dup")],
247
+ selectedIndex: 1,
248
+ )
249
+
250
+ let track = Track(path: stamped("dup", 5), src: "dup")
251
+ let result = await selector.select(track: track, player: player)
252
+ guard case let .play(.setQueue(tracks, startIndex, sourcePath)) = result else {
253
+ Issue.record("expected .play(.setQueue), got \(result)")
254
+ return
255
+ }
256
+ #expect(tracks.count == 2)
257
+ #expect(startIndex == 1)
258
+ #expect(sourcePath == parentPath)
259
+ #expect(browser.expandedPaths == [stamped("dup", 5)])
169
260
  }
170
261
  }
171
262
 
172
- // MARK: - Contextual URL Expansion
263
+ // MARK: - Contextual path Expansion
173
264
 
174
- @Suite("contextual URL expansion")
265
+ @Suite("contextual path expansion")
175
266
  @MainActor
176
- struct ContextualUrlExpansionTests {
267
+ struct ContextualPathExpansionTests {
177
268
  @Test func expands_returnsSetQueue() async {
178
269
  let (selector, browser, player) = makeSelector()
179
270
  let parentPath = "/library/radio"
180
271
  let trackSrc = "song.mp3"
181
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
272
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
182
273
 
183
274
  let expandedTracks = [
184
275
  Track(id: "a", src: "first.mp3"),
@@ -187,7 +278,7 @@ struct ContextualUrlExpansionTests {
187
278
  ]
188
279
  browser.expandQueueResult = (tracks: expandedTracks, selectedIndex: 1)
189
280
 
190
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
281
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
191
282
  let result = await selector.select(track: track, player: player)
192
283
  guard case let .play(intent) = result else {
193
284
  Issue.record("expected .play, got \(result)")
@@ -200,7 +291,7 @@ struct ContextualUrlExpansionTests {
200
291
  #expect(tracks.count == 3)
201
292
  #expect(startIndex == 1)
202
293
  #expect(sourcePath == parentPath)
203
- #expect(browser.expandedUrls == [contextualUrl])
294
+ #expect(browser.expandedPaths == [contextualPath])
204
295
  #expect(browser.trackLoadEvents.count == 1)
205
296
  }
206
297
 
@@ -209,14 +300,14 @@ struct ContextualUrlExpansionTests {
209
300
  browser.trackLoadHandlerResult = true
210
301
  let parentPath = "/library/radio"
211
302
  let trackSrc = "song.mp3"
212
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
303
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
213
304
 
214
305
  browser.expandQueueResult = (
215
306
  tracks: [Track(id: "a", src: "song.mp3")],
216
307
  selectedIndex: 0,
217
308
  )
218
309
 
219
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
310
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
220
311
  let result = await selector.select(track: track, player: player)
221
312
  guard case .intercepted = result else {
222
313
  Issue.record("expected .intercepted, got \(result)")
@@ -228,11 +319,11 @@ struct ContextualUrlExpansionTests {
228
319
  let (selector, browser, player) = makeSelector()
229
320
  let parentPath = "/library/radio"
230
321
  let trackSrc = "song.mp3"
231
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
322
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
232
323
 
233
324
  browser.expandQueueResult = nil
234
325
 
235
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
326
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
236
327
  let result = await selector.select(track: track, player: player)
237
328
  guard case let .play(intent) = result else {
238
329
  Issue.record("expected .play, got \(result)")
@@ -249,11 +340,11 @@ struct ContextualUrlExpansionTests {
249
340
  let (selector, browser, player) = makeSelector()
250
341
  let parentPath = "/library/radio"
251
342
  let trackSrc = "song.mp3"
252
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
343
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
253
344
 
254
345
  browser.expandQueueError = MockError.expansion
255
346
 
256
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
347
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
257
348
  let result = await selector.select(track: track, player: player)
258
349
  guard case let .play(intent) = result else {
259
350
  Issue.record("expected .play, got \(result)")
@@ -266,3 +357,47 @@ struct ContextualUrlExpansionTests {
266
357
  #expect(loaded.src == trackSrc)
267
358
  }
268
359
  }
360
+
361
+ // MARK: - Disabled Track
362
+
363
+ @Suite("disabled track")
364
+ @MainActor
365
+ struct DisabledTrackTests {
366
+ @Test func disabledPlayable_returnsNone() async {
367
+ let (selector, browser, player) = makeSelector()
368
+ let track = Track(id: "t1", src: "https://example.com/a.mp3", disabled: true)
369
+ let result = await selector.select(track: track, player: player)
370
+ guard case .none = result else {
371
+ Issue.record("Expected .none for a disabled track, got \(result)")
372
+ return
373
+ }
374
+ // Refused outright: not even the JS load handler hears about it.
375
+ #expect(browser.trackLoadEvents.isEmpty)
376
+ }
377
+
378
+ @Test func disabledBrowsable_returnsNone() async {
379
+ // Unavailable means unavailable — a disabled browsable doesn't navigate
380
+ // either (the ladder grays or hides it; a delivered tap stays inert).
381
+ let (selector, _, player) = makeSelector()
382
+ let track = Track(id: "t1", path: "/gone", disabled: true)
383
+ let result = await selector.select(track: track, player: player)
384
+ guard case .none = result else {
385
+ Issue.record("Expected .none for a disabled browsable, got \(result)")
386
+ return
387
+ }
388
+ }
389
+
390
+ @Test func disabledContextual_neverExpands() async {
391
+ let (selector, browser, player) = makeSelector()
392
+ browser.expandQueueResult = (tracks: [Track(id: "a", src: "a")], selectedIndex: 0)
393
+ let track = Track(
394
+ id: "t1", path: "/home?__trackId=t1", src: "https://example.com/a.mp3", disabled: true,
395
+ )
396
+ let result = await selector.select(track: track, player: player)
397
+ guard case .none = result else {
398
+ Issue.record("Expected .none, got \(result)")
399
+ return
400
+ }
401
+ #expect(browser.expandedPaths.isEmpty)
402
+ }
403
+ }
@@ -5,7 +5,7 @@ import os.log
5
5
 
6
6
  @MainActor protocol TrackSelectionBrowser {
7
7
  func awaitTrackLoadHandler(event: TrackLoadEvent) async -> Bool
8
- func expandQueueFromContextualUrl(_ url: String) async throws -> (tracks: [Track], selectedIndex: Int)?
8
+ func expandQueueFromContextualPath(_ path: String) async throws -> (tracks: [Track], selectedIndex: Int)?
9
9
  }
10
10
 
11
11
  @MainActor protocol TrackSelectionPlayer {
@@ -36,9 +36,9 @@ class TrackSelector {
36
36
  case play(PlaybackIntent)
37
37
  /// JS handleTrackLoad handler intercepted — caller should not play.
38
38
  case intercepted
39
- /// Track is browsable — caller should navigate to this URL.
40
- case browse(url: String)
41
- /// Nothing to do (no src, no url).
39
+ /// Track is browsable — caller should navigate to this path.
40
+ case browse(path: String)
41
+ /// Nothing to do (no src, no path).
42
42
  case none
43
43
  }
44
44
 
@@ -54,16 +54,22 @@ class TrackSelector {
54
54
  // MARK: - Selection
55
55
 
56
56
  /// Resolves a track selection to a concrete action.
57
- /// Handles contextual URL expansion, queue reuse, and handler interception.
57
+ /// Handles contextual path expansion, queue reuse, and handler interception.
58
58
  func select(
59
59
  track: Track,
60
60
  player: some TrackSelectionPlayer,
61
61
  ) async -> SelectionResult {
62
- let url = track.url
62
+ // A disabled track is unavailable — it never plays, whichever surface or
63
+ // stale resume path delivered the selection (Track.disabled).
64
+ if track.disabled == true {
65
+ return .none
66
+ }
67
+
68
+ let path = track.path
63
69
 
64
- // 1. Contextual URL (playable-only track with queue context)
65
- if let url, BrowserPathHelper.isContextual(url) {
66
- return await handleContextualUrl(url, track: track, player: player)
70
+ // 1. Contextual path (playable-only track with queue context)
71
+ if let path, BrowserPathHelper.isContextual(path) {
72
+ return await handleContextualPath(path, track: track, player: player)
67
73
  }
68
74
 
69
75
  // 2. Has src — single playable track
@@ -71,9 +77,9 @@ class TrackSelector {
71
77
  return await handlePlayableTrack(track, player: player)
72
78
  }
73
79
 
74
- // 3. Has url — browsable
75
- if let url {
76
- return .browse(url: url)
80
+ // 3. Has path — browsable
81
+ if let path {
82
+ return .browse(path: path)
77
83
  }
78
84
 
79
85
  // 4. Neither
@@ -82,31 +88,40 @@ class TrackSelector {
82
88
 
83
89
  // MARK: - Private Helpers
84
90
 
85
- private func handleContextualUrl(
86
- _ url: String,
91
+ private func handleContextualPath(
92
+ _ path: String,
87
93
  track: Track,
88
94
  player: some TrackSelectionPlayer,
89
95
  ) async -> SelectionResult {
90
- let parentPath = BrowserPathHelper.stripTrackId(url)
91
- let trackId = BrowserPathHelper.extractTrackId(url)
92
-
93
- // Check if queue already came from this parent path — just skip to the track
94
- if let trackId,
95
- parentPath == player.queueSourcePath,
96
- let index = player.tracks.firstIndex(where: { $0.src == trackId })
97
- {
98
- logger.debug("Queue already from \(parentPath), skipping to index \(index)")
99
- let queue = player.tracks
100
- let event = TrackLoadEvent(track: track, queue: queue, startIndex: Double(index))
101
- if await browserManager.awaitTrackLoadHandler(event: event) {
102
- return .intercepted
96
+ let parentPath = BrowserPathHelper.stripTrackId(path)
97
+ let trackId = BrowserPathHelper.extractTrackId(path)
98
+
99
+ // Check if queue already came from this parent path — just skip to the
100
+ // track. Exact-surface match first: a contextual path carries the tapped
101
+ // page position (`__index`), so path equality pins the exact copy when
102
+ // the page holds the same identity more than once. The identity match
103
+ // remains for index-less paths (e.g. pre-stamp persisted state); an
104
+ // index-stamped path with no exact match falls through to expansion,
105
+ // which re-scopes the queue to the tapped section.
106
+ if parentPath == player.queueSourcePath {
107
+ var skipIndex = player.tracks.firstIndex(where: { $0.path == path })
108
+ if skipIndex == nil, BrowserPathHelper.extractIndex(path) == nil, let trackId {
109
+ skipIndex = player.tracks.firstIndex(where: { $0.identity == trackId })
110
+ }
111
+ if let index = skipIndex {
112
+ logger.debug("Queue already from \(parentPath), skipping to index \(index)")
113
+ let queue = player.tracks
114
+ let event = TrackLoadEvent(track: track, queue: queue, startIndex: Double(index))
115
+ if await browserManager.awaitTrackLoadHandler(event: event) {
116
+ return .intercepted
117
+ }
118
+ return .play(.skipTo(index: index))
103
119
  }
104
- return .play(.skipTo(index: index))
105
120
  }
106
121
 
107
- // Expand the queue from the contextual URL
122
+ // Expand the queue from the contextual path
108
123
  do {
109
- if let expanded = try await browserManager.expandQueueFromContextualUrl(url) {
124
+ if let expanded = try await browserManager.expandQueueFromContextualPath(path) {
110
125
  let (tracks, startIndex) = expanded
111
126
  let event = TrackLoadEvent(track: track, queue: tracks, startIndex: Double(startIndex))
112
127
  if await browserManager.awaitTrackLoadHandler(event: event) {
@@ -8,7 +8,11 @@ enum ArtworkImageFetcher {
8
8
  private static let logger = Logger(subsystem: "com.audiobrowser", category: "ArtworkImageFetcher")
9
9
 
10
10
  @MainActor
11
- static func fetchImage(from source: ImageSource, svgScale: CGFloat? = nil) async -> UIImage? {
11
+ static func fetchImage(
12
+ from source: ImageSource,
13
+ svgScale: CGFloat? = nil,
14
+ downsampleTo: (size: CGSize, scale: CGFloat)? = nil,
15
+ ) async -> UIImage? {
12
16
  guard let url = URL(string: source.uri) else { return nil }
13
17
 
14
18
  var options: KingfisherOptionsInfo = []
@@ -24,6 +28,17 @@ enum ArtworkImageFetcher {
24
28
  }
25
29
  if let svgScale {
26
30
  options.append(.processor(SVGProcessor(size: nil, scale: svgScale)))
31
+ } else if let downsampleTo {
32
+ // Downsample rasters at decode time. Any size hint passed upstream (the
33
+ // resolver's ImageContext) is advisory — a CDN can ignore it and return
34
+ // the original — so this is the only sizing that is guaranteed to
35
+ // happen. The processed image is cached under a size-qualified key;
36
+ // cacheOriginalImage keeps the untouched original in the disk cache so
37
+ // the same URL at another target size re-processes instead of
38
+ // re-downloading.
39
+ options.append(.processor(DownsamplingImageProcessor(size: downsampleTo.size)))
40
+ options.append(.scaleFactor(downsampleTo.scale))
41
+ options.append(.cacheOriginalImage)
27
42
  }
28
43
 
29
44
  do {
@@ -25,6 +25,17 @@ Object.keys(_types).forEach(function (key) {
25
25
  }
26
26
  });
27
27
  });
28
+ var _getTrackIdentity = require("./utils/getTrackIdentity");
29
+ Object.keys(_getTrackIdentity).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (key in exports && exports[key] === _getTrackIdentity[key]) return;
32
+ Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: function () {
35
+ return _getTrackIdentity[key];
36
+ }
37
+ });
38
+ });
28
39
  var _useDebug = require("./utils/useDebug");
29
40
  Object.keys(_useDebug).forEach(function (key) {
30
41
  if (key === "default" || key === "__esModule") return;
@@ -1 +1 @@
1
- {"version":3,"names":["_features","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_types","_useDebug"],"sourceRoot":"../../src","sources":["AudioBrowser.ts"],"mappings":";;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,MAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,MAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,MAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,MAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,SAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,SAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,SAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,SAAA,CAAAN,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
1
+ {"version":3,"names":["_features","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_types","_getTrackIdentity","_useDebug"],"sourceRoot":"../../src","sources":["AudioBrowser.ts"],"mappings":";;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,MAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,MAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,MAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,MAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,iBAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,iBAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,iBAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,iBAAA,CAAAN,GAAA;IAAA;EAAA;AAAA;AACA,IAAAO,SAAA,GAAAX,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAS,SAAA,EAAAR,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,SAAA,CAAAP,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAG,SAAA,CAAAP,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
@@ -22,9 +22,12 @@ function isObjectValue(value) {
22
22
  }
23
23
  function isTransformableRequestConfig(source) {
24
24
  if (!isObjectValue(source)) return false;
25
- // Any RequestConfig key marks a request config. A static ResolvedTrack
26
- // shares none of these at the top level (its per-track request override is
27
- // nested under `request`).
25
+ // A static ResolvedTrack page also carries a top-level `path`, so `path`
26
+ // alone cannot mark a request config. A page is recognised by its required
27
+ // `title` — no RequestConfig field is named `title`.
28
+ if ('title' in source) return false;
29
+ // Any RequestConfig key marks a request config. (A static ResolvedTrack's
30
+ // per-track request override is nested under `request`, not top-level.)
28
31
  return 'baseUrl' in source || 'path' in source || 'headers' in source || 'query' in source || 'method' in source || 'body' in source || 'contentType' in source || 'userAgent' in source || 'transform' in source || 'transformSync' in source;
29
32
  }
30
33
  function isRouteConfig(source) {
@@ -87,7 +90,7 @@ function searchSourceToRouteEntry(source) {
87
90
  */
88
91
  function wrapTracksAsResolvedTrack(tracks) {
89
92
  return {
90
- url: TABS_ROUTE_PATH,
93
+ path: TABS_ROUTE_PATH,
91
94
  title: 'Tabs',
92
95
  children: tracks
93
96
  };
@@ -191,10 +194,10 @@ function validateBrowserConfiguration(config) {
191
194
  continue;
192
195
  }
193
196
  if (!isCallback(source) && !isRouteConfig(source) && !isTransformableRequestConfig(source)) {
194
- // Falls through to the static-page branch; a page needs url + title.
197
+ // Falls through to the static-page branch; a page needs path + title.
195
198
  const page = source;
196
- if (typeof page.url !== 'string' || typeof page.title !== 'string') {
197
- warn(`route "${path}" matched no source shape — expected a callback, ` + `a request config ({ baseUrl, path, headers, ... }), a ` + `RouteConfig ({ browse, media, artwork }), or a static page ` + `({ url, title, children? }). It will be served as static ` + `content and likely render a blank screen.`);
199
+ if (typeof page.path !== 'string' || typeof page.title !== 'string') {
200
+ warn(`route "${path}" matched no source shape — expected a callback, ` + `a request config ({ baseUrl, path, headers, ... }), a ` + `RouteConfig ({ browse, media, artwork }), or a static page ` + `({ path, title, children? }). It will be served as static ` + `content and likely render a blank screen.`);
198
201
  }
199
202
  }
200
203
  }
@@ -220,7 +223,7 @@ function toNativeConfig(config) {
220
223
  handleTrackLoad: config.handleTrackLoad,
221
224
  androidControllerOfflineError: config.androidControllerOfflineError,
222
225
  carPlayLoadingTitle: config.carPlayLoadingTitle,
223
- resolveAlbumUrl: config.resolveAlbumUrl,
226
+ resolveAlbumPath: config.resolveAlbumPath,
224
227
  formatNavigationError: config.formatNavigationError
225
228
  };
226
229
  }
@@ -1 +1 @@
1
- {"version":3,"names":["isCallback","source","isObjectValue","value","isTransformableRequestConfig","isRouteConfig","artwork","splitLayer","layer","resolve","resolver","Promise","config","flattenBrowseSource","browseCallback","browseConfig","browseStatic","searchSourceToRouteEntry","path","SEARCH_ROUTE_PATH","searchCallback","searchConfig","wrapTracksAsResolvedTrack","tracks","url","TABS_ROUTE_PATH","title","children","tabsSourceToRouteEntry","Array","isArray","wrappedCallback","flattenRouteEntry","browse","media","DEFAULT_ROUTE_PATH","exports","flattenRoutes","routes","tabs","search","entries","Object","push","length","undefined","MAX_TABS","validateBrowserConfiguration","warn","message","console","startsWith","page","toNativeConfig","request","requestResolver","browseResolver","nowPlayingArtwork","singleTrack","handleTrackLoad","androidControllerOfflineError","carPlayLoadingTitle","resolveAlbumUrl","formatNavigationError"],"sourceRoot":"../../../src","sources":["features/browser-config.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["isCallback","source","isObjectValue","value","isTransformableRequestConfig","isRouteConfig","artwork","splitLayer","layer","resolve","resolver","Promise","config","flattenBrowseSource","browseCallback","browseConfig","browseStatic","searchSourceToRouteEntry","path","SEARCH_ROUTE_PATH","searchCallback","searchConfig","wrapTracksAsResolvedTrack","tracks","TABS_ROUTE_PATH","title","children","tabsSourceToRouteEntry","Array","isArray","wrappedCallback","flattenRouteEntry","browse","media","DEFAULT_ROUTE_PATH","exports","flattenRoutes","routes","tabs","search","entries","Object","push","length","undefined","MAX_TABS","validateBrowserConfiguration","warn","message","console","startsWith","page","toNativeConfig","request","requestResolver","browseResolver","nowPlayingArtwork","singleTrack","handleTrackLoad","androidControllerOfflineError","carPlayLoadingTitle","resolveAlbumPath","formatNavigationError"],"sourceRoot":"../../../src","sources":["features/browser-config.ts"],"mappings":"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","ignoreList":[]}
@@ -55,8 +55,8 @@ let currentConfiguration;
55
55
  * }
56
56
  * },
57
57
  * tabs: [
58
- * { title: 'Home', url: '/' },
59
- * { title: 'Search', url: '/search' }
58
+ * { title: 'Home', path: '/' },
59
+ * { title: 'Search', path: '/search' }
60
60
  * ]
61
61
  * })
62
62
  * ```
@@ -44,26 +44,29 @@ function toggleActiveTrackFavorited() {
44
44
  }
45
45
 
46
46
  /**
47
- * Sets the list of favorited track source identifiers.
47
+ * Sets the list of favorited track identifiers.
48
48
  *
49
49
  * This syncs your app's favorites with the native favorites cache, enabling
50
50
  * the heart button in media controllers (notification, Android Auto, CarPlay)
51
51
  * to show the correct state.
52
52
  *
53
- * Note: This is only needed if the tracks provided to AudioBrowser do not
54
- * include the `favorited` field. If your API already includes favorite state in
55
- * track responses, the native favorites cache is populated automatically during browsing.
53
+ * Each identifier is matched **exactly** against a track's *identity* — its
54
+ * `id` when set, falling back to `src`. Store favorites as the same stable
55
+ * identifier you put in `Track.id` (or as the full `src` for id-less tracks).
56
56
  *
57
- * When the heart button is tapped in media controllers or when you call
58
- * `setActiveTrackFavorited()`, the native favorites cache is automatically
59
- * updated. You only need to call this on app launch to hydrate the cache.
57
+ * A track whose response already carries `favorited` keeps that value on
58
+ * display surfaces — a caller-set flag wins over cache hydration. The cache
59
+ * itself is written only by this call and by heart toggles
60
+ * (`setActiveTrackFavorited()` / media-controller taps), so you only need to
61
+ * call this on app launch — and again whenever the collection changes outside
62
+ * the player (a sync from another device, say).
60
63
  *
61
- * @param favorites - Array of favorited track `src` values
64
+ * @param favorites - Array of favorited track identities (`id`, or `src` for id-less tracks)
62
65
  *
63
66
  * @example
64
67
  * ```ts
65
- * const favoriteSrcs = await loadFavoritesFromStorage()
66
- * setFavorites(favoriteSrcs)
68
+ * const favoriteIds = await loadFavoritesFromStorage()
69
+ * setFavorites(favoriteIds)
67
70
  * ```
68
71
  */
69
72
  function setFavorites(favorites) {