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