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
@@ -7,11 +7,6 @@
7
7
 
8
8
  #pragma once
9
9
 
10
- #if __has_include(<NitroModules/NitroHash.hpp>)
11
- #include <NitroModules/NitroHash.hpp>
12
- #else
13
- #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
- #endif
15
10
  #if __has_include(<NitroModules/JSIConverter.hpp>)
16
11
  #include <NitroModules/JSIConverter.hpp>
17
12
  #else
@@ -22,54 +17,75 @@
22
17
  #else
23
18
  #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
19
  #endif
20
+ #if __has_include(<NitroModules/JSIHelpers.hpp>)
21
+ #include <NitroModules/JSIHelpers.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+ #if __has_include(<NitroModules/PropNameIDCache.hpp>)
26
+ #include <NitroModules/PropNameIDCache.hpp>
27
+ #else
28
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
29
+ #endif
30
+
31
+ // Forward declaration of `StyleDisplay` to properly resolve imports.
32
+ namespace margelo::nitro::audiobrowser { enum class StyleDisplay; }
33
+ // Forward declaration of `ArtworkRendering` to properly resolve imports.
34
+ namespace margelo::nitro::audiobrowser { enum class ArtworkRendering; }
35
+
36
+ #include "StyleDisplay.hpp"
37
+ #include <optional>
38
+ #include "ArtworkRendering.hpp"
25
39
 
26
40
  namespace margelo::nitro::audiobrowser {
27
41
 
28
42
  /**
29
- * An enum which can be represented as a JavaScript union (TrackStyle).
43
+ * A struct which can be represented as a JavaScript object (TrackStyle).
30
44
  */
31
- enum class TrackStyle {
32
- LIST SWIFT_NAME(list) = 0,
33
- GRID SWIFT_NAME(grid) = 1,
34
- } CLOSED_ENUM;
45
+ struct TrackStyle final {
46
+ public:
47
+ std::optional<StyleDisplay> display SWIFT_PRIVATE;
48
+ std::optional<ArtworkRendering> artworkRendering SWIFT_PRIVATE;
49
+
50
+ public:
51
+ TrackStyle() = default;
52
+ explicit TrackStyle(std::optional<StyleDisplay> display, std::optional<ArtworkRendering> artworkRendering): display(display), artworkRendering(artworkRendering) {}
53
+
54
+ public:
55
+ friend bool operator==(const TrackStyle& lhs, const TrackStyle& rhs) = default;
56
+ };
35
57
 
36
58
  } // namespace margelo::nitro::audiobrowser
37
59
 
38
60
  namespace margelo::nitro {
39
61
 
40
- // C++ TrackStyle <> JS TrackStyle (union)
62
+ // C++ TrackStyle <> JS TrackStyle (object)
41
63
  template <>
42
64
  struct JSIConverter<margelo::nitro::audiobrowser::TrackStyle> final {
43
65
  static inline margelo::nitro::audiobrowser::TrackStyle fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
44
- std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
45
- switch (hashString(unionValue.c_str(), unionValue.size())) {
46
- case hashString("list"): return margelo::nitro::audiobrowser::TrackStyle::LIST;
47
- case hashString("grid"): return margelo::nitro::audiobrowser::TrackStyle::GRID;
48
- default: [[unlikely]]
49
- throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum TrackStyle - invalid value!");
50
- }
66
+ jsi::Object obj = arg.asObject(runtime);
67
+ return margelo::nitro::audiobrowser::TrackStyle(
68
+ JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display"))),
69
+ JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))
70
+ );
51
71
  }
