react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.20

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 (660) hide show
  1. package/README.md +35 -21
  2. package/android/build.gradle +35 -12
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
  11. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  12. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
  13. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
  14. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  15. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
  16. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  17. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  18. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  19. package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
  20. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
  21. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
  22. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  23. package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
  24. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
  25. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
  26. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  27. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
  28. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
  29. package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
  30. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  31. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
  32. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  33. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  34. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  35. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  36. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  37. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  38. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  39. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  40. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
  41. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  42. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  43. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
  44. package/android/src/main/res/values/strings.xml +25 -5
  45. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  46. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
  47. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  48. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
  49. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
  50. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  51. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  52. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
  53. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  54. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
  55. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  56. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  57. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  58. package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
  59. package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
  60. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
  61. package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
  62. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  63. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  64. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  65. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  66. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  67. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  68. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  69. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  70. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  71. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  72. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  73. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
  74. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  75. package/ios/Browser/BrowserConfig.swift +102 -5
  76. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  77. package/ios/Browser/BrowserManager.swift +230 -205
  78. package/ios/Browser/BrowserPathHelper.swift +44 -44
  79. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  80. package/ios/Browser/JsonModels.swift +264 -106
  81. package/ios/Browser/SearchDrillIn.swift +7 -7
  82. package/ios/Browser/SectionPresentation.swift +64 -0
  83. package/ios/Browser/SectionScope.swift +58 -24
  84. package/ios/Browser/StyleResolver.swift +58 -0
  85. package/ios/Browser/TabBarEntries.swift +9 -3
  86. package/ios/Browser/TrackIdTemplate.swift +37 -0
  87. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  88. package/ios/CarPlay/CarPlayController.swift +227 -85
  89. package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
  90. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  91. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  92. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  93. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  94. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  95. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  96. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  97. package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
  98. package/ios/Extension/Section+Copying.swift +29 -0
  99. package/ios/Extension/String+IcyCharset.swift +21 -0
  100. package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
  101. package/ios/Extension/Track+Copying.swift +8 -12
  102. package/ios/Extension/Track+Identity.swift +18 -0
  103. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  104. package/ios/Http/HttpClient.swift +8 -2
  105. package/ios/HybridAudioBrowser.swift +253 -100
  106. package/ios/Model/NitroTypeStubs.swift +125 -23
  107. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  108. package/ios/Model/RemoteCommand.swift +18 -0
  109. package/ios/Model/TrackPlayerError.swift +2 -2
  110. package/ios/Option/SessionCategories.swift +3 -2
  111. package/ios/Player/MediaIntentCriteria.swift +17 -9
  112. package/ios/Player/MediaLoader.swift +33 -9
  113. package/ios/Player/NowPlayingUpdater.swift +54 -6
  114. package/ios/Player/PlaybackCoordinator.swift +13 -6
  115. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  116. package/ios/Player/PlaybackTimer.swift +8 -2
  117. package/ios/Player/QueueManager.swift +46 -13
  118. package/ios/Player/RetryManager.swift +80 -18
  119. package/ios/Player/SeekCompletionHandler.swift +3 -1
  120. package/ios/Player/ShuffleOrder.swift +33 -0
  121. package/ios/Player/SleepTimerHandling.swift +13 -0
  122. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  123. package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
  124. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  125. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  126. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  127. package/ios/Tests/JsonModelsTests.swift +150 -1
  128. package/ios/Tests/LRUCacheTests.swift +111 -0
  129. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  130. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  131. package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
  132. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  133. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  134. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  135. package/ios/Tests/QueueManagerTests.swift +122 -10
  136. package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
  137. package/ios/Tests/RemoteCommandTests.swift +67 -0
  138. package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
  139. package/ios/Tests/RetryManagerTests.swift +89 -0
  140. package/ios/Tests/SearchDrillInTests.swift +16 -16
  141. package/ios/Tests/SectionPresentationTests.swift +90 -0
  142. package/ios/Tests/SectionScopeTests.swift +87 -55
  143. package/ios/Tests/StyleResolverTests.swift +127 -0
  144. package/ios/Tests/Support/TestTypes.swift +8 -1
  145. package/ios/Tests/TabBarEntriesTests.swift +19 -19
  146. package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
  147. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  148. package/ios/Tests/TrackIdentityTests.swift +20 -0
  149. package/ios/Tests/TrackSelectorTests.swift +168 -33
  150. package/ios/TrackPlayer.swift +63 -35
  151. package/ios/TrackPlayerCallbacks.swift +5 -38
  152. package/ios/TrackSelector.swift +45 -30
  153. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  154. package/ios/Util/LRUCache.swift +4 -1
  155. package/ios/Util/NetworkMonitor.swift +43 -26
  156. package/ios/Util/SFSymbolRenderer.swift +24 -2
  157. package/ios/Util/SVGProcessor.swift +9 -2
  158. package/lib/commonjs/AudioBrowser.js +8 -8
  159. package/lib/commonjs/AudioBrowser.js.map +1 -1
  160. package/lib/commonjs/CLAUDE.md +52 -0
  161. package/lib/commonjs/features/browser-config.js +12 -8
  162. package/lib/commonjs/features/browser-config.js.map +1 -1
  163. package/lib/commonjs/features/browser.js +11 -3
  164. package/lib/commonjs/features/browser.js.map +1 -1
  165. package/lib/commonjs/features/carConnection.js +1 -1
  166. package/lib/commonjs/features/equalizer.js +1 -1
  167. package/lib/commonjs/features/errors.js +3 -3
  168. package/lib/commonjs/features/favorites.js +14 -11
  169. package/lib/commonjs/features/favorites.js.map +1 -1
  170. package/lib/commonjs/features/gate.js +16 -1
  171. package/lib/commonjs/features/gate.js.map +1 -1
  172. package/lib/commonjs/features/metadata.js +20 -3
  173. package/lib/commonjs/features/metadata.js.map +1 -1
  174. package/lib/commonjs/features/network.js +1 -1
  175. package/lib/commonjs/features/nowPlaying.js +1 -1
  176. package/lib/commonjs/features/output.js +1 -1
  177. package/lib/commonjs/features/playback/controls.js +4 -2
  178. package/lib/commonjs/features/playback/controls.js.map +1 -1
  179. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  180. package/lib/commonjs/features/playback/playing.js +1 -1
  181. package/lib/commonjs/features/playback/progress.js +3 -2
  182. package/lib/commonjs/features/playback/progress.js.map +1 -1
  183. package/lib/commonjs/features/playback/rate.js +8 -1
  184. package/lib/commonjs/features/playback/rate.js.map +1 -1
  185. package/lib/commonjs/features/playback/state.js +3 -4
  186. package/lib/commonjs/features/playback/state.js.map +1 -1
  187. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  188. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  189. package/lib/commonjs/features/playback/volume.js +1 -1
  190. package/lib/commonjs/features/player/options.js +48 -34
  191. package/lib/commonjs/features/player/options.js.map +1 -1
  192. package/lib/commonjs/features/player/setup.js +18 -9
  193. package/lib/commonjs/features/player/setup.js.map +1 -1
  194. package/lib/commonjs/features/player/validateOptions.js +23 -0
  195. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  196. package/lib/commonjs/features/queue/activeTrack.js +15 -4
  197. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  198. package/lib/commonjs/features/queue/queue.js +13 -6
  199. package/lib/commonjs/features/queue/queue.js.map +1 -1
  200. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  201. package/lib/commonjs/features/queue/shuffle.js +1 -1
  202. package/lib/commonjs/features/remoteControls.js +13 -92
  203. package/lib/commonjs/features/remoteControls.js.map +1 -1
  204. package/lib/commonjs/features/sleepTimer.js +13 -1
  205. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  206. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  207. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  208. package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
  209. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  210. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  211. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  212. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  213. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  214. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  215. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  216. package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
  217. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  218. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
  219. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
  220. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  221. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  222. package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
  223. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  224. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  225. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  226. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
  227. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
  228. package/lib/commonjs/web/browser/BrowserManager.js +175 -100
  229. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  230. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  231. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  232. package/lib/commonjs/web/browser/SearchManager.js +18 -23
  233. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  234. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  235. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  236. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  237. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  238. package/lib/commonjs/web/browser/sections.js +89 -0
  239. package/lib/commonjs/web/browser/sections.js.map +1 -0
  240. package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
  241. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  242. package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
  243. package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
  244. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  245. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  246. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  247. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  248. package/lib/module/AudioBrowser.js +2 -1
  249. package/lib/module/AudioBrowser.js.map +1 -1
  250. package/lib/module/CLAUDE.md +52 -0
  251. package/lib/module/features/browser-config.js +12 -8
  252. package/lib/module/features/browser-config.js.map +1 -1
  253. package/lib/module/features/browser.js +11 -3
  254. package/lib/module/features/browser.js.map +1 -1
  255. package/lib/module/features/carConnection.js +1 -1
  256. package/lib/module/features/equalizer.js +1 -1
  257. package/lib/module/features/errors.js +3 -3
  258. package/lib/module/features/favorites.js +14 -11
  259. package/lib/module/features/favorites.js.map +1 -1
  260. package/lib/module/features/gate.js +16 -1
  261. package/lib/module/features/gate.js.map +1 -1
  262. package/lib/module/features/metadata.js +20 -3
  263. package/lib/module/features/metadata.js.map +1 -1
  264. package/lib/module/features/network.js +1 -1
  265. package/lib/module/features/nowPlaying.js +1 -1
  266. package/lib/module/features/output.js +1 -1
  267. package/lib/module/features/playback/controls.js +4 -2
  268. package/lib/module/features/playback/controls.js.map +1 -1
  269. package/lib/module/features/playback/playWhenReady.js +1 -1
  270. package/lib/module/features/playback/playing.js +1 -1
  271. package/lib/module/features/playback/progress.js +3 -2
  272. package/lib/module/features/playback/progress.js.map +1 -1
  273. package/lib/module/features/playback/rate.js +8 -1
  274. package/lib/module/features/playback/rate.js.map +1 -1
  275. package/lib/module/features/playback/state.js +3 -4
  276. package/lib/module/features/playback/state.js.map +1 -1
  277. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  278. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  279. package/lib/module/features/playback/volume.js +1 -1
  280. package/lib/module/features/player/options.js +47 -32
  281. package/lib/module/features/player/options.js.map +1 -1
  282. package/lib/module/features/player/setup.js +17 -8
  283. package/lib/module/features/player/setup.js.map +1 -1
  284. package/lib/module/features/player/validateOptions.js +19 -0
  285. package/lib/module/features/player/validateOptions.js.map +1 -0
  286. package/lib/module/features/queue/activeTrack.js +15 -4
  287. package/lib/module/features/queue/activeTrack.js.map +1 -1
  288. package/lib/module/features/queue/queue.js +13 -6
  289. package/lib/module/features/queue/queue.js.map +1 -1
  290. package/lib/module/features/queue/repeatMode.js +1 -1
  291. package/lib/module/features/queue/shuffle.js +1 -1
  292. package/lib/module/features/remoteControls.js +13 -92
  293. package/lib/module/features/remoteControls.js.map +1 -1
  294. package/lib/module/features/sleepTimer.js +13 -1
  295. package/lib/module/features/sleepTimer.js.map +1 -1
  296. package/lib/module/test-utils/reactNativeStub.js +58 -0
  297. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  298. package/lib/module/utils/LazyNativeEmitter.js +28 -14
  299. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  300. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  301. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  302. package/lib/module/utils/getTrackIdentity.js +20 -0
  303. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  304. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  305. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  306. package/lib/module/web/NativeAudioBrowser.js +200 -75
  307. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  308. package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
  309. package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
  310. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  311. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  312. package/lib/module/web/TrackPlayer/Player.js +254 -39
  313. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  314. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  315. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  316. package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
  317. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
  318. package/lib/module/web/browser/BrowserManager.js +175 -100
  319. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  320. package/lib/module/web/browser/FavoriteManager.js +31 -21
  321. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  322. package/lib/module/web/browser/SearchManager.js +18 -23
  323. package/lib/module/web/browser/SearchManager.js.map +1 -1
  324. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  325. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  326. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  327. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  328. package/lib/module/web/browser/sections.js +83 -0
  329. package/lib/module/web/browser/sections.js.map +1 -0
  330. package/lib/module/web/http/RequestConfigBuilder.js +110 -27
  331. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  332. package/lib/module/web/http/followMediaRedirect.js +215 -0
  333. package/lib/module/web/http/followMediaRedirect.js.map +1 -0
  334. package/lib/module/web/player/NowPlayingManager.js +4 -3
  335. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  336. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  337. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  338. package/lib/typescript/package.json +1 -0
  339. package/lib/typescript/src/AudioBrowser.d.ts +1 -1
  340. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  341. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  342. package/lib/typescript/src/features/browser.d.ts +10 -2
  343. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  344. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  345. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  346. package/lib/typescript/src/features/errors.d.ts +3 -3
  347. package/lib/typescript/src/features/favorites.d.ts +14 -11
  348. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  349. package/lib/typescript/src/features/gate.d.ts +18 -2
  350. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  351. package/lib/typescript/src/features/metadata.d.ts +20 -3
  352. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  353. package/lib/typescript/src/features/network.d.ts +1 -1
  354. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  355. package/lib/typescript/src/features/output.d.ts +1 -1
  356. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  357. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  358. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  359. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  360. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  361. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  362. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  363. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  364. package/lib/typescript/src/features/playback/state.d.ts +3 -4
  365. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  366. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  367. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  368. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  369. package/lib/typescript/src/features/player/options.d.ts +108 -56
  370. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  371. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  372. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  373. package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
  374. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  375. package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
  376. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  377. package/lib/typescript/src/features/queue/queue.d.ts +7 -4
  378. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  379. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  380. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  381. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  382. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  383. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  384. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  385. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  386. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  387. package/lib/typescript/src/types/browser-native.d.ts +3 -2
  388. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  389. package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
  390. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  391. package/lib/typescript/src/types/browser.d.ts +82 -57
  392. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  393. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
  394. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  395. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  396. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  397. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  398. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  399. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  400. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  401. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
  402. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
  403. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  404. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  405. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  406. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  407. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  408. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  409. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  410. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  411. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  412. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  413. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  414. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  415. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  416. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  417. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
  418. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  419. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  420. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
  421. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  422. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  423. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  424. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  425. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
  426. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  427. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  428. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  429. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  430. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
  431. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  432. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
  433. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
  434. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  435. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  436. package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
  437. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  438. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  439. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  440. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  441. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
  442. package/nitrogen/generated/android/c++/JSection.hpp +116 -0
  443. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  444. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  445. package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
  446. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  447. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  448. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  449. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  450. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  451. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  452. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  453. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  454. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  455. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
  456. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  457. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  458. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  459. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  460. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
  461. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  462. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  463. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  464. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  465. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  466. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
  467. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
  468. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  469. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  470. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
  471. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  472. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  473. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
  474. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
  475. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
  476. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
  477. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  478. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  479. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  480. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  481. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
  482. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  483. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  484. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  485. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  486. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  487. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
  488. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  489. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  490. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  491. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  492. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  493. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
  494. package/nitrogen/generated/ios/swift/Section.swift +82 -0
  495. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  496. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  497. package/nitrogen/generated/ios/swift/Track.swift +28 -63
  498. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  499. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  500. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  501. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  502. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  503. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  504. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  505. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  506. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  507. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  508. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  509. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
  510. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  511. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  512. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  513. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  514. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  515. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
  516. package/nitrogen/generated/shared/c++/Section.hpp +102 -0
  517. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  518. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  519. package/nitrogen/generated/shared/c++/Track.hpp +18 -30
  520. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  521. package/package.json +24 -11
  522. package/src/AudioBrowser.ts +2 -1
  523. package/src/CLAUDE.md +52 -0
  524. package/src/features/battery.test.ts +79 -0
  525. package/src/features/browser-config.test.ts +21 -6
  526. package/src/features/browser-config.ts +12 -8
  527. package/src/features/browser.test.ts +1 -1
  528. package/src/features/browser.ts +11 -3
  529. package/src/features/carConnection.ts +1 -1
  530. package/src/features/equalizer.ts +1 -1
  531. package/src/features/errors.ts +3 -3
  532. package/src/features/favorites.ts +14 -11
  533. package/src/features/gate.ts +16 -1
  534. package/src/features/hook-wiring.test.ts +302 -0
  535. package/src/features/metadata.ts +20 -3
  536. package/src/features/network.ts +1 -1
  537. package/src/features/nowPlaying.ts +1 -1
  538. package/src/features/output.ts +1 -1
  539. package/src/features/playback/controls.ts +4 -2
  540. package/src/features/playback/playWhenReady.ts +1 -1
  541. package/src/features/playback/playing.ts +1 -1
  542. package/src/features/playback/progress.test.ts +169 -0
  543. package/src/features/playback/progress.ts +3 -2
  544. package/src/features/playback/rate.ts +8 -1
  545. package/src/features/playback/state.ts +3 -4
  546. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  547. package/src/features/playback/trackPlaybackTime.ts +1 -6
  548. package/src/features/playback/volume.ts +1 -1
  549. package/src/features/player/options.ts +126 -76
  550. package/src/features/player/setup.test.ts +24 -7
  551. package/src/features/player/setup.ts +22 -10
  552. package/src/features/player/validateOptions.ts +21 -0
  553. package/src/features/queue/activeTrack.ts +15 -4
  554. package/src/features/queue/queue.ts +13 -6
  555. package/src/features/queue/repeatMode.ts +1 -1
  556. package/src/features/queue/shuffle.ts +1 -1
  557. package/src/features/remoteControls.ts +13 -92
  558. package/src/features/sleepTimer.test.ts +172 -0
  559. package/src/features/sleepTimer.ts +9 -1
  560. package/src/nitro-stub-drift.test.ts +277 -0
  561. package/src/public-surface.test.ts +147 -0
  562. package/src/specs/audio-browser.nitro.ts +5 -4
  563. package/src/style-resolution.test.ts +210 -0
  564. package/src/test-utils/reactNativeStub.ts +60 -0
  565. package/src/types/browser-native.ts +7 -3
  566. package/src/types/browser-nodes.ts +396 -134
  567. package/src/types/browser.ts +85 -59
  568. package/src/utils/LazyNativeEmitter.ts +28 -15
  569. package/src/utils/NativeUpdatedValue.ts +29 -13
  570. package/src/utils/getTrackIdentity.ts +20 -0
  571. package/src/utils/useDebug.test.ts +182 -0
  572. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  573. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  574. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
  575. package/src/web/NativeAudioBrowser.ts +239 -82
  576. package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
  577. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
  578. package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
  579. package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
  580. package/src/web/TrackPlayer/Player.load.test.ts +137 -1
  581. package/src/web/TrackPlayer/Player.ts +284 -37
  582. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  583. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  584. package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
  585. package/src/web/__tests__/lifecycle.test.ts +345 -0
  586. package/src/web/__tests__/lifecycleHarness.ts +351 -0
  587. package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
  588. package/src/web/__tests__/loadSupersession.test.ts +175 -0
  589. package/src/web/__tests__/retry.test.ts +169 -0
  590. package/src/web/browser/BrowserManager.ts +209 -111
  591. package/src/web/browser/FavoriteManager.test.ts +84 -0
  592. package/src/web/browser/FavoriteManager.ts +28 -23
  593. package/src/web/browser/SearchManager.test.ts +75 -0
  594. package/src/web/browser/SearchManager.ts +25 -28
  595. package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
  596. package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
  597. package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
  598. package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
  599. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  600. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  601. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  602. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  603. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  604. package/src/web/browser/sections.test.ts +91 -0
  605. package/src/web/browser/sections.ts +72 -0
  606. package/src/web/http/RequestConfigBuilder.test.ts +241 -1
  607. package/src/web/http/RequestConfigBuilder.ts +140 -33
  608. package/src/web/http/followMediaRedirect.test.ts +289 -0
  609. package/src/web/http/followMediaRedirect.ts +253 -0
  610. package/src/web/player/NowPlayingManager.ts +4 -3
  611. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  612. package/src/web/util/BrowserPathHelper.ts +61 -13
  613. package/website/guide/android-auto.md +114 -0
  614. package/website/guide/android-certificates.md +409 -0
  615. package/website/guide/artwork.md +255 -0
  616. package/website/guide/audio-output.md +135 -0
  617. package/website/guide/automotive.md +86 -0
  618. package/website/guide/basic-usage.md +151 -0
  619. package/website/guide/battery.md +115 -0
  620. package/website/guide/browser.md +581 -0
  621. package/website/guide/carplay.md +620 -0
  622. package/website/guide/configuration.md +173 -0
  623. package/website/guide/equalizer.md +231 -0
  624. package/website/guide/errors.md +310 -0
  625. package/website/guide/favorites.md +154 -0
  626. package/website/guide/gate.md +143 -0
  627. package/website/guide/getting-started.md +46 -0
  628. package/website/guide/hooks.md +238 -0
  629. package/website/guide/live-streams.md +125 -0
  630. package/website/guide/metadata.md +239 -0
  631. package/website/guide/migrating-from-track-player.md +244 -0
  632. package/website/guide/native-callback-fetch.md +127 -0
  633. package/website/guide/network.md +86 -0
  634. package/website/guide/now-playing.md +175 -0
  635. package/website/guide/playback.md +328 -0
  636. package/website/guide/queue.md +300 -0
  637. package/website/guide/remote-controls.md +256 -0
  638. package/website/guide/search.md +255 -0
  639. package/website/guide/sleep-timer.md +268 -0
  640. package/website/guide/track.md +247 -0
  641. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  642. package/ios/Model/MediaURL.swift +0 -67
  643. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  644. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  645. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  646. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  647. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  648. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  649. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  653. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  654. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  655. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  656. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  657. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  658. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  659. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
  660. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -6,8 +6,12 @@
