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

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 (901) hide show
  1. package/AudioBrowser.podspec +1 -1
  2. package/README.md +180 -2
  3. package/android/build.gradle +10 -0
  4. package/android/gradle.properties +1 -1
  5. package/android/src/main/AndroidManifest.xml +15 -0
  6. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +401 -141
  7. package/android/src/main/java/com/audiobrowser/Callbacks.kt +4 -6
  8. package/android/src/main/java/com/audiobrowser/Service.kt +67 -18
  9. package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +37 -0
  10. package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +34 -0
  11. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +443 -178
  12. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +300 -0
  13. package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +79 -0
  14. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +41 -4
  15. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +43 -0
  16. package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
  17. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +35 -0
  18. package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +109 -97
  19. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +6 -10
  20. package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +40 -8
  21. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +31 -37
  22. package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +4 -1
  23. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +119 -0
  24. package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +18 -1
  25. package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +153 -0
  26. package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +141 -0
  27. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +15 -3
  28. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +415 -131
  29. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +154 -276
  30. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +348 -0
  31. package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +129 -0
  32. package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +85 -0
  33. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +27 -5
  34. package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +56 -0
  35. package/android/src/main/java/com/audiobrowser/player/Player.kt +452 -576
  36. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +20 -0
  37. package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +130 -0
  38. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +190 -67
  39. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +117 -53
  40. package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +37 -3
  41. package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +80 -0
  42. package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +50 -0
  43. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +84 -40
  44. package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +81 -0
  45. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +141 -0
  46. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +37 -0
  47. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +41 -0
  48. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +209 -0
  49. package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +161 -0
  50. package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +28 -0
  51. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +26 -0
  52. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +24 -0
  53. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +49 -0
  54. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +36 -0
  55. package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +23 -300
  56. package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +13 -23
  57. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
  58. package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +4 -3
  59. package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +211 -0
  60. package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +58 -0
  61. package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +2 -1
  62. package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +14 -0
  63. package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +6 -111
  64. package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +22 -8
  65. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +111 -80
  66. package/android/src/main/res/values/strings.xml +4 -0
  67. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +166 -0
  68. package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +46 -0
  69. package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +39 -0
  70. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +114 -0
  71. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +141 -0
  72. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +4 -0
  73. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +177 -0
  74. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +54 -0
  75. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +96 -0
  76. package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +1 -2
  77. package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +72 -0
  78. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +159 -0
  79. package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +56 -0
  80. package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +216 -0
  81. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +442 -0
  82. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +191 -0
  83. package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +198 -0
  84. package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +137 -0
  85. package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +36 -0
  86. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +268 -0
  87. package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +68 -0
  88. package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +40 -0
  89. package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +193 -0
  90. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +107 -0
  91. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +73 -0
  92. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +75 -0
  93. package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +18 -0
  94. package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +163 -0
  95. package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +34 -0
  96. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +28 -0
  97. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +53 -0
  98. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +58 -0
  99. package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +57 -0
  100. package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +40 -0
  101. package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +34 -0
  102. package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +50 -0
  103. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +63 -0
  104. package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +35 -0
  105. package/android/src/test/resources/certs/isrg-root-x1.pem +31 -0
  106. package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
  107. package/android/src/test/resources/certs/letsencrypt-r13.pem +29 -0
  108. package/android/src/test/resources/certs/stationplaylist-leaf.pem +34 -0
  109. package/ios/Browser/BrowserConfig.swift +41 -11
  110. package/ios/Browser/BrowserManager+URLResolution.swift +213 -93
  111. package/ios/Browser/BrowserManager.swift +303 -110
  112. package/ios/Browser/BrowserPathHelper.swift +32 -3
  113. package/ios/Browser/JsonModels.swift +287 -66
  114. package/ios/Browser/MediaResolveComposer.swift +114 -0
  115. package/ios/Browser/SearchDrillIn.swift +44 -0
  116. package/ios/Browser/SectionScope.swift +35 -0
  117. package/ios/Browser/TabBarEntries.swift +15 -0
  118. package/ios/CarPlay/CPInterfaceController+Safe.swift +126 -0
  119. package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
  120. package/ios/CarPlay/CarPlayController.swift +739 -149
  121. package/ios/CarPlay/CarPlayImageLoader.swift +57 -8
  122. package/ios/CarPlay/CarPlayListItemFactory.swift +44 -35
  123. package/ios/CarPlay/CarPlayNowPlayingManager.swift +180 -36
  124. package/ios/CarPlay/ImageRowItem+CarPlay.swift +19 -0
  125. package/ios/CarPlay/RNABAudioBrowser.h +15 -10
  126. package/ios/CarPlay/RNABAudioBrowser.m +15 -14
  127. package/ios/CarPlay/RNABCarPlaySceneDelegate.m +2 -0
  128. package/ios/CarPlay/RNABMediaAddHandler.swift +75 -0
  129. package/ios/CarPlay/RNABMediaAffinityHandler.swift +69 -0
  130. package/ios/CarPlay/RNABMediaIntentHandler.swift +53 -43
  131. package/ios/Extension/Capability+RemoteCommand.swift +7 -9
  132. package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
  133. package/ios/Extension/NavigationError+Conversion.swift +8 -1
  134. package/ios/Extension/PlayerCapabilities+Favorite.swift +20 -0
  135. package/ios/Extension/RequestConfig+Copying.swift +31 -0
  136. package/ios/Extension/ResolvedTrack+Copying.swift +10 -4
  137. package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
  138. package/ios/Extension/Track+AVPlayer.swift +4 -4
  139. package/ios/Extension/Track+Copying.swift +9 -3
  140. package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
  141. package/ios/Extension/Variant+Artwork.swift +49 -0
  142. package/ios/HybridAudioBrowser.swift +750 -173
  143. package/ios/Model/NitroTypeStubs.swift +299 -224
  144. package/ios/Model/PlayerUpdateOptions.swift +23 -23
  145. package/ios/Model/RemoteCommand.swift +6 -6
  146. package/ios/Model/TrackPlayerError.swift +82 -18
  147. package/ios/NowPlayingInfo/NowPlayingInfoController.swift +99 -121
  148. package/ios/NowPlayingInfo/SilentAudioPrimer.swift +73 -0
  149. package/ios/Observer/PlayerItemNotificationObserver.swift +45 -5
  150. package/ios/Observer/PlayerItemPropertyObserver.swift +27 -4
  151. package/ios/Observer/PlayerTimeObserver.swift +3 -53
  152. package/ios/Option/SessionCategories.swift +33 -11
  153. package/ios/Player/AirPlayStallJudgement.swift +25 -0
  154. package/ios/Player/EndOfTrackJudgement.swift +32 -0
  155. package/ios/Player/LoadSeekCoordinator.swift +16 -1
  156. package/ios/Player/MediaIntentCriteria.swift +139 -0
  157. package/ios/Player/MediaLoader.swift +28 -10
  158. package/ios/Player/NowPlayingUpdater.swift +119 -26
  159. package/ios/Player/PersistedPlaybackState.swift +11 -0
  160. package/ios/Player/PlaybackCoordinator.swift +282 -51
  161. package/ios/Player/PlaybackCoordinatorCallbacks.swift +6 -1
  162. package/ios/Player/PlaybackEffectHandler.swift +19 -9
  163. package/ios/Player/PlaybackErrorHandler.swift +97 -13
  164. package/ios/Player/PlaybackStateMachine.swift +18 -13
  165. package/ios/Player/PlaybackStateStore.swift +27 -0
  166. package/ios/Player/PlaybackTimer.swift +61 -0
  167. package/ios/Player/PlayingStateManager.swift +3 -2
  168. package/ios/Player/QueueManager.swift +67 -61
  169. package/ios/Player/RetryManager.swift +97 -21
  170. package/ios/Player/SleepTimerHandling.swift +8 -0
  171. package/ios/Player/SleepTimerManager.swift +38 -5
  172. package/ios/Player/VolumeFader.swift +77 -0
  173. package/ios/RemoteCommand/RemoteCommandController.swift +31 -15
  174. package/ios/Tests/AirPlayStallJudgementTests.swift +52 -0
  175. package/ios/Tests/BrowserPathHelperTests.swift +28 -0
  176. package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
  177. package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
  178. package/ios/Tests/EndOfTrackJudgementTests.swift +35 -0
  179. package/ios/Tests/JsonModelsTests.swift +58 -0
  180. package/ios/Tests/LoadSeekCoordinatorTests.swift +38 -7
  181. package/ios/Tests/MediaIntentCriteriaTests.swift +170 -0
  182. package/ios/Tests/MediaLoaderAssetOptionsTests.swift +46 -0
  183. package/ios/Tests/MediaLoaderTests.swift +23 -8
  184. package/ios/Tests/MediaResolveTests.swift +126 -0
  185. package/ios/Tests/OnceValueTests.swift +79 -0
  186. package/ios/Tests/PlaybackCoordinatorTests.swift +784 -13
  187. package/ios/Tests/PlaybackErrorHandlerTests.swift +380 -46
  188. package/ios/Tests/PlaybackStateMachineTests.swift +33 -8
  189. package/ios/Tests/PlaybackStateStoreTests.swift +48 -0
  190. package/ios/Tests/PlaybackTimerTests.swift +55 -0
  191. package/ios/Tests/PlayingStateManagerTests.swift +57 -0
  192. package/ios/Tests/QueueManagerTests.swift +149 -18
  193. package/ios/Tests/ResolvedTrackCopyingTests.swift +15 -8
  194. package/ios/Tests/RetryManagerTests.swift +89 -0
  195. package/ios/Tests/SearchDrillInTests.swift +63 -0
  196. package/ios/Tests/SectionScopeTests.swift +82 -0
  197. package/ios/Tests/SleepTimerManagerTests.swift +104 -0
  198. package/ios/Tests/Support/TestTypes.swift +61 -20
  199. package/ios/Tests/TabBarEntriesTests.swift +71 -0
  200. package/ios/Tests/TrackSelectorTests.swift +15 -15
  201. package/ios/TrackPlayer.swift +380 -58
  202. package/ios/TrackPlayerCallbacks.swift +0 -6
  203. package/ios/TrackSelector.swift +5 -5
  204. package/ios/Util/ArtworkImageFetcher.swift +4 -2
  205. package/ios/Util/NetworkMonitor.swift +7 -3
  206. package/ios/Util/OnceValue.swift +34 -0
  207. package/lib/commonjs/features/browser-config.js +227 -0
  208. package/lib/commonjs/features/browser-config.js.map +1 -0
  209. package/lib/commonjs/features/browser.js +59 -158
  210. package/lib/commonjs/features/browser.js.map +1 -1
  211. package/lib/commonjs/features/carConnection.js +42 -0
  212. package/lib/commonjs/features/carConnection.js.map +1 -0
  213. package/lib/commonjs/features/errors.js +34 -2
  214. package/lib/commonjs/features/errors.js.map +1 -1
  215. package/lib/commonjs/features/gate.js +149 -0
  216. package/lib/commonjs/features/gate.js.map +1 -0
  217. package/lib/commonjs/features/index.js +22 -0
  218. package/lib/commonjs/features/index.js.map +1 -1
  219. package/lib/commonjs/features/metadata.js.map +1 -1
  220. package/lib/commonjs/features/nowPlaying.js +35 -0
  221. package/lib/commonjs/features/nowPlaying.js.map +1 -1
  222. package/lib/commonjs/features/output.js +37 -23
  223. package/lib/commonjs/features/output.js.map +1 -1
  224. package/lib/commonjs/features/playback/controls.js +9 -0
  225. package/lib/commonjs/features/playback/controls.js.map +1 -1
  226. package/lib/commonjs/features/playback/index.js +11 -0
  227. package/lib/commonjs/features/playback/index.js.map +1 -1
  228. package/lib/commonjs/features/playback/progress.js +28 -12
  229. package/lib/commonjs/features/playback/progress.js.map +1 -1
  230. package/lib/commonjs/features/playback/trackPlaybackTime.js +92 -0
  231. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -0
  232. package/lib/commonjs/features/playback/volume.js +1 -1
  233. package/lib/commonjs/features/player/options.js +49 -13
  234. package/lib/commonjs/features/player/options.js.map +1 -1
  235. package/lib/commonjs/features/player/setup.js +249 -3
  236. package/lib/commonjs/features/player/setup.js.map +1 -1
  237. package/lib/commonjs/features/queue/queue.js +10 -3
  238. package/lib/commonjs/features/queue/queue.js.map +1 -1
  239. package/lib/commonjs/features/remoteControls.js +1 -12
  240. package/lib/commonjs/features/remoteControls.js.map +1 -1
  241. package/lib/commonjs/features/sleepTimer.js +9 -4
  242. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  243. package/lib/commonjs/native.js +5 -0
  244. package/lib/commonjs/native.js.map +1 -1
  245. package/lib/commonjs/native.web.js +4 -1
  246. package/lib/commonjs/native.web.js.map +1 -1
  247. package/lib/commonjs/utils/LazyNativeEmitter.js +0 -3
  248. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  249. package/lib/commonjs/utils/NativeUpdatedValue.js +0 -3
  250. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  251. package/lib/commonjs/utils/artwork.js +31 -0
  252. package/lib/commonjs/utils/artwork.js.map +1 -0
  253. package/lib/commonjs/web/NativeAudioBrowser.js +301 -124
  254. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  255. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +71 -0
  256. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  257. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +34 -0
  258. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  259. package/lib/commonjs/web/TrackPlayer/Player.js +90 -26
  260. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  261. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +21 -0
  262. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  263. package/lib/commonjs/web/TrackPlayer/QueueManager.js +273 -0
  264. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -0
  265. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +158 -0
  266. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -0
  267. package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
  268. package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
  269. package/lib/commonjs/web/TrackPlayer/SleepTimer.js +34 -3
  270. package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
  271. package/lib/commonjs/web/TrackPlayer/VolumeFader.js +75 -0
  272. package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +1 -0
  273. package/lib/commonjs/web/TrackPlayer/index.js +13 -5
  274. package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
  275. package/lib/commonjs/web/browser/BrowserManager.js +176 -50
  276. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  277. package/lib/commonjs/web/browser/NavigationErrorManager.js +3 -1
  278. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  279. package/lib/commonjs/web/browser/SearchManager.js +11 -9
  280. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  281. package/lib/commonjs/web/browser/assertBrowsePageShape.js +22 -0
  282. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -0
  283. package/lib/commonjs/web/browser/classifyTrackNavigation.js +41 -0
  284. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -0
  285. package/lib/commonjs/web/browser/parseSearchResponse.js +20 -0
  286. package/lib/commonjs/web/browser/parseSearchResponse.js.map +1 -0
  287. package/lib/commonjs/web/http/HttpClient.js +4 -23
  288. package/lib/commonjs/web/http/HttpClient.js.map +1 -1
  289. package/lib/commonjs/web/http/RequestConfigBuilder.js +107 -58
  290. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  291. package/lib/commonjs/web/playbackErrorKind.js +57 -0
  292. package/lib/commonjs/web/playbackErrorKind.js.map +1 -0
  293. package/lib/commonjs/web/player/MediaSessionManager.js +121 -0
  294. package/lib/commonjs/web/player/MediaSessionManager.js.map +1 -0
  295. package/lib/commonjs/web/player/NowPlayingManager.js +6 -5
  296. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  297. package/lib/commonjs/web/player/OptionsManager.js +9 -17
  298. package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
  299. package/lib/commonjs/web/player/RemoteCommandController.js +96 -0
  300. package/lib/commonjs/web/player/RemoteCommandController.js.map +1 -0
  301. package/lib/module/features/browser-config.js +221 -0
  302. package/lib/module/features/browser-config.js.map +1 -0
  303. package/lib/module/features/browser.js +53 -156
  304. package/lib/module/features/browser.js.map +1 -1
  305. package/lib/module/features/carConnection.js +37 -0
  306. package/lib/module/features/carConnection.js.map +1 -0
  307. package/lib/module/features/errors.js +34 -2
  308. package/lib/module/features/errors.js.map +1 -1
  309. package/lib/module/features/gate.js +144 -0
  310. package/lib/module/features/gate.js.map +1 -0
  311. package/lib/module/features/index.js +2 -0
  312. package/lib/module/features/index.js.map +1 -1
  313. package/lib/module/features/metadata.js.map +1 -1
  314. package/lib/module/features/nowPlaying.js +33 -0
  315. package/lib/module/features/nowPlaying.js.map +1 -1
  316. package/lib/module/features/output.js +32 -19
  317. package/lib/module/features/output.js.map +1 -1
  318. package/lib/module/features/playback/controls.js +8 -0
  319. package/lib/module/features/playback/controls.js.map +1 -1
  320. package/lib/module/features/playback/index.js +1 -0
  321. package/lib/module/features/playback/index.js.map +1 -1
  322. package/lib/module/features/playback/progress.js +28 -12
  323. package/lib/module/features/playback/progress.js.map +1 -1
  324. package/lib/module/features/playback/trackPlaybackTime.js +88 -0
  325. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -0
  326. package/lib/module/features/playback/volume.js +1 -1
  327. package/lib/module/features/player/options.js +48 -13
  328. package/lib/module/features/player/options.js.map +1 -1
  329. package/lib/module/features/player/setup.js +249 -3
  330. package/lib/module/features/player/setup.js.map +1 -1
  331. package/lib/module/features/queue/queue.js +10 -3
  332. package/lib/module/features/queue/queue.js.map +1 -1
  333. package/lib/module/features/remoteControls.js +0 -11
  334. package/lib/module/features/remoteControls.js.map +1 -1
  335. package/lib/module/features/sleepTimer.js +9 -4
  336. package/lib/module/features/sleepTimer.js.map +1 -1
  337. package/lib/module/native.js +5 -0
  338. package/lib/module/native.js.map +1 -1
  339. package/lib/module/native.web.js +4 -1
  340. package/lib/module/native.web.js.map +1 -1
  341. package/lib/module/utils/LazyNativeEmitter.js +0 -3
  342. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  343. package/lib/module/utils/NativeUpdatedValue.js +0 -3
  344. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  345. package/lib/module/utils/artwork.js +26 -0
  346. package/lib/module/utils/artwork.js.map +1 -0
  347. package/lib/module/web/NativeAudioBrowser.js +302 -125
  348. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  349. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +67 -0
  350. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
  351. package/lib/module/web/TrackPlayer/PlaybackTimer.js +29 -0
  352. package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +1 -0
  353. package/lib/module/web/TrackPlayer/Player.js +90 -26
  354. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  355. package/lib/module/web/TrackPlayer/PlayingStateFactory.js +17 -0
  356. package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
  357. package/lib/module/web/TrackPlayer/QueueManager.js +269 -0
  358. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -0
  359. package/lib/module/web/TrackPlayer/QueuePlayer.js +154 -0
  360. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -0
  361. package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
  362. package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
  363. package/lib/module/web/TrackPlayer/SleepTimer.js +34 -3
  364. package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
  365. package/lib/module/web/TrackPlayer/VolumeFader.js +70 -0
  366. package/lib/module/web/TrackPlayer/VolumeFader.js.map +1 -0
  367. package/lib/module/web/TrackPlayer/index.js +2 -1
  368. package/lib/module/web/TrackPlayer/index.js.map +1 -1
  369. package/lib/module/web/browser/BrowserManager.js +176 -50
  370. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  371. package/lib/module/web/browser/NavigationErrorManager.js +3 -1
  372. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  373. package/lib/module/web/browser/SearchManager.js +11 -9
  374. package/lib/module/web/browser/SearchManager.js.map +1 -1
  375. package/lib/module/web/browser/assertBrowsePageShape.js +18 -0
  376. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -0
  377. package/lib/module/web/browser/classifyTrackNavigation.js +38 -0
  378. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -0
  379. package/lib/module/web/browser/parseSearchResponse.js +16 -0
  380. package/lib/module/web/browser/parseSearchResponse.js.map +1 -0
  381. package/lib/module/web/http/HttpClient.js +4 -23
  382. package/lib/module/web/http/HttpClient.js.map +1 -1
  383. package/lib/module/web/http/RequestConfigBuilder.js +107 -58
  384. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  385. package/lib/module/web/playbackErrorKind.js +52 -0
  386. package/lib/module/web/playbackErrorKind.js.map +1 -0
  387. package/lib/module/web/player/MediaSessionManager.js +116 -0
  388. package/lib/module/web/player/MediaSessionManager.js.map +1 -0
  389. package/lib/module/web/player/NowPlayingManager.js +7 -5
  390. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  391. package/lib/module/web/player/OptionsManager.js +9 -17
  392. package/lib/module/web/player/OptionsManager.js.map +1 -1
  393. package/lib/module/web/player/RemoteCommandController.js +92 -0
  394. package/lib/module/web/player/RemoteCommandController.js.map +1 -0
  395. package/lib/typescript/src/features/browser-config.d.ts +12 -0
  396. package/lib/typescript/src/features/browser-config.d.ts.map +1 -0
  397. package/lib/typescript/src/features/browser.d.ts +40 -1
  398. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  399. package/lib/typescript/src/features/carConnection.d.ts +20 -0
  400. package/lib/typescript/src/features/carConnection.d.ts.map +1 -0
  401. package/lib/typescript/src/features/errors.d.ts +58 -3
  402. package/lib/typescript/src/features/errors.d.ts.map +1 -1
  403. package/lib/typescript/src/features/gate.d.ts +77 -0
  404. package/lib/typescript/src/features/gate.d.ts.map +1 -0
  405. package/lib/typescript/src/features/index.d.ts +2 -0
  406. package/lib/typescript/src/features/index.d.ts.map +1 -1
  407. package/lib/typescript/src/features/metadata.d.ts +6 -4
  408. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  409. package/lib/typescript/src/features/nowPlaying.d.ts +27 -0
  410. package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
  411. package/lib/typescript/src/features/output.d.ts +28 -18
  412. package/lib/typescript/src/features/output.d.ts.map +1 -1
  413. package/lib/typescript/src/features/playback/controls.d.ts +5 -0
  414. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  415. package/lib/typescript/src/features/playback/index.d.ts +1 -0
  416. package/lib/typescript/src/features/playback/index.d.ts.map +1 -1
  417. package/lib/typescript/src/features/playback/progress.d.ts +3 -0
  418. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  419. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +34 -0
  420. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -0
  421. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  422. package/lib/typescript/src/features/player/options.d.ts +92 -93
  423. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  424. package/lib/typescript/src/features/player/setup.d.ts +206 -167
  425. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  426. package/lib/typescript/src/features/queue/queue.d.ts +9 -2
  427. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  428. package/lib/typescript/src/features/remoteControls.d.ts +0 -13
  429. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  430. package/lib/typescript/src/features/sleepTimer.d.ts +18 -3
  431. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  432. package/lib/typescript/src/index.d.ts.map +1 -1
  433. package/lib/typescript/src/native.d.ts +1 -2
  434. package/lib/typescript/src/native.d.ts.map +1 -1
  435. package/lib/typescript/src/specs/audio-browser.nitro.d.ts +17 -225
  436. package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
  437. package/lib/typescript/src/types/browser-native.d.ts +9 -3
  438. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  439. package/lib/typescript/src/types/browser-nodes.d.ts +145 -13
  440. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  441. package/lib/typescript/src/types/browser.d.ts +349 -85
  442. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  443. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +0 -2
  444. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  445. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +0 -2
  446. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  447. package/lib/typescript/src/utils/artwork.d.ts +19 -0
  448. package/lib/typescript/src/utils/artwork.d.ts.map +1 -0
  449. package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
  450. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +3 -1
  451. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +24 -10
  452. package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
  453. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +1 -11
  454. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +33 -1
  455. package/nitrogen/generated/android/c++/JArtworkVariants.hpp +61 -0
  456. package/nitrogen/generated/android/c++/JBrowseResult.hpp +5 -0
  457. package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
  458. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +6 -0
  459. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +58 -0
  460. package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +58 -0
  461. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +100 -0
  462. package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +3 -2
  463. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +112 -0
  464. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +121 -0
  465. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +123 -0
  466. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +133 -0
  467. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +121 -0
  468. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +139 -0
  469. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +6 -0
  470. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +134 -0
  471. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -0
  472. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +6 -0
  473. 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
  474. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +6 -0
  475. package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +79 -0
  476. package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +0 -10
  477. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +9 -4
  478. package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +80 -0
  479. package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +2 -0
  480. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +6 -0
  481. package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +2 -0
  482. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
  483. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
  484. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +6 -0
  485. package/nitrogen/generated/android/c++/JGate.hpp +62 -0
  486. package/nitrogen/generated/android/c++/JGateDecision.hpp +64 -0
  487. package/nitrogen/generated/android/c++/{JThumbsRating.hpp → JGateEvent.hpp} +16 -15
  488. package/nitrogen/generated/android/c++/JGateReason.hpp +58 -0
  489. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +236 -212
  490. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +25 -22
  491. package/nitrogen/generated/android/c++/JIOSOptions.hpp +95 -0
  492. package/nitrogen/generated/android/c++/JImageRowItem.hpp +33 -3
  493. package/nitrogen/generated/android/c++/JMediaReference.hpp +58 -0
  494. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +33 -1
  495. package/nitrogen/generated/android/c++/{JPartialAndroidSetupPlayerOptions.hpp → JNativeAndroidSetupOptions.hpp} +10 -10
  496. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +61 -28
  497. package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +74 -0
  498. package/nitrogen/generated/android/c++/{JPartialIOSSetupPlayerOptions.hpp → JNativeIOSSetupOptions.hpp} +10 -10
  499. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +20 -9
  500. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +182 -0
  501. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +16 -19
  502. package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +6 -0
  503. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +1 -11
  504. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +96 -0
  505. package/nitrogen/generated/android/c++/JNotificationButton.hpp +3 -3
  506. package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +3 -17
  507. package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +7 -3
  508. package/nitrogen/generated/android/c++/JOptions.hpp +14 -9
  509. package/nitrogen/generated/android/c++/{JIosOutput.hpp → JOutput.hpp} +15 -15
  510. package/nitrogen/generated/android/c++/JOutputType.hpp +82 -0
  511. package/nitrogen/generated/android/c++/JPlayback.hpp +2 -0
  512. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +6 -0
  513. package/nitrogen/generated/android/c++/JPlaybackError.hpp +18 -3
  514. package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +2 -0
  515. package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +76 -0
  516. package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
  517. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +11 -5
  518. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +23 -5
  519. package/nitrogen/generated/android/c++/JRetryConfig.hpp +11 -7
  520. package/nitrogen/generated/android/c++/JSearchMode.hpp +27 -9
  521. package/nitrogen/generated/android/c++/JSearchParams.hpp +9 -3
  522. package/nitrogen/generated/android/c++/JStallReason.hpp +58 -0
  523. package/nitrogen/generated/android/c++/JTrack.hpp +23 -5
  524. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +6 -0
  525. package/nitrogen/generated/android/c++/JTrackRequest.hpp +93 -0
  526. package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +14 -1
  527. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +26 -0
  528. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +71 -0
  529. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +41 -0
  530. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +121 -0
  531. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +26 -0
  532. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +70 -0
  533. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
  534. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +2 -8
  535. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +10 -4
  536. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkVariants.kt +41 -0
  537. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
  538. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ThumbsRating.kt → FavoriteConfig.kt} +6 -6
  539. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RatingType.kt → FavoritesMatchMode.kt} +5 -10
  540. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +50 -0
  541. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
  542. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +80 -0
  543. 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
  544. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +80 -0
  545. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +80 -0
  546. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +80 -0
  547. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +80 -0
  548. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +80 -0
  549. 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
  550. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_RemoteSetRatingEvent.kt → Func_void_GateEvent.kt} +9 -9
  551. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_IosOutput.kt → Func_void_Output.kt} +9 -9
  552. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
  553. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{HeartRating.kt → Gate.kt} +9 -6
  554. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PercentageRating.kt → GateDecision.kt} +9 -6
  555. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{StarRating.kt → GateEvent.kt} +6 -6
  556. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GateReason.kt +23 -0
  557. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +103 -109
  558. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +44 -0
  559. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +24 -3
  560. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +23 -0
  561. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +10 -4
  562. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialAndroidSetupPlayerOptions.kt → NativeAndroidSetupOptions.kt} +5 -5
  563. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +18 -6
  564. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RemoteSetRatingEvent.kt → NativeGateRequest.kt} +12 -6
  565. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialIOSSetupPlayerOptions.kt → NativeIOSSetupOptions.kt} +5 -5
  566. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
  567. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +66 -0
  568. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
  569. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +3 -1
  570. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +2 -8
  571. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +44 -0
  572. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButton.kt +5 -5
  573. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +3 -6
  574. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +6 -3
  575. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
  576. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{IosOutput.kt → Output.kt} +6 -6
  577. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +31 -0
  578. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +12 -3
  579. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +29 -0
  580. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
  581. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  582. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -3
  583. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +7 -4
  584. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +12 -6
  585. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +6 -3
  586. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +23 -0
  587. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +12 -3
  588. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +44 -0
  589. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +7 -4
  590. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +53 -0
  591. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +65 -0
  592. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +53 -0
  593. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +139 -28
  594. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +1098 -447
  595. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +60 -39
  596. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +170 -112
  597. package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
  598. package/nitrogen/generated/ios/swift/AndroidOptions.swift +2 -12
  599. package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +95 -1
  600. package/nitrogen/generated/ios/swift/ArtworkVariants.swift +34 -0
  601. package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
  602. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +29 -0
  603. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +40 -0
  604. package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +67 -0
  605. package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +17 -4
  606. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +54 -0
  607. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +69 -0
  608. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +54 -0
  609. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +60 -0
  610. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +60 -0
  611. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +61 -0
  612. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +61 -0
  613. 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
  614. package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +46 -0
  615. package/nitrogen/generated/ios/swift/{Func_void_RemoteSetRatingEvent.swift → Func_void_GateEvent.swift} +10 -10
  616. package/nitrogen/generated/ios/swift/{Func_void_IosOutput.swift → Func_void_Output.swift} +10 -10
  617. package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +46 -0
  618. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  619. package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +46 -0
  620. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
  621. 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
  622. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +66 -0
  623. package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +81 -0
  624. package/nitrogen/generated/ios/swift/Gate.swift +47 -0
  625. package/nitrogen/generated/ios/swift/GateDecision.swift +40 -0
  626. package/nitrogen/generated/ios/swift/GateEvent.swift +29 -0
  627. package/nitrogen/generated/ios/swift/GateReason.swift +40 -0
  628. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +20 -14
  629. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +230 -197
  630. package/nitrogen/generated/ios/swift/IOSOptions.swift +51 -0
  631. package/nitrogen/generated/ios/swift/ImageRowItem.swift +121 -2
  632. package/nitrogen/generated/ios/swift/MediaReference.swift +40 -0
  633. package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +107 -1
  634. package/nitrogen/generated/ios/swift/{PartialAndroidSetupPlayerOptions.swift → NativeAndroidSetupOptions.swift} +5 -5
  635. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +161 -22
  636. package/nitrogen/generated/ios/swift/NativeGateRequest.swift +58 -0
  637. package/nitrogen/generated/ios/swift/{PartialIOSSetupPlayerOptions.swift → NativeIOSSetupOptions.swift} +5 -5
  638. package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
  639. package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +206 -0
  640. package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +12 -25
  641. package/nitrogen/generated/ios/swift/NavigationErrorType.swift +8 -0
  642. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +1 -30
  643. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +90 -0
  644. package/nitrogen/generated/ios/swift/NotificationButton.swift +4 -4
  645. package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +1 -48
  646. package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +19 -1
  647. package/nitrogen/generated/ios/swift/Options.swift +10 -4
  648. package/nitrogen/generated/ios/swift/{IosOutput.swift → Output.swift} +7 -7
  649. package/nitrogen/generated/ios/swift/OutputType.swift +72 -0
  650. package/nitrogen/generated/ios/swift/PlaybackError.swift +43 -2
  651. package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +64 -0
  652. package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
  653. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +27 -8
  654. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +74 -8
  655. package/nitrogen/generated/ios/swift/RetryConfig.swift +35 -4
  656. package/nitrogen/generated/ios/swift/SearchMode.swift +36 -12
  657. package/nitrogen/generated/ios/swift/SearchParams.swift +7 -2
  658. package/nitrogen/generated/ios/swift/StallReason.swift +40 -0
  659. package/nitrogen/generated/ios/swift/Track.swift +74 -8
  660. package/nitrogen/generated/ios/swift/TrackRequest.swift +106 -0
  661. package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +60 -1
  662. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +18 -0
  663. package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +19 -0
  664. package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +18 -0
  665. package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
  666. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +1 -12
  667. package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +10 -2
  668. package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +87 -0
  669. package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
  670. package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +0 -2
  671. package/nitrogen/generated/shared/c++/{StarRating.hpp → FavoriteConfig.hpp} +18 -17
  672. package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +76 -0
  673. package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +110 -0
  674. package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +6 -5
  675. package/nitrogen/generated/shared/c++/{HeartRating.hpp → Gate.hpp} +21 -16
  676. package/nitrogen/generated/shared/c++/{ThumbsRating.hpp → GateDecision.hpp} +23 -17
  677. package/nitrogen/generated/shared/c++/{PercentageRating.hpp → GateEvent.hpp} +18 -17
  678. package/nitrogen/generated/shared/c++/GateReason.hpp +76 -0
  679. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +21 -18
  680. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +44 -35
  681. package/nitrogen/generated/shared/c++/IOSOptions.hpp +91 -0
  682. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +33 -2
  683. package/nitrogen/generated/shared/c++/MediaReference.hpp +76 -0
  684. package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +10 -2
  685. package/nitrogen/generated/shared/c++/{PartialAndroidSetupPlayerOptions.hpp → NativeAndroidSetupOptions.hpp} +11 -11
  686. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +31 -15
  687. package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +97 -0
  688. package/nitrogen/generated/shared/c++/{PartialIOSSetupPlayerOptions.hpp → NativeIOSSetupOptions.hpp} +11 -11
  689. package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
  690. package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +138 -0
  691. package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +9 -7
  692. package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +8 -0
  693. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +1 -12
  694. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +92 -0
  695. package/nitrogen/generated/shared/c++/NotificationButton.hpp +8 -8
  696. package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +3 -19
  697. package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +6 -2
  698. package/nitrogen/generated/shared/c++/Options.hpp +8 -8
  699. package/nitrogen/generated/shared/c++/{IosOutput.hpp → Output.hpp} +18 -18
  700. package/nitrogen/generated/shared/c++/OutputType.hpp +108 -0
  701. package/nitrogen/generated/shared/c++/PlaybackError.hpp +18 -3
  702. package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +100 -0
  703. package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
  704. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +9 -6
  705. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +24 -5
  706. package/nitrogen/generated/shared/c++/RetryConfig.hpp +10 -6
  707. package/nitrogen/generated/shared/c++/SearchMode.hpp +39 -15
  708. package/nitrogen/generated/shared/c++/SearchParams.hpp +9 -2
  709. package/nitrogen/generated/shared/c++/StallReason.hpp +76 -0
  710. package/nitrogen/generated/shared/c++/Track.hpp +24 -5
  711. package/nitrogen/generated/shared/c++/TrackRequest.hpp +93 -0
  712. package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +6 -2
  713. package/package.json +11 -6
  714. package/src/codegen-no-config-variant.test.ts +70 -0
  715. package/src/features/browser-config.test.ts +110 -0
  716. package/src/features/browser-config.ts +293 -0
  717. package/src/features/browser.test.ts +19 -0
  718. package/src/features/browser.ts +58 -201
  719. package/src/features/carConnection.ts +36 -0
  720. package/src/features/errors.ts +68 -2
  721. package/src/features/gate.test.ts +72 -0
  722. package/src/features/gate.ts +155 -0
  723. package/src/features/index.ts +2 -0
  724. package/src/features/metadata.ts +6 -12
  725. package/src/features/nowPlaying.ts +36 -0
  726. package/src/features/output.ts +35 -22
  727. package/src/features/playback/controls.ts +8 -0
  728. package/src/features/playback/index.ts +1 -0
  729. package/src/features/playback/progress.ts +31 -12
  730. package/src/features/playback/trackPlaybackTime.test.ts +66 -0
  731. package/src/features/playback/trackPlaybackTime.ts +106 -0
  732. package/src/features/playback/volume.ts +1 -1
  733. package/src/features/player/options.test.ts +38 -0
  734. package/src/features/player/options.ts +140 -75
  735. package/src/features/player/setup.test.ts +117 -0
  736. package/src/features/player/setup.ts +390 -175
  737. package/src/features/queue/queue.test.ts +36 -0
  738. package/src/features/queue/queue.ts +15 -4
  739. package/src/features/remoteControls.ts +0 -23
  740. package/src/features/sleepTimer.ts +23 -4
  741. package/src/native.ts +5 -0
  742. package/src/native.web.ts +4 -1
  743. package/src/specs/audio-browser.nitro.ts +131 -41
  744. package/src/types/browser-native.ts +19 -6
  745. package/src/types/browser-nodes.ts +160 -13
  746. package/src/types/browser.ts +377 -89
  747. package/src/utils/LazyNativeEmitter.ts +0 -2
  748. package/src/utils/NativeUpdatedValue.ts +0 -2
  749. package/src/utils/artwork.ts +30 -0
  750. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +301 -0
  751. package/src/web/NativeAudioBrowser.ts +365 -158
  752. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +139 -0
  753. package/src/web/TrackPlayer/PlaybackStateMachine.ts +82 -0
  754. package/src/web/TrackPlayer/PlaybackTimer.test.ts +67 -0
  755. package/src/web/TrackPlayer/PlaybackTimer.ts +33 -0
  756. package/src/web/TrackPlayer/Player.dispatch.test.ts +229 -0
  757. package/src/web/TrackPlayer/Player.load.test.ts +86 -0
  758. package/src/web/TrackPlayer/Player.ts +113 -39
  759. package/src/web/TrackPlayer/PlayingStateFactory.test.ts +51 -0
  760. package/src/web/TrackPlayer/PlayingStateFactory.ts +24 -0
  761. package/src/web/TrackPlayer/QueueManager.test.ts +289 -0
  762. package/src/web/TrackPlayer/QueueManager.ts +302 -0
  763. package/src/web/TrackPlayer/QueuePlayer.ts +188 -0
  764. package/src/web/TrackPlayer/RepeatMode.ts +1 -1
  765. package/src/web/TrackPlayer/SleepTimer.ts +38 -3
  766. package/src/web/TrackPlayer/VolumeFader.ts +74 -0
  767. package/src/web/TrackPlayer/index.ts +2 -1
  768. package/src/web/browser/BrowserManager.ts +231 -62
  769. package/src/web/browser/NavigationErrorManager.ts +3 -1
  770. package/src/web/browser/SearchManager.ts +11 -15
  771. package/src/web/browser/__tests__/layer-resolver.test.ts +69 -0
  772. package/src/web/browser/assertBrowsePageShape.test.ts +27 -0
  773. package/src/web/browser/assertBrowsePageShape.ts +27 -0
  774. package/src/web/browser/classifyTrackNavigation.test.ts +41 -0
  775. package/src/web/browser/classifyTrackNavigation.ts +35 -0
  776. package/src/web/browser/parseSearchResponse.test.ts +25 -0
  777. package/src/web/browser/parseSearchResponse.ts +15 -0
  778. package/src/web/http/HttpClient.ts +5 -35
  779. package/src/web/http/RequestConfigBuilder.test.ts +59 -0
  780. package/src/web/http/RequestConfigBuilder.ts +120 -63
  781. package/src/web/playbackErrorKind.test.ts +55 -0
  782. package/src/web/playbackErrorKind.ts +56 -0
  783. package/src/web/player/MediaSessionManager.test.ts +220 -0
  784. package/src/web/player/MediaSessionManager.ts +151 -0
  785. package/src/web/player/NowPlayingManager.test.ts +46 -0
  786. package/src/web/player/NowPlayingManager.ts +6 -5
  787. package/src/web/player/OptionsManager.ts +12 -18
  788. package/src/web/player/RemoteCommandController.test.ts +171 -0
  789. package/src/web/player/RemoteCommandController.ts +166 -0
  790. package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +0 -59
  791. package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +0 -72
  792. package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +0 -36
  793. package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +0 -146
  794. package/ios/CarPlay/Track+CarPlay.swift +0 -63
  795. package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +0 -9
  796. package/ios/Player/PlaybackProgressUpdateManager.swift +0 -68
  797. package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +0 -166
  798. package/lib/commonjs/features/rating.js +0 -2
  799. package/lib/commonjs/features/rating.js.map +0 -1
  800. package/lib/commonjs/types/player.js +0 -2
  801. package/lib/commonjs/types/player.js.map +0 -1
  802. package/lib/commonjs/web/SimpleRouter.test.js +0 -174
  803. package/lib/commonjs/web/SimpleRouter.test.js.map +0 -1
  804. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +0 -293
  805. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  806. package/lib/module/features/rating.js +0 -2
  807. package/lib/module/features/rating.js.map +0 -1
  808. package/lib/module/types/player.js +0 -2
  809. package/lib/module/types/player.js.map +0 -1
  810. package/lib/module/web/SimpleRouter.test.js +0 -174
  811. package/lib/module/web/SimpleRouter.test.js.map +0 -1
  812. package/lib/module/web/TrackPlayer/PlaylistPlayer.js +0 -288
  813. package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
  814. package/lib/typescript/src/features/rating.d.ts +0 -14
  815. package/lib/typescript/src/features/rating.d.ts.map +0 -1
  816. package/lib/typescript/src/native.web.d.ts +0 -3
  817. package/lib/typescript/src/native.web.d.ts.map +0 -1
  818. package/lib/typescript/src/types/player.d.ts +0 -64
  819. package/lib/typescript/src/types/player.d.ts.map +0 -1
  820. package/lib/typescript/src/web/NativeAudioBrowser.d.ts +0 -188
  821. package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +0 -1
  822. package/lib/typescript/src/web/SimpleRouter.d.ts +0 -40
  823. package/lib/typescript/src/web/SimpleRouter.d.ts.map +0 -1
  824. package/lib/typescript/src/web/SimpleRouter.test.d.ts +0 -2
  825. package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +0 -1
  826. package/lib/typescript/src/web/TrackPlayer/Event.d.ts +0 -11
  827. package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +0 -1
  828. package/lib/typescript/src/web/TrackPlayer/Player.d.ts +0 -59
  829. package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +0 -1
  830. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +0 -39
  831. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +0 -1
  832. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +0 -6
  833. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +0 -1
  834. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +0 -4
  835. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +0 -1
  836. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +0 -34
  837. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +0 -1
  838. package/lib/typescript/src/web/TrackPlayer/State.d.ts +0 -13
  839. package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +0 -1
  840. package/lib/typescript/src/web/TrackPlayer/index.d.ts +0 -5
  841. package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +0 -1
  842. package/lib/typescript/src/web/browser/BrowserManager.d.ts +0 -154
  843. package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +0 -1
  844. package/lib/typescript/src/web/browser/FavoriteManager.d.ts +0 -42
  845. package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +0 -1
  846. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +0 -40
  847. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +0 -1
  848. package/lib/typescript/src/web/browser/SearchManager.d.ts +0 -22
  849. package/lib/typescript/src/web/browser/SearchManager.d.ts.map +0 -1
  850. package/lib/typescript/src/web/http/HttpClient.d.ts +0 -25
  851. package/lib/typescript/src/web/http/HttpClient.d.ts.map +0 -1
  852. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +0 -98
  853. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +0 -1
  854. package/lib/typescript/src/web/player/NowPlayingManager.d.ts +0 -34
  855. package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +0 -1
  856. package/lib/typescript/src/web/player/OptionsManager.d.ts +0 -32
  857. package/lib/typescript/src/web/player/OptionsManager.d.ts.map +0 -1
  858. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +0 -69
  859. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +0 -1
  860. package/lib/typescript/src/web/util/shuffle.d.ts +0 -8
  861. package/lib/typescript/src/web/util/shuffle.d.ts.map +0 -1
  862. package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +0 -86
  863. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +0 -147
  864. package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +0 -80
  865. package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +0 -87
  866. package/nitrogen/generated/android/c++/JHeartRating.hpp +0 -57
  867. package/nitrogen/generated/android/c++/JIosOutputType.hpp +0 -85
  868. package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +0 -96
  869. package/nitrogen/generated/android/c++/JPercentageRating.hpp +0 -57
  870. package/nitrogen/generated/android/c++/JRatingType.hpp +0 -73
  871. package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +0 -66
  872. package/nitrogen/generated/android/c++/JStarRating.hpp +0 -57
  873. package/nitrogen/generated/android/c++/JUpdateOptions.hpp +0 -105
  874. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +0 -34
  875. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +0 -105
  876. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +0 -50
  877. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +0 -32
  878. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +0 -47
  879. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +0 -53
  880. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +0 -77
  881. package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +0 -119
  882. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +0 -68
  883. package/nitrogen/generated/ios/swift/HeartRating.swift +0 -29
  884. package/nitrogen/generated/ios/swift/IosOutputType.swift +0 -76
  885. package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +0 -101
  886. package/nitrogen/generated/ios/swift/PercentageRating.swift +0 -29
  887. package/nitrogen/generated/ios/swift/RatingType.swift +0 -60
  888. package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +0 -58
  889. package/nitrogen/generated/ios/swift/StarRating.swift +0 -29
  890. package/nitrogen/generated/ios/swift/ThumbsRating.swift +0 -29
  891. package/nitrogen/generated/ios/swift/UpdateOptions.swift +0 -143
  892. package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +0 -20
  893. package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +0 -109
  894. package/nitrogen/generated/shared/c++/IosOutputType.hpp +0 -112
  895. package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +0 -104
  896. package/nitrogen/generated/shared/c++/RatingType.hpp +0 -96
  897. package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +0 -94
  898. package/nitrogen/generated/shared/c++/UpdateOptions.hpp +0 -111
  899. package/src/features/rating.ts +0 -17
  900. package/src/types/player.ts +0 -69
  901. package/src/web/TrackPlayer/PlaylistPlayer.ts +0 -346