52
- static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::audiobrowser::TrackStyle arg) {
53
- switch (arg) {
54
- case margelo::nitro::audiobrowser::TrackStyle::LIST: return JSIConverter<std::string>::toJSI(runtime, "list");
55
- case margelo::nitro::audiobrowser::TrackStyle::GRID: return JSIConverter<std::string>::toJSI(runtime, "grid");
56
- default: [[unlikely]]
57
- throw std::invalid_argument("Cannot convert TrackStyle to JS - invalid value: "
58
- + std::to_string(static_cast<int>(arg)) + "!");
59
- }
72
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::audiobrowser::TrackStyle& arg) {
73
+ jsi::Object obj(runtime);
74
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "display"), JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::toJSI(runtime, arg.display));
75
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering"), JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::toJSI(runtime, arg.artworkRendering));
76
+ return obj;
60
77
  }
61
78
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
62
- if (!value.isString()) {
79
+ if (!value.isObject()) {
63
80
  return false;
64
81
  }
65
- std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
66
- switch (hashString(unionValue.c_str(), unionValue.size())) {
67
- case hashString("list"):
68
- case hashString("grid"):
69
- return true;
70
- default:
71
- return false;
82
+ jsi::Object obj = value.getObject(runtime);
83
+ if (!nitro::isPlainObject(runtime, obj)) {
84
+ return false;
72
85
  }
86
+ if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::StyleDisplay>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "display")))) return false;
87
+ if (!JSIConverter<std::optional<margelo::nitro::audiobrowser::ArtworkRendering>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "artworkRendering")))) return false;
88
+ return true;
73
89
  }
74
90
  };
75
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-audio-browser",
3
- "version": "0.2.0-next.11",
3
+ "version": "0.2.0-next.12",
4
4
  "description": "react-native-audio-browser is a react native package built with Nitro",
