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
@@ -8,6 +8,7 @@ import android.os.Handler
8
8
  import android.os.IBinder
9
9
  import android.os.Looper
10
10
  import androidx.annotation.Keep
11
+ import androidx.car.app.connection.CarConnection
11
12
  import androidx.lifecycle.DefaultLifecycleObserver
12
13
  import androidx.lifecycle.LifecycleOwner
13
14
  import androidx.lifecycle.ProcessLifecycleOwner
@@ -15,38 +16,41 @@ import androidx.media3.session.MediaBrowser
15
16
  import androidx.media3.session.SessionToken
16
17
  import com.audiobrowser.browser.BrowserConfig
17
18
  import com.audiobrowser.browser.BrowserManager
18
- import com.audiobrowser.browser.handleTrackLoad
19
19
  import com.audiobrowser.browser.CallbackException
20
20
  import com.audiobrowser.browser.ContentNotFoundException
21
21
  import com.audiobrowser.browser.HttpStatusException
22
22
  import com.audiobrowser.browser.NetworkException
23
+ import com.audiobrowser.browser.handleTrackLoad
24
+ import com.audiobrowser.browser.resolveMediaUrl
23
25
  import com.audiobrowser.extension.NumberExt.Companion.toSeconds
24
- import com.audiobrowser.http.RequestConfigBuilder
25
- import com.audiobrowser.model.PlaybackMetadata
26
26
  import com.audiobrowser.model.PlayerSetupOptions
27
27
  import com.audiobrowser.model.PlayerUpdateOptions
28
+ import com.audiobrowser.player.Player
28
29
  import com.audiobrowser.util.BatteryOptimizationHelper
29
30
  import com.audiobrowser.util.BatteryWarningStore
30
31
  import com.audiobrowser.util.BrowserPathHelper
31
- import com.audiobrowser.util.CoilBitmapLoader
32
+ import com.audiobrowser.util.OutputMonitor
33
+ import com.audiobrowser.util.OutputSwitcher
32
34
  import com.audiobrowser.util.SystemVolumeMonitor
33
35
  import com.facebook.proguard.annotations.DoNotStrip
34
36
  import com.google.common.util.concurrent.ListenableFuture
35
37
  import com.margelo.nitro.NitroModules
36
- import com.margelo.nitro.audiobrowser.ChapterMetadata
37
- import com.margelo.nitro.audiobrowser.TimedMetadata
38
- import com.margelo.nitro.audiobrowser.TrackMetadata
39
38
  import com.margelo.nitro.audiobrowser.BatteryOptimizationStatus
40
39
  import com.margelo.nitro.audiobrowser.BatteryOptimizationStatusChangedEvent
41
40
  import com.margelo.nitro.audiobrowser.BatteryWarningPendingChangedEvent
41
+ import com.margelo.nitro.audiobrowser.ChapterMetadata
42
42
  import com.margelo.nitro.audiobrowser.EqualizerSettings
43
43
  import com.margelo.nitro.audiobrowser.FavoriteChangedEvent
44
44
  import com.margelo.nitro.audiobrowser.FormatNavigationErrorParams
45
45
  import com.margelo.nitro.audiobrowser.FormattedNavigationError
46
+ import com.margelo.nitro.audiobrowser.Gate
47
+ import com.margelo.nitro.audiobrowser.GateDecision
48
+ import com.margelo.nitro.audiobrowser.GateEvent
46
49
  import com.margelo.nitro.audiobrowser.HybridAudioBrowserSpec
47
- import com.margelo.nitro.audiobrowser.IosOutput
48
50
  import com.margelo.nitro.audiobrowser.MediaRequestConfig
49
51
  import com.margelo.nitro.audiobrowser.NativeBrowserConfiguration
52
+ import com.margelo.nitro.audiobrowser.NativeGateRequest
53
+ import com.margelo.nitro.audiobrowser.NativeSetupPlayerOptions
50
54
  import com.margelo.nitro.audiobrowser.NativeUpdateOptions
51
55
  import com.margelo.nitro.audiobrowser.NavigationError
52
56
  import com.margelo.nitro.audiobrowser.NavigationErrorEvent
@@ -54,7 +58,7 @@ import com.margelo.nitro.audiobrowser.NavigationErrorType
54
58
  import com.margelo.nitro.audiobrowser.NowPlayingMetadata
55
59
  import com.margelo.nitro.audiobrowser.NowPlayingUpdate
56
60
  import com.margelo.nitro.audiobrowser.Options
57
- import com.margelo.nitro.audiobrowser.PartialSetupPlayerOptions
61
+ import com.margelo.nitro.audiobrowser.Output
58
62
  import com.margelo.nitro.audiobrowser.Playback
59
63
  import com.margelo.nitro.audiobrowser.PlaybackActiveTrackChangedEvent
60
64
  import com.margelo.nitro.audiobrowser.PlaybackError
@@ -62,6 +66,7 @@ import com.margelo.nitro.audiobrowser.PlaybackErrorEvent
62
66
  import com.margelo.nitro.audiobrowser.PlaybackPlayWhenReadyChangedEvent
63
67
  import com.margelo.nitro.audiobrowser.PlaybackProgressUpdatedEvent
64
68
  import com.margelo.nitro.audiobrowser.PlaybackQueueEndedEvent
69
+ import com.margelo.nitro.audiobrowser.PlaybackState
65
70
  import com.margelo.nitro.audiobrowser.PlayingState
66
71
  import com.margelo.nitro.audiobrowser.Progress
67
72
  import com.margelo.nitro.audiobrowser.RemoteJumpBackwardEvent
@@ -69,25 +74,25 @@ import com.margelo.nitro.audiobrowser.RemoteJumpForwardEvent
69
74
  import com.margelo.nitro.audiobrowser.RemotePlayIdEvent
70
75
  import com.margelo.nitro.audiobrowser.RemotePlaySearchEvent
71
76
  import com.margelo.nitro.audiobrowser.RemoteSeekEvent
72
- import com.margelo.nitro.audiobrowser.RemoteSetRatingEvent
73
77
  import com.margelo.nitro.audiobrowser.RemoteSkipEvent
74
78
  import com.margelo.nitro.audiobrowser.RepeatMode
75
79
  import com.margelo.nitro.audiobrowser.RepeatModeChangedEvent
76
- import com.margelo.nitro.audiobrowser.RequestConfig
77
80
  import com.margelo.nitro.audiobrowser.ResolvedTrack
78
81
  import com.margelo.nitro.audiobrowser.SleepTimer
82
+ import com.margelo.nitro.audiobrowser.TimedMetadata
79
83
  import com.margelo.nitro.audiobrowser.Track
80
- import com.margelo.nitro.audiobrowser.UpdateOptions
84
+ import com.margelo.nitro.audiobrowser.TrackMetadata
81
85
  import com.margelo.nitro.core.Promise
82
86
  import java.util.concurrent.TimeUnit
87
+ import java.util.concurrent.atomic.AtomicReference
83
88
  import kotlin.coroutines.resume