@@ -2,61 +2,59 @@ package com.audiobrowser.player
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import androidx.media3.common.AudioAttributes
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import androidx.core.net.toUri
6
8
  import androidx.media3.common.C
7
- import androidx.media3.common.ForwardingPlayer
8
9
  import androidx.media3.common.HeartRating
9
- import androidx.media3.common.MediaItem
10
- import androidx.media3.common.TrackSelectionParameters
10
+ import androidx.media3.common.MediaMetadata
11
11
  import androidx.media3.database.StandaloneDatabaseProvider
12
12
  import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
13
13
  import androidx.media3.datasource.cache.SimpleCache
14
- import androidx.media3.exoplayer.DefaultRenderersFactory
15
14
  import androidx.media3.exoplayer.ExoPlayer
16
- import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
17
15
  import androidx.media3.session.MediaLibraryService
18
16
  import androidx.media3.session.MediaSession
19
17
  import com.audiobrowser.AudioBrowser
20
18
  import com.audiobrowser.Callbacks
19
+ import com.audiobrowser.browser.displayArtworkSource
20
+ import com.audiobrowser.browser.resolveArtworkUrl
21
+ import com.audiobrowser.browser.unattributedArtworkSource
21
22
  import com.audiobrowser.extension.NumberExt.Companion.toSeconds