6
6
  * 1. System paths (prefixed with `/__`): Root, recent, and search paths
7
7
  * 2. Contextual URLs: Embed parent context in track identifiers for Media3 integration
8
8
  *
9
- * Contextual URL format: `{parentPath}?__trackId={trackSrc}`
10
- * Example: "/library/radio?__trackId=song.mp3"
9
+ * Contextual URL format: `{parentPath}?__trackId={trackIdentity}&__index={childIndex}`
10
+ * Example: "/library/radio?__trackId=song.mp3&__index=2"
11
+ *
12
+ * `__trackId` is the identity check; `__index` (the child's position on the
13
+ * page at stamp time) is only a tie-breaker between surfaces that carry the
14
+ * same identity — a stale index never selects a different track.
11
15
  *
12
16
  * This allows:
13
17
  * - Media3 to reference playable-only tracks (tracks with `src` but no `url`)
@@ -21,6 +25,9 @@ export const BrowserPathHelper = {
21
25
  /** Query parameter name for contextual track identifiers */
22
26
  CONTEXTUAL_TRACK_PARAM: '__trackId',
23
27
 
28
+ /** Query parameter name for the tapped child's page position (tie-breaker) */
29
+ CONTEXTUAL_INDEX_PARAM: '__index',
30
+
24
31
  /**
25
32
  * Create a search path for a given query
26
33
  */
@@ -43,13 +50,13 @@ export const BrowserPathHelper = {
43
50
  },
44
51
 
45
52
  /**
46
- * Strips the __trackId parameter from a contextual URL to get the parent path.
47
- * If the URL is not contextual, returns it unchanged.
53
+ * Strips the contextual parameters (__trackId and __index) from a contextual
54
+ * URL to get the parent path. If the URL is not contextual, returns it unchanged.
48
55
  *
49
56
  * @param url The URL to process
50
- * @returns The URL without the __trackId parameter
57
+ * @returns The URL without the contextual parameters
51
58
  *
52
- * Example: "/library/radio?__trackId=song.mp3" → "/library/radio"
59
+ * Example: "/library/radio?__trackId=song.mp3&__index=2" → "/library/radio"
53
60
  * Example: "/search?q=jazz&__trackId=song.mp3" → "/search?q=jazz"
54
61
  */
55
62
  stripTrackId(url: string): string {
@@ -62,8 +69,9 @@ export const BrowserPathHelper = {
62
69
  const isFullUrl = url.startsWith('http://') || url.startsWith('https://')
63
70
  const urlObj = new URL(url, isFullUrl ? undefined : 'http://placeholder')
64
71
 
65
- // Remove the __trackId parameter
72
+ // Remove the contextual parameters
66
73
  urlObj.searchParams.delete(this.CONTEXTUAL_TRACK_PARAM)
74
+ urlObj.searchParams.delete(this.CONTEXTUAL_INDEX_PARAM)
67
75
 
68
76
  // Return the appropriate format
69
77
  if (isFullUrl) {
@@ -75,7 +83,7 @@ export const BrowserPathHelper = {
75
83
  } catch {
76
84
  // Fallback: simple string manipulation
77
85
  const paramPattern = new RegExp(
78
- `[?&]${this.CONTEXTUAL_TRACK_PARAM}=[^&]*`,
86
+ `[?&](?:${this.CONTEXTUAL_TRACK_PARAM}|${this.CONTEXTUAL_INDEX_PARAM})=[^&]*`,
79
87
  'g'
80
88
  )
81
89
  let result = url.replace(paramPattern, '')
@@ -87,6 +95,24 @@ export const BrowserPathHelper = {
87
95
  }
88
96
  },
89
97
 
98
+ /**
99
+ * Builds a contextual URL by appending a track identifier — and optionally
100
+ * the tapped child's page position — to a parent path.
101
+ *
102
+ * @param parentPath The parent container path
103
+ * @param trackId The track identity (`id` when non-blank, else `src`)
104
+ * @param index The child's position on the page at stamp time (tie-breaker)
105
+ * @returns A contextual URL combining parent path, track ID, and index
106
+ *
107
+ * Example: build("/library", "song.mp3", 2) → "/library?__trackId=song.mp3&__index=2"
108
+ */
109
+ build(parentPath: string, trackId: string, index?: number): string {
110
+ const separator = parentPath.includes('?') ? '&' : '?'
111
+ const indexParam =
112
+ index === undefined ? '' : `&${this.CONTEXTUAL_INDEX_PARAM}=${index}`
113
+ return `${parentPath}${separator}${this.CONTEXTUAL_TRACK_PARAM}=${encodeURIComponent(trackId)}${indexParam}`
114
+ },
115
+
90
116
  /**
91
117
  * Extracts the track ID from a contextual URL.
92
118
  * Returns undefined if the URL is not contextual or doesn't contain the track ID parameter.
@@ -100,18 +126,40 @@ export const BrowserPathHelper = {
100
126
  if (!this.isContextual(path)) {
101
127
  return undefined
102
128
  }
129
+ return this.extractParam(path, this.CONTEXTUAL_TRACK_PARAM)
130
+ },
131
+
132
+ /**
133
+ * Extracts the stamped page index from a contextual URL, or undefined when
134
+ * the URL is not contextual or carries no (valid, non-negative) index.
135
+ *
136
+ * Example: "/library/radio?__trackId=song.mp3&__index=2" → 2
137
+ */
138
+ extractIndex(path: string): number | undefined {
139
+ if (!this.isContextual(path)) {
140
+ return undefined
141
+ }
142
+
143
+ const raw = this.extractParam(path, this.CONTEXTUAL_INDEX_PARAM)
144
+ if (raw === undefined || !/^\d+$/.test(raw)) {
145
+ return undefined
146
+ }
147
+ return Number.parseInt(raw, 10)
148
+ },
103
149
 
150
+ /**
151
+ * Extracts a single query parameter, handling both full URLs and paths,
152
+ * with a regex fallback for input the URL parser rejects.
153
+ */
154
+ extractParam(path: string, param: string): string | undefined {
104
155
  try {
105
- // Parse the URL - handle both full URLs and paths
106
156
  const isFullUrl =
107
157
  path.startsWith('http://') || path.startsWith('https://')
108
158
  const urlObj = new URL(path, isFullUrl ? undefined : 'http://placeholder')
109
- return urlObj.searchParams.get(this.CONTEXTUAL_TRACK_PARAM) ?? undefined
159
+ return urlObj.searchParams.get(param) ?? undefined
110
160
  } catch {
111
161
  // Fallback: regex extraction
112
- const match = path.match(
113
- new RegExp(`[?&]${this.CONTEXTUAL_TRACK_PARAM}=([^&]*)`)
114
- )
162
+ const match = path.match(new RegExp(`[?&]${param}=([^&]*)`))
115
163
  return match?.[1] ? decodeURIComponent(match[1]) : undefined
116
164
  }
117
165
  },
@@ -0,0 +1,114 @@
1
+ ---
2
+ description: 'Put your browse tree and playback on the Android Auto car screen: the automotive app descriptor, the manifest entries the library merges in, and "Hey Google, play…" voice search.'
3
+ ---
4
+
5
+ # Android Auto
6
+
7
+ ## Overview
8
+
9
+ Android Auto shows your [browse tree](/guide/basic-usage) on the car screen and plays through the same player as your app. Two systems drive it:
10
+
11
+ 1. **Browse tree** — Android Auto reads your content from a `MediaBrowserService`, which mirrors your browser routes. The library ships this service.
12
+ 2. **Voice search** — "Hey Google, play…" arrives as a `MEDIA_PLAY_FROM_SEARCH` intent, which the library routes to your `search` source.
13
+
14
+ Unlike CarPlay — which needs entitlements and scene delegates — Android Auto needs almost no app-side code. The library's own `AndroidManifest.xml` declares the media-browser service, the media session, the voice-search intent filter, and the foreground-service permissions, and those **merge into your app automatically**. You only have to declare your app as an automotive media app.
15
+
16
+ ## Setup
17
+
18
+ ### 1. Declare the automotive app descriptor
19
+
20
+ Add this `<meta-data>` inside the `<application>` tag of your app's `AndroidManifest.xml`:
21
+
22
+ ```xml
23
+ <meta-data
24
+ android:name="com.google.android.gms.car.application"
25
+ android:resource="@xml/automotive_app_desc" />
26
+ ```
27
+
28
+ ### 2. Add the descriptor resource
29
+
30
+ Create `android/app/src/main/res/xml/automotive_app_desc.xml`:
31
+
32
+ ```xml
33
+ <automotiveApp>
34
+ <uses name="media"/>
35
+ </automotiveApp>
36
+ ```
37
+
38
+ That's the whole setup. The `MediaBrowserService`, media session, `MEDIA_PLAY_FROM_SEARCH` intent filter, headless service for background playback, and `FOREGROUND_SERVICE` / `FOREGROUND_SERVICE_MEDIA_PLAYBACK` permissions all come from the library's merged manifest — you don't declare them yourself.
39
+
40
+ ## Voice search
41
+
42
+ Google Assistant requests ("play some jazz", "play the radishes") arrive as a `MEDIA_PLAY_FROM_SEARCH` intent. The library normalizes it into the same structured `SearchParams` your in-app and Siri searches use and calls your `search` source — no extra manifest setup, since the library declares the intent filter.
43
+
44
+ Android sends a subset of what iOS does: the query plus music focuses (genre / artist / album), always with `reference: 'unknown'`. See [Search](/guide/search) for how to resolve these requests and the cross-platform differences.
45
+
46
+ ## Browse display
47
+
48
+ Android Auto reads a page's layout from the **parent item** that opens it — before the page resolves. So the layout is declared in two places: on the parent item (a tab, or a row on another page) and on the page itself.
49
+
50
+ ```ts
51
+ // The parent item — a tab, or a row on another page. Android Auto
52
+ // reads the layout from here, before the page resolves.
53
+ {
54
+ title: 'Stations',
55
+ path: '/browse/stations',
56
+ style: { display: 'grid' }, // layout of the page this opens ('list' is the default)
57
+ }
58
+
59
+ // The page it opens — the ResolvedTrack served for '/browse/stations':
60
+ {
61
+ path: '/browse/stations',
62
+ title: 'Stations',
63
+ style: { display: 'grid' }, // the page-wide declaration
64
+ children: [ /* the tiles */ ]
65
+ }
66
+ ```
67
+
68
+ - **`style.display` on the parent item** — `'list'` (rows) or `'grid'` (tiles). Below the root this is the only layout signal Android Auto honors, and it is only sent when you declare it — leave it off and the page renders as a list. In dev mode the library warns when an all-grid page is opened from a parent item with no declared `display`. (A section's "view all" page needs no declaration of its own: it reuses the section's `style`.)
69
+ - **Sections** — declare `sections` on the page to group its tracks under headers; each [`Section`](/api/types/browser-nodes/#section)'s `title` renders as a header above its children. A section that carries a `path` also gets a browsable "view more" row appended after its children, navigating there. No header is tappable on Android Auto at any `display`, so the row is the only affordance the section's `path` has here; its title comes from the [`viewMoreTitle`](/api/types/browser/#viewmoretitle) browser option, defaulting to `"View more"` — one wording for every section. For per-section copy (`"View all 42 albums"`), leave `path` unset and append your own browsable `Track` to `children` instead, titled however you like; this page's content is Android-only anyway, so CarPlay never sees it.
70
+
71
+ ::: warning AAOS lays out the whole page from the parent item's `display`
72
+ On AOSP-based Android Automotive units — including mainstream cars' OEM skins — the parent item's `display` is the **only** below-root layout signal honored: the whole page renders as list or grid, uniformly. Per-section `display` differences (a single-line grid above a list, say) render on CarPlay and projected Android Auto, but an AAOS unit shows the page in the single declared layout. There is no way to describe a mixed page there — pick the layout that suits its dominant section, or split the content across pages. `gridWrap: false` also has no single-line container there: the grid wraps, showing more, never less.
73
+ :::
74
+
75
+ ```ts
76
+ sections: [
77
+ {
78
+ title: 'Live now',
79
+ children: [
80
+ { title: 'Morning Show', src: '…' },
81
+ { title: 'Afternoon Drive', src: '…' }
82
+ ]
83
+ },
84
+ { title: 'Up next', children: [{ title: 'Late Night Jazz', src: '…' }] }
85
+ ]
86
+ ```
87
+
88
+ ## Now Playing buttons
89
+
90
+ The buttons on the Now Playing screen come from the same
91
+ [`remoteButtonLayout`](/guide/remote-controls#button-layout-android) that drives the
92
+ notification — there's no car-specific option. Two behaviors are particular to
93
+ the car, though:
94
+
95
+ - **The head unit has the last word.** Slots are a preference. A unit with a
96
+ spare position may promote the first `overflow` entry onto the main row, so
97
+ a button you placed in overflow can still appear beside play/pause.
98
+ - **Fewer buttons, cleaner row.** If you want just
99
+ `jump-back │ play/pause │ jump-forward`, leave the rest out of the layout
100
+ entirely — they keep working from the steering wheel and headset regardless.
101
+
102
+ ## Testing
103
+
104
+ Test in a real car, or in Google's **Desktop Head Unit (DHU)** emulator:
105
+
106
+ 1. Install the **Android Auto Desktop Head Unit emulator** from the SDK Manager (_SDK Tools → Android Auto Desktop Head Unit emulator_).
107
+ 2. In the phone's **Android Auto** settings, enable Developer Mode (tap the version 10×), then **"Start head unit server"**.
108
+ 3. Connect the phone over USB and run the DHU.
109
+
110
+ See Google's [Test Android Auto apps with DHU](https://developer.android.com/training/cars/testing/dhu) for the full walkthrough. Your app appears under the media apps — browse the tree, exercise voice search, and verify artwork, grid/list styles, and section headers.
111
+
112
+ ## Reference
113
+
114
+ See the [example app](https://github.com/radio-garden/react-native-audio-browser/tree/main/apps/example-native/android) for a complete working manifest and descriptor.
@@ -0,0 +1,409 @@
1
+ ---
2
+ description: 'Fix Android SSLHandshakeException / "Trust anchor for certification path not found" on HTTPS streams whose server omits its intermediate certificate.'
3
+ ---
4
+
5
+ # Android: SSL "Trust anchor for certification path not found" (missing intermediate certificates)
6
+
7
+ **If Android playback of a radio stream (or any HTTPS audio source) fails with an
8
+ `SSLHandshakeException` / `CertPathValidatorException` / "Trust anchor for
9
+ certification path not found" — and the same stream plays fine on iOS and in a
10
+ desktop browser — you are in the right place.** This is almost always a server
11
+ that omits an intermediate certificate; the fix is to let the client chase the
12
+ missing certificate via the leaf's Authority Information Access (AIA) pointer, and
13
+ this guide shows the one-time Android setup that does it.
14
+
15
+ ## The problem
16
+
17
+ Some HTTPS servers are misconfigured to send only their **leaf** certificate and
18
+ omit the **intermediate** certificate(s) needed to chain up to a trusted root. A
19
+ correctly configured server sends the full chain; a misconfigured one leaves the
20
+ client to find the gap on its own.
21
+
22
+ - **iOS and web browsers** recover automatically: they follow the leaf's
23
+ Authority Information Access (AIA) "CA Issuers" pointer to fetch the missing
24
+ intermediate. (This is why the stream works everywhere except Android.)
25
+ - **Android's default trust manager does not.** It validates only what the server
26
+ sent, so playback of such a stream fails with:
27
+
28
+ ```
29
+ javax.net.ssl.SSLHandshakeException:
30
+ java.security.cert.CertPathValidatorException:
31
+ Trust anchor for certification path not found.
32
+ ```
33
+
34
+ This affects media playback because the Android player transports audio over
35
+ `HttpURLConnection`, which uses the process-wide default `SSLSocketFactory`.
36
+
37
+ > **iOS needs no setup.** Apple's TLS stack chases AIA intermediates on its own,
38
+ > so there is nothing to configure on iOS — this guide is Android-only.
39
+
40
+ ## Is this actually your problem?
41
+
42
+ Before changing process-global TLS state, confirm the failing stream really is a
43
+ missing-intermediate case and not some other TLS failure (an expired cert, an SNI
44
+ mismatch, or a genuinely untrusted root — none of which this fix addresses).
45
+
46
+ Run, against your failing stream's **host** — use just the hostname, not the full
47
+ stream URL. If your stream URL is `https://example.com:8000/live.mp3`, the host is
48
+ `example.com` and the port is `8000` (omit the path); plain `https://` URLs with no
49
+ port use `443`:
50
+
51
+ ```sh
52
+ openssl s_client -connect example.com:443 \
53
+ -servername example.com -showcerts </dev/null
54
+ ```
55
+
56
+ In the output:
57
+
58
+ - A **complete** chain lists two or more certificates under
59
+ `Certificate chain` (leaf `s:.../CN=host` then one or more issuers `i:...`) and
60
+ ends with `Verify return code: 0 (ok)`.
61
+ - A **missing-intermediate** server typically shows **only one** certificate in
62
+ the chain and a verify error such as
63
+ `Verify return code: 21 (unable to verify the first certificate)` — yet the
64
+ leaf's AIA block contains a `CA Issuers - URI:` pointer. That combination is the
65
+ exact case this fix repairs.
66
+
67
+ Prefer a browser? Paste the host into the [SSL Labs server
68
+ test](https://www.ssllabs.com/ssltest/) — it flags **"Chain issues: Incomplete"**
69
+ for this exact problem — or use [whatsmychaincert.com](https://whatsmychaincert.com/).
70
+
71
+ > **Want to see a broken chain first?** `incomplete-chain.badssl.com` is a public
72
+ > test host that deliberately omits its intermediate — the canonical example of
73
+ > this misconfiguration. Run the command above against
74
+ > `incomplete-chain.badssl.com:443` to see exactly what the "one certificate +
75
+ > verify error + AIA pointer present" signature looks like before you go hunting in
76
+ > your own stream.
77
+
78
+ If the chain is complete, or there is no `CA Issuers` AIA pointer, this fix will
79
+ **not** help — see [Caveats](#caveats).
80
+
81
+ ## Opt in
82
+
83
+ The library ships an **additive** AIA-chasing `SSLSocketFactory` but does **not**
84
+ install it for you — installing it sets process-global TLS state, which is a
85
+ decision for the host application to make, not a library. Enable it by setting it
86
+ as the default in your **existing** `MainApplication`'s `onCreate`, before any
87
+ playback begins. `MainApplication.onCreate` runs before React Native loads and
88
+ executes your JS bundle, so it is always early enough — no playback can have
89
+ started yet.
90
+
91
+ > Add the highlighted line to the `MainApplication` you already have (it lives at
92
+ > `android/app/src/main/java/.../MainApplication.kt` or `.java`). **Don't create a
93
+ > new file or a second `MainApplication` class** — just drop the call into the
94
+ > `onCreate` you already have, after `super.onCreate()`.
95
+
96
+ ::: code-group
97
+
98
+ ```kotlin [MainApplication.kt]
99
+ import com.audiobrowser.tls.AiaTls
100
+ import javax.net.ssl.HttpsURLConnection
101
+ import javax.net.ssl.SSLSocketFactory
102
+ import android.util.Log
103
+
104
+ class MainApplication : Application() {
105
+ override fun onCreate() {
106
+ super.onCreate()
107
+
108
+ try {
109
+ val aiaFactory = AiaTls.socketFactory() // [!code ++]
110
+ HttpsURLConnection.setDefaultSSLSocketFactory(aiaFactory) // [!code ++]
111
+ installedSslFactory = aiaFactory // [!code ++]
112
+ } catch (e: Exception) {
113
+ // Never let TLS setup block startup; playback keeps default behaviour.
114
+ Log.w("MainApplication", "Failed to install AIA-chasing TLS", e)
115
+ }
116
+
117
+ // ... the rest of your existing setup
118
+ }
119
+
120
+ companion object {
121
+ /** Kept so you can confirm the install — see "Verify it worked". */
122
+ var installedSslFactory: SSLSocketFactory? = null
123
+ }
124
+ }
125
+ ```
126
+
127
+ ```java [MainApplication.java]
128
+ import com.audiobrowser.tls.AiaTls;
129
+ import javax.net.ssl.HttpsURLConnection;
130
+ import javax.net.ssl.SSLSocketFactory;
131
+ import android.util.Log;
132
+
133
+ public class MainApplication extends Application {
134
+ /** Kept so you can confirm the install — see "Verify it worked". */
135
+ public static SSLSocketFactory installedSslFactory = null;
136
+
137
+ @Override
138
+ public void onCreate() {
139
+ super.onCreate();
140
+
141
+ try {
142
+ SSLSocketFactory aiaFactory = AiaTls.socketFactory(); // [!code ++]
143
+ HttpsURLConnection.setDefaultSSLSocketFactory(aiaFactory); // [!code ++]
144
+ installedSslFactory = aiaFactory; // [!code ++]
145
+ } catch (Exception e) {
146
+ // Never let TLS setup block startup; playback keeps default behaviour.
147
+ Log.w("MainApplication", "Failed to install AIA-chasing TLS", e);
148
+ }
149
+
150
+ // ... the rest of your existing setup
151
+ }
152
+ }
153
+ ```
154
+
155
+ :::
156
+
157
+ That's all. The player's data source picks up the process default automatically.
158
+ `AiaTls` ships with the `react-native-audio-browser` Android module you already
159
+ installed — the `com.audiobrowser.tls` import resolves with no extra Gradle
160
+ dependency.
161
+
162
+ > **Rebuild after adding this.** `AiaTls` is native Kotlin, so a JS-only reload
163
+ > won't pick up the new call — do a full native rebuild:
164
+ >
165
+ > ```sh
166
+ > npx react-native run-android
167
+ > # if the build seems stale, clear Gradle first:
168
+ > cd android && ./gradlew clean && cd .. && npx react-native run-android
169
+ > ```
170
+ >
171
+ > No manifest or permission change is needed — RN apps already hold the `INTERNET`
172
+ > permission the AIA fetch uses, and the cleartext fetch is handled for you (see
173
+ > [The cleartext fetch is handled for you](#the-cleartext-fetch-is-handled-for-you)).
174
+
175
+ ## The cleartext fetch is handled for you
176
+
177
+ The completed-chain check happens over HTTPS — only the side fetch of the _missing
178
+ intermediate_ uses the leaf's `CA Issuers` URL, which CAs publish over plain
179
+ `http://` (mandated by the CA/Browser Forum Baseline Requirements, to avoid a
180
+ chicken-and-egg TLS dependency when fetching the very certificate needed to
181
+ complete a TLS chain).
182
+
183
+ That's a problem on most modern apps: targeting Android 9+ (API 28) **blocks
184
+ cleartext HTTP by default**, which would silently break the fetch on release
185
+ builds. So the library doesn't use Android's HTTP stack for it — the `http`
186
+ intermediate fetch goes over a **raw socket**, which isn't subject to your app's
187
+ `usesCleartextTraffic` / network-security-config policy. **You don't need to enable
188
+ cleartext or add a network security config.**
189
+
190
+ This stays safe: the fetched certificate is cryptographically verified to be the
191
+ genuine issuer and re-validated against the system trust anchors before use, so a
192
+ tampered response over plain HTTP can only fail the chain, never weaken it.
193
+
194
+ ## Verify it worked
195
+
196
+ A silent success is hard to tell apart from "my problem was something else," so
197
+ confirm it explicitly:
198
+
199
+ 1. **Confirm the factory is installed.** The `catch` above only logs on _failure_,
200
+ so **no warning at startup means it installed without throwing** — enough for a
201
+ simple app. But it is _not_ enough if your app uses other networking libraries:
202
+ `setDefaultSSLSocketFactory` is last-write-wins (see [Caveats](#caveats)), so
203
+ another library setting its own default afterward silently deactivates the fix
204
+ with no warning. The comparison below is the **only reliable check** in that
205
+ case. `AiaTls.socketFactory()` returns a stock TLS socket factory configured
206
+ with the AIA-chasing trust manager, so its _class name_ looks identical to the
207
+ platform default — don't try to recognise it by class. Instead compare the
208
+ process default against the reference the opt-in snippet stored in
209
+ `installedSslFactory`. Put this **after** the install `try`/`catch` (so it
210
+ reflects the real process default, not the value you just set), anywhere that
211
+ runs after startup:
212
+
213
+ ::: code-group
214
+
215
+ ```kotlin [Kotlin]
216
+ // place OUTSIDE the install try/catch — this tests the live process default
217
+ val active = HttpsURLConnection.getDefaultSSLSocketFactory() ===
218
+ MainApplication.installedSslFactory
219
+ Log.i("MainApplication", "AIA TLS active: $active")
220
+ ```
221
+
222
+ ```java [Java]
223
+ // place OUTSIDE the install try/catch — this tests the live process default
224
+ boolean active = HttpsURLConnection.getDefaultSSLSocketFactory()
225
+ == MainApplication.installedSslFactory;
226
+ Log.i("MainApplication", "AIA TLS active: " + active);
227
+ ```
228
+
229
+ :::
230
+
231
+ 2. **Confirm playback recovers.** Play the exact stream that previously failed.
232
+ It should now start, and the
233
+ `SSLHandshakeException: ... Trust anchor for certification path not found`
234
+ should no longer appear in `adb logcat`. No real broken stream on hand? Point
235
+ the player at a known-broken-chain source such as
236
+ `https://incomplete-chain.badssl.com/` to prove the wiring end-to-end before you
237
+ rely on it. Keep one of these around as a regression check for future upgrades.
238
+
239
+ ### Still failing after install?
240
+
241
+ Work through these in order:
242
+
243
+ 1. **Is it even a missing-intermediate case?** Re-run the
244
+ [diagnosis](#is-this-actually-your-problem). If the chain is complete, there's
245
+ no `CA Issuers` AIA pointer, or the root is genuinely untrusted/expired, this
246
+ fix can't repair it — that's a different problem.
247
+ 2. **Did the AIA fetch fail?** When the chaser tries to fetch the intermediate and
248
+ can't, the library logs a warning:
249
+
250
+ ```
251
+ AIA CA-issuer fetch failed: http://…/issuer.crt
252
+ ```
253
+
254
+ Filter `adb logcat` for `AIA CA-issuer fetch failed`. If it appears, the fetch
255
+ was attempted but couldn't be reached — see the next point. (You do _not_ need
256
+ to enable cleartext; the library's fetch already bypasses that policy.)
257
+
258
+ ::: warning The logger starts with the playback service
259
+ The library plants its logging trees when its service first starts, so a fetch
260
+ that fails **before** any playback has begun logs nothing — even though the
261
+ socket factory is process-wide and already in use. If you see no line at all,
262
+ start playback once and retry, or plant your own tree at startup.
263
+ :::
264
+
265
+ 3. **Reachable from your environment?** The fetch is a plain outbound request to
266
+ the `CA Issuers - URI` during the handshake, so a corporate proxy, VPN,
267
+ firewall, or a locked-down emulator network can block it. Confirm the URL
268
+ resolves by fetching it yourself — `curl -I "http://…/issuer.crt"` — from the
269
+ same network the device is on.
270
+ 4. **Custom Network Security Config?** If your app ships a
271
+ `res/xml/network_security_config.xml` with custom trust anchors or pinning,
272
+ that's an unrelated cause of TLS failures — this fix won't override it.
273
+
274
+ ## How it works
275
+
276
+ `AiaTls.socketFactory()` wraps the **platform default** trust manager. On the
277
+ happy path the default accepts the server-presented chain on the first try and
278
+ nothing extra happens. Only when the default _rejects_ a chain does it:
279
+
280
+ 1. read the leaf's AIA "CA Issuers" URL,
281
+ 2. fetch the missing intermediate (over a raw socket for `http` URLs, so it works
282
+ regardless of your cleartext policy; cached for the process lifetime),
283
+ 3. re-validate the completed chain **against the same system trust anchors**.
284
+
285
+ A fetched certificate is used only if it is genuinely the issuer of the preceding
286
+ one. Because the final decision is still the system trust manager's, this can
287
+ **only ever add a missing intermediate — it can never weaken trust**. Untrusted,
288
+ expired, or self-signed roots still fail exactly as before.
289
+
290
+ ## Caveats
291
+
292
+ - **Scope.** `setDefaultSSLSocketFactory` is process-wide, so it affects every
293
+ `HttpURLConnection`-based TLS connection in your app, not only playback. (It
294
+ does not affect OkHttp or a `WebView`, which keep their own TLS stacks.) It is
295
+ also last-write-wins: if other code sets its own default afterwards, it
296
+ overrides this one.
297
+ - **First-connection latency on broken servers.** When a server omits its
298
+ intermediate, the first connection performs one extra HTTP request for the
299
+ intermediate during the TLS handshake. Well-configured servers pay no cost. The
300
+ fetched intermediate is cached for the **process lifetime only**, so this
301
+ one-time cost recurs once per cold start for each broken server.
302
+ - **Not a cure-all.** It only rescues servers that omit an intermediate _and_
303
+ publish an AIA "CA Issuers" pointer that resolves to a trusted root. Servers
304
+ with no AIA extension, an unreachable intermediate, or a genuinely untrusted
305
+ root still fail.
306
+ - **The AIA fetch needs outbound network reachability.** Completing the chain
307
+ performs a plain HTTP request to the `CA Issuers` host during the handshake; a
308
+ proxy, VPN, or firewall that blocks it makes the fix look broken even on a
309
+ genuinely missing-intermediate server. See
310
+ [Still failing after install?](#still-failing-after-install) to diagnose.
311
+ - **Unrelated cause: custom Network Security Config.** A
312
+ `res/xml/network_security_config.xml` with custom trust anchors or certificate
313
+ pinning is a _different_ source of TLS failures that this fix does not address.
314
+
315
+ ## What you are opting into
316
+
317
+ Trust is never weakened. The chain is only ever _extended_, and the completed
318
+ chain is re-validated by the platform's own trust manager against the same
319
+ system anchors, so anything Android would have rejected is still rejected.
320
+
321
+ What you are accepting is an outbound request driven by a certificate that just
322
+ failed validation. Completing a chain means fetching the missing intermediate
323
+ from a URL written into that certificate, so a rejected handshake can cause a
324
+ cleartext request to a **host, port and path of the server's choosing** —
325
+ including a loopback or LAN address — and that request is deliberately not
326
+ subject to your app's `NetworkSecurityPolicy` (see
327
+ [The cleartext fetch is handled for you](#the-cleartext-fetch-is-handled-for-you)).
328
+
329
+ This is inherent to AIA chasing: browsers and Apple's Secure Transport do the
330
+ same thing, which is why the servers in question work everywhere except Android.
331
+ It is bounded rather than absent:
332
+
333
+ - the fetch happens **only** when the chain does not already reach one of your
334
+ trust anchors, so the common non-path failures — an expired leaf, a hostname
335
+ mismatch — do not trigger one. (The test is on the issuer name at the top of
336
+ the chain, so a chain topping at a root your device does _not_ carry, such as
337
+ a server still serving the retired DST Root CA X3 cross-sign, is still chased
338
+ even if its real problem is expiry.)
339
+ - the URL is rejected unless it is free of control characters, so it cannot
340
+ inject request lines of its own;
341
+ - at most 3 distinct `CA Issuers` URLs per certificate are tried, a response is
342
+ capped at 1 MiB, and at most 5 redirects are followed per fetch;
343
+ - the whole chase runs under a single 20-second wall-clock budget — every
344
+ connect, TLS handshake, read and redirect hop is clipped to the time
345
+ remaining, and nothing new starts once it has expired. So the worst case a
346
+ hostile certificate can inflict on one failed handshake is roughly those
347
+ 20 seconds, plus at most one DNS lookup that was already in flight when the
348
+ budget expired (Java offers no timeout for name resolution, so a lookup can
349
+ overrun the budget by its own duration — but a new one is never started past
350
+ it);
351
+ - the cache holds at most 32 entries, and only successful fetches;
352
+ - responses are never trusted on their say-so: a fetched certificate is used
353
+ only if it is the genuine issuer of the certificate below it, and the
354
+ completed chain still has to validate.
355
+
356
+ If your threat model does not allow a failed handshake to trigger an outbound
357
+ connection at all, don't install this — or install it on a single client
358
+ (see [Advanced: custom OkHttp client](#advanced-custom-okhttp-client)) rather
359
+ than as the process default.
360
+
361
+ ## Advanced: custom OkHttp client
362
+
363
+ If you manage your own HTTP client instead of changing the process default, use
364
+ `AiaTls.trustManager()` to obtain the wrapped `X509TrustManager` and build a
365
+ matching `SSLSocketFactory` from it. OkHttp's `sslSocketFactory(...)` needs
366
+ **both** the factory and the trust manager:
367
+
368
+ ::: code-group
369
+
370
+ ```kotlin [Kotlin]
371
+ import com.audiobrowser.tls.AiaTls
372
+ import javax.net.ssl.SSLContext
373
+ import okhttp3.OkHttpClient
374
+
375
+ val trustManager = AiaTls.trustManager()
376
+ val sslContext = SSLContext.getInstance("TLS").apply {
377
+ init(null, arrayOf(trustManager), null)
378
+ }
379
+
380
+ val client = OkHttpClient.Builder()
381
+ .sslSocketFactory(sslContext.socketFactory, trustManager)
382
+ .build()
383
+ ```
384
+
385
+ ```java [Java]
386
+ import com.audiobrowser.tls.AiaTls;
387
+ import javax.net.ssl.SSLContext;
388
+ import javax.net.ssl.X509TrustManager;
389
+ import okhttp3.OkHttpClient;
390
+
391
+ X509TrustManager trustManager = AiaTls.trustManager();
392
+ SSLContext sslContext = SSLContext.getInstance("TLS");
393
+ sslContext.init(null, new X509TrustManager[] { trustManager }, null);
394
+
395
+ OkHttpClient client = new OkHttpClient.Builder()
396
+ .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
397
+ .build();
398
+ ```
399
+
400
+ :::
401
+
402
+ This confines AIA chasing to that one client and leaves the process default
403
+ untouched.
404
+
405
+ > **Lifecycle.** Each call to `AiaTls.socketFactory()` / `AiaTls.trustManager()`
406
+ > builds a fresh component with its **own** fetched-intermediate cache, so build
407
+ > once and reuse it (don't construct one per request). The components are
408
+ > thread-safe — the fetch cache is a synchronized, bounded LRU — so a single
409
+ > instance is safe to install as the process default or share across a client.