5
5
  "keywords": [
6
6
  "react-native",
@@ -1,4 +1,5 @@
1
1
  // `./native` is deliberately not re-exported — see public-surface.test.ts.
2
2
  export * from './features'
3
3
  export * from './types'
4
+ export * from './utils/getTrackIdentity'
4
5
  export * from './utils/useDebug'
@@ -18,7 +18,7 @@ describe('toNativeConfig route classification', () => {
18
18
 
19
19
  it('keeps a static page with a string artwork out of the RouteConfig branch', () => {
20
20
  const page = {
21
- url: '/x',
21
+ path: '/x',
22
22
  title: 'X',
23
23
  artwork: 'https://img.example.com/x.png',
24
24
  children: []
@@ -28,6 +28,21 @@ describe('toNativeConfig route classification', () => {
28
28
  expect(entry?.artwork).toBeUndefined()
29
29
  })
30
30
 
31
+ it('classifies a static page as static despite its top-level `path` key', () => {
32
+ // `path` is also a RequestConfig field; the page's required `title` must
33
+ // win the classification (a config with a `path` alone is not a page).
34
+ const page = { path: '/x', title: 'X', children: [] }
35
+ const entry = entryFor({ '/x': page }, '/x')
36
+ expect(entry?.browseStatic).toEqual(page)
37
+ expect(entry?.browseConfig).toBeUndefined()
38
+ })
39
+
40
+ it('classifies a path-only request config as a request config', () => {
41
+ const entry = entryFor({ '/x': { path: '/remapped' } }, '/x')
42
+ expect(entry?.browseConfig).toEqual({ path: '/remapped' })
43
+ expect(entry?.browseStatic).toBeUndefined()
44
+ })
45
+
31
46
  it('classifies a method-only request config as a request config', () => {
32
47
  const entry = entryFor({ '/x': { method: 'POST' } }, '/x')
33
48
  expect(entry?.browseConfig).toEqual({ method: 'POST' })
@@ -66,13 +81,13 @@ describe('validateBrowserConfiguration', () => {
66
81
  it('is silent for a valid configuration', () => {
67
82
  validateBrowserConfiguration({
68
83
  routes: {
69
- '/favorites': async () => ({ url: '/favorites', title: 'Favorites' }),
84
+ '/favorites': async () => ({ path: '/favorites', title: 'Favorites' }),
70
85
  '/albums/{id}': { baseUrl: 'https://api.example.com' },
71
86
  '*': { baseUrl: 'https://api.example.com' }
72
87
  },
73
88
  tabs: [
74
- { title: 'Home', url: '/' },
75
- { title: 'Search', url: '/search' }
89
+ { title: 'Home', path: '/' },
90
+ { title: 'Search', path: '/search' }
76
91
  ]
77
92
  })
78
93
  expect(warnings).toEqual([])
@@ -96,14 +111,14 @@ describe('validateBrowserConfiguration', () => {
96
111
 
97
112
  it('does not flag a valid static page as unclassifiable', () => {
98
113
  validateBrowserConfiguration({
99
- routes: { '/x': { url: '/x', title: 'X', children: [] } }
114
+ routes: { '/x': { path: '/x', title: 'X', children: [] } }
100
115
  })
101
116
  expect(warnings).toEqual([])
102
117
  })
103
118
 
104
119
  it('warns on more than 4 static tabs', () => {
105
120
  validateBrowserConfiguration({
106
- tabs: [1, 2, 3, 4, 5].map((n) => ({ title: `Tab ${n}`, url: `/${n}` }))
121
+ tabs: [1, 2, 3, 4, 5].map((n) => ({ title: `Tab ${n}`, path: `/${n}` }))
107
122
  })
108
123
  expect(warnings.some((w) => w.includes('at most 4'))).toBe(true)
109
124
  })
@@ -38,9 +38,12 @@ function isTransformableRequestConfig(
38
38
  source: unknown
39
39
  ): source is TransformableRequestConfig {
40
40
  if (!isObjectValue(source)) return false
41
- // Any RequestConfig key marks a request config. A static ResolvedTrack
42
- // shares none of these at the top level (its per-track request override is
43
- // nested under `request`).
41
+ // A static ResolvedTrack page also carries a top-level `path`, so `path`
42
+ // alone cannot mark a request config. A page is recognised by its required
43
+ // `title` — no RequestConfig field is named `title`.
44
+ if ('title' in source) return false
45
+ // Any RequestConfig key marks a request config. (A static ResolvedTrack's
46
+ // per-track request override is nested under `request`, not top-level.)
44
47
  return (
45
48
  'baseUrl' in source ||
46
49
  'path' in source ||
@@ -120,7 +123,7 @@ function searchSourceToRouteEntry(source: SearchSource): NativeRouteEntry {
120
123
  */
121
124
  function wrapTracksAsResolvedTrack(tracks: Track[]): ResolvedTrack {
122
125
  return {
123
- url: TABS_ROUTE_PATH,
126
+ path: TABS_ROUTE_PATH,
124
127
  title: 'Tabs',
125
128
  children: tracks
126
129
  }
@@ -245,14 +248,14 @@ export function validateBrowserConfiguration(
245
248
  !isRouteConfig(source) &&
246
249
  !isTransformableRequestConfig(source)
247
250
  ) {
248
- // Falls through to the static-page branch; a page needs url + title.
251
+ // Falls through to the static-page branch; a page needs path + title.
249
252
  const page = source as Partial<ResolvedTrack>
250
- if (typeof page.url !== 'string' || typeof page.title !== 'string') {
253
+ if (typeof page.path !== 'string' || typeof page.title !== 'string') {
251
254
  warn(
252
255
  `route "${path}" matched no source shape — expected a callback, ` +
253
256
  `a request config ({ baseUrl, path, headers, ... }), a ` +
254
257
  `RouteConfig ({ browse, media, artwork }), or a static page ` +
255
- `({ url, title, children? }). It will be served as static ` +
258
+ `({ path, title, children? }). It will be served as static ` +
256
259
  `content and likely render a blank screen.`
257
260
  )
258
261
  }
@@ -287,7 +290,7 @@ export function toNativeConfig(
287
290
  handleTrackLoad: config.handleTrackLoad,
288
291
  androidControllerOfflineError: config.androidControllerOfflineError,
289
292
  carPlayLoadingTitle: config.carPlayLoadingTitle,
290
- resolveAlbumUrl: config.resolveAlbumUrl,
293
+ resolveAlbumPath: config.resolveAlbumPath,
291
294
  formatNavigationError: config.formatNavigationError
292
295
  }
293
296
  }
@@ -12,7 +12,7 @@ describe('getBrowserConfiguration', () => {
12
12
  })
13
13
 
14
14
  it('returns the last configuration in its public shape', () => {
15
- const config = { tabs: [{ title: 'Home', url: '/' }] }
15
+ const config = { tabs: [{ title: 'Home', path: '/' }] }
16
16
  configureBrowser(config)
17
17
  expect(getBrowserConfiguration()).toBe(config)
18
18
  })
@@ -43,8 +43,8 @@ let currentConfiguration: BrowserConfiguration | undefined
43
43
  * }
44
44
  * },
45
45
  * tabs: [
46
- * { title: 'Home', url: '/' },
47
- * { title: 'Search', url: '/search' }
46
+ * { title: 'Home', path: '/' },
47
+ * { title: 'Search', path: '/search' }
48
48
  * ]
49
49
  * })
50
50
  * ```
@@ -37,26 +37,29 @@ export function toggleActiveTrackFavorited(): void {
37
37
  }
38
38
 
39
39
  /**
40
- * Sets the list of favorited track source identifiers.
40
+ * Sets the list of favorited track identifiers.
41
41
  *
42
42
  * This syncs your app's favorites with the native favorites cache, enabling
43
43
  * the heart button in media controllers (notification, Android Auto, CarPlay)
44
44
  * to show the correct state.
45
45
  *
46
- * Note: This is only needed if the tracks provided to AudioBrowser do not
47
- * include the `favorited` field. If your API already includes favorite state in
48
- * track responses, the native favorites cache is populated automatically during browsing.
46
+ * Each identifier is matched **exactly** against a track's *identity* — its
47
+ * `id` when set, falling back to `src`. Store favorites as the same stable
48
+ * identifier you put in `Track.id` (or as the full `src` for id-less tracks).
49
49
  *
50
- * When the heart button is tapped in media controllers or when you call
51
- * `setActiveTrackFavorited()`, the native favorites cache is automatically
52
- * updated. You only need to call this on app launch to hydrate the cache.
50
+ * A track whose response already carries `favorited` keeps that value on
51
+ * display surfaces — a caller-set flag wins over cache hydration. The cache
52
+ * itself is written only by this call and by heart toggles
53
+ * (`setActiveTrackFavorited()` / media-controller taps), so you only need to
54
+ * call this on app launch — and again whenever the collection changes outside
55
+ * the player (a sync from another device, say).
53
56
  *
54
- * @param favorites - Array of favorited track `src` values
57
+ * @param favorites - Array of favorited track identities (`id`, or `src` for id-less tracks)
55
58
  *
56
59
  * @example
57
60
  * ```ts
58
- * const favoriteSrcs = await loadFavoritesFromStorage()
59
- * setFavorites(favoriteSrcs)
61
+ * const favoriteIds = await loadFavoritesFromStorage()
62
+ * setFavorites(favoriteIds)
60
63
  * ```
61
64
  */
62
65
  export function setFavorites(favorites: string[]): void {
@@ -4,10 +4,12 @@ import { renderHook, act, cleanup } from '@testing-library/react'
4
4
  import { describe, it, expect, afterEach, vi } from 'vitest'
5
5
 
6
6
  /**
7
- * The 22 hooks that are a single delegation to `useNativeUpdatedValue`. Their
7
+ * The hooks that are a single delegation to `useNativeUpdatedValue`. Their
8
8
  * behaviour is covered once in `utils/useNativeUpdatedValue.test.ts`; what is
9
9
  * per-hook and untestable there is the wiring — which native callback slot the
10
10
  * emitter claims, and which `eventKey` is pulled out of the payload.
11
+ * (`useActiveTrack` and `useQueue` are getter-refreshed instead — they update
12
+ * on transitions AND favorite toggles — and are covered in their own block.)
11
13
  *
12
14
  * Both are easy to get wrong and silent when wrong: the hook simply never
13
15
  * updates, or yields the whole event where a field was meant. Five slots
@@ -159,13 +161,6 @@ const wirings: Wiring[] = [
159
161
  event: true,
160
162
  expected: true
161
163
  },
162
- {
163
- name: 'useQueue',
164
- hook: useQueue,
165
- slot: 'onPlaybackQueueChanged',
166
- event: [{ src: 'a' }],
167
- expected: [{ src: 'a' }]
168
- },
169
164
  {
170
165
  name: 'useFormattedNavigationError',
171
166
  hook: useFormattedNavigationError,
@@ -210,13 +205,6 @@ const wirings: Wiring[] = [
210
205
  event: { repeatMode: 'queue' },
211
206
  expected: 'queue'
212
207
  },
213
- {
214
- name: 'useActiveTrack',
215
- hook: useActiveTrack,
216
- slot: 'onPlaybackActiveTrackChanged',
217
- event: { track: { src: 'a' } },
218
- expected: { src: 'a' }
219
- },
220
208
  {
221
209
  name: 'useBatteryWarningPending',
222
210
  hook: useBatteryWarningPending,
@@ -252,3 +240,63 @@ describe('hook wiring table', () => {
252
240
  expect(new Set(wirings.map((w) => w.slot)).size).toBe(wirings.length)
253
241
  })
254
242
  })
243
+
244
+ /**
245
+ * `useActiveTrack` and `useQueue` are getter-refreshed: a favorite toggle
246
+ * mutates the active track in place WITHOUT a transition event —
247
+ * transitions and mutations are separate events — so
248
+ * these hooks re-read their native getter on the transition slot AND on
249
+ * `onFavoriteChanged`.
250
+ */
251
+ const { nativeBrowser } = await import('../native')
252
+
253
+ describe.each([
254
+ {
255
+ name: 'useActiveTrack',
256
+ hook: useActiveTrack,
257
+ getter: 'getActiveTrack',
258
+ transitionSlot: 'onPlaybackActiveTrackChanged',
259
+ initial: { src: 'a', favorited: false },
260
+ mutated: { src: 'a', favorited: true }
261
+ },
262
+ {
263
+ name: 'useQueue',
264
+ hook: useQueue,
265
+ getter: 'getQueue',
266
+ transitionSlot: 'onPlaybackQueueChanged',
267
+ initial: [{ src: 'a', favorited: false }],
268
+ mutated: [{ src: 'a', favorited: true }]
269
+ }
270
+ ])(
271
+ '$name (getter-refreshed)',
272
+ ({ hook, getter, transitionSlot, initial, mutated }) => {
273
+ it(`re-reads ${getter} on ${transitionSlot} and onFavoriteChanged`, () => {
274
+ expect(slots.has(transitionSlot)).toBe(true)
275
+ expect(slots.has('onFavoriteChanged')).toBe(true)
276
+
277
+ const getterMock = (
278
+ nativeBrowser as unknown as Record<string, ReturnType<typeof vi.fn>>
279
+ )[getter]!
280
+
281
+ getterMock.mockReturnValue(initial)
282
+ const { result } = renderHook(() => hook())
283
+ expect(result.current).toEqual(initial)
284
+
285
+ // A transition re-reads the getter (the event payload is not consumed).
286
+ getterMock.mockReturnValue(mutated)
287
+ act(() => slots.get(transitionSlot)!({}))
288
+ expect(result.current).toEqual(mutated)
289
+
290
+ // A favorite toggle mutates in place — no transition event fires, but
291
+ // the hook must still refresh.
292
+ getterMock.mockReturnValue(initial)
293
+ act(() =>
294
+ slots.get('onFavoriteChanged')!({
295
+ track: { src: 'a' },
296
+ favorited: false
297
+ })
298
+ )
299
+ expect(result.current).toEqual(initial)
300
+ })
301
+ }
302
+ )
@@ -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()
@@ -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
  }