22
- import com.margelo.nitro.audiobrowser.ImageContext
23
23
  import com.audiobrowser.model.PlayerSetupOptions
24
24
  import com.audiobrowser.model.PlayerUpdateOptions
25
- import com.audiobrowser.util.AndroidAudioContentTypeFactory
26
25
  import com.audiobrowser.util.CoilBitmapLoader
27
- import com.audiobrowser.util.EqualizerManager
28
26
  import com.audiobrowser.util.NetworkConnectivityMonitor
29
27
  import com.audiobrowser.util.PlayingStateFactory
30
28
  import com.audiobrowser.util.RepeatModeFactory
31
29
  import com.audiobrowser.util.TrackFactory
32
- import com.margelo.nitro.audiobrowser.AndroidPlayerWakeMode
33
30
  import com.margelo.nitro.audiobrowser.AppKilledPlaybackBehavior
34
31
  import com.margelo.nitro.audiobrowser.FavoriteChangedEvent
32
+ import com.margelo.nitro.audiobrowser.GateEvent
33
+ import com.margelo.nitro.audiobrowser.GateReason
34
+ import com.margelo.nitro.audiobrowser.ImageContext
35
+ import com.margelo.nitro.audiobrowser.ImageSource
36
+ import com.margelo.nitro.audiobrowser.NativeGateRequest
35
37
  import com.margelo.nitro.audiobrowser.NowPlayingMetadata
