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

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
@@ -42,21 +42,32 @@ import type {
42
42
  AudioBrowser as AudioBrowserSpec,
43
43
  Output
44
44
  } from '../specs/audio-browser.nitro'
45
- import type { ResolvedTrack, Track, TrackLoadEvent } from '../types'
45
+ import type {
46
+ ResolvedTrack,
47
+ Track,
48
+ TrackLoadEvent,
49
+ TransformableRequestConfig
50
+ } from '../types'
46
51
  import type { NativeBrowserConfiguration } from '../types/browser-native'
52
+ import { getTrackIdentity } from '../utils/getTrackIdentity'
47
53
  import { BrowserManager } from './browser/BrowserManager'
48
54
  import { classifyTrackNavigation } from './browser/classifyTrackNavigation'
49
55
  import { FavoriteManager } from './browser/FavoriteManager'
50
56
  import { NavigationErrorManager } from './browser/NavigationErrorManager'
51
57
  import { SearchManager } from './browser/SearchManager'
58
+ import { followMediaRedirect } from './http/followMediaRedirect'
52
59
  import { HttpClient } from './http/HttpClient'
53
- import { RequestConfigBuilder } from './http/RequestConfigBuilder'
60
+ import {
61
+ RequestConfigBuilder,
62
+ type ResolvedMediaRequest
63
+ } from './http/RequestConfigBuilder'
54
64
  import { NowPlayingManager } from './player/NowPlayingManager'
55
65
  import { OptionsManager } from './player/OptionsManager'
56
66
  import { RemoteCommandController } from './player/RemoteCommandController'
57
67
  import { QueuePlayer, SleepTimerManager, VolumeFader } from './TrackPlayer'
58
68
  import { PlaybackTimer } from './TrackPlayer/PlaybackTimer'
59
69
  import { derivePlayingState } from './TrackPlayer/PlayingStateFactory'
70
+ import { BrowserPathHelper } from './util/BrowserPathHelper'
60
71
 
61
72
  /**
62
73
  * Web implementation of AudioBrowser (unified browser + player)
@@ -94,7 +105,6 @@ export class NativeAudioBrowser
94
105
  private remoteCommands: RemoteCommandController
95
106
 
96
107
  // Player state
97
- private currentLoadId = 0
98
108
  private progressTimer = new PlaybackTimer()
99
109
  private intervalTimer = new PlaybackTimer()
100
110
  private _online: boolean =
@@ -304,6 +314,11 @@ export class NativeAudioBrowser
304
314
 
305
315
  if (newState.state === 'error' && newState.error) {
306
316
  this.onPlaybackError({ error: newState.error })
317
+ } else if (oldState.state === 'error') {
318
+ // Leaving the error state clears it, as it does natively. The getter
319
+ // already reads undefined — consumers subscribe to the event, so without
320
+ // this a stale error outlives the next successful load.
321
+ this.onPlaybackError({ error: undefined })
307
322
  }
308
323
  }
309
324
 
@@ -414,11 +429,13 @@ export class NativeAudioBrowser
414
429
  * Attempts to skip to a track already in the current queue.
415
430
  * Used as an optimization to avoid re-expanding the queue.
416
431
  *
417
- * @param trackId The track's src identifier
432
+ * @param track The tapped track (its contextual path pins the exact copy)
433
+ * @param trackId The track's identity (id, falling back to src)
418
434
  * @param parentPath The parent path to check against queueSourcePath
419
435
  * @returns true if successfully skipped to existing track, false otherwise
420
436
  */
421
437
  private async trySkipToExistingQueueTrack(
438
+ track: Track,
422
439
  trackId: string,
423
440
  parentPath: string
424
441
  ): Promise<boolean> {
@@ -426,8 +443,21 @@ export class NativeAudioBrowser
426
443
  return false
427
444
  }
428
445
 
446
+ // Exact-surface match first: a contextual path carries the tapped page
447
+ // position (__index), so path equality pins the exact copy when the page
448
+ // holds the same identity more than once. The identity match remains for
449
+ // index-less paths (e.g. pre-stamp persisted state); an index-stamped path
450
+ // with no exact match falls through to expansion, which re-scopes the
451
+ // queue to the tapped section.
429
452
  const queue = this.getQueue()
