react-native-audio-browser 0.2.0-next.8 → 0.2.0

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 (973) hide show
  1. package/AudioBrowser.podspec +1 -1
  2. package/README.md +2 -180
  3. package/android/build.gradle +1 -17
  4. package/android/gradle.properties +1 -1
  5. package/android/src/main/AndroidManifest.xml +0 -15
  6. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +141 -401
  7. package/android/src/main/java/com/audiobrowser/Callbacks.kt +6 -4
  8. package/android/src/main/java/com/audiobrowser/Service.kt +18 -67
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +178 -442
  10. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +4 -41
  11. package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
  12. package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +97 -109
  13. package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
  14. package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +8 -40
  15. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +46 -40
  16. package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +1 -4
  17. package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +1 -18
  18. package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +3 -15
  19. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +134 -422
  20. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +404 -248
  21. package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +59 -0
  22. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +5 -25
  23. package/android/src/main/java/com/audiobrowser/player/Player.kt +582 -469
  24. package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +67 -190
  25. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +53 -118
  26. package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +3 -37
  27. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +40 -85
  28. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +0 -24
  29. package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +300 -23
  30. package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +23 -13
  31. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
  32. package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +3 -4
  33. package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +1 -2
  34. package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +72 -0
  35. package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +36 -0
  36. package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +111 -6
  37. package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +8 -22
  38. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +80 -112
  39. package/android/src/main/res/values/strings.xml +0 -4
  40. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +0 -4
  41. package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +2 -1
  42. package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +0 -72
  43. package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +146 -0
  44. package/ios/Browser/BrowserConfig.swift +11 -41
  45. package/ios/Browser/BrowserManager+URLResolution.swift +93 -213
  46. package/ios/Browser/BrowserManager.swift +110 -303
  47. package/ios/Browser/BrowserPathHelper.swift +3 -32
  48. package/ios/Browser/JsonModels.swift +66 -287
  49. package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
  50. package/ios/CarPlay/CarPlayController.swift +149 -739
  51. package/ios/CarPlay/CarPlayImageLoader.swift +8 -57
  52. package/ios/CarPlay/CarPlayListItemFactory.swift +35 -44
  53. package/ios/CarPlay/CarPlayNowPlayingManager.swift +36 -180
  54. package/ios/CarPlay/RNABAudioBrowser.h +10 -15
  55. package/ios/CarPlay/RNABAudioBrowser.m +14 -15
  56. package/ios/CarPlay/RNABCarPlaySceneDelegate.m +0 -2
  57. package/ios/CarPlay/RNABMediaIntentHandler.swift +43 -53
  58. package/ios/CarPlay/Track+CarPlay.swift +63 -0
  59. package/ios/Extension/Capability+RemoteCommand.swift +9 -7
  60. package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
  61. package/ios/Extension/NavigationError+Conversion.swift +1 -8
  62. package/ios/Extension/ResolvedTrack+Copying.swift +4 -10
  63. package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
  64. package/ios/Extension/Track+AVPlayer.swift +4 -4
  65. package/ios/Extension/Track+Copying.swift +3 -9
  66. package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
  67. package/ios/HybridAudioBrowser.swift +173 -751
  68. package/ios/Model/NitroTypeStubs.swift +224 -299
  69. package/ios/Model/PlayerUpdateOptions.swift +23 -23
  70. package/ios/Model/RemoteCommand.swift +6 -24
  71. package/ios/Model/TrackPlayerError.swift +18 -82
  72. package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +9 -0
  73. package/ios/NowPlayingInfo/NowPlayingInfoController.swift +121 -99
  74. package/ios/Observer/PlayerItemNotificationObserver.swift +5 -45
  75. package/ios/Observer/PlayerItemPropertyObserver.swift +4 -27
  76. package/ios/Observer/PlayerTimeObserver.swift +53 -3
  77. package/ios/Option/SessionCategories.swift +11 -34
  78. package/ios/Player/LoadSeekCoordinator.swift +1 -16
  79. package/ios/Player/MediaLoader.swift +11 -31
  80. package/ios/Player/NowPlayingUpdater.swift +26 -119
  81. package/ios/Player/PlaybackCoordinator.swift +51 -285
  82. package/ios/Player/PlaybackCoordinatorCallbacks.swift +1 -6
  83. package/ios/Player/PlaybackEffectHandler.swift +9 -19
  84. package/ios/Player/PlaybackErrorHandler.swift +13 -97
  85. package/ios/Player/PlaybackProgressUpdateManager.swift +68 -0
  86. package/ios/Player/PlaybackStateMachine.swift +13 -18
  87. package/ios/Player/PlayingStateManager.swift +2 -3
  88. package/ios/Player/QueueManager.swift +61 -67
  89. package/ios/Player/RetryManager.swift +21 -98
  90. package/ios/Player/SleepTimerHandling.swift +0 -8
  91. package/ios/Player/SleepTimerManager.swift +11 -72
  92. package/ios/RemoteCommand/RemoteCommandController.swift +21 -40
  93. package/ios/Tests/BrowserPathHelperTests.swift +0 -28
  94. package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
  95. package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
  96. package/ios/Tests/LoadSeekCoordinatorTests.swift +7 -40
  97. package/ios/Tests/MediaLoaderTests.swift +20 -32
  98. package/ios/Tests/PlaybackCoordinatorTests.swift +13 -787
  99. package/ios/Tests/PlaybackErrorHandlerTests.swift +46 -380
  100. package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +166 -0
  101. package/ios/Tests/PlaybackStateMachineTests.swift +8 -33
  102. package/ios/Tests/QueueManagerTests.swift +18 -149
  103. package/ios/Tests/ResolvedTrackCopyingTests.swift +8 -15
  104. package/ios/Tests/Support/TestTypes.swift +20 -61
  105. package/ios/Tests/TrackSelectorTests.swift +15 -15
  106. package/ios/TrackPlayer.swift +58 -380
  107. package/ios/TrackPlayerCallbacks.swift +44 -5
  108. package/ios/TrackSelector.swift +5 -5
  109. package/ios/Util/ArtworkImageFetcher.swift +2 -4
  110. package/ios/Util/NetworkMonitor.swift +3 -7
  111. package/ios/Util/OnceValue.swift +0 -34
  112. package/lib/commonjs/AudioBrowser.js +11 -0
  113. package/lib/commonjs/AudioBrowser.js.map +1 -1
  114. package/lib/commonjs/features/browser.js +158 -59
  115. package/lib/commonjs/features/browser.js.map +1 -1
  116. package/lib/commonjs/features/equalizer.js +1 -1
  117. package/lib/commonjs/features/errors.js +5 -37
  118. package/lib/commonjs/features/errors.js.map +1 -1
  119. package/lib/commonjs/features/favorites.js +1 -1
  120. package/lib/commonjs/features/index.js +0 -22
  121. package/lib/commonjs/features/index.js.map +1 -1
  122. package/lib/commonjs/features/metadata.js +3 -3
  123. package/lib/commonjs/features/metadata.js.map +1 -1
  124. package/lib/commonjs/features/network.js +1 -1
  125. package/lib/commonjs/features/nowPlaying.js +1 -36
  126. package/lib/commonjs/features/nowPlaying.js.map +1 -1
  127. package/lib/commonjs/features/output.js +23 -37
  128. package/lib/commonjs/features/output.js.map +1 -1
  129. package/lib/commonjs/features/playback/controls.js +0 -9
  130. package/lib/commonjs/features/playback/controls.js.map +1 -1
  131. package/lib/commonjs/features/playback/index.js +0 -11
  132. package/lib/commonjs/features/playback/index.js.map +1 -1
  133. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  134. package/lib/commonjs/features/playback/playing.js +1 -1
  135. package/lib/commonjs/features/playback/progress.js +13 -29
  136. package/lib/commonjs/features/playback/progress.js.map +1 -1
  137. package/lib/commonjs/features/playback/state.js +1 -1
  138. package/lib/commonjs/features/playback/volume.js +2 -2
  139. package/lib/commonjs/features/player/options.js +29 -79
  140. package/lib/commonjs/features/player/options.js.map +1 -1
  141. package/lib/commonjs/features/player/setup.js +3 -256
  142. package/lib/commonjs/features/player/setup.js.map +1 -1
  143. package/lib/commonjs/features/queue/activeTrack.js +1 -1
  144. package/lib/commonjs/features/queue/queue.js +5 -12
  145. package/lib/commonjs/features/queue/queue.js.map +1 -1
  146. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  147. package/lib/commonjs/features/queue/shuffle.js +1 -1
  148. package/lib/commonjs/features/rating.js +2 -0
  149. package/lib/commonjs/features/rating.js.map +1 -0
  150. package/lib/commonjs/features/remoteControls.js +104 -14
  151. package/lib/commonjs/features/remoteControls.js.map +1 -1
  152. package/lib/commonjs/features/sleepTimer.js +5 -10
  153. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  154. package/lib/commonjs/native.js +0 -5
  155. package/lib/commonjs/native.js.map +1 -1
  156. package/lib/commonjs/native.web.js +1 -4
  157. package/lib/commonjs/native.web.js.map +1 -1
  158. package/lib/commonjs/types/player.js +2 -0
  159. package/lib/commonjs/types/player.js.map +1 -0
  160. package/lib/commonjs/utils/LazyNativeEmitter.js +8 -8
  161. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  162. package/lib/commonjs/utils/NativeUpdatedValue.js +3 -0
  163. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  164. package/lib/commonjs/web/NativeAudioBrowser.js +124 -301
  165. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  166. package/lib/commonjs/web/SimpleRouter.test.js +174 -0
  167. package/lib/commonjs/web/SimpleRouter.test.js.map +1 -0
  168. package/lib/commonjs/web/TrackPlayer/Player.js +26 -90
  169. package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
  170. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +293 -0
  171. package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
  172. package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
  173. package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
  174. package/lib/commonjs/web/TrackPlayer/SleepTimer.js +3 -34
  175. package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
  176. package/lib/commonjs/web/TrackPlayer/index.js +5 -13
  177. package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
  178. package/lib/commonjs/web/browser/BrowserManager.js +50 -176
  179. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  180. package/lib/commonjs/web/browser/NavigationErrorManager.js +1 -3
  181. package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
  182. package/lib/commonjs/web/browser/SearchManager.js +9 -11
  183. package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
  184. package/lib/commonjs/web/http/HttpClient.js +23 -4
  185. package/lib/commonjs/web/http/HttpClient.js.map +1 -1
  186. package/lib/commonjs/web/http/RequestConfigBuilder.js +58 -107
  187. package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
  188. package/lib/commonjs/web/player/NowPlayingManager.js +5 -6
  189. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  190. package/lib/commonjs/web/player/OptionsManager.js +17 -9
  191. package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
  192. package/lib/module/AudioBrowser.js +1 -4
  193. package/lib/module/AudioBrowser.js.map +1 -1
  194. package/lib/module/features/browser.js +156 -53
  195. package/lib/module/features/browser.js.map +1 -1
  196. package/lib/module/features/equalizer.js +1 -1
  197. package/lib/module/features/errors.js +5 -37
  198. package/lib/module/features/errors.js.map +1 -1
  199. package/lib/module/features/favorites.js +1 -1
  200. package/lib/module/features/index.js +0 -2
  201. package/lib/module/features/index.js.map +1 -1
  202. package/lib/module/features/metadata.js +3 -3
  203. package/lib/module/features/metadata.js.map +1 -1
  204. package/lib/module/features/network.js +1 -1
  205. package/lib/module/features/nowPlaying.js +1 -34
  206. package/lib/module/features/nowPlaying.js.map +1 -1
  207. package/lib/module/features/output.js +19 -32
  208. package/lib/module/features/output.js.map +1 -1
  209. package/lib/module/features/playback/controls.js +0 -8
  210. package/lib/module/features/playback/controls.js.map +1 -1
  211. package/lib/module/features/playback/index.js +0 -1
  212. package/lib/module/features/playback/index.js.map +1 -1
  213. package/lib/module/features/playback/playWhenReady.js +1 -1
  214. package/lib/module/features/playback/playing.js +1 -1
  215. package/lib/module/features/playback/progress.js +13 -29
  216. package/lib/module/features/playback/progress.js.map +1 -1
  217. package/lib/module/features/playback/state.js +1 -1
  218. package/lib/module/features/playback/volume.js +2 -2
  219. package/lib/module/features/player/options.js +29 -79
  220. package/lib/module/features/player/options.js.map +1 -1
  221. package/lib/module/features/player/setup.js +3 -256
  222. package/lib/module/features/player/setup.js.map +1 -1
  223. package/lib/module/features/queue/activeTrack.js +1 -1
  224. package/lib/module/features/queue/queue.js +5 -12
  225. package/lib/module/features/queue/queue.js.map +1 -1
  226. package/lib/module/features/queue/repeatMode.js +1 -1
  227. package/lib/module/features/queue/shuffle.js +1 -1
  228. package/lib/module/features/rating.js +2 -0
  229. package/lib/module/features/rating.js.map +1 -0
  230. package/lib/module/features/remoteControls.js +103 -13
  231. package/lib/module/features/remoteControls.js.map +1 -1
  232. package/lib/module/features/sleepTimer.js +5 -10
  233. package/lib/module/features/sleepTimer.js.map +1 -1
  234. package/lib/module/native.js +0 -5
  235. package/lib/module/native.js.map +1 -1
  236. package/lib/module/native.web.js +1 -4
  237. package/lib/module/native.web.js.map +1 -1
  238. package/lib/module/types/player.js +2 -0
  239. package/lib/module/types/player.js.map +1 -0
  240. package/lib/module/utils/LazyNativeEmitter.js +8 -8
  241. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  242. package/lib/module/utils/NativeUpdatedValue.js +3 -0
  243. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  244. package/lib/module/web/NativeAudioBrowser.js +125 -302
  245. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  246. package/lib/module/web/SimpleRouter.test.js +174 -0
  247. package/lib/module/web/SimpleRouter.test.js.map +1 -0
  248. package/lib/module/web/TrackPlayer/Player.js +26 -90
  249. package/lib/module/web/TrackPlayer/Player.js.map +1 -1
  250. package/lib/module/web/TrackPlayer/PlaylistPlayer.js +288 -0
  251. package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
  252. package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
  253. package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
  254. package/lib/module/web/TrackPlayer/SleepTimer.js +3 -34
  255. package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
  256. package/lib/module/web/TrackPlayer/index.js +1 -2
  257. package/lib/module/web/TrackPlayer/index.js.map +1 -1
  258. package/lib/module/web/browser/BrowserManager.js +50 -176
  259. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  260. package/lib/module/web/browser/NavigationErrorManager.js +1 -3
  261. package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
  262. package/lib/module/web/browser/SearchManager.js +9 -11
  263. package/lib/module/web/browser/SearchManager.js.map +1 -1
  264. package/lib/module/web/http/HttpClient.js +23 -4
  265. package/lib/module/web/http/HttpClient.js.map +1 -1
  266. package/lib/module/web/http/RequestConfigBuilder.js +58 -107
  267. package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
  268. package/lib/module/web/player/NowPlayingManager.js +5 -7
  269. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  270. package/lib/module/web/player/OptionsManager.js +17 -9
  271. package/lib/module/web/player/OptionsManager.js.map +1 -1
  272. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  273. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  274. package/lib/typescript/src/features/browser.d.ts +1 -40
  275. package/lib/typescript/src/features/browser.d.ts.map +1 -1
  276. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  277. package/lib/typescript/src/features/errors.d.ts +6 -61
  278. package/lib/typescript/src/features/errors.d.ts.map +1 -1
  279. package/lib/typescript/src/features/favorites.d.ts +1 -1
  280. package/lib/typescript/src/features/index.d.ts +0 -2
  281. package/lib/typescript/src/features/index.d.ts.map +1 -1
  282. package/lib/typescript/src/features/metadata.d.ts +7 -9
  283. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  284. package/lib/typescript/src/features/network.d.ts +1 -1
  285. package/lib/typescript/src/features/nowPlaying.d.ts +1 -28
  286. package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
  287. package/lib/typescript/src/features/output.d.ts +18 -28
  288. package/lib/typescript/src/features/output.d.ts.map +1 -1
  289. package/lib/typescript/src/features/playback/controls.d.ts +0 -5
  290. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  291. package/lib/typescript/src/features/playback/index.d.ts +0 -1
  292. package/lib/typescript/src/features/playback/index.d.ts.map +1 -1
  293. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  294. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  295. package/lib/typescript/src/features/playback/progress.d.ts +1 -4
  296. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  297. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  298. package/lib/typescript/src/features/playback/volume.d.ts +2 -2
  299. package/lib/typescript/src/features/player/options.d.ts +127 -176
  300. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  301. package/lib/typescript/src/features/player/setup.d.ts +167 -213
  302. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  303. package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
  304. package/lib/typescript/src/features/queue/queue.d.ts +4 -11
  305. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  306. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  307. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  308. package/lib/typescript/src/features/rating.d.ts +14 -0
  309. package/lib/typescript/src/features/rating.d.ts.map +1 -0
  310. package/lib/typescript/src/features/remoteControls.d.ts +35 -23
  311. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  312. package/lib/typescript/src/features/sleepTimer.d.ts +4 -19
  313. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  314. package/lib/typescript/src/index.d.ts.map +1 -1
  315. package/lib/typescript/src/native.d.ts +2 -1
  316. package/lib/typescript/src/native.d.ts.map +1 -1
  317. package/lib/typescript/src/native.web.d.ts +3 -0
  318. package/lib/typescript/src/native.web.d.ts.map +1 -0
  319. package/lib/typescript/src/specs/audio-browser.nitro.d.ts +225 -17
  320. package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
  321. package/lib/typescript/src/types/browser-native.d.ts +3 -9
  322. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  323. package/lib/typescript/src/types/browser-nodes.d.ts +13 -145
  324. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  325. package/lib/typescript/src/types/browser.d.ts +85 -349
  326. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  327. package/lib/typescript/src/types/player.d.ts +64 -0
  328. package/lib/typescript/src/types/player.d.ts.map +1 -0
  329. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +6 -8
  330. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  331. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +2 -0
  332. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  333. package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
  334. package/lib/typescript/src/web/NativeAudioBrowser.d.ts +188 -0
  335. package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +1 -0
  336. package/lib/typescript/src/web/SimpleRouter.d.ts +40 -0
  337. package/lib/typescript/src/web/SimpleRouter.d.ts.map +1 -0
  338. package/lib/typescript/src/web/SimpleRouter.test.d.ts +2 -0
  339. package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +1 -0
  340. package/lib/typescript/src/web/TrackPlayer/Event.d.ts +11 -0
  341. package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +1 -0
  342. package/lib/typescript/src/web/TrackPlayer/Player.d.ts +59 -0
  343. package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +1 -0
  344. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +39 -0
  345. package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +1 -0
  346. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +6 -0
  347. package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +1 -0
  348. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +4 -0
  349. package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +1 -0
  350. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +34 -0
  351. package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +1 -0
  352. package/lib/typescript/src/web/TrackPlayer/State.d.ts +13 -0
  353. package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +1 -0
  354. package/lib/typescript/src/web/TrackPlayer/index.d.ts +5 -0
  355. package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +1 -0
  356. package/lib/typescript/src/web/browser/BrowserManager.d.ts +154 -0
  357. package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +1 -0
  358. package/lib/typescript/src/web/browser/FavoriteManager.d.ts +42 -0
  359. package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +1 -0
  360. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +40 -0
  361. package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +1 -0
  362. package/lib/typescript/src/web/browser/SearchManager.d.ts +22 -0
  363. package/lib/typescript/src/web/browser/SearchManager.d.ts.map +1 -0
  364. package/lib/typescript/src/web/http/HttpClient.d.ts +25 -0
  365. package/lib/typescript/src/web/http/HttpClient.d.ts.map +1 -0
  366. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +98 -0
  367. package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +1 -0
  368. package/lib/typescript/src/web/player/NowPlayingManager.d.ts +34 -0
  369. package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +1 -0
  370. package/lib/typescript/src/web/player/OptionsManager.d.ts +32 -0
  371. package/lib/typescript/src/web/player/OptionsManager.d.ts.map +1 -0
  372. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +69 -0
  373. package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +1 -0
  374. package/lib/typescript/src/web/util/shuffle.d.ts +8 -0
  375. package/lib/typescript/src/web/util/shuffle.d.ts.map +1 -0
  376. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +2 -4
  377. package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +10 -24
  378. package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
  379. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +20 -10
  380. package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +86 -0
  381. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +1 -33
  382. package/nitrogen/generated/android/c++/JBrowseResult.hpp +0 -5
  383. package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
  384. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +0 -6
  385. package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +2 -3
  386. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +0 -6
  387. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +147 -0
  388. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +0 -8
  389. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +0 -6
  390. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +0 -6
  391. package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +80 -0
  392. package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +10 -0
  393. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +9 -14
  394. package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +0 -2
  395. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +0 -6
  396. package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +0 -2
  397. package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +87 -0
  398. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
  399. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +0 -6
  400. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +0 -6
  401. package/nitrogen/generated/android/c++/JHeartRating.hpp +57 -0
  402. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +221 -245
  403. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +22 -25
  404. package/nitrogen/generated/android/c++/JImageRowItem.hpp +3 -33
  405. package/nitrogen/generated/android/c++/{JOutput.hpp → JIosOutput.hpp} +15 -15
  406. package/nitrogen/generated/android/c++/JIosOutputType.hpp +85 -0
  407. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +1 -33
  408. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +28 -61
  409. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +9 -20
  410. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +24 -21
  411. package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +0 -6
  412. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +20 -10
  413. package/nitrogen/generated/android/c++/{JRemoteButton.hpp → JNotificationButton.hpp} +20 -20
  414. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +94 -0
  415. package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +17 -3
  416. package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +3 -7
  417. package/nitrogen/generated/android/c++/JOptions.hpp +14 -19
  418. package/nitrogen/generated/android/c++/{JNativeAndroidSetupOptions.hpp → JPartialAndroidSetupPlayerOptions.hpp} +10 -10
  419. package/nitrogen/generated/android/c++/{JNativeIOSSetupOptions.hpp → JPartialIOSSetupPlayerOptions.hpp} +10 -10
  420. package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +96 -0
  421. package/nitrogen/generated/android/c++/JPercentageRating.hpp +57 -0
  422. package/nitrogen/generated/android/c++/JPlayback.hpp +0 -2
  423. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +0 -6
  424. package/nitrogen/generated/android/c++/JPlaybackError.hpp +3 -18
  425. package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +0 -2
  426. package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
  427. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  428. package/nitrogen/generated/android/c++/JRatingType.hpp +73 -0
  429. package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +66 -0
  430. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +5 -23
  431. package/nitrogen/generated/android/c++/JRetryConfig.hpp +7 -11
  432. package/nitrogen/generated/android/c++/JSearchMode.hpp +9 -27
  433. package/nitrogen/generated/android/c++/JSearchParams.hpp +3 -9
  434. package/nitrogen/generated/android/c++/JStarRating.hpp +57 -0
  435. package/nitrogen/generated/android/c++/{JGateEvent.hpp → JThumbsRating.hpp} +15 -16
  436. package/nitrogen/generated/android/c++/JTrack.hpp +5 -23
  437. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +0 -6
  438. package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +1 -14
  439. package/nitrogen/generated/android/c++/JUpdateOptions.hpp +105 -0
  440. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +34 -0
  441. package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +105 -0
  442. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +26 -0
  443. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +75 -0
  444. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
  445. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +9 -3
  446. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +50 -0
  447. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +4 -10
  448. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
  449. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
  450. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt} +12 -12
  451. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_Output.kt → Func_void_IosOutput.kt} +9 -9
  452. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_GateEvent.kt → Func_void_RemoteSetRatingEvent.kt} +9 -9
  453. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
  454. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Gate.kt → HeartRating.kt} +6 -9
  455. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +109 -103
  456. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +3 -24
  457. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Output.kt → IosOutput.kt} +6 -6
  458. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +32 -0
  459. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +4 -10
  460. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +6 -18
  461. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
  462. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
  463. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +1 -3
  464. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +9 -3
  465. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → NotificationButton.kt} +8 -5
  466. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +50 -0
  467. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +6 -3
  468. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +3 -6
  469. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
  470. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeAndroidSetupOptions.kt → PartialAndroidSetupPlayerOptions.kt} +5 -5
  471. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeIOSSetupOptions.kt → PartialIOSSetupPlayerOptions.kt} +5 -5
  472. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +47 -0
  473. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateDecision.kt → PercentageRating.kt} +6 -9
  474. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +3 -12
  475. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
  476. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  477. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateReason.kt → RatingType.kt} +10 -5
  478. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ArtworkVariants.kt → RemoteSetRatingEvent.kt} +6 -9
  479. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +3 -12
  480. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +4 -7
  481. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +6 -12
  482. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +3 -6
  483. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateEvent.kt → StarRating.kt} +6 -6
  484. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → ThumbsRating.kt} +6 -6
  485. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +3 -12
  486. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +4 -7
  487. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +53 -0
  488. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +77 -0
  489. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_RemoteButtonLayout.kt → Variant_NullType_NotificationButtonLayout.kt} +9 -9
  490. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +28 -139
  491. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +489 -1125
  492. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +45 -66
  493. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +118 -176
  494. package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
  495. package/nitrogen/generated/ios/swift/AndroidOptions.swift +22 -12
  496. package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +119 -0
  497. package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +1 -95
  498. package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
  499. package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +4 -17
  500. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +68 -0
  501. package/nitrogen/generated/ios/swift/{Func_void_Output.swift → Func_void_IosOutput.swift} +10 -10
  502. package/nitrogen/generated/ios/swift/{Func_void_GateEvent.swift → Func_void_RemoteSetRatingEvent.swift} +10 -10
  503. package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
  504. package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_GateDecision__.swift → Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift} +17 -17
  505. package/nitrogen/generated/ios/swift/HeartRating.swift +29 -0
  506. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +14 -20
  507. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +197 -230
  508. package/nitrogen/generated/ios/swift/ImageRowItem.swift +2 -121
  509. package/nitrogen/generated/ios/swift/{Output.swift → IosOutput.swift} +7 -7
  510. package/nitrogen/generated/ios/swift/IosOutputType.swift +76 -0
  511. package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +1 -107
  512. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +22 -161
  513. package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
  514. package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +25 -12
  515. package/nitrogen/generated/ios/swift/NavigationErrorType.swift +0 -8
  516. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +41 -12
  517. package/nitrogen/generated/ios/swift/{RemoteButton.swift → NotificationButton.swift} +10 -10
  518. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +92 -0
  519. package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +48 -1
  520. package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +1 -19
  521. package/nitrogen/generated/ios/swift/Options.swift +4 -10
  522. package/nitrogen/generated/ios/swift/{NativeAndroidSetupOptions.swift → PartialAndroidSetupPlayerOptions.swift} +5 -5
  523. package/nitrogen/generated/ios/swift/{NativeIOSSetupOptions.swift → PartialIOSSetupPlayerOptions.swift} +5 -5
  524. package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +101 -0
  525. package/nitrogen/generated/ios/swift/PercentageRating.swift +29 -0
  526. package/nitrogen/generated/ios/swift/PlaybackError.swift +2 -43
  527. package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
  528. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  529. package/nitrogen/generated/ios/swift/RatingType.swift +60 -0
  530. package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +58 -0
  531. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +8 -74
  532. package/nitrogen/generated/ios/swift/RetryConfig.swift +4 -35
  533. package/nitrogen/generated/ios/swift/SearchMode.swift +12 -36
  534. package/nitrogen/generated/ios/swift/SearchParams.swift +2 -7
  535. package/nitrogen/generated/ios/swift/StarRating.swift +29 -0
  536. package/nitrogen/generated/ios/swift/ThumbsRating.swift +29 -0
  537. package/nitrogen/generated/ios/swift/Track.swift +8 -74
  538. package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +1 -60
  539. package/nitrogen/generated/ios/swift/UpdateOptions.swift +143 -0
  540. package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +20 -0
  541. package/nitrogen/generated/ios/swift/{Variant_NullType_RemoteButtonLayout.swift → Variant_NullType_NotificationButtonLayout.swift} +3 -3
  542. package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
  543. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +19 -8
  544. package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +109 -0
  545. package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +2 -10
  546. package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
  547. package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +2 -0
  548. package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +5 -6
  549. package/nitrogen/generated/shared/c++/{GateEvent.hpp → HeartRating.hpp} +17 -18
  550. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +18 -21
  551. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +35 -44
  552. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +2 -33
  553. package/nitrogen/generated/shared/c++/{Output.hpp → IosOutput.hpp} +18 -18
  554. package/nitrogen/generated/shared/c++/IosOutputType.hpp +112 -0
  555. package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +2 -10
  556. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +15 -31
  557. package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
  558. package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +7 -9
  559. package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +0 -8
  560. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +19 -8
  561. package/nitrogen/generated/shared/c++/{RemoteButton.hpp → NotificationButton.hpp} +25 -25
  562. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +102 -0
  563. package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +19 -3
  564. package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +2 -6
  565. package/nitrogen/generated/shared/c++/Options.hpp +8 -8
  566. package/nitrogen/generated/shared/c++/{NativeAndroidSetupOptions.hpp → PartialAndroidSetupPlayerOptions.hpp} +11 -11
  567. package/nitrogen/generated/shared/c++/{NativeIOSSetupOptions.hpp → PartialIOSSetupPlayerOptions.hpp} +11 -11
  568. package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +104 -0
  569. package/nitrogen/generated/shared/c++/{FavoriteConfig.hpp → PercentageRating.hpp} +17 -18
  570. package/nitrogen/generated/shared/c++/PlaybackError.hpp +3 -18
  571. package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
  572. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  573. package/nitrogen/generated/shared/c++/RatingType.hpp +96 -0
  574. package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +94 -0
  575. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +5 -24
  576. package/nitrogen/generated/shared/c++/RetryConfig.hpp +6 -10
  577. package/nitrogen/generated/shared/c++/SearchMode.hpp +15 -39
  578. package/nitrogen/generated/shared/c++/SearchParams.hpp +2 -9
  579. package/nitrogen/generated/shared/c++/{Gate.hpp → StarRating.hpp} +16 -21
  580. package/nitrogen/generated/shared/c++/{GateDecision.hpp → ThumbsRating.hpp} +17 -23
  581. package/nitrogen/generated/shared/c++/Track.hpp +5 -24
  582. package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +2 -6
  583. package/nitrogen/generated/shared/c++/UpdateOptions.hpp +111 -0
  584. package/package.json +5 -12
  585. package/src/AudioBrowser.ts +1 -4
  586. package/src/features/browser.ts +201 -58
  587. package/src/features/equalizer.ts +1 -1
  588. package/src/features/errors.ts +5 -71
  589. package/src/features/favorites.ts +1 -1
  590. package/src/features/index.ts +0 -2
  591. package/src/features/metadata.ts +15 -9
  592. package/src/features/network.ts +1 -1
  593. package/src/features/nowPlaying.ts +1 -37
  594. package/src/features/output.ts +22 -35
  595. package/src/features/playback/controls.ts +0 -8
  596. package/src/features/playback/index.ts +0 -1
  597. package/src/features/playback/playWhenReady.ts +1 -1
  598. package/src/features/playback/playing.ts +1 -1
  599. package/src/features/playback/progress.ts +13 -32
  600. package/src/features/playback/state.ts +1 -1
  601. package/src/features/playback/volume.ts +2 -2
  602. package/src/features/player/options.ts +110 -217
  603. package/src/features/player/setup.ts +175 -397
  604. package/src/features/queue/activeTrack.ts +1 -1
  605. package/src/features/queue/queue.ts +6 -17
  606. package/src/features/queue/repeatMode.ts +1 -1
  607. package/src/features/queue/shuffle.ts +1 -1
  608. package/src/features/rating.ts +17 -0
  609. package/src/features/remoteControls.ts +115 -13
  610. package/src/features/sleepTimer.ts +5 -24
  611. package/src/native.ts +0 -5
  612. package/src/native.web.ts +1 -4
  613. package/src/specs/audio-browser.nitro.ts +41 -131
  614. package/src/types/browser-native.ts +6 -19
  615. package/src/types/browser-nodes.ts +13 -160
  616. package/src/types/browser.ts +89 -377
  617. package/src/types/player.ts +69 -0
  618. package/src/utils/LazyNativeEmitter.ts +8 -9
  619. package/src/utils/NativeUpdatedValue.ts +2 -0
  620. package/src/web/NativeAudioBrowser.ts +158 -365
  621. package/src/web/TrackPlayer/Player.ts +39 -113
  622. package/src/web/TrackPlayer/PlaylistPlayer.ts +346 -0
  623. package/src/web/TrackPlayer/RepeatMode.ts +1 -1
  624. package/src/web/TrackPlayer/SleepTimer.ts +3 -38
  625. package/src/web/TrackPlayer/index.ts +1 -2
  626. package/src/web/browser/BrowserManager.ts +62 -231
  627. package/src/web/browser/NavigationErrorManager.ts +1 -3
  628. package/src/web/browser/SearchManager.ts +15 -11
  629. package/src/web/http/HttpClient.ts +35 -5
  630. package/src/web/http/RequestConfigBuilder.ts +63 -120
  631. package/src/web/player/NowPlayingManager.ts +5 -6
  632. package/src/web/player/OptionsManager.ts +18 -12
  633. package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +0 -37
  634. package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +0 -34
  635. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +0 -299
  636. package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +0 -79
  637. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +0 -42
  638. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +0 -35
  639. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +0 -172
  640. package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +0 -153
  641. package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +0 -141
  642. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -347
  643. package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +0 -129
  644. package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +0 -85
  645. package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +0 -56
  646. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +0 -20
  647. package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +0 -130
  648. package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +0 -80
  649. package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +0 -50
  650. package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +0 -81
  651. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +0 -141
  652. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +0 -37
  653. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +0 -41
  654. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +0 -209
  655. package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +0 -161
  656. package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +0 -28
  657. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +0 -24
  658. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +0 -49
  659. package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +0 -36
  660. package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +0 -211
  661. package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +0 -58
  662. package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +0 -14
  663. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +0 -165
  664. package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +0 -46
  665. package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +0 -39
  666. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +0 -114
  667. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +0 -141
  668. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +0 -177
  669. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +0 -55
  670. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +0 -94
  671. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +0 -203
  672. package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +0 -56
  673. package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +0 -216
  674. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +0 -45
  675. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +0 -442
  676. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +0 -190
  677. package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +0 -198
  678. package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +0 -137
  679. package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +0 -36
  680. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +0 -268
  681. package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +0 -68
  682. package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +0 -40
  683. package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +0 -193
  684. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +0 -107
  685. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +0 -73
  686. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +0 -75
  687. package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +0 -18
  688. package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +0 -163
  689. package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +0 -34
  690. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +0 -28
  691. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +0 -53
  692. package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +0 -58
  693. package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +0 -57
  694. package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +0 -40
  695. package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +0 -34
  696. package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +0 -50
  697. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +0 -63
  698. package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +0 -35
  699. package/android/src/test/resources/certs/isrg-root-x1.pem +0 -31
  700. package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
  701. package/android/src/test/resources/certs/letsencrypt-r13.pem +0 -29
  702. package/android/src/test/resources/certs/stationplaylist-leaf.pem +0 -34
  703. package/ios/Browser/MediaResolveComposer.swift +0 -114
  704. package/ios/Browser/SearchDrillIn.swift +0 -44
  705. package/ios/Browser/SectionScope.swift +0 -35
  706. package/ios/Browser/TabBarEntries.swift +0 -15
  707. package/ios/CarPlay/CPInterfaceController+Safe.swift +0 -126
  708. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  709. package/ios/CarPlay/RNABMediaAddHandler.swift +0 -75
  710. package/ios/CarPlay/RNABMediaAffinityHandler.swift +0 -69
  711. package/ios/Extension/PlayerCapabilities+Favorite.swift +0 -20
  712. package/ios/Extension/RequestConfig+Copying.swift +0 -31
  713. package/ios/Extension/Variant+Artwork.swift +0 -49
  714. package/ios/NowPlayingInfo/SilentAudioPrimer.swift +0 -73
  715. package/ios/Player/AirPlayStallJudgement.swift +0 -25
  716. package/ios/Player/EndOfTrackJudgement.swift +0 -32
  717. package/ios/Player/MediaIntentCriteria.swift +0 -139
  718. package/ios/Player/PersistedPlaybackState.swift +0 -11
  719. package/ios/Player/PlaybackStateStore.swift +0 -27
  720. package/ios/Player/PlaybackTimer.swift +0 -61
  721. package/ios/Player/VolumeFader.swift +0 -77
  722. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +0 -47
  723. package/ios/Tests/AirPlayStallJudgementTests.swift +0 -52
  724. package/ios/Tests/EndOfTrackJudgementTests.swift +0 -35
  725. package/ios/Tests/JsonModelsTests.swift +0 -58
  726. package/ios/Tests/MediaIntentCriteriaTests.swift +0 -170
  727. package/ios/Tests/MediaLoaderAssetOptionsTests.swift +0 -46
  728. package/ios/Tests/MediaResolveTests.swift +0 -126
  729. package/ios/Tests/OnceValueTests.swift +0 -79
  730. package/ios/Tests/PlaybackStateStoreTests.swift +0 -48
  731. package/ios/Tests/PlaybackTimerTests.swift +0 -55
  732. package/ios/Tests/PlayingStateManagerTests.swift +0 -57
  733. package/ios/Tests/RemoteCommandControllerTests.swift +0 -156
  734. package/ios/Tests/RemoteCommandTests.swift +0 -67
  735. package/ios/Tests/RetryManagerTests.swift +0 -89
  736. package/ios/Tests/SearchDrillInTests.swift +0 -63
  737. package/ios/Tests/SectionScopeTests.swift +0 -82
  738. package/ios/Tests/SleepTimerManagerTests.swift +0 -107
  739. package/ios/Tests/Support/ManualSleepTimerScheduler.swift +0 -47
  740. package/ios/Tests/TabBarEntriesTests.swift +0 -71
  741. package/lib/commonjs/features/browser-config.js +0 -227
  742. package/lib/commonjs/features/browser-config.js.map +0 -1
  743. package/lib/commonjs/features/carConnection.js +0 -42
  744. package/lib/commonjs/features/carConnection.js.map +0 -1
  745. package/lib/commonjs/features/gate.js +0 -149
  746. package/lib/commonjs/features/gate.js.map +0 -1
  747. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -86
  748. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +0 -1
  749. package/lib/commonjs/features/player/validateOptions.js +0 -24
  750. package/lib/commonjs/features/player/validateOptions.js.map +0 -1
  751. package/lib/commonjs/utils/artwork.js +0 -31
  752. package/lib/commonjs/utils/artwork.js.map +0 -1
  753. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +0 -71
  754. package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
  755. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +0 -34
  756. package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +0 -1
  757. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +0 -21
  758. package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
  759. package/lib/commonjs/web/TrackPlayer/QueueManager.js +0 -273
  760. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +0 -1
  761. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +0 -158
  762. package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +0 -1
  763. package/lib/commonjs/web/TrackPlayer/VolumeFader.js +0 -75
  764. package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +0 -1
  765. package/lib/commonjs/web/browser/assertBrowsePageShape.js +0 -22
  766. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +0 -1
  767. package/lib/commonjs/web/browser/classifyTrackNavigation.js +0 -41
  768. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +0 -1
  769. package/lib/commonjs/web/browser/parseSearchResponse.js +0 -20
  770. package/lib/commonjs/web/browser/parseSearchResponse.js.map +0 -1
  771. package/lib/commonjs/web/playbackErrorKind.js +0 -57
  772. package/lib/commonjs/web/playbackErrorKind.js.map +0 -1
  773. package/lib/commonjs/web/player/MediaSessionManager.js +0 -121
  774. package/lib/commonjs/web/player/MediaSessionManager.js.map +0 -1
  775. package/lib/commonjs/web/player/RemoteCommandController.js +0 -96
  776. package/lib/commonjs/web/player/RemoteCommandController.js.map +0 -1
  777. package/lib/module/features/browser-config.js +0 -221
  778. package/lib/module/features/browser-config.js.map +0 -1
  779. package/lib/module/features/carConnection.js +0 -37
  780. package/lib/module/features/carConnection.js.map +0 -1
  781. package/lib/module/features/gate.js +0 -144
  782. package/lib/module/features/gate.js.map +0 -1
  783. package/lib/module/features/playback/trackPlaybackTime.js +0 -83
  784. package/lib/module/features/playback/trackPlaybackTime.js.map +0 -1
  785. package/lib/module/features/player/validateOptions.js +0 -20
  786. package/lib/module/features/player/validateOptions.js.map +0 -1
  787. package/lib/module/utils/artwork.js +0 -26
  788. package/lib/module/utils/artwork.js.map +0 -1
  789. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +0 -67
  790. package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
  791. package/lib/module/web/TrackPlayer/PlaybackTimer.js +0 -29
  792. package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +0 -1
  793. package/lib/module/web/TrackPlayer/PlayingStateFactory.js +0 -17
  794. package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
  795. package/lib/module/web/TrackPlayer/QueueManager.js +0 -269
  796. package/lib/module/web/TrackPlayer/QueueManager.js.map +0 -1
  797. package/lib/module/web/TrackPlayer/QueuePlayer.js +0 -154
  798. package/lib/module/web/TrackPlayer/QueuePlayer.js.map +0 -1
  799. package/lib/module/web/TrackPlayer/VolumeFader.js +0 -70
  800. package/lib/module/web/TrackPlayer/VolumeFader.js.map +0 -1
  801. package/lib/module/web/browser/assertBrowsePageShape.js +0 -18
  802. package/lib/module/web/browser/assertBrowsePageShape.js.map +0 -1
  803. package/lib/module/web/browser/classifyTrackNavigation.js +0 -38
  804. package/lib/module/web/browser/classifyTrackNavigation.js.map +0 -1
  805. package/lib/module/web/browser/parseSearchResponse.js +0 -16
  806. package/lib/module/web/browser/parseSearchResponse.js.map +0 -1
  807. package/lib/module/web/playbackErrorKind.js +0 -52
  808. package/lib/module/web/playbackErrorKind.js.map +0 -1
  809. package/lib/module/web/player/MediaSessionManager.js +0 -116
  810. package/lib/module/web/player/MediaSessionManager.js.map +0 -1
  811. package/lib/module/web/player/RemoteCommandController.js +0 -92
  812. package/lib/module/web/player/RemoteCommandController.js.map +0 -1
  813. package/lib/typescript/src/features/browser-config.d.ts +0 -12
  814. package/lib/typescript/src/features/browser-config.d.ts.map +0 -1
  815. package/lib/typescript/src/features/carConnection.d.ts +0 -20
  816. package/lib/typescript/src/features/carConnection.d.ts.map +0 -1
  817. package/lib/typescript/src/features/gate.d.ts +0 -78
  818. package/lib/typescript/src/features/gate.d.ts.map +0 -1
  819. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +0 -34
  820. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +0 -1
  821. package/lib/typescript/src/features/player/validateOptions.d.ts +0 -2
  822. package/lib/typescript/src/features/player/validateOptions.d.ts.map +0 -1
  823. package/lib/typescript/src/utils/artwork.d.ts +0 -19
  824. package/lib/typescript/src/utils/artwork.d.ts.map +0 -1
  825. package/nitrogen/generated/android/c++/JArtworkVariants.hpp +0 -61
  826. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  827. package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +0 -58
  828. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +0 -100
  829. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +0 -112
  830. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +0 -121
  831. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +0 -123
  832. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +0 -133
  833. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +0 -121
  834. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +0 -139
  835. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +0 -134
  836. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +0 -131
  837. package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +0 -79
  838. package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +0 -80
  839. package/nitrogen/generated/android/c++/JGate.hpp +0 -62
  840. package/nitrogen/generated/android/c++/JGateDecision.hpp +0 -64
  841. package/nitrogen/generated/android/c++/JGateReason.hpp +0 -58
  842. package/nitrogen/generated/android/c++/JIOSOptions.hpp +0 -95
  843. package/nitrogen/generated/android/c++/JMediaReference.hpp +0 -58
  844. package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +0 -74
  845. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +0 -182
  846. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +0 -96
  847. package/nitrogen/generated/android/c++/JOutputType.hpp +0 -82
  848. package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +0 -76
  849. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +0 -86
  850. package/nitrogen/generated/android/c++/JStallReason.hpp +0 -58
  851. package/nitrogen/generated/android/c++/JTrackRequest.hpp +0 -93
  852. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  853. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  854. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +0 -26
  855. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +0 -75
  856. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +0 -41
  857. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +0 -121
  858. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +0 -26
  859. package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +0 -70
  860. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +0 -50
  861. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +0 -80
  862. 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 +0 -80
  863. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +0 -80
  864. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +0 -80
  865. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +0 -80
  866. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +0 -80
  867. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +0 -80
  868. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +0 -44
  869. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +0 -23
  870. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeGateRequest.kt +0 -44
  871. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +0 -66
  872. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +0 -44
  873. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +0 -31
  874. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +0 -29
  875. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +0 -26
  876. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +0 -44
  877. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +0 -23
  878. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +0 -44
  879. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  880. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +0 -65
  881. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +0 -53
  882. package/nitrogen/generated/ios/swift/ArtworkVariants.swift +0 -34
  883. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  884. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  885. package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +0 -67
  886. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +0 -54
  887. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +0 -69
  888. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +0 -54
  889. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +0 -60
  890. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +0 -60
  891. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +0 -61
  892. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +0 -61
  893. package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +0 -77
  894. package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +0 -46
  895. package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +0 -46
  896. package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +0 -46
  897. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +0 -53
  898. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +0 -66
  899. package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +0 -81
  900. package/nitrogen/generated/ios/swift/Gate.swift +0 -47
  901. package/nitrogen/generated/ios/swift/GateDecision.swift +0 -40
  902. package/nitrogen/generated/ios/swift/GateEvent.swift +0 -29
  903. package/nitrogen/generated/ios/swift/GateReason.swift +0 -40
  904. package/nitrogen/generated/ios/swift/IOSOptions.swift +0 -51
  905. package/nitrogen/generated/ios/swift/MediaReference.swift +0 -40
  906. package/nitrogen/generated/ios/swift/NativeGateRequest.swift +0 -58
  907. package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +0 -206
  908. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +0 -90
  909. package/nitrogen/generated/ios/swift/OutputType.swift +0 -72
  910. package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +0 -64
  911. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +0 -57
  912. package/nitrogen/generated/ios/swift/StallReason.swift +0 -40
  913. package/nitrogen/generated/ios/swift/TrackRequest.swift +0 -106
  914. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  915. package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +0 -19
  916. package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +0 -18
  917. package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +0 -87
  918. package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +0 -76
  919. package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +0 -110
  920. package/nitrogen/generated/shared/c++/GateReason.hpp +0 -76
  921. package/nitrogen/generated/shared/c++/IOSOptions.hpp +0 -91
  922. package/nitrogen/generated/shared/c++/MediaReference.hpp +0 -76
  923. package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +0 -97
  924. package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +0 -138
  925. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +0 -92
  926. package/nitrogen/generated/shared/c++/OutputType.hpp +0 -108
  927. package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +0 -100
  928. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +0 -94
  929. package/nitrogen/generated/shared/c++/StallReason.hpp +0 -76
  930. package/nitrogen/generated/shared/c++/TrackRequest.hpp +0 -93
  931. package/src/codegen-no-config-variant.test.ts +0 -70
  932. package/src/features/browser-config.test.ts +0 -110
  933. package/src/features/browser-config.ts +0 -293
  934. package/src/features/browser.test.ts +0 -19
  935. package/src/features/carConnection.ts +0 -36
  936. package/src/features/gate.test.ts +0 -72
  937. package/src/features/gate.ts +0 -155
  938. package/src/features/playback/trackPlaybackTime.test.ts +0 -80
  939. package/src/features/playback/trackPlaybackTime.ts +0 -101
  940. package/src/features/player/options.test.ts +0 -38
  941. package/src/features/player/setup.test.ts +0 -125
  942. package/src/features/player/validateOptions.ts +0 -22
  943. package/src/features/queue/queue.test.ts +0 -36
  944. package/src/public-surface.test.ts +0 -116
  945. package/src/utils/artwork.ts +0 -30
  946. package/src/web/NativeAudioBrowser.playWhenReady.test.ts +0 -301
  947. package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +0 -139
  948. package/src/web/TrackPlayer/PlaybackStateMachine.ts +0 -82
  949. package/src/web/TrackPlayer/PlaybackTimer.test.ts +0 -67
  950. package/src/web/TrackPlayer/PlaybackTimer.ts +0 -33
  951. package/src/web/TrackPlayer/Player.dispatch.test.ts +0 -229
  952. package/src/web/TrackPlayer/Player.load.test.ts +0 -86
  953. package/src/web/TrackPlayer/PlayingStateFactory.test.ts +0 -51
  954. package/src/web/TrackPlayer/PlayingStateFactory.ts +0 -24
  955. package/src/web/TrackPlayer/QueueManager.test.ts +0 -289
  956. package/src/web/TrackPlayer/QueueManager.ts +0 -302
  957. package/src/web/TrackPlayer/QueuePlayer.ts +0 -188
  958. package/src/web/TrackPlayer/VolumeFader.ts +0 -74
  959. package/src/web/browser/__tests__/layer-resolver.test.ts +0 -69
  960. package/src/web/browser/assertBrowsePageShape.test.ts +0 -27
  961. package/src/web/browser/assertBrowsePageShape.ts +0 -27
  962. package/src/web/browser/classifyTrackNavigation.test.ts +0 -41
  963. package/src/web/browser/classifyTrackNavigation.ts +0 -35
  964. package/src/web/browser/parseSearchResponse.test.ts +0 -25
  965. package/src/web/browser/parseSearchResponse.ts +0 -15
  966. package/src/web/http/RequestConfigBuilder.test.ts +0 -59
  967. package/src/web/playbackErrorKind.test.ts +0 -55
  968. package/src/web/playbackErrorKind.ts +0 -56
  969. package/src/web/player/MediaSessionManager.test.ts +0 -220
  970. package/src/web/player/MediaSessionManager.ts +0 -151
  971. package/src/web/player/NowPlayingManager.test.ts +0 -46
  972. package/src/web/player/RemoteCommandController.test.ts +0 -171
  973. package/src/web/player/RemoteCommandController.ts +0 -166