36
- import com.margelo.nitro.audiobrowser.NowPlayingUpdate
37
38
  import com.margelo.nitro.audiobrowser.Playback
38
39
  import com.margelo.nitro.audiobrowser.PlaybackActiveTrackChangedEvent
39
40
  import com.margelo.nitro.audiobrowser.PlaybackError
41
+ import com.margelo.nitro.audiobrowser.PlaybackPlayWhenReadyChangedEvent
40
42
  import com.margelo.nitro.audiobrowser.PlaybackProgressUpdatedEvent
41
43
  import com.margelo.nitro.audiobrowser.PlaybackQueueEndedEvent
42
44
  import com.margelo.nitro.audiobrowser.PlaybackState
43
45
  import com.margelo.nitro.audiobrowser.PlayingState
44
- import com.margelo.nitro.audiobrowser.RatingType
45
- import com.margelo.nitro.audiobrowser.RemoteJumpBackwardEvent
46
- import com.margelo.nitro.audiobrowser.RemoteJumpForwardEvent
47
- import com.margelo.nitro.audiobrowser.RemoteSeekEvent
48
46
  import com.margelo.nitro.audiobrowser.RepeatMode
49
47
  import com.margelo.nitro.audiobrowser.SearchParams
50
- import com.margelo.nitro.audiobrowser.SleepTimer as NitroSleepTimer
51
- import com.margelo.nitro.audiobrowser.SleepTimerEndOfTrack
52
- import com.margelo.nitro.audiobrowser.SleepTimerTime
53
48
  import com.margelo.nitro.audiobrowser.Track
54
- import com.margelo.nitro.core.NullType
55
49
  import java.io.File
50
+ import java.io.IOException
56
51
  import java.util.concurrent.TimeUnit
57
52
  import kotlin.time.Duration.Companion.seconds
58
53
  import kotlinx.coroutines.CompletableDeferred
54
+ import kotlinx.coroutines.Dispatchers
55
+ import kotlinx.coroutines.MainScope
59
56
  import kotlinx.coroutines.TimeoutCancellationException
57
+ import kotlinx.coroutines.withContext
60
58
  import kotlinx.coroutines.withTimeout
61
59
  import timber.log.Timber
62
60
 
