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
@@ -1,35 +1,41 @@
1
1
  package com.audiobrowser.player
2
2
 
3
3
  import android.os.Bundle
4
- import androidx.media3.common.HeartRating
5
4
  import androidx.media3.common.MediaItem
6
5
  import androidx.media3.common.MediaMetadata
7
6
  import androidx.media3.common.Rating
8
7
  import androidx.media3.session.LibraryResult
8
+ import androidx.media3.session.MediaConstants
9
9
  import androidx.media3.session.MediaLibraryService
10
10
  import androidx.media3.session.MediaSession
11
- import androidx.media3.session.MediaConstants
12
11
  import androidx.media3.session.SessionCommand
13
12
  import androidx.media3.session.SessionError
14
13
  import androidx.media3.session.SessionResult
15
- import com.audiobrowser.AudioBrowser
16
14
  import com.audiobrowser.browser.handleTrackLoad
15
+ import com.audiobrowser.extension.toTrack
17
16
  import com.audiobrowser.util.BrowserPathHelper
18
- import com.audiobrowser.util.RatingFactory
19
- import com.audiobrowser.util.ResolvedTrackFactory
17
+ import com.audiobrowser.util.RatingFavorites
20
18
  import com.audiobrowser.util.TrackFactory
21
- import com.margelo.nitro.audiobrowser.Track
22
19
  import com.google.common.collect.ImmutableList
23
20
  import com.google.common.util.concurrent.Futures
24
21
  import com.google.common.util.concurrent.ListenableFuture
25
- import com.margelo.nitro.audiobrowser.FavoriteChangedEvent
26
- import com.margelo.nitro.audiobrowser.NotificationButtonLayout
22
+ import com.margelo.nitro.audiobrowser.Gate
23
+ import com.margelo.nitro.audiobrowser.GateEvent
24
+ import com.margelo.nitro.audiobrowser.GateReason
25
+ import com.margelo.nitro.audiobrowser.MediaReference
26
+ import com.margelo.nitro.audiobrowser.NativeGateRequest
27
27
  import com.margelo.nitro.audiobrowser.PlayerCapabilities
28
- import com.margelo.nitro.audiobrowser.RemoteSetRatingEvent
28
+ import com.margelo.nitro.audiobrowser.RemoteButtonLayout
29
+ import com.margelo.nitro.audiobrowser.SearchParams
30
+ import com.margelo.nitro.audiobrowser.Track
31
+ import kotlinx.coroutines.CancellationException
29
32
  import kotlinx.coroutines.CoroutineScope
30
33
  import kotlinx.coroutines.Dispatchers
31
34
  import kotlinx.coroutines.SupervisorJob
35
+ import kotlinx.coroutines.TimeoutCancellationException
36
+ import kotlinx.coroutines.cancel
32
37
  import kotlinx.coroutines.guava.future
38
+ import kotlinx.coroutines.launch
33
39
  import kotlinx.coroutines.withContext
34
40
  import timber.log.Timber
35
41
 
@@ -42,7 +48,9 @@ class MediaSessionCallback(private val player: Player) :
42
48
  internal val commandManager = MediaSessionCommandManager()
43
49
  private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
44
50
 
45
- // Track which controllers are subscribed to which media IDs
51
+ // Track which controllers are subscribed to which media IDs.
52
+ // Mutated on the Media3 application thread (onSubscribe/onUnsubscribe) but read from other
53
+ // threads (network observer, JS-triggered notifies) — guard all access with synchronized(this).
46
54
  private val parentIdSubscriptions =
47
55
  mutableMapOf<String, MutableSet<MediaSession.ControllerInfo>>()
48
56
  private var mediaLibrarySession: MediaLibraryService.MediaLibrarySession? = null
@@ -51,7 +59,8 @@ class MediaSessionCallback(private val player: Player) :
51
59
  * Recommended artwork size in pixels from the connected media browser (e.g., Android Auto).
52
60
  * Updated when onGetLibraryRoot is called with EXTRAS_KEY_MEDIA_ART_SIZE_PIXELS hint.
53
61
  */
54
- @Volatile var artworkSizeHintPixels: Int? = null
62
+ @Volatile
63
+ var artworkSizeHintPixels: Int? = null
55
64
  private set
56
65
 
57
66
  init {
@@ -72,36 +81,99 @@ class MediaSessionCallback(private val player: Player) :
72
81
  }
73
82
 
74
83
  /** Creates an offline error MediaItem. */