@@ -24,45 +24,19 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
24
24
  /// Shared instance for CarPlay access. Set when HybridAudioBrowser is created.
25
25
  private(set) nonisolated(unsafe) weak static var shared: HybridAudioBrowser?
26
26
 
27
- /// Fires when a new instance becomes `shared` (e.g. on a JS runtime reload),
28
- /// so connected external controllers (CarPlay) can re-subscribe — their
29
- /// listeners sit on the previous instance's emitters and would otherwise go
30
- /// silent.
31
- static let instanceChangedEmitter = Emitter<Void>()
32
-
33
27
  // MARK: - Private Properties
34
28
 
35
29
  private var player: TrackPlayer?
36
30
  private let networkMonitor = NetworkMonitor()
37
- private let playbackStateStore = PlaybackStateStore()
38
31
  let browserManager = BrowserManager()
39
32
  private let trackSelector: TrackSelector
40
33
  private var volumeObservation: NSKeyValueObservation?
41
34
  private var routeChangeObserver: NSObjectProtocol?
42
- private var interruptionObserver: NSObjectProtocol?
43
- private var mediaServicesResetObserver: NSObjectProtocol?
44
- /// Re-applies our resolved audio-session category. Captured at setup so a
45
- /// media-services reset (which clears the category) can restore it.
46
- private var applyAudioSessionCategory: () -> Void = {}
47
35
  private var nowPlayingOverride: NowPlayingUpdate?
