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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (660) hide show
  1. package/README.md +35 -21
  2. package/android/build.gradle +35 -12
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
  11. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  12. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
  13. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
  14. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  15. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
  16. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  17. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  18. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  19. package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
  20. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
  21. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
  22. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  23. package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
  24. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
  25. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
  26. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  27. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
  28. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
  29. package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
  30. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  31. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
  32. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  33. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  34. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  35. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  36. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  37. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  38. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  39. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  40. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
  41. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  42. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  43. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
  44. package/android/src/main/res/values/strings.xml +25 -5
  45. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  46. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
  47. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  48. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
  49. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
  50. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  51. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  52. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
  53. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  54. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
  55. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  56. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  57. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  58. package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
  59. package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
  60. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
  61. package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
  62. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  63. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  64. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  65. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  66. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  67. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  68. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  69. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  70. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  71. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  72. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  73. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
  74. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  75. package/ios/Browser/BrowserConfig.swift +102 -5
  76. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  77. package/ios/Browser/BrowserManager.swift +230 -205
  78. package/ios/Browser/BrowserPathHelper.swift +44 -44
  79. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  80. package/ios/Browser/JsonModels.swift +264 -106
  81. package/ios/Browser/SearchDrillIn.swift +7 -7
  82. package/ios/Browser/SectionPresentation.swift +64 -0
  83. package/ios/Browser/SectionScope.swift +58 -24
  84. package/ios/Browser/StyleResolver.swift +58 -0
  85. package/ios/Browser/TabBarEntries.swift +9 -3
  86. package/ios/Browser/TrackIdTemplate.swift +37 -0
  87. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  88. package/ios/CarPlay/CarPlayController.swift +227 -85
  89. package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
  90. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  91. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  92. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  93. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  94. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  95. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  96. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  97. package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
  98. package/ios/Extension/Section+Copying.swift +29 -0
  99. package/ios/Extension/String+IcyCharset.swift +21 -0
  100. package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
  101. package/ios/Extension/Track+Copying.swift +8 -12
  102. package/ios/Extension/Track+Identity.swift +18 -0
  103. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  104. package/ios/Http/HttpClient.swift +8 -2
  105. package/ios/HybridAudioBrowser.swift +253 -100
  106. package/ios/Model/NitroTypeStubs.swift +125 -23
  107. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  108. package/ios/Model/RemoteCommand.swift +18 -0
  109. package/ios/Model/TrackPlayerError.swift +2 -2
  110. package/ios/Option/SessionCategories.swift +3 -2
  111. package/ios/Player/MediaIntentCriteria.swift +17 -9
  112. package/ios/Player/MediaLoader.swift +33 -9
  113. package/ios/Player/NowPlayingUpdater.swift +54 -6
  114. package/ios/Player/PlaybackCoordinator.swift +13 -6
  115. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  116. package/ios/Player/PlaybackTimer.swift +8 -2
  117. package/ios/Player/QueueManager.swift +46 -13
  118. package/ios/Player/RetryManager.swift +80 -18
  119. package/ios/Player/SeekCompletionHandler.swift +3 -1
  120. package/ios/Player/ShuffleOrder.swift +33 -0
  121. package/ios/Player/SleepTimerHandling.swift +13 -0
  122. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  123. package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
  124. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  125. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  126. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  127. package/ios/Tests/JsonModelsTests.swift +150 -1
  128. package/ios/Tests/LRUCacheTests.swift +111 -0
  129. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  130. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  131. package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
  132. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  133. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  134. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  135. package/ios/Tests/QueueManagerTests.swift +122 -10
  136. package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
  137. package/ios/Tests/RemoteCommandTests.swift +67 -0
  138. package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
  139. package/ios/Tests/RetryManagerTests.swift +89 -0
  140. package/ios/Tests/SearchDrillInTests.swift +16 -16
  141. package/ios/Tests/SectionPresentationTests.swift +90 -0
  142. package/ios/Tests/SectionScopeTests.swift +87 -55
  143. package/ios/Tests/StyleResolverTests.swift +127 -0
  144. package/ios/Tests/Support/TestTypes.swift +8 -1
  145. package/ios/Tests/TabBarEntriesTests.swift +19 -19
  146. package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
  147. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  148. package/ios/Tests/TrackIdentityTests.swift +20 -0
  149. package/ios/Tests/TrackSelectorTests.swift +168 -33
  150. package/ios/TrackPlayer.swift +63 -35
  151. package/ios/TrackPlayerCallbacks.swift +5 -38
  152. package/ios/TrackSelector.swift +45 -30
  153. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  154. package/ios/Util/LRUCache.swift +4 -1
  155. package/ios/Util/NetworkMonitor.swift +43 -26
  156. package/ios/Util/SFSymbolRenderer.swift +24 -2
  157. package/ios/Util/SVGProcessor.swift +9 -2
  158. package/lib/commonjs/AudioBrowser.js +8 -8
  159. package/lib/commonjs/AudioBrowser.js.map +1 -1
  160. package/lib/commonjs/CLAUDE.md +52 -0
  161. package/lib/commonjs/features/browser-config.js +12 -8
  162. package/lib/commonjs/features/browser-config.js.map +1 -1
  163. package/lib/commonjs/features/browser.js +11 -3
  164. package/lib/commonjs/features/browser.js.map +1 -1
  165. package/lib/commonjs/features/carConnection.js +1 -1
  166. package/lib/commonjs/features/equalizer.js +1 -1
  167. package/lib/commonjs/features/errors.js +3 -3
  168. package/lib/commonjs/features/favorites.js +14 -11
  169. package/lib/commonjs/features/favorites.js.map +1 -1
  170. package/lib/commonjs/features/gate.js +16 -1
  171. package/lib/commonjs/features/gate.js.map +1 -1
  172. package/lib/commonjs/features/metadata.js +20 -3
  173. package/lib/commonjs/features/metadata.js.map +1 -1
  174. package/lib/commonjs/features/network.js +1 -1
  175. package/lib/commonjs/features/nowPlaying.js +1 -1
  176. package/lib/commonjs/features/output.js +1 -1
  177. package/lib/commonjs/features/playback/controls.js +4 -2
  178. package/lib/commonjs/features/playback/controls.js.map +1 -1
  179. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  180. package/lib/commonjs/features/playback/playing.js +1 -1
  181. package/lib/commonjs/features/playback/progress.js +3 -2
  182. package/lib/commonjs/features/playback/progress.js.map +1 -1
  183. package/lib/commonjs/features/playback/rate.js +8 -1
  184. package/lib/commonjs/features/playback/rate.js.map +1 -1
  185. package/lib/commonjs/features/playback/state.js +3 -4
  186. package/lib/commonjs/features/playback/state.js.map +1 -1
  187. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  188. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  189. package/lib/commonjs/features/playback/volume.js +1 -1
  190. package/lib/commonjs/features/player/options.js +48 -34
  191. package/lib/commonjs/features/player/options.js.map +1 -1
  192. package/lib/commonjs/features/player/setup.js +18 -9
  193. package/lib/commonjs/features/player/setup.js.map +1 -1
  194. package/lib/commonjs/features/player/validateOptions.js +23 -0
  195. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  196. package/lib/commonjs/features/queue/activeTrack.js +15 -4
  197. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  198. package/lib/commonjs/features/queue/queue.js +13 -6
  199. package/lib/commonjs/features/queue/queue.js.map +1 -1
  200. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  201. package/lib/commonjs/features/queue/shuffle.js +1 -1
  202. package/lib/commonjs/features/remoteControls.js +13 -92
  203. package/lib/commonjs/features/remoteControls.js.map +1 -1
  204. package/lib/commonjs/features/sleepTimer.js +13 -1
  205. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  206. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  207. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  208. package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
  209. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  210. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  211. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  212. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  213. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  214. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  215. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  216. package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
  217. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  218. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
  219. package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
  220. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  221. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  222. package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
  223. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  224. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  225. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  226. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
  227. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
  228. package/lib/commonjs/web/browser/BrowserManager.js +175 -100
  229. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  230. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  231. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  232. package/lib/commonjs/web/browser/SearchManager.js +18 -23
  233. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  234. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  235. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  236. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  237. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  238. package/lib/commonjs/web/browser/sections.js +89 -0
  239. package/lib/commonjs/web/browser/sections.js.map +1 -0
  240. package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
  241. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  242. package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
  243. package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
  244. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  245. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  246. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  247. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  248. package/lib/module/AudioBrowser.js +2 -1
  249. package/lib/module/AudioBrowser.js.map +1 -1
  250. package/lib/module/CLAUDE.md +52 -0
  251. package/lib/module/features/browser-config.js +12 -8
  252. package/lib/module/features/browser-config.js.map +1 -1
  253. package/lib/module/features/browser.js +11 -3
  254. package/lib/module/features/browser.js.map +1 -1
  255. package/lib/module/features/carConnection.js +1 -1
  256. package/lib/module/features/equalizer.js +1 -1
  257. package/lib/module/features/errors.js +3 -3
  258. package/lib/module/features/favorites.js +14 -11
  259. package/lib/module/features/favorites.js.map +1 -1
  260. package/lib/module/features/gate.js +16 -1
  261. package/lib/module/features/gate.js.map +1 -1
  262. package/lib/module/features/metadata.js +20 -3
  263. package/lib/module/features/metadata.js.map +1 -1
  264. package/lib/module/features/network.js +1 -1
  265. package/lib/module/features/nowPlaying.js +1 -1
  266. package/lib/module/features/output.js +1 -1
  267. package/lib/module/features/playback/controls.js +4 -2
  268. package/lib/module/features/playback/controls.js.map +1 -1
  269. package/lib/module/features/playback/playWhenReady.js +1 -1
  270. package/lib/module/features/playback/playing.js +1 -1
  271. package/lib/module/features/playback/progress.js +3 -2
  272. package/lib/module/features/playback/progress.js.map +1 -1
  273. package/lib/module/features/playback/rate.js +8 -1
  274. package/lib/module/features/playback/rate.js.map +1 -1
  275. package/lib/module/features/playback/state.js +3 -4
  276. package/lib/module/features/playback/state.js.map +1 -1
  277. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  278. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  279. package/lib/module/features/playback/volume.js +1 -1
  280. package/lib/module/features/player/options.js +47 -32
  281. package/lib/module/features/player/options.js.map +1 -1
  282. package/lib/module/features/player/setup.js +17 -8
  283. package/lib/module/features/player/setup.js.map +1 -1
  284. package/lib/module/features/player/validateOptions.js +19 -0
  285. package/lib/module/features/player/validateOptions.js.map +1 -0
  286. package/lib/module/features/queue/activeTrack.js +15 -4
  287. package/lib/module/features/queue/activeTrack.js.map +1 -1
  288. package/lib/module/features/queue/queue.js +13 -6
  289. package/lib/module/features/queue/queue.js.map +1 -1
  290. package/lib/module/features/queue/repeatMode.js +1 -1
  291. package/lib/module/features/queue/shuffle.js +1 -1
  292. package/lib/module/features/remoteControls.js +13 -92
  293. package/lib/module/features/remoteControls.js.map +1 -1
  294. package/lib/module/features/sleepTimer.js +13 -1
  295. package/lib/module/features/sleepTimer.js.map +1 -1
  296. package/lib/module/test-utils/reactNativeStub.js +58 -0
  297. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  298. package/lib/module/utils/LazyNativeEmitter.js +28 -14
  299. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  300. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  301. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  302. package/lib/module/utils/getTrackIdentity.js +20 -0
  303. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  304. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  305. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  306. package/lib/module/web/NativeAudioBrowser.js +200 -75
  307. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  308. package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
  309. package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
  310. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
  311. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
  312. package/lib/module/web/TrackPlayer/Player.js +254 -39
  313. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  314. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  315. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  316. package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
  317. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
  318. package/lib/module/web/browser/BrowserManager.js +175 -100
  319. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  320. package/lib/module/web/browser/FavoriteManager.js +31 -21
  321. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  322. package/lib/module/web/browser/SearchManager.js +18 -23
  323. package/lib/module/web/browser/SearchManager.js.map +1 -1
  324. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  325. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  326. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  327. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  328. package/lib/module/web/browser/sections.js +83 -0
  329. package/lib/module/web/browser/sections.js.map +1 -0
  330. package/lib/module/web/http/RequestConfigBuilder.js +110 -27
  331. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  332. package/lib/module/web/http/followMediaRedirect.js +215 -0
  333. package/lib/module/web/http/followMediaRedirect.js.map +1 -0
  334. package/lib/module/web/player/NowPlayingManager.js +4 -3
  335. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  336. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  337. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  338. package/lib/typescript/package.json +1 -0
  339. package/lib/typescript/src/AudioBrowser.d.ts +1 -1
  340. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  341. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  342. package/lib/typescript/src/features/browser.d.ts +10 -2
  343. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  344. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  345. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  346. package/lib/typescript/src/features/errors.d.ts +3 -3
  347. package/lib/typescript/src/features/favorites.d.ts +14 -11
  348. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  349. package/lib/typescript/src/features/gate.d.ts +18 -2
  350. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  351. package/lib/typescript/src/features/metadata.d.ts +20 -3
  352. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  353. package/lib/typescript/src/features/network.d.ts +1 -1
  354. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  355. package/lib/typescript/src/features/output.d.ts +1 -1
  356. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  357. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  358. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  359. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  360. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  361. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  362. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  363. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  364. package/lib/typescript/src/features/playback/state.d.ts +3 -4
  365. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  366. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  367. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  368. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  369. package/lib/typescript/src/features/player/options.d.ts +108 -56
  370. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  371. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  372. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  373. package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
  374. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  375. package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
  376. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  377. package/lib/typescript/src/features/queue/queue.d.ts +7 -4
  378. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  379. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  380. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  381. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  382. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  383. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  384. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  385. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  386. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  387. package/lib/typescript/src/types/browser-native.d.ts +3 -2
  388. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  389. package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
  390. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  391. package/lib/typescript/src/types/browser.d.ts +82 -57
  392. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  393. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
  394. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  395. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  396. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  397. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  398. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  399. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  400. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  401. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
  402. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
  403. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  404. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  405. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  406. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  407. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  408. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  409. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  410. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  411. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  412. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  413. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  414. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  415. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  416. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  417. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
  418. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  419. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  420. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
  421. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  422. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  423. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  424. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  425. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
  426. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  427. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  428. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  429. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  430. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
  431. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  432. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
  433. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
  434. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  435. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  436. package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
  437. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  438. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  439. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  440. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  441. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
  442. package/nitrogen/generated/android/c++/JSection.hpp +116 -0
  443. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  444. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  445. package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
  446. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  447. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  448. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  449. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  450. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  451. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  452. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  453. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  454. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  455. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
  456. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  457. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  458. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  459. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  460. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
  461. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  462. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  463. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  464. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  465. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  466. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
  467. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
  468. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  469. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  470. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
  471. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  472. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  473. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
  474. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
  475. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
  476. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
  477. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  478. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  479. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  480. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  481. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
  482. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  483. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  484. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  485. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  486. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  487. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
  488. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  489. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  490. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  491. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  492. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  493. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
  494. package/nitrogen/generated/ios/swift/Section.swift +82 -0
  495. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  496. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  497. package/nitrogen/generated/ios/swift/Track.swift +28 -63
  498. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  499. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  500. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  501. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  502. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  503. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  504. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  505. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  506. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  507. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  508. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  509. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
  510. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  511. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  512. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  513. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  514. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  515. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
  516. package/nitrogen/generated/shared/c++/Section.hpp +102 -0
  517. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  518. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  519. package/nitrogen/generated/shared/c++/Track.hpp +18 -30
  520. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  521. package/package.json +24 -11
  522. package/src/AudioBrowser.ts +2 -1
  523. package/src/CLAUDE.md +52 -0
  524. package/src/features/battery.test.ts +79 -0
  525. package/src/features/browser-config.test.ts +21 -6
  526. package/src/features/browser-config.ts +12 -8
  527. package/src/features/browser.test.ts +1 -1
  528. package/src/features/browser.ts +11 -3
  529. package/src/features/carConnection.ts +1 -1
  530. package/src/features/equalizer.ts +1 -1
  531. package/src/features/errors.ts +3 -3
  532. package/src/features/favorites.ts +14 -11
  533. package/src/features/gate.ts +16 -1
  534. package/src/features/hook-wiring.test.ts +302 -0
  535. package/src/features/metadata.ts +20 -3
  536. package/src/features/network.ts +1 -1
  537. package/src/features/nowPlaying.ts +1 -1
  538. package/src/features/output.ts +1 -1
  539. package/src/features/playback/controls.ts +4 -2
  540. package/src/features/playback/playWhenReady.ts +1 -1
  541. package/src/features/playback/playing.ts +1 -1
  542. package/src/features/playback/progress.test.ts +169 -0
  543. package/src/features/playback/progress.ts +3 -2
  544. package/src/features/playback/rate.ts +8 -1
  545. package/src/features/playback/state.ts +3 -4
  546. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  547. package/src/features/playback/trackPlaybackTime.ts +1 -6
  548. package/src/features/playback/volume.ts +1 -1
  549. package/src/features/player/options.ts +126 -76
  550. package/src/features/player/setup.test.ts +24 -7
  551. package/src/features/player/setup.ts +22 -10
  552. package/src/features/player/validateOptions.ts +21 -0
  553. package/src/features/queue/activeTrack.ts +15 -4
  554. package/src/features/queue/queue.ts +13 -6
  555. package/src/features/queue/repeatMode.ts +1 -1
  556. package/src/features/queue/shuffle.ts +1 -1
  557. package/src/features/remoteControls.ts +13 -92
  558. package/src/features/sleepTimer.test.ts +172 -0
  559. package/src/features/sleepTimer.ts +9 -1
  560. package/src/nitro-stub-drift.test.ts +277 -0
  561. package/src/public-surface.test.ts +147 -0
  562. package/src/specs/audio-browser.nitro.ts +5 -4
  563. package/src/style-resolution.test.ts +210 -0
  564. package/src/test-utils/reactNativeStub.ts +60 -0
  565. package/src/types/browser-native.ts +7 -3
  566. package/src/types/browser-nodes.ts +396 -134
  567. package/src/types/browser.ts +85 -59
  568. package/src/utils/LazyNativeEmitter.ts +28 -15
  569. package/src/utils/NativeUpdatedValue.ts +29 -13
  570. package/src/utils/getTrackIdentity.ts +20 -0
  571. package/src/utils/useDebug.test.ts +182 -0
  572. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  573. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  574. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
  575. package/src/web/NativeAudioBrowser.ts +239 -82
  576. package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
  577. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
  578. package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
  579. package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
  580. package/src/web/TrackPlayer/Player.load.test.ts +137 -1
  581. package/src/web/TrackPlayer/Player.ts +284 -37
  582. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  583. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  584. package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
  585. package/src/web/__tests__/lifecycle.test.ts +345 -0
  586. package/src/web/__tests__/lifecycleHarness.ts +351 -0
  587. package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
  588. package/src/web/__tests__/loadSupersession.test.ts +175 -0
  589. package/src/web/__tests__/retry.test.ts +169 -0
  590. package/src/web/browser/BrowserManager.ts +209 -111
  591. package/src/web/browser/FavoriteManager.test.ts +84 -0
  592. package/src/web/browser/FavoriteManager.ts +28 -23
  593. package/src/web/browser/SearchManager.test.ts +75 -0
  594. package/src/web/browser/SearchManager.ts +25 -28
  595. package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
  596. package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
  597. package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
  598. package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
  599. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  600. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  601. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  602. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  603. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  604. package/src/web/browser/sections.test.ts +91 -0
  605. package/src/web/browser/sections.ts +72 -0
  606. package/src/web/http/RequestConfigBuilder.test.ts +241 -1
  607. package/src/web/http/RequestConfigBuilder.ts +140 -33
  608. package/src/web/http/followMediaRedirect.test.ts +289 -0
  609. package/src/web/http/followMediaRedirect.ts +253 -0
  610. package/src/web/player/NowPlayingManager.ts +4 -3
  611. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  612. package/src/web/util/BrowserPathHelper.ts +61 -13
  613. package/website/guide/android-auto.md +114 -0
  614. package/website/guide/android-certificates.md +409 -0
  615. package/website/guide/artwork.md +255 -0
  616. package/website/guide/audio-output.md +135 -0
  617. package/website/guide/automotive.md +86 -0
  618. package/website/guide/basic-usage.md +151 -0
  619. package/website/guide/battery.md +115 -0
  620. package/website/guide/browser.md +581 -0
  621. package/website/guide/carplay.md +620 -0
  622. package/website/guide/configuration.md +173 -0
  623. package/website/guide/equalizer.md +231 -0
  624. package/website/guide/errors.md +310 -0
  625. package/website/guide/favorites.md +154 -0
  626. package/website/guide/gate.md +143 -0
  627. package/website/guide/getting-started.md +46 -0
  628. package/website/guide/hooks.md +238 -0
  629. package/website/guide/live-streams.md +125 -0
  630. package/website/guide/metadata.md +239 -0
  631. package/website/guide/migrating-from-track-player.md +244 -0
  632. package/website/guide/native-callback-fetch.md +127 -0
  633. package/website/guide/network.md +86 -0
  634. package/website/guide/now-playing.md +175 -0
  635. package/website/guide/playback.md +328 -0
  636. package/website/guide/queue.md +300 -0
  637. package/website/guide/remote-controls.md +256 -0
  638. package/website/guide/search.md +255 -0
  639. package/website/guide/sleep-timer.md +268 -0
  640. package/website/guide/track.md +247 -0
  641. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  642. package/ios/Model/MediaURL.swift +0 -67
  643. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  644. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  645. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  646. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  647. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  648. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  649. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  653. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  654. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  655. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  656. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  657. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  658. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  659. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
  660. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -246,7 +246,9 @@ struct ResetTests {
246
246
  c.capture(position: 25.0)
247
247
 
248
248
  // Let the (immediately-cancelled, itemless) seek completion drain.
249
- for _ in 0 ..< 20 { await Task.yield() }
249
+ for _ in 0 ..< 20 {
250
+ await Task.yield()
251
+ }
250
252
 
251
253
  #expect(spy.calls.isEmpty)
252
254
  #expect(c.pendingTime == 25.0)
@@ -57,12 +57,12 @@ struct MediaIntentCriteriaFromTests {
57
57
  mediaTypeMode: String? = nil,
58
58
  reference: MediaIntentCriteria.Reference = .unknown,
59
59
  hasMediaType: Bool = false,
60
- appName: String? = "Radio Garden"
60
+ appName: String? = "Radio Garden",
61
61
  ) -> MediaIntentCriteria {
62
62
  MediaIntentCriteria.from(
63
63
  mediaName: mediaName, genreNames: genreNames, artistName: artistName,
64
64
  albumName: albumName, mediaTypeMode: mediaTypeMode,
65
- reference: reference, hasMediaType: hasMediaType, appName: appName
65
+ reference: reference, hasMediaType: hasMediaType, appName: appName,
66
66
  )
67
67
  }
68
68
 
@@ -120,7 +120,7 @@ struct TransitionTests {
120
120
  @MainActor
121
121
  private func makeRetrying() -> (
122
122
  PlaybackCoordinator, MockRetryHandling, MockPlaybackCoordinatorCallbacks,
123
- MockPlaybackEffectHandler
123
+ MockPlaybackEffectHandler,
124
124
  ) {
125
125
  let retryHandler = MockRetryHandling()
126
126
  retryHandler.isRetryableResult = true
@@ -14,9 +14,11 @@ private final class MockRetryHandler: RetryHandling {
14
14
  var attemptRetryResult = false
15
15
  var attemptRetryCallCount = 0
16
16
  var lastStartFromCurrentTime: Bool?
17
+ var lastHTTPStatusCode: Int??
17
18
  var resetCallCount = 0
18
19
 
19
- func isRetryable(_ error: Error?) -> Bool {
20
+ func isRetryable(_ error: Error?, httpStatusCode: Int?) -> Bool {
21
+ lastHTTPStatusCode = .some(httpStatusCode)
20
22
  guard let error else { return false }
21
23
  let code = (error as NSError).code
22
24
  return retryableErrors.contains(code)
@@ -14,10 +14,11 @@ struct PlaybackStateStoreTests {
14
14
 
15
15
  private func sampleTrack() -> JsonTrack {
16
16
  JsonTrack(
17
- id: "abc", url: nil, title: "Test FM", subtitle: nil, artwork: nil,
18
- artist: "City, Country", albumUrl: nil, album: nil, description: nil,
17
+ id: "abc", path: nil, title: "Test FM", subtitle: nil, artwork: nil,
18
+ artist: "City, Country", albumPath: nil, album: nil, description: nil,
19
19
  genre: nil, duration: nil, src: "/listen/abc/channel.mp3", request: nil,
20
- style: nil, childrenStyle: nil, groupTitle: nil, live: true, imageRow: nil,
20
+ style: JsonStyle(display: "grid", artworkRendering: "stencil"),
21
+ disabled: false, live: true, icyCharset: "windows-1251",
21
22
  )
22
23
  }
23
24
 
@@ -0,0 +1,37 @@
1
+ import AVFoundation
2
+ import Testing
3
+
4
+ @testable import AudioBrowserTestable
5
+
6
+ @MainActor
7
+ @Suite("PlayerTimeObserver")
8
+ struct PlayerTimeObserverTests {
9
+ /// An AVPlayer retains its time observers, so one left registered outlives both this object and
10
+ /// the caller's interest in it (#96).
11
+ @Test func unregisteringDropsTheToken() {
12
+ // Held: `avPlayer` is weak, so a temporary is gone before the next line.
13
+ let player = AVPlayer()
14
+ let observer = PlayerTimeObserver(onAudioDidStart: {})
15
+ observer.avPlayer = player
16
+
17
+ observer.registerForBoundaryTimeEvents()
18
+ #expect(observer.boundaryTimeStartObserverToken != nil)
19
+
20
+ observer.unregisterForBoundaryTimeEvents()
21
+ #expect(observer.boundaryTimeStartObserverToken == nil)
22
+ }
23
+
24
+ /// Swapping players has to unregister from the outgoing one — it is a different AVPlayer, and
25
+ /// the token cannot be removed from it afterwards.
26
+ @Test func swappingPlayersUnregistersFromThePrevious() {
27
+ let first = AVPlayer()
28
+ let second = AVPlayer()
29
+ let observer = PlayerTimeObserver(onAudioDidStart: {})
30
+ observer.avPlayer = first
31
+ observer.registerForBoundaryTimeEvents()
32
+
33
+ observer.avPlayer = second
34
+
35
+ #expect(observer.boundaryTimeStartObserverToken == nil)
36
+ }
37
+ }
@@ -74,6 +74,87 @@ struct NextTests {
74
74
  #expect(q.currentIndex == 0)
75
75
  }
76
76
 
77
+ /// The start track must be first in the shuffle order, not wherever the permutation put it —
78
+ /// otherwise auto-advance ends the queue as soon as it is reached (#96).
79
+ @Test func shuffle_startTrackLeadsTheOrder() {
80
+ for start in 0 ..< 6 {
81
+ let q = QueueManager()
82
+ q.setQueue(tracks("a", "b", "c", "d", "e", "f"), initialIndex: start)
83
+ q.shuffleEnabled = true
84
+
85
+ #expect(q.shuffleOrder.isFirst(start))
86
+ #expect(!q.isLastInPlaybackOrder)
87
+ #expect(q.nextTracks.count == 5)
88
+ #expect(q.previousTracks.isEmpty)
89
+ }
90
+ }
91
+
92
+ /// Same hazard on enabling shuffle mid-queue: the existing order has the current track wherever
93
+ /// it landed, so without re-pinning, turning shuffle on can immediately report end-of-queue.
94
+ @Test func shuffle_enablingMidQueueLeadsFromCurrent() {
95
+ for start in 0 ..< 6 {
96
+ let q = QueueManager()
97
+ q.setQueue(tracks("a", "b", "c", "d", "e", "f"))
98
+ _ = try? q.skipTo(start)
99
+
100
+ q.shuffleEnabled = true
101
+
102
+ #expect(q.shuffleOrder.isFirst(start))
103
+ #expect(!q.isLastInPlaybackOrder)
104
+ #expect(q.nextTracks.count == 5)
105
+ }
106
+ }
107
+
108
+ /// Re-pinning happens on enable only — flipping it off and on again must not renege on the
109
+ /// order mid-listen, and a redundant `= true` must not reshuffle under the listener.
110
+ @Test func shuffle_redundantEnableKeepsTheOrder() {
111
+ let q = QueueManager()
112
+ q.setQueue(tracks("a", "b", "c", "d", "e", "f"))
113
+ q.shuffleEnabled = true
114
+ let order = q.shuffleOrder.shuffled
115
+
116
+ q.shuffleEnabled = true
117
+ #expect(q.shuffleOrder.shuffled == order)
118
+
119
+ q.shuffleEnabled = false
120
+ #expect(q.shuffleOrder.shuffled == order)
121
+ }
122
+
123
+ /// A queue reorder permutes positions, not identities, so it must leave the
124
+ /// shuffled playback order alone. Re-inserting at a random shuffle position
125
+ /// would silently reshuffle under the listener.
126
+ @Test func shuffle_moveKeepsThePlaybackOrder() {
127
+ for target in 0 ..< 5 {
128
+ let q = QueueManager()
129
+ q.setQueue(tracks("a", "b", "c", "d", "e"))
130
+ q.shuffleEnabled = true
131
+ let before = q.shuffleOrder.shuffled.map { q.tracks[$0].id }
132
+
133
+ _ = try? q.move(fromIndex: 0, toIndex: target)
134
+
135
+ #expect(q.shuffleOrder.shuffled.map { q.tracks[$0].id } == before)
136
+ }
137
+ }
138
+
139
+ /// The same hazard as `shuffle_startTrackLeadsTheOrder`, reached by moving the
140
+ /// *playing* track: a random re-insert can drop it last in the shuffle order,
141
+ /// which reads as `isLastInPlaybackOrder` and ends the queue at the next track
142
+ /// boundary with tracks still unplayed.
143
+ @Test func shuffle_movingCurrentTrackDoesNotEndTheQueue() {
144
+ for target in 1 ..< 6 {
145
+ let q = QueueManager()
146
+ q.setQueue(tracks("a", "b", "c", "d", "e", "f"), initialIndex: 0)
147
+ q.shuffleEnabled = true // pins "a" first in the order
148
+ #expect(!q.isLastInPlaybackOrder)
149
+
150
+ _ = try? q.move(fromIndex: 0, toIndex: target)
151
+
152
+ #expect(q.currentTrack?.id == "a")
153
+ #expect(!q.isLastInPlaybackOrder)
154
+ #expect(q.nextTracks.count == 5)
155
+ }
156
+ }
157
+
77
158
  @Test func shuffle_visitsAllTracks() {
78
159
  let q = QueueManager()
79
160
  q.setQueue(tracks("a", "b", "c", "d"))
@@ -336,6 +417,25 @@ struct AddTests {
336
417
  #expect(q.tracks[2].id == "c")
337
418
  #expect(q.tracks[3].id == "d")
338
419
  }
420
+
421
+ /// Filling an empty queue must pin the start track to the head of the shuffle
422
+ /// order, exactly as `setQueue` does — `insert(at:count:)` places new indices
423
+ /// at random positions, so without the pin the start track can land last and
424
+ /// the queue reports end-of-queue as soon as it finishes. `shuffleEnabled`'s
425
+ /// didSet only re-pins on the off→on edge, so shuffle-already-on gets nothing
426
+ /// from it.
427
+ @Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() {
428
+ for start in 0 ..< 6 {
429
+ let q = QueueManager()
430
+ q.shuffleEnabled = true
431
+ q.add(tracks("a", "b", "c", "d", "e", "f"), initialIndex: start)
432
+
433
+ #expect(q.shuffleOrder.isFirst(start))
434
+ #expect(!q.isLastInPlaybackOrder)
435
+ #expect(q.nextTracks.count == 5)
436
+ #expect(q.previousTracks.isEmpty)
437
+ }
438
+ }
339
439
  }
340
440
 
341
441
  // MARK: - Mutations: addAt()
@@ -368,6 +468,18 @@ struct AddAtTests {
368
468
  #expect(q.currentIndex == 0)
369
469
  }
370
470
 
471
+ /// Same pinning requirement as `add()` into an empty queue — see its twin.
472
+ @Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() throws {
473
+ let q = QueueManager()
474
+ q.shuffleEnabled = true
475
+ try q.addAt(tracks("a", "b", "c", "d", "e", "f"), at: 0)
476
+
477
+ #expect(q.shuffleOrder.isFirst(0))
478
+ #expect(!q.isLastInPlaybackOrder)
479
+ #expect(q.nextTracks.count == 5)
480
+ #expect(q.previousTracks.isEmpty)
481
+ }
482
+
371
483
  @Test func invalidIndex_throws() {
372
484
  let q = QueueManager()
373
485
  q.setQueue(tracks("a"))
@@ -645,10 +757,10 @@ struct ClearTests {
645
757
  @Suite("replace & removeUpcomingTracks")
646
758
  @MainActor
647
759
  struct OtherMutationTests {
648
- @Test func replace_updatesTrackAtIndex() {
760
+ @Test func replace_updatesTrackAtIndex() throws {
649
761
  let q = QueueManager()
650
762
  q.setQueue(tracks("a", "b", "c"))
651
- q.replace(1, Track(id: "B"))
763
+ try q.replace(1, Track(id: "B"))
652
764
  #expect(q.tracks[1].id == "B")
653
765
  }
654
766
 
@@ -692,10 +804,10 @@ struct OtherMutationTests {
692
804
  #expect(q.tracks[0].id == "a")
693
805
  }
694
806
 
695
- @Test func replace_currentTrack_reflectsInCurrentTrack() {
807
+ @Test func replace_currentTrack_reflectsInCurrentTrack() throws {
696
808
  let q = QueueManager()
697
809
  q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
698
- q.replace(1, Track(id: "B"))
810
+ try q.replace(1, Track(id: "B"))
699
811
  #expect(q.currentTrack?.id == "B")
700
812
  #expect(q.currentIndex == 1)
701
813
  }
@@ -767,13 +879,13 @@ struct ComputedPropertyTests {
767
879
  let q = QueueManager()
768
880
  q.setQueue(tracks("a", "b", "c", "d"))
769
881
  q.shuffleEnabled = true
770
- let nextFromProp = q.nextTracks.map(\.id)
882
+ let nextFromProp = q.nextTracks.map { $0.id! }
771
883
  // Navigate forward only as many times as nextTracks reports
772
884
  // (nextTracks does not include wrapped tracks)
773
885
  var nextFromNav: [String] = []
774
886
  for _ in 0 ..< nextFromProp.count {
775
887
  _ = q.next()
776
- nextFromNav.append(q.currentTrack!.id)
888
+ nextFromNav.append(q.currentTrack!.id!)
777
889
  }
778
890
  #expect(nextFromProp == nextFromNav)
779
891
  }
@@ -786,12 +898,12 @@ struct ComputedPropertyTests {
786
898
  for _ in 0 ..< 3 {
787
899
  _ = q.next()
788
900
  }
789
- let prevFromProp = q.previousTracks.map(\.id)
901
+ let prevFromProp = q.previousTracks.map { $0.id! }
790
902
  // Navigate backward the same number of times
791
903
  var prevFromNav: [String] = []
792
904
  for _ in 0 ..< prevFromProp.count {
793
905
  _ = q.previous()
794
- prevFromNav.insert(q.currentTrack!.id, at: 0)
906
+ prevFromNav.insert(q.currentTrack!.id!, at: 0)
795
907
  }
796
908
  #expect(prevFromProp == prevFromNav)
797
909
  }
@@ -880,12 +992,12 @@ struct DelegateTests {
880
992
  #expect(spy.calls.count == 1)
881
993
  }
882
994
 
883
- @Test func queueDidChangeTracks_firesOnReplace() {
995
+ @Test func queueDidChangeTracks_firesOnReplace() throws {
884
996
  let q = QueueManager()
885
997
  q.setQueue(tracks("a", "b"))
886
998
  let spy = DelegateSpy()
887
999
  q.delegate = spy
888
- q.replace(0, Track(id: "A"))
1000
+ try q.replace(0, Track(id: "A"))
889
1001
  #expect(spy.calls.count == 1)
890
1002
  }
891
1003
 
@@ -0,0 +1,172 @@
1
+ import Foundation
2
+ import MediaPlayer
3
+ import Testing
4
+
5
+ @testable import AudioBrowserTestable
6
+
7
+ /// Exercises the controller against the real `MPRemoteCommandCenter`.
8
+ ///
9
+ /// `MPRemoteCommandCenter`'s `init()` is unavailable, so `shared()` — a
10
+ /// process-wide singleton — is the only center available. The suite is
11
+ /// `.serialized` because of that shared state, and every test resets the
12
+ /// commands it touches through `reset()`.
13
+ @Suite("RemoteCommandController", .serialized)
14
+ @MainActor
15
+ struct RemoteCommandControllerTests {
16
+ final class SpyCallbacks: RemoteCommandCallbacks {
17
+ var events: [String] = []
18
+ func remotePlay() { events.append("play") }
19
+ func remotePause() { events.append("pause") }
20
+ func remoteStop() { events.append("stop") }
21
+ func remotePlayPause() { events.append("playPause") }
22
+ func remoteNext() { events.append("next") }
23
+ func remotePrevious() { events.append("previous") }
24
+ func remoteJumpForward(interval: Double) { events.append("jumpForward:\(interval)") }
25
+ func remoteJumpBackward(interval: Double) { events.append("jumpBackward:\(interval)") }
26
+ func remoteSeek(position: Double) { events.append("seek:\(position)") }
27
+ func remoteChangeRepeatMode(mode: RepeatMode) { events.append("repeat:\(mode)") }
28
+ func remoteChangeShuffleMode(enabled: Bool) { events.append("shuffle:\(enabled)") }
29
+ func remoteChangePlaybackRate(rate: Float) { events.append("rate:\(rate)") }
30
+ }
31
+
32
+ /// Clears every command on the shared center so a test starts from a known
33
+ /// state regardless of what ran before it.
34
+ private func makeController() -> (RemoteCommandController, SpyCallbacks) {
35
+ let center = MPRemoteCommandCenter.shared()
36
+ let reset = RemoteCommandController(remoteCommandCenter: center)
37
+ reset.enable(commands: RemoteCommand.all())
38
+ reset.disableAll()
39
+
40
+ let spy = SpyCallbacks()
41
+ return (RemoteCommandController(remoteCommandCenter: center, callbacks: spy), spy)
42
+ }
43
+
44
+ /// The reported bug: a changed jump interval used to disable the very command
45
+ /// it had just re-enabled, removing the skip button from the lock screen,
46
+ /// Control Center and CarPlay while `enabledCommands` still reported it on.
47
+ @Test func changedJumpIntervalKeepsCommandEnabled() {
48
+ let (controller, _) = makeController()
49
+ let center = MPRemoteCommandCenter.shared()
50
+
51
+ controller.enable(commands: [
52
+ .play, .skipForward(preferredIntervals: [15]), .skipBackward(preferredIntervals: [15]),
53
+ ])
54
+ #expect(center.skipForwardCommand.isEnabled)
55
+
56
+ controller.enable(commands: [
57
+ .play, .skipForward(preferredIntervals: [30]), .skipBackward(preferredIntervals: [30]),
58
+ ])
59
+
60
+ #expect(center.skipForwardCommand.isEnabled)
61
+ #expect(center.skipBackwardCommand.isEnabled)
62
+ #expect(controller.commandTargetPointers["skipForward"] != nil)
63
+ #expect(controller.commandTargetPointers["skipBackward"] != nil)
64
+ #expect(center.skipForwardCommand.preferredIntervals == [30])
65
+ #expect(center.skipBackwardCommand.preferredIntervals == [30])
66
+ }
67
+
68
+ @Test func changedPlaybackRatesKeepCommandEnabled() {
69
+ let (controller, _) = makeController()
70
+ let center = MPRemoteCommandCenter.shared()
71
+
72
+ controller.enable(commands: [.changePlaybackRate(supportedPlaybackRates: [1.0])])
73
+ controller.enable(commands: [.changePlaybackRate(supportedPlaybackRates: [1.0, 1.5, 2.0])])
74
+
75
+ #expect(center.changePlaybackRateCommand.isEnabled)
76
+ #expect(controller.commandTargetPointers["changePlaybackRate"] != nil)
77
+ #expect(center.changePlaybackRateCommand.supportedPlaybackRates == [1.0, 1.5, 2.0])
78
+ }
79
+
80
+ @Test func removedCommandIsDisabledAndUntargeted() {
81
+ let (controller, _) = makeController()
82
+ let center = MPRemoteCommandCenter.shared()
83
+
84
+ controller.enable(commands: [.play, .pause, .next])
85
+ #expect(center.nextTrackCommand.isEnabled)
86
+
87
+ controller.enable(commands: [.play, .pause])
88
+
89
+ #expect(!center.nextTrackCommand.isEnabled)
90
+ #expect(controller.commandTargetPointers["nextTrack"] == nil)
91
+ #expect(center.playCommand.isEnabled)
92
+ #expect(controller.commandTargetPointers["play"] != nil)
93
+ }
94
+
95
+ @Test func disableAllClearsEveryTarget() {
96
+ let (controller, _) = makeController()
97
+ let center = MPRemoteCommandCenter.shared()
98
+
99
+ controller.enable(commands: [.play, .pause, .skipForward(preferredIntervals: [15])])
100
+ controller.disableAll()
101
+
102
+ #expect(controller.commandTargetPointers.isEmpty)
103
+ #expect(!center.playCommand.isEnabled)
104
+ #expect(!center.pauseCommand.isEnabled)
105
+ #expect(!center.skipForwardCommand.isEnabled)
106
+ }
107
+
108
+ /// Re-enabling the same set must not accumulate handlers: `enableRemoteCommand`
109
+ /// removes the previous target before adding the new one.
110
+ @Test func reEnablingKeepsOneTargetPerCommand() {
111
+ let (controller, _) = makeController()
112
+
113
+ controller.enable(commands: [.play, .pause])
114
+ let first = controller.commandTargetPointers["play"]
115
+ controller.enable(commands: [.play, .pause])
116
+ let second = controller.commandTargetPointers["play"]
117
+
118
+ #expect(controller.commandTargetPointers.count == 2)
119
+ #expect(first as AnyObject? !== second as AnyObject?)
120
+ }
121
+
122
+ /// `setSkipAvailability` greys out next/previous when the queue has nowhere to
123
+ /// go, and the state must survive a later `enable(commands:)`.
124
+ @Test func skipAvailabilitySurvivesReEnable() {
125
+ let (controller, _) = makeController()
126
+ let center = MPRemoteCommandCenter.shared()
127
+
128
+ controller.enable(commands: [.next, .previous])
129
+ controller.setSkipAvailability(canNext: false, canPrevious: true)
130
+ #expect(!center.nextTrackCommand.isEnabled)
131
+ #expect(center.previousTrackCommand.isEnabled)
132
+
133
+ controller.enable(commands: [.next, .previous, .play])
134
+
135
+ #expect(!center.nextTrackCommand.isEnabled)
136
+ #expect(center.previousTrackCommand.isEnabled)
137
+ }
138
+
139
+ /// The handler properties must not capture the controller strongly. Assigning a
140
+ /// bare method reference (`= handlePlayCommandDefault`) curries to
141
+ /// `{ [self] in self.method($0) }`, and storing that back on self is a permanent
142
+ /// cycle — one leaked controller per player instance, across every JS reload.
143
+ @Test func doesNotRetainItselfThroughHandlers() {
144
+ weak var weakController: RemoteCommandController?
145
+ autoreleasepool {
146
+ let (controller, _) = makeController()
147
+ // Forces every lazy handler to initialize, which is what closes the cycle.
148
+ controller.enable(commands: RemoteCommand.all())
149
+ controller.disableAll()
150
+ weakController = controller
151
+ }
152
+ #expect(weakController == nil)
153
+ }
154
+
155
+ @Test func shuffleAndRepeatStateSurviveCommandCenterSwitch() {
156
+ let (controller, _) = makeController()
157
+ let center = MPRemoteCommandCenter.shared()
158
+
159
+ controller.enable(commands: [.changeShuffleMode, .changeRepeatMode])
160
+ controller.updateShuffleMode(true)
161
+ controller.updateRepeatMode(.queue)
162
+
163
+ // Same center — switchCommandCenter no-ops, but the restore path is the
164
+ // interesting part: state is re-applied from the stored values.
165
+ controller.switchCommandCenter(center)
166
+
167
+ #expect(center.changeShuffleModeCommand.currentShuffleType == .items)
168
+ #expect(center.changeRepeatModeCommand.currentRepeatType == .all)
169
+ #expect(center.changeShuffleModeCommand.isEnabled)
170
+ #expect(controller.commandTargetPointers["changeShuffleMode"] != nil)
171
+ }
172
+ }
@@ -0,0 +1,67 @@
1
+ import Foundation
2
+ import Testing
3
+
4
+ @testable import AudioBrowserTestable
5
+
6
+ @Suite("RemoteCommand")
7
+ struct RemoteCommandTests {
8
+ @Test func removedCommandsAreDisabled() {
9
+ let toDisable = RemoteCommand.commandsToDisable(
10
+ enabled: [.play, .pause, .next, .previous],
11
+ replacedBy: [.play, .pause],
12
+ )
13
+ #expect(toDisable.map(\.key) == ["nextTrack", "previousTrack"])
14
+ }
15
+
16
+ @Test func retainedCommandsAreNotDisabled() {
17
+ let toDisable = RemoteCommand.commandsToDisable(
18
+ enabled: [.play, .pause],
19
+ replacedBy: [.play, .pause, .stop],
20
+ )
21
+ #expect(toDisable.isEmpty)
22
+ }
23
+
24
+ /// A changed jump interval is the same command with a new associated value —
25
+ /// diffing by `==` would report it as removed and disable the button that the
26
+ /// update had just re-enabled.
27
+ @Test func changedJumpIntervalIsNotDisabled() {
28
+ let toDisable = RemoteCommand.commandsToDisable(
29
+ enabled: [.skipForward(preferredIntervals: [15]), .skipBackward(preferredIntervals: [15])],
30
+ replacedBy: [.skipForward(preferredIntervals: [30]), .skipBackward(preferredIntervals: [30])],
31
+ )
32
+ #expect(toDisable.isEmpty)
33
+ }
34
+
35
+ @Test func changedPlaybackRatesAreNotDisabled() {
36
+ let toDisable = RemoteCommand.commandsToDisable(
37
+ enabled: [.changePlaybackRate(supportedPlaybackRates: [1.0])],
38
+ replacedBy: [.changePlaybackRate(supportedPlaybackRates: [1.0, 1.5, 2.0])],
39
+ )
40
+ #expect(toDisable.isEmpty)
41
+ }
42
+
43
+ @Test func everyCommandIsDisabledWhenNoneRemain() {
44
+ let toDisable = RemoteCommand.commandsToDisable(
45
+ enabled: RemoteCommand.all(),
46
+ replacedBy: [],
47
+ )
48
+ #expect(toDisable.count == RemoteCommand.all().count)
49
+ }
50
+
51
+ /// `key` is the identity used both for the diff above and for the
52
+ /// `commandTargetPointers` slot each command's handler lives in. Two cases
53
+ /// sharing a key would silently share one slot: enabling one would remove the
54
+ /// other's handler.
55
+ @Test func keysAreUnique() {
56
+ let all = RemoteCommand.all()
57
+ #expect(Set(all.map(\.key)).count == all.count)
58
+ }
59
+
60
+ @Test func droppedJumpCommandIsStillDisabled() {
61
+ let toDisable = RemoteCommand.commandsToDisable(
62
+ enabled: [.play, .skipForward(preferredIntervals: [15])],
63
+ replacedBy: [.play],
64
+ )
65
+ #expect(toDisable.map(\.key) == ["skipForward"])
66
+ }
67
+ }