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
@@ -12,15 +12,15 @@ private final class BrowserMock: TrackSelectionBrowser {
12
12
 
13
13
  // Record calls for verification
14
14
  var trackLoadEvents: [TrackLoadEvent] = []
15
- var expandedUrls: [String] = []
15
+ var expandedPaths: [String] = []
16
16
 
17
17
  func awaitTrackLoadHandler(event: TrackLoadEvent) async -> Bool {
18
18
  trackLoadEvents.append(event)
19
19
  return trackLoadHandlerResult
20
20
  }
21
21
 
22
- func expandQueueFromContextualUrl(_ url: String) async throws -> (tracks: [Track], selectedIndex: Int)? {
23
- expandedUrls.append(url)
22
+ func expandQueueFromContextualPath(_ path: String) async throws -> (tracks: [Track], selectedIndex: Int)? {
23
+ expandedPaths.append(path)
24
24
  if let error = expandQueueError {
25
25
  throw error
26
26
  }
@@ -53,23 +53,23 @@ private func makeSelector() -> (TrackSelector, BrowserMock, PlayerMock) {
53
53
  @Suite("browsable track")
54
54
  @MainActor
55
55
  struct BrowsableTrackTests {
56
- @Test func urlOnly_returnsBrowse() async {
56
+ @Test func pathOnly_returnsBrowse() async {
57
57
  let (selector, _, player) = makeSelector()
58
- let track = Track(id: "t1", url: "/some/path")
58
+ let track = Track(id: "t1", path: "/some/path")
59
59
  let result = await selector.select(track: track, player: player)
60
- guard case let .browse(url) = result else {
60
+ guard case let .browse(path) = result else {
61
61
  Issue.record("expected .browse, got \(result)")
62
62
  return
63
63
  }
64
- #expect(url == "/some/path")
64
+ #expect(path == "/some/path")
65
65
  }
66
66
  }
67
67
 
68
- // MARK: - No src or url
68
+ // MARK: - No src or path
69
69
 
70
- @Suite("no src or url")
70
+ @Suite("no src or path")
71
71
  @MainActor
72
- struct NoSrcOrUrlTests {
72
+ struct NoSrcOrPathTests {
73
73
  @Test func returnsNone() async {
74
74
  let (selector, _, player) = makeSelector()
75
75
  let track = Track(id: "t1")
@@ -115,25 +115,56 @@ struct PlayableTrackTests {
115
115
  }
116
116
  }
117
117
 
118
- // MARK: - Contextual URL Queue Reuse
118
+ // MARK: - Contextual path Queue Reuse
119
119
 
120
- @Suite("contextual URL queue reuse")
120
+ @Suite("contextual path queue reuse")
121
121
  @MainActor
122
- struct ContextualUrlQueueReuseTests {
122
+ struct ContextualPathQueueReuseTests {
123
123
  @Test func matchingQueueSourcePath_returnsSkipTo() async {
124
124
  let (selector, browser, player) = makeSelector()
125
125
  let parentPath = "/library/radio"
126
126
  let trackSrc = "song.mp3"
127
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
127
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
128
+
129
+ // Id-less queue tracks: identity falls back to src.
130
+ player.queueSourcePath = parentPath
131
+ player.tracks = [
132
+ Track(src: "other.mp3"),
133
+ Track(src: "song.mp3"),
134
+ Track(src: "another.mp3"),
135
+ ]
136
+
137
+ let track = Track(path: contextualPath, src: trackSrc)
138
+ let result = await selector.select(track: track, player: player)
139
+ guard case let .play(intent) = result else {
140
+ Issue.record("expected .play, got \(result)")
141
+ return
142
+ }
143
+ guard case let .skipTo(index) = intent else {
144
+ Issue.record("expected .skipTo, got \(intent)")
145
+ return
146
+ }
147
+ #expect(index == 1)
148
+ #expect(browser.trackLoadEvents.count == 1)
149
+ #expect(browser.expandedPaths.isEmpty)
150
+ }
151
+
152
+ // Skip-in-place matches by id when queue tracks carry ids: the contextual
153
+ // trackId is the row's identity (its id), and the queue row is found even
154
+ // though its src differs textually from the tapped track's.
155
+ @Test func matchingQueueSourcePath_skipsToTrackByIdWhenSrcDiffers() async {
156
+ let (selector, browser, player) = makeSelector()
157
+ let parentPath = "/library/radio"
158
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: "b")
128
159
 
129
160
  player.queueSourcePath = parentPath
130
161
  player.tracks = [
131
162
  Track(id: "a", src: "other.mp3"),
132
- Track(id: "b", src: "song.mp3"),
163
+ Track(id: "b", src: "https://cdn.example/song.mp3?token=queue"),
133
164
  Track(id: "c", src: "another.mp3"),
134
165
  ]
135
166
 
136
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
167
+ let track = Track(id: "b", path: contextualPath, src: "song.mp3")
137
168
  let result = await selector.select(track: track, player: player)
138
169
  guard case let .play(intent) = result else {
139
170
  Issue.record("expected .play, got \(result)")
@@ -144,8 +175,7 @@ struct ContextualUrlQueueReuseTests {
144
175
  return
145
176
  }
146
177
  #expect(index == 1)
147
- #expect(browser.trackLoadEvents.count == 1)
148
- #expect(browser.expandedUrls.isEmpty)
178
+ #expect(browser.expandedPaths.isEmpty)
149
179
  }
150
180
 
151
181
  @Test func handlerIntercepts_returnsIntercepted() async {
@@ -153,32 +183,93 @@ struct ContextualUrlQueueReuseTests {
153
183
  browser.trackLoadHandlerResult = true
154
184
  let parentPath = "/library/radio"
155
185
  let trackSrc = "song.mp3"
156
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
186
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
157
187
 
158
188
  player.queueSourcePath = parentPath
159
189
  player.tracks = [
160
- Track(id: "a", src: "song.mp3"),
190
+ Track(src: "song.mp3"),
161
191
  ]
162
192
 
163
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
193
+ let track = Track(path: contextualPath, src: trackSrc)
164
194
  let result = await selector.select(track: track, player: player)
165
195
  guard case .intercepted = result else {
166
196
  Issue.record("expected .intercepted, got \(result)")
167
197
  return
168
198
  }
199
+ #expect(browser.expandedPaths.isEmpty)
200
+ }
201
+
202
+ // A queue holding the same identity twice (normal for music playlists):
203
+ // exact contextual-path equality — which carries the stamped page index —
204
+ // pins the tapped copy instead of the first one.
205
+ @Test func duplicateIdentityInQueue_pathMatchPinsTheTappedCopy() async {
206
+ let (selector, browser, player) = makeSelector()
207
+ let parentPath = "/playlist"
208
+ func stamped(_ id: String, _ index: Int) -> String {
209
+ BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
210
+ }
211
+
212
+ player.queueSourcePath = parentPath
213
+ player.tracks = [
214
+ Track(path: stamped("a", 0), src: "a"),
215
+ Track(path: stamped("b", 1), src: "b"),
216
+ Track(path: stamped("a", 2), src: "a"),
217
+ ]
218
+
219
+ let track = Track(path: stamped("a", 2), src: "a")
220
+ let result = await selector.select(track: track, player: player)
221
+ guard case let .play(.skipTo(index)) = result else {
222
+ Issue.record("expected .play(.skipTo), got \(result)")
223
+ return
224
+ }
225
+ #expect(index == 2)
226
+ #expect(browser.expandedPaths.isEmpty)
227
+ }
228
+
229
+ // A tap in section B whose identity also sits in the active section-A
230
+ // queue: the exact surface is not in the queue, so the selection
231
+ // re-expands (re-scoping to the tapped section) instead of silently
232
+ // skipping inside the old section's queue.
233
+ @Test func indexStampedPathNotInQueue_reExpandsInsteadOfIdentitySkip() async {
234
+ let (selector, browser, player) = makeSelector()
235
+ let parentPath = "/city"
236
+ func stamped(_ id: String, _ index: Int) -> String {
237
+ BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
238
+ }
239
+
240
+ player.queueSourcePath = parentPath
241
+ player.tracks = [
242
+ Track(path: stamped("dup", 0), src: "dup"),
243
+ Track(path: stamped("x", 1), src: "x"),
244
+ ]
245
+ browser.expandQueueResult = (
246
+ tracks: [Track(path: stamped("y", 4), src: "y"), Track(path: stamped("dup", 5), src: "dup")],
247
+ selectedIndex: 1,
248
+ )
249
+
250
+ let track = Track(path: stamped("dup", 5), src: "dup")
251
+ let result = await selector.select(track: track, player: player)
252
+ guard case let .play(.setQueue(tracks, startIndex, sourcePath)) = result else {
253
+ Issue.record("expected .play(.setQueue), got \(result)")
254
+ return
255
+ }
256
+ #expect(tracks.count == 2)
257
+ #expect(startIndex == 1)
258
+ #expect(sourcePath == parentPath)
259
+ #expect(browser.expandedPaths == [stamped("dup", 5)])
169
260
  }
170
261
  }
171
262
 
172
- // MARK: - Contextual URL Expansion
263
+ // MARK: - Contextual path Expansion
173
264
 
174
- @Suite("contextual URL expansion")
265
+ @Suite("contextual path expansion")
175
266
  @MainActor
176
- struct ContextualUrlExpansionTests {
267
+ struct ContextualPathExpansionTests {
177
268
  @Test func expands_returnsSetQueue() async {
178
269
  let (selector, browser, player) = makeSelector()
179
270
  let parentPath = "/library/radio"
180
271
  let trackSrc = "song.mp3"
181
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
272
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
182
273
 
183
274
  let expandedTracks = [
184
275
  Track(id: "a", src: "first.mp3"),
@@ -187,7 +278,7 @@ struct ContextualUrlExpansionTests {
187
278
  ]
188
279
  browser.expandQueueResult = (tracks: expandedTracks, selectedIndex: 1)
189
280
 
190
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
281
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
191
282
  let result = await selector.select(track: track, player: player)
192
283
  guard case let .play(intent) = result else {
193
284
  Issue.record("expected .play, got \(result)")
@@ -200,7 +291,7 @@ struct ContextualUrlExpansionTests {
200
291
  #expect(tracks.count == 3)
201
292
  #expect(startIndex == 1)
202
293
  #expect(sourcePath == parentPath)
203
- #expect(browser.expandedUrls == [contextualUrl])
294
+ #expect(browser.expandedPaths == [contextualPath])
204
295
  #expect(browser.trackLoadEvents.count == 1)
205
296
  }
206
297
 
@@ -209,14 +300,14 @@ struct ContextualUrlExpansionTests {
209
300
  browser.trackLoadHandlerResult = true
210
301
  let parentPath = "/library/radio"
211
302
  let trackSrc = "song.mp3"
212
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
303
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
213
304
 
214
305
  browser.expandQueueResult = (
215
306
  tracks: [Track(id: "a", src: "song.mp3")],
216
307
  selectedIndex: 0,
217
308
  )
218
309
 
219
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
310
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
220
311
  let result = await selector.select(track: track, player: player)
221
312
  guard case .intercepted = result else {
222
313
  Issue.record("expected .intercepted, got \(result)")
@@ -228,11 +319,11 @@ struct ContextualUrlExpansionTests {
228
319
  let (selector, browser, player) = makeSelector()
229
320
  let parentPath = "/library/radio"
230
321
  let trackSrc = "song.mp3"
231
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
322
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
232
323
 
233
324
  browser.expandQueueResult = nil
234
325
 
235
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
326
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
236
327
  let result = await selector.select(track: track, player: player)
237
328
  guard case let .play(intent) = result else {
238
329
  Issue.record("expected .play, got \(result)")
@@ -249,11 +340,11 @@ struct ContextualUrlExpansionTests {
249
340
  let (selector, browser, player) = makeSelector()
250
341
  let parentPath = "/library/radio"
251
342
  let trackSrc = "song.mp3"
252
- let contextualUrl = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
343
+ let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
253
344
 
254
345
  browser.expandQueueError = MockError.expansion
255
346
 
256
- let track = Track(id: "t1", url: contextualUrl, src: trackSrc)
347
+ let track = Track(id: "t1", path: contextualPath, src: trackSrc)
257
348
  let result = await selector.select(track: track, player: player)
258
349
  guard case let .play(intent) = result else {
259
350
  Issue.record("expected .play, got \(result)")
@@ -266,3 +357,47 @@ struct ContextualUrlExpansionTests {
266
357
  #expect(loaded.src == trackSrc)
267
358
  }
268
359
  }
360
+
361
+ // MARK: - Disabled Track
362
+
363
+ @Suite("disabled track")
364
+ @MainActor
365
+ struct DisabledTrackTests {
366
+ @Test func disabledPlayable_returnsNone() async {
367
+ let (selector, browser, player) = makeSelector()
368
+ let track = Track(id: "t1", src: "https://example.com/a.mp3", disabled: true)
369
+ let result = await selector.select(track: track, player: player)
370
+ guard case .none = result else {
371
+ Issue.record("Expected .none for a disabled track, got \(result)")
372
+ return
373
+ }
374
+ // Refused outright: not even the JS load handler hears about it.
375
+ #expect(browser.trackLoadEvents.isEmpty)
376
+ }
377
+
378
+ @Test func disabledBrowsable_returnsNone() async {
379
+ // Unavailable means unavailable — a disabled browsable doesn't navigate
380
+ // either (the ladder grays or hides it; a delivered tap stays inert).
381
+ let (selector, _, player) = makeSelector()
382
+ let track = Track(id: "t1", path: "/gone", disabled: true)
383
+ let result = await selector.select(track: track, player: player)
384
+ guard case .none = result else {
385
+ Issue.record("Expected .none for a disabled browsable, got \(result)")
386
+ return
387
+ }
388
+ }
389
+
390
+ @Test func disabledContextual_neverExpands() async {
391
+ let (selector, browser, player) = makeSelector()
392
+ browser.expandQueueResult = (tracks: [Track(id: "a", src: "a")], selectedIndex: 0)
393
+ let track = Track(
394
+ id: "t1", path: "/home?__trackId=t1", src: "https://example.com/a.mp3", disabled: true,
395
+ )
396
+ let result = await selector.select(track: track, player: player)
397
+ guard case .none = result else {
398
+ Issue.record("Expected .none, got \(result)")
399
+ return
400
+ }
401
+ #expect(browser.expandedPaths.isEmpty)
402
+ }
403
+ }
@@ -136,7 +136,7 @@ class TrackPlayer {
136
136
  var previousTracks: [Track] { coordinator.previousTracks }
137
137
  var isLastInPlaybackOrder: Bool { coordinator.isLastInPlaybackOrder }
138
138
  var playbackActive: Bool { coordinator.playbackActive }
139
- var sleepTimerManager: SleepTimerManager { coordinator.sleepTimerManager as! SleepTimerManager }
139
+ var sleepTimerManager: any SleepTimerHandling { coordinator.sleepTimerManager }
140
140
  var queue: QueueManager { coordinator.queue }
141
141
  var loadSeekCoordinator: LoadSeekCoordinator { coordinator.loadSeekCoordinator }
142
142
  var playingStateManager: PlayingStateManager { coordinator.playingStateManager }
@@ -195,12 +195,14 @@ class TrackPlayer {
195
195
  return seconds.isNaN ? 0 : seconds
196
196
  }
197
197
 
198
+ /// The current item's duration, or 0 when it has none (a live stream).
199
+ ///
200
+ /// Don't reintroduce `item.asset.duration`: it is deprecated as of the iOS 16
201
+ /// minimum and blocks the main actor fetching a remote asset's duration. Read
202
+ /// on every progress tick.
198
203
  var duration: Double {
199
204
  guard let item = avPlayer.currentItem else { return 0.0 }
200
205
 
201
- if !item.asset.duration.seconds.isNaN {
202
- return item.asset.duration.seconds
203
- }
204
206
  if !item.duration.seconds.isNaN {
205
207
  return item.duration.seconds
206
208
  }
@@ -252,8 +254,22 @@ class TrackPlayer {
252
254
  var rate: Float {
253
255
  get { coordinator.rate }
254
256
  set {
257
+ // A non-positive rate leaves the player unable to play at all — and would
258
+ // poison the next play() through `defaultRate` below. Rejected here, at
259
+ // the one setter every source funnels through (JS, remote command, CarPlay
260
+ // rate cycling, a restored snapshot), rather than at each of them.
261
+ guard newValue > 0 else { return }
255
262
  coordinator.rate = newValue
256
- avPlayer.rate = newValue
263
+ // Assigning a nonzero `AVPlayer.rate` IS `play()` — so setting the speed
264
+ // while paused would start audible playback behind the user's back, with
265
+ // `playWhenReady` still false (the lock screen would read "paused" over
266
+ // sound). On Android and web the speed control is independent of the play
267
+ // intent; this keeps iOS the same.
268
+ avPlayer.defaultRate = newValue
269
+ if playWhenReady { avPlayer.rate = newValue }
270
+ // Same reason as the repeat/shuffle edges below — a speed changed while
271
+ // paused would otherwise not reach the snapshot the cold resume restores.
272
+ persistPlaybackState()
257
273
  }
258
274
  }
259
275
 
@@ -312,17 +328,6 @@ class TrackPlayer {
312
328
  coordinator.load(track, playWhenReady: playWhenReady)
313
329
  }
314
330
 
315
- func togglePlaying() {
316
- switch avPlayer.timeControlStatus {
317
- case .playing, .waitingToPlayAtSpecifiedRate:
318
- pause()
319
- case .paused:
320
- play()
321
- @unknown default:
322
- fatalError("Unknown AVPlayer.timeControlStatus")
323
- }
324
- }
325
-
326
331
  func play() { coordinator.play() }
327
332
  func pause() { coordinator.pause() }
328
333
  func togglePlayback() { coordinator.togglePlayback() }
@@ -456,13 +461,22 @@ class TrackPlayer {
456
461
  )
457
462
  }
458
463
 
464
+ /// Empties the queue and the published now-playing info. Unlinking the
465
+ /// session belongs in `destroy()`: `avPlayer` survives a clear, and only
466
+ /// `setupAVPlayer()` re-links.
459
467
  func clear() {
460
468
  coordinator.clear()
461
- nowPlayingInfoController.unlinkPlayer()
462
469
  }
463
470
 
464
471
  func destroy() {
465
472
  clear()
473
+ // The player is going away for good — now the session must go with it.
474
+ nowPlayingInfoController.unlinkPlayer()
475
+ // An AVPlayer retains its time observers: one left registered is still attached when the
476
+ // player is released, and outlives this object.
477
+ playerTimeObserver.unregisterForBoundaryTimeEvents()
478
+ playerObserver.stopObserving()
479
+ stopObservingAVPlayerItem()
466
480
  remoteCommandController.disableAll()
467
481
  }
468
482
 
@@ -545,10 +559,12 @@ class TrackPlayer {
545
559
 
546
560
  nowPlayingInfoController.linkPlayer(avPlayer)
547
561
 
562
+ // Stage the rate unconditionally, so a freshly recreated player carries it
563
+ // before anything plays — `rate`'s setter holds the same invariant, and on
564
+ // the playWhenReady branch below `defaultRate` was previously left at 1.0.
565
+ avPlayer.defaultRate = rate
548
566
  if playWhenReady {
549
567
  startPlayback()
550
- } else {
551
- avPlayer.defaultRate = rate
552
568
  }
553
569
  }
554
570
 
@@ -750,13 +766,28 @@ extension TrackPlayer {
750
766
  PersistedPlaybackState(
751
767
  track: JsonTrack(from: track),
752
768
  positionMs: positionMs,
753
- repeatMode: repeatMode.persistedString,
769
+ repeatMode: repeatMode.stringValue,
754
770
  shuffleEnabled: shuffleEnabled,
755
771
  playbackSpeed: rate,
756
- )
772
+ ),
757
773
  )
758
774
  }
759
775
 
776
+ /// Applies the player settings a snapshot carries — the inverse of
777
+ /// `persistPlaybackState()`, and the twin of the Kotlin store's `restore()`.
778
+ ///
779
+ /// Call BEFORE building the queue: `setQueue` rebuilds the shuffle order
780
+ /// pinned to the starting track, so shuffle has to be set for that order to
781
+ /// be the one playback uses (and the mode events then precede the
782
+ /// queue-changed event, as on Android). An unrecognized repeat mode restores
783
+ /// as `.off` rather than failing the whole resume over one field; a
784
+ /// non-positive speed is rejected by `rate`'s own setter.
785
+ func restorePlaybackSettings(from state: PersistedPlaybackState) {
786
+ repeatMode = RepeatMode(fromString: state.repeatMode) ?? .off
787
+ shuffleEnabled = state.shuffleEnabled
788
+ rate = state.playbackSpeed
789
+ }
790
+
760
791
  /// Start a 5 s repeating save while playback is active. A previous task is
761
792
  /// cancelled first so there is never more than one running at a time.
762
793
  private func startPeriodicSave() {
@@ -776,17 +807,6 @@ extension TrackPlayer {
776
807
  }
777
808
  }
778
809
 
779
- private extension RepeatMode {
780
- /// String representation stored in `PersistedPlaybackState.repeatMode`.
781
- var persistedString: String {
782
- switch self {
783
- case .off: "off"
784
- case .track: "track"
785
- case .queue: "queue"
786
- }
787
- }
788
- }
789
-
790
810
  // MARK: - PlaybackEffectHandler
791
811
 
792
812
  extension TrackPlayer: PlaybackEffectHandler {
@@ -852,15 +872,23 @@ extension TrackPlayer: PlaybackEffectHandler {
852
872
  }
853
873
 
854
874
  func clearNowPlaying() {
855
- nowPlayingInfoController.clear()
875
+ nowPlayingUpdater.clearNowPlaying()
856
876
  }
857
877
 
878
+ // Both persist: a snapshot is otherwise only written on track load, on pause,
879
+ // and every 5s while playing, so a mode toggled while paused was lost — the
880
+ // cold-start resume would restore the value from before the toggle. Android
881
+ // persists on every setting change (`PlayerListener.onRepeatModeChanged` /
882
+ // `onShuffleModeEnabledChanged`); these are the same two edges, and the
883
+ // coordinator only calls them on a real change.
858
884
  func updateRemoteRepeatMode(_ mode: RepeatMode) {
859
885
  remoteCommandController.updateRepeatMode(mode)
886
+ persistPlaybackState()
860
887
  }
861
888
 
862
889
  func updateRemoteShuffleMode(_ enabled: Bool) {
863
890
  remoteCommandController.updateShuffleMode(enabled)
891
+ persistPlaybackState()
864
892
  }
865
893
 
866
894
  func updateSkipAvailability(canNext: Bool, canPrevious: Bool) {
@@ -879,8 +907,8 @@ extension TrackPlayer: TrackSelectionPlayer {}
879
907
  // MARK: - Queue Methods (thin forwarders)
880
908
 
881
909
  extension TrackPlayer {
882
- func replace(_ index: Int, _ track: Track) {
883
- coordinator.replace(index, track)
910
+ func replace(_ index: Int, _ track: Track) throws {
911
+ try coordinator.replace(index, track)
884
912
  }
885
913
 
886
914
  func setQueue(
@@ -9,10 +9,11 @@ import NitroModules
9
9
  /// Note: Method names are prefixed to avoid conflicts with JS callback property names.
10
10
  /// All callbacks are called from the main actor since TrackPlayer is @MainActor.
11
11
  ///
12
- /// Extends PlaybackCoordinatorCallbacks for the state machine / coordinator callbacks,
13
- /// then adds metadata, seek, remote, and configuration callbacks that are TrackPlayer-specific.
12
+ /// Extends PlaybackCoordinatorCallbacks for the state machine / coordinator callbacks
13
+ /// and RemoteCommandCallbacks for the remote-command events, then adds metadata,
14
+ /// seek, and configuration callbacks that are TrackPlayer-specific.
14
15
  @MainActor
15
- protocol TrackPlayerCallbacks: PlaybackCoordinatorCallbacks {
16
+ protocol TrackPlayerCallbacks: PlaybackCoordinatorCallbacks, RemoteCommandCallbacks {
16
17
  // MARK: - Metadata Events
17
18
 
18
19
  /// Called when common metadata is received.
@@ -34,32 +35,7 @@ protocol TrackPlayerCallbacks: PlaybackCoordinatorCallbacks {
34
35
 
35
36
  // MARK: - Remote Control Events
36
37
 
37
- /// Called when play is triggered remotely.
38
- func remotePlay()
39
-
40
- /// Called when pause is triggered remotely.
41
- func remotePause()
42
-
43
- /// Called when stop is triggered remotely.
44
- func remoteStop()
45
-
46
- /// Called when toggle play/pause is triggered remotely.
47
- func remotePlayPause()
48
-
49
- /// Called when next is triggered remotely.
50
- func remoteNext()
51
-
52
- /// Called when previous is triggered remotely.
53
- func remotePrevious()
54
-
55
- /// Called when jump forward is triggered remotely.
56
- func remoteJumpForward(interval: Double)
57
-
58
- /// Called when jump backward is triggered remotely.
59
- func remoteJumpBackward(interval: Double)
60
-
61
- /// Called when seek is triggered remotely.
62
- func remoteSeek(position: Double)
38
+ // The command-center events live in RemoteCommandCallbacks, inherited above.
63
39
 
64
40
  /// Called when change playback position is triggered remotely.
65
41
  func remoteChangePlaybackPosition(position: Double)
@@ -70,15 +46,6 @@ protocol TrackPlayerCallbacks: PlaybackCoordinatorCallbacks {
70
46
  /// Called when play from search is triggered remotely.
71
47
  func remotePlaySearch(query: String)
72
48
 
73
- /// Called when repeat mode change is triggered remotely (CarPlay/lock screen).
74
- func remoteChangeRepeatMode(mode: RepeatMode)
75
-
76
- /// Called when shuffle mode change is triggered remotely (CarPlay/lock screen).
77
- func remoteChangeShuffleMode(enabled: Bool)
78
-
79
- /// Called when playback rate change is triggered remotely (CarPlay/lock screen).
80
- func remoteChangePlaybackRate(rate: Float)
81
-
82
49
  // MARK: - Configuration Events
83
50
 
84
51
  /// Called when options are changed.