84
89
  import kotlin.coroutines.resumeWithException
85
90
  import kotlin.coroutines.suspendCoroutine
86
91
  import kotlinx.coroutines.CancellationException
87
92
  import kotlinx.coroutines.Job
88
93
  import kotlinx.coroutines.MainScope
94
+ import kotlinx.coroutines.cancel
89
95
  import kotlinx.coroutines.cancelChildren
90
- import kotlinx.coroutines.delay
91
96
  import kotlinx.coroutines.launch
92
97
  import kotlinx.coroutines.runBlocking
93
98
  import timber.log.Timber
@@ -95,6 +100,58 @@ import timber.log.Timber
95
100
  @Keep
96
101
  @DoNotStrip
97
102
  class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
103
+
104
+ /**
105
+ * Nitro calls this only when JS calls `dispose()` explicitly — never on runtime teardown, which
106
+ * is what [AudioBrowserLifecycleModule] is for. Both routes land here.
107
+ *
108
+ * The Service outlives this object, so everything registered in `init` or on connect has to be
109
+ * undone. Left alone, the running [Player] goes on invoking callbacks into a dead JSI runtime,
110
+ * and every reload strands another ServiceConnection and lifecycle observer, each retaining a
111
+ * BrowserManager.
112
+ *
113
+ * Runs the teardown on main: the state below is written from main-thread coroutines
114
+ * ([onServiceConnected], [onServiceDisconnected]), while dispose itself arrives on the JS thread
115
+ * or the teardown callback's thread.
116
+ */
117
+ override fun dispose() {
118
+ current.compareAndSet(this, null)
119
+ systemVolumeMonitor.destroy()
120
+ outputMonitor.destroy()
121
+ // Only when still ours: on a reload the replacement may already own the slot.
122
+ carConnectionTarget.compareAndSet(onCarConnectedChanged, null)
123
+
124
+ handler.post {
125
+ connectedService?.let { service ->
126
+ // Identity-guarded: both instances bind to the same singleton Service, so a late dispose
127
+ // must not strip the wiring a replacement has already installed.
128
+ if (service.player.getCallbacks() === callbacks) service.player.setCallbacks(null)
129
+ if (service.player.browser === this) {
130
+ service.player.browser = null
131
+ service.player.forgetBrowserRegistration()
132
+ }
133
+ if (service.onBatteryWarningPendingChanged === onBatteryWarningPending) {
134
+ service.onBatteryWarningPendingChanged = null
135
+ }
136
+ }
137
+ connectedService = null
138
+
139
+ // Dropping the reference disconnects nothing — the controller stays in the session's
140
+ // connected list, holding a binder and a listener.
141
+ mediaBrowserFuture?.let { MediaBrowser.releaseFuture(it) }
142
+ mediaBrowserFuture = null
143
+ setupPromise = null
144
+
145
+ serviceBinding.unbind(this)
146
+
147
+ ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
148
+ // After the observer removal, so this post is not the one dropped.
149
+ handler.removeCallbacksAndMessages(null)
150
+ mainScope.cancel()
151
+ }
152
+ super.dispose()
153
+ }
154
+
98
155
  private val mainScope = MainScope()
99
156
  private var navigationJob: Job? = null
100
157
  private val handler = Handler(Looper.getMainLooper())
@@ -107,14 +164,18 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
107
164
  NativeBrowserConfiguration(
108
165
  path = null,
109
166
  request = null,
167
+ requestResolver = null,
168
+ browse = null,
169
+ browseResolver = null,
110
170
  media = null,
111
171
  artwork = null,
172
+ nowPlayingArtwork = null,
112
173
  routes = null,
113
174
  singleTrack = null,
114
175
  handleTrackLoad = null,
115
176
  androidControllerOfflineError = null,
116
- carPlayUpNextButton = null,
117
- carPlayNowPlayingButtons = null,
177
+ carPlayLoadingTitle = null,
178
+ resolveAlbumUrl = null,
118
179
  formatNavigationError = null,
119
180
  )
120
181
 
@@ -123,17 +184,31 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
123
184
  setOnPathChanged { path -> onPathChanged(path) }
124
185
  setOnContentChanged { content -> onContentChanged(content) }
125
186
  setOnTabsChanged { tabs -> onTabsChanged(tabs) }
187
+ setOnArtworkRegistriesCleared { connectedService?.player?.browseArtworkRegistry?.clear() }
126
188
  }
127
189
 
128
190
  private val systemVolumeMonitor = SystemVolumeMonitor(context)
191
+ private val outputMonitor = OutputMonitor(context)
129
192
 
130
193
  // MARK: Player state
131
194
  private var updateOptions: PlayerUpdateOptions = PlayerUpdateOptions()
132
195
  private var mediaBrowserFuture: ListenableFuture<MediaBrowser>? = null
133
196
  private var setupOptions = PlayerSetupOptions()
134
197
  private var connectedService: Service? = null
198
+
199
+ private val serviceBinding = ServiceBinding(context)
200
+
201
+ /** Held rather than inlined so [dispose] can tell its own wiring from a replacement's. */
202
+ private val onBatteryWarningPending: (Boolean) -> Unit = { pending ->
203
+ post { onBatteryWarningPendingChanged(BatteryWarningPendingChangedEvent(pending)) }
204
+ }
135
205
  private var setupPromise: ((Unit) -> Unit)? = null
136
206
 
207
+ // Initial player state staged before the player exists — from setup options or the imperative
208
+ // setters called pre-bind. Strict last-write-wins; consumed when the player comes up.
209
+ private var pendingPlayWhenReady: Boolean? = null
210
+ private var pendingRepeatMode: RepeatMode? = null
211
+
137
212
  /** Post callback to main handler for consistent async delivery to JS - avoids deadlocks */
138
213
  private fun post(block: () -> Unit) = handler.post(block)
139
214
 
@@ -144,13 +219,21 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
144
219
  override var onNavigationError: (NavigationErrorEvent) -> Unit = {}
145
220
  override var onFormattedNavigationError: (FormattedNavigationError?) -> Unit = {}
146
221
 
222
+ // MARK: Gate callbacks (native→JS; set from JS, native CALLS them)
223
+ // Default resolver: only reachable in the init window where the native side has recorded
224
+ // hasResolver=true but JS hasn't yet assigned the real `resolveGate` slot. It DENIES by default
225
+ // (gated=true) so an active gate never serves content during that window — same fail-closed
226
+ // direction as the resolver-error path in [gateDecision]. The static-gate fast path skips it
227
+ // entirely once a resolver-less gate is set.
228
+ override var resolveGate: (request: NativeGateRequest) -> Promise<Promise<GateDecision>> = {
229
+ Promise.resolved(Promise.resolved(GateDecision(gated = true, gate = null)))
230
+ }
231
+ override var onGate: (event: GateEvent) -> Unit = {}
232
+
147
233
  // MARK: Player callbacks
