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
@@ -15,9 +15,10 @@ export interface TrackLoadEvent {
15
15
 
16
16
  /**
17
17
  * Callback for handling track load events.
18
- * When set on BrowserConfiguration, navigateTrack() will call this handler
19
- * instead of auto-loading/playing the track. The native side awaits completion
20
- * before proceeding (e.g., showing Now Playing in CarPlay).
18
+ * When set on BrowserConfiguration, loading a playable track — a browse-row
19
+ * tap or a `navigate(track)` call — invokes this handler instead of the
20
+ * library auto-loading/playing it. The native side awaits the returned
21
+ * promise before proceeding (e.g., showing Now Playing in CarPlay).
21
22
  */
22
23
  export type HandleTrackLoadCallback = (event: TrackLoadEvent) => Promise<void>
23
24
 
@@ -187,7 +188,7 @@ export interface RequestConfig {
187
188
  method?: HttpMethod
188
189
  /**
189
190
  * The request path.
190
- * - For browser requests, this is the track's `url`
191
+ * - For browser requests, this is the track's `path`
191
192
  * - For media requests, this is the track's `src` value
192
193
  * - For artwork requests, this is the track's `artwork` URL
193
194
  */
@@ -351,7 +352,7 @@ export type RequestConfigResolver = () =>
351
352
  /**
352
353
  * Configuration for artwork image requests
353
354
  *
354
- * ## Configuration Hierarchy
355
+ * ### Configuration Hierarchy
355
356
  *
356
357
  * When a request is made, configs are merged in this order (later overrides earlier):
357
358
  * 1. `request` (base config) - shared settings like user agent, common headers
@@ -360,7 +361,7 @@ export type RequestConfigResolver = () =>
360
361
  * 4. `imageQueryParams` - automatic size query param injection (if configured)
361
362
  * 5. `transform(request, context)` result - final modifications (if provided)
362
363
  *
363
- * ## Usage Patterns
364
+ * ### Usage Patterns
364
365
  *
365
366
  * **Simple CDN configuration:**
366
367
  * ```typescript
@@ -426,7 +427,7 @@ export interface ImageQueryParams {
426
427
  * Used for both `media` (audio streaming) and `artwork` (image loading) configuration
427
428
  * in BrowserConfiguration.
428
429
  *
429
- * ## Configuration Hierarchy
430
+ * ### Configuration Hierarchy
430
431
  *
431
432
  * When a request is made, configs are merged in this order (later overrides earlier):
432
433
  * 1. `request` (base config) - shared settings like user agent, common headers
@@ -434,7 +435,7 @@ export interface ImageQueryParams {
434
435
  * 3. `resolve(track)` result - per-track overrides (if provided)
435
436
  * 4. `transform(request)` result - final modifications (if provided)
436
437
  *
437
- * ## Usage Patterns
438
+ * ### Usage Patterns
438
439
  *
439
440
  * **Simple CDN configuration:**
440
441
  * ```typescript
@@ -590,9 +591,9 @@ export interface ArtworkRequestConfig extends RequestConfig {
590
591
  *
591
592
  * **Response shape (HTTP / `TransformableRequestConfig`):** the endpoint must
592
593
  * return a single **page object** — a {@link ResolvedTrack}
593
- * (`{ title, url?, children: Track[] }`). The `children` array holds the rows
594
+ * (`{ title, path?, children: Track[] }`). The `children` array holds the rows
594
595
  * shown for the container; each child is a playable leaf (`src`) or a navigable
595
- * sub-container (`url`). A callback / static `ResolvedTrack` returns the same
596
+ * sub-container (`path`). A callback / static `ResolvedTrack` returns the same
596
597
  * page object directly.
597
598
  *
598
599
  * {@link SearchSource} and {@link TabsSource} HTTP endpoints return this same
@@ -656,43 +657,10 @@ export type TabsSource =
656
657
  */
657
658
  export type SearchSource = SearchSourceCallback | TransformableRequestConfig
658
659
 
659
- /**
660
- * How ids passed to `setFavorites` are matched against a
661
- * track's `src` to hydrate its `favorited` flag.
662
- *
663
- * - `'exact'`: the id must equal `src`.
664
- * - `'partial'`: the id matches if it appears as a complete path segment within
665
- * `src` (delimited by `/`, `?`, `#`, or the string boundaries). Useful when
666
- * favorites are stored as a stable identifier that is embedded in — but not
667
- * equal to — the playable `src` URL.
668
- *
669
- * @example
670
- * ```text
671
- * setFavorites(['abc123']) + track.src = '/stream/jazz-fm/abc123'
672
- *
673
- * 'exact' 'abc123' === '/stream/jazz-fm/abc123' → not favorited
674
- * 'partial' 'abc123' is the last segment of the src → favorited
675
- *
676
- * setFavorites(['https://cdn.example.com/jazz-fm/abc123.mp3'])
677
- *
678
- * 'exact' id === src → favorited
679
- * 'partial' id is a full segment of src → favorited
680
- * ```
681
- */
682
- export type FavoritesMatchMode = 'exact' | 'partial'
683
-
684
- /**
685
- * Object form of the `favorite` capability — enables favoriting with an explicit
686
- * {@link FavoritesMatchMode}. (A bare `true` is shorthand for `{ match: 'exact' }`.)
687
- */
688
- export interface FavoriteConfig {
689
- match: FavoritesMatchMode
690
- }
691
-
692
660
  export type BrowserConfiguration = {
693
661
  /**
694
662
  * Initial navigation path. Setting this triggers initial navigation to the
695
- * specified path. When unset, the first tab's URL is used; when there are
663
+ * specified path. When unset, the first tab's path is used; when there are
696
664
  * no tabs either, `/`.
697
665
  */
698
666
  path?: string | undefined
@@ -874,19 +842,19 @@ export type BrowserConfiguration = {
874
842
 
875
843
  /**
876
844
  * Configuration for navigation tabs in the media browser.
877
- * The first tab's URL is automatically loaded when the browser starts.
845
+ * The first tab's path is automatically loaded when the browser starts.
878
846
  *
879
847
  * Optional - if not provided, no tab navigation will be available.
880
848
  * Limited to maximum 4 tabs for automotive platform compatibility (Android Auto/CarPlay).
881
849
  *
882
- * Can provide static array of Track objects with urls as tabs, API configuration, or custom callback.
850
+ * Can provide static array of Track objects with paths as tabs, API configuration, or custom callback.
883
851
  */
884
852
  tabs?: TabsSource
885
853
 
886
854
  /**
887
- * Route-specific configurations. Maps URL path patterns to browse sources.
855
+ * Route-specific configurations. Maps path patterns to browse sources.
888
856
  *
889
- * ## Matching
857
+ * ### Matching
890
858
  *
891
859
  * Patterns match on **exact segment count** — `/artists` does NOT match
892
860
  * `/artists/123`. Segments may be:
@@ -900,7 +868,7 @@ export type BrowserConfiguration = {
900
868
  * When several patterns match, the most specific wins
901
869
  * (constants > parameters > wildcards > tail wildcard).
902
870
  *
903
- * ## Defaults and reserved keys
871
+ * ### Defaults and reserved keys
904
872
  *
905
873
  * A path matching no route is fetched over HTTP via the `request` →
906
874
  * `browse` layers with the path applied — most APIs only need explicit
@@ -983,22 +951,22 @@ export type BrowserConfiguration = {
983
951
 
984
952
  /**
985
953
  * Called when the album line on the CarPlay Now Playing screen is tapped
986
- * and the active track has no {@link Track.albumUrl}. Return a browse path
954
+ * and the active track has no {@link Track.albumPath}. Return a browse path
987
955
  * to navigate the CarPlay browse stack there, or `undefined` if the tap was
988
956
  * handled (or should do nothing).
989
957
  *
990
- * The album line is tappable whenever the active track has an `albumUrl`
958
+ * The album line is tappable whenever the active track has an `albumPath`
991
959
  * or this callback is configured.
992
960
  *
993
961
  * @example
994
962
  * ```typescript
995
- * resolveAlbumUrl: (track) =>
963
+ * resolveAlbumPath: (track) =>
996
964
  * track.album ? `/album/${slugify(track.album)}` : undefined
997
965
  * ```
998
966
  *
999
967
  * @platform ios
1000
968
  */
1001
- resolveAlbumUrl?: ResolveAlbumUrlCallback
969
+ resolveAlbumPath?: ResolveAlbumPathCallback
1002
970
 
1003
971
  /**
1004
972
  * Callback to customize error messages for navigation errors.
@@ -1038,6 +1006,17 @@ export type CarPlayNowPlayingButton =
1038
1006
  | 'favorite'
1039
1007
  | 'playback-rate'
1040
1008
 
1009
+ /**
1010
+ * Where the now-playing indicator draws on a CarPlay list row.
1011
+ *
1012
+ * - `'leading'`: in the artwork slot, replacing the row's artwork while it
1013
+ * plays (the system default, as Apple Music does)
1014
+ * - `'trailing'`: at the end of the row, keeping the artwork visible
1015
+ *
1016
+ * @platform ios
1017
+ */
1018
+ export type CarPlayPlayingIndicatorLocation = 'leading' | 'trailing'
1019
+
1041
1020
  /**
1042
1021
  * Position of the "Ask Siri to Play Audio" assistant cell on a CarPlay list template.
1043
1022
  *
@@ -1111,6 +1090,6 @@ export type FormatNavigationErrorCallback = (
1111
1090
 
1112
1091
  /**
1113
1092
  * Maps the tapped active track to a browse path for the CarPlay album line,
1114
- * or `undefined` to do nothing. See `resolveAlbumUrl`.
1093
+ * or `undefined` to do nothing. See `resolveAlbumPath`.
1115
1094
  */
1116
- export type ResolveAlbumUrlCallback = (track: Track) => string | undefined
1095
+ export type ResolveAlbumPathCallback = (track: Track) => string | undefined
@@ -1,28 +1,31 @@
1
1
  type Callback<T> = (arg: T) => void
2
2
 
3
3
  /**
4
- * A lightweight event emitter that lazily bridges native callbacks to multiple JavaScript listeners.
4
+ * The subscription object behind the library's **discrete** events — the
5
+ * remote-control exports (`onRemotePlay`, `onRemoteSeek`, …) and other
6
+ * one-shot signals with no "current value" to cache. Subscribe with
7
+ * {@link addListener}; the returned function unsubscribes. The emitter is
8
+ * not callable itself.
5
9
  *
6
- * The native callback is only assigned when the first listener is added, making the emitter
7
- * creation lightweight with no side effects until actually used.
8
- *
9
- * Use this for optional/rare events like remote controls where most apps won't need to listen.
10
- * For critical state changes (playback, progress, etc.), use NativeEmitter instead to ensure
11
- * no events are missed.
10
+ * "Lazy" is an implementation detail you may notice in timing: the native
11
+ * side only starts delivering the event once the first listener is added.
12
+ * State that always has a current reading uses `NativeUpdatedValue`
13
+ * instead.
12
14
  *
13
15
  * @example
14
16
  * ```typescript
15
- * export const onRemotePlay = LazyNativeEmitter.emitterize<void>(
16
- * (cb) => (nativeBrowser.onRemotePlay = cb)
17
- * )
17
+ * import { onRemotePlay } from 'react-native-audio-browser'
18
18
  *
19
19
  * const unsubscribe = onRemotePlay.addListener(() => console.log('play'))
20
+ * // later:
21
+ * unsubscribe()
20
22
  * ```
21
23
  */
22
24
  export class LazyNativeEmitter<T> {
23
25
  private setter: (callback: (data: T) => void) => void
24
26
  private listeners: Set<(arg: T) => void> | undefined
25
27
 
28
+ /** @internal */
26
29
  constructor(setter: (callback: (data: T) => void) => void) {
27
30
  this.setter = setter
28
31
  }
@@ -38,6 +41,12 @@ export class LazyNativeEmitter<T> {
38
41
  return listeners
39
42
  }
40
43
 
44
+ /**
45
+ * Subscribes to the event.
46
+ *
47
+ * @param callback - Invoked with each event
48
+ * @returns A function that removes the listener
49
+ */
41
50
  addListener(callback: Callback<T>): () => void {
42
51
  const listeners = this.getListeners()
43
52
  listeners.add(callback)
@@ -50,6 +59,7 @@ export class LazyNativeEmitter<T> {
50
59
  * @param setter - Function that sets the native callback property
51
60
  * @returns The emitter instance — subscribe with `addListener`, which returns
52
61
  * an unsubscribe function
62
+ * @internal
53
63
  */
54
64
  static emitterize<T>(
55
65
  setter: (callback: (data: T) => void) => void
@@ -1,30 +1,39 @@
1
1
  type Callback<T> = (arg: T) => void
2
2
 
3
3
  /**
4
- * Bridges native value changes to JavaScript, storing the latest value for immediate access.
4
+ * The subscription object behind the library's **state** events — every
5
+ * `on<Thing>Changed`-style export (`onPlaybackChanged`, `onQueueChanged`, …)
6
+ * is one of these. Subscribe with {@link addListener}; the returned function
7
+ * unsubscribes. The emitter is not callable itself.
5
8
  *
6
- * Subscribes to native changes immediately at module load to ensure no updates are missed.
7
- * The `lastValue` property caches the most recent value, allowing hooks to read the current
8
- * state synchronously without calling a separate getter function.
9
- *
10
- * Use this for state-based values that have a "current value" (playback state, options, progress).
11
- * For discrete events without a current value (remote controls), use LazyNativeEmitter instead.
9
+ * It tracks a value that always has a *current* reading (playback state,
10
+ * progress, options): {@link lastValue} caches the most recent one so hooks —
11
+ * and you — can read it synchronously without a getter round-trip. Discrete
12
+ * events with no current value (remote-control presses) use
13
+ * `LazyNativeEmitter` instead.
12
14
  *
13
15
  * @example
14
16
  * ```typescript
15
- * export const onPlaybackChanged = NativeUpdatedValue.emitterize<Playback>(
16
- * (cb) => (nativeBrowser.onPlaybackChanged = cb)
17
- * )
17
+ * import { onPlaybackChanged } from 'react-native-audio-browser'
18
18
  *
19
- * export function usePlayback(): Playback {
20
- * return useUpdatedNativeValue(getPlayback, onPlaybackChanged)
21
- * }
19
+ * const unsubscribe = onPlaybackChanged.addListener((p) => {
20
+ * console.log(p.state)
21
+ * })
22
+ * // later:
23
+ * unsubscribe()
22
24
  * ```
23
25
  */
24
26
  export class NativeUpdatedValue<T> {
25
27
  private listeners = new Set<Callback<T>>()
28
+
29
+ /**
30
+ * The most recent value delivered by native, or `undefined` before the
31
+ * first update. Subscription starts at module load, so this is safe to
32
+ * read synchronously at any time.
33
+ */
26
34
  lastValue: T | undefined = undefined
27
35
 
36
+ /** @internal */
28
37
  constructor(setter: (callback: (data: T) => void) => void) {
29
38
  // Install native callback immediately at module load
30
39
  setter((data: T) => {
@@ -35,6 +44,12 @@ export class NativeUpdatedValue<T> {
35
44
  })
36
45
  }
37
46
 
47
+ /**
48
+ * Subscribes to value changes.
49
+ *
50
+ * @param callback - Invoked with each new value
51
+ * @returns A function that removes the listener
52
+ */
38
53
  addListener(callback: Callback<T>): () => void {
39
54
  this.listeners.add(callback)
40
55
  return () => this.listeners.delete(callback)
@@ -45,6 +60,7 @@ export class NativeUpdatedValue<T> {
45
60
  *
46
61
  * @param setter - Function that sets the native callback property
47
62
  * @returns The subscription instance with lastValue and addListener
63
+ * @internal
48
64
  */
49
65
  static emitterize<T>(
50
66
  setter: (callback: (data: T) => void) => void
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A track's identity: its opaque `id` when present (non-blank), falling back
3
+ * to the playable `src`. Two tracks refer to the same item exactly when their
4
+ * identities are equal. Browsable-only tracks (neither `id` nor `src`) have
5
+ * no identity — they are addressed by their `path` instead.
6
+ *
7
+ * This is the same comparison the library uses everywhere it matches tracks:
8
+ * favorites (`setFavorites`), the CarPlay / Android Auto "now playing" row
9
+ * indicator, and queue expansion and in-place skipping when a track is
10
+ * selected from browse content. Use it for your own bookkeeping — keying a
11
+ * favorites collection, deduplicating history — so your comparisons agree
12
+ * with the library's.
13
+ */
14
+ // Decision record: ADR 0008 (docs/adr/).
15
+ export function getTrackIdentity(track: {
16
+ id?: string
17
+ src?: string
18
+ }): string | undefined {
19
+ return track.id?.length ? track.id : track.src
20
+ }
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ type Subscribable = {
4
+ addListener(callback: (event: unknown) => void): () => void
5
+ }
6
+
7
+ /**
8
+ * Hook for a native-fetched value that must stay fresh across SEVERAL event
9
+ * sources — e.g. the active track, which changes on a queue transition
10
+ * (`onActiveTrackChanged`) but is also mutated in place by a favorite toggle
11
+ * (`onFavoriteChanged`).
12
+ *
13
+ * Unlike {@link useNativeUpdatedValue}, this never reads a cached event
14
+ * payload: every trigger re-reads `getter()`, and the mount effect re-reads
15
+ * once after the subscriptions attach (so an event firing between render and
16
+ * effect cannot be missed). The getters are cheap synchronous Nitro calls.
17
+ *
18
+ * @param getter - Synchronous native getter for the current value
19
+ * @param emitters - Event sources that invalidate the value (a stable,
20
+ * module-level array — identity changes resubscribe)
21
+ */
22
+ export function useNativeValueRefreshedBy<T>(
23
+ getter: () => T,
24
+ emitters: readonly Subscribable[]
25
+ ): T {
26
+ const [value, setValue] = useState(getter)
27
+
28
+ useEffect(() => {
29
+ setValue(getter())
30
+ const cleanups = emitters.map((emitter) =>
31
+ emitter.addListener(() => setValue(getter()))
32
+ )
33
+ return () => cleanups.forEach((cleanup) => cleanup())
34
+ // eslint-disable-next-line react-hooks/exhaustive-deps
35
+ }, [getter, ...emitters])
36
+
37
+ return value
38
+ }
@@ -44,6 +44,7 @@ import type {
44
44
  } from '../specs/audio-browser.nitro'
45
45
  import type { ResolvedTrack, Track, TrackLoadEvent } from '../types'
46
46
  import type { NativeBrowserConfiguration } from '../types/browser-native'
47
+ import { getTrackIdentity } from '../utils/getTrackIdentity'
47
48
  import { BrowserManager } from './browser/BrowserManager'
48
49
  import { classifyTrackNavigation } from './browser/classifyTrackNavigation'
49
50
  import { FavoriteManager } from './browser/FavoriteManager'
@@ -57,6 +58,7 @@ import { RemoteCommandController } from './player/RemoteCommandController'
57
58
  import { QueuePlayer, SleepTimerManager, VolumeFader } from './TrackPlayer'
58
59
  import { PlaybackTimer } from './TrackPlayer/PlaybackTimer'
59
60
  import { derivePlayingState } from './TrackPlayer/PlayingStateFactory'
61
+ import { BrowserPathHelper } from './util/BrowserPathHelper'
60
62
 
61
63
  /**
62
64
  * Web implementation of AudioBrowser (unified browser + player)
@@ -414,11 +416,13 @@ export class NativeAudioBrowser
414
416
  * Attempts to skip to a track already in the current queue.
415
417
  * Used as an optimization to avoid re-expanding the queue.
416
418
  *
417
- * @param trackId The track's src identifier
419
+ * @param track The tapped track (its contextual path pins the exact copy)
420
+ * @param trackId The track's identity (id, falling back to src)
418
421
  * @param parentPath The parent path to check against queueSourcePath
419
422
  * @returns true if successfully skipped to existing track, false otherwise
420
423
  */
421
424
  private async trySkipToExistingQueueTrack(
425
+ track: Track,
422
426
  trackId: string,
423
427
  parentPath: string
424
428
  ): Promise<boolean> {
@@ -426,8 +430,21 @@ export class NativeAudioBrowser
426
430
  return false
427
431
  }
428
432
 
433
+ // Exact-surface match first: a contextual path carries the tapped page
434
+ // position (__index), so path equality pins the exact copy when the page
435
+ // holds the same identity more than once. The identity match remains for
436
+ // index-less paths (e.g. pre-stamp persisted state); an index-stamped path
437
+ // with no exact match falls through to expansion, which re-scopes the
438
+ // queue to the tapped section.
429
439
  const queue = this.getQueue()
430
- const index = queue.findIndex((t) => t.src === trackId)
440
+ let index = queue.findIndex((t) => t.path != null && t.path === track.path)
441
+ if (
442
+ index < 0 &&
443
+ track.path != null &&
444
+ BrowserPathHelper.extractIndex(track.path) === undefined
445
+ ) {
446
+ index = queue.findIndex((t) => getTrackIdentity(t) === trackId)
447
+ }
431
448
 
432
449
  if (index < 0) {
433
450
  return false
@@ -474,6 +491,11 @@ export class NativeAudioBrowser
474
491
  */
475
492
  private async navigateTrackAsync(track: Track): Promise<void> {
476
493
  try {
494
+ // A disabled track is unavailable — it never plays, whichever surface
495
+ // or stale resume path delivered the selection (Track.disabled; mirrors
496
+ // iOS TrackSelector.select and Android navigateTrack).
497
+ if (track.disabled === true) return
498
+
477
499
  const nav = classifyTrackNavigation(track)
478
500
  switch (nav.kind) {
479
501
  case 'contextual':
@@ -481,6 +503,7 @@ export class NativeAudioBrowser
481
503
  if (
482
504
  nav.trackId &&
483
505
  (await this.trySkipToExistingQueueTrack(
506
+ track,
484
507
  nav.trackId,
485
508
  nav.parentPath
486
509
  ))
@@ -504,7 +527,7 @@ export class NativeAudioBrowser
504
527
  return
505
528
 
506
529
  case 'invalid':
507
- throw new Error("Track must have either 'url' or 'src' property")
530
+ throw new Error("Track must have either 'path' or 'src' property")
508
531
  }
509
532
  } catch (error) {
510
533
  const message = error instanceof Error ? error.message : 'Unknown error'
@@ -941,13 +964,14 @@ export class NativeAudioBrowser
941
964
  setActiveTrackFavorited(favorited: boolean): void {
942
965
  const track = this.getActiveTrack()
943
966
  const index = this.getActiveTrackIndex()
944
- if (!track || !track.src || index === undefined) return
967
+ if (!track || getTrackIdentity(track) === undefined || index === undefined)
968
+ return
945
969
 
946
- // Update favorites set via manager
970
+ // Update favorites set via manager (keyed by track identity)
947
971
  if (favorited) {
948
- this.favoriteManager.addFavorite(track.src)
972
+ this.favoriteManager.addFavorite(track)
949
973
  } else {
950
- this.favoriteManager.removeFavorite(track.src)
974
+ this.favoriteManager.removeFavorite(track)
951
975
  }
952
976
 
953
977
  // Create updated track with new favorited state
@@ -959,27 +983,18 @@ export class NativeAudioBrowser
959
983
  // Replace the track in the queue
960
984
  this.queue.replaceTrack(index, updatedTrack)
961
985
 
962
- // Emit favorite changed event
986
+ // Emit favorite changed only. A favorite toggle is an in-place mutation of
987
+ // the active track, not a transition — onPlaybackActiveTrackChanged and
988
+ // onPlaybackQueueChanged stay transition-only, and the useActiveTrack /
989
+ // useQueue hooks subscribe to onFavoriteChanged themselves.
963
990
  this.onFavoriteChanged({ track: updatedTrack, favorited })
964
-
965
- // Emit active track changed so useActiveTrack() hook updates
966
- this.onPlaybackActiveTrackChanged({
967
- lastIndex: index,
968
- lastTrack: track,
969
- lastPosition: this.element?.currentTime ?? 0,
970
- index,
971
- track: updatedTrack
972
- })
973
-
974
- // Emit queue changed so useQueue() hook updates
975
- this.emitQueueChanged()
976
991
  }
977
992
 
978
993
  toggleActiveTrackFavorited(): void {
979
994
  const track = this.getActiveTrack()
980
- if (!track || !track.src) return
995
+ if (!track || getTrackIdentity(track) === undefined) return
981
996
 
982
- const isFavorited = this.favoriteManager.isFavorited(track.src)
997
+ const isFavorited = this.favoriteManager.isFavorited(track)
983
998
  this.setActiveTrackFavorited(!isFavorited)
984
999
  }
985
1000