75
- private fun createOfflineMediaItem(): MediaItem {
76
- val errorTitle = player.context.getString(com.audiobrowser.R.string.audio_browser_offline_error)
77
- val errorSubtitle =
78
- player.context.getString(com.audiobrowser.R.string.audio_browser_offline_error_subtitle)
79
- return MediaItem.Builder()
80
- .setMediaId(BrowserPathHelper.OFFLINE_PATH)
84
+ private fun createOfflineMediaItem(): MediaItem =
85
+ createErrorMediaItem(
86
+ mediaId = BrowserPathHelper.OFFLINE_PATH,
87
+ title = player.context.getString(com.audiobrowser.R.string.audio_browser_offline_error),
88
+ subtitle =
89
+ player.context.getString(com.audiobrowser.R.string.audio_browser_offline_error_subtitle),
90
+ )
91
+
92
+ /**
93
+ * Builds a non-browsable, non-playable [MediaItem] used to surface an error inside an Android
94
+ * Auto / AAOS browse list. Rendered as a greyed-out, non-interactive tile, which is the only
95
+ * side-effect-free way to communicate a browse failure to legacy controllers (Media3 drops
96
+ * [LibraryResult.ofError] on the legacy browse bridge, leaving an empty "No items" screen).
97
+ */
98
+ private fun createErrorMediaItem(mediaId: String, title: String, subtitle: String): MediaItem =
99
+ MediaItem.Builder()
100
+ .setMediaId(mediaId)
81
101
  .setMediaMetadata(
82
102
  MediaMetadata.Builder()
83
- .setTitle(errorTitle)
84
- .setSubtitle(errorSubtitle)
103
+ .setTitle(title)
104
+ .setSubtitle(subtitle)
105
+ // Non-browsable, non-playable. Android Auto ignores these flags and still drills into a
106
+ // tapped tile, but onGetChildren returns an empty list for the sentinel paths so it's a
107
+ // harmless "No Items" dead-end rather than an endless stack of error pages.
85
108
  .setIsBrowsable(false)
86
109
  .setIsPlayable(false)
87
110
  .build()
88
111
  )
89
112
  .build()
90
- }
113
+
114
+ /**
115
+ * Builds the gate tile from a per-request chrome: while a gate is active, tabs stay visible but a
116
+ * gated browse/search level serves this single tile (see the gate checks in [onGetChildren] /
117
+ * [onGetSearchResult]). Same shape as the error tiles — non-browsable, non-playable — and
118
+ * deliberately NOT accompanied by a SessionError: a gate is deliberate app state, not a failure.
119
+ * The message renders as the tile's subtitle (newlines collapse to spaces — list rows are
120
+ * single-line).
121
+ */
122
+ /**
123
+ * Wraps a raw external-search query string into the structured [SearchParams] the gate request
124
+ * carries. The car search surfaces only give a free-text query, so the other fields stay null —
125
+ * mirrors BrowserManager's query→SearchParams construction.
126
+ */
127
+ private fun searchParams(query: String): SearchParams =
128
+ SearchParams(
129
+ mode = null,
130
+ query = query,
131
+ genre = null,
132
+ artist = null,
133
+ album = null,
134
+ title = null,
135
+ playlist = null,
136
+ reference = MediaReference.UNKNOWN,
137
+ )
138
+
139
+ private fun createGateMediaItem(gate: Gate): MediaItem =
140
+ createErrorMediaItem(
141
+ mediaId = BrowserPathHelper.GATE_PATH,
142
+ title = gate.title,
143
+ subtitle = gate.message?.replace('\n', ' ') ?: "",
144
+ )
145
+
146
+ /**
147
+ * Builds a generic "something went wrong" error tile for a browse failure. The true offline case
148
+ * is handled separately by the [networkMonitor] guard in [onGetChildren]; anything reaching the
149
+ * catch block is an online-but-failed request (e.g. server down, bad status), so it must NOT be
150
+ * labelled "no internet connection".
151
+ */
152
+ private fun createBrowseErrorMediaItem(): MediaItem =
153
+ createErrorMediaItem(
154
+ mediaId = BrowserPathHelper.ERROR_PATH,
155
+ title = player.context.getString(com.audiobrowser.R.string.audio_browser_browse_error),
156
+ subtitle =
157
+ player.context.getString(com.audiobrowser.R.string.audio_browser_browse_error_subtitle),
158
+ )
91
159
 
92
160
  fun updateMediaSession(
93
161
  mediaSession: MediaSession,
94
162
  capabilities: PlayerCapabilities,
95
- notificationButtons: NotificationButtonLayout?,
163
+ remoteButtonLayout: RemoteButtonLayout?,
96
164
  searchAvailable: Boolean,
165
+ forwardJumpInterval: Double,
166
+ backwardJumpInterval: Double,
97
167
  ) {
98
168
  // Store as MediaLibrarySession for notifyChildrenChanged support
99
169
  this.mediaLibrarySession = mediaSession as? MediaLibraryService.MediaLibrarySession
100
170
  commandManager.updateMediaSession(
101
171
  mediaSession,
102
172
  capabilities,
103
- notificationButtons,
173
+ remoteButtonLayout,
104
174
  searchAvailable,
175
+ forwardJumpInterval,
176
+ backwardJumpInterval,
105
177
  )
106
178
  }
107
179
 
@@ -113,18 +185,6 @@ class MediaSessionCallback(private val player: Player) :
113
185
  return commandManager.buildConnectionResult(session)
114
186
  }
115
187
 
116
- /** Updates the favorite state of the current track and emits onFavoriteChanged. */
117
- private fun setFavorited(favorited: Boolean) {
118
- val currentTrack = player.currentTrack ?: return
119
-
120
- // Update native favorites cache (only tracks with src can be favorited)
121
- currentTrack.src?.let { src -> player.browser?.browserManager?.updateFavorite(src, favorited) }
122
-
123
- player.setActiveTrackFavorited(favorited)
124
- val event = FavoriteChangedEvent(player.currentTrack ?: currentTrack, favorited)
125
- player.callbacks?.onFavoriteChanged(event)
126
- }
127
-
128
188
  override fun onCustomCommand(
129
189
  session: MediaSession,
130
190
  controller: MediaSession.ControllerInfo,
@@ -135,7 +195,7 @@ class MediaSessionCallback(private val player: Player) :
135
195
  if (command.customAction == MediaSessionCommandManager.CUSTOM_ACTION_FAVORITE) {
136
196
  val currentFavorited = player.currentTrack?.favorited ?: false
137
197
  Timber.d("Favorite button tapped - toggling from $currentFavorited to ${!currentFavorited}")
138
- setFavorited(!currentFavorited)
198
+ player.setActiveTrackFavorited(!currentFavorited)
139
199
  return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
140
200
  }
141
201
 
@@ -150,15 +210,10 @@ class MediaSessionCallback(private val player: Player) :
150
210
  controller: MediaSession.ControllerInfo,
151
211
  rating: Rating,
152
212
  ): ListenableFuture<SessionResult> {
153
- if (rating is HeartRating) {
154
- setFavorited(rating.isHeart)
155
- }
156
-
157
- // Also emit onRemoteSetRating for listeners
158
- RatingFactory.media3ToBridge(rating)?.let {
159
- val event = RemoteSetRatingEvent(it)
160
- player.callbacks?.onRemoteSetRating(event)
161
- }
213
+ // A heart rating from a controller (e.g. Google Assistant "I like this") toggles the
214
+ // now-playing favorite. setActiveTrackFavorited fires onFavoriteChanged so the consumer
215
+ // persists it — the same path as the notification / CarPlay heart button.
216
+ RatingFavorites.favoritedFor(rating)?.let { player.setActiveTrackFavorited(it) }
162
217
  return super.onSetRating(session, controller, rating)
163
218
  }
