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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/README.md +14 -4
  2. package/android/build.gradle +20 -1
  3. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
  5. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  6. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  7. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -58
  8. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  9. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  10. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -7
  11. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  12. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  13. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
  14. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  15. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  16. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +109 -74
  17. package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
  18. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  19. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  20. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  21. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  22. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  23. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
  24. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
  25. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  26. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
  27. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  28. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  29. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  30. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  31. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  32. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  33. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  34. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  36. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  38. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  39. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  40. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  41. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  42. package/ios/Browser/BrowserConfig.swift +102 -5
  43. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  44. package/ios/Browser/BrowserManager.swift +229 -180
  45. package/ios/Browser/BrowserPathHelper.swift +44 -44
  46. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  47. package/ios/Browser/JsonModels.swift +258 -106
  48. package/ios/Browser/SearchDrillIn.swift +6 -6
  49. package/ios/Browser/SectionPresentation.swift +64 -0
  50. package/ios/Browser/SectionScope.swift +58 -24
  51. package/ios/Browser/StyleResolver.swift +58 -0
  52. package/ios/Browser/TabBarEntries.swift +9 -3
  53. package/ios/Browser/TrackIdTemplate.swift +37 -0
  54. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  55. package/ios/CarPlay/CarPlayController.swift +227 -85
  56. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  57. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  58. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  59. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  60. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  61. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  62. package/ios/Extension/Section+Copying.swift +31 -0
  63. package/ios/Extension/Track+Copying.swift +6 -12
  64. package/ios/Extension/Track+Identity.swift +18 -0
  65. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  66. package/ios/Http/HttpClient.swift +8 -2
  67. package/ios/HybridAudioBrowser.swift +166 -45
  68. package/ios/Model/NitroTypeStubs.swift +106 -21
  69. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  70. package/ios/Player/MediaIntentCriteria.swift +13 -5
  71. package/ios/Player/MediaLoader.swift +33 -9
  72. package/ios/Player/NowPlayingUpdater.swift +54 -6
  73. package/ios/Player/PlaybackCoordinator.swift +13 -6
  74. package/ios/Player/PlaybackTimer.swift +8 -2
  75. package/ios/Player/QueueManager.swift +36 -10
  76. package/ios/Player/RetryManager.swift +4 -3
  77. package/ios/Player/ShuffleOrder.swift +23 -0
  78. package/ios/Player/SleepTimerHandling.swift +13 -0
  79. package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
  80. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  81. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  82. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  83. package/ios/Tests/JsonModelsTests.swift +150 -1
  84. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  85. package/ios/Tests/QueueManagerTests.swift +72 -6
  86. package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
  87. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  88. package/ios/Tests/SearchDrillInTests.swift +16 -16
  89. package/ios/Tests/SectionPresentationTests.swift +90 -0
  90. package/ios/Tests/SectionScopeTests.swift +87 -55
  91. package/ios/Tests/StyleResolverTests.swift +127 -0
  92. package/ios/Tests/Support/TestTypes.swift +7 -0
  93. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  94. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  95. package/ios/Tests/TrackIdentityTests.swift +20 -0
  96. package/ios/Tests/TrackSelectorTests.swift +168 -33
  97. package/ios/TrackPlayer.swift +57 -34
  98. package/ios/TrackSelector.swift +45 -30
  99. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  100. package/ios/Util/NetworkMonitor.swift +43 -26
  101. package/ios/Util/SFSymbolRenderer.swift +24 -2
  102. package/ios/Util/SVGProcessor.swift +9 -2
  103. package/lib/commonjs/AudioBrowser.js +11 -0
  104. package/lib/commonjs/AudioBrowser.js.map +1 -1
  105. package/lib/commonjs/features/browser-config.js +11 -8
  106. package/lib/commonjs/features/browser-config.js.map +1 -1
  107. package/lib/commonjs/features/browser.js +2 -2
  108. package/lib/commonjs/features/favorites.js +13 -10
  109. package/lib/commonjs/features/favorites.js.map +1 -1
  110. package/lib/commonjs/features/playback/controls.js +4 -2
  111. package/lib/commonjs/features/playback/controls.js.map +1 -1
  112. package/lib/commonjs/features/playback/progress.js +2 -1
  113. package/lib/commonjs/features/playback/progress.js.map +1 -1
  114. package/lib/commonjs/features/playback/rate.js +8 -1
  115. package/lib/commonjs/features/playback/rate.js.map +1 -1
  116. package/lib/commonjs/features/playback/state.js +2 -3
  117. package/lib/commonjs/features/playback/state.js.map +1 -1
  118. package/lib/commonjs/features/player/options.js.map +1 -1
  119. package/lib/commonjs/features/player/setup.js +3 -1
  120. package/lib/commonjs/features/player/setup.js.map +1 -1
  121. package/lib/commonjs/features/player/validateOptions.js +2 -0
  122. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  123. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  124. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  125. package/lib/commonjs/features/queue/queue.js +11 -4
  126. package/lib/commonjs/features/queue/queue.js.map +1 -1
  127. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  128. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  129. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  130. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  131. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  132. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  133. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  134. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  135. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  136. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  137. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  138. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  139. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  140. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  141. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  142. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  143. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  144. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  145. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  146. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  147. package/lib/commonjs/web/browser/sections.js +89 -0
  148. package/lib/commonjs/web/browser/sections.js.map +1 -0
  149. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  150. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  151. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  152. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  153. package/lib/module/AudioBrowser.js +1 -0
  154. package/lib/module/AudioBrowser.js.map +1 -1
  155. package/lib/module/features/browser-config.js +11 -8
  156. package/lib/module/features/browser-config.js.map +1 -1
  157. package/lib/module/features/browser.js +2 -2
  158. package/lib/module/features/favorites.js +13 -10
  159. package/lib/module/features/favorites.js.map +1 -1
  160. package/lib/module/features/playback/controls.js +4 -2
  161. package/lib/module/features/playback/controls.js.map +1 -1
  162. package/lib/module/features/playback/progress.js +2 -1
  163. package/lib/module/features/playback/progress.js.map +1 -1
  164. package/lib/module/features/playback/rate.js +8 -1
  165. package/lib/module/features/playback/rate.js.map +1 -1
  166. package/lib/module/features/playback/state.js +2 -3
  167. package/lib/module/features/playback/state.js.map +1 -1
  168. package/lib/module/features/player/options.js.map +1 -1
  169. package/lib/module/features/player/setup.js +3 -1
  170. package/lib/module/features/player/setup.js.map +1 -1
  171. package/lib/module/features/player/validateOptions.js +2 -0
  172. package/lib/module/features/player/validateOptions.js.map +1 -1
  173. package/lib/module/features/queue/activeTrack.js +14 -3
  174. package/lib/module/features/queue/activeTrack.js.map +1 -1
  175. package/lib/module/features/queue/queue.js +11 -4
  176. package/lib/module/features/queue/queue.js.map +1 -1
  177. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  178. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  179. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  180. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  181. package/lib/module/utils/getTrackIdentity.js +20 -0
  182. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  183. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  184. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  185. package/lib/module/web/NativeAudioBrowser.js +32 -24
  186. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  187. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  188. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  189. package/lib/module/web/browser/BrowserManager.js +144 -87
  190. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  191. package/lib/module/web/browser/FavoriteManager.js +31 -21
  192. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  193. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  194. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  195. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  196. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  197. package/lib/module/web/browser/sections.js +83 -0
  198. package/lib/module/web/browser/sections.js.map +1 -0
  199. package/lib/module/web/player/NowPlayingManager.js +4 -3
  200. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  201. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  202. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  203. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  204. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  205. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  206. package/lib/typescript/src/features/browser.d.ts +2 -2
  207. package/lib/typescript/src/features/favorites.d.ts +13 -10
  208. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  209. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  210. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  211. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  212. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  213. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  214. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  215. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  216. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  217. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  218. package/lib/typescript/src/features/player/options.d.ts +21 -19
  219. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  220. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  221. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  222. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  223. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  224. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  225. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  226. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  227. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  228. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  229. package/lib/typescript/src/types/browser-nodes.d.ts +361 -126
  230. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  231. package/lib/typescript/src/types/browser.d.ts +33 -53
  232. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  233. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  234. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  235. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  236. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  237. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  238. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  239. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  240. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  241. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  242. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  243. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  244. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  245. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  246. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  247. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  248. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  249. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  250. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  251. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  252. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  253. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  254. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  255. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  256. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  257. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  258. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  259. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  260. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  261. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  262. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
  263. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  264. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  265. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  266. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
  267. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  268. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -7
  269. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  270. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  271. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  272. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  273. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  274. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
  275. package/nitrogen/generated/android/c++/JSection.hpp +120 -0
  276. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  277. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  278. package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
  279. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  280. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  281. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  282. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  283. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  284. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  285. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  286. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  287. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +18 -6
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  297. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
  298. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
  299. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
  300. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  301. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  302. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  303. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  304. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  305. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  306. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  307. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  308. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  309. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  310. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  311. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  312. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  313. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  314. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  315. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  316. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  317. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  318. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  319. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  320. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  321. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  322. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  323. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  324. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  325. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  326. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  327. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  328. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  329. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  330. package/package.json +7 -2
  331. package/src/AudioBrowser.ts +1 -0
  332. package/src/features/browser-config.test.ts +21 -6
  333. package/src/features/browser-config.ts +11 -8
  334. package/src/features/browser.test.ts +1 -1
  335. package/src/features/browser.ts +2 -2
  336. package/src/features/favorites.ts +13 -10
  337. package/src/features/hook-wiring.test.ts +63 -15
  338. package/src/features/playback/controls.ts +4 -2
  339. package/src/features/playback/progress.ts +2 -1
  340. package/src/features/playback/rate.ts +8 -1
  341. package/src/features/playback/state.ts +2 -3
  342. package/src/features/playback/trackPlaybackTime.ts +1 -1
  343. package/src/features/player/options.ts +27 -19
  344. package/src/features/player/setup.test.ts +12 -3
  345. package/src/features/player/setup.ts +3 -1
  346. package/src/features/player/validateOptions.ts +2 -0
  347. package/src/features/queue/activeTrack.ts +14 -3
  348. package/src/features/queue/queue.ts +11 -4
  349. package/src/nitro-stub-drift.test.ts +40 -0
  350. package/src/public-surface.test.ts +40 -0
  351. package/src/specs/audio-browser.nitro.ts +4 -3
  352. package/src/style-resolution.test.ts +210 -0
  353. package/src/types/browser-native.ts +2 -2
  354. package/src/types/browser-nodes.ts +377 -135
  355. package/src/types/browser.ts +34 -55
  356. package/src/utils/LazyNativeEmitter.ts +20 -10
  357. package/src/utils/NativeUpdatedValue.ts +29 -13
  358. package/src/utils/getTrackIdentity.ts +20 -0
  359. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  360. package/src/web/NativeAudioBrowser.ts +37 -22
  361. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  362. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  363. package/src/web/browser/BrowserManager.ts +176 -95
  364. package/src/web/browser/FavoriteManager.test.ts +84 -0
  365. package/src/web/browser/FavoriteManager.ts +28 -23
  366. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  367. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  368. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  369. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  370. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  371. package/src/web/browser/sections.test.ts +91 -0
  372. package/src/web/browser/sections.ts +72 -0
  373. package/src/web/player/NowPlayingManager.ts +4 -3
  374. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  375. package/src/web/util/BrowserPathHelper.ts +61 -13
  376. package/website/guide/android-auto.md +114 -0
  377. package/website/guide/android-certificates.md +409 -0
  378. package/website/guide/artwork.md +255 -0
  379. package/website/guide/audio-output.md +135 -0
  380. package/website/guide/automotive.md +86 -0
  381. package/website/guide/basic-usage.md +151 -0
  382. package/website/guide/battery.md +115 -0
  383. package/website/guide/browser.md +581 -0
  384. package/website/guide/carplay.md +620 -0
  385. package/website/guide/configuration.md +173 -0
  386. package/website/guide/equalizer.md +231 -0
  387. package/website/guide/errors.md +310 -0
  388. package/website/guide/favorites.md +154 -0
  389. package/website/guide/gate.md +143 -0
  390. package/website/guide/getting-started.md +46 -0
  391. package/website/guide/hooks.md +238 -0
  392. package/website/guide/live-streams.md +125 -0
  393. package/website/guide/metadata.md +239 -0
  394. package/website/guide/migrating-from-track-player.md +244 -0
  395. package/website/guide/native-callback-fetch.md +127 -0
  396. package/website/guide/network.md +86 -0
  397. package/website/guide/now-playing.md +175 -0
  398. package/website/guide/playback.md +328 -0
  399. package/website/guide/queue.md +300 -0
  400. package/website/guide/remote-controls.md +256 -0
  401. package/website/guide/search.md +255 -0
  402. package/website/guide/sleep-timer.md +268 -0
  403. package/website/guide/track.md +246 -0
  404. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  405. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  406. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  407. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  408. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  409. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  410. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  411. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  412. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  413. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  414. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  415. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  416. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -8,14 +8,19 @@ import java.net.URLEncoder