148
234
  override var onPlaybackChanged: (data: Playback) -> Unit = {}
149
- override var onRemoteBookmark: () -> Unit = {}
150
- override var onRemoteDislike: () -> Unit = {}
151
235
  override var onRemoteJumpBackward: (RemoteJumpBackwardEvent) -> Unit = {}
152
236
  override var onRemoteJumpForward: (RemoteJumpForwardEvent) -> Unit = {}
153
- override var onRemoteLike: () -> Unit = {}
154
237
  override var onRemoteNext: () -> Unit = {}
155
238
  override var onRemotePause: () -> Unit = {}
156
239
  override var onChapterMetadata: (chapters: Array<ChapterMetadata>) -> Unit = {}
@@ -162,6 +245,7 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
162
245
  {}
163
246
  override var onPlaybackPlayingState: (data: PlayingState) -> Unit = {}
164
247
  override var onPlaybackProgressUpdated: (data: PlaybackProgressUpdatedEvent) -> Unit = {}
248
+ override var onPlaybackInterval: () -> Unit = {}
165
249
  override var onPlaybackQueueEnded: (data: PlaybackQueueEndedEvent) -> Unit = {}
166
250
  override var onPlaybackQueueChanged: (queue: Array<Track>) -> Unit = {}
167
251
  override var onPlaybackRepeatModeChanged: (data: RepeatModeChangedEvent) -> Unit = {}
@@ -171,7 +255,6 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
171
255
  override var onRemotePlaySearch: (RemotePlaySearchEvent) -> Unit = {}
172
256
  override var onRemotePrevious: () -> Unit = {}
173
257
  override var onRemoteSeek: (RemoteSeekEvent) -> Unit = {}
174
- override var onRemoteSetRating: (RemoteSetRatingEvent) -> Unit = {}
175
258
  override var onRemoteSkip: (RemoteSkipEvent) -> Unit = {}
176
259
  override var onRemoteStop: () -> Unit = {}
177
260
  override var onOptionsChanged: (Options) -> Unit = {}
@@ -184,14 +267,11 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
184
267
  override var onBatteryOptimizationStatusChanged: (BatteryOptimizationStatusChangedEvent) -> Unit =
185
268
  {}
186
269
  override var onSystemVolumeChanged: (Double) -> Unit = {}
187
- override var onIosOutputChanged: (IosOutput) -> Unit = {}
270
+ override var onOutputChanged: (Output) -> Unit = {}
188
271
 
189
272
  // MARK: Remote handlers
190
- override var handleRemoteBookmark: (() -> Unit)? = null
191
- override var handleRemoteDislike: (() -> Unit)? = null
192
273
  override var handleRemoteJumpBackward: ((RemoteJumpBackwardEvent) -> Unit)? = null
193
274
  override var handleRemoteJumpForward: ((RemoteJumpForwardEvent) -> Unit)? = null
194
- override var handleRemoteLike: (() -> Unit)? = null
195
275
  override var handleRemoteNext: (() -> Unit)? = null
196
276
  override var handleRemotePause: (() -> Unit)? = null
197
277
  override var handleRemotePlay: (() -> Unit)? = null
@@ -211,12 +291,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
211
291
  }
212
292
 