164
219
 
@@ -220,25 +275,55 @@ class MediaSessionCallback(private val player: Player) :
220
275
  )
221
276
  return scope.future {
222
277
  // Wait for browser to be registered if it's not available yet
223
- val browserManager =
224
- player
225
- .awaitBrowser()
226
- .also { Timber.d("Browser ready, proceeding with onGetChildren") }
227
- .browserManager
278
+ val audioBrowser =
279
+ player.awaitBrowser().also { Timber.d("Browser ready, proceeding with onGetChildren") }
280
+ val browserManager = audioBrowser.browserManager
281
+
282
+ // While a gate is active, tabs stay visible (the root keeps serving them below) but each
283
+ // non-root level is resolved per request: a gated path — including re-queries of the gate
284
+ // tile's own sentinel path — serves the single gate tile, so drilling into it re-shows the
285
+ // message instead of dead-ending in "No items"; an allowed path falls through to real
286
+ // children. Checked before the offline guard: gated content isn't coming back with
287
+ // connectivity, so the gate copy is the truer message.
288
+ if (parentId != BrowserPathHelper.ROOT_PATH) {
289
+ val outcome =
290
+ audioBrowser.gateDecision(
291
+ NativeGateRequest(reason = GateReason.BROWSE, path = parentId, search = null)
292
+ )
293
+ if (outcome.gated) {
294
+ audioBrowser.onGate(GateEvent(GateReason.BROWSE))
295
+ // gateDecision guarantees non-null chrome on a gated decision (override → default →
296
+ // built-in).
297
+ return@future LibraryResult.ofItemList(
298
+ ImmutableList.of(createGateMediaItem(outcome.chrome!!)),
299
+ params,
300
+ )
301
+ }
302
+ }
303
+
304
+ // The error / offline tiles are dead-ends. Some controllers (e.g. Android Auto when online)
305
+ // treat a non-browsable tile as tappable and subscribe to its mediaId anyway; returning the
306
+ // error tile again here would push an endless stack of error pages. Return nothing instead.
307
+ // For the offline tile, re-send the alert when still offline so a tap surfaces the
308
+ // explanation again — but only then, since these paths are also re-queried by
309
+ // notifyChildrenChanged (e.g. when connectivity returns) without any user action.
310
+ if (parentId == BrowserPathHelper.OFFLINE_PATH || parentId == BrowserPathHelper.ERROR_PATH) {
311
+ if (parentId == BrowserPathHelper.OFFLINE_PATH && !player.networkMonitor.isOnline.value) {
312
+ sendBrowseError(session, browser, offline = true)
313
+ }
314
+ return@future LibraryResult.ofItemList(ImmutableList.of<MediaItem>(), params)
315
+ }
228
316
 
229
317
  // Show offline error when offline:
230
318
  if (
231
319
  !player.networkMonitor.isOnline.value && browserManager.config.androidControllerOfflineError
232
320
  ) {
233
321
  Timber.w("Network offline - returning error message for: $parentId")
322
+ sendBrowseError(session, browser, offline = true)
234
323
  return@future LibraryResult.ofItemList(ImmutableList.of(createOfflineMediaItem()), params)
235
324
  }
236
325
 
237
326
  try {
238
- // Get context and imageLoader for SVG pre-rendering
239
- val context = player.context
240
- val imageLoader = player.imageLoader
241
-
242
327
  val children =
243
328
  if (parentId == BrowserPathHelper.RECENT_PATH) {
244
329
  // TODO: implement recent media items
@@ -246,36 +331,90 @@ class MediaSessionCallback(private val player: Player) :
246
331
  } else if (parentId == BrowserPathHelper.ROOT_PATH) {
247
332
  // Return tabs as root children (limited to 4 for automotive platform compatibility)
248
333
  // TODO: Check what Android Auto does with empty tabs list - may need to return error?
249
- val tabs = browserManager.queryTabs().take(4)
250
- if (imageLoader != null) {
251
- tabs.map { tab -> TrackFactory.toMedia3WithSvgSupport(tab, context, imageLoader) }
252
- } else {
253
- tabs.map { tab -> TrackFactory.toMedia3(tab) }
334
+ val tabs = browserManager.queryTabs()
335
+ if (tabs.size > 4) {
336
+ Timber.w(
337
+ "Root has ${tabs.size} tabs; dropping ${tabs.size - 4} (Android Auto root limit)"
338
+ )
254
339
  }
340
+ toMediaItems(tabs.take(4))
255
341
  } else {
256
342
  // Resolve the specific path and get its children
257
343
  val resolvedTrack = browserManager.resolve(parentId)
258
344
 
259
345
  // Convert children to MediaItems (url is already set to contextual URL)
260
- val trackChildren = resolvedTrack.children
261
- ?: throw IllegalStateException(
262
- "Expected browsed ResolvedTrack to have a children array"
263
- )
264
- if (imageLoader != null) {
265
- trackChildren.map { track -> TrackFactory.toMedia3WithSvgSupport(track, context, imageLoader) }
266
- } else {
267
- trackChildren.map { track -> TrackFactory.toMedia3(track) }
268
- }
346
+ val trackChildren =
347
+ resolvedTrack.children
348
+ ?: throw IllegalStateException(
349
+ "Expected browsed ResolvedTrack to have a children array"
350
+ )
351
+ toMediaItems(trackChildren.toList())
269
352
  }
270
353
 
271
354
  LibraryResult.ofItemList(ImmutableList.copyOf(children.paginate(page, pageSize)), params)
272
355
  } catch (e: Exception) {
356
+ // A cancelled browse (controller disconnected) is not a failure — don't render it as an
357
+ // error tile. awaitBrowser's TimeoutCancellationException IS a real failure, keep that.
358
+ if (e is CancellationException && e !is TimeoutCancellationException) throw e
273
359
  Timber.e(e, "Error getting children for parentId: $parentId")
274
- LibraryResult.ofError(SessionError.ERROR_UNKNOWN)
360
+ // Surface an error tile instead of a bare ofError(): Media3 drops the error message on the
361
+ // legacy browse bridge, which would otherwise leave an empty "No items" screen in Android
362
+ // Auto. See https://github.com/androidx/media/issues/2901
363
+ //
364
+ // Verified on a head unit (2026-06): the AA browse list NEVER renders error text — not for
365
+ // transient sendError, sticky non-fatal replication, or fatal replication
366
+ // (setLibraryErrorReplicationMode). Fatal replication does show the message on the
367
+ // playback screen, but presents the session as STATE_ERROR with no actions, hiding the
368
+ // now-playing item and transport controls — not worth it. Tiles remain the only in-browse
369
+ // signal we control.
370
+ sendBrowseError(session, browser, offline = false)
371
+ LibraryResult.ofItemList(ImmutableList.of(createBrowseErrorMediaItem()), params)
275
372
  }
276
373
  }
277
374
  }
