react-native-audio-browser 0.1.1 → 0.2.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1000) hide show
  1. package/AudioBrowser.podspec +1 -1
  2. package/README.md +184 -2
  3. package/android/build.gradle +17 -5
  4. package/android/gradle.properties +2 -2
  5. package/android/src/main/AndroidManifest.xml +18 -1
  6. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +473 -158
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  8. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  9. package/android/src/main/java/com/audiobrowser/Callbacks.kt +4 -6
  10. package/android/src/main/java/com/audiobrowser/Service.kt +67 -18
  11. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  12. package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +37 -0
  13. package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +34 -0
  14. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +442 -178
  15. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +299 -0
  16. package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +79 -0
  17. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +41 -4
  18. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +42 -0
  19. package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
  20. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +35 -0
  21. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  22. package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +109 -97
  23. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +6 -10
  24. package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +40 -8
  25. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +40 -46
  26. package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +4 -1
  27. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +172 -0
  28. package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +18 -1
  29. package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +153 -0
  30. package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +141 -0
  31. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +15 -3
  32. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +422 -134
  33. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +248 -404
  34. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +347 -0
  35. package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +129 -0
  36. package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +85 -0
  37. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +25 -5
  38. package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +56 -0
  39. package/android/src/main/java/com/audiobrowser/player/Player.kt +475 -577
  40. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +20 -0
  41. package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +130 -0
  42. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +190 -67
  43. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +118 -53
  44. package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +37 -3
  45. package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +80 -0
  46. package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +50 -0
  47. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +85 -40
  48. package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +81 -0
  49. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +156 -0
  50. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +109 -0
  51. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +37 -0
  52. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +55 -0
  53. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +265 -0
  54. package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +161 -0
  55. package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +28 -0
  56. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +24 -0
  57. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +24 -0
  58. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +49 -0
  59. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +36 -0
  60. package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +23 -300
  61. package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +13 -23
  62. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
  63. package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +4 -3
  64. package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +211 -0
  65. package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +58 -0
  66. package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +2 -1
  67. package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +14 -0
  68. package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +6 -111
  69. package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +22 -8
  70. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +112 -80
  71. package/android/src/main/res/values/strings.xml +4 -0
  72. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  73. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +165 -0
  74. package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +46 -0
  75. package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +39 -0
  76. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +114 -0
  77. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +141 -0
  78. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +4 -0
  79. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +177 -0
  80. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +55 -0
  81. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +94 -0
  82. package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +1 -2
  83. package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +72 -0
  84. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +203 -0
  85. package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +56 -0
  86. package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +216 -0
  87. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  88. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +442 -0
  89. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +190 -0
  90. package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +198 -0
  91. package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +137 -0
  92. package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +36 -0
  93. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +268 -0
  94. package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +68 -0
  95. package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +40 -0
  96. package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +193 -0
  97. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +153 -0
  98. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +186 -0
  99. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +158 -0
  100. package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +18 -0
  101. package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +163 -0
  102. package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +34 -0
  103. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +28 -0
  104. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +53 -0
  105. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +58 -0
  106. package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +57 -0
  107. package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +40 -0
  108. package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +34 -0
  109. package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +50 -0
  110. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +63 -0
  111. package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +35 -0
  112. package/android/src/test/resources/certs/isrg-root-x1.pem +31 -0
  113. package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
  114. package/android/src/test/resources/certs/letsencrypt-r13.pem +29 -0
  115. package/android/src/test/resources/certs/stationplaylist-leaf.pem +34 -0
  116. package/ios/Browser/BrowserConfig.swift +41 -11
  117. package/ios/Browser/BrowserManager+URLResolution.swift +213 -93
  118. package/ios/Browser/BrowserManager.swift +303 -135
  119. package/ios/Browser/BrowserPathHelper.swift +32 -3
  120. package/ios/Browser/JsonModels.swift +287 -66
  121. package/ios/Browser/MediaResolveComposer.swift +114 -0
  122. package/ios/Browser/SearchDrillIn.swift +44 -0
  123. package/ios/Browser/SectionScope.swift +35 -0
  124. package/ios/Browser/TabBarEntries.swift +15 -0
  125. package/ios/CarPlay/CPInterfaceController+Safe.swift +126 -0
  126. package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
  127. package/ios/CarPlay/CarPlayController.swift +739 -149
  128. package/ios/CarPlay/CarPlayImageLoader.swift +57 -8
  129. package/ios/CarPlay/CarPlayListItemFactory.swift +44 -35
  130. package/ios/CarPlay/CarPlayNowPlayingManager.swift +180 -36
  131. package/ios/CarPlay/ImageRowItem+CarPlay.swift +19 -0
  132. package/ios/CarPlay/RNABAudioBrowser.h +15 -10
  133. package/ios/CarPlay/RNABAudioBrowser.m +15 -14
  134. package/ios/CarPlay/RNABCarPlaySceneDelegate.m +2 -0
  135. package/ios/CarPlay/RNABMediaAddHandler.swift +75 -0
  136. package/ios/CarPlay/RNABMediaAffinityHandler.swift +69 -0
  137. package/ios/CarPlay/RNABMediaIntentHandler.swift +53 -43
  138. package/ios/Extension/Capability+RemoteCommand.swift +7 -9
  139. package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
  140. package/ios/Extension/NavigationError+Conversion.swift +8 -1
  141. package/ios/Extension/PlayerCapabilities+Favorite.swift +20 -0
  142. package/ios/Extension/RequestConfig+Copying.swift +31 -0
  143. package/ios/Extension/ResolvedTrack+Copying.swift +10 -4
  144. package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
  145. package/ios/Extension/Track+AVPlayer.swift +4 -4
  146. package/ios/Extension/Track+Copying.swift +9 -3
  147. package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
  148. package/ios/Extension/Variant+Artwork.swift +49 -0
  149. package/ios/HybridAudioBrowser.swift +805 -197
  150. package/ios/Model/NitroTypeStubs.swift +299 -224
  151. package/ios/Model/PlayerUpdateOptions.swift +23 -23
  152. package/ios/Model/RemoteCommand.swift +24 -6
  153. package/ios/Model/TrackPlayerError.swift +82 -18
  154. package/ios/NowPlayingInfo/NowPlayingInfoController.swift +99 -121
  155. package/ios/NowPlayingInfo/SilentAudioPrimer.swift +73 -0
  156. package/ios/Observer/PlayerItemNotificationObserver.swift +45 -5
  157. package/ios/Observer/PlayerItemPropertyObserver.swift +27 -4
  158. package/ios/Observer/PlayerTimeObserver.swift +3 -53
  159. package/ios/Option/SessionCategories.swift +34 -11
  160. package/ios/Player/AirPlayStallJudgement.swift +25 -0
  161. package/ios/Player/EndOfTrackJudgement.swift +32 -0
  162. package/ios/Player/LoadSeekCoordinator.swift +16 -1
  163. package/ios/Player/MediaIntentCriteria.swift +139 -0
  164. package/ios/Player/MediaLoader.swift +31 -11
  165. package/ios/Player/NowPlayingUpdater.swift +119 -26
  166. package/ios/Player/PersistedPlaybackState.swift +11 -0
  167. package/ios/Player/PlaybackCoordinator.swift +285 -51
  168. package/ios/Player/PlaybackCoordinatorCallbacks.swift +6 -1
  169. package/ios/Player/PlaybackEffectHandler.swift +19 -9
  170. package/ios/Player/PlaybackErrorHandler.swift +98 -14
  171. package/ios/Player/PlaybackStateMachine.swift +18 -13
  172. package/ios/Player/PlaybackStateStore.swift +27 -0
  173. package/ios/Player/PlaybackTimer.swift +61 -0
  174. package/ios/Player/PlayingStateManager.swift +3 -2
  175. package/ios/Player/QueueManager.swift +77 -64
  176. package/ios/Player/RetryManager.swift +175 -38
  177. package/ios/Player/ShuffleOrder.swift +10 -0
  178. package/ios/Player/SleepTimerHandling.swift +8 -0
  179. package/ios/Player/SleepTimerManager.swift +72 -11
  180. package/ios/Player/VolumeFader.swift +77 -0
  181. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  182. package/ios/RemoteCommand/RemoteCommandController.swift +40 -21
  183. package/ios/Tests/AirPlayStallJudgementTests.swift +52 -0
  184. package/ios/Tests/BrowserPathHelperTests.swift +28 -0
  185. package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
  186. package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
  187. package/ios/Tests/EndOfTrackJudgementTests.swift +35 -0
  188. package/ios/Tests/JsonModelsTests.swift +58 -0
  189. package/ios/Tests/LRUCacheTests.swift +111 -0
  190. package/ios/Tests/LoadSeekCoordinatorTests.swift +40 -7
  191. package/ios/Tests/MediaIntentCriteriaTests.swift +170 -0
  192. package/ios/Tests/MediaLoaderAssetOptionsTests.swift +46 -0
  193. package/ios/Tests/MediaLoaderTests.swift +32 -20
  194. package/ios/Tests/MediaResolveTests.swift +126 -0
  195. package/ios/Tests/OnceValueTests.swift +79 -0
  196. package/ios/Tests/PlaybackCoordinatorTests.swift +787 -13
  197. package/ios/Tests/PlaybackErrorHandlerTests.swift +383 -47
  198. package/ios/Tests/PlaybackStateMachineTests.swift +33 -8
  199. package/ios/Tests/PlaybackStateStoreTests.swift +48 -0
  200. package/ios/Tests/PlaybackTimerTests.swift +55 -0
  201. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  202. package/ios/Tests/PlayingStateManagerTests.swift +57 -0
  203. package/ios/Tests/QueueManagerTests.swift +195 -18
  204. package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
  205. package/ios/Tests/RemoteCommandTests.swift +67 -0
  206. package/ios/Tests/ResolvedTrackCopyingTests.swift +15 -8
  207. package/ios/Tests/RetryManagerTests.swift +178 -0
  208. package/ios/Tests/SearchDrillInTests.swift +63 -0
  209. package/ios/Tests/SectionScopeTests.swift +82 -0
  210. package/ios/Tests/SleepTimerManagerTests.swift +107 -0
  211. package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
  212. package/ios/Tests/Support/TestTypes.swift +61 -20
  213. package/ios/Tests/TabBarEntriesTests.swift +71 -0
  214. package/ios/Tests/TrackSelectorTests.swift +15 -15
  215. package/ios/TrackPlayer.swift +385 -58
  216. package/ios/TrackPlayerCallbacks.swift +5 -44
  217. package/ios/TrackSelector.swift +5 -5
  218. package/ios/Util/ArtworkImageFetcher.swift +4 -2
  219. package/ios/Util/LRUCache.swift +4 -1
  220. package/ios/Util/NetworkMonitor.swift +7 -3
  221. package/ios/Util/OnceValue.swift +34 -0
  222. package/lib/commonjs/AudioBrowser.js +0 -11
  223. package/lib/commonjs/AudioBrowser.js.map +1 -1
  224. package/lib/commonjs/CLAUDE.md +52 -0
  225. package/lib/commonjs/features/browser-config.js +227 -0
  226. package/lib/commonjs/features/browser-config.js.map +1 -0
  227. package/lib/commonjs/features/browser.js +60 -159
  228. package/lib/commonjs/features/browser.js.map +1 -1
  229. package/lib/commonjs/features/carConnection.js +42 -0
  230. package/lib/commonjs/features/carConnection.js.map +1 -0
  231. package/lib/commonjs/features/equalizer.js +1 -1
  232. package/lib/commonjs/features/errors.js +37 -5
  233. package/lib/commonjs/features/errors.js.map +1 -1
  234. package/lib/commonjs/features/favorites.js +1 -1
  235. package/lib/commonjs/features/gate.js +164 -0
  236. package/lib/commonjs/features/gate.js.map +1 -0
  237. package/lib/commonjs/features/index.js +22 -0
  238. package/lib/commonjs/features/index.js.map +1 -1
  239. package/lib/commonjs/features/metadata.js +20 -3
  240. package/lib/commonjs/features/metadata.js.map +1 -1
  241. package/lib/commonjs/features/network.js +1 -1
  242. package/lib/commonjs/features/nowPlaying.js +36 -1
  243. package/lib/commonjs/features/nowPlaying.js.map +1 -1
  244. package/lib/commonjs/features/output.js +37 -23
  245. package/lib/commonjs/features/output.js.map +1 -1
  246. package/lib/commonjs/features/playback/controls.js +9 -0
  247. package/lib/commonjs/features/playback/controls.js.map +1 -1
  248. package/lib/commonjs/features/playback/index.js +11 -0
  249. package/lib/commonjs/features/playback/index.js.map +1 -1
  250. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  251. package/lib/commonjs/features/playback/playing.js +1 -1
  252. package/lib/commonjs/features/playback/progress.js +29 -13
  253. package/lib/commonjs/features/playback/progress.js.map +1 -1
  254. package/lib/commonjs/features/playback/state.js +1 -1
  255. package/lib/commonjs/features/playback/trackPlaybackTime.js +86 -0
  256. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -0
  257. package/lib/commonjs/features/playback/volume.js +2 -2
  258. package/lib/commonjs/features/player/options.js +79 -29
  259. package/lib/commonjs/features/player/options.js.map +1 -1
  260. package/lib/commonjs/features/player/setup.js +256 -3
  261. package/lib/commonjs/features/player/setup.js.map +1 -1
  262. package/lib/commonjs/features/player/validateOptions.js +21 -0
  263. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  264. package/lib/commonjs/features/queue/activeTrack.js +1 -1
  265. package/lib/commonjs/features/queue/queue.js +12 -5
  266. package/lib/commonjs/features/queue/queue.js.map +1 -1
  267. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  268. package/lib/commonjs/features/queue/shuffle.js +1 -1
  269. package/lib/commonjs/features/remoteControls.js +14 -104
  270. package/lib/commonjs/features/remoteControls.js.map +1 -1
  271. package/lib/commonjs/features/sleepTimer.js +22 -5
  272. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  273. package/lib/commonjs/native.js +5 -0
  274. package/lib/commonjs/native.js.map +1 -1
  275. package/lib/commonjs/native.web.js +4 -1
  276. package/lib/commonjs/native.web.js.map +1 -1
  277. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  278. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  279. package/lib/commonjs/utils/LazyNativeEmitter.js +8 -8
  280. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  281. package/lib/commonjs/utils/NativeUpdatedValue.js +0 -3
  282. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  283. package/lib/commonjs/utils/artwork.js +31 -0
  284. package/lib/commonjs/utils/artwork.js.map +1 -0
  285. package/lib/commonjs/web/NativeAudioBrowser.js +302 -125
  286. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  287. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +71 -0
  288. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  289. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +34 -0
  290. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  291. package/lib/commonjs/web/TrackPlayer/Player.js +90 -26
  292. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  293. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +21 -0
  294. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  295. package/lib/commonjs/web/TrackPlayer/QueueManager.js +273 -0
  296. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -0
  297. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +158 -0
  298. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -0
  299. package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
  300. package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
  301. package/lib/commonjs/web/TrackPlayer/SleepTimer.js +34 -3
  302. package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
  303. package/lib/commonjs/web/TrackPlayer/VolumeFader.js +75 -0
  304. package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +1 -0
  305. package/lib/commonjs/web/TrackPlayer/index.js +13 -5
  306. package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
  307. package/lib/commonjs/web/browser/BrowserManager.js +176 -50
  308. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  309. package/lib/commonjs/web/browser/NavigationErrorManager.js +3 -1
  310. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  311. package/lib/commonjs/web/browser/SearchManager.js +11 -9
  312. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  313. package/lib/commonjs/web/browser/assertBrowsePageShape.js +22 -0
  314. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -0
  315. package/lib/commonjs/web/browser/classifyTrackNavigation.js +41 -0
  316. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -0
  317. package/lib/commonjs/web/browser/parseSearchResponse.js +20 -0
  318. package/lib/commonjs/web/browser/parseSearchResponse.js.map +1 -0
  319. package/lib/commonjs/web/http/HttpClient.js +4 -23
  320. package/lib/commonjs/web/http/HttpClient.js.map +1 -1
  321. package/lib/commonjs/web/http/RequestConfigBuilder.js +107 -58
  322. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  323. package/lib/commonjs/web/playbackErrorKind.js +57 -0
  324. package/lib/commonjs/web/playbackErrorKind.js.map +1 -0
  325. package/lib/commonjs/web/player/MediaSessionManager.js +121 -0
  326. package/lib/commonjs/web/player/MediaSessionManager.js.map +1 -0
  327. package/lib/commonjs/web/player/NowPlayingManager.js +6 -5
  328. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  329. package/lib/commonjs/web/player/OptionsManager.js +9 -17
  330. package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
  331. package/lib/commonjs/web/player/RemoteCommandController.js +96 -0
  332. package/lib/commonjs/web/player/RemoteCommandController.js.map +1 -0
  333. package/lib/module/AudioBrowser.js +1 -1
  334. package/lib/module/AudioBrowser.js.map +1 -1
  335. package/lib/module/CLAUDE.md +52 -0
  336. package/lib/module/features/browser-config.js +221 -0
  337. package/lib/module/features/browser-config.js.map +1 -0
  338. package/lib/module/features/browser.js +54 -157
  339. package/lib/module/features/browser.js.map +1 -1
  340. package/lib/module/features/carConnection.js +37 -0
  341. package/lib/module/features/carConnection.js.map +1 -0
  342. package/lib/module/features/equalizer.js +1 -1
  343. package/lib/module/features/errors.js +37 -5
  344. package/lib/module/features/errors.js.map +1 -1
  345. package/lib/module/features/favorites.js +1 -1
  346. package/lib/module/features/gate.js +159 -0
  347. package/lib/module/features/gate.js.map +1 -0
  348. package/lib/module/features/index.js +2 -0
  349. package/lib/module/features/index.js.map +1 -1
  350. package/lib/module/features/metadata.js +20 -3
  351. package/lib/module/features/metadata.js.map +1 -1
  352. package/lib/module/features/network.js +1 -1
  353. package/lib/module/features/nowPlaying.js +34 -1
  354. package/lib/module/features/nowPlaying.js.map +1 -1
  355. package/lib/module/features/output.js +32 -19
  356. package/lib/module/features/output.js.map +1 -1
  357. package/lib/module/features/playback/controls.js +8 -0
  358. package/lib/module/features/playback/controls.js.map +1 -1
  359. package/lib/module/features/playback/index.js +1 -0
  360. package/lib/module/features/playback/index.js.map +1 -1
  361. package/lib/module/features/playback/playWhenReady.js +1 -1
  362. package/lib/module/features/playback/playing.js +1 -1
  363. package/lib/module/features/playback/progress.js +29 -13
  364. package/lib/module/features/playback/progress.js.map +1 -1
  365. package/lib/module/features/playback/state.js +1 -1
  366. package/lib/module/features/playback/trackPlaybackTime.js +83 -0
  367. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -0
  368. package/lib/module/features/playback/volume.js +2 -2
  369. package/lib/module/features/player/options.js +79 -29
  370. package/lib/module/features/player/options.js.map +1 -1
  371. package/lib/module/features/player/setup.js +256 -3
  372. package/lib/module/features/player/setup.js.map +1 -1
  373. package/lib/module/features/player/validateOptions.js +17 -0
  374. package/lib/module/features/player/validateOptions.js.map +1 -0
  375. package/lib/module/features/queue/activeTrack.js +1 -1
  376. package/lib/module/features/queue/queue.js +12 -5
  377. package/lib/module/features/queue/queue.js.map +1 -1
  378. package/lib/module/features/queue/repeatMode.js +1 -1
  379. package/lib/module/features/queue/shuffle.js +1 -1
  380. package/lib/module/features/remoteControls.js +13 -103
  381. package/lib/module/features/remoteControls.js.map +1 -1
  382. package/lib/module/features/sleepTimer.js +22 -5
  383. package/lib/module/features/sleepTimer.js.map +1 -1
  384. package/lib/module/native.js +5 -0
  385. package/lib/module/native.js.map +1 -1
  386. package/lib/module/native.web.js +4 -1
  387. package/lib/module/native.web.js.map +1 -1
  388. package/lib/module/test-utils/reactNativeStub.js +58 -0
  389. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  390. package/lib/module/utils/LazyNativeEmitter.js +8 -8
  391. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  392. package/lib/module/utils/NativeUpdatedValue.js +0 -3
  393. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  394. package/lib/module/utils/artwork.js +26 -0
  395. package/lib/module/utils/artwork.js.map +1 -0
  396. package/lib/module/web/NativeAudioBrowser.js +303 -126
  397. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  398. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +67 -0
  399. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  400. package/lib/module/web/TrackPlayer/PlaybackTimer.js +29 -0
  401. package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  402. package/lib/module/web/TrackPlayer/Player.js +90 -26
  403. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  404. package/lib/module/web/TrackPlayer/PlayingStateFactory.js +17 -0
  405. package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  406. package/lib/module/web/TrackPlayer/QueueManager.js +269 -0
  407. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -0
  408. package/lib/module/web/TrackPlayer/QueuePlayer.js +154 -0
  409. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -0
  410. package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
  411. package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
  412. package/lib/module/web/TrackPlayer/SleepTimer.js +34 -3
  413. package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
  414. package/lib/module/web/TrackPlayer/VolumeFader.js +70 -0
  415. package/lib/module/web/TrackPlayer/VolumeFader.js.map +1 -0
  416. package/lib/module/web/TrackPlayer/index.js +2 -1
  417. package/lib/module/web/TrackPlayer/index.js.map +1 -1
  418. package/lib/module/web/browser/BrowserManager.js +176 -50
  419. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  420. package/lib/module/web/browser/NavigationErrorManager.js +3 -1
  421. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  422. package/lib/module/web/browser/SearchManager.js +11 -9
  423. package/lib/module/web/browser/SearchManager.js.map +1 -1
  424. package/lib/module/web/browser/assertBrowsePageShape.js +18 -0
  425. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -0
  426. package/lib/module/web/browser/classifyTrackNavigation.js +38 -0
  427. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -0
  428. package/lib/module/web/browser/parseSearchResponse.js +16 -0
  429. package/lib/module/web/browser/parseSearchResponse.js.map +1 -0
  430. package/lib/module/web/http/HttpClient.js +4 -23
  431. package/lib/module/web/http/HttpClient.js.map +1 -1
  432. package/lib/module/web/http/RequestConfigBuilder.js +107 -58
  433. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  434. package/lib/module/web/playbackErrorKind.js +52 -0
  435. package/lib/module/web/playbackErrorKind.js.map +1 -0
  436. package/lib/module/web/player/MediaSessionManager.js +116 -0
  437. package/lib/module/web/player/MediaSessionManager.js.map +1 -0
  438. package/lib/module/web/player/NowPlayingManager.js +7 -5
  439. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  440. package/lib/module/web/player/OptionsManager.js +9 -17
  441. package/lib/module/web/player/OptionsManager.js.map +1 -1
  442. package/lib/module/web/player/RemoteCommandController.js +92 -0
  443. package/lib/module/web/player/RemoteCommandController.js.map +1 -0
  444. package/lib/typescript/src/AudioBrowser.d.ts +0 -1
  445. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  446. package/lib/typescript/src/features/browser-config.d.ts +12 -0
  447. package/lib/typescript/src/features/browser-config.d.ts.map +1 -0
  448. package/lib/typescript/src/features/browser.d.ts +40 -1
  449. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  450. package/lib/typescript/src/features/carConnection.d.ts +20 -0
  451. package/lib/typescript/src/features/carConnection.d.ts.map +1 -0
  452. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  453. package/lib/typescript/src/features/errors.d.ts +61 -6
  454. package/lib/typescript/src/features/errors.d.ts.map +1 -1
  455. package/lib/typescript/src/features/favorites.d.ts +1 -1
  456. package/lib/typescript/src/features/gate.d.ts +93 -0
  457. package/lib/typescript/src/features/gate.d.ts.map +1 -0
  458. package/lib/typescript/src/features/index.d.ts +2 -0
  459. package/lib/typescript/src/features/index.d.ts.map +1 -1
  460. package/lib/typescript/src/features/metadata.d.ts +26 -7
  461. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  462. package/lib/typescript/src/features/network.d.ts +1 -1
  463. package/lib/typescript/src/features/nowPlaying.d.ts +28 -1
  464. package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
  465. package/lib/typescript/src/features/output.d.ts +28 -18
  466. package/lib/typescript/src/features/output.d.ts.map +1 -1
  467. package/lib/typescript/src/features/playback/controls.d.ts +5 -0
  468. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  469. package/lib/typescript/src/features/playback/index.d.ts +1 -0
  470. package/lib/typescript/src/features/playback/index.d.ts.map +1 -1
  471. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  472. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  473. package/lib/typescript/src/features/playback/progress.d.ts +4 -1
  474. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  475. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  476. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +34 -0
  477. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -0
  478. package/lib/typescript/src/features/playback/volume.d.ts +2 -2
  479. package/lib/typescript/src/features/player/options.d.ts +176 -127
  480. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  481. package/lib/typescript/src/features/player/setup.d.ts +216 -167
  482. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  483. package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
  484. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  485. package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
  486. package/lib/typescript/src/features/queue/queue.d.ts +11 -4
  487. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  488. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  489. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  490. package/lib/typescript/src/features/remoteControls.d.ts +23 -35
  491. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  492. package/lib/typescript/src/features/sleepTimer.d.ts +19 -4
  493. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  494. package/lib/typescript/src/index.d.ts.map +1 -1
  495. package/lib/typescript/src/native.d.ts +1 -2
  496. package/lib/typescript/src/native.d.ts.map +1 -1
  497. package/lib/typescript/src/specs/audio-browser.nitro.d.ts +17 -225
  498. package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
  499. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  500. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  501. package/lib/typescript/src/types/browser-native.d.ts +9 -3
  502. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  503. package/lib/typescript/src/types/browser-nodes.d.ts +145 -13
  504. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  505. package/lib/typescript/src/types/browser.d.ts +349 -85
  506. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  507. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -6
  508. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  509. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +0 -2
  510. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  511. package/lib/typescript/src/utils/artwork.d.ts +19 -0
  512. package/lib/typescript/src/utils/artwork.d.ts.map +1 -0
  513. package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
  514. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +4 -2
  515. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +24 -10
  516. package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
  517. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -20
  518. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +33 -1
  519. package/nitrogen/generated/android/c++/JArtworkVariants.hpp +61 -0
  520. package/nitrogen/generated/android/c++/JBrowseResult.hpp +5 -0
  521. package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
  522. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +6 -0
  523. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +58 -0
  524. package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +58 -0
  525. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +100 -0
  526. package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +3 -2
  527. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +112 -0
  528. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +121 -0
  529. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +123 -0
  530. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +133 -0
  531. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +121 -0
  532. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +139 -0
  533. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +6 -0
  534. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +134 -0
  535. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -0
  536. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +6 -0
  537. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +131 -0
  538. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +6 -0
  539. package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +79 -0
  540. package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +0 -10
  541. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +14 -9
  542. package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +80 -0
  543. package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +2 -0
  544. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +6 -0
  545. package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +2 -0
  546. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
  547. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
  548. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +6 -0
  549. package/nitrogen/generated/android/c++/JGate.hpp +62 -0
  550. package/nitrogen/generated/android/c++/JGateDecision.hpp +64 -0
  551. package/nitrogen/generated/android/c++/{JThumbsRating.hpp → JGateEvent.hpp} +16 -15
  552. package/nitrogen/generated/android/c++/JGateReason.hpp +58 -0
  553. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +247 -223
  554. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +26 -23
  555. package/nitrogen/generated/android/c++/JIOSOptions.hpp +95 -0
  556. package/nitrogen/generated/android/c++/JImageRowItem.hpp +33 -3
  557. package/nitrogen/generated/android/c++/JMediaReference.hpp +58 -0
  558. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +33 -1
  559. package/nitrogen/generated/android/c++/{JPartialAndroidSetupPlayerOptions.hpp → JNativeAndroidSetupOptions.hpp} +10 -10
  560. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +61 -28
  561. package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +74 -0
  562. package/nitrogen/generated/android/c++/{JPartialIOSSetupPlayerOptions.hpp → JNativeIOSSetupOptions.hpp} +10 -10
  563. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +20 -9
  564. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +182 -0
  565. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +21 -24
  566. package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +6 -0
  567. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -20
  568. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +96 -0
  569. package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +3 -17
  570. package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +7 -3
  571. package/nitrogen/generated/android/c++/JOptions.hpp +19 -14
  572. package/nitrogen/generated/android/c++/{JIosOutput.hpp → JOutput.hpp} +15 -15
  573. package/nitrogen/generated/android/c++/JOutputType.hpp +82 -0
  574. package/nitrogen/generated/android/c++/JPlayback.hpp +2 -0
  575. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +6 -0
  576. package/nitrogen/generated/android/c++/JPlaybackError.hpp +18 -3
  577. package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +2 -0
  578. package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +76 -0
  579. package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
  580. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +11 -5
  581. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +20 -20
  582. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  583. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +23 -5
  584. package/nitrogen/generated/android/c++/JRetryConfig.hpp +11 -7
  585. package/nitrogen/generated/android/c++/JSearchMode.hpp +27 -9
  586. package/nitrogen/generated/android/c++/JSearchParams.hpp +9 -3
  587. package/nitrogen/generated/android/c++/JStallReason.hpp +58 -0
  588. package/nitrogen/generated/android/c++/JTrack.hpp +23 -5
  589. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +6 -0
  590. package/nitrogen/generated/android/c++/JTrackRequest.hpp +93 -0
  591. package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +14 -1
  592. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +26 -0
  593. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +71 -0
  594. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  595. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  596. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +41 -0
  597. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +121 -0
  598. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +26 -0
  599. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +70 -0
  600. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
  601. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -9
  602. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +10 -4
  603. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkVariants.kt +41 -0
  604. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
  605. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{HeartRating.kt → FavoriteConfig.kt} +6 -6
  606. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RatingType.kt → FavoritesMatchMode.kt} +5 -10
  607. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +50 -0
  608. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
  609. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +80 -0
  610. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +80 -0
  611. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +80 -0
  612. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +80 -0
  613. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +80 -0
  614. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +80 -0
  615. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +80 -0
  616. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt} +12 -12
  617. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_RemoteSetRatingEvent.kt → Func_void_GateEvent.kt} +9 -9
  618. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_IosOutput.kt → Func_void_Output.kt} +9 -9
  619. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
  620. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ThumbsRating.kt → Gate.kt} +9 -6
  621. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PercentageRating.kt → GateDecision.kt} +9 -6
  622. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{StarRating.kt → GateEvent.kt} +6 -6
  623. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → GateReason.kt} +5 -8
  624. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +104 -110
  625. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +44 -0
  626. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +24 -3
  627. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +23 -0
  628. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +10 -4
  629. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialAndroidSetupPlayerOptions.kt → NativeAndroidSetupOptions.kt} +5 -5
  630. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +18 -6
  631. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RemoteSetRatingEvent.kt → NativeGateRequest.kt} +12 -6
  632. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialIOSSetupPlayerOptions.kt → NativeIOSSetupOptions.kt} +5 -5
  633. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
  634. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +66 -0
  635. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
  636. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +3 -1
  637. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -9
  638. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +44 -0
  639. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +3 -6
  640. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +6 -3
  641. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
  642. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{IosOutput.kt → Output.kt} +6 -6
  643. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +31 -0
  644. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +12 -3
  645. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +29 -0
  646. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
  647. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  648. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +26 -0
  649. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  650. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -3
  651. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +7 -4
  652. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +12 -6
  653. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +6 -3
  654. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +23 -0
  655. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +12 -3
  656. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +44 -0
  657. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +7 -4
  658. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +53 -0
  659. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  660. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +65 -0
  661. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +53 -0
  662. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +139 -28
  663. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +1126 -490
  664. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +66 -45
  665. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +178 -120
  666. package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
  667. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -22
  668. package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +95 -1
  669. package/nitrogen/generated/ios/swift/ArtworkVariants.swift +34 -0
  670. package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
  671. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +29 -0
  672. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +40 -0
  673. package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +67 -0
  674. package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +17 -4
  675. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +54 -0
  676. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +69 -0
  677. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +54 -0
  678. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +60 -0
  679. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +60 -0
  680. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +61 -0
  681. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +61 -0
  682. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +77 -0
  683. package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +46 -0
  684. package/nitrogen/generated/ios/swift/{Func_void_RemoteSetRatingEvent.swift → Func_void_GateEvent.swift} +10 -10
  685. package/nitrogen/generated/ios/swift/{Func_void_IosOutput.swift → Func_void_Output.swift} +10 -10
  686. package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +46 -0
  687. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  688. package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +46 -0
  689. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
  690. package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift → Func_void_std__shared_ptr_Promise_GateDecision__.swift} +17 -17
  691. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +66 -0
  692. package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +81 -0
  693. package/nitrogen/generated/ios/swift/Gate.swift +47 -0
  694. package/nitrogen/generated/ios/swift/GateDecision.swift +40 -0
  695. package/nitrogen/generated/ios/swift/GateEvent.swift +29 -0
  696. package/nitrogen/generated/ios/swift/GateReason.swift +40 -0
  697. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +21 -15
  698. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +232 -199
  699. package/nitrogen/generated/ios/swift/IOSOptions.swift +51 -0
  700. package/nitrogen/generated/ios/swift/ImageRowItem.swift +121 -2
  701. package/nitrogen/generated/ios/swift/MediaReference.swift +40 -0
  702. package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +107 -1
  703. package/nitrogen/generated/ios/swift/{PartialAndroidSetupPlayerOptions.swift → NativeAndroidSetupOptions.swift} +5 -5
  704. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +161 -22
  705. package/nitrogen/generated/ios/swift/NativeGateRequest.swift +58 -0
  706. package/nitrogen/generated/ios/swift/{PartialIOSSetupPlayerOptions.swift → NativeIOSSetupOptions.swift} +5 -5
  707. package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
  708. package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +206 -0
  709. package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +12 -25
  710. package/nitrogen/generated/ios/swift/NavigationErrorType.swift +8 -0
  711. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -41
  712. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +90 -0
  713. package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +1 -48
  714. package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +19 -1
  715. package/nitrogen/generated/ios/swift/Options.swift +10 -4
  716. package/nitrogen/generated/ios/swift/{IosOutput.swift → Output.swift} +7 -7
  717. package/nitrogen/generated/ios/swift/OutputType.swift +72 -0
  718. package/nitrogen/generated/ios/swift/PlaybackError.swift +43 -2
  719. package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +64 -0
  720. package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
  721. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +27 -8
  722. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +10 -10
  723. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  724. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +74 -8
  725. package/nitrogen/generated/ios/swift/RetryConfig.swift +35 -4
  726. package/nitrogen/generated/ios/swift/SearchMode.swift +36 -12
  727. package/nitrogen/generated/ios/swift/SearchParams.swift +7 -2
  728. package/nitrogen/generated/ios/swift/StallReason.swift +40 -0
  729. package/nitrogen/generated/ios/swift/Track.swift +74 -8
  730. package/nitrogen/generated/ios/swift/TrackRequest.swift +106 -0
  731. package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +60 -1
  732. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +18 -0
  733. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  734. package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +19 -0
  735. package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +18 -0
  736. package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
  737. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -19
  738. package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +10 -2
  739. package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +87 -0
  740. package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
  741. package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +0 -2
  742. package/nitrogen/generated/shared/c++/{StarRating.hpp → FavoriteConfig.hpp} +18 -17
  743. package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +76 -0
  744. package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +110 -0
  745. package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +6 -5
  746. package/nitrogen/generated/shared/c++/{PercentageRating.hpp → Gate.hpp} +21 -16
  747. package/nitrogen/generated/shared/c++/{HeartRating.hpp → GateDecision.hpp} +23 -17
  748. package/nitrogen/generated/shared/c++/{ThumbsRating.hpp → GateEvent.hpp} +18 -17
  749. package/nitrogen/generated/shared/c++/GateReason.hpp +76 -0
  750. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +22 -19
  751. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +45 -36
  752. package/nitrogen/generated/shared/c++/IOSOptions.hpp +91 -0
  753. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +33 -2
  754. package/nitrogen/generated/shared/c++/MediaReference.hpp +76 -0
  755. package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +10 -2
  756. package/nitrogen/generated/shared/c++/{PartialAndroidSetupPlayerOptions.hpp → NativeAndroidSetupOptions.hpp} +11 -11
  757. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +31 -15
  758. package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +97 -0
  759. package/nitrogen/generated/shared/c++/{PartialIOSSetupPlayerOptions.hpp → NativeIOSSetupOptions.hpp} +11 -11
  760. package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
  761. package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +138 -0
  762. package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +9 -7
  763. package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +8 -0
  764. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -19
  765. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +92 -0
  766. package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +3 -19
  767. package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +6 -2
  768. package/nitrogen/generated/shared/c++/Options.hpp +8 -8
  769. package/nitrogen/generated/shared/c++/{IosOutput.hpp → Output.hpp} +18 -18
  770. package/nitrogen/generated/shared/c++/OutputType.hpp +108 -0
  771. package/nitrogen/generated/shared/c++/PlaybackError.hpp +18 -3
  772. package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +100 -0
  773. package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
  774. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +9 -6
  775. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +25 -25
  776. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  777. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +24 -5
  778. package/nitrogen/generated/shared/c++/RetryConfig.hpp +10 -6
  779. package/nitrogen/generated/shared/c++/SearchMode.hpp +39 -15
  780. package/nitrogen/generated/shared/c++/SearchParams.hpp +9 -2
  781. package/nitrogen/generated/shared/c++/StallReason.hpp +76 -0
  782. package/nitrogen/generated/shared/c++/Track.hpp +24 -5
  783. package/nitrogen/generated/shared/c++/TrackRequest.hpp +93 -0
  784. package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +6 -2
  785. package/package.json +20 -8
  786. package/src/AudioBrowser.ts +1 -1
  787. package/src/CLAUDE.md +52 -0
  788. package/src/codegen-no-config-variant.test.ts +70 -0
  789. package/src/features/battery.test.ts +79 -0
  790. package/src/features/browser-config.test.ts +110 -0
  791. package/src/features/browser-config.ts +293 -0
  792. package/src/features/browser.test.ts +19 -0
  793. package/src/features/browser.ts +59 -202
  794. package/src/features/carConnection.ts +36 -0
  795. package/src/features/equalizer.ts +1 -1
  796. package/src/features/errors.ts +71 -5
  797. package/src/features/favorites.ts +1 -1
  798. package/src/features/gate.test.ts +72 -0
  799. package/src/features/gate.ts +170 -0
  800. package/src/features/hook-wiring.test.ts +254 -0
  801. package/src/features/index.ts +2 -0
  802. package/src/features/metadata.ts +26 -15
  803. package/src/features/network.ts +1 -1
  804. package/src/features/nowPlaying.ts +37 -1
  805. package/src/features/output.ts +35 -22
  806. package/src/features/playback/controls.ts +8 -0
  807. package/src/features/playback/index.ts +1 -0
  808. package/src/features/playback/playWhenReady.ts +1 -1
  809. package/src/features/playback/playing.ts +1 -1
  810. package/src/features/playback/progress.test.ts +169 -0
  811. package/src/features/playback/progress.ts +32 -13
  812. package/src/features/playback/state.ts +1 -1
  813. package/src/features/playback/trackPlaybackTime.test.ts +76 -0
  814. package/src/features/playback/trackPlaybackTime.ts +101 -0
  815. package/src/features/playback/volume.ts +2 -2
  816. package/src/features/player/options.test.ts +38 -0
  817. package/src/features/player/options.ts +217 -110
  818. package/src/features/player/setup.test.ts +125 -0
  819. package/src/features/player/setup.ts +400 -175
  820. package/src/features/player/validateOptions.ts +19 -0
  821. package/src/features/queue/activeTrack.ts +1 -1
  822. package/src/features/queue/queue.test.ts +36 -0
  823. package/src/features/queue/queue.ts +17 -6
  824. package/src/features/queue/repeatMode.ts +1 -1
  825. package/src/features/queue/shuffle.ts +1 -1
  826. package/src/features/remoteControls.ts +13 -115
  827. package/src/features/sleepTimer.test.ts +172 -0
  828. package/src/features/sleepTimer.ts +32 -5
  829. package/src/native.ts +5 -0
  830. package/src/native.web.ts +4 -1
  831. package/src/public-surface.test.ts +107 -0
  832. package/src/specs/audio-browser.nitro.ts +132 -42
  833. package/src/test-utils/reactNativeStub.ts +60 -0
  834. package/src/types/browser-native.ts +19 -6
  835. package/src/types/browser-nodes.ts +160 -13
  836. package/src/types/browser.ts +377 -89
  837. package/src/utils/LazyNativeEmitter.ts +9 -8
  838. package/src/utils/NativeUpdatedValue.ts +0 -2
  839. package/src/utils/artwork.ts +30 -0
  840. package/src/utils/useDebug.test.ts +182 -0
  841. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  842. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +301 -0
  843. package/src/web/NativeAudioBrowser.ts +366 -159
  844. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +139 -0
  845. package/src/web/TrackPlayer/PlaybackStateMachine.ts +82 -0
  846. package/src/web/TrackPlayer/PlaybackTimer.test.ts +67 -0
  847. package/src/web/TrackPlayer/PlaybackTimer.ts +33 -0
  848. package/src/web/TrackPlayer/Player.dispatch.test.ts +229 -0
  849. package/src/web/TrackPlayer/Player.load.test.ts +86 -0
  850. package/src/web/TrackPlayer/Player.ts +113 -39
  851. package/src/web/TrackPlayer/PlayingStateFactory.test.ts +51 -0
  852. package/src/web/TrackPlayer/PlayingStateFactory.ts +24 -0
  853. package/src/web/TrackPlayer/QueueManager.test.ts +289 -0
  854. package/src/web/TrackPlayer/QueueManager.ts +302 -0
  855. package/src/web/TrackPlayer/QueuePlayer.ts +188 -0
  856. package/src/web/TrackPlayer/RepeatMode.ts +1 -1
  857. package/src/web/TrackPlayer/SleepTimer.ts +38 -3
  858. package/src/web/TrackPlayer/VolumeFader.ts +74 -0
  859. package/src/web/TrackPlayer/index.ts +2 -1
  860. package/src/web/browser/BrowserManager.ts +231 -62
  861. package/src/web/browser/NavigationErrorManager.ts +3 -1
  862. package/src/web/browser/SearchManager.ts +11 -15
  863. package/src/web/browser/__tests__/layer-resolver.test.ts +69 -0
  864. package/src/web/browser/assertBrowsePageShape.test.ts +27 -0
  865. package/src/web/browser/assertBrowsePageShape.ts +27 -0
  866. package/src/web/browser/classifyTrackNavigation.test.ts +41 -0
  867. package/src/web/browser/classifyTrackNavigation.ts +35 -0
  868. package/src/web/browser/parseSearchResponse.test.ts +25 -0
  869. package/src/web/browser/parseSearchResponse.ts +15 -0
  870. package/src/web/http/HttpClient.ts +5 -35
  871. package/src/web/http/RequestConfigBuilder.test.ts +59 -0
  872. package/src/web/http/RequestConfigBuilder.ts +120 -63
  873. package/src/web/playbackErrorKind.test.ts +55 -0
  874. package/src/web/playbackErrorKind.ts +56 -0
  875. package/src/web/player/MediaSessionManager.test.ts +220 -0
  876. package/src/web/player/MediaSessionManager.ts +151 -0
  877. package/src/web/player/NowPlayingManager.test.ts +46 -0
  878. package/src/web/player/NowPlayingManager.ts +6 -5
  879. package/src/web/player/OptionsManager.ts +12 -18
  880. package/src/web/player/RemoteCommandController.test.ts +171 -0
  881. package/src/web/player/RemoteCommandController.ts +166 -0
  882. package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +0 -59
  883. package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +0 -72
  884. package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +0 -36
  885. package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +0 -146
  886. package/ios/CarPlay/Track+CarPlay.swift +0 -63
  887. package/ios/Model/MediaURL.swift +0 -67
  888. package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +0 -9
  889. package/ios/Player/PlaybackProgressUpdateManager.swift +0 -68
  890. package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +0 -166
  891. package/lib/commonjs/features/rating.js +0 -2
  892. package/lib/commonjs/features/rating.js.map +0 -1
  893. package/lib/commonjs/types/player.js +0 -2
  894. package/lib/commonjs/types/player.js.map +0 -1
  895. package/lib/commonjs/web/SimpleRouter.test.js +0 -174
  896. package/lib/commonjs/web/SimpleRouter.test.js.map +0 -1
  897. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +0 -293
  898. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  899. package/lib/module/features/rating.js +0 -2
  900. package/lib/module/features/rating.js.map +0 -1
  901. package/lib/module/types/player.js +0 -2
  902. package/lib/module/types/player.js.map +0 -1
  903. package/lib/module/web/SimpleRouter.test.js +0 -174
  904. package/lib/module/web/SimpleRouter.test.js.map +0 -1
  905. package/lib/module/web/TrackPlayer/PlaylistPlayer.js +0 -288
  906. package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  907. package/lib/typescript/src/features/rating.d.ts +0 -14
  908. package/lib/typescript/src/features/rating.d.ts.map +0 -1
  909. package/lib/typescript/src/native.web.d.ts +0 -3
  910. package/lib/typescript/src/native.web.d.ts.map +0 -1
  911. package/lib/typescript/src/types/player.d.ts +0 -64
  912. package/lib/typescript/src/types/player.d.ts.map +0 -1
  913. package/lib/typescript/src/web/NativeAudioBrowser.d.ts +0 -188
  914. package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +0 -1
  915. package/lib/typescript/src/web/SimpleRouter.d.ts +0 -40
  916. package/lib/typescript/src/web/SimpleRouter.d.ts.map +0 -1
  917. package/lib/typescript/src/web/SimpleRouter.test.d.ts +0 -2
  918. package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +0 -1
  919. package/lib/typescript/src/web/TrackPlayer/Event.d.ts +0 -11
  920. package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +0 -1
  921. package/lib/typescript/src/web/TrackPlayer/Player.d.ts +0 -59
  922. package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +0 -1
  923. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +0 -39
  924. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +0 -1
  925. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +0 -6
  926. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +0 -1
  927. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +0 -4
  928. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +0 -1
  929. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +0 -34
  930. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +0 -1
  931. package/lib/typescript/src/web/TrackPlayer/State.d.ts +0 -13
  932. package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +0 -1
  933. package/lib/typescript/src/web/TrackPlayer/index.d.ts +0 -5
  934. package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +0 -1
  935. package/lib/typescript/src/web/browser/BrowserManager.d.ts +0 -154
  936. package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +0 -1
  937. package/lib/typescript/src/web/browser/FavoriteManager.d.ts +0 -42
  938. package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +0 -1
  939. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +0 -40
  940. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +0 -1
  941. package/lib/typescript/src/web/browser/SearchManager.d.ts +0 -22
  942. package/lib/typescript/src/web/browser/SearchManager.d.ts.map +0 -1
  943. package/lib/typescript/src/web/http/HttpClient.d.ts +0 -25
  944. package/lib/typescript/src/web/http/HttpClient.d.ts.map +0 -1
  945. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +0 -98
  946. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +0 -1
  947. package/lib/typescript/src/web/player/NowPlayingManager.d.ts +0 -34
  948. package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +0 -1
  949. package/lib/typescript/src/web/player/OptionsManager.d.ts +0 -32
  950. package/lib/typescript/src/web/player/OptionsManager.d.ts.map +0 -1
  951. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +0 -69
  952. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +0 -1
  953. package/lib/typescript/src/web/util/shuffle.d.ts +0 -8
  954. package/lib/typescript/src/web/util/shuffle.d.ts.map +0 -1
  955. package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +0 -86
  956. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +0 -147
  957. package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +0 -80
  958. package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +0 -87
  959. package/nitrogen/generated/android/c++/JHeartRating.hpp +0 -57
  960. package/nitrogen/generated/android/c++/JIosOutputType.hpp +0 -85
  961. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  962. package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +0 -96
  963. package/nitrogen/generated/android/c++/JPercentageRating.hpp +0 -57
  964. package/nitrogen/generated/android/c++/JRatingType.hpp +0 -73
  965. package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +0 -66
  966. package/nitrogen/generated/android/c++/JStarRating.hpp +0 -57
  967. package/nitrogen/generated/android/c++/JUpdateOptions.hpp +0 -105
  968. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +0 -34
  969. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +0 -105
  970. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  971. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  972. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +0 -50
  973. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +0 -32
  974. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  975. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +0 -47
  976. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +0 -53
  977. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +0 -77
  978. package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +0 -119
  979. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +0 -68
  980. package/nitrogen/generated/ios/swift/HeartRating.swift +0 -29
  981. package/nitrogen/generated/ios/swift/IosOutputType.swift +0 -76
  982. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  983. package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +0 -101
  984. package/nitrogen/generated/ios/swift/PercentageRating.swift +0 -29
  985. package/nitrogen/generated/ios/swift/RatingType.swift +0 -60
  986. package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +0 -58
  987. package/nitrogen/generated/ios/swift/StarRating.swift +0 -29
  988. package/nitrogen/generated/ios/swift/ThumbsRating.swift +0 -29
  989. package/nitrogen/generated/ios/swift/UpdateOptions.swift +0 -143
  990. package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +0 -20
  991. package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +0 -109
  992. package/nitrogen/generated/shared/c++/IosOutputType.hpp +0 -112
  993. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
  994. package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +0 -104
  995. package/nitrogen/generated/shared/c++/RatingType.hpp +0 -96
  996. package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +0 -94
  997. package/nitrogen/generated/shared/c++/UpdateOptions.hpp +0 -111
  998. package/src/features/rating.ts +0 -17
  999. package/src/types/player.ts +0 -69
  1000. package/src/web/TrackPlayer/PlaylistPlayer.ts +0 -346
