react-native-audio-browser 0.1.1 → 0.2.0-next.10

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 (1000) hide show
  1. package/AudioBrowser.podspec +1 -1
  2. package/README.md +184 -2
  3. package/android/build.gradle +17 -5
  4. package/android/gradle.properties +2 -2
  5. package/android/src/main/AndroidManifest.xml +18 -1
  6. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +473 -158
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  8. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  9. package/android/src/main/java/com/audiobrowser/Callbacks.kt +4 -6
  10. package/android/src/main/java/com/audiobrowser/Service.kt +67 -18
  11. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  12. package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +37 -0
  13. package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +34 -0
  14. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +442 -178
  15. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +299 -0
  16. package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +79 -0
  17. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +41 -4
  18. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +42 -0
  19. package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
  20. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +35 -0
  21. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  22. package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +109 -97
  23. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +6 -10
  24. package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +40 -8
  25. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +40 -46
  26. package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +4 -1
  27. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +172 -0
  28. package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +18 -1
  29. package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +153 -0
  30. package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +141 -0
  31. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +15 -3
  32. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +422 -134
  33. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +248 -404
  34. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +347 -0
  35. package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +129 -0
  36. package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +85 -0
  37. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +25 -5
  38. package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +56 -0
  39. package/android/src/main/java/com/audiobrowser/player/Player.kt +475 -577
  40. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +20 -0
  41. package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +130 -0
  42. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +190 -67
  43. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +118 -53
  44. package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +37 -3
  45. package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +80 -0
  46. package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +50 -0
  47. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +85 -40
  48. package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +81 -0
  49. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +156 -0
  50. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +109 -0
  51. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +37 -0
  52. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +55 -0
  53. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +265 -0
  54. package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +161 -0
  55. package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +28 -0
  56. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +24 -0
  57. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +24 -0
  58. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +49 -0
  59. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +36 -0
  60. package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +23 -300
  61. package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +13 -23
  62. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
  63. package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +4 -3
  64. package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +211 -0
  65. package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +58 -0
  66. package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +2 -1
  67. package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +14 -0
  68. package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +6 -111
  69. package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +22 -8
  70. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +112 -80
  71. package/android/src/main/res/values/strings.xml +4 -0
  72. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  73. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +165 -0
  74. package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +46 -0
  75. package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +39 -0
  76. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +114 -0
  77. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +141 -0
  78. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +4 -0
  79. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +177 -0
  80. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +55 -0
  81. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +94 -0
  82. package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +1 -2
  83. package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +72 -0
  84. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +203 -0
  85. package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +56 -0
  86. package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +216 -0
  87. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  88. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +442 -0
  89. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +190 -0
  90. package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +198 -0
  91. package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +137 -0
  92. package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +36 -0
  93. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +268 -0
  94. package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +68 -0
  95. package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +40 -0
  96. package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +193 -0
  97. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +153 -0
  98. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +186 -0
  99. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +158 -0
  100. package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +18 -0
  101. package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +163 -0
  102. package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +34 -0
  103. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +28 -0
  104. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +53 -0
  105. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +58 -0
  106. package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +57 -0
  107. package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +40 -0
  108. package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +34 -0
  109. package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +50 -0
  110. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +63 -0
  111. package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +35 -0
  112. package/android/src/test/resources/certs/isrg-root-x1.pem +31 -0
  113. package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
  114. package/android/src/test/resources/certs/letsencrypt-r13.pem +29 -0
  115. package/android/src/test/resources/certs/stationplaylist-leaf.pem +34 -0
  116. package/ios/Browser/BrowserConfig.swift +41 -11
  117. package/ios/Browser/BrowserManager+URLResolution.swift +213 -93
  118. package/ios/Browser/BrowserManager.swift +303 -135
  119. package/ios/Browser/BrowserPathHelper.swift +32 -3
  120. package/ios/Browser/JsonModels.swift +287 -66
  121. package/ios/Browser/MediaResolveComposer.swift +114 -0
  122. package/ios/Browser/SearchDrillIn.swift +44 -0
  123. package/ios/Browser/SectionScope.swift +35 -0
  124. package/ios/Browser/TabBarEntries.swift +15 -0
  125. package/ios/CarPlay/CPInterfaceController+Safe.swift +126 -0
  126. package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
  127. package/ios/CarPlay/CarPlayController.swift +739 -149
  128. package/ios/CarPlay/CarPlayImageLoader.swift +57 -8
  129. package/ios/CarPlay/CarPlayListItemFactory.swift +44 -35
  130. package/ios/CarPlay/CarPlayNowPlayingManager.swift +180 -36
  131. package/ios/CarPlay/ImageRowItem+CarPlay.swift +19 -0
  132. package/ios/CarPlay/RNABAudioBrowser.h +15 -10
  133. package/ios/CarPlay/RNABAudioBrowser.m +15 -14
  134. package/ios/CarPlay/RNABCarPlaySceneDelegate.m +2 -0
  135. package/ios/CarPlay/RNABMediaAddHandler.swift +75 -0
  136. package/ios/CarPlay/RNABMediaAffinityHandler.swift +69 -0
  137. package/ios/CarPlay/RNABMediaIntentHandler.swift +53 -43
  138. package/ios/Extension/Capability+RemoteCommand.swift +7 -9
  139. package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
  140. package/ios/Extension/NavigationError+Conversion.swift +8 -1
  141. package/ios/Extension/PlayerCapabilities+Favorite.swift +20 -0
  142. package/ios/Extension/RequestConfig+Copying.swift +31 -0
  143. package/ios/Extension/ResolvedTrack+Copying.swift +10 -4
  144. package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
  145. package/ios/Extension/Track+AVPlayer.swift +4 -4
  146. package/ios/Extension/Track+Copying.swift +9 -3
  147. package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
  148. package/ios/Extension/Variant+Artwork.swift +49 -0
  149. package/ios/HybridAudioBrowser.swift +805 -197
  150. package/ios/Model/NitroTypeStubs.swift +299 -224
  151. package/ios/Model/PlayerUpdateOptions.swift +23 -23
  152. package/ios/Model/RemoteCommand.swift +24 -6
  153. package/ios/Model/TrackPlayerError.swift +82 -18
  154. package/ios/NowPlayingInfo/NowPlayingInfoController.swift +99 -121
  155. package/ios/NowPlayingInfo/SilentAudioPrimer.swift +73 -0
  156. package/ios/Observer/PlayerItemNotificationObserver.swift +45 -5
  157. package/ios/Observer/PlayerItemPropertyObserver.swift +27 -4
  158. package/ios/Observer/PlayerTimeObserver.swift +3 -53
  159. package/ios/Option/SessionCategories.swift +34 -11
  160. package/ios/Player/AirPlayStallJudgement.swift +25 -0
  161. package/ios/Player/EndOfTrackJudgement.swift +32 -0
  162. package/ios/Player/LoadSeekCoordinator.swift +16 -1
  163. package/ios/Player/MediaIntentCriteria.swift +139 -0
  164. package/ios/Player/MediaLoader.swift +31 -11
  165. package/ios/Player/NowPlayingUpdater.swift +119 -26
  166. package/ios/Player/PersistedPlaybackState.swift +11 -0
  167. package/ios/Player/PlaybackCoordinator.swift +285 -51
  168. package/ios/Player/PlaybackCoordinatorCallbacks.swift +6 -1
  169. package/ios/Player/PlaybackEffectHandler.swift +19 -9
  170. package/ios/Player/PlaybackErrorHandler.swift +98 -14
  171. package/ios/Player/PlaybackStateMachine.swift +18 -13
  172. package/ios/Player/PlaybackStateStore.swift +27 -0
  173. package/ios/Player/PlaybackTimer.swift +61 -0
  174. package/ios/Player/PlayingStateManager.swift +3 -2
  175. package/ios/Player/QueueManager.swift +77 -64
  176. package/ios/Player/RetryManager.swift +175 -38
  177. package/ios/Player/ShuffleOrder.swift +10 -0
  178. package/ios/Player/SleepTimerHandling.swift +8 -0
  179. package/ios/Player/SleepTimerManager.swift +72 -11
  180. package/ios/Player/VolumeFader.swift +77 -0
  181. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  182. package/ios/RemoteCommand/RemoteCommandController.swift +40 -21
  183. package/ios/Tests/AirPlayStallJudgementTests.swift +52 -0
  184. package/ios/Tests/BrowserPathHelperTests.swift +28 -0
  185. package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
  186. package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
  187. package/ios/Tests/EndOfTrackJudgementTests.swift +35 -0
  188. package/ios/Tests/JsonModelsTests.swift +58 -0
  189. package/ios/Tests/LRUCacheTests.swift +111 -0
  190. package/ios/Tests/LoadSeekCoordinatorTests.swift +40 -7
  191. package/ios/Tests/MediaIntentCriteriaTests.swift +170 -0
  192. package/ios/Tests/MediaLoaderAssetOptionsTests.swift +46 -0
  193. package/ios/Tests/MediaLoaderTests.swift +32 -20
  194. package/ios/Tests/MediaResolveTests.swift +126 -0
  195. package/ios/Tests/OnceValueTests.swift +79 -0
  196. package/ios/Tests/PlaybackCoordinatorTests.swift +787 -13
  197. package/ios/Tests/PlaybackErrorHandlerTests.swift +383 -47
  198. package/ios/Tests/PlaybackStateMachineTests.swift +33 -8
  199. package/ios/Tests/PlaybackStateStoreTests.swift +48 -0
  200. package/ios/Tests/PlaybackTimerTests.swift +55 -0
  201. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  202. package/ios/Tests/PlayingStateManagerTests.swift +57 -0
  203. package/ios/Tests/QueueManagerTests.swift +195 -18
  204. package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
  205. package/ios/Tests/RemoteCommandTests.swift +67 -0
  206. package/ios/Tests/ResolvedTrackCopyingTests.swift +15 -8
  207. package/ios/Tests/RetryManagerTests.swift +178 -0
  208. package/ios/Tests/SearchDrillInTests.swift +63 -0
  209. package/ios/Tests/SectionScopeTests.swift +82 -0
  210. package/ios/Tests/SleepTimerManagerTests.swift +107 -0
  211. package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
  212. package/ios/Tests/Support/TestTypes.swift +61 -20
  213. package/ios/Tests/TabBarEntriesTests.swift +71 -0
  214. package/ios/Tests/TrackSelectorTests.swift +15 -15
  215. package/ios/TrackPlayer.swift +385 -58
  216. package/ios/TrackPlayerCallbacks.swift +5 -44
  217. package/ios/TrackSelector.swift +5 -5
  218. package/ios/Util/ArtworkImageFetcher.swift +4 -2
  219. package/ios/Util/LRUCache.swift +4 -1
  220. package/ios/Util/NetworkMonitor.swift +7 -3
  221. package/ios/Util/OnceValue.swift +34 -0
  222. package/lib/commonjs/AudioBrowser.js +0 -11
  223. package/lib/commonjs/AudioBrowser.js.map +1 -1
  224. package/lib/commonjs/CLAUDE.md +52 -0
  225. package/lib/commonjs/features/browser-config.js +227 -0
  226. package/lib/commonjs/features/browser-config.js.map +1 -0
  227. package/lib/commonjs/features/browser.js +60 -159
  228. package/lib/commonjs/features/browser.js.map +1 -1
  229. package/lib/commonjs/features/carConnection.js +42 -0
  230. package/lib/commonjs/features/carConnection.js.map +1 -0
  231. package/lib/commonjs/features/equalizer.js +1 -1
  232. package/lib/commonjs/features/errors.js +37 -5
  233. package/lib/commonjs/features/errors.js.map +1 -1
  234. package/lib/commonjs/features/favorites.js +1 -1
  235. package/lib/commonjs/features/gate.js +164 -0
  236. package/lib/commonjs/features/gate.js.map +1 -0
  237. package/lib/commonjs/features/index.js +22 -0
  238. package/lib/commonjs/features/index.js.map +1 -1
  239. package/lib/commonjs/features/metadata.js +20 -3
  240. package/lib/commonjs/features/metadata.js.map +1 -1
  241. package/lib/commonjs/features/network.js +1 -1
  242. package/lib/commonjs/features/nowPlaying.js +36 -1
  243. package/lib/commonjs/features/nowPlaying.js.map +1 -1
  244. package/lib/commonjs/features/output.js +37 -23
  245. package/lib/commonjs/features/output.js.map +1 -1
  246. package/lib/commonjs/features/playback/controls.js +9 -0
  247. package/lib/commonjs/features/playback/controls.js.map +1 -1
  248. package/lib/commonjs/features/playback/index.js +11 -0
  249. package/lib/commonjs/features/playback/index.js.map +1 -1
  250. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  251. package/lib/commonjs/features/playback/playing.js +1 -1
  252. package/lib/commonjs/features/playback/progress.js +29 -13
  253. package/lib/commonjs/features/playback/progress.js.map +1 -1
  254. package/lib/commonjs/features/playback/state.js +1 -1
  255. package/lib/commonjs/features/playback/trackPlaybackTime.js +86 -0
  256. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -0
  257. package/lib/commonjs/features/playback/volume.js +2 -2
  258. package/lib/commonjs/features/player/options.js +79 -29
  259. package/lib/commonjs/features/player/options.js.map +1 -1
  260. package/lib/commonjs/features/player/setup.js +256 -3
  261. package/lib/commonjs/features/player/setup.js.map +1 -1
  262. package/lib/commonjs/features/player/validateOptions.js +21 -0
  263. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  264. package/lib/commonjs/features/queue/activeTrack.js +1 -1
  265. package/lib/commonjs/features/queue/queue.js +12 -5
  266. package/lib/commonjs/features/queue/queue.js.map +1 -1
  267. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  268. package/lib/commonjs/features/queue/shuffle.js +1 -1
  269. package/lib/commonjs/features/remoteControls.js +14 -104
  270. package/lib/commonjs/features/remoteControls.js.map +1 -1
  271. package/lib/commonjs/features/sleepTimer.js +22 -5
  272. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  273. package/lib/commonjs/native.js +5 -0
  274. package/lib/commonjs/native.js.map +1 -1
  275. package/lib/commonjs/native.web.js +4 -1
  276. package/lib/commonjs/native.web.js.map +1 -1
  277. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  278. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  279. package/lib/commonjs/utils/LazyNativeEmitter.js +8 -8
  280. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  281. package/lib/commonjs/utils/NativeUpdatedValue.js +0 -3
  282. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  283. package/lib/commonjs/utils/artwork.js +31 -0
  284. package/lib/commonjs/utils/artwork.js.map +1 -0
  285. package/lib/commonjs/web/NativeAudioBrowser.js +302 -125
  286. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  287. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +71 -0
  288. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  289. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +34 -0
  290. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  291. package/lib/commonjs/web/TrackPlayer/Player.js +90 -26
  292. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  293. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +21 -0
  294. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  295. package/lib/commonjs/web/TrackPlayer/QueueManager.js +273 -0
  296. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -0
  297. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +158 -0
  298. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -0
  299. package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
  300. package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
  301. package/lib/commonjs/web/TrackPlayer/SleepTimer.js +34 -3
  302. package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
  303. package/lib/commonjs/web/TrackPlayer/VolumeFader.js +75 -0
  304. package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +1 -0
  305. package/lib/commonjs/web/TrackPlayer/index.js +13 -5
  306. package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
  307. package/lib/commonjs/web/browser/BrowserManager.js +176 -50
  308. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  309. package/lib/commonjs/web/browser/NavigationErrorManager.js +3 -1
  310. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  311. package/lib/commonjs/web/browser/SearchManager.js +11 -9
  312. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  313. package/lib/commonjs/web/browser/assertBrowsePageShape.js +22 -0
  314. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -0
  315. package/lib/commonjs/web/browser/classifyTrackNavigation.js +41 -0
  316. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -0
  317. package/lib/commonjs/web/browser/parseSearchResponse.js +20 -0
  318. package/lib/commonjs/web/browser/parseSearchResponse.js.map +1 -0
  319. package/lib/commonjs/web/http/HttpClient.js +4 -23
  320. package/lib/commonjs/web/http/HttpClient.js.map +1 -1
  321. package/lib/commonjs/web/http/RequestConfigBuilder.js +107 -58
  322. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  323. package/lib/commonjs/web/playbackErrorKind.js +57 -0
  324. package/lib/commonjs/web/playbackErrorKind.js.map +1 -0
  325. package/lib/commonjs/web/player/MediaSessionManager.js +121 -0
  326. package/lib/commonjs/web/player/MediaSessionManager.js.map +1 -0
  327. package/lib/commonjs/web/player/NowPlayingManager.js +6 -5
  328. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  329. package/lib/commonjs/web/player/OptionsManager.js +9 -17
  330. package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
  331. package/lib/commonjs/web/player/RemoteCommandController.js +96 -0
  332. package/lib/commonjs/web/player/RemoteCommandController.js.map +1 -0
  333. package/lib/module/AudioBrowser.js +1 -1
  334. package/lib/module/AudioBrowser.js.map +1 -1
  335. package/lib/module/CLAUDE.md +52 -0
  336. package/lib/module/features/browser-config.js +221 -0
  337. package/lib/module/features/browser-config.js.map +1 -0
  338. package/lib/module/features/browser.js +54 -157
  339. package/lib/module/features/browser.js.map +1 -1
  340. package/lib/module/features/carConnection.js +37 -0
  341. package/lib/module/features/carConnection.js.map +1 -0
  342. package/lib/module/features/equalizer.js +1 -1
  343. package/lib/module/features/errors.js +37 -5
  344. package/lib/module/features/errors.js.map +1 -1
  345. package/lib/module/features/favorites.js +1 -1
  346. package/lib/module/features/gate.js +159 -0
  347. package/lib/module/features/gate.js.map +1 -0
  348. package/lib/module/features/index.js +2 -0
  349. package/lib/module/features/index.js.map +1 -1
  350. package/lib/module/features/metadata.js +20 -3
  351. package/lib/module/features/metadata.js.map +1 -1
  352. package/lib/module/features/network.js +1 -1
  353. package/lib/module/features/nowPlaying.js +34 -1
  354. package/lib/module/features/nowPlaying.js.map +1 -1
  355. package/lib/module/features/output.js +32 -19
  356. package/lib/module/features/output.js.map +1 -1
  357. package/lib/module/features/playback/controls.js +8 -0
  358. package/lib/module/features/playback/controls.js.map +1 -1
  359. package/lib/module/features/playback/index.js +1 -0
  360. package/lib/module/features/playback/index.js.map +1 -1
  361. package/lib/module/features/playback/playWhenReady.js +1 -1
  362. package/lib/module/features/playback/playing.js +1 -1
  363. package/lib/module/features/playback/progress.js +29 -13
  364. package/lib/module/features/playback/progress.js.map +1 -1
  365. package/lib/module/features/playback/state.js +1 -1
  366. package/lib/module/features/playback/trackPlaybackTime.js +83 -0
  367. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -0
  368. package/lib/module/features/playback/volume.js +2 -2
  369. package/lib/module/features/player/options.js +79 -29
  370. package/lib/module/features/player/options.js.map +1 -1
  371. package/lib/module/features/player/setup.js +256 -3
  372. package/lib/module/features/player/setup.js.map +1 -1
  373. package/lib/module/features/player/validateOptions.js +17 -0
  374. package/lib/module/features/player/validateOptions.js.map +1 -0
  375. package/lib/module/features/queue/activeTrack.js +1 -1
  376. package/lib/module/features/queue/queue.js +12 -5
  377. package/lib/module/features/queue/queue.js.map +1 -1
  378. package/lib/module/features/queue/repeatMode.js +1 -1
  379. package/lib/module/features/queue/shuffle.js +1 -1
  380. package/lib/module/features/remoteControls.js +13 -103
  381. package/lib/module/features/remoteControls.js.map +1 -1
  382. package/lib/module/features/sleepTimer.js +22 -5
  383. package/lib/module/features/sleepTimer.js.map +1 -1
  384. package/lib/module/native.js +5 -0
  385. package/lib/module/native.js.map +1 -1
  386. package/lib/module/native.web.js +4 -1
  387. package/lib/module/native.web.js.map +1 -1
  388. package/lib/module/test-utils/reactNativeStub.js +58 -0
  389. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  390. package/lib/module/utils/LazyNativeEmitter.js +8 -8
  391. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  392. package/lib/module/utils/NativeUpdatedValue.js +0 -3
  393. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  394. package/lib/module/utils/artwork.js +26 -0
  395. package/lib/module/utils/artwork.js.map +1 -0
  396. package/lib/module/web/NativeAudioBrowser.js +303 -126
  397. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  398. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +67 -0
  399. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  400. package/lib/module/web/TrackPlayer/PlaybackTimer.js +29 -0
  401. package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  402. package/lib/module/web/TrackPlayer/Player.js +90 -26
  403. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  404. package/lib/module/web/TrackPlayer/PlayingStateFactory.js +17 -0
  405. package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  406. package/lib/module/web/TrackPlayer/QueueManager.js +269 -0
  407. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -0
  408. package/lib/module/web/TrackPlayer/QueuePlayer.js +154 -0
  409. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -0
  410. package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
  411. package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
  412. package/lib/module/web/TrackPlayer/SleepTimer.js +34 -3
  413. package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
  414. package/lib/module/web/TrackPlayer/VolumeFader.js +70 -0
  415. package/lib/module/web/TrackPlayer/VolumeFader.js.map +1 -0
  416. package/lib/module/web/TrackPlayer/index.js +2 -1
  417. package/lib/module/web/TrackPlayer/index.js.map +1 -1
  418. package/lib/module/web/browser/BrowserManager.js +176 -50
  419. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  420. package/lib/module/web/browser/NavigationErrorManager.js +3 -1
  421. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  422. package/lib/module/web/browser/SearchManager.js +11 -9
  423. package/lib/module/web/browser/SearchManager.js.map +1 -1
  424. package/lib/module/web/browser/assertBrowsePageShape.js +18 -0
  425. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -0
  426. package/lib/module/web/browser/classifyTrackNavigation.js +38 -0
  427. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -0
  428. package/lib/module/web/browser/parseSearchResponse.js +16 -0
  429. package/lib/module/web/browser/parseSearchResponse.js.map +1 -0
  430. package/lib/module/web/http/HttpClient.js +4 -23
  431. package/lib/module/web/http/HttpClient.js.map +1 -1
  432. package/lib/module/web/http/RequestConfigBuilder.js +107 -58
  433. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  434. package/lib/module/web/playbackErrorKind.js +52 -0
  435. package/lib/module/web/playbackErrorKind.js.map +1 -0
  436. package/lib/module/web/player/MediaSessionManager.js +116 -0
  437. package/lib/module/web/player/MediaSessionManager.js.map +1 -0
  438. package/lib/module/web/player/NowPlayingManager.js +7 -5
  439. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  440. package/lib/module/web/player/OptionsManager.js +9 -17
  441. package/lib/module/web/player/OptionsManager.js.map +1 -1
  442. package/lib/module/web/player/RemoteCommandController.js +92 -0
  443. package/lib/module/web/player/RemoteCommandController.js.map +1 -0
  444. package/lib/typescript/src/AudioBrowser.d.ts +0 -1
  445. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  446. package/lib/typescript/src/features/browser-config.d.ts +12 -0
  447. package/lib/typescript/src/features/browser-config.d.ts.map +1 -0
  448. package/lib/typescript/src/features/browser.d.ts +40 -1
  449. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  450. package/lib/typescript/src/features/carConnection.d.ts +20 -0
  451. package/lib/typescript/src/features/carConnection.d.ts.map +1 -0
  452. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  453. package/lib/typescript/src/features/errors.d.ts +61 -6
  454. package/lib/typescript/src/features/errors.d.ts.map +1 -1
  455. package/lib/typescript/src/features/favorites.d.ts +1 -1
  456. package/lib/typescript/src/features/gate.d.ts +93 -0
  457. package/lib/typescript/src/features/gate.d.ts.map +1 -0
  458. package/lib/typescript/src/features/index.d.ts +2 -0
  459. package/lib/typescript/src/features/index.d.ts.map +1 -1
  460. package/lib/typescript/src/features/metadata.d.ts +26 -7
  461. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  462. package/lib/typescript/src/features/network.d.ts +1 -1
  463. package/lib/typescript/src/features/nowPlaying.d.ts +28 -1
  464. package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
  465. package/lib/typescript/src/features/output.d.ts +28 -18
  466. package/lib/typescript/src/features/output.d.ts.map +1 -1
  467. package/lib/typescript/src/features/playback/controls.d.ts +5 -0
  468. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  469. package/lib/typescript/src/features/playback/index.d.ts +1 -0
  470. package/lib/typescript/src/features/playback/index.d.ts.map +1 -1
  471. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  472. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  473. package/lib/typescript/src/features/playback/progress.d.ts +4 -1
  474. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  475. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  476. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +34 -0
  477. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -0
  478. package/lib/typescript/src/features/playback/volume.d.ts +2 -2
  479. package/lib/typescript/src/features/player/options.d.ts +176 -127
  480. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  481. package/lib/typescript/src/features/player/setup.d.ts +216 -167
  482. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  483. package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
  484. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  485. package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
  486. package/lib/typescript/src/features/queue/queue.d.ts +11 -4
  487. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  488. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  489. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  490. package/lib/typescript/src/features/remoteControls.d.ts +23 -35
  491. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  492. package/lib/typescript/src/features/sleepTimer.d.ts +19 -4
  493. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  494. package/lib/typescript/src/index.d.ts.map +1 -1
  495. package/lib/typescript/src/native.d.ts +1 -2
  496. package/lib/typescript/src/native.d.ts.map +1 -1
  497. package/lib/typescript/src/specs/audio-browser.nitro.d.ts +17 -225
  498. package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
  499. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  500. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  501. package/lib/typescript/src/types/browser-native.d.ts +9 -3
  502. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  503. package/lib/typescript/src/types/browser-nodes.d.ts +145 -13
  504. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  505. package/lib/typescript/src/types/browser.d.ts +349 -85
  506. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  507. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -6
  508. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  509. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +0 -2
  510. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  511. package/lib/typescript/src/utils/artwork.d.ts +19 -0
  512. package/lib/typescript/src/utils/artwork.d.ts.map +1 -0
  513. package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
  514. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +4 -2
  515. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +24 -10
  516. package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
  517. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -20
  518. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +33 -1
  519. package/nitrogen/generated/android/c++/JArtworkVariants.hpp +61 -0
  520. package/nitrogen/generated/android/c++/JBrowseResult.hpp +5 -0
  521. package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
  522. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +6 -0
  523. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +58 -0
  524. package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +58 -0
  525. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +100 -0
  526. package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +3 -2
  527. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +112 -0
  528. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +121 -0
  529. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +123 -0
  530. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +133 -0
  531. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +121 -0
  532. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +139 -0
  533. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +6 -0
  534. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +134 -0
  535. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -0
  536. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +6 -0
  537. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +131 -0
  538. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +6 -0
  539. package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +79 -0
  540. package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +0 -10
  541. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +14 -9
  542. package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +80 -0
  543. package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +2 -0
  544. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +6 -0
  545. package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +2 -0
  546. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
  547. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
  548. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +6 -0
  549. package/nitrogen/generated/android/c++/JGate.hpp +62 -0
  550. package/nitrogen/generated/android/c++/JGateDecision.hpp +64 -0
  551. package/nitrogen/generated/android/c++/{JThumbsRating.hpp → JGateEvent.hpp} +16 -15
  552. package/nitrogen/generated/android/c++/JGateReason.hpp +58 -0
  553. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +247 -223
  554. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +26 -23
  555. package/nitrogen/generated/android/c++/JIOSOptions.hpp +95 -0
  556. package/nitrogen/generated/android/c++/JImageRowItem.hpp +33 -3
  557. package/nitrogen/generated/android/c++/JMediaReference.hpp +58 -0
  558. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +33 -1
  559. package/nitrogen/generated/android/c++/{JPartialAndroidSetupPlayerOptions.hpp → JNativeAndroidSetupOptions.hpp} +10 -10
  560. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +61 -28
  561. package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +74 -0
  562. package/nitrogen/generated/android/c++/{JPartialIOSSetupPlayerOptions.hpp → JNativeIOSSetupOptions.hpp} +10 -10
  563. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +20 -9
  564. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +182 -0
  565. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +21 -24
  566. package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +6 -0
  567. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -20
  568. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +96 -0
  569. package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +3 -17
  570. package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +7 -3
  571. package/nitrogen/generated/android/c++/JOptions.hpp +19 -14
  572. package/nitrogen/generated/android/c++/{JIosOutput.hpp → JOutput.hpp} +15 -15
  573. package/nitrogen/generated/android/c++/JOutputType.hpp +82 -0
  574. package/nitrogen/generated/android/c++/JPlayback.hpp +2 -0
  575. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +6 -0
  576. package/nitrogen/generated/android/c++/JPlaybackError.hpp +18 -3
  577. package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +2 -0
  578. package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +76 -0
  579. package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
  580. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +11 -5
  581. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +20 -20
  582. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  583. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +23 -5
  584. package/nitrogen/generated/android/c++/JRetryConfig.hpp +11 -7
  585. package/nitrogen/generated/android/c++/JSearchMode.hpp +27 -9
  586. package/nitrogen/generated/android/c++/JSearchParams.hpp +9 -3
  587. package/nitrogen/generated/android/c++/JStallReason.hpp +58 -0
  588. package/nitrogen/generated/android/c++/JTrack.hpp +23 -5
  589. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +6 -0
  590. package/nitrogen/generated/android/c++/JTrackRequest.hpp +93 -0
  591. package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +14 -1
  592. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +26 -0
  593. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +71 -0
  594. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  595. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  596. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +41 -0
  597. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +121 -0
  598. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +26 -0
  599. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +70 -0
  600. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
  601. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -9
  602. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +10 -4
  603. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkVariants.kt +41 -0
  604. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
  605. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{HeartRating.kt → FavoriteConfig.kt} +6 -6
  606. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RatingType.kt → FavoritesMatchMode.kt} +5 -10
  607. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +50 -0
  608. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
  609. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +80 -0
  610. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +80 -0
  611. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +80 -0
  612. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +80 -0
  613. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +80 -0
  614. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +80 -0
  615. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +80 -0
  616. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt} +12 -12
  617. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_RemoteSetRatingEvent.kt → Func_void_GateEvent.kt} +9 -9
  618. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_IosOutput.kt → Func_void_Output.kt} +9 -9
  619. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
  620. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ThumbsRating.kt → Gate.kt} +9 -6
  621. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PercentageRating.kt → GateDecision.kt} +9 -6
  622. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{StarRating.kt → GateEvent.kt} +6 -6
  623. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → GateReason.kt} +5 -8
  624. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +104 -110
  625. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +44 -0
  626. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +24 -3
  627. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +23 -0
  628. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +10 -4
  629. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialAndroidSetupPlayerOptions.kt → NativeAndroidSetupOptions.kt} +5 -5
  630. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +18 -6
  631. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RemoteSetRatingEvent.kt → NativeGateRequest.kt} +12 -6
  632. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialIOSSetupPlayerOptions.kt → NativeIOSSetupOptions.kt} +5 -5
  633. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
  634. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +66 -0
  635. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
  636. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +3 -1
  637. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -9
  638. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +44 -0
  639. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +3 -6
  640. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +6 -3
  641. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
  642. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{IosOutput.kt → Output.kt} +6 -6
  643. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +31 -0
  644. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +12 -3
  645. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +29 -0
  646. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
  647. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  648. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +26 -0
  649. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -3
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +7 -4
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +12 -6
  653. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +6 -3
  654. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +23 -0
  655. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +12 -3
  656. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +44 -0
  657. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +7 -4
  658. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +53 -0
  659. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  660. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +65 -0
  661. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +53 -0
  662. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +139 -28
  663. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +1126 -490
  664. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +66 -45
  665. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +178 -120
  666. package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
  667. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -22
  668. package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +95 -1
  669. package/nitrogen/generated/ios/swift/ArtworkVariants.swift +34 -0
  670. package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
  671. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +29 -0
  672. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +40 -0
  673. package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +67 -0
  674. package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +17 -4
  675. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +54 -0
  676. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +69 -0
  677. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +54 -0
  678. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +60 -0
  679. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +60 -0
  680. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +61 -0
  681. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +61 -0
  682. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +77 -0
  683. package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +46 -0
  684. package/nitrogen/generated/ios/swift/{Func_void_RemoteSetRatingEvent.swift → Func_void_GateEvent.swift} +10 -10
  685. package/nitrogen/generated/ios/swift/{Func_void_IosOutput.swift → Func_void_Output.swift} +10 -10
  686. package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +46 -0
  687. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  688. package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +46 -0
  689. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
  690. package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift → Func_void_std__shared_ptr_Promise_GateDecision__.swift} +17 -17
  691. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +66 -0
  692. package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +81 -0
  693. package/nitrogen/generated/ios/swift/Gate.swift +47 -0
  694. package/nitrogen/generated/ios/swift/GateDecision.swift +40 -0
  695. package/nitrogen/generated/ios/swift/GateEvent.swift +29 -0
  696. package/nitrogen/generated/ios/swift/GateReason.swift +40 -0
  697. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +21 -15
  698. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +232 -199
  699. package/nitrogen/generated/ios/swift/IOSOptions.swift +51 -0
  700. package/nitrogen/generated/ios/swift/ImageRowItem.swift +121 -2
  701. package/nitrogen/generated/ios/swift/MediaReference.swift +40 -0
  702. package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +107 -1
  703. package/nitrogen/generated/ios/swift/{PartialAndroidSetupPlayerOptions.swift → NativeAndroidSetupOptions.swift} +5 -5
  704. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +161 -22
  705. package/nitrogen/generated/ios/swift/NativeGateRequest.swift +58 -0
  706. package/nitrogen/generated/ios/swift/{PartialIOSSetupPlayerOptions.swift → NativeIOSSetupOptions.swift} +5 -5
  707. package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
  708. package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +206 -0
  709. package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +12 -25
  710. package/nitrogen/generated/ios/swift/NavigationErrorType.swift +8 -0
  711. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -41
  712. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +90 -0
  713. package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +1 -48
  714. package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +19 -1
  715. package/nitrogen/generated/ios/swift/Options.swift +10 -4
  716. package/nitrogen/generated/ios/swift/{IosOutput.swift → Output.swift} +7 -7
  717. package/nitrogen/generated/ios/swift/OutputType.swift +72 -0
  718. package/nitrogen/generated/ios/swift/PlaybackError.swift +43 -2
  719. package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +64 -0
  720. package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
  721. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +27 -8
  722. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +10 -10
  723. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  724. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +74 -8
  725. package/nitrogen/generated/ios/swift/RetryConfig.swift +35 -4
  726. package/nitrogen/generated/ios/swift/SearchMode.swift +36 -12
  727. package/nitrogen/generated/ios/swift/SearchParams.swift +7 -2
  728. package/nitrogen/generated/ios/swift/StallReason.swift +40 -0
  729. package/nitrogen/generated/ios/swift/Track.swift +74 -8
  730. package/nitrogen/generated/ios/swift/TrackRequest.swift +106 -0
  731. package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +60 -1
  732. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +18 -0
  733. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  734. package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +19 -0
  735. package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +18 -0
  736. package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
  737. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -19
  738. package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +10 -2
  739. package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +87 -0
  740. package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
  741. package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +0 -2
  742. package/nitrogen/generated/shared/c++/{StarRating.hpp → FavoriteConfig.hpp} +18 -17
  743. package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +76 -0
  744. package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +110 -0
  745. package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +6 -5
  746. package/nitrogen/generated/shared/c++/{PercentageRating.hpp → Gate.hpp} +21 -16
  747. package/nitrogen/generated/shared/c++/{HeartRating.hpp → GateDecision.hpp} +23 -17
  748. package/nitrogen/generated/shared/c++/{ThumbsRating.hpp → GateEvent.hpp} +18 -17
  749. package/nitrogen/generated/shared/c++/GateReason.hpp +76 -0
  750. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +22 -19
  751. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +45 -36
  752. package/nitrogen/generated/shared/c++/IOSOptions.hpp +91 -0
  753. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +33 -2
  754. package/nitrogen/generated/shared/c++/MediaReference.hpp +76 -0
  755. package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +10 -2
  756. package/nitrogen/generated/shared/c++/{PartialAndroidSetupPlayerOptions.hpp → NativeAndroidSetupOptions.hpp} +11 -11
  757. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +31 -15
  758. package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +97 -0
  759. package/nitrogen/generated/shared/c++/{PartialIOSSetupPlayerOptions.hpp → NativeIOSSetupOptions.hpp} +11 -11
  760. package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
  761. package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +138 -0
  762. package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +9 -7
  763. package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +8 -0
  764. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -19
  765. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +92 -0
  766. package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +3 -19
  767. package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +6 -2
  768. package/nitrogen/generated/shared/c++/Options.hpp +8 -8
  769. package/nitrogen/generated/shared/c++/{IosOutput.hpp → Output.hpp} +18 -18
  770. package/nitrogen/generated/shared/c++/OutputType.hpp +108 -0
  771. package/nitrogen/generated/shared/c++/PlaybackError.hpp +18 -3
  772. package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +100 -0
  773. package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
  774. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +9 -6
  775. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +25 -25
  776. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  777. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +24 -5
  778. package/nitrogen/generated/shared/c++/RetryConfig.hpp +10 -6
  779. package/nitrogen/generated/shared/c++/SearchMode.hpp +39 -15
  780. package/nitrogen/generated/shared/c++/SearchParams.hpp +9 -2
  781. package/nitrogen/generated/shared/c++/StallReason.hpp +76 -0
  782. package/nitrogen/generated/shared/c++/Track.hpp +24 -5
  783. package/nitrogen/generated/shared/c++/TrackRequest.hpp +93 -0
  784. package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +6 -2
  785. package/package.json +20 -8
  786. package/src/AudioBrowser.ts +1 -1
  787. package/src/CLAUDE.md +52 -0
  788. package/src/codegen-no-config-variant.test.ts +70 -0
  789. package/src/features/battery.test.ts +79 -0
  790. package/src/features/browser-config.test.ts +110 -0
  791. package/src/features/browser-config.ts +293 -0
  792. package/src/features/browser.test.ts +19 -0
  793. package/src/features/browser.ts +59 -202
  794. package/src/features/carConnection.ts +36 -0
  795. package/src/features/equalizer.ts +1 -1
  796. package/src/features/errors.ts +71 -5
  797. package/src/features/favorites.ts +1 -1
  798. package/src/features/gate.test.ts +72 -0
  799. package/src/features/gate.ts +170 -0
  800. package/src/features/hook-wiring.test.ts +254 -0
  801. package/src/features/index.ts +2 -0
  802. package/src/features/metadata.ts +26 -15
  803. package/src/features/network.ts +1 -1
  804. package/src/features/nowPlaying.ts +37 -1
  805. package/src/features/output.ts +35 -22
  806. package/src/features/playback/controls.ts +8 -0
  807. package/src/features/playback/index.ts +1 -0
  808. package/src/features/playback/playWhenReady.ts +1 -1
  809. package/src/features/playback/playing.ts +1 -1
  810. package/src/features/playback/progress.test.ts +169 -0
  811. package/src/features/playback/progress.ts +32 -13
  812. package/src/features/playback/state.ts +1 -1
  813. package/src/features/playback/trackPlaybackTime.test.ts +76 -0
  814. package/src/features/playback/trackPlaybackTime.ts +101 -0
  815. package/src/features/playback/volume.ts +2 -2
  816. package/src/features/player/options.test.ts +38 -0
  817. package/src/features/player/options.ts +217 -110
  818. package/src/features/player/setup.test.ts +125 -0
  819. package/src/features/player/setup.ts +400 -175
  820. package/src/features/player/validateOptions.ts +19 -0
  821. package/src/features/queue/activeTrack.ts +1 -1
  822. package/src/features/queue/queue.test.ts +36 -0
  823. package/src/features/queue/queue.ts +17 -6
  824. package/src/features/queue/repeatMode.ts +1 -1
  825. package/src/features/queue/shuffle.ts +1 -1
  826. package/src/features/remoteControls.ts +13 -115
  827. package/src/features/sleepTimer.test.ts +172 -0
  828. package/src/features/sleepTimer.ts +32 -5
  829. package/src/native.ts +5 -0
  830. package/src/native.web.ts +4 -1
  831. package/src/public-surface.test.ts +107 -0
  832. package/src/specs/audio-browser.nitro.ts +132 -42
  833. package/src/test-utils/reactNativeStub.ts +60 -0
  834. package/src/types/browser-native.ts +19 -6
  835. package/src/types/browser-nodes.ts +160 -13
  836. package/src/types/browser.ts +377 -89
  837. package/src/utils/LazyNativeEmitter.ts +9 -8
  838. package/src/utils/NativeUpdatedValue.ts +0 -2
  839. package/src/utils/artwork.ts +30 -0
  840. package/src/utils/useDebug.test.ts +182 -0
  841. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  842. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +301 -0
  843. package/src/web/NativeAudioBrowser.ts +366 -159
  844. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +139 -0
  845. package/src/web/TrackPlayer/PlaybackStateMachine.ts +82 -0
  846. package/src/web/TrackPlayer/PlaybackTimer.test.ts +67 -0
  847. package/src/web/TrackPlayer/PlaybackTimer.ts +33 -0
  848. package/src/web/TrackPlayer/Player.dispatch.test.ts +229 -0
  849. package/src/web/TrackPlayer/Player.load.test.ts +86 -0
  850. package/src/web/TrackPlayer/Player.ts +113 -39
  851. package/src/web/TrackPlayer/PlayingStateFactory.test.ts +51 -0
  852. package/src/web/TrackPlayer/PlayingStateFactory.ts +24 -0
  853. package/src/web/TrackPlayer/QueueManager.test.ts +289 -0
  854. package/src/web/TrackPlayer/QueueManager.ts +302 -0
  855. package/src/web/TrackPlayer/QueuePlayer.ts +188 -0
  856. package/src/web/TrackPlayer/RepeatMode.ts +1 -1
  857. package/src/web/TrackPlayer/SleepTimer.ts +38 -3
  858. package/src/web/TrackPlayer/VolumeFader.ts +74 -0
  859. package/src/web/TrackPlayer/index.ts +2 -1
  860. package/src/web/browser/BrowserManager.ts +231 -62
  861. package/src/web/browser/NavigationErrorManager.ts +3 -1
  862. package/src/web/browser/SearchManager.ts +11 -15
  863. package/src/web/browser/__tests__/layer-resolver.test.ts +69 -0
  864. package/src/web/browser/assertBrowsePageShape.test.ts +27 -0
  865. package/src/web/browser/assertBrowsePageShape.ts +27 -0
  866. package/src/web/browser/classifyTrackNavigation.test.ts +41 -0
  867. package/src/web/browser/classifyTrackNavigation.ts +35 -0
  868. package/src/web/browser/parseSearchResponse.test.ts +25 -0
  869. package/src/web/browser/parseSearchResponse.ts +15 -0
  870. package/src/web/http/HttpClient.ts +5 -35
  871. package/src/web/http/RequestConfigBuilder.test.ts +59 -0
  872. package/src/web/http/RequestConfigBuilder.ts +120 -63
  873. package/src/web/playbackErrorKind.test.ts +55 -0
  874. package/src/web/playbackErrorKind.ts +56 -0
  875. package/src/web/player/MediaSessionManager.test.ts +220 -0
  876. package/src/web/player/MediaSessionManager.ts +151 -0
  877. package/src/web/player/NowPlayingManager.test.ts +46 -0
  878. package/src/web/player/NowPlayingManager.ts +6 -5
  879. package/src/web/player/OptionsManager.ts +12 -18
  880. package/src/web/player/RemoteCommandController.test.ts +171 -0
  881. package/src/web/player/RemoteCommandController.ts +166 -0
  882. package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +0 -59
  883. package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +0 -72
  884. package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +0 -36
  885. package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +0 -146
  886. package/ios/CarPlay/Track+CarPlay.swift +0 -63
  887. package/ios/Model/MediaURL.swift +0 -67
  888. package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +0 -9
  889. package/ios/Player/PlaybackProgressUpdateManager.swift +0 -68
  890. package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +0 -166
  891. package/lib/commonjs/features/rating.js +0 -2
  892. package/lib/commonjs/features/rating.js.map +0 -1
  893. package/lib/commonjs/types/player.js +0 -2
  894. package/lib/commonjs/types/player.js.map +0 -1
  895. package/lib/commonjs/web/SimpleRouter.test.js +0 -174
  896. package/lib/commonjs/web/SimpleRouter.test.js.map +0 -1
  897. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +0 -293
  898. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  899. package/lib/module/features/rating.js +0 -2
  900. package/lib/module/features/rating.js.map +0 -1
  901. package/lib/module/types/player.js +0 -2
  902. package/lib/module/types/player.js.map +0 -1
  903. package/lib/module/web/SimpleRouter.test.js +0 -174
  904. package/lib/module/web/SimpleRouter.test.js.map +0 -1
  905. package/lib/module/web/TrackPlayer/PlaylistPlayer.js +0 -288
  906. package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  907. package/lib/typescript/src/features/rating.d.ts +0 -14
  908. package/lib/typescript/src/features/rating.d.ts.map +0 -1
  909. package/lib/typescript/src/native.web.d.ts +0 -3
  910. package/lib/typescript/src/native.web.d.ts.map +0 -1
  911. package/lib/typescript/src/types/player.d.ts +0 -64
  912. package/lib/typescript/src/types/player.d.ts.map +0 -1
  913. package/lib/typescript/src/web/NativeAudioBrowser.d.ts +0 -188
  914. package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +0 -1
  915. package/lib/typescript/src/web/SimpleRouter.d.ts +0 -40
  916. package/lib/typescript/src/web/SimpleRouter.d.ts.map +0 -1
  917. package/lib/typescript/src/web/SimpleRouter.test.d.ts +0 -2
  918. package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +0 -1
  919. package/lib/typescript/src/web/TrackPlayer/Event.d.ts +0 -11
  920. package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +0 -1
  921. package/lib/typescript/src/web/TrackPlayer/Player.d.ts +0 -59
  922. package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +0 -1
  923. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +0 -39
  924. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +0 -1
  925. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +0 -6
  926. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +0 -1
  927. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +0 -4
  928. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +0 -1
  929. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +0 -34
  930. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +0 -1
  931. package/lib/typescript/src/web/TrackPlayer/State.d.ts +0 -13
  932. package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +0 -1
  933. package/lib/typescript/src/web/TrackPlayer/index.d.ts +0 -5
  934. package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +0 -1
  935. package/lib/typescript/src/web/browser/BrowserManager.d.ts +0 -154
  936. package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +0 -1
  937. package/lib/typescript/src/web/browser/FavoriteManager.d.ts +0 -42
  938. package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +0 -1
  939. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +0 -40
  940. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +0 -1
  941. package/lib/typescript/src/web/browser/SearchManager.d.ts +0 -22
  942. package/lib/typescript/src/web/browser/SearchManager.d.ts.map +0 -1
  943. package/lib/typescript/src/web/http/HttpClient.d.ts +0 -25
  944. package/lib/typescript/src/web/http/HttpClient.d.ts.map +0 -1
  945. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +0 -98
  946. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +0 -1
  947. package/lib/typescript/src/web/player/NowPlayingManager.d.ts +0 -34
  948. package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +0 -1
  949. package/lib/typescript/src/web/player/OptionsManager.d.ts +0 -32
  950. package/lib/typescript/src/web/player/OptionsManager.d.ts.map +0 -1
  951. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +0 -69
  952. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +0 -1
  953. package/lib/typescript/src/web/util/shuffle.d.ts +0 -8
  954. package/lib/typescript/src/web/util/shuffle.d.ts.map +0 -1
  955. package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +0 -86
  956. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +0 -147
  957. package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +0 -80
  958. package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +0 -87
  959. package/nitrogen/generated/android/c++/JHeartRating.hpp +0 -57
  960. package/nitrogen/generated/android/c++/JIosOutputType.hpp +0 -85
  961. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  962. package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +0 -96
  963. package/nitrogen/generated/android/c++/JPercentageRating.hpp +0 -57
  964. package/nitrogen/generated/android/c++/JRatingType.hpp +0 -73
  965. package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +0 -66
  966. package/nitrogen/generated/android/c++/JStarRating.hpp +0 -57
  967. package/nitrogen/generated/android/c++/JUpdateOptions.hpp +0 -105
  968. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +0 -34
  969. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +0 -105
  970. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  971. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  972. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +0 -50
  973. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +0 -32
  974. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  975. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +0 -47
  976. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +0 -53
  977. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +0 -77
  978. package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +0 -119
  979. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +0 -68
  980. package/nitrogen/generated/ios/swift/HeartRating.swift +0 -29
  981. package/nitrogen/generated/ios/swift/IosOutputType.swift +0 -76
  982. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  983. package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +0 -101
  984. package/nitrogen/generated/ios/swift/PercentageRating.swift +0 -29
  985. package/nitrogen/generated/ios/swift/RatingType.swift +0 -60
  986. package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +0 -58
  987. package/nitrogen/generated/ios/swift/StarRating.swift +0 -29
  988. package/nitrogen/generated/ios/swift/ThumbsRating.swift +0 -29
  989. package/nitrogen/generated/ios/swift/UpdateOptions.swift +0 -143
  990. package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +0 -20
  991. package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +0 -109
  992. package/nitrogen/generated/shared/c++/IosOutputType.hpp +0 -112
  993. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
  994. package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +0 -104
  995. package/nitrogen/generated/shared/c++/RatingType.hpp +0 -96
  996. package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +0 -94
  997. package/nitrogen/generated/shared/c++/UpdateOptions.hpp +0 -111
  998. package/src/features/rating.ts +0 -17
  999. package/src/types/player.ts +0 -69
  1000. package/src/web/TrackPlayer/PlaylistPlayer.ts +0 -346