278
375
 
376
+ /**
377
+ * Sends a non-fatal [SessionError] to the controller whose browse failed, complementing the error
378
+ * tile. Media3 browsers receive it via MediaController.Listener.onError; for legacy controllers
379
+ * Media3 transiently attaches the error code/message to the platform session's playback state
380
+ * without entering STATE_ERROR.
381
+ *
382
+ * NOTE: current Android Auto renders NOTHING for this (verified on a head unit — see
383
+ * androidx/media#2901; the transient state is cleared microseconds after being set). It is kept
384
+ * because it is the correct Media3-API error signal, costs nothing, and becomes user-visible if
385
+ * Android Auto moves to consuming SessionError via the Media3 controller API.
386
+ *
387
+ * Dispatched to the main thread: the session and its legacy stub are application-thread bound.
388
+ */
389
+ @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
390
+ private fun sendBrowseError(
391
+ session: MediaLibraryService.MediaLibrarySession,
392
+ browser: MediaSession.ControllerInfo,
393
+ offline: Boolean,
394
+ ) {
395
+ val message =
396
+ player.context.getString(
397
+ if (offline) com.audiobrowser.R.string.audio_browser_offline_error
398
+ else com.audiobrowser.R.string.audio_browser_browse_error
399
+ )
400
+ val code = if (offline) SessionError.ERROR_IO else SessionError.ERROR_UNKNOWN
401
+ scope.launch(Dispatchers.Main) { session.sendError(browser, SessionError(code, message)) }
402
+ }
403
+
404
+ /**
405
+ * Converts tracks to MediaItems for browse delivery, routing http(s) artwork through the
406
+ * content:// provider so Android Auto can load it via the ArtworkContentProvider. Image-row
407
+ * tracks (a CarPlay-only rendering) are expanded into their items as regular grouped rows first —
408
+ * see [TrackFactory.expandImageRows].
409
+ */
410
+ private fun toMediaItems(tracks: List<Track>): List<MediaItem> {
411
+ val registry = player.browseArtworkRegistry
412
+ val authority = com.audiobrowser.util.ArtworkUris.authorityFor(player.context.packageName)
413
+ return TrackFactory.expandImageRows(tracks).map {
414
+ TrackFactory.toBrowseMediaItem(it, registry, authority)
415
+ }
416
+ }
417
+
279
418
  override fun onGetItem(
280
419
  session: MediaLibraryService.MediaLibrarySession,
281
420
  browser: MediaSession.ControllerInfo,
@@ -288,6 +427,30 @@ class MediaSessionCallback(private val player: Player) :
288
427
  return@future LibraryResult.ofItem(createOfflineMediaItem(), null)
289
428
  }
290
429
 