@@ -66,25 +64,50 @@ class Player(internal val context: Context) {
66
64
  get() = options.appKilledPlaybackBehavior
67
65
 
68
66
  private var options = PlayerUpdateOptions()
67
+ private val mainHandler = Handler(Looper.getMainLooper())
69
68
  internal var callbacks: Callbacks? = null
70
69
  private lateinit var mediaSession: MediaSession
71
70
  val networkMonitor: NetworkConnectivityMonitor = NetworkConnectivityMonitor(context)
72
- private var equalizerManager: EqualizerManager? = null
71
+ internal val equalizer =
72
+ EqualizerController(onSettingsChanged = { settings -> callbacks?.onEqualizerChanged(settings) })
73
73
  private val mediaSessionCallback = MediaSessionCallback(this)
74
74
  internal val playbackStateStore = PlaybackStateStore(this)
75
- private val sleepTimer =
76
- object : SleepTimer() {
77
- override fun onComplete() {
78
- Timber.d("Sleep timer completed, stopping playback")
79
- stop()
80
- callbacks?.onSleepTimerChanged(NitroSleepTimer.create(NullType.NULL))
81
- }
82
- }
75
+ internal val volumeFader = VolumeFader(getVolume = { volume }, setVolume = { volume = it })
76
+ internal val sleepTimer =
77
+ SleepTimerManager(
78
+ volumeFader,
79
+ pause = { pause() },
80
+ onChanged = { callbacks?.onSleepTimerChanged(it) },
81
+ )
83
82
 
84
83
  lateinit var exoPlayer: ExoPlayer
85
84
  lateinit var forwardingPlayer: androidx.media3.common.Player
86
85
  /** Thread-safe cache of playWhenReady for access from non-main threads (e.g., retry policy) */
87
86
  @Volatile internal var playWhenReadyCache = false
87
+
88
+ /**
89
+ * Whether the current load has ever produced audio — selects between the retry policy's
90
+ * first-connect and recovery budgets. Volatile: read from ExoPlayer's loader thread. Set when
91
+ * playback starts, cleared on media-item transition (a new load starts unproven); retry reloads
92
+ * of the same load keep it.
93
+ */
94
+ @Volatile internal var hasPlayedCache = false
95
+
96
+ /** Last playWhenReady value emitted to JS — see [emitPlayWhenReadyChanged]. */
97
+ private var lastEmittedPlayWhenReady: Boolean? = null
98
+
99
+ /**
100
+ * Emits the playWhenReady change to JS exactly once per value. Two producers exist: the
101
+ * synchronous clear at ENDED in [setPlaybackState] (ordered before the state / queue-ended
102
+ * callbacks, matching iOS) and the async media3 listener (authoritative for changes ExoPlayer
103
+ * makes on its own, e.g. audio focus); the guard deduplicates their overlap.
104
+ */
105
+ internal fun emitPlayWhenReadyChanged(value: Boolean) {
106
+ if (lastEmittedPlayWhenReady == value) return
107
+ lastEmittedPlayWhenReady = value
108
+ callbacks?.onPlaybackPlayWhenReadyChanged(PlaybackPlayWhenReadyChangedEvent(value))
109
+ }
110
+
88
111
  private lateinit var mediaFactory: MediaFactory
89
112
  private lateinit var loadControl: DynamicLoadControl
90
113
  private var automaticBufferManager: AutomaticBufferManager? = null
@@ -95,49 +118,39 @@ class Player(internal val context: Context) {
95
118
  */
96
119
  @Volatile private var pendingNetworkRetry = false
97
120
 
98
- private var _browser: AudioBrowser? = null
99
121
  private var browserRegistered = CompletableDeferred<AudioBrowser>()
100
122
  private var _coilBitmapLoader: CoilBitmapLoader? = null
101
123
 
102
124
  /**
103
- * Coil ImageLoader for SVG pre-rendering in Android Auto browse items.
104
- * Set by Service after creation.
125
+ * Coil ImageLoader for SVG pre-rendering in Android Auto browse items. Set by Service after
126
+ * creation.
105
127
  */
106
128
  var imageLoader: coil3.ImageLoader? = null
107
129
 
130
+ /** Registry that maps content:// tokens to artwork URLs for the browse content provider. */
131
+ val browseArtworkRegistry = com.audiobrowser.browser.BrowseArtworkRegistry()
132
+
108
133
  /**
109
- * Set the CoilBitmapLoader for artwork URL transformation. Called from Service after creation.
134
+ * Set the CoilBitmapLoader for display-time bitmap loading. Called from Service after creation.
110
135
  */
111
136
  var coilBitmapLoader: CoilBitmapLoader?
112
137
  get() = _coilBitmapLoader
113
138
  set(value) {
114
139
  _coilBitmapLoader = value
115
- // If browser is already set, wire up the resolver
116
- _browser?.let { audioBrowser -> wireUpArtworkResolver(audioBrowser, value) }
117
140
  }
118
141
 
119
- var browser: AudioBrowser?
120
- get() = _browser
121
- set(value) {
122
- _browser = value
123
- value?.let { audioBrowser ->
124
- // Wire up artwork URL resolver
125
- // This ensures the resolver is available when onGetChildren resumes
126
- wireUpArtworkResolver(audioBrowser, _coilBitmapLoader)
127
-
128
- // NOTE: We do NOT complete browserRegistered here.
129
- // Configuration (routes/tabs) must be set before Android Auto can browse.
130
- // Call notifyBrowserConfigurationReady() after configuration is set.
131
- }
132
- }
142
+ // NOTE: setting the browser does NOT complete browserRegistered.
143
+ // Configuration (routes/tabs) must be set before Android Auto can browse.
144
+ // Call notifyBrowserConfigurationReady() after configuration is set.
145
+ var browser: AudioBrowser? = null
133
146
 
134
147
  /**
135
- * Notifies that the browser configuration is ready (routes/tabs are configured).
136
- * This should be called after setting the browser AND its configuration.
137
- * Only then will Android Auto be able to browse content.
148
+ * Notifies that the browser configuration is ready (routes/tabs are configured). This should be
149
+ * called after setting the browser AND its configuration. Only then will Android Auto be able to
150
+ * browse content.
138
151
  */
139
152
  fun notifyBrowserConfigurationReady() {
140
- val audioBrowser = _browser ?: return
153
+ val audioBrowser = browser ?: return
141
154
 
142
155
  if (!browserRegistered.isCompleted) {
143
156
  Timber.d("Browser configuration ready - completing deferred")
@@ -162,27 +175,6 @@ class Player(internal val context: Context) {
162
175
  }
163
176
  }
164
177
 
165
- /**
166
- * Wires up the artwork URL resolver between BrowserManager and CoilBitmapLoader. This enables
167
- * artwork URL transformation during browse-time (Phase 2).
168
- */
169
- private fun wireUpArtworkResolver(audioBrowser: AudioBrowser, loader: CoilBitmapLoader?) {
170
- val browserManager = audioBrowser.browserManager
171
- if (loader != null) {
172
- Timber.d("Wiring up artwork URL resolver")
173
- browserManager.artworkUrlResolver = { track, perRouteConfig, imageContext ->
174
- loader.transformArtworkUrlForTrack(track, perRouteConfig, imageContext)
175
- }
176
- // Clear content cache so that content resolved before the resolver was wired up
177
- // will be re-fetched with artwork transformation applied
178
- browserManager.clearContentCache()
179
- Timber.d("Cleared content cache after wiring artwork resolver")
180
- } else {
181
- Timber.d("CoilBitmapLoader not available - artwork URL transformation disabled")
182
- browserManager.artworkUrlResolver = null
183
- }
184
- }
185
-
186
178
  /**
187
179
  * Suspends until the browser is registered, with a timeout.
188
180
  *
@@ -200,133 +192,16 @@ class Player(internal val context: Context) {
200
192
  throw e
201
193
  }
202
194
 
203
- /**
204
- * ForwardingPlayer that intercepts external player actions and dispatches them to callbacks.
205
- *
206
- * This class blocks all external media item modifications to delegate control to RNAB. These
207
- * overrides prevent media controllers (like Android Auto, notifications) from directly modifying
208
- * the queue, ensuring all queue changes go through the RNAB API for proper state management and
209
- * event handling.
210
- */
211
- private inner class InterceptingPlayer(player: ExoPlayer) : ForwardingPlayer(player) {
212
-
213
- override fun setMediaItems(mediaItems: MutableList<MediaItem>, resetPosition: Boolean) {
214
- return super.setMediaItems(mediaItems, resetPosition)
215
- }
216
-
217
- override fun addMediaItems(mediaItems: MutableList<MediaItem>) {
218
- return super.addMediaItems(mediaItems)
219
- }
220
-
221
- override fun addMediaItems(index: Int, mediaItems: MutableList<MediaItem>) {
222
- return super.addMediaItems(index, mediaItems)
223
- }
224
-
225
- override fun setMediaItems(
226
- mediaItems: MutableList<MediaItem>,
227
- startIndex: Int,
228
- startPositionMs: Long,
229
- ) {
230
- return super.setMediaItems(mediaItems, startIndex, startPositionMs)
231
- }
232
-
233
- override fun setMediaItems(mediaItems: MutableList<MediaItem>) {
234
- return super.setMediaItems(mediaItems)
235
- }
236
-
237
- // Intercept playback controls and dispatch to callbacks or fall back to default behavior
238
- override fun play() {
239
- if (callbacks?.handleRemotePlay() != true) {
240
- super.play()
241
- }
242
- }
243
-
244
- override fun pause() {
245
- if (callbacks?.handleRemotePause() != true) {
246
- super.pause()
247
- }
248
- }
249
-
250
- override fun seekToNext() {
251
- Timber.Forest.d("InterceptingPlayer.seekToNext() called")
252
- if (callbacks?.handleRemoteNext() != true) {
253
- super.seekToNext()
254
- }
255
- }
256
-
257
- override fun seekToNextMediaItem() {
258
- Timber.Forest.d("InterceptingPlayer.seekToNextMediaItem() called")
259
- if (callbacks?.handleRemoteNext() != true) {
260
- super.seekToNextMediaItem()
261
- }
262
- }
263
-
264
- override fun seekToPrevious() {
265
- Timber.Forest.d("InterceptingPlayer.seekToPrevious() called")
266
- if (callbacks?.handleRemotePrevious() != true) {
267
- super.seekToPrevious()
268
- }
269
- }
270
-
271
- override fun seekToPreviousMediaItem() {
272
- Timber.Forest.d("InterceptingPlayer.seekToPreviousMediaItem() called")
273
- if (callbacks?.handleRemotePrevious() != true) {
274
- super.seekToPreviousMediaItem()
275
- }
276
- }
277
-
278
- override fun seekForward() {
279
- Timber.Forest.d("InterceptingPlayer.seekForward() called")
280
- if (
281
- callbacks?.handleRemoteJumpForward(
282
- RemoteJumpForwardEvent(interval = options.forwardJumpInterval)
283
- ) != true
284
- ) {
285
- super.seekForward()
286
- }
287
- }
288
-
289
- override fun seekBack() {
290
- if (
291
- callbacks?.handleRemoteJumpBackward(
292
- RemoteJumpBackwardEvent(interval = options.backwardJumpInterval)
293
- ) != true
294
- ) {
295
- super.seekBack()
296
- }
297
- }
298
-
299
- override fun stop() {
300
- if (callbacks?.handleRemoteStop() != true) {
301
- super.stop()
302
- }
303
- }
304
-
305
- override fun seekTo(mediaItemIndex: Int, positionMs: Long) {
306
- if (
307
- callbacks?.handleRemoteSeek(RemoteSeekEvent(position = positionMs.toDouble() / 1000.0)) !=
308
- true
309
- ) {
310
- super.seekTo(mediaItemIndex, positionMs)
311
- }
312
- }
313
-
314
- override fun seekTo(positionMs: Long) {
315
- if (
316
- callbacks?.handleRemoteSeek(RemoteSeekEvent(position = positionMs.toDouble() / 1000.0)) !=
317
- true
318
- ) {
319
- super.seekTo(positionMs)
320
- }
321
- }
322
- }
323
-
324
195
  private lateinit var playerListener: PlayerListener
325
196
  private var cache: SimpleCache? = null
326
197
 
327
- private val progressUpdateManager: PlaybackProgressUpdateManager by lazy {
328
- PlaybackProgressUpdateManager {
329
- val index = currentIndex ?: return@PlaybackProgressUpdateManager
198
+ private val progressTimer: PlaybackTimer by lazy {
199
+ PlaybackTimer(
200
+ isActive = {
201
+ it == PlaybackState.LOADING || it == PlaybackState.BUFFERING || it == PlaybackState.PLAYING
202
+ }
203
+ ) {
204
+ val index = currentIndex ?: return@PlaybackTimer
330
205
  val event =
331
206
  PlaybackProgressUpdatedEvent(
332
207
  position = position.toSeconds(),
@@ -338,7 +213,12 @@ class Player(internal val context: Context) {
338
213
  }
339
214
  }
340
215
 
216
+ private val intervalTimer: PlaybackTimer by lazy {
217
+ PlaybackTimer(isActive = { it == PlaybackState.PLAYING }) { callbacks?.onPlaybackInterval() }
218
+ }
219
+
341
220
  internal var playingState: PlayingState = PlayingState(false, false)
221
+ private set
342
222
 
343
223
  val currentTrack: Track?
344
224
  get() = exoPlayer.currentMediaItem?.let { TrackFactory.fromMedia3(it) }
@@ -352,9 +232,6 @@ class Player(internal val context: Context) {
352
232
  internal var playbackState: PlaybackState = PlaybackState.NONE
353
233
  private set
354
234
 
355
- /** Current now playing metadata override (null = use track metadata) */
356
- private var nowPlayingOverride: NowPlayingUpdate? = null
357
-
358
235
  fun getPlayback(): Playback {
359
236
  return Playback(playbackState, playbackError)
360
237
  }
@@ -363,9 +240,33 @@ class Player(internal val context: Context) {
363
240
  return playingState
364
241
  }
365
242
 
243
+ /**
244
+ * Re-derives [playingState] from the current `playWhenReady` + [playbackState] and emits
245
+ * `onPlaybackPlayingState` when it changed. The only writer of [playingState] — called from the
246
+ * state machine ([setPlaybackState]) and from [PlayerListener.onPlayWhenReadyChanged], the change
247
+ * points of its two inputs.
248
+ */
249
+ internal fun refreshPlayingState() {
250
+ val newPlayingState = PlayingStateFactory.derive(playWhenReady, playbackState)
251
+ if (newPlayingState == playingState) return
252
+ Timber.d(
253
+ "PlayingState changed: playing=${newPlayingState.playing}, buffering=${newPlayingState.buffering}"
254
+ )
255
+ playingState = newPlayingState
256
+ callbacks?.onPlaybackPlayingState(playingState)
257
+ }
258
+
366
259
  var playWhenReady: Boolean
367
260
  get() = exoPlayer.playWhenReady
368
261
  set(value) {
262
+ // Raising the intent at STATE_ENDED must replay, not silently no-op:
263
+ // ExoPlayer's setPlayWhenReady(true) never restarts an ended player (the
264
+ // ENDED→seekToDefaultPosition replay lives in media3's media-button
265
+ // path, not the Player API). In the setter so every writer gets it —
266
+ // play(), togglePlayback() and JS setPlayWhenReady alike.
267
+ if (value && exoPlayer.playbackState == ExoPlayer.STATE_ENDED) {
268
+ exoPlayer.seekToDefaultPosition()
269
+ }
369
270
  playWhenReadyCache = value
370
271
  exoPlayer.playWhenReady = value
371
272
  }
@@ -398,8 +299,6 @@ class Player(internal val context: Context) {
398
299
  val isPlaying
399
300
  get() = exoPlayer.isPlaying
400
301
 
401
- var ratingType: RatingType = RatingType.NONE
402
-
403
302
  var repeatMode: RepeatMode
404
303
  get() = RepeatModeFactory.fromMedia3(exoPlayer.repeatMode)
405
304
  internal set(value) {
@@ -429,8 +328,14 @@ class Player(internal val context: Context) {
429
328
  .map { index -> TrackFactory.fromMedia3(exoPlayer.getMediaItemAt(index)) }
430
329
  .toTypedArray()
431
330
 
432
- val isLastTrack: Boolean
433
- get() = exoPlayer.currentMediaItemIndex == exoPlayer.mediaItemCount - 1
331
+ /**
332
+ * Whether nothing follows the current item in *playback order* — shuffle- and repeat-aware via
333
+ * media3's own order computation (the linear `currentMediaItemIndex == count - 1` check missed a
334
+ * shuffled queue whose playback order ends on a different linear index). Matches iOS's
335
+ * `isLastInPlaybackOrder`.
336
+ */
337
+ val isLastInPlaybackOrder: Boolean
338
+ get() = exoPlayer.nextMediaItemIndex == C.INDEX_UNSET
434
339
 
435
340
  /**
436
341
  * The source path from which the current queue was expanded (e.g., from a contextual URL). Used
@@ -467,6 +372,12 @@ class Player(internal val context: Context) {
467
372
  fun setup(setupOptions: PlayerSetupOptions) {
468
373
  Timber.Forest.d("Setting up player with new options")
469
374
 
375
+ nowPlaying.enabled = setupOptions.autoUpdateNowPlayingMetadata
376
+ // Wrap the Nitro Func into a plain suspend call so the updater (and its tests) never touch
377
+ // bridge types; the double-await depth is unchanged (invoke -> bridge Promise -> JS promise).
378
+ nowPlaying.formatter =
379
+ setupOptions.nowPlayingMetadataFormatter?.let { f -> { params -> f.invoke(params).await() } }
380
+
470
381
  val isInitialSetup = !::exoPlayer.isInitialized
471
382
 
472
383
  if (!isInitialSetup) {
@@ -476,6 +387,9 @@ class Player(internal val context: Context) {
476
387
  }
477
388
 
478
389
  if (setupOptions.maxCacheSize > 0) {
390
+ // SimpleCache locks its folder — re-setup must release the previous
391
+ // instance first or the constructor throws.
392
+ cache?.release()
479
393
  cache =
480
394
  SimpleCache(
481
395
  File(context.cacheDir, "RNAB"),
@@ -489,94 +403,46 @@ class Player(internal val context: Context) {
489
403
  cache = null
490
404
  }
491
405
 
492
- // Recreate ExoPlayer with new setup options
493
- val renderer = DefaultRenderersFactory(context)
494
- renderer.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
495
-
496
- // Create bandwidth meter for adaptive bitrate selection in HLS/DASH
497
- val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
498
-
499
- loadControl = run {
500
- val minBuffer = setupOptions.minBuffer.toInt()
501
- val maxBuffer = setupOptions.maxBuffer.toInt()
502
- val playBuffer = setupOptions.playBuffer.toInt()
503
- // When automatic (rebufferBuffer is null), start at playBuffer and let AutomaticBufferManager
504
- // adjust
505
- // When fixed (rebufferBuffer is set), use that value
506
- val playAfterRebuffer = setupOptions.rebufferBuffer?.toInt() ?: playBuffer
507
- val backBuffer = setupOptions.backBuffer.toInt()
508
- val config =
509
- BufferConfig(
510
- minBufferMs = minBuffer,
511
- maxBufferMs = maxBuffer,
512
- bufferForPlaybackMs = playBuffer,
513
- bufferForPlaybackAfterRebufferMs = playAfterRebuffer,
514
- backBufferMs = backBuffer,
515
- )
516
- DynamicLoadControl(initialConfig = config)
517
- }
518
- // Create MediaFactory with reference to browser for media URL transformation
519
- // shouldRetry checks playWhenReady to avoid retrying when paused (e.g., another app took audio
520
- // focus). Uses thread-safe cache since this is called from ExoPlayer's playback thread.
521
- // isOnline and onRetryPending enable network-aware retry acceleration.
522
- mediaFactory =
523
- MediaFactory(
406
+ // One engine generation: load control, media factory, ExoPlayer (pure construction —
407
+ // see buildPlayerEngine; lifecycle and wiring stay here).
408
+ val engine =
409
+ buildPlayerEngine(
524
410
  context,
411
+ setupOptions,
525
412
  cache,
526
- setupOptions.retryPolicy,
527
413
  shouldRetry = { playWhenReadyCache },
528
414
  isOnline = { networkMonitor.getOnline() },
529
- onRetryPending = { isNetworkError ->
415
+ hasPlayed = { hasPlayedCache },
416
+ onRetryPending = { exception, isNetworkError ->
530
417
  // Track pending network retries for acceleration when connectivity returns
531
418
  pendingNetworkRetry = isNetworkError
532
419
  if (isNetworkError) {
533
420
  Timber.d("Network retry pending, will accelerate on connectivity restoration")
534
421
  }
422
+ reportRetryingError(exception)
535
423
  },
536
- transferListener = bandwidthMeter,
537
- ) { url ->
538
- browser?.getMediaRequestConfig(url)
539
- }
540
-
541
- exoPlayer =
542
- ExoPlayer.Builder(context)
543
- .setRenderersFactory(renderer)
544
- .setBandwidthMeter(bandwidthMeter)
545
- .setHandleAudioBecomingNoisy(setupOptions.handleAudioBecomingNoisy)
546
- .setMediaSourceFactory(mediaFactory)
547
- .setWakeMode(
548
- when (setupOptions.wakeMode) {
549
- AndroidPlayerWakeMode.NONE -> C.WAKE_MODE_NONE
550
- AndroidPlayerWakeMode.LOCAL -> C.WAKE_MODE_LOCAL
551
- AndroidPlayerWakeMode.NETWORK -> C.WAKE_MODE_NETWORK
552
- }
553
- )
554
- .setLoadControl(loadControl)
555
- .setName("AudioBrowser")
556
- .build()
557
- exoPlayer.setAudioAttributes(
558
- AudioAttributes.Builder()
559
- .setUsage(C.USAGE_MEDIA)
560
- .setContentType(AndroidAudioContentTypeFactory.toMedia3(setupOptions.audioContentType))
561
- .build(),
562
- true, // handle audio focus
563
- )
424
+ resolveMediaConfig = { url -> browser?.getMediaRequestConfig(url) },
425
+ )
426
+ loadControl = engine.loadControl
427
+ mediaFactory = engine.mediaFactory
428
+ exoPlayer = engine.exoPlayer
564
429
 
565
- // Apply setup-specific options
566
- setupOptions.audioOffload?.let {
567
- val audioOffloadPreferences =
568
- TrackSelectionParameters.AudioOffloadPreferences.Builder()
569
- .setAudioOffloadMode(
570
- TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED
571
- )
572
- .setIsGaplessSupportRequired(it.gaplessSupportRequired)
573
- .setIsSpeedChangeSupportRequired(it.rateChangeSupportRequired)
574
- .build()
575
- exoPlayer.trackSelectionParameters.audioOffloadPreferences = audioOffloadPreferences
430
+ // A rebuilt engine starts paused and fires no change event — sync the
431
+ // eager cache and tell JS, or the stale true sticks (and the emit dedupe
432
+ // would swallow the next real rise).
433
+ if (!isInitialSetup) {
434
+ playWhenReadyCache = false
435
+ emitPlayWhenReadyChanged(false)
576
436
  }
577
437
 
578
438
  // Recreate forwarding player with new ExoPlayer
579
- forwardingPlayer = InterceptingPlayer(exoPlayer)
439
+ forwardingPlayer =
440
+ InterceptingPlayer(
441
+ exoPlayer,
442
+ { callbacks },
443
+ { options },
444
+ keepSessionAliveOnError = setupOptions.keepSessionAliveOnError,
445
+ )
580
446
 
581
447
  if (isInitialSetup) {
582
448
  // Initial setup - create player listener and emit initial state
@@ -585,7 +451,7 @@ class Player(internal val context: Context) {
585
451
  callbacks?.onPlaybackChanged(Playback(PlaybackState.NONE, null))
586
452
 
587
453
  // Initialize equalizer with audio session ID
588
- initializeEqualizer()
454
+ equalizer.initialize(exoPlayer.audioSessionId)
589
455
  } else {
590
456
  // Re-setup - re-add listener and update MediaSession
591
457
  forwardingPlayer.addListener(playerListener)
@@ -620,8 +486,8 @@ class Player(internal val context: Context) {
620
486
  }
621
487
 
622
488
  /**
623
- * Starts observing network connectivity changes and invokes the callback when state changes.
624
- * Also handles accelerating pending network retries when connectivity is restored.
489
+ * Starts observing network connectivity changes and invokes the callback when state changes. Also
490
+ * handles accelerating pending network retries when connectivity is restored.
625
491
  *
626
492
  * @param scope The coroutine scope to use for observation
627
493
  */
@@ -629,6 +495,10 @@ class Player(internal val context: Context) {
629
495
  networkMonitor.observeOnline(scope) { isOnline ->
630
496
  callbacks?.onOnlineChanged(isOnline)
631
497
 
498
+ // Re-render now-playing so the formatter's stall classification (buffering vs offline) tracks
499
+ // connectivity immediately, not only on the next playback transition.
500
+ nowPlaying.render()
501
+
632
502
  // Accelerate pending network retry when connectivity is restored
633
503
  if (isOnline && pendingNetworkRetry) {
634
504
  Timber.d("Network restored with pending retry, triggering immediate retry")
@@ -804,36 +674,18 @@ class Player(internal val context: Context) {
804
674
  * controllers without interrupting playback.
805
675
  */
806
676
  fun setActiveTrackFavorited(favorited: Boolean) {
807
- val index = exoPlayer.currentMediaItemIndex
808
- if (index == C.INDEX_UNSET) return
809
-
810
677
  val currentTrack = this.currentTrack ?: return
811
678
 
812
- // Update native favorites cache (only tracks with src can be favorited)
679
+ // Persist to the native favorites cache regardless of player state — the
680
+ // user's gesture is durable even when there's no active media item to
681
+ // update (queue tear-down, between-track gaps, etc.).
813
682
  currentTrack.src?.let { src -> browser?.browserManager?.updateFavorite(src, favorited) }
814
683
 
684
+ val index = exoPlayer.currentMediaItemIndex
685
+ if (index == C.INDEX_UNSET) return
686
+
815
687
  // Create updated Track with new favorited state
816
- val updatedTrack =
817
- Track(
818
- url = currentTrack.url,
819
- src = currentTrack.src,
820
- artwork = currentTrack.artwork,
821
- artworkSource = currentTrack.artworkSource,
822
- artworkCarPlayTinted = currentTrack.artworkCarPlayTinted,
823
- title = currentTrack.title,
824
- subtitle = currentTrack.subtitle,
825
- artist = currentTrack.artist,
826
- album = currentTrack.album,
827
- description = currentTrack.description,
828
- genre = currentTrack.genre,
829
- duration = currentTrack.duration,
830
- style = currentTrack.style,
831
- childrenStyle = currentTrack.childrenStyle,
832
- favorited = favorited,
833
- groupTitle = currentTrack.groupTitle,
834
- live = currentTrack.live,
835
- imageRow = currentTrack.imageRow,
836
- )
688
+ val updatedTrack = currentTrack.copy(favorited = favorited)
837
689
 
838
690
  // Use buildUpon() on the existing MediaItem to update only the metadata
839
691
  // This preserves internal references and avoids playback interruption
@@ -881,78 +733,152 @@ class Player(internal val context: Context) {
881
733
  // MARK: - Now Playing Metadata
882
734
 
883
735
  /**
884
- * Updates the now playing notification metadata. Pass null to clear overrides and revert to track
885
- * metadata.
736
+ * Now Playing rendering (flash/override/formatter precedence, dedupe, stale guards, artwork
737
+ * keying) lives in [NowPlayingUpdater]; this object is its platform surface — current playback
738
+ * reads plus MediaItem stamping via [replaceMediaItem], and artwork resolution via the browser.
886
739
  */
887
- fun updateNowPlaying(update: NowPlayingUpdate?) {
888
- nowPlayingOverride = update
889
- applyNowPlayingMetadata()
890
- }
740
+ val nowPlaying: NowPlayingUpdater =
741
+ NowPlayingUpdater(
742
+ object : NowPlayingSurface {
743
+ override val currentIndex
744
+ get() = this@Player.currentIndex
745
+
746
+ override val currentTrack
747
+ get() = this@Player.currentTrack
748
+
749
+ override val playbackState
750
+ get() = this@Player.playbackState
751
+
752
+ override val playbackError
753
+ get() = this@Player.playbackError
754
+
755
+ override val playWhenReady
756
+ get() = exoPlayer.playWhenReady
757
+
758
+ override val isRebuffering
759
+ get() = loadControl.isRebuffering
760
+
761
+ override val isOnline
762
+ get() = networkMonitor.getOnline()
763
+
764
+ override val hasNowPlayingArtworkConfig
765
+ get() = browser?.browserManager?.config?.nowPlayingArtwork != null
766
+
767
+ override fun stampFields(
768
+ index: Int,
769
+ track: Track,
770
+ title: String?,
771
+ secondaryLine: String?,
772
+ album: String?,
773
+ ) =
774
+ restampMediaItem(index, track) {
775
+ setTitle(title)
776
+ .setDisplayTitle(title)
777
+ .setArtist(secondaryLine)
778
+ // Android Auto reads DISPLAY_SUBTITLE (not ARTIST) once DISPLAY_TITLE is set, so
779
+ // mirror the line into `subtitle`; `artist` still drives the lock screen /
780
+ // notification.
781
+ .setSubtitle(secondaryLine)
782
+ .setAlbumTitle(album)
783
+ }
784
+
785
+ override fun stampArtwork(index: Int, track: Track, uri: String) =
786
+ restampMediaItem(index, track) { setArtworkUri(uri.toUri()) }
787
+
788
+ override suspend fun resolveNowPlayingArtwork(track: Track, sizePx: Double): String? {
789
+ val browserManager = browser?.browserManager ?: return null
790
+ val config = browserManager.config.nowPlayingArtwork ?: return null
791
+ val resolved =
792
+ try {
793
+ browserManager.resolveArtworkUrl(track, config, ImageContext(sizePx, sizePx))
794
+ } catch (e: Exception) {
795
+ Timber.e(e, "Failed to resolve now-playing artwork for track id=${track.id}")
796
+ null
797
+ }
798
+ val uri = resolved?.uri?.takeIf { it.isNotEmpty() } ?: return null
799
+ // Remember how this URI was produced: Media3 hands it back to the bitmap loader, which
800
+ // re-resolves Track-first (with the nowPlayingArtwork kind, not the global artwork
801
+ // config).
802
+ browserManager.artworkResolutions.register(uri, track, config)
803
+ return uri
804
+ }
891
805
 
892
- /** Gets the current now playing metadata (override if set, else track metadata). */
893
- fun getNowPlaying(): NowPlayingMetadata? {
894
- val track = currentTrack ?: return null
895
- val override = nowPlayingOverride
896
-
897
- return NowPlayingMetadata(
898
- elapsedTime = null,
899
- title = override?.title ?: track.title,
900
- album = track.album,
901
- artist = override?.artist ?: track.artist,
902
- duration = track.duration,
903
- artwork = track.artwork,
904
- description = track.description,
905
- mediaId = track.src ?: track.url,
906
- genre = track.genre,
907
- rating = null, // TODO: map track.favorited to rating if needed
806
+ override fun emitNowPlayingChanged(metadata: NowPlayingMetadata) {
807
+ callbacks?.onNowPlayingChanged(metadata)
808
+ }
809
+ },
810
+ MainScope(),
908
811
  )
909
- }
910
812
 
911
- /**
912
- * Clears the now playing override when track changes. Called from
913
- * PlayerListener.onMediaItemTransition.
914
- */
915
- internal fun clearNowPlayingOverride() {
916
- nowPlayingOverride = null
813
+ /** Republishes the item at [index] with mutated metadata, preserving uri and Track tag. */
814
+ private fun restampMediaItem(
815
+ index: Int,
816
+ track: Track,
817
+ mutate: MediaMetadata.Builder.() -> MediaMetadata.Builder,
818
+ ) {
819
+ val currentMediaItem = exoPlayer.getMediaItemAt(index)
820
+ val updatedMediaItem =
821
+ currentMediaItem
822
+ .buildUpon()
823
+ .setUri(currentMediaItem.localConfiguration?.uri)
824
+ .setMediaMetadata(currentMediaItem.mediaMetadata.buildUpon().mutate().build())
825
+ .setTag(track)
826
+ .build()
827
+ exoPlayer.replaceMediaItem(index, updatedMediaItem)
917
828
  }
918
829
 
919
830
  /**
920
831
  * Resets the retry timer when track changes. Called from PlayerListener.onMediaItemTransition.
921
832
  */
833
+ /**
834
+ * A restart from a terminal error begins a new load of the same track: fresh retry budgets and
835
+ * unproven playback, so the user's tap yields a visible first-connect retry window instead of a
836
+ * single silent attempt — the same behavior as re-selecting the track (ADR 0004). Hooked off the
837
+ * player error clearing: every recovery path (retry(), play(), the transport controls'
838
+ * idle-error recovery) goes through exoPlayer.prepare(), which clears the error.
839
+ */
840
+ internal fun resetForNewLoad() {
841
+ resetRetryTimer()
842
+ hasPlayedCache = false
843
+ }
844
+
922
845
  internal fun resetRetryTimer() {
923
846
  mediaFactory.resetRetryTimer()
924
847
  }
925
848
 
926
849
  /**
927
- * Applies the current now playing metadata to the media notification. Uses the override if set,
928
- * otherwise uses track metadata.
850
+ * Finds the queue Track whose published artwork URI matches [uri], for display-time artwork
851
+ * resolution of app-supplied queue tracks that never went through browse (so they are not in the
852
+ * resolution registry). Must run on the main thread (ExoPlayer access).
929
853
  */
930
- private fun applyNowPlayingMetadata() {
931
- val index = currentIndex ?: return
932
- val track = currentTrack ?: return
933
- val override = nowPlayingOverride
934
-
935
- val currentMediaItem = exoPlayer.getMediaItemAt(index)
936
- val updatedMetadata =
937
- currentMediaItem.mediaMetadata
938
- .buildUpon()
939
- .setTitle(override?.title ?: track.title)
940
- .setDisplayTitle(override?.title ?: track.title)
941
- .setArtist(override?.artist ?: track.artist)
942
- .build()
943
-
944
- val updatedMediaItem =
945
- currentMediaItem
946
- .buildUpon()
947
- .setUri(currentMediaItem.localConfiguration?.uri)
948
- .setMediaMetadata(updatedMetadata)
949
- .setTag(track)
950
- .build()
951
-
952
- exoPlayer.replaceMediaItem(index, updatedMediaItem)
854
+ private fun findQueueTrackByArtworkUri(uri: String): Track? {
855
+ for (i in 0 until exoPlayer.mediaItemCount) {
856
+ val item = exoPlayer.getMediaItemAt(i)
857
+ if (item.mediaMetadata.artworkUri?.toString() == uri) {
858
+ return item.localConfiguration?.tag as? Track
859
+ }
860
+ }
861
+ return null
862
+ }
953
863
 
954
- // Notify JS of the now playing metadata change
955
- getNowPlaying()?.let { callbacks?.onNowPlayingChanged(it) }
864
+ /**
865
+ * Track-first display-time artwork resolution for [CoilBitmapLoader]: registry hit
866
+ * (browse/now-playing-resolved URIs) → queue-tag lookup (app-supplied tracks with raw artwork) →
867
+ * header-only fallback for unattributable URIs (registry eviction / process-death restore). Null
868
+ * means "fetch the URI as-is".
869
+ */
870
+ suspend fun resolveDisplayArtwork(uri: String, sizeHintPixels: Int?): ImageSource? {
871
+ val browserManager = browser?.browserManager ?: return null
872
+ val imageContext =
873
+ sizeHintPixels?.takeIf { it > 0 }?.let { ImageContext(it.toDouble(), it.toDouble()) }
874
+ browserManager.displayArtworkSource(uri, imageContext)?.let {
875
+ return it
876
+ }
877
+ withContext(Dispatchers.Main) { findQueueTrackByArtworkUri(uri) }
878
+ ?.let { track ->
879
+ return browserManager.resolveArtworkUrl(track, null, imageContext)
880
+ }
881
+ return browserManager.unattributedArtworkSource(uri)
956
882
  }
957
883
 
958
884
  /**
@@ -975,12 +901,25 @@ class Player(internal val context: Context) {
975
901
  }
976
902
 
977
903
  fun play() {
978
- exoPlayer.play()
904
+ // Through the setter so the STATE_ENDED replay handling applies
905
+ // (ExoPlayer.play() is only setPlayWhenReady(true)).
906
+ playWhenReady = true
979
907
  if (currentTrack != null) {
908
+ // No-op unless the player is STATE_IDLE (ExoPlayer.prepare early-returns
909
+ // otherwise), so this only reconnects after a stop() or error and never
910
+ // re-buffers a healthy stream. Reconnecting is also how live streams
911
+ // rejoin the live edge on resume.
980
912
  exoPlayer.prepare()
981
913
  }
982
914
  }
983
915
 
916
+ /** Jump to the live edge (default position) of a live item; no-op otherwise. */
917
+ fun seekToLiveEdge() {
918
+ if (exoPlayer.isCurrentMediaItemLive) {
919
+ exoPlayer.seekToDefaultPosition()
920
+ }
921
+ }
922
+
984
923
  /**
985
924
  * Executes a search and plays the results. Used for voice commands with structured search
986
925
  * parameters.
@@ -990,7 +929,20 @@ class Player(internal val context: Context) {
990
929
  */
991
930
  suspend fun playFromSearch(params: SearchParams): Boolean {
992
931
  return try {
993
- val browserManager = awaitBrowser().browserManager
932
+ val audioBrowser = awaitBrowser()
933
+ // A gate blocks external-surface "play" search too — otherwise voice search is a way around
934
+ // the gate (mirrors the iOS play-media intent guard). Resolved per request; a gated decision
935
+ // fires onGate(search) and refuses.
936
+ val gateOutcome =
937
+ audioBrowser.gateDecision(
938
+ NativeGateRequest(reason = GateReason.SEARCH, path = null, search = params)
939
+ )
940
+ if (gateOutcome.gated) {
941
+ Timber.i("playFromSearch refused — request is gated")
942
+ audioBrowser.onGate(GateEvent(GateReason.SEARCH))
943
+ return false
944
+ }
945
+ val browserManager = audioBrowser.browserManager
994
946
 
995
947
  Timber.d(
996
948
  "Executing voice search: mode=${params.mode}, query='${params.query}', artist='${params.artist}', album='${params.album}'"
@@ -1019,7 +971,10 @@ class Player(internal val context: Context) {
1019
971
  }
1020
972
 
1021
973
  fun pause() {
1022
- exoPlayer.pause()
974
+ // Through the setter so the eager playWhenReadyCache write holds for the
975
+ // drop direction too — the retry policy reads it off-main before
976
+ // ExoPlayer's listener round-trip syncs it.
977
+ playWhenReady = false
1023
978
  }
1024
979
 
1025
980
  fun togglePlayback() {
@@ -1036,8 +991,11 @@ class Player(internal val context: Context) {
1036
991
  * required for playback.
1037
992
  */
1038
993
  fun stop() {
1039
- playbackState = PlaybackState.STOPPED
1040
- exoPlayer.playWhenReady = false
994
+ // State first so the machine's STOPPED guard suppresses the PAUSED transition
995
+ // from the playWhenReady drop below; through setPlaybackState so the change
996
+ // is emitted (direct field assignment bypasses events — see its docstring).
997
+ setPlaybackState(PlaybackState.STOPPED)
998
+ playWhenReady = false
1041
999
  exoPlayer.stop()
1042
1000
  }
1043
1001
 
@@ -1051,7 +1009,15 @@ class Player(internal val context: Context) {
1051
1009
  * use [pause].
1052
1010
  */
1053
1011
  fun destroy() {
1012
+ // Cancel without a final save: the loop would tick against the released
1013
+ // player and persist position 0 over the real resumption position.
1014
+ playbackStateStore.cancelPeriodicSave()
1015
+ // An armed timer's Runnable would outlive the release and fire pause() into the
1016
+ // released player. Cleared here, while a mid-ramp fade can still restore volume.
1017
+ sleepTimer.clear()
1054
1018
  stop()
1019
+ nowPlaying.destroy()
1020
+ mediaSessionCallback.destroy()
1055
1021
  forwardingPlayer.removeListener(playerListener)
1056
1022
  automaticBufferManager?.detach()
1057
1023
  automaticBufferManager = null
@@ -1059,8 +1025,7 @@ class Player(internal val context: Context) {
1059
1025
  cache?.release()
1060
1026
  cache = null
1061
1027
  networkMonitor.destroy()
1062
- equalizerManager?.release()
1063
- equalizerManager = null
1028
+ equalizer.release()
1064
1029
  }
1065
1030
 
1066
1031
  fun seekTo(duration: Long, unit: TimeUnit) {
@@ -1073,6 +1038,57 @@ class Player(internal val context: Context) {
1073
1038
  exoPlayer.seekTo(positionMs)
1074
1039
  }
1075
1040
 
1041
+ /**
1042
+ * Invalidates in-flight [reportRetryingError] posts: bumped on track change so a report
1043
+ * classified for the outgoing track can't land on the incoming one's spinner. Written on main,
1044
+ * read on ExoPlayer's loader thread.
1045
+ */
1046
+ @Volatile private var retryingErrorGeneration = 0
1047
+
1048
+ /**
1049
+ * Surfaces a load failure the retry policy is still working on: attached to the current
1050
+ * (non-terminal) state via [Callbacks.onPlaybackChanged], so UIs can show the cause over the
1051
+ * spinner. Not an ERROR transition, and not a [Callbacks.onPlaybackError] — that event stays
1052
+ * reserved for terminal errors. Called from ExoPlayer's loader thread, hence the main hop.
1053
+ */
1054
+ private fun reportRetryingError(exception: IOException) {
1055
+ val error = PlaybackErrorClassifier.retryingLoadError(exception, networkMonitor.getOnline())
1056
+ val generation = retryingErrorGeneration
1057
+ mainHandler.post {
1058
+ // The track changed while this post was in flight — the error belongs to the old one.
1059
+ if (generation != retryingErrorGeneration) return@post
1060
+ // Only attach while the failure is what the listener is looking at. During PLAYING/READY
1061
+ // buffered audio is still audibly fine, and ExoPlayer's in-place retry can succeed without
1062
+ // any state change — an advisory attached there would never clear.
1063
+ if (
1064
+ playbackState != PlaybackState.LOADING &&
1065
+ playbackState != PlaybackState.BUFFERING &&
1066
+ playbackState != PlaybackState.PAUSED
1067
+ ) {
1068
+ return@post
1069
+ }
1070
+ // Each failed attempt re-reports; identical repeats add nothing (and would
1071
+ // re-render now-playing every backoff tick).
1072
+ if (playbackError == error) return@post
1073
+ playbackError = error
1074
+ callbacks?.onPlaybackChanged(Playback(playbackState, error))
1075
+ nowPlaying.render()
1076
+ }
1077
+ }
1078
+
1079
+ /**
1080
+ * Drops a retrying error whose track is gone. The new track may keep the state on
1081
+ * LOADING/BUFFERING — no state-change emit — so the clear is emitted here, or the old track's
1082
+ * error would linger over the new one's spinner.
1083
+ */
1084
+ internal fun clearRetryingError() {
1085
+ retryingErrorGeneration++
1086
+ if (playbackError?.retrying != true) return
1087
+ playbackError = null
1088
+ callbacks?.onPlaybackChanged(Playback(playbackState, null))
1089
+ nowPlaying.render()
1090
+ }
1091
+
1076
1092
  /**
1077
1093
  * Updates the player state and emits a state change event if the state has changed. Only emits an
1078
1094
  * event if the new state differs from the current state.
@@ -1088,39 +1104,78 @@ class Player(internal val context: Context) {
1088
1104
  val oldState = playbackState
1089
1105
  playbackState = state
1090
1106
 
1107
+ // Audio rendered: this load has proven itself — retries from here on get
1108
+ // the retry policy's full recovery budget, not the short first-connect one.
1109
+ if (state == PlaybackState.PLAYING) {
1110
+ hasPlayedCache = true
1111
+ }
1112
+
1113
+ // A natural end exhausts the play intent — nothing is left to play. Keeping
1114
+ // playWhenReady true inverted the play/pause toggle, held audio focus forever
1115
+ // (ExoPlayer abandons it only at IDLE or on the intent dropping), and kept the
1116
+ // periodic position save running. Cleared and emitted before the state /
1117
+ // queue-ended callbacks so JS observes the native order (intent → state →
1118
+ // queueEnded); the media3 listener's later echo is deduplicated by
1119
+ // emitPlayWhenReadyChanged. The state machine's ENDED guard keeps the drop
1120
+ // from re-reporting the state as PAUSED.
1121
+ if (state == PlaybackState.ENDED && playWhenReady) {
1122
+ playWhenReady = false
1123
+ emitPlayWhenReadyChanged(false)
1124
+ }
1125
+
1126
+ // The last track fires no AUTO media-item transition, so an armed
1127
+ // end-of-track sleep timer would stay set forever and pause the next
1128
+ // track played instead.
1129
+ if (state == PlaybackState.ENDED) {
1130
+ sleepTimer.onTrackEnd()
1131
+ }
1132
+
1091
1133
  // Clear error when transitioning away from error state
1092
1134
  if (oldState == PlaybackState.ERROR) {
1093
1135
  playbackError = null
1094
1136
  callbacks?.onPlaybackError(null)
1095
1137
  }
1096
1138
 
1139
+ // A retrying error rides along through LOADING/BUFFERING/PAUSED; a state that
1140
+ // proves recovery (READY/PLAYING — data flowed) or abandons the attempt clears
1141
+ // it, before the Playback below is built so the emitted event is already clean.
1142
+ if (
1143
+ playbackError?.retrying == true &&
1144
+ state != PlaybackState.LOADING &&
1145
+ state != PlaybackState.BUFFERING &&
1146
+ state != PlaybackState.PAUSED
1147
+ ) {
1148
+ playbackError = null
1149
+ }
1150
+
1097
1151
  val playback = Playback(state, playbackError)
1098
1152
  callbacks?.onPlaybackChanged(playback)
1099
1153
 
1100
- // Emit queue ended event when playback ends on the last track
1154
+ // Re-render the now-playing on every state change: the metadata formatter receives
1155
+ // `playbackState` (plus isRebuffering / isOnline / error), so any transition can change its
1156
+ // output — the live song while paused, a "Reconnecting…" line on a rebuffer, an offline/error
1157
+ // line. The publish-dedupe in applyNowPlayingFields drops redundant updates, so re-running
1158
+ // through the rapid startup sequence (none→loading→buffering→ready→playing) is cheap.
1159
+ nowPlaying.render()
1160
+
1161
+ // Emit queue ended event when playback ends on the last track. Repeat modes never
1162
+ // conceptually end the queue (matches web's endsQueue()): an ENDED that surfaces while
1163
+ // repeating (e.g. a seek to the end) must not read as "playlist over".
1101
1164
  // This coupling ensures queue ended events are always triggered consistently with state
1102
1165
  // changes
1103
- if (state == PlaybackState.ENDED && isLastTrack) {
1166
+ if (state == PlaybackState.ENDED && isLastInPlaybackOrder && repeatMode == RepeatMode.OFF) {
1104
1167
  currentIndex?.let { index ->
1105
1168
  val event =
1106
1169
  PlaybackQueueEndedEvent(track = index.toDouble(), position = position.toSeconds())
1107
1170
  callbacks?.onPlaybackQueueEnded(event)
1108
1171
  }
1109
- // Reset saved position to 0 so resumption starts from beginning (only when not repeating)
1110
- if (repeatMode == RepeatMode.OFF) {
1111
- playbackStateStore.savePositionZero()
1112
- }
1172
+ // Reset saved position to 0 so resumption starts from beginning
1173
+ playbackStateStore.savePositionZero()
1113
1174
  }
1114
1175
 
1115
- progressUpdateManager.onPlaybackStateChanged(state)
1116
- val newPlayingState = PlayingStateFactory.derive(playWhenReady, state)
1117
- if (newPlayingState != playingState) {
1118
- Timber.d(
1119
- "PlayingState changed: playing=${newPlayingState.playing}, buffering=${newPlayingState.buffering}"
1120
- )
1121
- playingState = newPlayingState
1122
- callbacks?.onPlaybackPlayingState(playingState)
1123
- }
1176
+ progressTimer.onPlaybackStateChanged(state)
1177
+ intervalTimer.onPlaybackStateChanged(state)
1178
+ refreshPlayingState()
1124
1179
  }
1125
1180
  }
1126
1181
 
@@ -1130,7 +1185,11 @@ class Player(internal val context: Context) {
1130
1185
  * @param interval The interval in seconds, or null to disable progress updates
1131
1186
  */
1132
1187
  fun setProgressUpdateInterval(interval: Double?) {
1133
- progressUpdateManager.setUpdateInterval(interval)
1188
+ progressTimer.setInterval(interval)
1189
+ }
1190
+
1191
+ fun setPlaybackIntervalEnabled(enabled: Boolean) {
1192
+ intervalTimer.setInterval(if (enabled) 1.0 else null)
1134
1193
  }
1135
1194
 
1136
1195
  /**
@@ -1149,8 +1208,6 @@ class Player(internal val context: Context) {
1149
1208
 
1150
1209
  // Check what changed
1151
1210
  val skipSilenceChanged = previousOptions.skipSilence != options.skipSilence
1152
- val ratingTypeChanged = previousOptions.ratingType != options.ratingType
1153
- val shuffleChanged = previousOptions.shuffle != options.shuffle
1154
1211
  val progressUpdateEventIntervalChanged =
1155
1212
  previousOptions.progressUpdateEventInterval != options.progressUpdateEventInterval
1156
1213
  val forwardJumpIntervalChanged =
@@ -1165,8 +1222,6 @@ class Player(internal val context: Context) {
1165
1222
 
1166
1223
  val hasChanged =
1167
1224
  skipSilenceChanged ||
1168
- ratingTypeChanged ||
1169
- shuffleChanged ||
1170
1225
  progressUpdateEventIntervalChanged ||
1171
1226
  forwardJumpIntervalChanged ||
1172
1227
  backwardJumpIntervalChanged ||
@@ -1179,12 +1234,10 @@ class Player(internal val context: Context) {
1179
1234
  skipSilence = options.skipSilence
1180
1235
  }
1181
1236
 
1182
- if (ratingTypeChanged) {
1183
- options.ratingType?.let { ratingType = it }
1184
- }
1185
-
1186
- if (shuffleChanged) {
1187
- shuffleMode = options.shuffle
1237
+ if (capabilitiesChanged) {
1238
+ // The `favorite` capability is the single favoriting switch — propagate
1239
+ // its match mode to the browser so it can hydrate row hearts.
1240
+ browser?.browserManager?.setFavoriteMatch(options.capabilities.favoriteMatch)
1188
1241
  }
1189
1242
 
1190
1243
  if (progressUpdateEventIntervalChanged) {
@@ -1241,187 +1294,6 @@ class Player(internal val context: Context) {
1241
1294
  return networkMonitor.getOnline()
1242
1295
  }
1243
1296
 
1244
- /** Initializes the equalizer with the player's audio session ID. */
1245
- private fun initializeEqualizer() {
1246
- val audioSessionId = exoPlayer.audioSessionId
1247
- if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
1248
- Timber.d("Skipping equalizer init - no audio session yet, will init on first playback")
1249
- return
1250
- }
1251
-
1252
- try {
1253
- equalizerManager =
1254
- EqualizerManager(audioSessionId).apply {
1255
- setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
1256
- }
1257
- Timber.d("Equalizer initialized with session ID: $audioSessionId")
1258
- } catch (e: Exception) {
1259
- Timber.e(e, "Failed to initialize equalizer")
1260
- equalizerManager = null
1261
- }
1262
- }
1263
-
1264
- /**
1265
- * Reinitializes the equalizer when the audio session ID changes. Preserves current settings
1266
- * (enabled state, preset, or custom levels). Also handles first-time initialization when the
1267
- * equalizer was skipped at startup due to unset audio session ID.
1268
- */
1269
- internal fun reinitializeEqualizer(newAudioSessionId: Int) {
1270
- val oldManager = equalizerManager
1271
-
1272
- // First-time initialization (skipped at startup because session was unset)
1273
- if (oldManager == null) {
1274
- Timber.d("First-time equalizer initialization with session ID: $newAudioSessionId")
1275
- try {
1276
- equalizerManager =
1277
- EqualizerManager(newAudioSessionId).apply {
1278
- setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
1279
- }
1280
- Timber.d("Equalizer initialized with session ID: $newAudioSessionId")
1281
- } catch (e: Exception) {
1282
- Timber.e(e, "Failed to initialize equalizer")
1283
- equalizerManager = null
1284
- }
1285
- return
1286
- }
1287
-
1288
- // Same session ID, no need to reinitialize
1289
- if (oldManager.audioSessionId == newAudioSessionId) {
1290
- return
1291
- }
1292
-
1293
- try {
1294
- // Capture current settings before releasing old equalizer
1295
- val currentSettings = oldManager.getSettings()
1296
-
1297
- // Release old equalizer
1298
- oldManager.release()
1299
-
1300
- // Create new equalizer with new session ID
1301
- equalizerManager =
1302
- EqualizerManager(newAudioSessionId).apply {
1303
- setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
1304
- }
1305
-
1306
- // Restore previous settings if available
1307
- currentSettings?.let { settings ->
1308
- if (settings.activePreset != null) {
1309
- // Restore preset
1310
- equalizerManager?.setPreset(settings.activePreset)
1311
- } else if (settings.enabled) {
1312
- // Restore custom levels
1313
- equalizerManager?.setLevels(settings.bandLevels)
1314
- }
1315
- // Restore enabled state
1316
- equalizerManager?.setEnabled(settings.enabled)
1317
- }
1318
-
1319
- Timber.d("Equalizer reinitialized for new session ID: $newAudioSessionId")
1320
- } catch (e: Exception) {
1321
- Timber.e(e, "Failed to reinitialize equalizer")
1322
- equalizerManager = null
1323
- }
1324
- }
1325
-
1326
- /**
1327
- * Gets the current equalizer settings.
1328
- *
1329
- * @return Current equalizer settings or null if not available
1330
- */
1331
- fun getEqualizerSettings(): com.margelo.nitro.audiobrowser.EqualizerSettings? {
1332
- return equalizerManager?.getSettings()
1333
- }
1334
-
1335
- /**
1336
- * Enables or disables the equalizer.
1337
- *
1338
- * @param enabled true to enable, false to disable
1339
- */
1340
- fun setEqualizerEnabled(enabled: Boolean) {
1341
- equalizerManager?.setEnabled(enabled)
1342
- }
1343
-
1344
- /**
1345
- * Applies a preset to the equalizer.
1346
- *
1347
- * @param preset Name of the preset to apply
1348
- */
1349
- fun setEqualizerPreset(preset: String) {
1350
- equalizerManager?.setPreset(preset)
1351
- }
1352
-
1353
- /**
1354
- * Sets custom band levels for the equalizer.
1355
- *
1356
- * @param levels Array of level values in millibels for each band
1357
- */
1358
- fun setEqualizerLevels(levels: DoubleArray) {
1359
- equalizerManager?.setLevels(levels)
1360
- }
1361
-
1362
- // MARK: - Sleep Timer
1363
-
1364
- /**
1365
- * Gets the current sleep timer state.
1366
- *
1367
- * @return Sleep timer state or null if no timer is active
1368
- */
1369
- fun getSleepTimer(): NitroSleepTimer {
1370
- return when {
1371
- sleepTimer.time != null -> {
1372
- NitroSleepTimer.create(SleepTimerTime(sleepTimer.time!!))
1373
- }
1374
- sleepTimer.sleepWhenPlayedToEnd -> {
1375
- NitroSleepTimer.create(SleepTimerEndOfTrack(true))
1376
- }
1377
- else -> {
1378
- NitroSleepTimer.create(NullType.NULL)
1379
- }
1380
- }
1381
- }
1382
-
1383
- /**
1384
- * Sets a sleep timer to stop playback after the specified duration.
1385
- *
1386
- * @param seconds Number of seconds until playback stops
1387
- */
1388
- fun setSleepTimer(seconds: Double) {
1389
- sleepTimer.sleepAfter(seconds)
1390
- callbacks?.onSleepTimerChanged(getSleepTimer())
1391
- }
1392
-
1393
- /** Sets a sleep timer to stop playback when the current track finishes playing. */
1394
- fun setSleepTimerToEndOfTrack() {
1395
- sleepTimer.sleepWhenPlayedToEnd()
1396
- callbacks?.onSleepTimerChanged(getSleepTimer())
1397
- }
1398
-
1399
- /**
1400
- * Clears the active sleep timer.
1401
- *
1402
- * @return true if a timer was cleared, false if no timer was active
1403
- */
1404
- fun clearSleepTimer(): Boolean {
1405
- val wasRunning = sleepTimer.clear()
1406
- if (wasRunning) {
1407
- callbacks?.onSleepTimerChanged(NitroSleepTimer.create(NullType.NULL))
1408
- }
1409
- return wasRunning
1410
- }
1411
-
1412
- /**
1413
- * Checks if the sleep timer is set to end on track completion and stops playback if so. Called
1414
- * when a track naturally finishes playing.
1415
- */
1416
- internal fun checkSleepTimerOnTrackEnd() {
1417
- if (sleepTimer.sleepWhenPlayedToEnd) {
1418
- Timber.d("Sleep timer triggered on track end, stopping playback")
1419
- sleepTimer.clear()
1420
- stop()
1421
- callbacks?.onSleepTimerChanged(NitroSleepTimer.create(NullType.NULL))
1422
- }
1423
- }
1424
-
1425
1297
  /**
1426
1298
  * Validates that an index is within bounds [0, trackCount).
1427
1299
  *
@@ -1469,6 +1341,10 @@ class Player(internal val context: Context) {
1469
1341
  mediaSessionCallback.notifyContentChanged(path)
1470
1342
  }
1471
1343
 
1344
+ fun invalidateAllContent() {
1345
+ mediaSessionCallback.invalidateAllContent()
1346
+ }
1347
+
1472
1348
  /**
1473
1349
  * Returns the recommended artwork size in pixels from the connected media browser (e.g., Android
1474
1350
  * Auto), or null if not provided.