@@ -1,3 +1,4 @@
1
+ import AVFoundation
1
2
  import Foundation
2
3
  import Testing
3
4
 
@@ -7,21 +8,30 @@ import Testing
7
8
 
8
9
  @MainActor
9
10
  private final class MockRetryHandler: RetryHandling {
11
+ var isEnabled = true
12
+ var hasPlayed = false
10
13
  var retryableErrors: Set<Int> = []
11
14
  var attemptRetryResult = false
12
15
  var attemptRetryCallCount = 0
13
16
  var lastStartFromCurrentTime: Bool?
17
+ var lastHTTPStatusCode: Int??
14
18
  var resetCallCount = 0
15
19
 
16
- func isRetryable(_ error: Error?) -> Bool {
20
+ func isRetryable(_ error: Error?, httpStatusCode: Int?) -> Bool {
21
+ lastHTTPStatusCode = .some(httpStatusCode)
17
22
  guard let error else { return false }
18
23
  let code = (error as NSError).code
19
24
  return retryableErrors.contains(code)
20
25
  }
21
26
 
27
+ var attemptRetryDelayNs: UInt64 = 0
28
+
22
29
  func attemptRetry(startFromCurrentTime: Bool) async -> Bool {
23
30
  attemptRetryCallCount += 1
24
31
  lastStartFromCurrentTime = startFromCurrentTime
32
+ if attemptRetryDelayNs > 0 {
33
+ try? await Task.sleep(nanoseconds: attemptRetryDelayNs)
34
+ }
25
35
  return attemptRetryResult
26
36
  }
27
37
 
@@ -41,52 +51,180 @@ struct ClassifyTests {
41
51
  #expect(result == .notConnectedToInternet)
42
52
  }
43
53
 
44
- @Test func internetError_ignoresFallback() {
45
- let error = URLError(.notConnectedToInternet)
46
- let result = PlaybackErrorHandler.classify(error: error, fallback: .failedToLoadKeyValue)
47
- #expect(result == .notConnectedToInternet)
54
+ /// `.badURL` rather than `.timedOut`: timeouts now classify as
55
+ /// `.hostUnreachable`, so they no longer exercise the fallback path. Both
56
+ /// fallbacks are asserted to pin that `fallback` is plumbed, not hardcoded.
57
+ @Test func unclassifiableError_returnsFallback() {
58
+ #expect(
59
+ PlaybackErrorHandler.classify(error: URLError(.badURL), fallback: .playbackFailed)
60
+ == .playbackFailed,
61
+ )
62
+ #expect(
63
+ PlaybackErrorHandler.classify(error: URLError(.badURL), fallback: .failedToLoadKeyValue)
64
+ == .failedToLoadKeyValue,
65
+ )
48
66
  }
49
67
 
50
- @Test func otherError_returnsFallback_playbackFailed() {
51
- let error = URLError(.timedOut)
68
+ @Test(arguments: [
69
+ URLError.Code.timedOut,
70
+ .cannotFindHost,
71
+ // A TLS failure counts as unreachable: we never got a usable connection.
72
+ .secureConnectionFailed,
73
+ ])
74
+ func transportError_returnsHostUnreachable(code: URLError.Code) {
75
+ let result = PlaybackErrorHandler.classify(error: URLError(code), fallback: .playbackFailed)
76
+ #expect(result == .hostUnreachable)
77
+ }
78
+
79
+ /// AVFoundation hands back plain `NSError`s rather than bridged `URLError`s.
80
+ /// `as?` covers this, but only for `NSURLErrorDomain` — worth pinning, since
81
+ /// losing it would silently downgrade every real timeout to the fallback.
82
+ @Test func unbridgedUrlDomainError_isClassified() {
83
+ let error = NSError(domain: NSURLErrorDomain, code: URLError.Code.cannotFindHost.rawValue)
52
84
  let result = PlaybackErrorHandler.classify(error: error, fallback: .playbackFailed)
53
- #expect(result == .playbackFailed)
85
+ #expect(result == .hostUnreachable)
54
86
  }
55
87
 
56
- @Test func otherError_returnsFallback_failedToLoadKeyValue() {
57
- let error = URLError(.timedOut)
58
- let result = PlaybackErrorHandler.classify(error: error, fallback: .failedToLoadKeyValue)
59
- #expect(result == .failedToLoadKeyValue)
88
+ @Test func httpStatus_isSurfaced() {
89
+ let result = PlaybackErrorHandler.classify(
90
+ error: nil,
91
+ fallback: .failedToLoadKeyValue,
92
+ httpStatusCode: 404,
93
+ )
94
+ #expect(result == .httpStatus(404))
95
+ }
96
+
97
+ /// Also covers the nil-error path: with no error and no usable status, the
98
+ /// context fallback is all that is left.
99
+ @Test func successStatus_returnsFallback() {
100
+ #expect(
101
+ PlaybackErrorHandler.classify(
102
+ error: nil,
103
+ fallback: .failedToLoadKeyValue,
104
+ httpStatusCode: 200,
105
+ ) == .failedToLoadKeyValue,
106
+ )
107
+ #expect(
108
+ PlaybackErrorHandler.classify(error: nil, fallback: .playbackFailed) == .playbackFailed,
109
+ )
110
+ }
111
+
112
+ /// A transport failure means no response arrived, so a status left over in
113
+ /// the item's error log must not outrank it.
114
+ @Test func transportError_outranksHttpStatus() {
115
+ let result = PlaybackErrorHandler.classify(
116
+ error: URLError(.notConnectedToInternet),
117
+ fallback: .playbackFailed,
118
+ httpStatusCode: 500,
119
+ )
120
+ #expect(result == .notConnectedToInternet)
121
+ }
122
+
123
+ /// A `URLError` we can't classify must fall *through* to the status check,
124
+ /// not short-circuit to the fallback.
125
+ @Test func unclassifiableTransportError_stillUsesHttpStatus() {
126
+ let result = PlaybackErrorHandler.classify(
127
+ error: URLError(.badURL),
128
+ fallback: .playbackFailed,
129
+ httpStatusCode: 500,
130
+ )
131
+ #expect(result == .httpStatus(500))
132
+ }
133
+
134
+ /// The server's own answer is better evidence than "the decoder choked",
135
+ /// which is a downstream symptom of being handed an error page.
136
+ @Test func httpStatus_outranksUnplayableMedia() {
137
+ let error = NSError(
138
+ domain: AVFoundationErrorDomain,
139
+ code: AVError.fileFormatNotRecognized.rawValue,
140
+ )
141
+ let result = PlaybackErrorHandler.classify(
142
+ error: error,
143
+ fallback: .playbackFailed,
144
+ httpStatusCode: 404,
145
+ )
146
+ #expect(result == .httpStatus(404))
147
+ }
148
+
149
+ @Test func unplayableMedia_returnsTrackWasUnplayable() {
150
+ let error = NSError(
151
+ domain: AVFoundationErrorDomain,
152
+ code: AVError.fileFormatNotRecognized.rawValue,
153
+ )
154
+ let result = PlaybackErrorHandler.classify(error: error, fallback: .playbackFailed)
155
+ #expect(result == .trackWasUnplayable)
60
156
  }
61
157
 
62
- @Test func nilError_returnsFallback() {
63
- let result = PlaybackErrorHandler.classify(error: nil, fallback: .playbackFailed)
158
+ /// Being in `AVFoundationErrorDomain` is not enough — `AVError.unknown` is
159
+ /// in-domain but tells us nothing.
160
+ @Test func unrelatedAVError_returnsFallback() {
161
+ let error = NSError(domain: AVFoundationErrorDomain, code: AVError.unknown.rawValue)
162
+ let result = PlaybackErrorHandler.classify(error: error, fallback: .playbackFailed)
64
163
  #expect(result == .playbackFailed)
65
164
  }
66
165
  }
67
166
 
68
- // MARK: - PlaybackErrorContext
167
+ // MARK: - Error Log Status Extraction
69
168
 
70
- @Suite("PlaybackErrorContext")
169
+ /// The sole source of every `.httpStatus` classification in production.
170
+ @Suite("PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes:)")
71
171
  @MainActor
72
- struct ContextTests {
73
- @Test func playbackContext_startFromCurrentTimeTrue() {
74
- #expect(PlaybackErrorContext.playback.startFromCurrentTime == true)
172
+ struct ErrorLogStatusTests {
173
+ /// `errorStatusCode` carries negative OSStatus values in the same field.
174
+ @Test func skipsNonHttpStatusCodes() {
175
+ #expect(PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes: [-12939, -1008]) == nil)
176
+ #expect(PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes: [-12939, 404]) == 404)
75
177
  }
76
178
 
77
- @Test func playbackContext_fallbackIsPlaybackFailed() {
78
- #expect(PlaybackErrorContext.playback.fallbackError == .playbackFailed)
179
+ /// A live stream reconnects; older entries describe attempts that are no
180
+ /// longer why we are failing.
181
+ @Test func prefersTheNewestInRangeStatus() {
182
+ #expect(PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes: [500, 404]) == 404)
183
+ #expect(PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes: [404, 500, -12939]) == 500)
79
184
  }
80
185
 
81
- @Test func mediaLoadContext_startFromCurrentTimeFalse() {
82
- #expect(PlaybackErrorContext.mediaLoad.startFromCurrentTime == false)
186
+ @Test func nilWhenNothingQualifies() {
187
+ #expect(PlaybackErrorHandler.httpStatusCode(fromErrorStatusCodes: []) == nil)
83
188
  }
189
+ }
84
190
 
85
- @Test func mediaLoadContext_fallbackIsFailedToLoadKeyValue() {
86
- #expect(PlaybackErrorContext.mediaLoad.fallbackError == .failedToLoadKeyValue)
191
+ // MARK: - Cross-platform Kind Mapping
192
+
193
+ /// Only the HTTP table is tested: the `kind` switch is exhaustive with no
194
+ /// `default`, so the compiler already forces every new case to be classified
195
+ /// deliberately, and asserting the other arms would just retype the switch.
196
+ /// The status ranges are different — `404, 410` must precede `400...499`.
197
+ @Suite("PlaybackError.kind")
198
+ @MainActor
199
+ struct PlaybackErrorKindTests {
200
+ @Test func httpStatus_mapsToKind() {
201
+ // Not parameterized: the generated Nitro enum is not Sendable, so it
202
+ // cannot cross a `@Test(arguments:)` isolation boundary.
203
+ let expectations: [(Int, PlaybackErrorKind)] = [
204
+ // Ordering: these must not be swallowed by the 400...499 arm.
205
+ (404, .notFound),
206
+ (410, .notFound),
207
+ // Range edges.
208
+ (400, .rejected),
209
+ (499, .rejected),
210
+ (500, .serverError),
211
+ (599, .serverError),
212
+ (302, .unknown),
213
+ (600, .unknown),
214
+ ]
215
+ for (status, expected) in expectations {
216
+ #expect(
217
+ TrackPlayerError.PlaybackError.httpStatus(status).kind == expected,
218
+ "HTTP \(status)",
219
+ )
220
+ }
87
221
  }
88
222
  }
89
223
 
224
+ // `PlaybackErrorContext`'s two properties are asserted through `handleError`
225
+ // below — `lastStartFromCurrentTime` and the surfaced fallback — rather than
226
+ // read off the enum directly, so the tests exercise the path a caller uses.
227
+
90
228
  // MARK: - handleError
91
229
 
92
230
  @Suite("handleError")
@@ -119,16 +257,20 @@ struct HandleErrorTests {
119
257
  #expect(surfacedError == .notConnectedToInternet)
120
258
  }
121
259
 
260
+ /// Also pins that a completed retry task clears its own handle:
261
+ /// `evaluateSessionRelease` gates on `pendingRetryTask == nil`, so a stale
262
+ /// completed task blocked audio-session release for the rest of the track.
122
263
  @Test func retryableError_triggersRetry() async {
123
264
  let mock = MockRetryHandler()
124
265
  mock.retryableErrors = [URLError.Code.timedOut.rawValue]
125
266
  mock.attemptRetryResult = true
126
267
  let handler = PlaybackErrorHandler(retryHandler: mock)
127
- var surfacedError: TrackPlayerError.PlaybackError?
128
- handler.onError = { surfacedError = $0 }
268
+ var surfaced: [TrackPlayerError.PlaybackError] = []
269
+ handler.onError = { surfaced.append($0) }
129
270
 
130
271
  let error = URLError(.timedOut)
131
272
  handler.handleError(error, context: .playback)
273
+ #expect(handler.pendingRetryTask != nil)
132
274
 
133
275
  // Wait for the retry task to complete
134
276
  await handler.pendingRetryTask?.value
@@ -136,18 +278,159 @@ struct HandleErrorTests {
136
278
  #expect(mock.attemptRetryCallCount == 1)
137
279
  #expect(mock.lastStartFromCurrentTime == true)
138
280
  // Retry succeeded, no error surfaced
139
- #expect(surfacedError == nil)
281
+ #expect(surfaced.isEmpty)
282
+ #expect(handler.pendingRetryTask == nil)
140
283
  }
141
284
 
142
- @Test func retryExhausted_surfacesClassifiedError() async {
285
+ /// A cancelled retry must stay silent: track change / stop cancel the task,
286
+ /// but its tail still runs — attemptRetry returns false (wait cancelled) and
287
+ /// the unguarded body surfaced .errorOccurred over the new track / the
288
+ /// deliberate stop.
289
+ @Test func cancelledRetry_staysSilent() async throws {
290
+ let mock = MockRetryHandler()
291
+ mock.retryableErrors = [URLError.Code.timedOut.rawValue]
292
+ mock.attemptRetryResult = false
293
+ mock.attemptRetryDelayNs = 50_000_000
294
+ let handler = PlaybackErrorHandler(retryHandler: mock)
295
+ var surfaced: [TrackPlayerError.PlaybackError] = []
296
+ handler.onError = { surfaced.append($0) }
297
+
298
+ handler.handleError(URLError(.timedOut), context: .playback)
299
+ let task = handler.pendingRetryTask
300
+ handler.cancelPendingRetry()
301
+ #expect(handler.pendingRetryTask == nil)
302
+ await task?.value
303
+
304
+ #expect(surfaced.isEmpty)
305
+ }
306
+
307
+ /// A second failure while a retry is in flight cancels the first. The
308
+ /// superseded task's tail must not surface an error, and must not null out
309
+ /// the handle belonging to the retry that replaced it.
310
+ @Test func supersedingError_cancelsTheInFlightRetry() async {
143
311
  let mock = MockRetryHandler()
144
312
  mock.retryableErrors = [URLError.Code.timedOut.rawValue]
313
+ mock.attemptRetryResult = true
314
+ mock.attemptRetryDelayNs = 20_000_000
315
+ let handler = PlaybackErrorHandler(retryHandler: mock)
316
+ var surfaced: [TrackPlayerError.PlaybackError] = []
317
+ handler.onError = { surfaced.append($0) }
318
+
319
+ handler.handleError(URLError(.timedOut), context: .playback)
320
+ let first = handler.pendingRetryTask
321
+ handler.handleError(URLError(.timedOut), context: .playback)
322
+ let second = handler.pendingRetryTask
323
+
324
+ await first?.value
325
+ await second?.value
326
+
327
+ #expect(mock.attemptRetryCallCount == 2)
328
+ #expect(surfaced.isEmpty)
329
+ }
330
+
331
+ /// The advisory report must precede the retry outcome: it exists so UIs can
332
+ /// show the cause while the backoff runs, and it must carry the same
333
+ /// classification the terminal path would produce.
334
+ @Test func retryableError_reportsRetryingErrorBeforeTheRetryResolves() async {
335
+ let mock = MockRetryHandler()
336
+ mock.retryableErrors = [URLError.Code.cannotConnectToHost.rawValue]
337
+ mock.attemptRetryResult = true
338
+ let handler = PlaybackErrorHandler(retryHandler: mock)
339
+ var retrying: [TrackPlayerError.PlaybackError] = []
340
+ var surfaced: [TrackPlayerError.PlaybackError] = []
341
+ handler.onRetryingError = { retrying.append($0) }
342
+ handler.onError = { surfaced.append($0) }
343
+
344
+ handler.handleError(URLError(.cannotConnectToHost), context: .mediaLoad)
345
+
346
+ // Reported synchronously, before the retry task has run at all.
347
+ #expect(retrying == [.hostUnreachable])
348
+ #expect(mock.attemptRetryCallCount == 0)
349
+
350
+ await handler.pendingRetryTask?.value
351
+ // A successful retry surfaces nothing further.
352
+ #expect(surfaced.isEmpty)
353
+ }
354
+
355
+ /// Exhaustion after an advisory report still surfaces the terminal error —
356
+ /// the advisory does not consume it.
357
+ @Test func retryExhausted_reportsRetryingThenTerminal() async {
358
+ let mock = MockRetryHandler()
359
+ mock.retryableErrors = [URLError.Code.cannotConnectToHost.rawValue]
145
360
  mock.attemptRetryResult = false
146
361
  let handler = PlaybackErrorHandler(retryHandler: mock)
362
+ var retrying: [TrackPlayerError.PlaybackError] = []
363
+ var surfaced: [TrackPlayerError.PlaybackError] = []
364
+ handler.onRetryingError = { retrying.append($0) }
365
+ handler.onError = { surfaced.append($0) }
366
+
367
+ handler.handleError(URLError(.cannotConnectToHost), context: .mediaLoad)
368
+ await handler.pendingRetryTask?.value
369
+
370
+ #expect(retrying == [.hostUnreachable])
371
+ #expect(surfaced == [.hostUnreachable])
372
+ }
373
+
374
+ /// With the policy disabled every error is terminal: no advisory, no retry
375
+ /// task — the error surfaces synchronously even for a retryable class.
376
+ @Test func retryDisabled_skipsTheRetryingReport() {
377
+ let mock = MockRetryHandler()
378
+ mock.isEnabled = false
379
+ mock.retryableErrors = [URLError.Code.cannotConnectToHost.rawValue]
380
+ let handler = PlaybackErrorHandler(retryHandler: mock)
381
+ var retrying: [TrackPlayerError.PlaybackError] = []
147
382
  var surfacedError: TrackPlayerError.PlaybackError?
383
+ handler.onRetryingError = { retrying.append($0) }
148
384
  handler.onError = { surfacedError = $0 }
149
385
 
150
- let error = URLError(.timedOut)
386
+ handler.handleError(URLError(.cannotConnectToHost), context: .mediaLoad)
387
+
388
+ #expect(retrying.isEmpty)
389
+ #expect(handler.pendingRetryTask == nil)
390
+ #expect(surfacedError == .hostUnreachable)
391
+ #expect(mock.attemptRetryCallCount == 0)
392
+ }
393
+
394
+ /// The status reaches `classify` on the non-retryable path. Both call sites
395
+ /// forward it separately; dropping either one is invisible otherwise.
396
+ @Test func nonRetryable_forwardsHttpStatusCode() {
397
+ let mock = MockRetryHandler()
398
+ let handler = PlaybackErrorHandler(retryHandler: mock)
399
+ var surfacedError: TrackPlayerError.PlaybackError?
400
+ handler.onError = { surfacedError = $0 }
401
+
402
+ handler.handleError(nil, context: .playback, httpStatusCode: 404)
403
+
404
+ #expect(surfacedError == .httpStatus(404))
405
+ }
406
+
407
+ /// The same, on the retry-exhausted path.
408
+ @Test func retryExhausted_forwardsHttpStatusCode() async {
409
+ let mock = MockRetryHandler()
410
+ mock.retryableErrors = [URLError.Code.badURL.rawValue]
411
+ mock.attemptRetryResult = false
412
+ let handler = PlaybackErrorHandler(retryHandler: mock)
413
+ var surfacedError: TrackPlayerError.PlaybackError?
414
+ handler.onError = { surfacedError = $0 }
415
+
416
+ handler.handleError(URLError(.badURL), context: .playback, httpStatusCode: 503)
417
+ await handler.pendingRetryTask?.value
418
+
419
+ #expect(surfacedError == .httpStatus(503))
420
+ }
421
+
422
+ /// `.badURL` keeps this focused on the retry-exhaustion path: it is
423
+ /// unclassifiable, so the surfaced error is the context fallback and not a
424
+ /// classification the error itself carried.
425
+ @Test func retryExhausted_surfacesClassifiedError() async {
426
+ let mock = MockRetryHandler()
427
+ mock.retryableErrors = [URLError.Code.badURL.rawValue]
428
+ mock.attemptRetryResult = false
429
+ let handler = PlaybackErrorHandler(retryHandler: mock)
430
+ var surfacedError: TrackPlayerError.PlaybackError?
431
+ handler.onError = { surfacedError = $0 }
432
+
433
+ let error = URLError(.badURL)
151
434
  handler.handleError(error, context: .mediaLoad)
152
435
 
153
436
  await handler.pendingRetryTask?.value
@@ -176,38 +459,91 @@ struct HandleErrorTests {
176
459
  }
177
460
  }
178
461
 
179
- // MARK: - cancelPendingRetry
462
+ // `cancelPendingRetry` is asserted through `cancelledRetry_staysSilent`, which
463
+ // exercises the cancel *and* the silence it has to buy.
464
+
465
+ // MARK: - resetRetry
180
466
 
181
- @Suite("cancelPendingRetry")
467
+ @Suite("resetRetry")
182
468
  @MainActor
183
- struct CancelTests {
184
- @Test func cancelClearsPendingTask() async {
469
+ struct ErrorHandlerResetTests {
470
+ /// A restart from terminal error must reset budgets AND mark playback
471
+ /// unproven — keeping `hasPlayed` would grant the tap a 2-minute recovery
472
+ /// window at the one moment the listener is definitely watching.
473
+ @Test func resetForNewLoad_clearsBudgetsAndHasPlayed() {
474
+ let mock = MockRetryHandler()
475
+ mock.hasPlayed = true
476
+ let handler = PlaybackErrorHandler(retryHandler: mock)
477
+
478
+ handler.resetForNewLoad()
479
+
480
+ #expect(mock.resetCallCount == 1)
481
+ #expect(mock.hasPlayed == false)
482
+ }
483
+
484
+ /// A retry is started first: with no pending task the `pendingRetryTask ==
485
+ /// nil` assertion passes even if `resetRetry` drops its `cancelPendingRetry`.
486
+ @Test func resetCancelsAndResetsHandler() async {
185
487
  let mock = MockRetryHandler()
186
488
  mock.retryableErrors = [URLError.Code.timedOut.rawValue]
187
489
  mock.attemptRetryResult = true
490
+ mock.attemptRetryDelayNs = 20_000_000
188
491
  let handler = PlaybackErrorHandler(retryHandler: mock)
189
492
 
190
- let error = URLError(.timedOut)
191
- handler.handleError(error, context: .playback)
493
+ handler.handleError(URLError(.timedOut), context: .playback)
494
+ let task = handler.pendingRetryTask
495
+ #expect(task != nil)
496
+
497
+ handler.resetRetry()
192
498
 
193
- #expect(handler.pendingRetryTask != nil)
194
- handler.cancelPendingRetry()
195
499
  #expect(handler.pendingRetryTask == nil)
500
+ #expect(mock.resetCallCount == 1)
501
+ await task?.value
196
502
  }
197
503
  }
198
504
 
199
- // MARK: - resetRetry
505
+ // MARK: - Nitro Payload
200
506
 
201
- @Suite("resetRetry")
507
+ @Suite("PlaybackError.toNitroError")
202
508
  @MainActor
203
- struct ErrorHandlerResetTests {
204
- @Test func resetCancelsAndResetsHandler() {
205
- let mock = MockRetryHandler()
206
- let handler = PlaybackErrorHandler(retryHandler: mock)
509
+ struct PlaybackErrorNitroCodeTests {
510
+ @Test func httpStatus_producesTheFullPayload() {
511
+ let payload = TrackPlayerError.PlaybackError.httpStatus(404).toNitroError()
512
+ #expect(payload.kind == .notFound)
513
+ #expect(payload.code == "http-status")
514
+ #expect(payload.message == "Server responded with HTTP 404")
515
+ #expect(payload.statusCode == 404)
516
+ }
207
517
 
208
- handler.resetRetry()
518
+ @Test func nonHttpError_carriesNoStatusCode() {
519
+ #expect(TrackPlayerError.PlaybackError.playbackStalled.toNitroError().statusCode == nil)
520
+ }
209
521
 
210
- #expect(handler.pendingRetryTask == nil)
211
- #expect(mock.resetCallCount == 1)
522
+ /// `retrying` is nil — not false — on a terminal payload, so JS consumers
523
+ /// see the field only while it means something.
524
+ @Test func retryingFlag_isNilUnlessSet() {
525
+ #expect(TrackPlayerError.PlaybackError.hostUnreachable.toNitroError().retrying == nil)
526
+ #expect(
527
+ TrackPlayerError.PlaybackError.hostUnreachable.toNitroError(retrying: true).retrying == true,
528
+ )
529
+ }
530
+
531
+ /// `code` is an untyped wire contract: nothing on either side of the bridge
532
+ /// catches a typo, and telemetry groups issues by it. Worth an exhaustive
533
+ /// table even though `kind` is what the UI branches on.
534
+ @Test func codes_areStable() {
535
+ let expectations: [(TrackPlayerError.PlaybackError, String)] = [
536
+ (.failedToLoadKeyValue, "failed-to-load"),
537
+ (.invalidSourceUrl("nil"), "invalid-source-url"),
538
+ (.notConnectedToInternet, "not-connected-to-internet"),
539
+ (.hostUnreachable, "host-unreachable"),
540
+ (.httpStatus(500), "http-status"),
541
+ (.playbackFailed, "playback-failed"),
542
+ (.trackWasUnplayable, "track-unplayable"),
543
+ (.playbackStalled, "playback-stalled"),
544
+ ]
545
+ for (error, expected) in expectations {
546
+ #expect(error.toNitroError().code == expected, "\(error)")
547
+ }
212
548
  }
213
549
  }
@@ -76,7 +76,7 @@ struct LoadSeekCompletedTests {
76
76
  for state in allStates where state != .loading {
77
77
  #expect(
78
78
  nextPlaybackState(from: state, on: .loadSeekCompleted) == nil,
79
- "Expected nil from \(state), got non-nil"
79
+ "Expected nil from \(state), got non-nil",
80
80
  )
81
81
  }
82
82
  }
@@ -95,24 +95,33 @@ struct AVPlayerPausedTests {
95
95
  #expect(nextPlaybackState(from: .error, on: .avPlayerPaused(hasAsset: true)) == nil)
96
96
  }
97
97
 
98
+ /// After a natural end the play intent is cleared, so the call-site
99
+ /// `!playWhenReady` gate no longer swallows stray pause observations —
100
+ /// without this guard a late timeControlStatus pause would re-report
101
+ /// .ended as .paused. (hasAsset: false still maps to .none: an emptied
102
+ /// element after ended is "nothing loaded".)
103
+ @Test func fromEnded_hasAssetTrue_isSuppressed() {
104
+ #expect(nextPlaybackState(from: .ended, on: .avPlayerPaused(hasAsset: true)) == nil)
105
+ }
106
+
98
107
  @Test func hasAssetFalse_transitionsToNone() {
99
108
  // hasAsset: false → .none from any non-stopped state
100
109
  let nonStoppedStates = allStates.filter { $0 != .stopped }
101
110
  for state in nonStoppedStates {
102
111
  #expect(
103
112
  nextPlaybackState(from: state, on: .avPlayerPaused(hasAsset: false)) == PlaybackState.none,
104
- "Expected .none from \(state) with hasAsset: false"
113
+ "Expected .none from \(state) with hasAsset: false",
105
114
  )
106
115
  }
107
116
  }
108
117
 
109
118
  @Test func hasAssetTrue_transitionsToPaused() {
110
- // hasAsset: true → .paused from states that aren't stopped or error
111
- let validStates = allStates.filter { $0 != .stopped && $0 != .error }
119
+ // hasAsset: true → .paused from states that aren't stopped, error, or ended
120
+ let validStates = allStates.filter { $0 != .stopped && $0 != .error && $0 != .ended }
112
121
  for state in validStates {
113
122
  #expect(
114
123
  nextPlaybackState(from: state, on: .avPlayerPaused(hasAsset: true)) == .paused,
115
- "Expected .paused from \(state) with hasAsset: true"
124
+ "Expected .paused from \(state) with hasAsset: true",
116
125
  )
117
126
  }
118
127
  }
@@ -120,8 +129,10 @@ struct AVPlayerPausedTests {
120
129
 
121
130
  // MARK: - bufferingSufficient
122
131
 
123
- @Suite("bufferingSufficient — suppressed from .playing and .ended")
132
+ @Suite("bufferingSufficient — suppressed from terminal states and .playing")
124
133
  struct BufferingSufficientTests {
134
+ private let suppressedStates: [PlaybackState] = [.playing, .ended, .stopped, .error]
135
+
125
136
  @Test func fromPlaying_isSuppressed() {
126
137
  #expect(nextPlaybackState(from: .playing, on: .bufferingSufficient) == nil)
127
138
  }
@@ -133,11 +144,25 @@ struct BufferingSufficientTests {
133
144
  #expect(nextPlaybackState(from: .ended, on: .bufferingSufficient) == nil)
134
145
  }
135
146
 
147
+ /// stop() seeks non-live tracks back to 0 with the asset still loaded, so the
148
+ /// buffer refill flips playbackLikelyToKeepUp right after stopping. Leaving
149
+ /// .stopped would both surface a wrong state and skip the reload-on-play path
150
+ /// (which only triggers from .stopped/.error).
151
+ @Test func fromStopped_isSuppressed() {
152
+ #expect(nextPlaybackState(from: .stopped, on: .bufferingSufficient) == nil)
153
+ }
154
+
155
+ /// Recovery from .error always goes through a reload (→ .loading); a direct
156
+ /// jump to .ready would clear playbackError without recovering anything.
157
+ @Test func fromError_isSuppressed() {
158
+ #expect(nextPlaybackState(from: .error, on: .bufferingSufficient) == nil)
159
+ }
160
+
136
161
  @Test func fromOtherStates_transitionsToReady() {
137
- for state in allStates where state != .playing && state != .ended {
162
+ for state in allStates where !suppressedStates.contains(state) {
138
163
  #expect(
139
164
  nextPlaybackState(from: state, on: .bufferingSufficient) == .ready,
140
- "Expected .ready from \(state)"
165
+ "Expected .ready from \(state)",
141
166
  )
142
167
  }
143
168
  }
@@ -0,0 +1,48 @@
1
+ import Foundation
2
+ import Testing
3
+
4
+ @testable import AudioBrowserTestable
5
+
6
+ @Suite("PlaybackStateStore", .serialized)
7
+ struct PlaybackStateStoreTests {
8
+ private func freshDefaults() -> UserDefaults {
9
+ let suite = "PlaybackStateStoreTests"
10
+ let d = UserDefaults(suiteName: suite)!
11
+ d.removePersistentDomain(forName: suite)
12
+ return d
13
+ }
14
+
15
+ private func sampleTrack() -> JsonTrack {
16
+ JsonTrack(
17
+ id: "abc", url: nil, title: "Test FM", subtitle: nil, artwork: nil,
18
+ artist: "City, Country", albumUrl: nil, album: nil, description: nil,
19
+ genre: nil, duration: nil, src: "/listen/abc/channel.mp3", request: nil,
20
+ style: nil, childrenStyle: nil, groupTitle: nil, live: true, imageRow: nil,
21
+ )
22
+ }
23
+
24
+ @Test func roundTrip_preservesState() {
25
+ let store = PlaybackStateStore(defaults: freshDefaults())
26
+ let state = PersistedPlaybackState(
27
+ track: sampleTrack(), positionMs: nil, repeatMode: "off",
28
+ shuffleEnabled: false, playbackSpeed: 1.0,
29
+ )
30
+ store.save(state)
31
+ let loaded = store.load()
32
+ #expect(loaded?.track.src == "/listen/abc/channel.mp3")
33
+ #expect(loaded?.track.live == true)
34
+ #expect(loaded?.positionMs == nil)
35
+ #expect(loaded?.playbackSpeed == 1.0)
36
+ }
37
+
38
+ @Test func load_isNilBeforeAnySave() {
39
+ #expect(PlaybackStateStore(defaults: freshDefaults()).load() == nil)
40
+ }
41
+
42
+ @Test func clear_removesState() {
43
+ let store = PlaybackStateStore(defaults: freshDefaults())
44
+ store.save(PersistedPlaybackState(track: sampleTrack(), positionMs: 5000, repeatMode: "off", shuffleEnabled: false, playbackSpeed: 1.0))
45
+ store.clear()
46
+ #expect(store.load() == nil)
47
+ }
48
+ }