430
+ // Return the error tile as a non-browsable item so tapping it can't re-browse into a
431
+ // failing path (which would push another error page onto the stack).
432
+ if (mediaId == BrowserPathHelper.ERROR_PATH) {
433
+ return@future LibraryResult.ofItem(createBrowseErrorMediaItem(), null)
434
+ }
435
+
436
+ if (mediaId == BrowserPathHelper.GATE_PATH) {
437
+ // A direct fetch of the gate tile's own sentinel (a controller re-reading the item it was
438
+ // served). Resolve the chrome for the gate path but do NOT emit onGate — that fires at the
439
+ // browse/search serve sites, not on an item lookup.
440
+ val outcome =
441
+ player
442
+ .awaitBrowser()
443
+ .gateDecision(
444
+ NativeGateRequest(
445
+ reason = GateReason.BROWSE,
446
+ path = BrowserPathHelper.GATE_PATH,
447
+ search = null,
448
+ )
449
+ )
450
+ if (!outcome.gated) return@future LibraryResult.ofError(SessionError.ERROR_BAD_VALUE)
451
+ return@future LibraryResult.ofItem(createGateMediaItem(outcome.chrome!!), null)
452
+ }
453
+
291
454
  if (mediaId == BrowserPathHelper.ROOT_PATH || mediaId == BrowserPathHelper.RECENT_PATH) {
292
455
  return@future LibraryResult.ofItem(
293
456
  MediaItem.Builder()
@@ -303,11 +466,33 @@ class MediaSessionCallback(private val player: Player) :
303
466
  // Wait for browser to be registered if it's not available yet
304
467
  val browserManager = player.awaitBrowser().browserManager
305
468
 
469
+ // Serve tracks from the track cache first (keyed by url and src). Besides avoiding an HTTP
470
+ // resolve, this keeps item identity correct for contextual mediaIds: resolve() strips
471
+ // __trackId and would return the *parent container's* metadata as the item.
472
+ val browseAuthority =
473
+ com.audiobrowser.util.ArtworkUris.authorityFor(player.context.packageName)
474
+ browserManager.getCachedTrack(mediaId)?.let { track ->
475
+ return@future LibraryResult.ofItem(
476
+ TrackFactory.toBrowseMediaItem(track, player.browseArtworkRegistry, browseAuthority),
477
+ null,
478
+ )
479
+ }
480
+
306
481
  try {
307
482
  val resolvedTrack = browserManager.resolve(mediaId)
308
- val mediaItem = ResolvedTrackFactory.toMedia3(resolvedTrack)
309
- LibraryResult.ofItem(mediaItem, null)
483
+ // Through the one Track conversion, so the resolve path renders identically
484
+ // to the cached-track path above (list line from subtitle, favorited heart)
485
+ // and the item's tag is a Track, as fromMedia3 expects.
486
+ LibraryResult.ofItem(
487
+ TrackFactory.toBrowseMediaItem(
488
+ resolvedTrack.toTrack(),
489
+ player.browseArtworkRegistry,
490
+ browseAuthority,
491
+ ),
492
+ null,
493
+ )
310
494
  } catch (e: Exception) {
495
+ if (e is CancellationException && e !is TimeoutCancellationException) throw e
311
496
  Timber.e(e, "Error getting item for mediaId: $mediaId")
312
497
  LibraryResult.ofError(SessionError.ERROR_UNKNOWN)
313
498
  }
@@ -322,8 +507,17 @@ class MediaSessionCallback(private val player: Player) :
322
507
  ): ListenableFuture<LibraryResult<Void>> {
323
508
  Timber.d("onSubscribe: ${browser.packageName}, parentId = $parentId")
324
509
 
325
- parentIdSubscriptions.getOrPut(parentId) { mutableSetOf() }.add(browser)
326
- return super.onSubscribe(session, browser, parentId, params)
510
+ synchronized(this) { parentIdSubscriptions.getOrPut(parentId) { mutableSetOf() }.add(browser) }
511
+
512
+ // Don't call super: Media3's default onSubscribe validates the parent by calling
513
+ // onGetItem(parentId), which for us is a full resolve() — an HTTP fetch whose result the
514
+ // legacy stub discards for Android Auto controllers. onGetChildren already surfaces browse
515
+ // failures. Keep the default's other behavior: notify Media3 (non-legacy) browsers so they
516
+ // fetch the children; legacy browsers get onLoadChildren from the framework after subscribing.
517
+ if (browser.controllerVersion != MediaSession.ControllerInfo.LEGACY_CONTROLLER_VERSION) {
518
+ session.notifyChildrenChanged(browser, parentId, Int.MAX_VALUE, params)
519
+ }
520
+ return Futures.immediateFuture(LibraryResult.ofVoid())
327
521
  }
328
522
 
329
523
  override fun onUnsubscribe(
@@ -333,9 +527,11 @@ class MediaSessionCallback(private val player: Player) :
333
527
  ): ListenableFuture<LibraryResult<Void>> {
334
528
  Timber.d("onUnsubscribe: ${browser.packageName}, parentId = $parentId")
335
529
 
336
- parentIdSubscriptions[parentId]?.remove(browser)
337
- if (parentIdSubscriptions[parentId]?.isEmpty() == true) {
338
- parentIdSubscriptions.remove(parentId)
530
+ synchronized(this) {
531
+ parentIdSubscriptions[parentId]?.remove(browser)
532
+ if (parentIdSubscriptions[parentId]?.isEmpty() == true) {
533
+ parentIdSubscriptions.remove(parentId)
534
+ }
339
535
  }
340
536
 
341
537
  return super.onUnsubscribe(session, browser, parentId)
@@ -344,22 +540,38 @@ class MediaSessionCallback(private val player: Player) :
344
540
  /**
345
541
  * Notifies all subscribed controllers to refresh their content. Called internally when network
346
542
  * state changes to refresh all subscribed paths.
543
+ *
544
+ * Safe to call from any thread: snapshots the subscribed paths and dispatches the session calls
545
+ * to the main (Media3 application) thread.
347
546
  */
348
547
  private fun notifySubscribedChildrenChanged() {
349
- parentIdSubscriptions.keys.forEach { parentId ->
350
- mediaLibrarySession?.notifyChildrenChanged(parentId, Int.MAX_VALUE, null)
548
+ val session = mediaLibrarySession ?: return
549
+ val parentIds = synchronized(this) { parentIdSubscriptions.keys.toList() }
550
+ scope.launch(Dispatchers.Main) {
551
+ parentIds.forEach { parentId -> session.notifyChildrenChanged(parentId, Int.MAX_VALUE, null) }
351
552
  }
352
553
  }
353
554
 
354
555
  /**
355
556
  * Notifies external controllers that content at the given path has changed. Controllers
356
- * subscribed to this path will refresh their UI.
557
+ * subscribed to this path will refresh their UI. Safe to call from any thread.
357
558
  *
358
559
  * @param path The path where content has changed
359
560
  */
360
561
  fun notifyContentChanged(path: String) {
361
562
  Timber.d("Notifying content changed for path: $path")
362
- mediaLibrarySession?.notifyChildrenChanged(path, Int.MAX_VALUE, null)
563
+ val session = mediaLibrarySession ?: return
564
+ scope.launch(Dispatchers.Main) { session.notifyChildrenChanged(path, Int.MAX_VALUE, null) }
565
+ }
566
+
567
+ /**
568
+ * Notifies all subscribed controllers that content everywhere has changed (e.g. on a locale
569
+ * switch) so they re-query their children. Pairs with AudioBrowser.invalidateAllContent(), which
570
+ * clears the content cache first.
571
+ */
572
+ fun invalidateAllContent() {
573
+ Timber.d("Invalidating all content - notifying all subscribed paths")
574
+ notifySubscribedChildrenChanged()
363
575
  }
364
576
 
365
577
  /**
@@ -367,10 +579,15 @@ class MediaSessionCallback(private val player: Player) :
367
579
  * controllers to refresh their content.
368
580
  */
369
581
  fun notifyBrowserReady() {
370
- Timber.d("Browser ready - notifying ${parentIdSubscriptions.size} subscribed paths")
582
+ Timber.d("Browser ready - notifying all subscribed paths")
371
583
  notifySubscribedChildrenChanged()
372
584
  }
373
585
 
586
+ /** Cancels in-flight browse/search work. Call when the owning player is destroyed. */
587
+ fun destroy() {
588
+ scope.cancel()
589
+ }
590
+
374
591
  override fun onSearch(
375
592
  session: MediaLibraryService.MediaLibrarySession,
376
593
  browser: MediaSession.ControllerInfo,
@@ -379,11 +596,27 @@ class MediaSessionCallback(private val player: Player) :
379
596
  ): ListenableFuture<LibraryResult<Void>> {
380
597
  Timber.d("onSearch: ${browser.packageName}, query = $query")
381
598
  return scope.future {
382
- val browserManager = player.browser?.browserManager
599
+ // Wait for browser registration like onGetChildren does, so a cold-start voice search
600
+ // doesn't fail before JS has configured the browser.
601
+ val audioBrowser =
602
+ try {
603
+ player.awaitBrowser()
604
+ } catch (e: TimeoutCancellationException) {
605
+ Timber.w("Timed out waiting for browser - search not available")
606
+ return@future LibraryResult.ofError(SessionError.ERROR_NOT_SUPPORTED)
607
+ }
608
+ val browserManager = audioBrowser.browserManager
383
609
 
384
- if (browserManager == null) {
385
- Timber.w("AudioBrowser not registered - search not available")
386
- return@future LibraryResult.ofError(SessionError.ERROR_NOT_SUPPORTED)
610
+ // External-surface search is gated with the rest of the browse tree — otherwise search is
611
+ // a way around the gate. One "result": the gate tile (see onGetSearchResult).
612
+ val searchOutcome =
613
+ audioBrowser.gateDecision(
614
+ NativeGateRequest(reason = GateReason.SEARCH, path = null, search = searchParams(query))
615
+ )
616
+ if (searchOutcome.gated) {
617
+ audioBrowser.onGate(GateEvent(GateReason.SEARCH))
618
+ session.notifySearchResultChanged(browser, query, 1, params)
619
+ return@future LibraryResult.ofVoid()
387
620
  }
388
621
 
389
622
  // Check if search is configured
@@ -404,6 +637,7 @@ class MediaSessionCallback(private val player: Player) :
404
637
 
405
638
  LibraryResult.ofVoid()
406
639
  } catch (e: Exception) {
640
+ if (e is CancellationException && e !is TimeoutCancellationException) throw e
407
641
  Timber.e(e, "Error during search for query: $query")
408
642
  LibraryResult.ofError(SessionError.ERROR_UNKNOWN)
409
643
  }
@@ -422,21 +656,38 @@ class MediaSessionCallback(private val player: Player) :
422
656
  "onGetSearchResult: ${browser.packageName}, query = $query, page = $page, pageSize = $pageSize"
423
657
  )
424
658
  return scope.future {
425
- val browserManager = player.browser?.browserManager
659
+ val audioBrowser =
660
+ try {
661
+ player.awaitBrowser()
662
+ } catch (e: TimeoutCancellationException) {
663
+ Timber.w("Timed out waiting for browser - search not available")
664
+ return@future LibraryResult.ofError(SessionError.ERROR_NOT_SUPPORTED)
665
+ }
666
+ val browserManager = audioBrowser.browserManager
426
667
 
427
- if (browserManager == null) {
428
- Timber.w("AudioBrowser not registered - search not available")
429
- return@future LibraryResult.ofError(SessionError.ERROR_NOT_SUPPORTED)
668
+ // Gated: the single search "result" is the gate tile (paired with onSearch's count of 1).
669
+ val searchOutcome =
670
+ audioBrowser.gateDecision(
671
+ NativeGateRequest(reason = GateReason.SEARCH, path = null, search = searchParams(query))
672
+ )
673
+ if (searchOutcome.gated) {
674
+ audioBrowser.onGate(GateEvent(GateReason.SEARCH))
675
+ return@future LibraryResult.ofItemList(
676
+ ImmutableList.of(createGateMediaItem(searchOutcome.chrome!!)),
677
+ params,
678
+ )
430
679
  }
431
680
 
432
681
  try {
433
682
  // Get cached search results from BrowserManager
434
683
  browserManager.getCachedSearchResults(query)?.let { tracks ->
435
684
  // Convert to MediaItems
685
+ val searchAuthority =
686
+ com.audiobrowser.util.ArtworkUris.authorityFor(player.context.packageName)
436
687
  val mediaItems =
437
688
  tracks.map { track ->
438
689
  Timber.d("Search result: ${track.title} (url=${track.url}, src=${track.src})")
439
- TrackFactory.toMedia3(track)
690
+ TrackFactory.toBrowseMediaItem(track, player.browseArtworkRegistry, searchAuthority)
440
691
  }
441
692
 
442
693
  val paginatedItems = mediaItems.paginate(page, pageSize)
@@ -448,6 +699,7 @@ class MediaSessionCallback(private val player: Player) :
448
699
  LibraryResult.ofItemList(ImmutableList.of(), params)
449
700
  }
450
701
  } catch (e: Exception) {
702
+ if (e is CancellationException && e !is TimeoutCancellationException) throw e
451
703
  Timber.e(e, "Error getting search results for query: $query")
452
704
  LibraryResult.ofError(SessionError.ERROR_UNKNOWN)
453
705
  }
@@ -462,65 +714,84 @@ class MediaSessionCallback(private val player: Player) :
462
714
  startPositionMs: Long,
463
715
  ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
464
716
  Timber.Forest.d(
465
- "onSetMediaItems: ${controller.packageName}, count=${mediaItems.size}, mediaId=${mediaItems[0].mediaId}, uri=${mediaItems[0].localConfiguration?.uri}, title=${mediaItems[0].mediaMetadata.title}"
717
+ "onSetMediaItems: ${controller.packageName}, count=${mediaItems.size}, mediaId=${mediaItems.firstOrNull()?.mediaId}, uri=${mediaItems.firstOrNull()?.localConfiguration?.uri}, title=${mediaItems.firstOrNull()?.mediaMetadata?.title}"
466
718
  )
467
719
 
720
+ if (mediaItems.isEmpty()) {
721
+ return Futures.immediateFuture(
722
+ MediaSession.MediaItemsWithStartPosition(emptyList(), 0, startPositionMs)
723
+ )
724
+ }
725
+
468
726
  return scope.future {
469
727
  val audioBrowser = player.awaitBrowser()
470
728
 
471
- // Helper: returns the current player state unchanged so Media3 doesn't modify playback
472
- fun currentPlayerState(): MediaSession.MediaItemsWithStartPosition {
473
- val currentItems = player.tracks.map { TrackFactory.toMedia3(it) }
474
- val currentIndex = player.currentIndex ?: 0
475
- return MediaSession.MediaItemsWithStartPosition(
476
- currentItems,
477
- currentIndex,
478
- startPositionMs,
479
- )
480
- }
729
+ // Helper: returns the current player state unchanged so Media3 doesn't modify playback.
730
+ // ExoPlayer is main-thread confined; this future runs on IO, so hop to Main for the reads.
731
+ suspend fun currentPlayerState(): MediaSession.MediaItemsWithStartPosition =
732
+ withContext(Dispatchers.Main) {
733
+ val currentItems = player.tracks.map { TrackFactory.toMedia3(it) }
734
+ val currentIndex = player.currentIndex ?: 0
735
+ MediaSession.MediaItemsWithStartPosition(currentItems, currentIndex, startPositionMs)
736
+ }
481
737
 
482
- // Check if this is a single contextual URL that matches the current queue source
483
- if (mediaItems.size == 1) {
484
- val mediaId = mediaItems[0].mediaId
485
- if (BrowserPathHelper.isContextual(mediaId)) {
486
- val parentPath = BrowserPathHelper.stripTrackId(mediaId)
487
- val trackId = BrowserPathHelper.extractTrackId(mediaId)
488
-
489
- // Check if queue already came from this parent path - just skip to the track
490
- if (trackId != null && parentPath == player.queueSourcePath) {
491
- val index = player.tracks.indexOfFirst { it.src == trackId }
492
- if (index >= 0) {
493
- Timber.d("Queue already from $parentPath, skipping to index $index")
494
- val track = player.tracks[index]
495
- return@future handleTrackLoad(audioBrowser.configuration.handleTrackLoad, track, player.tracks, index.toDouble(), ::currentPlayerState) {
496
- // Return the existing queue items with the new start index
497
- val existingItems = player.tracks.map { TrackFactory.toMedia3(it) }
498
- MediaSession.MediaItemsWithStartPosition(
499
- existingItems,
500
- index,
501
- startPositionMs,
502
- )
503
- }
738
+ val browserManager = audioBrowser.browserManager
739
+
740
+ // A single tapped item resolves to the contextual url of the list it was
741
+ // tapped in: directly for contextual mediaIds, via the track cache for
742
+ // stable-id mediaIds (see BrowserManager.contextualUrlFor). A search
743
+ // selection is not a list tap — its queue comes from the search results
744
+ // (resolveMediaItemsForPlayback), never from a browsed container.
745
+ val singleContextualUrl =
746
+ mediaItems
747
+ .singleOrNull()
748
+ ?.takeIf { it.requestMetadata.searchQuery == null }
749
+ ?.let { browserManager.contextualUrlFor(it.mediaId) }
750
+
751
+ // Check if this is a single item from the current queue source
752
+ if (singleContextualUrl != null) {
753
+ val parentPath = BrowserPathHelper.stripTrackId(singleContextualUrl)
754
+ val trackId = BrowserPathHelper.extractTrackId(singleContextualUrl)
755
+
756
+ // Check if queue already came from this parent path - just skip to the track
757
+ if (trackId != null && parentPath == player.queueSourcePath) {
758
+ val queueTracks = withContext(Dispatchers.Main) { player.tracks }
759
+ val index = queueTracks.indexOfFirst { it.src == trackId }
760
+ if (index >= 0) {
761
+ Timber.d("Queue already from $parentPath, skipping to index $index")
762
+ val track = queueTracks[index]
763
+ return@future handleTrackLoad(
764
+ audioBrowser.configuration.handleTrackLoad,
765
+ track,
766
+ queueTracks,
767
+ index.toDouble(),
768
+ ::currentPlayerState,
769
+ ) {
770
+ // Return the existing queue items with the new start index
771
+ val existingItems = queueTracks.map { TrackFactory.toMedia3(it) }
772
+ MediaSession.MediaItemsWithStartPosition(existingItems, index, startPositionMs)
504
773
  }
505
774
  }
506
775
  }
507
776
  }
508
777
 
509
- val browserManager = audioBrowser.browserManager
510
778
  val result =
511
779
  browserManager.resolveMediaItemsForPlayback(mediaItems, startIndex, startPositionMs)
512
780
 
513
781
  val tracks = result.mediaItems.map { TrackFactory.fromMedia3(it) }.toTypedArray()
514
782
  val selectedTrack = tracks.getOrElse(result.startIndex) { tracks.first() }
515
783
 
516
- handleTrackLoad(audioBrowser.configuration.handleTrackLoad, selectedTrack, tracks, result.startIndex.toDouble(), ::currentPlayerState) {
784
+ handleTrackLoad(
785
+ audioBrowser.configuration.handleTrackLoad,
786
+ selectedTrack,
787
+ tracks,
788
+ result.startIndex.toDouble(),
789
+ ::currentPlayerState,
790
+ ) {
517
791
  // If this was a contextual URL expansion, track the source path (only for default behavior)
518
- if (mediaItems.size == 1) {
519
- val mediaId = mediaItems[0].mediaId
520
- if (BrowserPathHelper.isContextual(mediaId)) {
521
- val parentPath = BrowserPathHelper.stripTrackId(mediaId)
522
- withContext(Dispatchers.Main) { player.queueSourcePath = parentPath }
523
- }
792
+ if (singleContextualUrl != null) {
793
+ val parentPath = BrowserPathHelper.stripTrackId(singleContextualUrl)
794
+ withContext(Dispatchers.Main) { player.queueSourcePath = parentPath }
524
795
  }
525
796
  result
526
797
  }
@@ -537,25 +808,42 @@ class MediaSessionCallback(private val player: Player) :
537
808
  *
538
809
  * @param mediaSession The media session
539
810
  * @param controller The controller requesting resumption
540
- * @param playback True if playback should start, false if just gathering info for UI
811
+ * @param isForPlayback True if this should start playback; false if just gathering info for the
812
+ * boot-time resumption notification (no network; local metadata only).
541
813
  * @see https://developer.android.com/media/media3/session/background-playback#resumption
542
814
  */
543
815
  override fun onPlaybackResumption(
544
816
  mediaSession: MediaSession,
545
817
  controller: MediaSession.ControllerInfo,
818
+ isForPlayback: Boolean,
546
819
  ): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
547
820
  Timber.d("${controller.packageName}")
548
821
 
549
822
  return scope.future {
550
- // Restore player settings and persisted state
823
+ // A pure read first: restore()'s side effects (it applies repeat/shuffle/speed to the
824
+ // live player, emitting option-changed events to JS) belong to actual resumption only.
551
825
  val state =
552
- // restore() sets player properties which must happen on main thread
553
- withContext(Dispatchers.Main) { player.playbackStateStore.restore() }
826
+ player.playbackStateStore.get()
554
827
  ?: run {
555
828
  Timber.w("No persisted playback state found")
556
829
  throw IllegalStateException("No playback state to resume")
557
830
  }
558
831
 
832
+ // isForPlayback == false is the device-boot-time notification case: network may be
833
+ // unavailable and we must not start playback. Return just the locally-stored track with its
834
+ // already-local metadata; skip the settings restore and network queue expansion below.
835
+ if (!isForPlayback) {
836
+ Timber.d("Info-gathering resumption (boot-time); returning stored track without expansion")
837
+ return@future MediaSession.MediaItemsWithStartPosition(
838
+ ImmutableList.of(TrackFactory.toMedia3(state.track)),
839
+ 0,
840
+ state.positionMs,
841
+ )
842
+ }
843
+
844
+ // restore() sets player properties which must happen on main thread
845
+ withContext(Dispatchers.Main) { player.playbackStateStore.restore() }
846
+
559
847
  val url = state.track.url
560
848
  Timber.d("Resuming from url=$url, positionMs=${state.positionMs}")
561
849