213
293
  init {
294
+ current.set(this)
295
+
214
296
  // Auto-bind to service if it's already running
215
297
  launchInScope {
216
298
  try {
217
299
  Timber.d("Attempting to auto-bind to existing AudioBrowserService from AudioBrowser")
218
300
  val intent = Intent(context, Service::class.java)
219
- val bound = context.bindService(intent, this@AudioBrowser, Context.BIND_AUTO_CREATE)
301
+ val bound = serviceBinding.bind(intent, this@AudioBrowser)
220
302
  Timber.d("Auto-bind result: $bound")
221
303
 
222
304
  if (!bound) {
@@ -232,9 +314,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
232
314
  handler.post { ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver) }
233
315
 
234
316
  // Observe system volume changes
235
- systemVolumeMonitor.setOnVolumeChanged { volume ->
236
- post { onSystemVolumeChanged(volume) }
237
- }
317
+ systemVolumeMonitor.setOnVolumeChanged { volume -> post { onSystemVolumeChanged(volume) } }
318
+
319
+ // Observe current audio output changes (active media route via AudioManager)
320
+ outputMonitor.setOnOutputChanged { output -> post { onOutputChanged(output) } }
321
+ outputMonitor.start()
322
+
323
+ // Observe the car connection (Android Auto / Android Automotive)
324
+ startCarConnectionObserver(context, handler)
238
325
  }
239
326
 
240
327
  // ============================================================================
@@ -244,8 +331,12 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
244
331
  internal fun buildConfig(): BrowserConfig {
245
332
  return BrowserConfig(
246
333
  request = _configuration.request,
334
+ requestResolver = _configuration.requestResolver,
335
+ browse = _configuration.browse,
336
+ browseResolver = _configuration.browseResolver,
247
337
  media = _configuration.media,
248
338
  artwork = _configuration.artwork,
339
+ nowPlayingArtwork = _configuration.nowPlayingArtwork,
249
340
  routes = _configuration.routes,
250
341
  singleTrack = _configuration.singleTrack ?: false,
251
342
  androidControllerOfflineError = _configuration.androidControllerOfflineError ?: true,
@@ -253,34 +344,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
253
344
  }
254
345
 
255
346
  /**
256
- * Returns the artwork configuration for use by CoilBitmapLoader. This provides access to the base
257
- * request config and artwork-specific config.
258
- */
259
- fun getArtworkConfig(): CoilBitmapLoader.ArtworkConfig? {
260
- val artworkConfig = _configuration.artwork ?: return null
261
- val baseConfig =
262
- _configuration.request?.let { RequestConfigBuilder.toRequestConfig(it) }
263
- ?: RequestConfig(null, null, null, null, null, null, null, null)
264
- return CoilBitmapLoader.ArtworkConfig(baseConfig, artworkConfig)
265
- }
266
-
267
- /**
268
- * Creates a request config for media URL transformation by merging base and media configs.
269
- * Returns null if no media configuration is set.
347
+ * Media URL transformation for [com.audiobrowser.player.TransformingDataSource]. Resolution lives
348
+ * in [resolveMediaUrl] (browser/BrowserUrlResolution.kt); this shell owns only the blocking
349
+ * bridge: it runs on ExoPlayer's IO thread (TransformingDataSource.open), so blocking here is
350
+ * safe and intentional.
270
351
  */
271
352
  fun getMediaRequestConfig(originalUrl: String): MediaRequestConfig? {
272
- val mediaConfig = _configuration.media ?: return null
273
-
274
353
  return try {
275
- // Create base request config with the original URL as path
276
- val baseConfig =
277
- _configuration.request?.let { RequestConfigBuilder.toRequestConfig(it) }
278
- ?: RequestConfig(null, null, null, null, null, null, null, null)
279
- val urlRequestConfig = RequestConfig(null, originalUrl, null, null, null, null, null, null)
280
- val mergedBaseConfig = RequestConfigBuilder.mergeConfig(baseConfig, urlRequestConfig)
281
-
282
- // Apply media transformation
283
- runBlocking { RequestConfigBuilder.mergeConfig(mergedBaseConfig, mediaConfig) }
354
+ runBlocking { browserManager.resolveMediaUrl(originalUrl) }
284
355
  } catch (e: Exception) {
285
356
  Timber.e(e, "Failed to transform media URL: $originalUrl")
286
357
  null
@@ -292,10 +363,15 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
292
363
  return _configuration.routes?.any { it.path != BrowserManager.SEARCH_ROUTE_PATH } == true
293
364
  }
294
365
 
295
- private fun getDefaultPath(): String? {
366
+ // Suspending on purpose: queryTabs() resolves tabs by invoking JS callbacks,
367
+ // which can only run on the JS thread. Callers MUST invoke this from a
368
+ // coroutine (off the JS thread) — never via runBlocking on a synchronous Nitro
369
+ // setter, which runs on the JS thread and would deadlock (the JS thread blocks
370
+ // waiting for callbacks that need that same thread to run).
371
+ private suspend fun getDefaultPath(): String? {
296
372
  return try {
297
373
  browserManager.config = buildConfig()
298
- val tabs = runBlocking { browserManager.queryTabs() }
374
+ val tabs = browserManager.queryTabs()
299
375
  if (tabs.isNotEmpty()) {
300
376
  Timber.d("Using first tab as default path: ${tabs[0].url}")
301
377
  tabs[0].url
@@ -303,6 +379,8 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
303
379
  Timber.d("Using root path as default: /")
304
380
  "/"
305
381
  }
382
+ } catch (e: CancellationException) {
383
+ throw e // Rethrow so cooperative cancellation isn't swallowed
306
384
  } catch (e: Exception) {
307
385
  Timber.e(e, "Failed to get default path, falling back to /")
308
386
  "/"
@@ -318,23 +396,25 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
318
396
  set(value) {
319
397
  if (hasValidConfiguration()) {
320
398
  browserManager.config = buildConfig()
321
- (value ?: getDefaultPath())?.let { path ->
322
- clearNavigationError()
323
-
324
- // Cancel previous navigation to avoid race conditions
325
- navigationJob?.cancel()
326
-
327
- navigationJob =
328
- mainScope.launch {
329
- try {
330
- browserManager.navigate(path)
331
- } catch (e: CancellationException) {
332
- throw e // Rethrow to properly cancel
333
- } catch (e: Exception) {
334
- handleBrowserException(e, path, "setting path: $path")
335
- }
399
+ clearNavigationError()
400
+
401
+ // Cancel previous navigation to avoid race conditions
402
+ navigationJob?.cancel()
403
+
404
+ // Resolve the default path inside the coroutine (getDefaultPath queries
405
+ // tabs via JS callbacks) so it never blocks the JS thread this setter
406
+ // runs on.
407
+ navigationJob =
408
+ mainScope.launch {
409
+ val path = value ?: getDefaultPath() ?: return@launch
410
+ try {
411
+ browserManager.navigate(path)
412
+ } catch (e: CancellationException) {
413
+ throw e // Rethrow to properly cancel
414
+ } catch (e: Exception) {
415
+ handleBrowserException(e, path, "setting path: $path")
336
416
  }
337
- }
417
+ }
338
418
  }
339
419
  }
340
420
 
@@ -352,45 +432,54 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
352
432
  // This allows Android Auto to start browsing content
353
433
  connectedService?.player?.notifyBrowserConfigurationReady()
354
434
 
355
- // Navigate to initial path or default to first tab
356
- (value.path ?: getDefaultPath())?.let { path ->
357
- clearNavigationError()
358
-
359
- // Cancel previous navigation to avoid race conditions
360
- navigationJob?.cancel()
361
-
362
- navigationJob =
363
- mainScope.launch {
364
- try {
365
- browserManager.navigate(path)
366
- } catch (e: CancellationException) {
367
- throw e // Rethrow to properly cancel
368
- } catch (e: Exception) {
369
- handleBrowserException(e, path, "setting configuration path: $path")
370
- }
435
+ clearNavigationError()
436
+
437
+ // Cancel previous navigation to avoid race conditions
438
+ navigationJob?.cancel()
439
+
440
+ // Navigate to the initial path, or default to the first tab. The default
441
+ // path is resolved inside the coroutine (getDefaultPath queries tabs via
442
+ // JS callbacks) so it never blocks the JS thread this setter runs on —
443
+ // doing it synchronously here via runBlocking deadlocked: the JS callbacks
444
+ // can't run because the JS thread is blocked waiting for them.
445
+ navigationJob =
446
+ mainScope.launch {
447
+ val path = value.path ?: getDefaultPath() ?: return@launch
448
+ try {
449
+ browserManager.navigate(path)
450
+ } catch (e: CancellationException) {
451
+ throw e // Rethrow to properly cancel
452
+ } catch (e: Exception) {
453
+ handleBrowserException(e, path, "setting configuration path: $path")
371
454
  }
372
- }
455
+ }
373
456
  }
374
457
 
375
458
  private var navigationError: NavigationError? = null
376
459
  private var formattedNavigationError: FormattedNavigationError? = null
377
460
 
378
461
  override fun getNavigationError(): NavigationError? = navigationError
462
+
379
463
  override fun getFormattedNavigationError(): FormattedNavigationError? = formattedNavigationError
380
464
 
381
465
  /** Creates a default formatted error from a NavigationError */
382
466
  private fun defaultFormattedError(error: NavigationError): FormattedNavigationError {
383
- val title = when (error.code) {
384
- NavigationErrorType.CONTENT_NOT_FOUND -> "Content Not Found"
385
- NavigationErrorType.NETWORK_ERROR -> "Network Error"
386
- NavigationErrorType.HTTP_ERROR -> {
387
- // Use system-localized HTTP status text (e.g., "Not Found", "Service Unavailable")
388
- error.statusCode?.let { httpStatusText(it.toInt()) } ?: "Server Error"
467
+ val title =
468
+ when (error.code) {
469
+ NavigationErrorType.CONTENT_NOT_FOUND -> "Content Not Found"
470
+ NavigationErrorType.NETWORK_ERROR -> "Network Error"
471
+ NavigationErrorType.HTTP_ERROR -> {
472
+ // Use system-localized HTTP status text (e.g., "Not Found", "Service Unavailable")
473
+ error.statusCode?.let { httpStatusText(it.toInt()) } ?: "Server Error"
474
+ }
475
+ NavigationErrorType.CALLBACK_ERROR -> "Error"
476
+ NavigationErrorType.UNKNOWN_ERROR -> "Error"
477
+ // Not a failure — a container that resolved with no children. Neutral copy. See ADR 0001.
478
+ NavigationErrorType.EMPTY_CONTENT -> "Nothing here"
479
+ NavigationErrorType.TIMEOUT -> "Couldn't load"
389
480
  }
390
- NavigationErrorType.CALLBACK_ERROR -> "Error"
391
- NavigationErrorType.UNKNOWN_ERROR -> "Error"
392
- }
393
- return FormattedNavigationError(title, error.message)
481
+ // Omit an empty message so it renders as title-only (e.g. the empty-content case).
482
+ return FormattedNavigationError(title, error.message.takeIf { it.isNotEmpty() })
394
483
  }
395
484
 
396
485
  /** Returns localized HTTP status text for the given status code */
@@ -457,19 +546,35 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
457
546
  }
458
547
  is NetworkException -> {
459
548
  Timber.e(e, "Network error $logContext")
460
- setNavigationError(NavigationErrorType.NETWORK_ERROR, e.message ?: "Network request failed", path)
549
+ setNavigationError(
550
+ NavigationErrorType.NETWORK_ERROR,
551
+ e.message ?: "Network request failed",
552
+ path,
553
+ )
461
554
  }
462
555
  is ContentNotFoundException -> {
463
556
  Timber.e(e, "Content not found $logContext")
464
- setNavigationError(NavigationErrorType.CONTENT_NOT_FOUND, e.message ?: "Content not found", path)
557
+ setNavigationError(
558
+ NavigationErrorType.CONTENT_NOT_FOUND,
559
+ e.message ?: "Content not found",
560
+ path,
561
+ )
465
562
  }
466
563
  is CallbackException -> {
467
564
  Timber.e(e, "Callback error $logContext")
468
- setNavigationError(NavigationErrorType.CALLBACK_ERROR, e.message ?: "An error occurred", path)
565
+ setNavigationError(
566
+ NavigationErrorType.CALLBACK_ERROR,
567
+ e.message ?: "An error occurred",
568
+ path,
569
+ )
469
570
  }
470
571
  else -> {
471
572
  Timber.e(e, "Unexpected error $logContext")
472
- setNavigationError(NavigationErrorType.UNKNOWN_ERROR, e.message ?: "An unexpected error occurred", path)
573
+ setNavigationError(
574
+ NavigationErrorType.UNKNOWN_ERROR,
575
+ e.message ?: "An unexpected error occurred",
576
+ path,
577
+ )
473
578
  }
474
579
  }
475
580
  }
@@ -529,10 +634,17 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
529
634
  val index = player.tracks.indexOfFirst { it.src == trackId }
530
635
  if (index >= 0) {
531
636
  Timber.d("Queue already from $parentPath, skipping to index $index")
532
- handleTrackLoad(_configuration.handleTrackLoad, track, player.tracks, index.toDouble(), intercepted = {}, defaultBehavior = {
533
- player.skipTo(index)
534
- player.play()
535
- })
637
+ handleTrackLoad(
638
+ _configuration.handleTrackLoad,
639
+ track,
640
+ player.tracks,
641
+ index.toDouble(),
642
+ intercepted = {},
643
+ defaultBehavior = {
644
+ player.skipTo(index)
645
+ player.play()
646
+ },
647
+ )
536
648
  return@launch
537
649
  }
538
650
  }
@@ -545,17 +657,31 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
545
657
  Timber.d(
546
658
  "Loading expanded queue: ${tracks.size} tracks, starting at index $startIndex"
547
659
  )
548
- handleTrackLoad(_configuration.handleTrackLoad, track, tracks, startIndex.toDouble(), intercepted = {}, defaultBehavior = {
549
- // Replace queue and seek to selected track
550
- // Use internal player methods directly to avoid blocking on main thread
551
- player.setQueue(tracks, startIndex, sourcePath = parentPath)
552
- player.play()
553
- })
660
+ handleTrackLoad(
661
+ _configuration.handleTrackLoad,
662
+ track,
663
+ tracks,
664
+ startIndex.toDouble(),
665
+ intercepted = {},
666
+ defaultBehavior = {
667
+ // Replace queue and seek to selected track
668
+ // Use internal player methods directly to avoid blocking on main thread
669
+ player.setQueue(tracks, startIndex, sourcePath = parentPath)
670
+ player.play()
671
+ },
672
+ )
554
673
  return@launch
555
674
  } else {
556
675
  // Fallback: just load the single track
557
676
  Timber.w("Queue expansion failed, loading single track")
558
- handleTrackLoad(_configuration.handleTrackLoad, track, arrayOf(track), 0.0, intercepted = {}, defaultBehavior = { player.load(track) })
677
+ handleTrackLoad(
678
+ _configuration.handleTrackLoad,
679
+ track,
680
+ arrayOf(track),
681
+ 0.0,
682
+ intercepted = {},
683
+ defaultBehavior = { player.load(track) },
684
+ )
559
685
  }
560
686
  }
561
687
  // Navigate to browsable track to show browsing UI
@@ -566,7 +692,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
566
692
  // If track is playable (has src), load it into player
567
693
  track.src != null -> {
568
694
  Timber.d("Loading playable track into player: ${track.title}")
569
- handleTrackLoad(_configuration.handleTrackLoad, track, arrayOf(track), 0.0, intercepted = {}, defaultBehavior = { player.load(track) })
695
+ handleTrackLoad(
696
+ _configuration.handleTrackLoad,
697
+ track,
698
+ arrayOf(track),
699
+ 0.0,
700
+ intercepted = {},
701
+ defaultBehavior = { player.load(track) },
702
+ )
570
703
  }
571
704
  else -> {
572
705
  throw IllegalArgumentException("Track must have either an 'url' or an 'src' property")
@@ -580,7 +713,7 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
580
713
  }
581
714
  }
582
715
 
583
- override fun onSearch(query: String): Promise<Array<Track>> {
716
+ override fun search(query: String): Promise<Array<Track>> {
584
717
  return Promise.async(mainScope) {
585
718
  Timber.d("Searching for: $query")
586
719
  val searchResults = browserManager.search(query)
@@ -607,32 +740,132 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
607
740
  }
608
741
  }
609
742
 
743
+ override fun invalidateAllContent() {
744
+ Timber.d("Invalidating all content")
745
+
746
+ // Clear all cached content so every path re-fetches fresh data
747
+ browserManager.clearContentCache()
748
+
749
+ // Notify external media controllers (Android Auto) to refresh subscribed paths
750
+ connectedService?.player?.invalidateAllContent()
751
+
752
+ // Refresh whatever the browser is currently viewing
753
+ mainScope.launch { browserManager.refresh() }
754
+ }
755
+
610
756
  override fun setFavorites(favorites: Array<String>) {
611
757
  browserManager.setFavorites(favorites.toList())
612
758
  }
613
759
 
760
+ // MARK: Gate
761
+
762
+ /** A gate decision for one request: whether to gate, and which chrome to render. */
763
+ data class GateOutcome(val gated: Boolean, val chrome: Gate?)
764
+
765
+ // The minimal chrome rendered when a gate is active but no override or stored
766
+ // default chrome exists (the resolver-only setGate overload).
767
+ private val builtInGate = Gate("Unavailable", null)
768
+
769
+ /**
770
+ * The three gate fields as one immutable triple so [gateDecision] reads them as a unit. Written
771
+ * from the JS thread, read from the Media3 application thread (the enforcement sites in
772
+ * MediaSessionCallback consult it) — hence the single field is @Volatile.
773
+ *
774
+ * Folding the three former @Volatile fields into one reference is what makes the read atomic:
775
+ *
776
+ * @Volatile gives per-field visibility but NO atomicity across a compound read, so reading three
777
+ * separate volatiles in [gateDecision] could interleave with a concurrent [clearGate] and yield
778
+ * a torn `(active=true, chrome=null)` state that crashed the force-unwrap at the serve sites.
779
+ * One volatile reference read once cannot tear.
780
+ */
781
+ private data class GateState(val active: Boolean, val chrome: Gate?, val hasResolver: Boolean)
782
+
783
+ @Volatile private var gateState = GateState(active = false, chrome = null, hasResolver = false)
784
+
785
+ override fun setGate(gate: Gate?, hasResolver: Boolean) {
786
+ // Single atomic assignment — readers never see a half-updated triple.
787
+ gateState = GateState(active = true, chrome = gate, hasResolver = hasResolver)
788
+ // Re-query every subscribed parent so a connected controller (Android
789
+ // Auto) swaps its lists for the gate tile without reconnecting. Notify
790
+ // only — the content cache stays warm for when the gate clears.
791
+ connectedService?.player?.invalidateAllContent()
792
+ }
793
+
794
+ override fun clearGate() {
795
+ if (!gateState.active) return
796
+ // Single atomic assignment — readers never see a half-cleared triple.
797
+ gateState = GateState(active = false, chrome = null, hasResolver = false)
798
+ connectedService?.player?.invalidateAllContent()
799
+ }
800
+
801
+ /**
802
+ * The single gate choke point. Each enforcement site (browse / search) calls this for the current
803
+ * request and serves the gate chrome when [GateOutcome.gated] is true. Structurally similar to
804
+ * the iOS `gateDecision(for:)` helper, but note the concurrency model differs: iOS confines gate
805
+ * state to `@MainActor`; here the consistent read comes from snapshotting the single [gateState]
806
+ * reference once (see [GateState]).
807
+ * - No active gate → allow.
808
+ * - Active gate, no resolver → static fast path: gate with the stored default chrome (or built-in
809
+ * if none), no JS hop.
810
+ * - Active gate with a resolver → ask JS per request. A resolver that throws / rejects / times
811
+ * out FAILS CLOSED by design: while a gate is active the consumer has declared content blocked,
812
+ * so the only safe fallback when we cannot compute the per-request decision is the gate's own
813
+ * chrome — never the content. A *successful* `gated:false` still allows. Chrome order on a
814
+ * gated decision: override → stored default → built-in.
815
+ *
816
+ * Every gated return guards chrome against null (`?: builtInGate`) so a gated outcome always
817
+ * carries a renderable chrome — the serve sites force-unwrap it.
818
+ */
819
+ suspend fun gateDecision(request: NativeGateRequest): GateOutcome {
820
+ // Read the whole gate triple once; a single volatile reference can't tear (see [GateState]).
821
+ val s = gateState
822
+ if (!s.active) return GateOutcome(false, null)
823
+ if (!s.hasResolver) return GateOutcome(true, s.chrome ?: builtInGate) // static fast path
824
+ val decision =
825
+ runCatching { resolveGate(request).await().await() }.getOrNull()
826
+ ?: return GateOutcome(true, s.chrome ?: builtInGate) // fail CLOSED on resolver error
827
+ if (!decision.gated) return GateOutcome(false, null) // explicit allow
828
+ return GateOutcome(true, decision.gate ?: s.chrome ?: builtInGate)
829
+ }
830
+
831
+ // MARK: Car connection (Android Auto / Android Automotive)
832
+
833
+ override fun isCarConnected(): Boolean = carConnected
834
+
835
+ override var onCarConnectedChanged: (Boolean) -> Unit = {}
836
+ set(value) {
837
+ field = value
838
+ carConnectionTarget.set(value)
839
+ // Immediately notify current state (the car may have connected before
840
+ // this JS runtime subscribed) — mirrors the iOS behavior.
841
+ value(carConnected)
842
+ }
843
+
614
844
  // ============================================================================
615
845
  // MARK: Player Setup and Options
616
846
  // ============================================================================
617
847
 
618
- override fun setupPlayer(options: PartialSetupPlayerOptions): Promise<Unit> {
848
+ override fun setupPlayer(options: NativeSetupPlayerOptions): Promise<Unit> {
619
849
  return Promise.async(mainScope) {
620
850
  setupOptions.update(options)
851
+ // The bundled runtime options and initial state are part of the atomic launch
852
+ // description: stage them here, apply them together with the engine below (or at
853
+ // service connect). Last-write-wins with their imperative counterparts.
854
+ options.options?.let { updateOptions.updateFromBridge(it) }
855
+ options.repeatMode?.let { pendingRepeatMode = it }
856
+ options.playWhenReady?.let { pendingPlayWhenReady = it }
621
857
 
622
858
  connectedService?.let {
859
+ it.player.applyOptions(updateOptions)
623
860
  it.player.setup(setupOptions)
861
+ applyPendingPlayerState(it.player)
624
862
  return@async
625
863
  }
626
864
 
627
865
  // Service not connected yet, bind to service
628
866
  suspendCoroutine<Unit> { continuation ->
629
867
  Timber.d("Binding to AudioBrowserService")
630
- val bound =
631
- context.bindService(
632
- Intent(context, Service::class.java),
633
- this@AudioBrowser,
634
- Context.BIND_AUTO_CREATE,
635
- )
868
+ val bound = serviceBinding.bind(Intent(context, Service::class.java), this@AudioBrowser)
636
869
 
637
870
  if (!bound) {
638
871
  continuation.resumeWithException(
@@ -652,8 +885,10 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
652
885
  connectedService?.let { player.applyOptions(updateOptions) }
653
886
  }
654
887
 
655
- override fun getOptions(): UpdateOptions {
656
- return player.getOptions().toNitro()
888
+ override fun getOptions(): Options {
889
+ // The holder is the source of truth whether or not the player exists yet — updateOptions
890
+ // merges into it and setup/connect applies it to the player.
891
+ return updateOptions.toNitro()
657
892
  }
658
893
 
659
894
  // ============================================================================
@@ -666,8 +901,10 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
666
901
 
667
902
  override fun reset() = runBlockingOnMain {
668
903
  player.stop()
669
- delay(300) // Allow playback to stop
670
904
  player.clear()
905
+ // clear() while IDLE emits no engine event, so the state machine never
906
+ // leaves STOPPED — a reset queue is "nothing loaded".
907
+ player.setPlaybackState(PlaybackState.NONE)
671
908
  }
672
909
 
673
910
  override fun play() = runBlockingOnMain { player.play() }
@@ -679,10 +916,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
679
916
  override fun stop() = runBlockingOnMain { player.stop() }
680
917
 
681
918
  override fun setPlayWhenReady(playWhenReady: Boolean) = runBlockingOnMain {
682
- player.playWhenReady = playWhenReady
919
+ // Pre-setup this stages the intent for the player to come up with — never throws.
920
+ connectedService?.player?.let { it.playWhenReady = playWhenReady }
921
+ ?: run { pendingPlayWhenReady = playWhenReady }
683
922
  }
684
923
 
685
- override fun getPlayWhenReady(): Boolean = runBlockingOnMain { player.playWhenReady }
924
+ override fun getPlayWhenReady(): Boolean = runBlockingOnMain {
925
+ connectedService?.player?.playWhenReady ?: pendingPlayWhenReady ?: false
926
+ }
686
927
 
687
928
  override fun seekTo(position: Double) = runBlockingOnMain {
688
929
  player.seekTo((position * 1000).toLong(), TimeUnit.MILLISECONDS)
@@ -692,6 +933,8 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
692
933
  player.seekBy((offset * 1000).toLong(), TimeUnit.MILLISECONDS)
693
934
  }
694
935
 
936
+ override fun seekToLiveEdge() = runBlockingOnMain { player.seekToLiveEdge() }
937
+
695
938
  override fun setVolume(level: Double) = runBlockingOnMain { player.volume = level.toFloat() }
696
939
 
697
940
  override fun getVolume(): Double = runBlockingOnMain { player.volume.toDouble() }
@@ -712,9 +955,14 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
712
955
 
713
956
  override fun getPlayingState(): PlayingState = runBlockingOnMain { player.getPlayingState() }
714
957
 
715
- override fun getRepeatMode(): RepeatMode = runBlockingOnMain { player.repeatMode }
958
+ override fun getRepeatMode(): RepeatMode = runBlockingOnMain {
959
+ connectedService?.player?.repeatMode ?: pendingRepeatMode ?: RepeatMode.OFF
960
+ }
716
961
 
717
- override fun setRepeatMode(mode: RepeatMode) = runBlockingOnMain { player.repeatMode = mode }
962
+ override fun setRepeatMode(mode: RepeatMode) = runBlockingOnMain {
963
+ // Pre-setup this stages the mode for the player to come up with — never throws.
964
+ connectedService?.player?.let { it.repeatMode = mode } ?: run { pendingRepeatMode = mode }
965
+ }
718
966
 
719
967
  override fun getShuffleEnabled(): Boolean = runBlockingOnMain { player.shuffleMode }
720
968
 
@@ -722,6 +970,10 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
722
970
  player.shuffleMode = enabled
723
971
  }
724
972
 
973
+ override fun setPlaybackIntervalEnabled(enabled: Boolean) = runBlockingOnMain {
974
+ player.setPlaybackIntervalEnabled(enabled)
975
+ }
976
+
725
977
  override fun getPlaybackError(): PlaybackError? = runBlockingOnMain { player.playbackError }
726
978
 
727
979
  override fun retry() = runBlockingOnMain { player.prepare() }
@@ -770,9 +1022,9 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
770
1022
  }
771
1023
  }
772
1024
 
773
- override fun setQueue(tracks: Array<Track>, startIndex: Double?, startPositionMs: Double?) =
1025
+ override fun setQueue(tracks: Array<Track>, startIndex: Double?, startPosition: Double?) =
774
1026
  runBlockingOnMain {
775
- player.setQueue(tracks, startIndex?.toInt() ?: 0, startPositionMs?.toLong() ?: 0)
1027
+ player.setQueue(tracks, startIndex?.toInt() ?: 0, ((startPosition ?: 0.0) * 1000).toLong())
776
1028
  }
777
1029
 
778
1030
  override fun setActiveTrackFavorited(favorited: Boolean) = runBlockingOnMain {
@@ -804,10 +1056,20 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
804
1056
  // ============================================================================
805
1057
 
806
1058
  override fun updateNowPlaying(update: NowPlayingUpdate?) = runBlockingOnMain {
807
- player.updateNowPlaying(update)
1059
+ player.nowPlaying.updateNowPlaying(update)
1060
+ }
1061
+
1062
+ override fun flashNowPlaying(update: NowPlayingUpdate, durationMs: Double) = runBlockingOnMain {
1063
+ player.nowPlaying.flashNowPlaying(update, durationMs)
808
1064
  }
809
1065
 
810
- override fun getNowPlaying(): NowPlayingMetadata? = runBlockingOnMain { player.getNowPlaying() }
1066
+ override fun clearNowPlayingFlash() = runBlockingOnMain {
1067
+ player.nowPlaying.clearNowPlayingFlash()
1068
+ }
1069
+
1070
+ override fun getNowPlaying(): NowPlayingMetadata? = runBlockingOnMain {
1071
+ player.nowPlaying.getNowPlaying()
1072
+ }
811
1073
 
812
1074
  // ============================================================================
813
1075
  // MARK: Network Connectivity
@@ -824,13 +1086,15 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
824
1086
  override fun setSystemVolume(volume: Double) = systemVolumeMonitor.setVolume(volume)
825
1087
 
826
1088
  // ============================================================================
827
- // MARK: External Audio Output (iOS only, stub on Android)
1089
+ // MARK: External Audio Output
828
1090
  // ============================================================================
829
1091
 
830
- override fun getIosOutput(): IosOutput? = null
1092
+ override fun getOutput(): Output? = outputMonitor.current
831
1093
 
832
- override fun openIosOutputPicker() {
833
- // No-op on Android
1094
+ override fun supportsOutputSwitcher(): Boolean = OutputSwitcher.isSupported()
1095
+
1096
+ override fun openOutputPicker() {
1097
+ OutputSwitcher.open(context)
834
1098
  }
835
1099
 
836
1100
  // ============================================================================
@@ -838,34 +1102,34 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
838
1102
  // ============================================================================
839
1103
 
840
1104
  override fun getEqualizerSettings(): EqualizerSettings? = runBlockingOnMain {
841
- player.getEqualizerSettings()
1105
+ player.equalizer.getSettings()
842
1106
  }
843
1107
 
844
1108
  override fun setEqualizerEnabled(enabled: Boolean) = runBlockingOnMain {
845
- player.setEqualizerEnabled(enabled)
1109
+ player.equalizer.setEnabled(enabled)
846
1110
  }
847
1111
 
848
1112
  override fun setEqualizerPreset(preset: String) = runBlockingOnMain {
849
- player.setEqualizerPreset(preset)
1113
+ player.equalizer.setPreset(preset)
850
1114
  }
851
1115
 
852
1116
  override fun setEqualizerLevels(levels: DoubleArray) = runBlockingOnMain {
853
- player.setEqualizerLevels(levels)
1117
+ player.equalizer.setLevels(levels)
854
1118
  }
855
1119
 
856
1120
  // ============================================================================
857
1121
  // MARK: Sleep Timer
858
1122
  // ============================================================================
859
1123
 
860
- override fun getSleepTimer(): SleepTimer = runBlockingOnMain { player.getSleepTimer() }
861
-
862
- override fun setSleepTimer(seconds: Double) = runBlockingOnMain { player.setSleepTimer(seconds) }
1124
+ override fun getSleepTimer(): SleepTimer = runBlockingOnMain { player.sleepTimer.get() }
863
1125
 
864
- override fun setSleepTimerToEndOfTrack() = runBlockingOnMain {
865
- player.setSleepTimerToEndOfTrack()
1126
+ override fun setSleepTimer(seconds: Double, fadeDuration: Double?) = runBlockingOnMain {
1127
+ player.sleepTimer.setAfter(seconds, fadeDuration)
866
1128
  }
867
1129
 
868
- override fun clearSleepTimer(): Boolean = runBlockingOnMain { player.clearSleepTimer() }
1130
+ override fun setSleepTimerToEndOfTrack() = runBlockingOnMain { player.sleepTimer.setEndOfTrack() }
1131
+
1132
+ override fun clearSleepTimer(): Boolean = runBlockingOnMain { player.sleepTimer.clear() }
869
1133
 
870
1134
  // ============================================================================
871
1135
  // MARK: Battery Optimization (Android only)
@@ -945,6 +1209,7 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
945
1209
  player.setCallbacks(callbacks)
946
1210
  player.applyOptions(updateOptions)
947
1211
  player.setup(setupOptions)
1212
+ applyPendingPlayerState(player)
948
1213
  // Start observing network connectivity changes
949
1214
  player.observeNetworkConnectivity(mainScope)
950
1215
  // Set browser reference for media URL transformation
@@ -958,13 +1223,7 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
958
1223
  }
959
1224
 
960
1225
  // Wire up battery warning callback from service
961
- connectedService?.onBatteryWarningPendingChanged = { pending ->
962
- post {
963
- this@AudioBrowser.onBatteryWarningPendingChanged(
964
- BatteryWarningPendingChangedEvent(pending)
965
- )
966
- }
967
- }
1226
+ connectedService?.onBatteryWarningPendingChanged = onBatteryWarningPending
968
1227
 
969
1228
  val sessionToken = SessionToken(context, ComponentName(context, Service::class.java))
970
1229
  mediaBrowserFuture = MediaBrowser.Builder(context, sessionToken).buildAsync()
@@ -974,6 +1233,21 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
974
1233
  }
975
1234
  }
976
1235
 
1236
+ /**
1237
+ * Applies initial player state staged before the player existed. Consumed on apply so a later
1238
+ * re-setup doesn't replay stale state.
1239
+ */
1240
+ private fun applyPendingPlayerState(player: Player) {
1241
+ pendingRepeatMode?.let {
1242
+ player.repeatMode = it
1243
+ pendingRepeatMode = null
1244
+ }
1245
+ pendingPlayWhenReady?.let {
1246
+ player.playWhenReady = it
1247
+ pendingPlayWhenReady = null
1248
+ }
1249
+ }
1250
+
977
1251
  override fun onServiceDisconnected(name: ComponentName) {
978
1252
  mainScope.coroutineContext.cancelChildren()
979
1253
  mediaBrowserFuture = null
@@ -1017,6 +1291,10 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
1017
1291
  post { this@AudioBrowser.onPlaybackProgressUpdated(event) }
1018
1292
  }
1019
1293
 
1294
+ override fun onPlaybackInterval() {
1295
+ post { this@AudioBrowser.onPlaybackInterval() }
1296
+ }
1297
+
1020
1298
  override fun onPlaybackPlayWhenReadyChanged(event: PlaybackPlayWhenReadyChangedEvent) {
1021
1299
  post { this@AudioBrowser.onPlaybackPlayWhenReadyChanged(event) }
1022
1300
  }
@@ -1166,12 +1444,8 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
1166
1444
  return handled
1167
1445
  }
1168
1446
 
1169
- override fun onRemoteSetRating(event: RemoteSetRatingEvent) {
1170
- post { this@AudioBrowser.onRemoteSetRating(event) }
1171
- }
1172
-
1173
1447
  override fun onOptionsChanged(options: PlayerUpdateOptions) {
1174
- // Not currently emitted to JS
1448
+ post { this@AudioBrowser.onOptionsChanged(options.toNitro()) }
1175
1449
  }
1176
1450
 
1177
1451
  override fun onFavoriteChanged(event: FavoriteChangedEvent) {
@@ -1194,4 +1468,45 @@ class AudioBrowser : HybridAudioBrowserSpec(), ServiceConnection {
1194
1468
  post { this@AudioBrowser.onSleepTimerChanged(timer) }
1195
1469
  }
1196
1470
  }
1471
+
1472
+ companion object {
1473
+ // Process-wide car-connection state, from the androidx.car.app
1474
+ // CarConnection provider — the documented Android Auto / Automotive
1475
+ // connection signal, served by the car system app. Observed ONCE per
1476
+ // process: AudioBrowser instances are re-created on a JS reload, and a
1477
+ // per-instance observeForever would leak each one. The latest instance's
1478
+ // callback registers as the single notification target (mirrors the iOS
1479
+ // static + shared-instance pattern).
1480
+ @Volatile private var carConnected = false
1481
+ // Compare-and-set, not @Volatile: a disposing instance clears the slot only if it still owns
1482
+ // it, and a replacement may be claiming it at the same moment.
1483
+ private val carConnectionTarget = AtomicReference<((Boolean) -> Unit)?>(null)
1484
+ private var carConnectionObserverStarted = false
1485
+
1486
+ /**
1487
+ * The instance the current JS runtime is using. Nitro hands out no registry, and the teardown
1488
+ * callback has nothing to dispose without one.
1489
+ */
1490
+ private val current = AtomicReference<AudioBrowser?>(null)
1491
+
1492
+ /** Called by [AudioBrowserLifecycleModule] when the React instance is torn down. */
1493
+ internal fun disposeCurrent() {
1494
+ current.getAndSet(null)?.dispose()
1495
+ }
1496
+
1497
+ private fun startCarConnectionObserver(context: Context, handler: Handler) {
1498
+ // Posted to main: LiveData observation is main-thread only, and the
1499
+ // single-start guard is then only ever touched from one thread.
1500
+ handler.post {
1501
+ if (carConnectionObserverStarted) return@post
1502
+ carConnectionObserverStarted = true
1503
+ CarConnection(context.applicationContext).type.observeForever { type ->
1504
+ val connected = type != CarConnection.CONNECTION_TYPE_NOT_CONNECTED
1505
+ if (connected == carConnected) return@observeForever
1506
+ carConnected = connected
1507
+ carConnectionTarget.get()?.invoke(connected)
1508
+ }
1509
+ }
1510
+ }
1511
+ }
1197
1512
  }