@@ -7,10 +7,13 @@ import com.audiobrowser.http.HttpClient
7
7
  import com.audiobrowser.http.RequestConfigBuilder
8
8
  import com.audiobrowser.util.BrowserPathHelper
9
9
  import com.audiobrowser.util.TrackFactory
10
+ import com.audiobrowser.util.artworkOf
10
11
  import com.margelo.nitro.audiobrowser.ArtworkRequestConfig
11
12
  import com.margelo.nitro.audiobrowser.BrowserSourceCallbackParam
13
+ import com.margelo.nitro.audiobrowser.FavoritesMatchMode
14
+ import com.margelo.nitro.audiobrowser.Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____
12
15
  import com.margelo.nitro.audiobrowser.ImageContext
13
- import com.margelo.nitro.audiobrowser.ImageSource
16
+ import com.margelo.nitro.audiobrowser.MediaReference
14
17
  import com.margelo.nitro.audiobrowser.MediaRequestConfig
15
18
  import com.margelo.nitro.audiobrowser.NativeRouteEntry
16
19
  import com.margelo.nitro.audiobrowser.RequestConfig
@@ -46,6 +49,7 @@ class BrowserManager {
46
49
  private var onPathChanged: ((String) -> Unit)? = null
47
50
  private var onContentChanged: ((ResolvedTrack?) -> Unit)? = null
48
51
  private var onTabsChanged: ((Array<Track>) -> Unit)? = null
52
+ private var onArtworkRegistriesCleared: (() -> Unit)? = null
49
53
 
50
54
  private var path: String = "/"
51
55
  set(value) {
@@ -86,28 +90,64 @@ class BrowserManager {
86
90
  // Invalidated via invalidateContentCache() when content changes
87
91
  private val contentCache = LruCache<String, ResolvedTrack>(20)
88
92
 
89
- // Cache for search results - keyed by query string
90
- private var lastSearchQuery: String? = null
91
- private var lastSearchResults: Array<Track>? = null
93
+ // Cache for the most recent search - the query string and its results stored as one pair, so a
94
+ // concurrent reader never sees one query matched with another query's results.
95
+ @Volatile private var lastSearch: Pair<String, Array<Track>>? = null
92
96
 
93
97
  // Set of favorited track identifiers (src)
94
98
  private var favoriteIds = setOf<String>()
95
99
 
100
+ // Favorite match mode, propagated from the player's `favorite` capability.
101
+ // null = favoriting disabled (no row hearts). Set via setFavoriteMatch.
102
+ private var favoriteMatch: FavoritesMatchMode? = null
103
+
96
104
  // Navigation tracking to prevent race conditions
97
105
  @Volatile private var currentNavigationId = 0
98
106
 
99
107
  /**
100
108
  * Browser configuration containing routes, search, tabs, and request settings. This can be
101
109
  * updated dynamically when the configuration changes.
110
+ *
111
+ * Setting a new config bumps [layerGeneration] so the cached request/browse resolver layers are
112
+ * re-resolved on the next request (the resolvers may close over config-derived state).
102
113
  */
103
114
  var config: BrowserConfig = BrowserConfig()
115
+ set(value) {
116
+ field = value
117
+ layerGeneration += 1
118
+ // Registered artwork resolutions pin configs (and their JS callback handles)
119
+ // from the previous configuration — never resolve through them again.
120
+ artworkResolutions.clear()
121
+ onArtworkRegistriesCleared?.invoke()
122
+ }
123
+
124
+ // Resolver-layer caching. The request/browse layers may be resolver thunks
125
+ // (config.requestResolver / config.browseResolver) resolved once per *content generation*:
126
+ // re-resolved when content is invalidated (clearContentCache, from invalidateAllContent) or when
127
+ // a new config is set, cached, and merged per request.
128
+ private var layerGeneration = 0
129
+ private var resolvedLayerGeneration = -1
130
+ private var resolvedRequestLayer: TransformableRequestConfig? = null
131
+ private var resolvedBrowseLayer: TransformableRequestConfig? = null
132
+
133
+ /** Test-only accessors for the resolver-layer cache state (see ensureLayersResolved). */
134
+ internal val layerGenerationForTest: Int
135
+ get() = layerGeneration
136
+
137
+ internal val resolvedLayerGenerationForTest: Int
138
+ get() = resolvedLayerGeneration
139
+
140
+ internal val resolvedRequestLayerForTest: TransformableRequestConfig?
141
+ get() = resolvedRequestLayer
142
+
143
+ internal val resolvedBrowseLayerForTest: TransformableRequestConfig?
144
+ get() = resolvedBrowseLayer
104
145
 
105
146
  /**
106
- * Callback to transform artwork URLs for tracks. Takes a track and optional per-route artwork
107
- * config, returns ImageSource or null. Injected by AudioBrowser when artwork config is set.
108
- * The ImageContext parameter provides size hints for CDN URL generation.
147
+ * Maps produced artwork URIs back to (Track, artwork-config kind) so display-time bitmap loading
148
+ * can re-resolve Track-first. See [ArtworkResolutionRegistry].
109
149
  */
110
- var artworkUrlResolver: (suspend (Track, ArtworkRequestConfig?, ImageContext?) -> ImageSource?)? = null
150
+ val artworkResolutions = ArtworkResolutionRegistry()
111
151
 
112
152
  /**
113
153
  * Sets the favorited track identifiers. Tracks will have their favorited field hydrated based on
@@ -118,6 +158,14 @@ class BrowserManager {
118
158
  Timber.d("Set ${favoriteIds.size} favorite IDs")
119
159
  }
120
160
 
161
+ /**
162
+ * Sets the favorite match mode (propagated from the `favorite` capability). null disables
163
+ * row-heart hydration.
164
+ */
165
+ fun setFavoriteMatch(match: FavoritesMatchMode?) {
166
+ favoriteMatch = match
167
+ }
168
+
121
169
  /**
122
170
  * Updates the favorite state for a single track identifier. Called when the heart button is
123
171
  * tapped in media controllers.
@@ -133,40 +181,30 @@ class BrowserManager {
133
181
  }
134
182
 
135
183
  /**
136
- * Hydrates the favorited field on a track based on the favoriteIds set. Only hydrates if
137
- * track.favorited is null (doesn't overwrite API-provided values). Only tracks with src can be
138
- * favorited.
184
+ * Hydrates the favorited field on a track based on the favoriteIds set. No-op unless favoriting
185
+ * is enabled (the `favorite` capability). Only playable (src-bearing) tracks are favoritable; the
186
+ * flag is set to true OR false so non-favorited tracks still show an (empty) heart. Doesn't
187
+ * overwrite API-provided values.
139
188
  */
140
189
  private fun hydrateFavorite(track: Track): Track {
190
+ val match = favoriteMatch ?: return track
141
191
  // Don't overwrite API-provided favorites
142
192
  if (track.favorited != null) return track
143
- if (favoriteIds.isEmpty()) return track
144
-
145
- val isFavorited = track.src?.let { favoriteIds.contains(it) } ?: false
146
- if (!isFavorited) return track
147
-
148
- return Track(
149
- url = track.url,
150
- src = track.src,
151
- artwork = track.artwork,
152
- artworkSource = track.artworkSource,
153
- artworkCarPlayTinted = track.artworkCarPlayTinted,
154
- title = track.title,
155
- subtitle = track.subtitle,
156
- artist = track.artist,
157
- album = track.album,
158
- description = track.description,
159
- genre = track.genre,
160
- duration = track.duration,
161
- style = track.style,
162
- childrenStyle = track.childrenStyle,
163
- favorited = true,
164
- groupTitle = track.groupTitle,
165
- live = track.live,
166
- imageRow = track.imageRow,
167
- )
193
+ // Only playable tracks are favoritable
194
+ val src = track.src ?: return track
195
+
196
+ val isFavorited = isFavorite(src, match)
197
+
198
+ return track.copy(favorited = isFavorited)
168
199
  }
169
200
 
201
+ /** Whether [src] is favorited under the given match mode. */
202
+ private fun isFavorite(src: String, match: FavoritesMatchMode): Boolean =
203
+ when (match) {
204
+ FavoritesMatchMode.EXACT -> favoriteIds.contains(src)
205
+ FavoritesMatchMode.PARTIAL -> favoriteIds.any { BrowserPathHelper.containsSegment(src, it) }
206
+ }
207
+
170
208
  /** Hydrates favorites on all children of a ResolvedTrack. */
171
209
  private fun hydrateChildren(resolvedTrack: ResolvedTrack): ResolvedTrack {
172
210
  val children = resolvedTrack.children ?: return resolvedTrack
@@ -174,20 +212,29 @@ class BrowserManager {
174
212
  return resolvedTrack.copy(children = hydratedChildren)
175
213
  }
176
214
 
177
- /** Cache a track by both url and src for O(1) lookup from either key. */
215
+ /** Cache a track by id, url, and src for O(1) lookup from any mediaId form. */
178
216
  private fun cacheTrack(track: Track) {
217
+ track.id?.takeUnless { it.isBlank() }?.let { trackCache.put(it, track) }
179
218
  track.url?.let { trackCache.put(it, track) }
180
219
  track.src?.let { trackCache.put(it, track) }
181
220
  }
182
221
 
183
222
  private fun cacheChildren(resolvedTrack: ResolvedTrack) {
184
- resolvedTrack.children?.forEach { track -> cacheTrack(track) }
223
+ resolvedTrack.children?.forEach { track ->
224
+ cacheTrack(track)
225
+ // Image-row items are playable surfaces of their own: cache them (with
226
+ // their stamped contextual urls) so an id-keyed mediaId from an
227
+ // expanded Android Auto tile can find its way back to queue expansion.
228
+ track.imageRow?.forEach { item ->
229
+ cacheTrack(with(TrackFactory) { item.toTrack(groupTitle = track.title) })
230
+ }
231
+ }
185
232
  }
186
233
 
187
234
  /**
188
- * Get a cached Track by mediaId (url or src), or null if not cached. Used by Media3 to rehydrate
189
- * MediaItem shells with full track metadata. Re-hydrates favorites in case setFavoriteStates was
190
- * called after caching.
235
+ * Get a cached Track by mediaId (stable id, url, or src), or null if not cached. Used by Media3
236
+ * to rehydrate MediaItem shells with full track metadata. Re-hydrates favorites in case
237
+ * setFavoriteStates was called after caching.
191
238
  */
192
239
  fun getCachedTrack(mediaId: String): Track? {
193
240
  // Try direct lookup first (matches url or src)
@@ -212,27 +259,76 @@ class BrowserManager {
212
259
  }
213
260
 
214
261
  /**
215
- * Resolves multiple media IDs from cache. Throws IllegalStateException if any mediaId is not
216
- * found in cache.
262
+ * Resolves a tapped mediaId to the contextual url to expand a queue from, or null when there is
263
+ * none. A contextual mediaId is its own; a stable-id mediaId (see TrackFactory.buildMediaItem)
264
+ * resolves through the track cache to the contextual url of the container it was most recently
265
+ * browsed in — a legacy car controller round-trips only the mediaId, so the cache is what
266
+ * remembers which list the row came from.
267
+ */
268
+ fun contextualUrlFor(mediaId: String): String? {
269
+ if (BrowserPathHelper.isContextual(mediaId)) return mediaId
270
+ return getCachedTrack(mediaId)?.url?.takeIf { BrowserPathHelper.isContextual(it) }
271
+ }
272
+
273
+ /**
274
+ * Resolves a single Media3 MediaItem to a Track. Prefers the track cache (keyed by id, url, and
275
+ * src).
217
276
  *
218
- * @param mediaIds The media IDs to resolve
219
- * @return List of Track objects
277
+ * A cache miss is legitimately reachable: a controller can replay a mediaId this process never
278
+ * browsed — e.g. a search-result track after process death. When the mediaId is a playable URL,
279
+ * or the item's requestMetadata carries the playable uri (stamped by TrackFactory for stable-id
280
+ * mediaIds, and round-tripped by Media3 controllers), fall back to a minimal track built from the
281
+ * item's own metadata instead of failing playback.
282
+ *
283
+ * @throws IllegalStateException if the mediaId is not cached and no playable URL is available
220
284
  */
221
- fun resolveMediaIdsFromCache(mediaIds: List<String>): List<Track> {
222
- return mediaIds.map { mediaId ->
223
- Timber.d("=== Resolving from cache: mediaId='$mediaId' ===")
285
+ private fun resolveMediaItemToTrack(mediaItem: MediaItem): Track {
286
+ val mediaId = mediaItem.mediaId
224
287
 
225
- getCachedTrack(mediaId)?.let { cachedTrack ->
226
- Timber.d("→ Found cached Track: '${cachedTrack.title}'")
227
- return@map cachedTrack
228
- }
288
+ getCachedTrack(mediaId)?.let { cachedTrack ->
289
+ Timber.d("Resolved mediaId='$mediaId' from cache: '${cachedTrack.title}'")
290
+ return cachedTrack
291
+ }
229
292
 
230
- // Cache miss - this indicates a bug in our caching system
231
- Timber.e("→ Cache MISS for mediaId='$mediaId' - this should not happen")
232
- throw IllegalStateException(
233
- "MediaItem not found in cache: $mediaId. This indicates a bug in the caching system."
293
+ val fallbackSrc =
294
+ if (mediaId.startsWith("http://") || mediaId.startsWith("https://")) {
295
+ mediaId
296
+ } else {
297
+ mediaItem.requestMetadata.mediaUri?.toString()
298
+ }
299
+ if (fallbackSrc != null) {
300
+ Timber.w("Cache MISS for mediaId='$mediaId' - building minimal track from media item")
301
+ val metadata = mediaItem.mediaMetadata
302
+ return hydrateFavorite(
303
+ Track(
304
+ // A mediaId distinct from the playable uri is the track's stable id —
305
+ // keep it so the item's identity (car now-playing row match) survives.
306
+ id = mediaId.takeIf { it != fallbackSrc },
307
+ url = null,
308
+ src = fallbackSrc,
309
+ artwork = artworkOf(metadata.artworkUri?.toString()),
310
+ artworkSource = null,
311
+ request = null,
312
+ artworkCarPlayTinted = null,
313
+ title = metadata.title?.toString() ?: mediaId,
314
+ subtitle = metadata.artist?.toString(),
315
+ artist = null,
316
+ albumUrl = null,
317
+ album = metadata.albumTitle?.toString(),
318
+ description = metadata.description?.toString(),
319
+ genre = metadata.genre?.toString(),
320
+ duration = null,
321
+ style = null,
322
+ childrenStyle = null,
323
+ favorited = null,
324
+ groupTitle = null,
325
+ live = null,
326
+ imageRow = null,
327
+ )
234
328
  )
235
329
  }
330
+
331
+ throw IllegalStateException("MediaItem not found in cache: $mediaId")
236
332
  }
237
333
 
238
334
  /**
@@ -249,7 +345,7 @@ class BrowserManager {
249
345
  * @param startIndex Index of the item to start playing
250
346
  * @param startPositionMs Position within the start item to begin playback
251
347
  * @return MediaSession.MediaItemsWithStartPosition ready for Media3
252
- * @throws IllegalStateException if any mediaId is not found in cache
348
+ * @throws IllegalStateException if a mediaId is neither cached nor a playable URL
253
349
  */
254
350
  suspend fun resolveMediaItemsForPlayback(
255
351
  mediaItems: List<MediaItem>,
@@ -270,10 +366,13 @@ class BrowserManager {
270
366
  val searchTracks = searchResults.children
271
367
 
272
368
  if (searchTracks != null && searchTracks.isNotEmpty()) {
273
- // Find the selected track in search results
369
+ // Find the selected track in search results (mediaId is the stable id
370
+ // when the track has one, else url/src — see TrackFactory)
274
371
  val mediaId = mediaItem.mediaId
275
372
  val selectedIndex =
276
- searchTracks.indexOfFirst { track -> track.url == mediaId || track.src == mediaId }
373
+ searchTracks.indexOfFirst { track ->
374
+ track.id == mediaId || track.url == mediaId || track.src == mediaId
375
+ }
277
376
 
278
377
  if (selectedIndex >= 0) {
279
378
  Timber.d(
@@ -297,11 +396,14 @@ class BrowserManager {
297
396
  }
298
397
 
299
398
  val mediaId = mediaItems[0].mediaId
399
+ // A failed search match falls through to plain resolution, never to browse
400
+ // expansion — the user asked for that one result, not a browsed list.
401
+ val contextualUrl = if (searchQuery == null) contextualUrlFor(mediaId) else null
300
402
 
301
- if (BrowserPathHelper.isContextual(mediaId)) {
302
- Timber.d("Attempting queue expansion for mediaId='$mediaId'")
403
+ if (contextualUrl != null) {
404
+ Timber.d("Attempting queue expansion for mediaId='$mediaId' via '$contextualUrl'")
303
405
 
304
- val expanded = expandQueueFromContextualUrl(mediaId)
406
+ val expanded = expandQueueFromContextualUrl(contextualUrl)
305
407
 
306
408
  if (expanded != null) {
307
409
  val (tracks, selectedIndex) = expanded
@@ -318,12 +420,11 @@ class BrowserManager {
318
420
  }
319
421
  }
320
422
 
321
- // No expansion - resolve from cache
322
- val mediaIds = mediaItems.map { it.mediaId }
323
- val cachedTracks = resolveMediaIdsFromCache(mediaIds)
423
+ // No expansion - resolve from cache (with a minimal-track fallback for replayed mediaIds)
424
+ val resolvedTracks = mediaItems.map { resolveMediaItemToTrack(it) }
324
425
 
325
426
  // Convert to Media3 MediaItems
326
- val resolvedMediaItems = cachedTracks.map { track -> TrackFactory.toMedia3(track) }
427
+ val resolvedMediaItems = resolvedTracks.map { track -> TrackFactory.toMedia3(track) }
327
428
 
328
429
  return MediaSession.MediaItemsWithStartPosition(resolvedMediaItems, startIndex, startPositionMs)
329
430
  }
@@ -355,6 +456,11 @@ class BrowserManager {
355
456
  if (useCache) {
356
457
  contentCache.get(normalizedPath)?.let { cached ->
357
458
  Timber.d("Content cache HIT for path='$normalizedPath'")
459
+ // Re-key the track cache even on a hit: an id-keyed lookup (stable-id
460
+ // mediaId → contextual url, see contextualUrlFor) must reflect the
461
+ // most recently *browsed* container, which a cached re-display
462
+ // otherwise wouldn't re-register.
463
+ cacheChildren(cached)
358
464
  // Re-hydrate favorites in case they changed since caching
359
465
  return hydrateChildren(cached)
360
466
  }
@@ -387,35 +493,37 @@ class BrowserManager {
387
493
  Timber.d("Invalidated content cache for path='$path'")
388
494
  }
389
495
 
390
- /** Clears all cached content. Used when artwork resolver is wired up to force re-fetch. */
496
+ /** Clears all cached content. */
391
497
  fun clearContentCache() {
392
498
  contentCache.evictAll()
499
+ // Bump the layer generation so request/browse resolver thunks are re-resolved on the next
500
+ // request (invalidateAllContent → clearContentCache is the documented re-resolve trigger).
501
+ layerGeneration += 1
502
+ // Invalidated content's artwork resolutions go with it (same staleness rule as the
503
+ // config setter).
504
+ artworkResolutions.clear()
505
+ onArtworkRegistriesCleared?.invoke()
393
506
  Timber.d("Cleared all content cache")
394
507
  }
395
508
 
396
509
  private suspend fun resolveUncached(path: String): ResolvedTrack {
397
- val routes = config.routes
398
- if (routes.isNullOrEmpty()) {
399
- Timber.e("No routes configured for path: $path")
400
- throw ContentNotFoundException(path)
510
+ // Match an explicit route (or the '*' default). With no match, fall back to
511
+ // the implicit default: fetch the path via the request + browse config.
512
+ val match = config.routes?.let { findBestRouteMatch(path, it) }
513
+
514
+ val resolvedTrack: ResolvedTrack
515
+ val effectiveArtworkConfig: ArtworkRequestConfig?
516
+ if (match != null) {
517
+ val (routeEntry, routeParams) = match
518
+ Timber.d("Matched route: ${routeEntry.path} with params: $routeParams")
519
+ resolvedTrack = resolveRouteEntry(routeEntry, path, routeParams)
520
+ effectiveArtworkConfig = routeEntry.artwork ?: config.artwork
521
+ } else {
522
+ Timber.d("No route matched for path: $path — using implicit default")
523
+ resolvedTrack = executeApiRequest(null, path, mapOf("path" to path))
524
+ effectiveArtworkConfig = config.artwork
401
525
  }
402
526
 
403
- // Find best matching route
404
- val (routeEntry, routeParams) =
405
- findBestRouteMatch(path, routes)
406
- ?: run {
407
- Timber.e("No route matched for path: $path")
408
- throw ContentNotFoundException(path)
409
- }
410
-
411
- Timber.d("Matched route: ${routeEntry.path} with params: $routeParams")
412
-
413
- // Resolve the track from the route
414
- val resolvedTrack = resolveRouteEntry(routeEntry, path, routeParams)
415
-
416
- // Get effective artwork config: per-route overrides global
417
- val effectiveArtworkConfig = routeEntry.artwork ?: config.artwork
418
-
419
527
  // Transform children: generate contextual URLs and transform artwork URLs
420
528
  val transformedChildren =
421
529
  resolvedTrack.children?.let { children ->
@@ -423,8 +531,11 @@ class BrowserManager {
423
531
  children
424
532
  .mapIndexed { index, track ->
425
533
  async {
426
- // Validate that track has stable identifier
427
- validateTrack(track, "Child track")
534
+ // Validate that track has stable identifier. A track carrying an
535
+ // imageRow is exempt: a url-less row is a pure preview — never
536
+ // selected, navigated to, or cached (its items carry their own
537
+ // identity; on Android Auto it expands into them).
538
+ if (track.imageRow.isNullOrEmpty()) validateTrack(track, "Child track")
428
539
 
429
540
  var transformedTrack = track
430
541
 
@@ -446,22 +557,35 @@ class BrowserManager {
446
557
  )
447
558
  }
448
559
 
449
- // Transform artwork URL if resolver is configured
450
- // At browse-time, we don't have display size info
451
- val resolver = artworkUrlResolver
452
- if (resolver != null) {
453
- val browseContext = ImageContext(null, null)
560
+ // Playable image-row items get contextual URLs like any list
561
+ // row, so a tile tap expands into its section's queue instead
562
+ // of a queue of one (ADR 0006).
563
+ transformedTrack.imageRow?.let { items ->
454
564
  transformedTrack =
455
- transformArtworkUrl(
456
- transformedTrack,
457
- effectiveArtworkConfig,
458
- resolver,
459
- path,
460
- index,
461
- browseContext,
565
+ transformedTrack.copy(
566
+ imageRow =
567
+ items
568
+ .map { item ->
569
+ if (item.url == null && item.src != null) {
570
+ item.copy(url = BrowserPathHelper.build(path, item.src))
571
+ } else {
572
+ item
573
+ }
574
+ }
575
+ .toTypedArray()
462
576
  )
463
577
  }
464
578
 
579
+ // Transform artwork URL. At browse-time there is no display size info.
580
+ transformedTrack =
581
+ transformArtworkUrl(
582
+ transformedTrack,
583
+ effectiveArtworkConfig,
584
+ path,
585
+ index,
586
+ ImageContext(null, null),
587
+ )
588
+
465
589
  transformedTrack
466
590
  }
467
591
  }
@@ -478,7 +602,7 @@ class BrowserManager {
478
602
  }
479
603
 
480
604
  /**
481
- * Transforms a track's artwork using the configured resolver. Populates artworkSource with the
605
+ * Transforms a track's artwork via [resolveArtworkUrl]. Populates artworkSource with the
482
606
  * transformed ImageSource, keeping artwork unchanged. Handles all edge cases: undefined returns,
483
607
  * errors, missing artwork.
484
608
  *
@@ -487,7 +611,6 @@ class BrowserManager {
487
611
  private suspend fun transformArtworkUrl(
488
612
  track: Track,
489
613
  artworkConfig: ArtworkRequestConfig?,
490
- resolver: suspend (Track, ArtworkRequestConfig?, ImageContext?) -> ImageSource?,
491
614
  path: String,
492
615
  index: Int,
493
616
  imageContext: ImageContext? = null,
@@ -498,7 +621,7 @@ class BrowserManager {
498
621
  }
499
622
 
500
623
  return try {
501
- val imageSource = resolver(track, artworkConfig, imageContext)
624
+ val imageSource = resolveArtworkUrl(track, artworkConfig, imageContext)
502
625
 
503
626
  when {
504
627
  // resolve returned null → no artwork source
@@ -511,6 +634,15 @@ class BrowserManager {
511
634
  // resolve returned ImageSource → set artworkSource
512
635
  else -> {
513
636
  Timber.d("[$path] Child[$index] '${track.title}': artworkSource set: ${imageSource.uri}")
637
+ // Remember how this URI was produced so display-time loading (which only
638
+ // gets a URI from Media3) can re-resolve Track-first with a size hint.
639
+ // Register the per-route config only when it isn't the global fallback,
640
+ // so display-time resolution reads the *current* config.artwork.
641
+ artworkResolutions.register(
642
+ imageSource.uri,
643
+ track,
644
+ artworkConfig?.takeIf { it !== config.artwork },
645
+ )
514
646
  track.copy(artworkSource = imageSource)
515
647
  }
516
648
  }
@@ -549,8 +681,20 @@ class BrowserManager {
549
681
  return null
550
682
  }
551
683
 
684
+ // Queue scope is the tapped section, not the whole page (ADR 0006). An
685
+ // id that no longer appears on the page aborts the expansion — the
686
+ // caller falls back to the stored single track; silently queueing the
687
+ // changed list would resume the wrong station.
688
+ val sectionTracks =
689
+ when (val section = SectionScope.section(children.toList(), trackId)) {
690
+ is SectionScope.Section.ImageRow ->
691
+ with(TrackFactory) { section.items.map { it.toTrack(groupTitle = null) } }
692
+ is SectionScope.Section.Run -> section.tracks
693
+ null -> return null
694
+ }
695
+
552
696
  // Filter to only playable tracks (tracks with src)
553
- val playableTracks = children.filter { track -> track.src != null }
697
+ val playableTracks = sectionTracks.filter { track -> track.src != null }
554
698
 
555
699
  if (playableTracks.isEmpty()) {
556
700
  Timber.w("Parent has no playable tracks, cannot expand queue")
@@ -642,8 +786,9 @@ class BrowserManager {
642
786
  * @return Array of Track results, or null if not found
643
787
  */
644
788
  fun getCachedSearchResults(query: String): Array<Track>? {
645
- if (query != lastSearchQuery) return null
646
- return lastSearchResults?.map { hydrateFavorite(it) }?.toTypedArray()
789
+ val (cachedQuery, cachedResults) = lastSearch ?: return null
790
+ if (query != cachedQuery) return null
791
+ return cachedResults.map { hydrateFavorite(it) }.toTypedArray()
647
792
  }
648
793
 
649
794
  /**
@@ -662,6 +807,7 @@ class BrowserManager {
662
807
  album = null,
663
808
  title = null,
664
809
  playlist = null,
810
+ reference = MediaReference.UNKNOWN,
665
811
  )
666
812
  )
667
813
  }
@@ -686,10 +832,11 @@ class BrowserManager {
686
832
 
687
833
  // Check if result is browsable-only (container/route) vs playable
688
834
  // If it's browsable but also playable (has src or playable=true), treat it as playable
835
+ val firstResultUrl = firstResult.url
689
836
  val tracksToFilter =
690
- if (firstResult.src == null) {
691
- Timber.d("First search result is browsable-only, resolving: ${firstResult.url}")
692
- val resolvedTrack = resolve(firstResult.url!!)
837
+ if (firstResult.src == null && firstResultUrl != null) {
838
+ Timber.d("First search result is browsable-only, resolving: $firstResultUrl")
839
+ val resolvedTrack = resolve(firstResultUrl)
693
840
  resolvedTrack.children
694
841
  ?.filter { it.src != null }
695
842
  ?.takeIf { it.isNotEmpty() }
@@ -717,6 +864,7 @@ class BrowserManager {
717
864
  album = null,
718
865
  title = null,
719
866
  playlist = null,
867
+ reference = MediaReference.UNKNOWN,
720
868
  )
721
869
  )
722
870
  }
@@ -735,20 +883,34 @@ class BrowserManager {
735
883
  val searchPath = BrowserPathHelper.createSearchPath(params.query)
736
884
 
737
885
  try {
738
- // Execute search
739
- val searchResults = resolveSearch(params)
886
+ // Execute search. Drop results without a stable identifier (url or src): search results come
887
+ // from server/JS data that doesn't pass through validateTrack like browse children do, and
888
+ // downstream conversion (TrackFactory.toMedia3) and browsable-result resolution require one.
889
+ val searchResults =
890
+ resolveSearch(params)
891
+ .filter { track ->
892
+ val valid = track.url != null || track.src != null
893
+ if (!valid) {
894
+ Timber.w("Dropping search result without url or src: '${track.title}'")
895
+ }
896
+ valid
897
+ }
898
+ .toTypedArray()
740
899
 
741
900
  // Create ResolvedTrack
742
901
  val searchResolvedTrack =
743
902
  ResolvedTrack(
903
+ id = null,
744
904
  url = searchPath,
745
905
  title = "Search: ${params.query}",
746
906
  children = searchResults,
747
907
  carPlaySiriListButton = null,
748
908
  artwork = null,
749
909
  artworkSource = null,
910
+ request = null,
750
911
  artworkCarPlayTinted = null,
751
912
  artist = null,
913
+ albumUrl = null,
752
914
  description = null,
753
915
  subtitle = null,
754
916
  album = null,
@@ -764,8 +926,7 @@ class BrowserManager {
764
926
  )
765
927
 
766
928
  // Cache search results for getCachedSearchResults()
767
- lastSearchQuery = params.query
768
- lastSearchResults = searchResults
929
+ lastSearch = params.query to searchResults
769
930
 
770
931
  // Cache individual tracks for Media3 lookups
771
932
  cacheChildren(searchResolvedTrack)
@@ -780,14 +941,17 @@ class BrowserManager {
780
941
  // Return empty search result on error
781
942
  val emptySearchResult =
782
943
  ResolvedTrack(
944
+ id = null,
783
945
  url = searchPath,
784
946
  title = "Search: ${params.query}",
785
947
  children = emptyArray(),
786
948
  carPlaySiriListButton = null,
787
949
  artwork = null,
788
950
  artworkSource = null,
951
+ request = null,
789
952
  artworkCarPlayTinted = null,
790
953
  artist = null,
954
+ albumUrl = null,
791
955
  description = null,
792
956
  subtitle = null,
793
957
  album = null,
@@ -848,6 +1012,15 @@ class BrowserManager {
848
1012
  onTabsChanged = callback
849
1013
  }
850
1014
 
1015
+ /**
1016
+ * Set callback invoked whenever both [artworkResolutions] and the browse-artwork registry are
1017
+ * cleared (config swap or content invalidation). The caller should clear any parallel
1018
+ * [com.audiobrowser.browser.BrowseArtworkRegistry] instance it owns.
1019
+ */
1020
+ fun setOnArtworkRegistriesCleared(callback: () -> Unit) {
1021
+ onArtworkRegistriesCleared = callback
1022
+ }
1023
+
851
1024
  /**
852
1025
  * Query navigation tabs from the configured tabs source. This is an async operation that resolves
853
1026
  * the tabs configuration.
@@ -925,15 +1098,25 @@ class BrowserManager {
925
1098
  entry.browseCallback?.let { callback ->
926
1099
  Timber.d("Resolving route via callback")
927
1100
  val param = BrowserSourceCallbackParam(path, routeParams)
928
- val promise = callback.invoke(param)
929
- val innerPromise = promise.await()
930
- val result = innerPromise.await()
931
-
932
- // Handle BrowseResult variant: either ResolvedTrack or BrowseError
933
- return result.match(
934
- first = { resolvedTrack -> resolvedTrack },
935
- second = { browseError -> throw CallbackException(browseError.error) }
936
- )
1101
+ // BrowserSourceCallback may return a BrowseResult synchronously or via a
1102
+ // Promise. Nitro flattens (ResolvedTrack | BrowseError) | Promise<BrowseResult>
1103
+ // into a 3-arm variant: sync track, sync error, or a Promise resolving to a
1104
+ // BrowseResult (which is itself a ResolvedTrack | BrowseError variant).
1105
+ return callback
1106
+ .invoke(param)
1107
+ .await()
1108
+ .match(
1109
+ first = { resolvedTrack -> resolvedTrack },
1110
+ second = { browseError -> throw CallbackException(browseError.error) },
1111
+ third = { promise ->
1112
+ promise
1113
+ .await()
1114
+ .match(
1115
+ first = { resolvedTrack -> resolvedTrack },
1116
+ second = { browseError -> throw CallbackException(browseError.error) },
1117
+ )
1118
+ },
1119
+ )
937
1120
  }
938
1121
 
939
1122
  entry.browseConfig?.let { apiConfig ->
@@ -999,34 +1182,119 @@ class BrowserManager {
999
1182
  }
1000
1183
 
1001
1184
  /**
1002
- * Execute an API request for browser content. Handles URL parameter substitution, config merging,
1003
- * and transforms.
1185
+ * Resolves a single request/browse layer. When a resolver thunk is present it is invoked and its
1186
+ * result awaited. The resolver is Promise-only (the TS layer normalizes a sync-or-async thunk via
1187
+ * Promise.resolve), so its native shape is `Promise<Promise<TransformableRequestConfig>>` — a
1188
+ * double await. When there is no resolver the static layer config is returned as-is.
1189
+ */
1190
+ private suspend fun resolveLayer(
1191
+ staticConfig: TransformableRequestConfig?,
1192
+ resolver: Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____?,
1193
+ ): TransformableRequestConfig? {
1194
+ if (resolver == null) return staticConfig
1195
+ // Promise-only resolver (the TS layer wraps a sync-or-async thunk in
1196
+ // Promise.resolve) → double await: the bridge promise, then the JS promise.
1197
+ return resolver.invoke().await().await()
1198
+ }
1199
+
1200
+ /**
1201
+ * Ensures the request/browse resolver layers are resolved for the current [layerGeneration],
1202
+ * caching the result. Re-resolves when the generation changes (config set or content
1203
+ * invalidation). Uses a simple generation guard with no in-flight cache: a benign idempotent
1204
+ * double-resolve under concurrent first-requests is acceptable, and on a thrown resolver nothing
1205
+ * is cached so the next request retries naturally.
1206
+ */
1207
+ internal suspend fun ensureLayersResolved() {
1208
+ if (resolvedLayerGeneration == layerGeneration) return
1209
+ val generation = layerGeneration
1210
+ val req = resolveLayer(config.request, config.requestResolver)
1211
+ val brw = resolveLayer(config.browse, config.browseResolver)
1212
+ // A newer generation started while we were awaiting — drop this stale result.
1213
+ if (generation != layerGeneration) return
1214
+ resolvedRequestLayer = req
1215
+ resolvedBrowseLayer = brw
1216
+ resolvedLayerGeneration = generation
1217
+ }
1218
+
1219
+ /**
1220
+ * Ensures the request layer is resolved for the current generation and returns it (the resolver
1221
+ * result when a [BrowserConfig.requestResolver] is configured, else the static
1222
+ * [BrowserConfig.request]).
1223
+ *
1224
+ * Consumers outside the browse path (media URL building, artwork) must obtain the request layer
1225
+ * through this accessor rather than reading the static `config.request`, so a resolver-only
1226
+ * consumer still gets a baseUrl/headers/transform for media, artwork, and now-playing artwork.
1227
+ */
1228
+ internal suspend fun resolvedRequestConfig(): TransformableRequestConfig? {
1229
+ ensureLayersResolved()
1230
+ return resolvedRequestLayer
1231
+ }
1232
+
1233
+ /**
1234
+ * Builds the HTTP request for an API-backed path by layering request (shared) → kind
1235
+ * (browse/search) → route configs. Each layer's transform receives the previous layer's output; a
1236
+ * layer with no transform merges its static fields.
1237
+ *
1238
+ * `initialQuery` seeds query params onto the BASE the kind layer receives (e.g. search q/mode/…):
1239
+ * a layer with a transform "wins completely" and is handed only the base, so params placed on a
1240
+ * layer's own static query would be dropped before the transform runs. The same goes for `path`:
1241
+ * it is carried from the base through every layer (only a transform may change it), so a kind
1242
+ * whose config supplies the path (search) must seed it via the `path` parameter. Mirrors iOS
1243
+ * `buildApiRequest`.
1244
+ *
1245
+ * @throws ContentNotFoundException when no layer supplies a baseUrl — there is nothing to fetch,
1246
+ * so the path is genuinely "not found" rather than a network error (mirrors iOS's `guard let
1247
+ * baseUrl`).
1248
+ */
1249
+ internal suspend fun buildApiRequest(
1250
+ kindConfig: TransformableRequestConfig?,
1251
+ routeConfig: TransformableRequestConfig?,
1252
+ path: String?,
1253
+ params: Map<String, String>,
1254
+ initialQuery: Map<String, String>? = null,
1255
+ ): HttpClient.HttpRequest {
1256
+ // Resolve the request/browse resolver thunks once per content generation (cached).
1257
+ ensureLayersResolved()
1258
+
1259
+ var merged =
1260
+ RequestConfig(
1261
+ method = null,
1262
+ path = path,
1263
+ baseUrl = null,
1264
+ headers = null,
1265
+ query = null,
1266
+ body = null,
1267
+ contentType = null,
1268
+ userAgent = null,
1269
+ )
1270
+ resolvedRequestLayer?.let { merged = RequestConfigBuilder.mergeConfig(merged, it, params) }
1271
+ if (!initialQuery.isNullOrEmpty()) {
1272
+ merged = merged.copy(query = (merged.query ?: emptyMap()) + initialQuery)
1273
+ }
1274
+ kindConfig?.let { merged = RequestConfigBuilder.mergeConfig(merged, it, params) }
1275
+ routeConfig?.let { merged = RequestConfigBuilder.mergeConfig(merged, it, params) }
1276
+
1277
+ if (merged.baseUrl.isNullOrBlank()) {
1278
+ throw ContentNotFoundException(path ?: "")
1279
+ }
1280
+ return RequestConfigBuilder.buildHttpRequest(merged)
1281
+ }
1282
+
1283
+ /**
1284
+ * Execute an API request for browser content. Request building (layering + transforms + baseUrl
1285
+ * guard) lives in [buildApiRequest]; this adds the browse-specific response shape (a
1286
+ * ResolvedTrack page object).
1004
1287
  */
1005
1288
  private suspend fun executeApiRequest(
1006
- apiConfig: TransformableRequestConfig,
1289
+ apiConfig: TransformableRequestConfig?,
1007
1290
  path: String,
1008
1291
  routeParams: Map<String, String>,
1009
1292
  ): ResolvedTrack {
1010
1293
  return withContext(Dispatchers.IO) {
1011
- // 1. Start with base config, using the navigation path as default
1012
- val baseConfig =
1013
- config.request?.let { req ->
1014
- RequestConfigBuilder.toRequestConfig(req).copy(path = req.path ?: path)
1015
- }
1016
- ?: RequestConfig(
1017
- path = path,
1018
- method = null,
1019
- baseUrl = null,
1020
- headers = null,
1021
- query = null,
1022
- body = null,
1023
- contentType = null,
1024
- userAgent = null,
1025
- )
1026
- val mergedConfig = RequestConfigBuilder.mergeConfig(baseConfig, apiConfig, routeParams)
1027
-
1028
- // 2. Build and execute HTTP request
1029
- val httpRequest = RequestConfigBuilder.buildHttpRequest(mergedConfig)
1294
+ // request (shared) → browse (kind) → route. apiConfig is null for the
1295
+ // implicit default (an unmatched browse path → fetch via request + browse + path).
1296
+ ensureLayersResolved()
1297
+ val httpRequest = buildApiRequest(resolvedBrowseLayer, apiConfig, path, routeParams)
1030
1298
  val response = httpClient.request(httpRequest)
1031
1299
 
1032
1300
  response.fold(
@@ -1066,24 +1334,10 @@ class BrowserManager {
1066
1334
  ): Array<Track> {
1067
1335
  return withContext(Dispatchers.IO) {
1068
1336
  try {
1069
- // 1. Start with base config
1070
- val baseConfig =
1071
- config.request?.let { RequestConfigBuilder.toRequestConfig(it) }
1072
- ?: RequestConfig(
1073
- method = null,
1074
- path = null,
1075
- baseUrl = null,
1076
- headers = null,
1077
- query = null,
1078
- body = null,
1079
- contentType = null,
1080
- userAgent = null,
1081
- )
1082
-
1083
- // 2. Build query parameters from SearchParams
1084
1337
  val searchQueryParams = buildMap {
1085
1338
  put("q", params.query)
1086
1339
  params.mode?.let { put("mode", it.toString().lowercase()) }
1340
+ if (params.reference == MediaReference.MY) put("reference", "my")
1087
1341
  params.genre?.let { put("genre", it) }
1088
1342
  params.artist?.let { put("artist", it) }
1089
1343
  params.album?.let { put("album", it) }
@@ -1091,31 +1345,26 @@ class BrowserManager {
1091
1345
  params.playlist?.let { put("playlist", it) }
1092
1346
  }
1093
1347
 
1094
- // 3. Create a copy of API config with added search parameters
1095
- val searchConfig =
1096
- TransformableRequestConfig(
1097
- transform = apiConfig.transform,
1098
- method = apiConfig.method,
1348
+ // request (shared) → search (kind); no browse layer and no route — search
1349
+ // is its own kind. The search params seed the base (see buildApiRequest
1350
+ // docs), and so does the search config's path: a layer's static path
1351
+ // never applies (the path is carried from the base), so the caller
1352
+ // seeds it — mirrors the web stub's fetchSearchResults.
1353
+ val httpRequest =
1354
+ buildApiRequest(
1355
+ kindConfig = apiConfig,
1356
+ routeConfig = null,
1099
1357
  path = apiConfig.path,
1100
- baseUrl = apiConfig.baseUrl,
1101
- headers = apiConfig.headers,
1102
- query = (apiConfig.query ?: emptyMap()) + searchQueryParams,
1103
- body = apiConfig.body,
1104
- contentType = apiConfig.contentType,
1105
- userAgent = apiConfig.userAgent,
1358
+ params = emptyMap(),
1359
+ initialQuery = searchQueryParams,
1106
1360
  )
1107
-
1108
- // 3. Merge configs and apply transform if provided
1109
- var mergedConfig = RequestConfigBuilder.mergeConfig(baseConfig, searchConfig, emptyMap())
1110
-
1111
- // 4. Build and execute HTTP request
1112
- val httpRequest = RequestConfigBuilder.buildHttpRequest(mergedConfig)
1113
1361
  val response = httpClient.request(httpRequest)
1114
1362
 
1115
1363
  response.fold(
1116
1364
  onSuccess = { httpResponse ->
1117
1365
  if (httpResponse.isSuccessful) {
1118
- // 4. Parse response as Track array
1366
+ // The search endpoint returns a bare Track array (unlike browse,
1367
+ // which returns a page object). iOS parses it the same way.
1119
1368
  val jsonTracks = json.decodeFromString<List<JsonTrack>>(httpResponse.body)
1120
1369
  jsonTracks.map { it.toNitro() }.toTypedArray()
1121
1370
  } else {
@@ -1156,8 +1405,23 @@ class CallbackException(message: String) : Exception(message)
1156
1405
  */
1157
1406
  data class BrowserConfig(
1158
1407
  val request: TransformableRequestConfig? = null,
1408
+ // Resolver thunk for the shared request layer. When set, it is resolved once per content
1409
+ // generation (re-resolved after invalidateAllContent), cached, and merged per request — instead
1410
+ // of carrying a static `request`. `request` and `requestResolver` are mutually exclusive in
1411
+ // practice (the consumer sets one or the other), but both are merged if present.
1412
+ val requestResolver:
1413
+ Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____? =
1414
+ null,
1415
+ val browse: TransformableRequestConfig? = null,
1416
+ // Resolver thunk for the browse layer. See `requestResolver`.
1417
+ val browseResolver:
1418
+ Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____? =
1419
+ null,
1159
1420
  val media: MediaRequestConfig? = null,
1160
1421
  val artwork: ArtworkRequestConfig? = null,
1422
+ // Now-playing-only artwork configuration (lock screen / notification / Android Auto now-playing).
1423
+ // A distinct kind from `artwork`; the now-playing path falls back to `artwork` when this is null.
1424
+ val nowPlayingArtwork: ArtworkRequestConfig? = null,
1161
1425
  // Routes as array with flattened entries (includes __tabs__, __search__, and __default__ special
1162
1426
  // routes)
1163
1427
  val routes: Array<NativeRouteEntry>? = null,