48
- /// When false, the now-playing surface uses the raw track fields (override + formatter ignored).
49
- private var nowPlayingMetadataEnabled = true
50
- /// Customizes the now-playing title/subtitle from the track + live timed metadata + playback state.
51
- private var nowPlayingMetadataFormatter: ((_ params: FormatNowPlayingParams) -> Promise<NowPlayingUpdate?>)?
52
- /// Keep the media session controllable through a terminal error (see `keepSessionAliveOnError`).
53
- private var keepSessionAliveOnError = true
54
- /// Initial player state staged before the player exists — from setup options or the imperative
55
- /// setters called pre-setup. Strict last-write-wins; consumed when the player comes up.
56
- private var pendingPlayWhenReady: Bool?
57
- private var pendingRepeatMode: RepeatMode?
58
- /// Latest live timed (ICY/ID3) metadata, passed to the formatter. Cleared on track change.
59
- private var latestTimedMetadata: TimedMetadata?
60
36
  private let playerOptions = PlayerUpdateOptions()
61
37
 
62
38
  /// Configured playback rates for the playback-rate capability (for CarPlay rate cycling)
63
39
  var playbackRates: [Double] { playerOptions.playbackRates }
64
- var carPlayUpNextButton: Bool { playerOptions.carPlayUpNextButton }
65
- var carPlayNowPlayingButtons: [CarPlayNowPlayingButton] { playerOptions.carPlayNowPlayingButtons }
66
40
  private var lastNavigationError: NavigationError? {
67
41
  didSet {
68
42
  // Skip if both nil (no real change)
@@ -89,21 +63,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
89
63
  public let queueChangedEmitter = Emitter<[Track]>()
90
64
  public let navigationErrorEmitter = Emitter<NavigationErrorEvent>()
91
65
  public let repeatModeChangedEmitter = Emitter<RepeatModeChangedEvent>()
92
- public let shuffleChangedEmitter = Emitter<Bool>()
93
66
  public let externalContentChangedEmitter = Emitter<String>()
94
- /// Signals that the gate's active state changed (set or cleared). Carries
95
- /// the active flag; per-request chrome is obtained at each serve site via
96
- /// `gateDecision(for:)`, so the emitter only tells surfaces to re-render.
97
- public let gateChangedEmitter = Emitter<Bool>()
98
- /// Fired when a voice media intent (`handlePlayMediaIntent`) successfully
99
- /// starts playback. CarPlay surfaces the Now Playing template in response, so
100
- /// the user lands on the playing station (with the rest of the results in Up
101
- /// Next) rather than the screen they invoked Siri from. No-op where unobserved.
102
- public let showNowPlayingRequestedEmitter = Emitter<Void>()
103
- /// Fires after `updateOptions` applies a change, so external-surface managers
104
- /// (CarPlay) can refresh UI driven by now-runtime-updatable options
105
- /// (now-playing buttons, Up Next).
106
- public let playerOptionsChangedEmitter = Emitter<Void>()
107
67
 
108
68
  // MARK: - Thread Safety
109
69
 
@@ -111,9 +71,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
111
71
  /// because DispatchQueue.main.sync blocks the caller — no concurrent access window.
112
72
  private func onMainActor<T>(_ work: @MainActor () -> T) -> T {
113
73
  if Thread.isMainThread {
114
- MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
74
+ return MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
115
75
  } else {
116
- DispatchQueue.main.sync {
76
+ return DispatchQueue.main.sync {
117
77
  MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
118
78
  }
119
79
  }
@@ -121,10 +81,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
121
81
 
122
82
  private func onMainActor<T>(_ work: @MainActor () throws -> T) throws -> T {
123
83
  if Thread.isMainThread {
124
- try MainActor.assumeIsolated { try UncheckedSendableBox(value: work()) }.value
84
+ return try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
125
85
  } else {
126
- try DispatchQueue.main.sync {
127
- try MainActor.assumeIsolated { try UncheckedSendableBox(value: work()) }.value
86
+ return try DispatchQueue.main.sync {
87
+ try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
128
88
  }
129
89
  }
130
90
  }
@@ -151,9 +111,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
151
111
  }
152
112
 
153
113
  public var configuration: NativeBrowserConfiguration = .init(
154
- path: nil, request: nil, requestResolver: nil, browse: nil, browseResolver: nil, media: nil, artwork: nil, nowPlayingArtwork: nil, routes: nil,
155
- singleTrack: nil, handleTrackLoad: nil,
156
- androidControllerOfflineError: nil, carPlayLoadingTitle: nil, resolveAlbumUrl: nil, formatNavigationError: nil,
114
+ path: nil, request: nil, media: nil, artwork: nil, routes: nil,
115
+ singleTrack: nil, handleTrackLoad: nil, androidControllerOfflineError: nil,
116
+ carPlayUpNextButton: nil, carPlayNowPlayingButtons: nil, formatNavigationError: nil,
157
117
  ) {
158
118
  didSet {
159
119
  onMainActor { browserManager.config = BrowserConfig(from: configuration) }
@@ -189,25 +149,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
189
149
  public var onNavigationError: (NavigationErrorEvent) -> Void = { _ in }
190
150
  public var onFormattedNavigationError: (FormattedNavigationError?) -> Void = { _ in }
191
151
 
192
- /// Per-request gate decision, set by JS. Native calls it at a serve site to
193
- /// learn whether a browse path / search interaction should be gated and with
194
- /// what chrome. The double `Promise` mirrors every other native→JS
195
- /// value-returning callback (e.g. `searchCallback`): the outer resolves to the
196
- /// JS-side Promise, the inner to the decision.
197
- ///
198
- /// Default fails CLOSED (`gated: true`). This default is only consulted in the
199
- /// init window where `hasResolver` was already recorded true but JS hasn't
200
- /// (re)bound the real resolver yet (JS reload / instance churn re-seeding gate
201
- /// state, e.g. CarPlay re-seeding `isGated` at scene connect). Serving content
202
- /// in that window would be a fail-open leak — same direction as the
203
- /// resolver-error path in `gateDecision`. Matches Android's default.
204
- public var resolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> = { _ in
205
- Promise.resolved(withResult: Promise.resolved(withResult: GateDecision(gated: true, gate: nil)))
206
- }
207
-
208
- /// Fired when a request is gated (served the gate). Set by JS.
209
- public var onGate: (GateEvent) -> Void = { _ in }
210
-
211
152
  // MARK: - Player Callbacks
212
153
 
213
154
  public var onChapterMetadata: ([ChapterMetadata]) -> Void = { _ in }
@@ -218,7 +159,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
218
159
  public var onPlaybackPlayWhenReadyChanged: (PlaybackPlayWhenReadyChangedEvent) -> Void = { _ in }
219
160
  public var onPlaybackPlayingState: (PlayingState) -> Void = { _ in }
220
161
  public var onPlaybackProgressUpdated: (PlaybackProgressUpdatedEvent) -> Void = { _ in }
221
- public var onPlaybackInterval: () -> Void = {}
222
162
  public var onPlaybackQueueEnded: (PlaybackQueueEndedEvent) -> Void = { _ in }
223
163
  public var onPlaybackQueueChanged: ([Track]) -> Void = { _ in }
224
164
  public var onPlaybackRepeatModeChanged: (RepeatModeChangedEvent) -> Void = { _ in }
@@ -228,8 +168,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
228
168
 
229
169
  // MARK: - Remote Callbacks
230
170
 
171
+ public var onRemoteBookmark: () -> Void = {}
172
+ public var onRemoteDislike: () -> Void = {}
231
173
  public var onRemoteJumpBackward: (RemoteJumpBackwardEvent) -> Void = { _ in }
232
174
  public var onRemoteJumpForward: (RemoteJumpForwardEvent) -> Void = { _ in }
175
+ public var onRemoteLike: () -> Void = {}
233
176
  public var onRemoteNext: () -> Void = {}
234
177
  public var onRemotePause: () -> Void = {}
235
178
  public var onRemotePlay: () -> Void = {}
@@ -237,13 +180,17 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
237
180
  public var onRemotePlaySearch: (RemotePlaySearchEvent) -> Void = { _ in }
238
181
  public var onRemotePrevious: () -> Void = {}
239
182
  public var onRemoteSeek: (RemoteSeekEvent) -> Void = { _ in }
183
+ public var onRemoteSetRating: (RemoteSetRatingEvent) -> Void = { _ in }
240
184
  public var onRemoteSkip: (RemoteSkipEvent) -> Void = { _ in }
241
185
  public var onRemoteStop: () -> Void = {}
242
186
 
243
187
  // MARK: - Remote Handlers (optional overrides from JS)
244
188
 
189
+ public var handleRemoteBookmark: (() -> Void)?
190
+ public var handleRemoteDislike: (() -> Void)?
245
191
  public var handleRemoteJumpBackward: ((RemoteJumpBackwardEvent) -> Void)?
246
192
  public var handleRemoteJumpForward: ((RemoteJumpForwardEvent) -> Void)?
193
+ public var handleRemoteLike: (() -> Void)?
247
194
  public var handleRemoteNext: (() -> Void)?
248
195
  public var handleRemotePause: (() -> Void)?
249
196
  public var handleRemotePlay: (() -> Void)?
@@ -261,9 +208,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
261
208
  public var onNowPlayingChanged: (NowPlayingMetadata) -> Void = { _ in }
262
209
  public var onOnlineChanged: (Bool) -> Void = { _ in } {
263
210
  didSet {
264
- // The monitor->JS bridge is wired centrally in setupPlayer (so it doesn't depend on a JS
265
- // subscription and can also drive the now-playing re-render + player reconnect). Here we only
266
- // push the current state to a newly-attached subscriber.
211
+ networkMonitor.onChanged = { [weak self] isOnline in
212
+ self?.onOnlineChanged(isOnline)
213
+ }
214
+ // Immediately notify current state
267
215
  onOnlineChanged(networkMonitor.isOnline)
268
216
  }
269
217
  }
@@ -277,7 +225,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
277
225
  }
278
226
  }
279
227
 
280
- public var onOutputChanged: (Output) -> Void = { _ in } {
228
+ public var onIosOutputChanged: (IosOutput) -> Void = { _ in } {
281
229
  didSet {
282
230
  setupRouteChangeObserver()
283
231
  }
@@ -297,11 +245,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
297
245
  HybridAudioBrowser.shared?.player?.destroy()
298
246
  playerAndConfiguredBrowser.reset()
299
247
  HybridAudioBrowser.shared = self
300
- // Tell connected external controllers (CarPlay) to re-subscribe against
301
- // this instance. Unconditional: `shared` is weak, so on a JS reload the
302
- // old instance may already be gone — "shared was nil" does not mean no
303
- // controller is subscribed to a dead instance's emitters.
304
- HybridAudioBrowser.instanceChangedEmitter.emit(())
305
248
  }
306
249
  setupEmitterToNitroForwarding()
307
250
  setupBrowserCallbacks()
@@ -376,17 +319,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
376
319
  let defaultFormatted = navError.defaultFormatted()
377
320
  if let formatter = onMainActor({ browserManager.config.formatNavigationError }) {
378
321
  let params = FormatNavigationErrorParams(error: navError, defaultFormatted: defaultFormatted, path: path)
379
- // Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes.
380
- // The result callbacks are @Sendable: a plain closure would inherit MainActor
381
- // isolation from this block, and Nitro resolves its promises synchronously on the
382
- // JS thread — Swift 6.2's dynamic isolation check then traps on closure entry.
383
- // The property is JS-thread-touched elsewhere too, so no hop back is needed.
322
+ // Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes
384
323
  DispatchQueue.main.async { [weak self] in
385
324
  formatter(params)
386
- .then { @Sendable [weak self] customDisplay in
325
+ .then { customDisplay in
387
326
  self?.lastFormattedNavigationError = customDisplay ?? defaultFormatted
388
327
  }
389
- .catch { @Sendable [weak self] _ in
328
+ .catch { _ in
390
329
  self?.lastFormattedNavigationError = defaultFormatted
391
330
  }
392
331
  }
@@ -415,11 +354,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
415
354
  guard let player else { return }
416
355
  let result = await trackSelector.select(track: track, player: player)
417
356
  switch result {
418
- case let .play(intent):
357
+ case .play(let intent):
419
358
  executePlayback(intent)
420
359
  case .intercepted:
421
360
  break
422
- case let .browse(url):
361
+ case .browse(let url):
423
362
  navigateToBrowsableUrl(url)
424
363
  case .none:
425
364
  break
@@ -430,15 +369,15 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
430
369
  private func executePlayback(_ intent: TrackSelector.PlaybackIntent) {
431
370
  onMainActor {
432
371
  switch intent {
433
- case let .skipTo(index):
372
+ case .skipTo(let index):
434
373
  do {
435
374
  try player?.skipTo(index, playWhenReady: true)
436
375
  } catch {
437
376
  logger.error("Failed to skip to track at index \(index): \(error)")
438
377
  }
439
- case let .setQueue(tracks, startIndex, sourcePath):
378
+ case .setQueue(let tracks, let startIndex, let sourcePath):
440
379
  player?.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: sourcePath)
441
- case let .loadTrack(track):
380
+ case .loadTrack(let track):
442
381
  player?.load(track, playWhenReady: true)
443
382
  }
444
383
  }
@@ -477,12 +416,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
477
416
  lastFormattedNavigationError
478
417
  }
479
418
 
480
- /// Internal signal sent on `externalContentChangedEmitter` to tell CarPlay to
481
- /// refresh every displayed template. Not a real path (paths are always
482
- /// `/…`), so it can't collide with one; kept internal — the public API is
483
- /// `invalidateAllContent()`.
484
- static let invalidateAllSentinel = "__rnab_invalidate_all__"
485
-
486
419
  public func notifyContentChanged(path: String) throws {
487
420
  onMainActor { browserManager.invalidateContentCache(path) }
488
421
 
@@ -501,168 +434,22 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
501
434
  }
502
435
  }
503
436
 
504
- public func invalidateAllContent() throws {
505
- onMainActor { browserManager.clearContentCache() }
506
-
507
- // Tell external controllers (CarPlay) to refresh every displayed template.
508
- externalContentChangedEmitter.emit(Self.invalidateAllSentinel)
509
-
510
- // Re-query the tabs source too: a callback source can resolve to new
511
- // titles after an invalidation (e.g. a locale switch). BrowserManager
512
- // emits tabsChanged only when the result actually differs, and a failed
513
- // re-query keeps the current tabs. (Android needs no equivalent — its
514
- // root re-query already runs queryTabs(); the web stub re-queries tabs on
515
- // the re-navigation.)
516
- Task {
517
- _ = try? await browserManager.queryTabs()
518
- }
519
-
520
- // Re-resolve the JS-facing current path with the cache cleared.
521
- Task {
522
- do {
523
- try await browserManager.refresh()
524
- } catch {
525
- handleNavigationError(error, path: onMainActor { browserManager.getPath() })
526
- }
527
- }
528
- }
529
-
530
437
  public func setFavorites(favorites: [String]) throws {
531
438
  onMainActor { browserManager.setFavorites(favorites) }
532
439
  }
533
440
 
534
- // MARK: - Gate
535
-
536
- /// The gate's default chrome, if one was set. Served when the resolver
537
- /// returns a gated decision with no per-request override, and for the static
538
- /// gate (no resolver). May be nil for a resolver-only gate.
539
- private(set) var defaultChrome: Gate?
540
-
541
- /// Whether JS installed a per-request resolver. When false the gate is static
542
- /// — every request is gated with `defaultChrome` and the JS hop is skipped.
543
- private(set) var hasResolver = false
544
-
545
- /// True once `setGate` runs, until `clearGate`. While active, external
546
- /// surfaces (CarPlay) keep their tabs visible but consult `gateDecision(for:)`
547
- /// per request, rendering the gate chrome where gated and refusing
548
- /// external-surface search. Playback, the queue and Now Playing are
549
- /// unaffected — a gate blocks finding content, never hearing it.
550
- private(set) var isGateActive = false
551
-
552
- /// The minimal built-in gate served when a request is gated but neither a
553
- /// per-request override nor a stored default chrome exists (resolver-only
554
- /// gate returning `true`).
555
- static let builtInGate = Gate(title: "Unavailable", message: nil)
556
-
557
- /// The outcome of a single gate decision: whether to gate, and (if so) the
558
- /// chrome to render.
559
- struct GateOutcome {
560
- let gated: Bool
561
- let chrome: Gate?
562
- }
563
-
564
- public func setGate(gate: Gate?, hasResolver: Bool) throws {
565
- onMainActor {
566
- defaultChrome = gate
567
- self.hasResolver = hasResolver
568
- isGateActive = true
569
- gateChangedEmitter.emit(true)
570
- }
571
- }
572
-
573
- public func clearGate() throws {
574
- onMainActor {
575
- guard isGateActive else { return }
576
- defaultChrome = nil
577
- hasResolver = false
578
- isGateActive = false
579
- gateChangedEmitter.emit(false)
580
- }
581
- }
582
-
583
- /// The single choke point for every gate enforcement site. Asks whether
584
- /// `request` should be gated and, if so, with what chrome (override → stored
585
- /// default → built-in). When no resolver is installed the gate is static and
586
- /// resolves without a JS hop.
587
- ///
588
- /// Resolver errors **fail CLOSED by design**: a thrown/rejected resolver, or a
589
- /// rejection at either `.await()` layer (bridge tear-down on JS reload, the JS
590
- /// runtime mid-reload, a serialization error) cannot prove the request is
591
- /// allowed — and a gate exists to withhold content, so serving on error would
592
- /// be a content leak (a paywall bypass). On error we gate with the stored
593
- /// default / built-in chrome so the user still sees a coherent gate page. A
594
- /// *successful* resolver returning `gated: false` still allows the request.
595
- @MainActor
596
- func gateDecision(for request: NativeGateRequest) async -> GateOutcome {
597
- guard isGateActive else { return GateOutcome(gated: false, chrome: nil) }
598
- if !hasResolver { return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate) }
599
- guard let decision = try? await resolveGate(request).await().await() else {
600
- // Resolver error: fail CLOSED (see doc comment) — gate with the default /
601
- // built-in so the gate page still renders rather than leaking content.
602
- return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate)
603
- }
604
- guard decision.gated else { return GateOutcome(gated: false, chrome: nil) } // explicit allow
605
- return GateOutcome(gated: true, chrome: decision.gate ?? defaultChrome ?? Self.builtInGate)
606
- }
607
-
608
- // MARK: - Car Connection (CarPlay)
609
-
610
- /// Static because the CarPlay scene can connect before the JS runtime
611
- /// creates the shared instance (cold start in the car).
612
- private nonisolated(unsafe) static var carPlayConnected = false
613
-
614
- /// Called by the CarPlay scene delegate (via `RNABCarPlayController`) on
615
- /// scene connect/disconnect — not from controller start/stop, which also
616
- /// cycle on a JS reload and would emit spurious connection changes.
617
- static func setCarPlayConnected(_ connected: Bool) {
618
- guard carPlayConnected != connected else { return }
619
- carPlayConnected = connected
620
- shared?.onCarConnectedChanged(connected)
621
- }
622
-
623
- public func isCarConnected() throws -> Bool {
624
- HybridAudioBrowser.carPlayConnected
625
- }
626
-
627
- public var onCarConnectedChanged: (Bool) -> Void = { _ in } {
628
- didSet {
629
- // Immediately notify current state (the scene may have connected before
630
- // this JS runtime subscribed).
631
- onCarConnectedChanged(HybridAudioBrowser.carPlayConnected)
632
- }
633
- }
634
-
635
441
  // MARK: - Player Setup
636
442
 
637
- public func setupPlayer(options: NativeSetupPlayerOptions) throws -> Promise<Void> {
443
+ public func setupPlayer(options: PartialSetupPlayerOptions) throws -> Promise<Void> {
638
444
  Promise.async {
639
- // Configure the audio session from the iOS setup options (category, mode, route policy, and
640
- // options like `allowBluetooth` / `allowAirPlay`), but do NOT activate it here. Activating a
641
- // non-mixable `.playback` session interrupts other apps' audio — so activating at setup
642
- // pauses whatever the user is listening to (Safari, Spotify, a podcast) the instant our app
643
- // launches, before they play anything. Setting the category is silent; only activation
644
- // interrupts. The session is activated lazily when playback actually starts producing output
645
- // (see `playerDidChangePlayingState`) and re-activated on interruption-end. Best-effort: a
646
- // category-config failure must never brick setup.
445
+ // Configure audio session
647
446
  let session = AVAudioSession.sharedInstance()
648
- let cfg: (
649
- category: AVAudioSession.Category,
650
- mode: AVAudioSession.Mode,
651
- policy: AVAudioSession.RouteSharingPolicy,
652
- options: AVAudioSession.CategoryOptions,
653
- ) = options.ios?.resolveAudioSessionConfig()
654
- ?? (category: .playback, mode: .default, policy: .default, options: [])
655
- // Capture category application once so a media-services reset (which
656
- // clears the category) can restore the exact same config later.
657
- let applyCategory: () -> Void = {
658
- try? session.setCategory(cfg.category, mode: cfg.mode, policy: cfg.policy, options: cfg.options)
659
- }
660
- applyCategory()
447
+ try session.setCategory(.playback, mode: .default)
448
+ try session.setActive(true)
661
449
 
662
450
  // Create player and configure on main actor
663
451
  await MainActor.run { [weak self] in
664
452
  guard let self else { return }
665
- self.applyAudioSessionCategory = applyCategory
666
453
 
667
454
  // Create player with self as callbacks delegate
668
455
  player = TrackPlayer(callbacks: self)
@@ -680,52 +467,18 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
680
467
  // Wire up network monitor for accelerated retries when connectivity is restored
681
468
  player?.networkMonitor = networkMonitor
682
469
 
683
- // Central fan-out for connectivity changes (wired once here, idempotent on re-setup, and
684
- // independent of any JS subscription):
685
- // - bridge to the JS `onOnlineChanged` event (no-op default until JS subscribes);
686
- // - re-render now-playing so the formatter's offline/online label updates immediately on a
687
- // connectivity change, not only on the next playback transition (fixes the stall-entry
688
- // race where the device goes offline after the buffering line was already rendered);
689
- // - reconnect a stalled stream when connectivity returns (see TrackPlayer.handleNetworkRestored).
690
- // The monitor only mutates state on the main thread, so this fires main-isolated.
691
- networkMonitor.onChanged = { [weak self] isOnline in
692
- MainActor.assumeIsolated {
693
- guard let self else { return }
694
- self.onOnlineChanged(isOnline)
695
- self.applyNowPlayingMetadata()
696
- if isOnline { self.player?.handleNetworkRestored() }
697
- }
698
- }
699
-
700
- // Observe audio-session interruptions so another app taking over audio
701
- // (or a phone call) reflects as a real pause rather than a stuck state.
702
- setupInterruptionObserver()
703
-
704
- // Observe media-server resets so a long-running stream recovers instead
705
- // of going permanently silent.
706
- setupMediaServicesResetObserver()
707
-
708
470
  // Configure media URL resolver
709
- player?.mediaLoader.mediaUrlResolver = { [weak self] src, track in
471
+ player?.mediaLoader.mediaUrlResolver = { [weak self] src in
710
472
  guard let self else {
711
473
  return MediaResolvedUrl(url: src, headers: nil, userAgent: nil)
712
474
  }
713
- return await browserManager.resolveMediaUrl(src, track: track)
475
+ return await browserManager.resolveMediaUrl(src)
714
476
  }
715
477
 
716
478
  // Configure artwork URL resolver for Now Playing (with size context)
717
479
  player?.nowPlayingUpdater.artworkUrlResolver = { [weak self] track, imageContext in
718
480
  guard let self else { return nil }
719
- let perRoute = (track.id?.isEmpty == false) ? browserManager.config.nowPlayingArtwork : nil
720
- return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: perRoute, imageContext: imageContext)
721
- }
722
-
723
- // Emit the JS `onNowPlayingChanged` event when the rendered text lines change. The updater
724
- // owns the system now-playing write; this shapes the JS metadata (elapsed time / artwork).
725
- player?.nowPlayingUpdater.onChanged = { [weak self] track, title, artist, album in
726
- guard let self else { return }
727
- self.onNowPlayingChanged(
728
- self.makeNowPlayingMetadata(track: track, title: title, artist: artist, album: album))
481
+ return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: nil, imageContext: imageContext)
729
482
  }
730
483
 
731
484
  // Configure sleep timer callback
@@ -733,30 +486,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
733
486
  self?.onSleepTimerChanged(state)
734
487
  }
735
488
 
736
- // Now-playing: whether to manage metadata, the optional formatter that owns the rendered
737
- // lines, and whether to keep the session alive through a terminal error.
738
- nowPlayingMetadataEnabled = options.autoUpdateNowPlayingMetadata ?? true
739
- nowPlayingMetadataFormatter = options.nowPlayingMetadataFormatter
740
- // Stored for API symmetry, but iOS needs no masking: a terminal error resolves the playback
741
- // state to *paused* (PlaybackCoordinator.applySideEffects), the now-playing info is retained
742
- // (never cleared on error), and next/previous stay enabled (gated on queue position, not
743
- // state). So the session stays controllable through errors by default — the behavior Android
744
- // achieves via InterceptingPlayer is already the iOS norm.
745
- keepSessionAliveOnError = options.keepSessionAliveOnError ?? true
746
-
747
- // The bundled runtime options and initial state are part of the atomic launch
748
- // description. Stage the state first so setup options and earlier pre-setup setter
749
- // calls resolve by strict last-write-wins, then apply everything to the live player.
750
- if let bundled = options.options {
751
- updateOptions(options: bundled)
752
- } else {
753
- // updateOptions applies remote commands itself; without bundled options, apply the
754
- // defaults (play, pause, next, previous, seekTo) here.
755
- applyRemoteCommands()
756
- }
757
- if let mode = options.repeatMode { pendingRepeatMode = mode }
758
- if let intent = options.playWhenReady { pendingPlayWhenReady = intent }
759
- applyPendingPlayerState()
489
+ // Apply default remote commands (play, pause, next, previous, seekTo)
490
+ applyRemoteCommands()
760
491
 
761
492
  // Notify listeners that player is ready (e.g., CarPlay)
762
493
  playerAndConfiguredBrowser.check()
@@ -764,22 +495,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
764
495
  }
765
496
  }
766
497
 
767
- /// Applies initial player state staged before the player existed — from setup options or the
768
- /// imperative setters called pre-setup. Consumed on apply so a later re-setup doesn't replay
769
- /// stale state.
770
- @MainActor
771
- private func applyPendingPlayerState() {
772
- guard let player else { return }
773
- if let mode = pendingRepeatMode {
774
- player.repeatMode = mode
775
- pendingRepeatMode = nil
776
- }
777
- if let intent = pendingPlayWhenReady {
778
- player.playWhenReady = intent
779
- pendingPlayWhenReady = nil
780
- }
781
- }
782
-
783
498
  public func updateOptions(options: NativeUpdateOptions) {
784
499
  onMainActor {
785
500
  let previousInterval = playerOptions.progressUpdateEventInterval
@@ -787,10 +502,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
787
502
  // Update stored options
788
503
  playerOptions.update(from: options)
789
504
 
790
- // Propagate the favorite match mode to the browser so it can hydrate
791
- // row hearts (the `favorite` capability is the single favoriting switch).
792
- browserManager.setFavoriteMatch(playerOptions.capabilities.favoriteMatch)
793
-
794
505
  // Apply remote commands to player
795
506
  applyRemoteCommands()
796
507
 
@@ -798,9 +509,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
798
509
  if playerOptions.progressUpdateEventInterval != previousInterval {
799
510
  player?.setProgressUpdateInterval(playerOptions.progressUpdateEventInterval)
800
511
  }
801
-
802
- onOptionsChanged(playerOptions.toOptions())
803
- playerOptionsChangedEmitter.emit(())
804
512
  }
805
513
  }
806
514
 
@@ -818,12 +526,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
818
526
  player.remoteCommands = remoteCommands
819
527
  }
820
528
 
821
- public func getOptions() throws -> Options {
822
- playerOptions.toOptions()
823
- }
824
-
825
- public func setPlaybackIntervalEnabled(enabled: Bool) {
826
- onMainActor { player?.setPlaybackIntervalEnabled(enabled) }
529
+ public func getOptions() throws -> UpdateOptions {
530
+ playerOptions.toUpdateOptions()
827
531
  }
828
532
 
829
533
  // MARK: - Playback Control
@@ -858,15 +562,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
858
562
  }
859
563
 
860
564
  public func setPlayWhenReady(playWhenReady: Bool) throws {
861
- onMainActor {
862
- // Pre-setup this stages the intent for the player to come up with — never a no-op.
863
- if let player { player.playWhenReady = playWhenReady }
864
- else { pendingPlayWhenReady = playWhenReady }
865
- }
565
+ onMainActor { player?.playWhenReady = playWhenReady }
866
566
  }
867
567
 
868
568
  public func getPlayWhenReady() throws -> Bool {
869
- onMainActor { player?.playWhenReady ?? pendingPlayWhenReady ?? false }
569
+ onMainActor { player?.playWhenReady ?? false }
870
570
  }
871
571
 
872
572
  public func seekTo(position: Double) throws {
@@ -877,10 +577,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
877
577
  onMainActor { player?.seekBy(offset) }
878
578
  }
879
579
 
880
- public func seekToLiveEdge() throws {
881
- onMainActor { player?.seekToLiveEdge() }
882
- }
883
-
884
580
  public func setVolume(level: Double) throws {
885
581
  onMainActor { player?.volume = Float(level) }
886
582
  }
@@ -918,15 +614,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
918
614
  }
919
615
 
920
616
  public func getRepeatMode() throws -> RepeatMode {
921
- onMainActor { player?.repeatMode ?? pendingRepeatMode ?? .off }
617
+ onMainActor { player?.repeatMode ?? .off }
922
618
  }
923
619
 
924
620
  public func setRepeatMode(mode: RepeatMode) throws {
925
- onMainActor {
926
- // Pre-setup this stages the mode for the player to come up with — never a no-op.
927
- if let player { player.repeatMode = mode }
928
- else { pendingRepeatMode = mode }
929
- }
621
+ onMainActor { player?.repeatMode = mode }
930
622
  }
931
623
 
932
624
  public func getShuffleEnabled() throws -> Bool {
@@ -938,13 +630,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
938
630
  }
939
631
 
940
632
  public func getPlaybackError() throws -> PlaybackError? {
941
- onMainActor { player?.coordinator.nitroPlaybackError() }
633
+ onMainActor { player?.playbackError?.toNitroError() }
942
634
  }
943
635
 
944
636
  public func retry() throws {
945
- // Re-resolve rather than replay the cached URL: short-lived URLs/tokens
946
- // may have expired since the failure (matches the internal retry path).
947
- onMainActor { player?.reloadResolving(startFromCurrentTime: true) }
637
+ onMainActor { player?.reload(startFromCurrentTime: true) }
948
638
  }
949
639
 
950
640
  // MARK: - Sleep Timer
@@ -958,9 +648,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
958
648
  }
959
649
  }
960
650
 
961
- public func setSleepTimer(seconds: Double, fadeDuration: Double?) throws {
651
+ public func setSleepTimer(seconds: Double) throws {
962
652
  onMainActor {
963
- player?.sleepTimerManager.set(seconds: seconds, fadeDuration: fadeDuration)
653
+ player?.sleepTimerManager.set(seconds: seconds)
964
654
  }
965
655
  }
966
656
 
@@ -1040,13 +730,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1040
730
  onMainActor {
1041
731
  guard let track = player?.currentTrack, let src = track.src else { return }
1042
732
  guard let index = player?.currentIndex, index >= 0 else { return }
1043
- // Optimistically reflect in the authoritative match set so the now-playing
1044
- // heart (hydrated from it in getActiveTrack) flips immediately; native
1045
- // reconciles to its canonical ids on the next setFavorites.
1046
- browserManager.setFavorited(src: src, favorited: favorited)
733
+ browserManager.updateFavorite(id: src, favorited: favorited)
734
+ // Create updated track with new favorited state
1047
735
  let updatedTrack = track.copying(favorited: favorited)
736
+ // Update the track in the player's queue so getActiveTrack() returns correct state
737
+ player?.replace(index, updatedTrack)
1048
738
  favoriteChangedEmitter.emit(FavoriteChangedEvent(track: updatedTrack, favorited: favorited))
1049
- // Fire active track changed so the now-playing heart + useActiveTrack() refresh.
739
+ // Fire active track changed so useActiveTrack() hook updates UI
1050
740
  let position = player?.currentTime ?? 0
1051
741
  activeTrackChangedEmitter.emit(PlaybackActiveTrackChangedEvent(
1052
742
  lastIndex: Double(index),
@@ -1058,24 +748,27 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1058
748
  }
1059
749
  }
1060
750
 
1061
- /// Whether the active (now-playing) track is favorited, per the authoritative
1062
- /// favorite set (via `getActiveTrack`'s hydration).
1063
- func isActiveTrackFavorited() -> Bool {
1064
- (try? getActiveTrack())?.favorited ?? false
1065
- }
1066
-
1067
751
  public func toggleActiveTrackFavorited() throws {
1068
- try? setActiveTrackFavorited(favorited: !isActiveTrackFavorited())
752
+ onMainActor {
753
+ guard let track = player?.currentTrack, let src = track.src else { return }
754
+ // Check current favorited state from cache
755
+ let currentTrack = browserManager.getCachedTrack(src)
756
+ let isFavorited = currentTrack?.favorited ?? track.favorited ?? false
757
+ try? setActiveTrackFavorited(favorited: !isFavorited)
758
+ }
1069
759
  }
1070
760
 
1071
- public func setQueue(tracks: [Track], startIndex: Double?, startPosition: Double?) throws {
1072
- onMainActor {
761
+ public func setQueue(tracks: [Track], startIndex: Double?, startPositionMs: Double?) throws {
762
+ try onMainActor {
1073
763
  guard let player else { return }
1074
- player.setQueue(
1075
- tracks,
1076
- initialIndex: startIndex.map { Int($0) } ?? 0,
1077
- startPositionMs: startPosition.map { $0 * 1000 },
1078
- )
764
+ player.clear()
765
+ player.add(tracks)
766
+ if let index = startIndex, index >= 0 {
767
+ try player.skipTo(Int(index))
768
+ }
769
+ if let position = startPositionMs {
770
+ player.seekTo(position / 1000.0)
771
+ }
1079
772
  }
1080
773
  }
1081
774
 
@@ -1100,11 +793,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1100
793
  }
1101
794
 
1102
795
  public func getActiveTrack() throws -> Track? {
1103
- // Hydrate `favorited` from the authoritative favorite set — the now-playing
1104
- // track is loaded outside the browse cache, so its own flag isn't otherwise
1105
- // kept in sync as favorites change. This is the single source of truth for
1106
- // "is the active track favorited" (now-playing heart, toggle direction, JS).
1107
- onMainActor { player?.currentTrack.map(browserManager.hydrateFavorite) }
796
+ onMainActor { player?.currentTrack }
1108
797
  }
1109
798
 
1110
799
  // MARK: - Now Playing
@@ -1116,57 +805,21 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1116
805
  }
1117
806
  }
1118
807
 
1119
- /// Transient now-playing fields (e.g. feedback for a refused remote
1120
- /// command). Outranks the formatter and the override while active, so live
1121
- /// metadata can't stomp it mid-flash. Reverted by a NATIVE timer — JS
1122
- /// timers pause with a backgrounded host on Android, and the lock screen
1123
- /// is exactly the backgrounded case — and cleared early on track change.
1124
- private var nowPlayingFlash: NowPlayingUpdate?
1125
- private var nowPlayingFlashRevert: DispatchWorkItem?
1126
-
1127
- /// Pending debounced audio-session release (see `playerShouldReleaseSession`).
1128
- private var sessionReleaseWork: DispatchWorkItem?
1129
-
1130
- public func flashNowPlaying(update: NowPlayingUpdate, durationMs: Double) throws {
1131
- onMainActor {
1132
- nowPlayingFlashRevert?.cancel()
1133
- nowPlayingFlash = update
1134
- let revert = DispatchWorkItem { [weak self] in
1135
- guard let self else { return }
1136
- self.nowPlayingFlash = nil
1137
- self.nowPlayingFlashRevert = nil
1138
- self.applyNowPlayingMetadata()
1139
- }
1140
- nowPlayingFlashRevert = revert
1141
- DispatchQueue.main.asyncAfter(deadline: .now() + durationMs / 1000, execute: revert)
1142
- applyNowPlayingMetadata()
1143
- }
1144
- }
1145
-
1146
- public func clearNowPlayingFlash() throws {
1147
- onMainActor {
1148
- guard nowPlayingFlash != nil else { return }
1149
- cancelNowPlayingFlash()
1150
- applyNowPlayingMetadata()
1151
- }
1152
- }
1153
-
1154
- private func cancelNowPlayingFlash() {
1155
- nowPlayingFlashRevert?.cancel()
1156
- nowPlayingFlashRevert = nil
1157
- nowPlayingFlash = nil
1158
- }
1159
-
1160
808
  public func getNowPlaying() throws -> NowPlayingMetadata? {
1161
809
  onMainActor {
1162
810
  guard let track = player?.currentTrack else { return nil }
1163
- let flash = nowPlayingFlash
1164
811
  let override = nowPlayingOverride
1165
- return makeNowPlayingMetadata(
1166
- track: track,
1167
- title: flash?.title ?? override?.title ?? track.title,
1168
- artist: flash?.artist ?? override?.artist ?? track.artist,
1169
- album: flash?.album ?? override?.album ?? track.album,
812
+ return NowPlayingMetadata(
813
+ elapsedTime: player?.currentTime,
814
+ title: override?.title ?? track.title,
815
+ album: track.album,
816
+ artist: override?.artist ?? track.artist,
817
+ duration: track.duration,
818
+ artwork: nowPlayingArtwork(for: track),
819
+ description: track.description,
820
+ mediaId: track.src ?? track.url,
821
+ genre: track.genre,
822
+ rating: nil,
1170
823
  )
1171
824
  }
1172
825
  }
@@ -1176,47 +829,36 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1176
829
  /// Falls back to the raw artwork string only if it's not an SF Symbol (which JS can't load).
1177
830
  private func nowPlayingArtwork(for track: Track) -> String? {
1178
831
  if let uri = track.artworkSource?.uri { return uri }
1179
- guard let artwork = track.artwork?.url else { return nil }
832
+ guard let artwork = track.artwork else { return nil }
1180
833
  return SFSymbolRenderer.isSFSymbol(artwork) ? nil : artwork
1181
834
  }
1182
835
 
1183
- /// Builds the JS-facing now-playing metadata for the given rendered text lines.
836
+ /// Applies the current now playing metadata (with override if set) to NowPlayingInfoCenter and notifies JS.
1184
837
  @MainActor
1185
- private func makeNowPlayingMetadata(
1186
- track: Track, title: String, artist: String?, album: String?,
1187
- ) -> NowPlayingMetadata {
1188
- NowPlayingMetadata(
838
+ private func applyNowPlayingMetadata() {
839
+ guard let track = player?.currentTrack else { return }
840
+ let override = nowPlayingOverride
841
+
842
+ let nowPlaying = NowPlayingMetadata(
1189
843
  elapsedTime: player?.currentTime,
1190
- title: title,
1191
- album: album,
1192
- artist: artist,
844
+ title: override?.title ?? track.title,
845
+ album: track.album,
846
+ artist: override?.artist ?? track.artist,
1193
847
  duration: track.duration,
1194
848
  artwork: nowPlayingArtwork(for: track),
1195
849
  description: track.description,
1196
850
  mediaId: track.src ?? track.url,
1197
851
  genre: track.genre,
852
+ rating: nil,
1198
853
  )
1199
- }
1200
854
 
1201
- /// Re-renders the now-playing surface via `NowPlayingUpdater`, handing it the current playback
1202
- /// signals, override, and formatter. Called on every track / metadata / playback-state change.
1203
- @MainActor
1204
- private func applyNowPlayingMetadata() {
1205
- guard let player, let track = player.currentTrack else { return }
1206
- // Classify a stall by connectivity so the formatter can show "No internet connection" vs
1207
- // "Reconnecting…" without a separate read. nil while not stalled.
1208
- let stalled: StallReason? =
1209
- player.isStalled ? (networkMonitor.getOnline() ? .buffering : .offline) : nil
1210
- player.nowPlayingUpdater.render(
1211
- track: track,
1212
- timedMetadata: latestTimedMetadata,
1213
- playWhenReady: player.playWhenReady,
1214
- stalled: stalled,
1215
- error: player.coordinator.nitroPlaybackError(),
1216
- flash: nowPlayingFlash,
1217
- override: nowPlayingMetadataEnabled ? nowPlayingOverride : nil,
1218
- formatter: nowPlayingMetadataEnabled ? nowPlayingMetadataFormatter : nil,
1219
- )
855
+ // Update NowPlayingInfoCenter with override values
856
+ if let override {
857
+ player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.title(override.title ?? track.title))
858
+ player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.artist(override.artist ?? track.artist))
859
+ }
860
+
861
+ onNowPlayingChanged(nowPlaying)
1220
862
  }
1221
863
 
1222
864
  // MARK: - Network
@@ -1251,63 +893,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1251
893
 
1252
894
  // MARK: - External Audio Output
1253
895
 
1254
- /// Observes audio-session interruptions (phone calls, or another app such as
1255
- /// Music/Spotify starting playback). iOS pauses our AVPlayer underneath us but
1256
- /// doesn't update our playback state, so without this the UI stays stuck on
1257
- /// "playing". On `.began` we reflect a real pause; on `.ended` we resume if we
1258
- /// were playing and the system says we should.
1259
- private func setupInterruptionObserver() {
1260
- if let observer = interruptionObserver {
1261
- NotificationCenter.default.removeObserver(observer)
1262
- }
1263
-
1264
- interruptionObserver = NotificationCenter.default.addObserver(
1265
- forName: AVAudioSession.interruptionNotification,
1266
- object: nil,
1267
- queue: .main,
1268
- ) { [weak self] notification in
1269
- self?.handleAudioSessionInterruption(notification)
1270
- }
1271
- }
1272
-
1273
- private func handleAudioSessionInterruption(_ notification: Notification) {
1274
- guard
1275
- let info = notification.userInfo,
1276
- let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
1277
- let type = AVAudioSession.InterruptionType(rawValue: typeValue)
1278
- else { return }
1279
-
1280
- switch type {
1281
- case .began:
1282
- // iOS 17+ fires a `.began` (with no matching `.ended`) when an output
1283
- // route such as headphones disappears. Reflecting a pause here would
1284
- // strand resume-intent state with nothing to clear it, and the
1285
- // route-change observer already handles the output change — so ignore it.
1286
- if #available(iOS 17.0, *),
1287
- let reasonValue = info[AVAudioSessionInterruptionReasonKey] as? UInt,
1288
- AVAudioSession.InterruptionReason(rawValue: reasonValue) == .routeDisconnected
1289
- {
1290
- return
1291
- }
1292
- onMainActor { player?.handleInterruptionBegan() }
1293
- case .ended:
1294
- let shouldResume = (info[AVAudioSessionInterruptionOptionKey] as? UInt)
1295
- .map { AVAudioSession.InterruptionOptions(rawValue: $0).contains(.shouldResume) }
1296
- ?? false
1297
- onMainActor {
1298
- // Reactivate the possibly-deactivated session before resuming — but
1299
- // only when a resume will happen: activating while staying paused
1300
- // grabs a non-mixable session with nothing to release it.
1301
- if shouldResume, player?.willResumeAfterInterruption == true {
1302
- try? AVAudioSession.sharedInstance().setActive(true)
1303
- }
1304
- player?.handleInterruptionEnded(shouldResume: shouldResume)
1305
- }
1306
- @unknown default:
1307
- break
1308
- }
1309
- }
1310
-
1311
896
  /// Sets up observer for audio route changes
1312
897
  private func setupRouteChangeObserver() {
1313
898
  if let observer = routeChangeObserver {
@@ -1318,95 +903,58 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1318
903
  forName: AVAudioSession.routeChangeNotification,
1319
904
  object: nil,
1320
905
  queue: .main,
1321
- ) { [weak self] notification in
1322
- guard let self else { return }
1323
- // The previous output disappeared (headphones unplugged, a Bluetooth
1324
- // speaker powered off). Pause rather than abruptly resume out of the
1325
- // built-in speaker — the platform convention every media app follows.
1326
- // A deliberate pause (clears play intent), not an interruption: the user
1327
- // restarts playback themselves once they want it.
1328
- if let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
1329
- AVAudioSession.RouteChangeReason(rawValue: reasonValue) == .oldDeviceUnavailable
1330
- {
1331
- // Also cancels a pending interruption auto-resume: unplugging during
1332
- // a call must not blast the built-in speaker when the call ends.
1333
- self.onMainActor { self.player?.handleRouteDisconnected() }
1334
- }
1335
- if let output = self.getCurrentOutput() {
1336
- self.onOutputChanged(output)
1337
- }
1338
- }
1339
- }
1340
-
1341
- /// Observes audio-server (`mediaserverd`) resets. When it fires, the session
1342
- /// category is cleared and every AVPlayer/session handle is invalid — a
1343
- /// long-running stream would otherwise go permanently silent until the app
1344
- /// restarts.
1345
- private func setupMediaServicesResetObserver() {
1346
- if let observer = mediaServicesResetObserver {
1347
- NotificationCenter.default.removeObserver(observer)
1348
- }
1349
- mediaServicesResetObserver = NotificationCenter.default.addObserver(
1350
- forName: AVAudioSession.mediaServicesWereResetNotification,
1351
- object: nil,
1352
- queue: .main,
1353
906
  ) { [weak self] _ in
1354
- guard let self else { return }
1355
- // Re-apply our category (the reset cleared it), then have the player
1356
- // recreate itself and reconnect the current stream.
1357
- self.applyAudioSessionCategory()
1358
- self.onMainActor { self.player?.handleMediaServicesReset() }
907
+ guard let self, let output = self.getCurrentOutput() else { return }
908
+ self.onIosOutputChanged(output)
1359
909
  }
1360
910
  }
1361
911
 
1362
912
  /// Gets the current audio output info
1363
- private func getCurrentOutput() -> Output? {
913
+ private func getCurrentOutput() -> IosOutput? {
1364
914
  let session = AVAudioSession.sharedInstance()
1365
915
  guard let output = session.currentRoute.outputs.first else { return nil }
1366
916
 
1367
- let (outputType, external): (OutputType, Bool) = switch output.portType {
917
+ let (outputType, external): (IosOutputType, Bool) = switch output.portType {
1368
918
  case .builtInSpeaker:
1369
- (.speaker, false)
919
+ (.builtInSpeaker, false)
1370
920
  case .builtInReceiver:
1371
- (.receiver, false)
921
+ (.builtInReceiver, false)
1372
922
  case .airPlay:
1373
923
  (.airplay, true)
1374
- case .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
1375
- (.bluetooth, true)
924
+ case .bluetoothA2DP:
925
+ (.bluetoothA2dp, true)
926
+ case .bluetoothHFP:
927
+ (.bluetoothHfp, true)
928
+ case .bluetoothLE:
929
+ (.bluetoothLe, true)
1376
930
  case .headphones:
1377
931
  (.headphones, true)
1378
932
  case .carAudio:
1379
- (.car, true)
933
+ (.carAudio, true)
1380
934
  case .HDMI:
1381
935
  (.hdmi, true)
1382
936
  case .usbAudio:
1383
- (.usb, true)
937
+ (.usbAudio, true)
1384
938
  default:
1385
939
  (.other, true)
1386
940
  }
1387
941
 
1388
- return Output(type: outputType, name: output.portName, external: external)
942
+ return IosOutput(type: outputType, name: output.portName, external: external)
1389
943
  }
1390
944
 
1391
- public func getOutput() throws -> Output? {
945
+ public func getIosOutput() throws -> IosOutput? {
1392
946
  getCurrentOutput()
1393
947
  }
1394
948
 
1395
- public func supportsOutputSwitcher() throws -> Bool {
1396
- // iOS can always present the system route picker (AVRoutePickerView).
1397
- true
1398
- }
1399
-
1400
- public func openOutputPicker() throws {
949
+ public func openIosOutputPicker() throws {
1401
950
  DispatchQueue.main.async { [weak self] in
1402
- guard let self else { return }
1403
951
  guard let windowScene = UIApplication.shared.connectedScenes
1404
952
  .compactMap({ $0 as? UIWindowScene })
1405
953
  .first(where: { $0.activationState == .foregroundActive }),
1406
954
  let window = windowScene.windows.first(where: { $0.isKeyWindow }),
1407
955
  var topController = window.rootViewController
1408
956
  else {
1409
- self.logger.error("openOutputPicker: no active window scene / root view controller")
957
+ self?.logger.debug("openIosOutputPicker: Could not find active window scene")
1410
958
  return
1411
959
  }
1412
960
 
@@ -1415,52 +963,25 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1415
963
  topController = presented
1416
964
  }
1417
965
 
1418
- // AVRoutePickerView has no public API to present its picker
1419
- // programmatically, so we add it to the hierarchy and tap its internal
1420
- // button. Two requirements that are easy to get wrong:
1421
- // 1. The view must be *visible* to the system (a `isHidden` source view
1422
- // suppresses the presentation), so we keep it transparent instead.
1423
- // 2. The internal button can be nested below the top-level subviews on
1424
- // modern iOS, so we search recursively rather than only direct children.
1425
- let routePicker = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
1426
- routePicker.alpha = 0.01
966
+ let routePicker = AVRoutePickerView(frame: .zero)
967
+ routePicker.isHidden = true
1427
968
  topController.view.addSubview(routePicker)
1428
969
 
1429
- let button = Self.firstButton(in: routePicker)
1430
- self.logger.notice(
1431
- "openOutputPicker: hierarchy=\(Self.describeHierarchy(routePicker), privacy: .public), buttonFound=\(button != nil, privacy: .public)",
1432
- )
1433
-
1434
- if let button {
1435
- button.sendActions(for: .touchUpInside)
1436
- } else {
1437
- self.logger.error("openOutputPicker: no UIButton found inside AVRoutePickerView")
970
+ // Find the button inside AVRoutePickerView and trigger it
971
+ for subview in routePicker.subviews {
972
+ if let button = subview as? UIButton {
973
+ button.sendActions(for: .touchUpInside)
974
+ break
975
+ }
1438
976
  }
1439
977
 
1440
- // Keep the source view in the hierarchy long enough for the picker to
1441
- // present (on iPad it anchors a popover to this view).
1442
- DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
978
+ // Remove after a delay to ensure picker has opened
979
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
1443
980
  routePicker.removeFromSuperview()
1444
981
  }
1445
982
  }
1446
983
  }
1447
984
 
1448
- /// Depth-first search for the first `UIButton` inside a view tree.
1449
- private static func firstButton(in view: UIView) -> UIButton? {
1450
- if let button = view as? UIButton { return button }
1451
- for subview in view.subviews {
1452
- if let button = firstButton(in: subview) { return button }
1453
- }
1454
- return nil
1455
- }
1456
-
1457
- /// Compact description of a view tree, e.g. `AVRoutePickerView[UIButton[UIImageView]]`.
1458
- private static func describeHierarchy(_ view: UIView) -> String {
1459
- let name = String(describing: type(of: view))
1460
- let children = view.subviews.map(describeHierarchy).joined(separator: ", ")
1461
- return children.isEmpty ? name : "\(name)[\(children)]"
1462
- }
1463
-
1464
985
  // MARK: - Equalizer (unsupported on iOS)
1465
986
 
1466
987
  public func getEqualizerSettings() throws -> EqualizerSettings? {
@@ -1505,28 +1026,12 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
1505
1026
  extension HybridAudioBrowser: TrackPlayerCallbacks {
1506
1027
  public func playerDidChangePlayback(_ playback: Playback) {
1507
1028
  onPlaybackChanged(playback)
1508
- // Re-render so the formatter reflects the new state — notably the error line on a failure (the
1509
- // coordinator sets playbackError before this fires). Redundant updates are deduped on publish.
1510
- applyNowPlayingMetadata()
1511
1029
  }
1512
1030
 
1513
1031
  public func playerDidChangeActiveTrack(_ event: PlaybackActiveTrackChangedEvent) {
1514
- // Clear now playing override + flash + live metadata when track changes
1515
- // (matches Kotlin behavior; a flash must not carry over to a new track)
1032
+ // Clear now playing override when track changes (matches Kotlin behavior)
1516
1033
  nowPlayingOverride = nil
1517
- cancelNowPlayingFlash()
1518
- latestTimedMetadata = nil
1519
- // Hydrate the active track's `favorited` from the authoritative set so JS
1520
- // consumers (useActiveTrack) match getActiveTrack()'s hydrated value — the
1521
- // coordinator emits the raw queue track, whose flag isn't kept in sync.
1522
- let hydrated = PlaybackActiveTrackChangedEvent(
1523
- lastIndex: event.lastIndex,
1524
- lastTrack: event.lastTrack,
1525
- lastPosition: event.lastPosition,
1526
- index: event.index,
1527
- track: event.track.map(browserManager.hydrateFavorite),
1528
- )
1529
- activeTrackChangedEmitter.emit(hydrated)
1034
+ activeTrackChangedEmitter.emit(event)
1530
1035
  // Also notify now playing changed when track changes
1531
1036
  applyNowPlayingMetadata()
1532
1037
  }
@@ -1535,60 +1040,12 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
1535
1040
  onPlaybackProgressUpdated(event)
1536
1041
  }
1537
1042
 
1538
- public func playerDidFirePlaybackInterval() {
1539
- onPlaybackInterval()
1540
- }
1541
-
1542
1043
  public func playerDidChangePlayWhenReady(_ playWhenReady: Bool) {
1543
- // Intent to play again is the earliest cancel signal for a pending session release.
1544
- if playWhenReady { cancelSessionRelease() }
1545
1044
  onPlaybackPlayWhenReadyChanged(PlaybackPlayWhenReadyChangedEvent(playWhenReady: playWhenReady))
1546
1045
  }
1547
1046
 
1548
1047
  public func playerDidChangePlayingState(_ state: PlayingState) {
1549
1048
  onPlaybackPlayingState(state)
1550
- // Lazily activate the audio session the moment playback actually produces — or is buffering to
1551
- // produce — output. This (not setup) is where we grab the session, so launching the app never
1552
- // interrupts other apps' audio; the interruption only happens when the user truly starts
1553
- // playback, which is correct. Keyed off play *state*, not `playWhenReady`, because the intent
1554
- // can be set at setup (before any track) while output only begins here. Idempotent:
1555
- // `setActive(true)` is a no-op when the session is already active.
1556
- if state.playing || state.buffering {
1557
- try? AVAudioSession.sharedInstance().setActive(true)
1558
- // The now-playing session's activation request at link time predates this
1559
- // (the audio session was still inactive, and the system may have declined);
1560
- // re-request now so the surface can appear even if playback never produces
1561
- // audio (first-ever load failing on a dead stream).
1562
- player?.nowPlayingInfoController.reactivateSessionIfNeeded()
1563
- // Output resumed — cancel any pending release armed by an earlier stop.
1564
- cancelSessionRelease()
1565
- }
1566
- // Re-render so the formatter reacts to a stall starting or recovering (`stalled`) — a buffering
1567
- // flag change that may not transition the coordinator state.
1568
- applyNowPlayingMetadata()
1569
- }
1570
-
1571
- public func playerShouldReleaseSession() {
1572
- // Release the audio session so other apps' audio (Spotify, a podcast) can resume — debounced,
1573
- // because iOS dislikes deactivating while our audio is still trailing off, and a quick
1574
- // pause→resume shouldn't thrash the session. The coordinator only requests this on a genuine
1575
- // stop (not an interruption, not mid-retry); a re-activation cancels it (see
1576
- // `playerDidChangePlayWhenReady` and the activation above). Mirrors the now-playing flash timer.
1577
- sessionReleaseWork?.cancel()
1578
- let release = DispatchWorkItem { [weak self] in
1579
- guard let self, self.player?.playWhenReady == false else { return }
1580
- self.sessionReleaseWork = nil
1581
- // `.notifyOthersOnDeactivation` signals other apps they may resume; without it the session
1582
- // is released but nobody is told, so the user's audio would not come back.
1583
- try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
1584
- }
1585
- sessionReleaseWork = release
1586
- DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: release)
1587
- }
1588
-
1589
- private func cancelSessionRelease() {
1590
- sessionReleaseWork?.cancel()
1591
- sessionReleaseWork = nil
1592
1049
  }
1593
1050
 
1594
1051
  public func playerDidEndQueue(_ event: PlaybackQueueEndedEvent) {
@@ -1604,7 +1061,6 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
1604
1061
  }
1605
1062
 
1606
1063
  public func playerDidChangeShuffleEnabled(_ enabled: Bool) {
1607
- shuffleChangedEmitter.emit(enabled)
1608
1064
  onPlaybackShuffleModeChanged(enabled)
1609
1065
  }
1610
1066
 
@@ -1620,10 +1076,7 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
1620
1076
  public func playerDidReceiveTimedMetadata(_ groups: [AVTimedMetadataGroup]) {
1621
1077
  for group in groups {
1622
1078
  if let metadata = TimedMetadata.from(items: group.items) {
1623
- latestTimedMetadata = metadata
1624
1079
  onTimedMetadata(metadata)
1625
- // Re-render the now-playing so the formatter can reflect the live song.
1626
- applyNowPlayingMetadata()
1627
1080
  }
1628
1081
  }
1629
1082
  }
@@ -1740,6 +1193,10 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
1740
1193
  remoteSeek(position: position)
1741
1194
  }
1742
1195
 
1196
+ public func remoteSetRating(rating _: Any) {
1197
+ // TODO: Convert rating to RemoteSetRatingEvent
1198
+ }
1199
+
1743
1200
  public func remotePlayId(id: String, index: Int?) {
1744
1201
  let event = RemotePlayIdEvent(id: id, index: index.map { Double($0) })
1745
1202
  if let handler = handleRemotePlayId {
@@ -1758,92 +1215,57 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
1758
1215
  }
1759
1216
  }
1760
1217
 
1761
- /// Static so the Siri intent handler can call it before the shared instance
1762
- /// exists: it waits on the `playerAndConfiguredBrowser` gate, which resolves
1763
- /// once RN boots. Uses the resolved `browser`, never `self`.
1764
- static func handlePlayMediaIntent(criteria: MediaIntentCriteria, completion: @escaping @Sendable (Bool) -> Void) {
1765
- Task { @MainActor in
1766
- guard let (browser, player) = await playerAndConfiguredBrowser.wait(timeout: .seconds(8)) else {
1767
- Logger(subsystem: "com.audiobrowser", category: "AudioBrowser")
1768
- .error("handlePlayMediaIntent: browser/player not ready within budget")
1769
- completion(false)
1770
- return
1771
- }
1772
- // No-criteria intent ("play «app»") → resume. The gate must NOT block
1773
- // this: the gate blocks *finding* content, never *hearing* the active or
1774
- // persisted track. The gate is checked below, for the search branch only.
1775
- if criteria.isResume {
1776
- if player.currentTrack != nil {
1777
- browser.logger.info("resume: warm — playing current track")
1778
- player.play()
1779
- browser.showNowPlayingRequestedEmitter.emit(())
1780
- completion(true)
1781
- } else if let state = browser.playbackStateStore.load() {
1782
- browser.logger.info("resume: cold — restoring persisted track")
1783
- let track = state.track.toNitro()
1784
- let startMs = (track.live == true) ? nil : state.positionMs
1785
- // Match Android resume: re-expand the full queue from the track's contextual
1786
- // URL (parent container → siblings + selected index). Fall back to the single
1787
- // track when the url isn't contextual or expansion fails.
1788
- if let url = state.track.url,
1789
- let expanded = try? await browser.browserManager.expandQueueFromContextualUrl(url)
1790
- {
1791
- player.setQueue(expanded.tracks, initialIndex: expanded.selectedIndex, startPositionMs: startMs, playWhenReady: true)
1792
- } else {
1793
- player.setQueue([track], initialIndex: 0, startPositionMs: startMs, playWhenReady: true)
1794
- }
1795
- browser.showNowPlayingRequestedEmitter.emit(())
1796
- completion(true)
1797
- } else {
1798
- browser.logger.error("resume: nothing playing and nothing persisted → no-op")
1799
- completion(false) // nothing playing and nothing persisted
1800
- }
1801
- return
1802
- }
1803
-
1804
- // Assemble the Nitro SearchParams here (MainActor) from the criteria's
1805
- // Sendable fields, so the structured mode/genre/… reach the request like
1806
- // Android. Today the API still text-searches `q`; the rest is forward-compat.
1807
- let params = SearchParams(
1808
- mode: criteria.searchMode.flatMap { SearchMode(fromString: $0) },
1809
- query: criteria.query,
1810
- genre: criteria.genre,
1811
- artist: criteria.artist,
1812
- album: criteria.album,
1813
- title: criteria.title,
1814
- playlist: criteria.playlist,
1815
- // .currentlyPlaying can't reach here — isResume routed it to resume.
1816
- reference: criteria.reference == .my ? .my : .unknown,
1817
- )
1818
-
1819
- // Search is *finding* new content — refused when the gate decides this
1820
- // request is gated (resume above is unaffected: the gate never blocks
1821
- // hearing, and the gate check stays after the resume check).
1822
- let outcome = await browser.gateDecision(
1823
- for: NativeGateRequest(reason: .search, path: nil, search: params),
1824
- )
1825
- guard !outcome.gated else {
1826
- browser.logger.info("handlePlayMediaIntent: search refused — gated")
1827
- browser.onGate(GateEvent(reason: .search))
1828
- completion(false)
1829
- return
1830
- }
1831
-
1218
+ /// Handles an INPlayMediaIntent by searching for the given term and starting playback.
1219
+ ///
1220
+ /// Host apps should call this from their `INPlayMediaIntentHandling` implementation.
1221
+ /// Uses the browser's search route to find tracks, then queues and plays them.
1222
+ ///
1223
+ /// - Parameters:
1224
+ /// - searchTerm: The search query from Siri.
1225
+ /// - completion: Called with `true` if tracks were found and queued, `false` otherwise.
1226
+ public func handlePlayMediaIntent(searchTerm: String, completion: @escaping @Sendable (Bool) -> Void) {
1227
+ Task {
1832
1228
  do {
1833
- guard let tracks = try await browser.browserManager.searchPlayable(params) else {
1229
+ let resolved = try await self.browserManager.search(searchTerm)
1230
+ guard let tracks = resolved.children, !tracks.isEmpty else {
1834
1231
  completion(false)
1835
1232
  return
1836
1233
  }
1837
- player.setQueue(tracks, initialIndex: 0, playWhenReady: true)
1838
- browser.showNowPlayingRequestedEmitter.emit(())
1234
+ await MainActor.run {
1235
+ self.getPlayer()?.setQueue(tracks, initialIndex: 0, playWhenReady: true)
1236
+ }
1839
1237
  completion(true)
1840
1238
  } catch {
1841
- browser.logger.error("handlePlayMediaIntent failed: \(error.localizedDescription)")
1239
+ self.logger.error("handlePlayMediaIntent failed: \(error.localizedDescription)")
1842
1240
  completion(false)
1843
1241
  }
1844
1242
  }
1845
1243
  }
1846
1244
 
1245
+ public func remoteLike() {
1246
+ if let handler = handleRemoteLike {
1247
+ handler()
1248
+ } else {
1249
+ onRemoteLike()
1250
+ }
1251
+ }
1252
+
1253
+ public func remoteDislike() {
1254
+ if let handler = handleRemoteDislike {
1255
+ handler()
1256
+ } else {
1257
+ onRemoteDislike()
1258
+ }
1259
+ }
1260
+
1261
+ public func remoteBookmark() {
1262
+ if let handler = handleRemoteBookmark {
1263
+ handler()
1264
+ } else {
1265
+ onRemoteBookmark()
1266
+ }
1267
+ }
1268
+
1847
1269
  public func remoteChangeRepeatMode(mode: RepeatMode) {
1848
1270
  // Apply the repeat mode change from CarPlay/lock screen
1849
1271
  try? setRepeatMode(mode: mode)