8
8
  * Utility for handling browser paths and contextual URLs in the media browser system.
9
9
  *
10
10
  * Handles two types of special paths:
11
- * 1. System paths (prefixed with `/__`): Root, recent, and search paths
11
+ * 1. System paths (prefixed with `/__`): root, search, offline, error, and gate paths
12
12
  * 2. Contextual URLs: Embed parent context in track identifiers for Media3 integration
13
13
  *
14
- * Contextual URL format: `{parentPath}?__trackId={trackSrc}` Example:
15
- * "/library/radio?__trackId=song.mp3"
14
+ * Contextual URL format: `{parentPath}?__trackId={trackIdentity}&__index={childIndex}` (the
15
+ * identity is the track's `id` when non-blank, else its `src`) Example:
16
+ * "/library/radio?__trackId=song.mp3&__index=2"
17
+ *
18
+ * `__trackId` is the identity check; `__index` (the child's position on the page at stamp time) is
19
+ * only a tie-breaker between surfaces that carry the same identity — a stale index never selects a
20
+ * different track.
16
21
  *
17
22
  * This allows:
18
- * - Media3 to reference playable-only tracks (tracks with `src` but no `url`)
23
+ * - Media3 to reference playable-only tracks (tracks with `src` but no `path`)
19
24
  * - Cache lookup to work consistently
20
25
  * - Parent context to be preserved for queue restoration
21
26
  */
@@ -23,9 +28,6 @@ object BrowserPathHelper {
23
28
  /** Root path for media browsing */
24
29
  const val ROOT_PATH = "/__root"
25
30
 
26
- /** Recent media path for playback resumption */
27
- const val RECENT_PATH = "/__recent"
28
-
29
31
  /** Search path prefix (full path is /__search?q=query) */
30
32
  const val SEARCH_PATH_PREFIX = "/__search"
31
33
 
@@ -41,9 +43,12 @@ object BrowserPathHelper {
41
43
  // Query parameter name for contextual track identifiers
42
44
  private const val CONTEXTUAL_TRACK_PARAM = "__trackId"
43
45
 
46
+ // Query parameter name for the tapped child's page position (tie-breaker)
47
+ private const val CONTEXTUAL_INDEX_PARAM = "__index"
48
+
44
49
  /** Check if a path is a special system path (not a regular navigation path) */
45
50
  fun isSpecialPath(path: String): Boolean {
46
- return path == ROOT_PATH || path == RECENT_PATH || path.startsWith("$SEARCH_PATH_PREFIX?")
51
+ return path == ROOT_PATH || path.startsWith("$SEARCH_PATH_PREFIX?")
47
52
  }
48
53
 
49
54
  /** Create a search path for a given query */
@@ -63,13 +68,13 @@ object BrowserPathHelper {
63
68
  }
64
69
 
65
70
  /**
66
- * Strips the __trackId parameter from a contextual URL to get the parent path. If the URL is not
67
- * contextual, returns it unchanged.
71
+ * Strips the contextual parameters (__trackId and __index) from a contextual URL to get the
72
+ * parent path. If the URL is not contextual, returns it unchanged.
68
73
  *
69
74
  * @param url The URL to process
70
- * @return The URL without the __trackId parameter
75
+ * @return The URL without the contextual parameters
71
76
  *
72
- * Example: "/library/radio?__trackId=song.mp3" → "/library/radio" Example:
77
+ * Example: "/library/radio?__trackId=song.mp3&__index=2" → "/library/radio" Example:
73
78
  * "/search?q=jazz&__trackId=song.mp3" → "/search?q=jazz"
74
79
  */
75
80
  fun stripTrackId(url: String): String {
@@ -79,13 +84,13 @@ object BrowserPathHelper {
79
84
 
80
85
  val uri = url.toUri()
81
86
 
82
- // Build URL preserving everything except __trackId parameter
87
+ // Build URL preserving everything except the contextual parameters
83
88
  val builder = uri.buildUpon()
84
89
  builder.clearQuery()
85
90
 
86
- // Re-add all query params except __trackId
91
+ // Re-add all query params except __trackId and __index
87
92
  uri.queryParameterNames.forEach { paramName ->
88
- if (paramName != CONTEXTUAL_TRACK_PARAM) {
93
+ if (paramName != CONTEXTUAL_TRACK_PARAM && paramName != CONTEXTUAL_INDEX_PARAM) {
89
94
  uri.getQueryParameters(paramName).forEach { value ->
90
95
  builder.appendQueryParameter(paramName, value)
91
96
  }
@@ -96,19 +101,21 @@ object BrowserPathHelper {
96
101
  }
97
102
 
98
103
  /**
99
- * Builds a contextual URL by appending a track identifier to a parent path. Handles existing
100
- * query parameters correctly.
104
+ * Builds a contextual URL by appending a track identifier — and optionally the tapped child's
105
+ * page position — to a parent path. Handles existing query parameters correctly.
101
106
  *
102
107
  * @param parentPath The parent container path
103
- * @param trackId The track identifier (typically the `src` value)
104
- * @return A contextual URL combining parent path and track ID
108
+ * @param trackId The track identity (`id` when non-blank, else `src` — see Track.identity)
109
+ * @param index The child's position on the page at stamp time (tie-breaker)
110
+ * @return A contextual URL combining parent path, track ID, and index
105
111
  *
106
- * Example: build("/library", "song.mp3") → "/library?__trackId=song.mp3" Example:
112
+ * Example: build("/library", "song.mp3", 2) → "/library?__trackId=song.mp3&__index=2" Example:
107
113
  * build("/search?q=jazz", "song.mp3") → "/search?q=jazz&__trackId=song.mp3"
108
114
  */
109
- fun build(parentPath: String, trackId: String): String {
115
+ fun build(parentPath: String, trackId: String, index: Int? = null): String {
110
116
  val separator = if (parentPath.contains('?')) '&' else '?'
111
- return "$parentPath$separator$CONTEXTUAL_TRACK_PARAM=${Uri.encode(trackId)}"
117
+ val indexParam = if (index != null) "&$CONTEXTUAL_INDEX_PARAM=$index" else ""
118
+ return "$parentPath$separator$CONTEXTUAL_TRACK_PARAM=${Uri.encode(trackId)}$indexParam"
112
119
  }
113
120
 
114
121
  /**
@@ -129,6 +136,21 @@ object BrowserPathHelper {
129
136
  return uri.getQueryParameter(CONTEXTUAL_TRACK_PARAM)
130
137
  }
131
138
 
139
+ /**
140
+ * Extracts the stamped page index from a contextual URL, or null when the URL is not contextual
141
+ * or carries no (valid, non-negative) index.
142
+ *
143
+ * Example: "/library/radio?__trackId=song.mp3&__index=2" → 2
144
+ */
145
+ fun extractIndex(path: String): Int? {
146
+ if (!isContextual(path)) {
147
+ return null
148
+ }
149
+
150
+ val raw = Uri.parse(path).getQueryParameter(CONTEXTUAL_INDEX_PARAM) ?: return null
151
+ return raw.toIntOrNull()?.takeIf { it >= 0 }
152
+ }
153
+
132
154
  /**
133
155
  * Combines a base URL with a path, ensuring proper slash handling.
134
156
  *
@@ -158,22 +180,4 @@ object BrowserPathHelper {
158
180
  val normalizedPath = path.trimStart('/')
159
181
  return "$normalizedBase$normalizedPath"
160
182
  }
161
-
162
- /**
163
- * True if [segment] appears in [path] as a complete path segment — bounded on the left by the
164
- * string start or `/`, and on the right by the string end or one of `/`, `?`, `#`.
165
- */
166
- fun containsSegment(path: String, segment: String): Boolean {
167
- if (segment.isEmpty()) return false
168
- var from = 0
169
- while (true) {
170
- val idx = path.indexOf(segment, from)
171
- if (idx < 0) return false
172
- val beforeOk = idx == 0 || path[idx - 1] == '/'
173
- val end = idx + segment.length
174
- val afterOk = end == path.length || path[end] == '/' || path[end] == '?' || path[end] == '#'
175
- if (beforeOk && afterOk) return true
176
- from = idx + 1
177
- }
178
- }
179
183
  }
@@ -2,30 +2,31 @@ package com.audiobrowser.util
2
2
 
3
3
  import android.os.Bundle
4
4
  import androidx.media.utils.MediaConstants
5
- import com.margelo.nitro.audiobrowser.ResolvedTrack
5
+ import com.margelo.nitro.audiobrowser.Section
6
+ import com.margelo.nitro.audiobrowser.StyleDisplay
6
7
  import com.margelo.nitro.audiobrowser.Track
7
- import com.margelo.nitro.audiobrowser.TrackStyle
8
8
 
9
9
  /** Builds MediaMetadata extras bundle for Android Auto/AAOS content styling. */
10
10
  object MediaExtrasBuilder {
11
11
 
12
12
  /**
13
- * Maps TrackStyle to MediaConstants content style value. When artwork is an android.resource://
14
- * URI, uses CATEGORY_* variants which add margins around icons (better for small vector icons).
13
+ * Maps a display mode to the MediaConstants content style value. When artwork is an
14
+ * android.resource:// URI, uses CATEGORY_* variants which add margins around icons (better for
15
+ * small vector icons).
15
16
  *
16
17
  * @see <a href="https://developer.android.com/training/cars/media#default-content-style">Default
17
18
  * content style</a>
18
19
  */
19
- private fun TrackStyle.toContentStyleValue(artwork: String?): Int {
20
+ private fun StyleDisplay.toContentStyleValue(artwork: String?): Int {
20
21
  val isAndroidResource = artwork?.startsWith("android.resource://") == true
21
22
  return when (this) {
22
- TrackStyle.LIST ->
23
+ StyleDisplay.LIST ->
23
24
  if (isAndroidResource) {
24
25
  MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
25
26
  } else {
26
27
  MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
27
28
  }
28
- TrackStyle.GRID ->
29
+ StyleDisplay.GRID ->
29
30
  if (isAndroidResource) {
30
31
  MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
31
32
  } else {
@@ -34,39 +35,43 @@ object MediaExtrasBuilder {
34
35
  }
35
36
  }
36
37
 
37
- fun build(track: Track): Bundle =
38
+ /**
39
+ * Extras for a track rendered on a browse surface. The owning [section] supplies the Android Auto
40
+ * group-title header hint (grouping is a per-item advisory in the flat MediaBrowser protocol —
41
+ * the section dies here, ADR 0010) and, from its page-folded style block, the per-item display
42
+ * hint: per-item hints derive from the section resolution, never from the track's own `display`,
43
+ * which is positional (ADR 0011).
44
+ *
45
+ * The track's own declared `display` is the page-layout *promise* for the page a browsable track
46
+ * opens — emitted as the parent-level BROWSABLE/PLAYABLE hint, the only below-root hint
47
+ * AOSP-derived AAOS media UIs honor. Declared or it doesn't exist: the library never derives a
48
+ * promise from pages it hasn't resolved, and never back-fills from ones it has.
49
+ */
50
+ fun build(track: Track, section: Section? = null): Bundle =
38
51
  build(
39
- groupTitle = track.groupTitle,
40
- style = track.style,
41
- childrenStyle = track.childrenStyle,
52
+ groupTitle = section?.title,
53
+ itemDisplay = section?.style?.display,
54
+ promisedDisplay = if (track.src == null) track.style?.display else null,
42
55
  artwork = track.artwork?.url,
43
56
  )
44
57
 
45
- fun build(resolvedTrack: ResolvedTrack): Bundle =
46
- build(
47
- groupTitle = resolvedTrack.groupTitle,
48
- style = resolvedTrack.style,
49
- childrenStyle = resolvedTrack.childrenStyle,
50
- artwork = resolvedTrack.artwork?.url,
51
- )
52
-
53
58
  private fun build(
54
59
  groupTitle: String?,
55
- style: TrackStyle?,
56
- childrenStyle: TrackStyle?,
60
+ itemDisplay: StyleDisplay?,
61
+ promisedDisplay: StyleDisplay?,
57
62
  artwork: String?,
58
63
  ): Bundle =
59
64
  Bundle().apply {
60
65
  groupTitle?.let {
61
66
  putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, it)
62
67
  }
63
- style?.let {
68
+ itemDisplay?.let {
64
69
  putInt(
65
70
  MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
66
71
  it.toContentStyleValue(artwork),
67
72
  )
68
73
  }
69
- childrenStyle?.let {
74
+ promisedDisplay?.let {
70
75
  val styleValue = it.toContentStyleValue(artwork)
71
76
  putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, styleValue)
72
77
  putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, styleValue)
@@ -0,0 +1,108 @@
1
+ package com.audiobrowser.util
2
+
3
+ import com.margelo.nitro.audiobrowser.ArtworkRendering
4
+ import com.margelo.nitro.audiobrowser.CardImage
5
+ import com.margelo.nitro.audiobrowser.GridTile
6
+ import com.margelo.nitro.audiobrowser.ImageShape
7
+ import com.margelo.nitro.audiobrowser.SectionStyle
8
+ import com.margelo.nitro.audiobrowser.StyleDisplay
9
+ import com.margelo.nitro.audiobrowser.TrackStyle
10
+
11
+ /**
12
+ * The all-nil blocks a decode collapses to "no declaration". Every field is listed exactly once
13
+ * here — the decoders and the persistence restore compare against these instead of re-enumerating
14
+ * the fields in each guard (a constructor gaining a field breaks this file, not silently a guard).
15
+ */
16
+ internal val EMPTY_TRACK_STYLE =
17
+ TrackStyle(
18
+ display = null,
19
+ artworkRendering = null,
20
+ imageShape = null,
21
+ accessorySymbol = null,
22
+ cardTint = null,
23
+ cardImage = null,
24
+ )
25
+
26
+ internal val EMPTY_SECTION_STYLE =
27
+ SectionStyle(
28
+ display = null,
29
+ artworkRendering = null,
30
+ imageShape = null,
31
+ accessorySymbol = null,
32
+ cardTint = null,
33
+ cardImage = null,
34
+ gridWrap = null,
35
+ gridTile = null,
36
+ )
37
+
38
+ /**
39
+ * The style block's wire strings (ADR 0011) ↔ Nitro enums, shared by the browse decoder and the
40
+ * playback-state store. Inbound mapping is lenient: an unknown or empty value is "no declaration",
41
+ * never an error.
42
+ */
43
+ internal fun StyleDisplay.toWireString(): String =
44
+ when (this) {
45
+ StyleDisplay.LIST -> "list"
46
+ StyleDisplay.GRID -> "grid"
47
+ }
48
+
49
+ internal fun ArtworkRendering.toWireString(): String =
50
+ when (this) {
51
+ ArtworkRendering.ORIGINAL -> "original"
52
+ ArtworkRendering.STENCIL -> "stencil"
53
+ }
54
+
55
+ internal fun String?.toStyleDisplay(): StyleDisplay? =
56
+ when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
57
+ "list" -> StyleDisplay.LIST
58
+ "grid" -> StyleDisplay.GRID
59
+ else -> null
60
+ }
61
+
62
+ internal fun String?.toArtworkRendering(): ArtworkRendering? =
63
+ when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
64
+ "original" -> ArtworkRendering.ORIGINAL
65
+ "stencil" -> ArtworkRendering.STENCIL
66
+ else -> null
67
+ }
68
+
69
+ internal fun ImageShape.toWireString(): String =
70
+ when (this) {
71
+ ImageShape.CIRCULAR -> "circular"
72
+ ImageShape.ROUNDED_RECTANGLE -> "rounded-rectangle"
73
+ }
74
+
75
+ internal fun String?.toImageShape(): ImageShape? =
76
+ when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
77
+ "circular" -> ImageShape.CIRCULAR
78
+ "rounded-rectangle" -> ImageShape.ROUNDED_RECTANGLE
79
+ else -> null
80
+ }
81
+
82
+ internal fun GridTile.toWireString(): String =
83
+ when (this) {
84
+ GridTile.PLAIN -> "plain"
85
+ GridTile.CARD -> "card"
86
+ GridTile.CONDENSED -> "condensed"
87
+ }
88
+
89
+ internal fun String?.toGridTile(): GridTile? =
90
+ when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
91
+ "plain" -> GridTile.PLAIN
92
+ "card" -> GridTile.CARD
93
+ "condensed" -> GridTile.CONDENSED
94
+ else -> null
95
+ }
96
+
97
+ internal fun CardImage.toWireString(): String =
98
+ when (this) {
99
+ CardImage.NORMAL -> "normal"
100
+ CardImage.BACKGROUND -> "background"
101
+ }
102
+
103
+ internal fun String?.toCardImage(): CardImage? =
104
+ when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
105
+ "normal" -> CardImage.NORMAL
106
+ "background" -> CardImage.BACKGROUND
107
+ else -> null
108
+ }
@@ -7,17 +7,16 @@ import androidx.media3.common.MediaItem
7
7
  import androidx.media3.common.MediaMetadata
8
8
  import com.audiobrowser.browser.BrowseArtworkRegistry
9
9
  import com.audiobrowser.browser.ResolvedArtwork
10
- import com.margelo.nitro.audiobrowser.ImageRowItem
10
+ import com.margelo.nitro.audiobrowser.Section
11
11
  import com.margelo.nitro.audiobrowser.Track
12
12
  import com.margelo.nitro.audiobrowser.TrackStyle
13
- import com.margelo.nitro.audiobrowser.Variant_String_ArtworkVariants
14
13
 
15
14
  /**
16
15
  * The single Track → Media3 [MediaItem] conversion. Owns the two easy-to-drift fallbacks: the
17
16
  * displayed artwork is the transformed `artworkSource.uri` falling back to the raw `artwork` field,
18
- * and the mediaId is the stable `id` for playable tracks, falling back to `url` then `src` (a Track
19
- * must have one of the latter two — see the `src` vs `url` note in CONTEXT.md). The list line
20
- * renders from `subtitle` (the now-playing line is re-stamped from `artist` by the now-playing
17
+ * and the mediaId is the stable `id` for playable tracks, falling back to `path` then `src` (a
18
+ * Track must have one of the latter two — see the `src` vs `path` note in CONTEXT.md). The list
19
+ * line renders from `subtitle` (the now-playing line is re-stamped from `artist` by the now-playing
21
20
  * pipeline; see the Now Playing Metadata guide).
22
21
  */
23
22
  object TrackFactory {
@@ -26,49 +25,42 @@ object TrackFactory {
26
25
  }
27
26
 
28
27
  /**
29
- * Android Auto has no image-row rendering; its closest equivalent is a grid of artwork tiles. A
30
- * track carrying `imageRow` expands into its items as grid-styled rows (the per-item
31
- * content-style hint — hosts that ignore it fall back to list rows) grouped under the row's
32
- * title, followed by the row itself as a browsable "view all" link when it has a `url` (a
33
- * url-less row is a pure preview and contributes only its items). Tracks without an `imageRow`
34
- * pass through unchanged.
28
+ * A synthetic browsable Track for a section's "view all" surface — the section has a path, title,
29
+ * and label, but no Track (ADR 0010). It carries the section's declared item block as its own,
30
+ * `display` included: the section's declaration about this content's layout is the only declared
31
+ * promise the "view all" page can ever have (there is no consumer-authored handle to declare one
32
+ * on), and without it Android Auto would always render that page as a list.
35
33
  */
36
- fun expandImageRows(tracks: List<Track>): List<Track> =
37
- tracks.flatMap { track ->
38
- val items = track.imageRow
39
- if (items.isNullOrEmpty()) return@flatMap listOf(track)
40
- val expanded = items.map { it.toTrack(groupTitle = track.title) }
41
- if (track.url != null) {
42
- expanded + track.copy(imageRow = null, groupTitle = track.title)
43
- } else {
44
- expanded
45
- }
46
- }
47
-
48
- /** The row-item equivalent of a full Track, for surfaces that render items as plain rows. */
49
- fun ImageRowItem.toTrack(groupTitle: String?): Track =
34
+ fun navigationTrack(section: Section): Track =
50
35
  Track(
51
- id = id,
52
- url = url,
53
- src = src,
54
- artwork = artwork?.let { Variant_String_ArtworkVariants.First(it) },
55
- artworkSource = artworkSource,
56
- request = request,
57
- artworkCarPlayTinted = null,
58
- title = title,
59
- subtitle = null,
60
- artist = artist,
61
- albumUrl = albumUrl,
62
- album = album,
36
+ id = null,
37
+ path = section.path,
38
+ src = null,
39
+ artwork = null,
40
+ artworkSource = null,
41
+ request = null,
42
+ title = section.title ?: "",
43
+ subtitle = section.subtitle,
44
+ artist = null,
45
+ albumPath = null,
46
+ album = null,
63
47
  description = null,
64
48
  genre = null,
65
49
  duration = null,
66
- style = TrackStyle.GRID,
67
- childrenStyle = null,
50
+ style =
51
+ section.style?.let {
52
+ TrackStyle(
53
+ display = it.display,
54
+ artworkRendering = it.artworkRendering,
55
+ imageShape = it.imageShape,
56
+ accessorySymbol = it.accessorySymbol,
57
+ cardTint = it.cardTint,
58
+ cardImage = it.cardImage,
59
+ )
60
+ },
61
+ disabled = null,
68
62
  favorited = null,
69
- groupTitle = groupTitle,
70
- live = live,
71
- imageRow = null,
63
+ live = null,
72
64
  )
73
65
 
74
66
  fun toMedia3(tracks: Array<Track>): List<MediaItem> {
@@ -90,10 +82,11 @@ object TrackFactory {
90
82
  track: Track,
91
83
  registry: BrowseArtworkRegistry,
92
84
  authority: String,
85
+ section: Section? = null,
93
86
  ): MediaItem {
94
87
  val rawUrl = artworkUri(track) // artworkSource.uri ?: artwork
95
88
  val scheme = rawUrl?.let { Uri.parse(it).scheme?.lowercase() }
96
- val builder = metadataBuilder(track)
89
+ val builder = metadataBuilder(track, section)
97
90
  if (rawUrl != null && (scheme == "http" || scheme == "https")) {
98
91
  val isSvg =
99
92
  SvgArtworkRenderer.isSvgUrl(rawUrl) || SvgArtworkRenderer.isSvgUrl(track.artwork?.url)
@@ -109,8 +102,11 @@ object TrackFactory {
109
102
  /** The transformed artworkSource wins over the raw artwork field. */
110
103
  private fun artworkUri(track: Track): String? = track.artworkSource?.uri ?: track.artwork?.url
111
104
 
112
- /** All metadata except artwork. */
113
- private fun metadataBuilder(track: Track): MediaMetadata.Builder =
105
+ /**
106
+ * All metadata except artwork. The owning [section] (browse surfaces only) supplies the
107
+ * group-title header hint and the per-item style default (ADR 0010).
108
+ */
109
+ private fun metadataBuilder(track: Track, section: Section? = null): MediaMetadata.Builder =
114
110
  MediaMetadata.Builder()
115
111
  .setTitle(track.title)
116
112
  .setArtist(track.subtitle)
@@ -119,26 +115,29 @@ object TrackFactory {
119
115
  .setGenre(track.genre)
120
116
  .setIsBrowsable(track.src == null)
121
117
  .setIsPlayable(track.src != null)
122
- .setExtras(MediaExtrasBuilder.build(track))
118
+ .setExtras(MediaExtrasBuilder.build(track, section))
123
119
  .apply { track.favorited?.let { setUserRating(HeartRating(it)) } }
124
120
 
125
121
  private fun buildMediaItem(track: Track, metadata: MediaMetadata): MediaItem {
126
- // A playable track's mediaId is the consumer's stable `id` when it has one.
127
- // Android Auto marks the "now playing" browse row by exact mediaId equality
128
- // between the row and the player's current item (media3 announces
129
- // `currentMediaItem.mediaId` in the legacy playback state), and a
122
+ // A playable track's mediaId is the consumer's stable `id` when it has one —
123
+ // the identity rule (id when non-blank, else src; see Track.identity /
124
+ // ADR 0008), except that an id-less playable track prefers its contextual
125
+ // `path` over `src` here, so this is deliberately NOT expressed via
126
+ // Track.identity. Android Auto marks the "now playing" browse row by exact
127
+ // mediaId equality between the row and the player's current item (media3
128
+ // announces `currentMediaItem.mediaId` in the legacy playback state), and a
130
129
  // consumer-loaded track's `src` can differ textually from the browse row's
131
130
  // for the same item (absolute vs relative, extra query params) — the id is
132
131
  // the one string both sides share. It is also context-free, so the same
133
132
  // station gets the same mediaId in every tab. Browsable-only tracks keep
134
- // `url` so navigation parentIds stay resolvable paths.
133
+ // `path` so navigation parentIds stay resolvable paths.
135
134
  val stableId = if (track.src != null) track.id?.takeUnless { it.isBlank() } else null
136
135
  val mediaId =
137
136
  stableId
138
- ?: track.url
137
+ ?: track.path
139
138
  ?: track.src
140
139
  ?: throw IllegalArgumentException(
141
- "Track must have either url or src defined. Track: title='${track.title}', artist='${track.artist}'"
140
+ "Track must have either path or src defined. Track: title='${track.title}', artist='${track.artist}'"
142
141
  )
143
142
  return MediaItem.Builder()
144
143
  .setMediaId(mediaId)