430
- const index = queue.findIndex((t) => t.src === trackId)
453
+ let index = queue.findIndex((t) => t.path != null && t.path === track.path)
454
+ if (
455
+ index < 0 &&
456
+ track.path != null &&
457
+ BrowserPathHelper.extractIndex(track.path) === undefined
458
+ ) {
459
+ index = queue.findIndex((t) => getTrackIdentity(t) === trackId)
460
+ }
431
461
 
432
462
  if (index < 0) {
433
463
  return false
@@ -474,6 +504,11 @@ export class NativeAudioBrowser
474
504
  */
475
505
  private async navigateTrackAsync(track: Track): Promise<void> {
476
506
  try {
507
+ // A disabled track is unavailable — it never plays, whichever surface
508
+ // or stale resume path delivered the selection (Track.disabled; mirrors
509
+ // iOS TrackSelector.select and Android navigateTrack).
510
+ if (track.disabled === true) return
511
+
477
512
  const nav = classifyTrackNavigation(track)
478
513
  switch (nav.kind) {
479
514
  case 'contextual':
@@ -481,6 +516,7 @@ export class NativeAudioBrowser
481
516
  if (
482
517
  nav.trackId &&
483
518
  (await this.trySkipToExistingQueueTrack(
519
+ track,
484
520
  nav.trackId,
485
521
  nav.parentPath
486
522
  ))
@@ -504,7 +540,7 @@ export class NativeAudioBrowser
504
540
  return
505
541
 
506
542
  case 'invalid':
507
- throw new Error("Track must have either 'url' or 'src' property")
543
+ throw new Error("Track must have either 'path' or 'src' property")
508
544
  }
509
545
  } catch (error) {
510
546
  const message = error instanceof Error ? error.message : 'Unknown error'
@@ -520,7 +556,7 @@ export class NativeAudioBrowser
520
556
  })
521
557
  }
522
558
 
523
- async onSearch(query: string): Promise<Track[]> {
559
+ async search(query: string): Promise<Track[]> {
524
560
  // Wrap query string in SearchParams (matches Android's search(query: String) overload)
525
561
  return this.searchManager.search({ query, reference: 'unknown' })
526
562
  }
@@ -587,71 +623,106 @@ export class NativeAudioBrowser
587
623
  * Supports the transform callback for URL manipulation.
588
624
  * Mirrors Android's MediaFactory.getMediaRequestConfig behavior.
589
625
  */
590
- private async resolveMediaUrl(src: string): Promise<string> {
591
- const { request, media } = this.browserManager.configuration
592
- return RequestConfigBuilder.resolveMediaUrl(src, request, media)
626
+ private async resolveMediaRequest(
627
+ src: string,
628
+ track: Track
629
+ ): Promise<ResolvedMediaRequest> {
630
+ // The *resolved* request layer, not `configuration.request`: a
631
+ // resolver-only config would otherwise reach media with no baseUrl and no
632
+ // headers. Browse, search and browse artwork all read it this way too.
633
+ //
634
+ // Caught, because unlike those three this one cannot fail the load: a
635
+ // resolver hiccup — a token refresh timing out — would otherwise take down
636
+ // a track whose `src` is already absolute and public and needed no layer at
637
+ // all. Native degrades the same way, `getMediaRequestConfig` returning null
638
+ // and the original URL playing on.
639
+ let request: TransformableRequestConfig | undefined
640
+ try {
641
+ request = await this.browserManager.resolvedRequestConfig()
642
+ } catch (e) {
643
+ console.error('Failed to resolve the request layer for media', e)
644
+ }
645
+ const { media } = this.browserManager.configuration
646
+ return RequestConfigBuilder.resolveMediaRequest(src, request, media, track)
593
647
  }
594
648
 
595
649
  /**
596
- * Reload from the queue's own entry, whose `src` is still unresolved —
597
- * `current` holds the resolved track, and re-feeding it into `load()` would
598
- * re-resolve the resolved URL (double-applying a `transform`) and replace
599
- * the queue entry with it.
650
+ * The queue's own entry, so the queue stays the source of truth for what is
651
+ * playing. `current` preserves the track as queued — the resolved URL is
652
+ * passed to `load()` separately — so either is safe to re-feed.
600
653
  */
601
- protected override reloadCurrent(): void {
654
+ protected override trackToReload(): Track | undefined {
602
655
  const index = this.queue.currentIndex
603
- const sourceTrack =
604
- index !== undefined ? this.queue.getTrack(index) : undefined
605
- if (sourceTrack) {
606
- this.load(sourceTrack)
607
- } else {
608
- super.reloadCurrent()
609
- }
656
+ const queued = index !== undefined ? this.queue.getTrack(index) : undefined
657
+ // The queue entry wins, so it stays the source of truth for what is
658
+ // playing; `current` is the fallback for a player loaded without one.
659
+ return queued ?? super.trackToReload()
610
660
  }
611
661
 
612
- load(track: Track, callback?: (track: Track) => void): void {
613
- const element = this.requireElement()
614
-
615
- // Clear now playing override when track changes (matches Android's PlayerListener.onMediaItemTransition)
616
- this.nowPlayingManager.clearNowPlayingOverride()
617
-
618
- // Match Android: load() modifies the queue.
619
- // If queue is empty, add the track. If queue has items, replace at currentIndex.
662
+ /**
663
+ * Makes `track` the queue's current entry, emitting the change.
664
+ *
665
+ * Matches Android, where `load()` modifies the queue: an empty queue takes
666
+ * the track as its only entry, a populated one has its current entry
667
+ * replaced.
668
+ */
669
+ private syncQueueTo(track: Track): void {
620
670
  if (this.queue.length === 0) {
621
671
  this.queue.setTracks([track])
622
672
  this.queue.currentIndex = 0
623
673
  this.emitQueueChanged()
624
- } else if (
674
+ return
675
+ }
676
+ if (
625
677
  this.queue.currentIndex !== undefined &&
626
678
  this.queue.getTrack(this.queue.currentIndex) !== track
627
679
  ) {
628
680
  this.queue.replaceTrack(this.queue.currentIndex, track)
629
681
  this.emitQueueChanged()
630
682
  }
683
+ }
631
684
 
685
+ /**
686
+ * Everything native does in its media-item transition, all of it before any
687
+ * network work: announce the new track, publish its now-playing metadata,
688
+ * cut the outgoing one, and report `loading`.
689
+ *
690
+ * Android's `onMediaItemTransition` fires at `prepare()` and does exactly
691
+ * this, with its LOADING following from `onEvents`; resolution happens later
692
+ * still, inside `TransformingDataSource.open()`. Doing it here rather than
693
+ * after the URL resolves is what gives the resolution window a state, and
694
+ * keeps `activeTrack` ahead of `loading` — the order a consumer that reads
695
+ * `getActiveTrack()` on the loading edge depends on.
696
+ */
697
+ private transitionTo(track: Track): void {
698
+ const element = this.requireElement()
632
699
  const lastTrack = this.current
633
700
  const lastPosition = element.currentTime
634
701
  const lastIndex = this.queue.lastIndex
635
702
  const currentIndex = this.queue.currentIndex
636
703
 
637
- // Set loading flag early so seekTo() calls during async URL resolution
638
- // are captured as pending seeks rather than silently dropped
639
- this._loadInProgress = true
640
- this._pendingSeek = undefined
641
-
642
- // Resolve the media URL before loading (async but we don't await)
643
- const loadId = ++this.currentLoadId
644
- const doLoad = async () => {
645
- const resolvedTrack: Track = track.src
646
- ? { ...track, src: await this.resolveMediaUrl(track.src) }
647
- : track
648
-
649
- // A newer load() was called while resolving — discard this stale result
650
- if (loadId !== this.currentLoadId) {
651
- return
652
- }
653
-
654
- super.load(resolvedTrack, (loadedTrack) => {
704
+ // A re-prepare of the item already current is not a transition, and must
705
+ // not be announced as one: ExoPlayer's `prepare()` does not re-fire
706
+ // `onMediaItemTransition` for an unchanged media item, so a consumer that
707
+ // resets on a track change would have its UI wiped by every retry. The
708
+ // `lastTrack === track` payload was the tell.
709
+ const isTransition = this.current !== track
710
+
711
+ // `current` first, so `getActiveTrack()` already reads the new track inside
712
+ // a handler and so `getNowPlaying()` publishes the new track's metadata. It
713
+ // also stops `getActiveTrackIndex()` — which moved with the queue already,
714
+ // synchronously — from disagreeing with it for the length of the
715
+ // resolution.
716
+ this.current = track
717
+
718
+ // Separately guarded: a throwing consumer handler must not skip the
719
+ // publish, and neither may fail the load.
720
+ if (isTransition) {
721
+ // As Android's onMediaItemTransition does: a new item is a clean slate,
722
+ // a retry of the same one keeps what the consumer set.
723
+ this.nowPlayingManager.clearNowPlayingOverride()
724
+
725
+ try {
655
726
  this.onPlaybackActiveTrackChanged({
656
727
  lastTrack,
657
728
  lastPosition,
@@ -659,24 +730,118 @@ export class NativeAudioBrowser
659
730
  index: currentIndex,
660
731
  track
661
732
  })
733
+ } catch (error) {
734
+ console.error('Failed to announce the active track change:', error)
735
+ }
662
736
 
663
- // Update now playing metadata
737
+ try {
738
+ // Once, here: duration reaches the media session through
739
+ // `updateProgress()` on each tick, so only metadata needs publishing.
740
+ // Android publishes from the same transition handler that announces.
664
741
  const nowPlaying = this.getNowPlaying()
665
742
  if (nowPlaying) {
666
743
  this.publishNowPlaying(nowPlaying)
667
744
  }
745
+ } catch (error) {
746
+ console.error('Failed to publish now-playing metadata:', error)
747
+ }
748
+ }
749
+
750
+ // Stop the outgoing track now rather than when Shaka eventually gets a URL.
751
+ // Announcing the new track while the old one is still audible is worse than
752
+ // the silence, and native has no such window — prepare() cuts the old item
753
+ // at the moment it announces. This adds no teardown: `player.load()`
754
+ // already unloads first (shaka player.js:1757-1762). It only moves when.
755
+ // Verified in a real browser to emit no `pause`, so no state comes of it.
756
+ this.requirePlayer()
757
+ .unload()
758
+ .catch((error: unknown) =>
759
+ console.error('Failed to unload the previous track:', error)
760
+ )
761
+
762
+ // Loading covers the resolution too. Shaka dispatches its own `loading`
763
+ // once `player.load()` is called — after resolution — and the state setter
764
+ // drops that as a no-op. The `buffering` that follows is Shaka's to report.
765
+ this.dispatch({ type: 'trackLoading' })
766
+ }
767
+
768
+ load(track: Track, callback?: (track: Track) => void): void {
769
+ // Before the queue is touched, so a load before `setupPlayer()` throws
770
+ // rather than half-applying.
771
+ this.requireElement()
772
+
773
+ this.syncQueueTo(track)
774
+
775
+ // Begun here, at the call, so a newer load supersedes this one immediately
776
+ // rather than once its own URL has resolved. Both continuations below carry
777
+ // it, as does `super.load()`.
778
+ const attempt = this.beginLoadAttempt()
779
+ // Read now: `reloadCurrent` holds the flag only for this synchronous call,
780
+ // and `super.load()` runs after the URL has resolved.
781
+ const reprepare = this._repreparing
782
+
783
+ // Loading ends being stopped. `dispatch()` drops every event while
784
+ // `_isStopped`, to keep Shaka's teardown noise off the stopped state, so
785
+ // the `trackLoading` in `transitionTo` would be swallowed on the stop → play
786
+ // path and the resolution window would report nothing. `super.load()` clears
787
+ // this too, but only after the URL resolves.
788
+ this._isStopped = false
668
789
 
669
- // Call the provided callback if any
670
- if (callback) {
671
- callback(loadedTrack)
790
+ // Set loading flag early so seekTo() calls during async URL resolution
791
+ // are captured as pending seeks rather than silently dropped
792
+ this._loadInProgress = true
793
+ this._pendingSeek = undefined
794
+
795
+ this.transitionTo(track)
796
+
797
+ // Resolve the media URL before loading (async but we don't await)
798
+ const doLoad = async () => {
799
+ const resolvedRequest = track.src
800
+ ? await this.resolveMediaRequest(track.src, track)
801
+ : undefined
802
+ // Web-only, and forced: a progressive file plays via `mediaElement.src`,
803
+ // which cannot carry headers. Native sends them with the media request
804
+ // and follows the redirect inline, so it needs none of this.
805
+ const resolvedMedia = resolvedRequest
806
+ ? await followMediaRedirect(
807
+ resolvedRequest.path ?? track.src!,
808
+ resolvedRequest.headers,
809
+ { mediaLayerHeaders: resolvedRequest.mediaLayerHeaders }
810
+ )
811
+ : undefined
812
+
813
+ // Something took over while the URL was resolving — discard this result
814
+ if (!attempt.isCurrent) return
815
+
816
+ super.load(
817
+ track,
818
+ (loadedTrack) => {
819
+ // Call the provided callback if any
820
+ if (callback) {
821
+ callback(loadedTrack)
822
+ }
823
+ },
824
+ {
825
+ headers: resolvedMedia?.headers,
826
+ src: resolvedMedia?.src,
827
+ attempt,
828
+ reprepare
672
829
  }
673
- })
830
+ )
674
831
  }
675
832
 
676
833
  // Execute async load without blocking, with error handling
677
834
  doLoad().catch((error: unknown) => {
678
- // Only the active load may surface a resolution failure.
679
- if (loadId !== this.currentLoadId) return
835
+ // A backstop with no reachable trigger today, kept because an unhandled
836
+ // rejection would be worse than a dead branch. Every await inside
837
+ // `doLoad` catches internally; `shaka.Player.load` is `async`, so it
838
+ // rejects rather than throwing and its rejection is handled against the
839
+ // attempt inside `Player.load`; and `this.player` is only ever assigned,
840
+ // so `requirePlayer()` cannot start succeeding and then fail mid-load.
841
+ //
842
+ // No attempt check either, for the same reason it would be dead: nothing
843
+ // awaits between the guard above and `super.load()`, so the attempt
844
+ // cannot have changed. Add an await there and this needs the check back.
680
845
  this._loadInProgress = false
681
846
  this._pendingSeek = undefined
682
847
  console.error('Error loading track:', error)
@@ -856,10 +1021,8 @@ export class NativeAudioBrowser
856
1021
  }
857
1022
 
858
1023
  override stop(): void {
859
- // Invalidate any in-flight load: its post-await continuation would call
860
- // super.load(), whose first line re-arms the player (_isStopped = false)
861
- // and revives the state out of Stopped.
862
- this.currentLoadId++
1024
+ // `Player.stop()` cancels the load attempt, so an in-flight load's
1025
+ // continuation cannot re-arm the player and revive it out of Stopped.
863
1026
  super.stop()
864
1027
  this.clearSleepTimerIfFading()
865
1028
  }
@@ -880,18 +1043,20 @@ export class NativeAudioBrowser
880
1043
  /**
881
1044
  * Override to check for sleep timer when track ends
882
1045
  */
883
- protected onTrackEnded(): void {
1046
+ protected onTrackEnded(): boolean {
884
1047
  // Check if sleep timer is set to end on track completion
885
1048
  if (this.sleepTimer.sleepWhenPlayedToEnd) {
886
1049
  console.log('Sleep timer triggered on track end, pausing playback')
887
1050
  this.sleepTimer.clear()
888
1051
  this.pause()
889
1052
  this.onSleepTimerChanged(null)
890
- return
1053
+ // Nothing is loading, so the caller must still report the end — the
1054
+ // element has stopped and its own `pause` is swallowed as end-of-track.
1055
+ return false
891
1056
  }
892
1057
 
893
1058
  // Otherwise proceed with normal track end behavior
894
- super.onTrackEnded()
1059
+ return super.onTrackEnded()
895
1060
  }
896
1061
 
897
1062
  // MARK: Queue management
@@ -941,13 +1106,14 @@ export class NativeAudioBrowser
941
1106
  setActiveTrackFavorited(favorited: boolean): void {
942
1107
  const track = this.getActiveTrack()
943
1108
  const index = this.getActiveTrackIndex()
944
- if (!track || !track.src || index === undefined) return
1109
+ if (!track || getTrackIdentity(track) === undefined || index === undefined)
1110
+ return
945
1111
 
946
- // Update favorites set via manager
1112
+ // Update favorites set via manager (keyed by track identity)
947
1113
  if (favorited) {
948
- this.favoriteManager.addFavorite(track.src)
1114
+ this.favoriteManager.addFavorite(track)
949
1115
  } else {
950
- this.favoriteManager.removeFavorite(track.src)
1116
+ this.favoriteManager.removeFavorite(track)
951
1117
  }
952
1118
 
953
1119
  // Create updated track with new favorited state
@@ -959,27 +1125,18 @@ export class NativeAudioBrowser
959
1125
  // Replace the track in the queue
960
1126
  this.queue.replaceTrack(index, updatedTrack)
961
1127
 
962
- // Emit favorite changed event
1128
+ // Emit favorite changed only. A favorite toggle is an in-place mutation of
1129
+ // the active track, not a transition — onPlaybackActiveTrackChanged and
1130
+ // onPlaybackQueueChanged stay transition-only, and the useActiveTrack /
1131
+ // useQueue hooks subscribe to onFavoriteChanged themselves.
963
1132
  this.onFavoriteChanged({ track: updatedTrack, favorited })
964
-
965
- // Emit active track changed so useActiveTrack() hook updates
966
- this.onPlaybackActiveTrackChanged({
967
- lastIndex: index,
968
- lastTrack: track,
969
- lastPosition: this.element?.currentTime ?? 0,
970
- index,
971
- track: updatedTrack
972
- })
973
-
974
- // Emit queue changed so useQueue() hook updates
975
- this.emitQueueChanged()
976
1133
  }
977
1134
 
978
1135
  toggleActiveTrackFavorited(): void {
979
1136
  const track = this.getActiveTrack()
980
- if (!track || !track.src) return
1137
+ if (!track || getTrackIdentity(track) === undefined) return
981
1138
 
982
- const isFavorited = this.favoriteManager.isFavorited(track.src)
1139
+ const isFavorited = this.favoriteManager.isFavorited(track)
983
1140
  this.setActiveTrackFavorited(!isFavorited)
984
1141
  }
985
1142
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * One attempt at loading a track, from the call that asked for it through to
3
+ * the continuations that finish it.
4
+ *
5
+ * A load on web spans two async boundaries — resolving the URL, then Shaka's
6
+ * own `load()` — and each has a continuation that must do nothing if something
7
+ * else has taken over in the meantime. Handing every continuation the same
8
+ * token turns that into one question, asked the same way in each place.
9
+ *
10
+ * Supersession is registered where it happens: {@link Player.beginLoadAttempt}
11
+ * cancels the previous attempt the moment a new one starts, and `stop()`
12
+ * cancels without starting one. Two integer generations used to stand in for
13
+ * this, incremented at different depths — the outer one at the call, the inner
14
+ * one only once the URL had resolved — so a load superseded during that gap
15
+ * still ran its own success path.
16
+ */
17
+ export class LoadAttempt {
18
+ private cancelled = false
19
+
20
+ /** Abandons this attempt; every continuation holding it will bail. */
21
+ cancel(): void {
22
+ this.cancelled = true
23
+ }
24
+
25
+ /** Whether this attempt still owns the player. */
26
+ get isCurrent(): boolean {
27
+ return !this.cancelled
28
+ }
29
+ }
@@ -47,17 +47,49 @@ describe('nextPlaybackState', () => {
47
47
  })
48
48
  })
49
49
 
50
- describe('loadSeekCompleted — only transitions from loading', () => {
50
+ describe('loadSeekCompleted — the settle after a load', () => {
51
51
  it('from loading → ready', () => {
52
- expect(nextPlaybackState('loading', { type: 'loadSeekCompleted' })).toBe(
53
- 'ready'
54
- )
52
+ expect(
53
+ nextPlaybackState('loading', {
54
+ type: 'loadSeekCompleted',
55
+ playWhenReady: false
56
+ })
57
+ ).toBe('ready')
58
+ })
59
+
60
+ it('from buffering → ready', () => {
61
+ // every load now passes through buffering, so that is where the settle
62
+ // usually arrives
63
+ expect(
64
+ nextPlaybackState('buffering', {
65
+ type: 'loadSeekCompleted',
66
+ playWhenReady: false
67
+ })
68
+ ).toBe('ready')
55
69
  })
56
70
 
57
71
  it('from any other state → suppressed', () => {
58
- for (const state of ALL_STATES.filter((s) => s !== 'loading')) {
72
+ for (const state of ALL_STATES.filter(
73
+ (s) => s !== 'loading' && s !== 'buffering'
74
+ )) {
59
75
  expect(
60
- nextPlaybackState(state, { type: 'loadSeekCompleted' })
76
+ nextPlaybackState(state, {
77
+ type: 'loadSeekCompleted',
78
+ playWhenReady: false
79
+ })
80
+ ).toBeNull()
81
+ }
82
+ })
83
+
84
+ it('suppressed while playWhenReady — ready is a transient before playing', () => {
85
+ // Android suppresses STATE_READY the same way; emitting it flashes a
86
+ // settled, non-loading state mid-startup
87
+ for (const state of ['loading', 'buffering'] as const) {
88
+ expect(
89
+ nextPlaybackState(state, {
90
+ type: 'loadSeekCompleted',
91
+ playWhenReady: true
92
+ })
61
93
  ).toBeNull()
62
94
  }
63
95
  })
@@ -103,7 +135,10 @@ describe('nextPlaybackState', () => {
103
135
 
104
136
  it('from playing → suppressed', () => {
105
137
  expect(
106
- nextPlaybackState('playing', { type: 'bufferingSufficient' })
138
+ nextPlaybackState('playing', {
139
+ type: 'bufferingSufficient',
140
+ playWhenReady: false
141
+ })
107
142
  ).toBeNull()
108
143
  })
109
144
 
@@ -112,27 +147,39 @@ describe('nextPlaybackState', () => {
112
147
  // the UI is rendering with nothing recovered. Same guard as iOS/Android.
113
148
  it('from error → suppressed (preserves the error)', () => {
114
149
  expect(
115
- nextPlaybackState('error', { type: 'bufferingSufficient' })
150
+ nextPlaybackState('error', {
151
+ type: 'bufferingSufficient',
152
+ playWhenReady: false
153
+ })
116
154
  ).toBeNull()
117
155
  })
118
156
 
119
157
  it('from ended → suppressed', () => {
120
158
  expect(
121
- nextPlaybackState('ended', { type: 'bufferingSufficient' })
159
+ nextPlaybackState('ended', {
160
+ type: 'bufferingSufficient',
161
+ playWhenReady: false
162
+ })
122
163
  ).toBeNull()
123
164
  })
124
165
 
125
166
  it('from stopped → suppressed', () => {
126
167
  expect(
127
- nextPlaybackState('stopped', { type: 'bufferingSufficient' })
168
+ nextPlaybackState('stopped', {
169
+ type: 'bufferingSufficient',
170
+ playWhenReady: false
171
+ })
128
172
  ).toBeNull()
129
173
  })
130
174
 
131
175
  it('from any other state → ready', () => {
132
176
  for (const state of ALL_STATES.filter((s) => !suppressed.includes(s))) {
133
- expect(nextPlaybackState(state, { type: 'bufferingSufficient' })).toBe(
134
- 'ready'
135
- )
177
+ expect(
178
+ nextPlaybackState(state, {
179
+ type: 'bufferingSufficient',
180
+ playWhenReady: false
181
+ })
182
+ ).toBe('ready')
136
183
  }
137
184
  })
138
185
  })