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
@@ -29,48 +29,16 @@ public final class RNABCarPlayController: NSObject {
29
29
  private var isStarted = false
30
30
  private var listenerRemovals: [() -> Void] = []
31
31
 
32
- /// Paths whose loading template has been pushed but not yet appeared. Guards a
33
- /// rapid double-tap from pushing the same destination twice (the first push is
34
- /// async, so `topTemplate` may not reflect it yet). Cleared when it appears.
35
- private var navigatingPaths: Set<String> = []
36
-
37
- /// Templates with a content load in flight. Guards a re-entrant load when a
38
- /// still-loading template re-appears (back-and-forth navigation) from spawning
39
- /// a second resolve + watchdog.
40
- private var loadingTemplates: Set<ObjectIdentifier> = []
41
-
42
- /// Tabs received while the user had templates pushed. Rebuilding the tab bar
43
- /// replaces the root template, which tears down the pushed navigation stack —
44
- /// so the rebuild is deferred until the user is back at the tab bar.
45
- private var pendingTabs: [Track]?
46
-
47
- /// Whether a gate is active. While set, tabs keep their tab-bar entries but
48
- /// each path is resolved per-request via `gateDecision(for:)`: a gated path
49
- /// renders the gate page instead of content; an allowed path shows real
50
- /// content. Mirrors `audioBrowser.isGateActive`; seeded in start() so a gate
51
- /// set before the scene connects renders at connect.
52
- private var isGated = false
53
-
54
- /// Drops a stale tab-bar build when a newer build supersedes it
55
- /// (latest-build-wins). Shared across BOTH the gated and non-gated `showTabBar`
56
- /// paths: each bumps and captures this at entry and bails before any
57
- /// `setRootTemplate` / `updateTemplates` once it's been bumped past their
58
- /// captured value. A build suspends the main actor at its `await` points
59
- /// (per-tab `gateDecision` when gated, first-tab `loadContent` when not), so a
60
- /// gate change can interleave a build of the opposite kind — most importantly a
61
- /// `clearGate` kicking the non-gated build while a gated build is mid-await.
62
- /// Covering both paths with one token means that clear-vs-gated interleave is
63
- /// caught too (not just gated-vs-gated), so a stale gated build can't paint a
64
- /// gate page over just-cleared content. Mirrors `albumArtistGeneration` in
65
- /// `CarPlayNowPlayingManager`.
66
- private var gateBuildGeneration: UInt = 0
67
-
68
- /// How long a browse resolve may run before the destination's loading spinner
69
- /// is replaced with an error state. The selection completion is fired
70
- /// immediately (so CarPlay never blocks the list — per Apple's async handler
71
- /// guidance we push the destination and fill it in), so this only bounds how
72
- /// long the *destination screen* spins. Backing out and re-tapping retries.
73
- private let resolveTimeout: Duration = .seconds(15)
32
+ /// Current navigation stack paths (for back navigation context)
33
+ private var navigationStack: [String] = []
34
+
35
+ /// Key (src ?? url) of the item currently being selected.
36
+ /// Prevents duplicate taps from triggering parallel async operations.
37
+ private var pendingSelectionKey: String?
38
+
39
+ /// URLs currently being resolved for navigation.
40
+ /// Prevents duplicate taps from pushing the same template twice.
41
+ private var pendingNavigationUrls: Set<String> = []
74
42
 
75
43
  /// Helper object for CPInterfaceControllerDelegate conformance
76
44
  private var interfaceDelegate: InterfaceControllerDelegate?
@@ -89,21 +57,9 @@ public final class RNABCarPlayController: NSObject {
89
57
  audioBrowser?.browserManager.config ?? BrowserConfig()
90
58
  }
91
59
 
92
- /// Whether a list item identifies the currently active (loaded) track.
93
- ///
94
- /// Identity is the stable `Track.id` when both sides carry one — the active
95
- /// track may have been loaded outside the browse tree (the consumer's own
96
- /// `load`/`setQueue`) with a `src` that differs textually from the browse
97
- /// row's (absolute vs relative, extra query params) while being the same
98
- /// item. Exact `src` equality remains the fallback for consumers that don't
99
- /// assign ids.
100
- private func isActiveTrack(id: String?, src: String?) -> Bool {
101
- guard let currentTrack = audioBrowser?.getPlayer()?.currentTrack else { return false }
102
- if let id, !id.isEmpty, let currentId = currentTrack.id, !currentId.isEmpty {
103
- return id == currentId
104
- }
105
- guard let src else { return false }
106
- return currentTrack.src == src
60
+ /// Checks if the given src matches the currently active (loaded) track
61
+ private func isActiveTrack(src: String) -> Bool {
62
+ audioBrowser?.getPlayer()?.currentTrack?.src == src
107
63
  }
108
64
 
109
65
  // MARK: - Initialization
@@ -118,22 +74,10 @@ public final class RNABCarPlayController: NSObject {
118
74
  self?.listItemFactory?.createListItem(for: track, handler: handler)
119
75
  ?? CPListItem(text: track.title, detailText: nil)
120
76
  }
121
- nowPlayingManager.navigateToUrl = { [weak self] url, title in
122
- Task { @MainActor in await self?.navigateToUrl(url, title: title) }
123
- }
124
77
  }
125
78
 
126
79
  // MARK: - Lifecycle
127
80
 
128
- /// Reports CarPlay scene connection to the library (drives the JS-side
129
- /// `isCarPlayConnected` / `onCarPlayConnectedChanged`). Called by
130
- /// RNABCarPlaySceneDelegate on scene connect/disconnect — deliberately not
131
- /// from start()/stop(), which also cycle on a JS runtime reload.
132
- @objc
133
- public static func setConnected(_ connected: Bool) {
134
- HybridAudioBrowser.setCarPlayConnected(connected)
135
- }
136
-
137
81
  @objc
138
82
  public func start() {
139
83
  guard !isStarted else { return }
@@ -146,18 +90,6 @@ public final class RNABCarPlayController: NSObject {
146
90
  interfaceDelegate = delegate
147
91
  interfaceController.delegate = delegate
148
92
 
149
- // Restart when a new HybridAudioBrowser replaces the shared instance
150
- // (JS runtime reload) — our subscriptions point at the old instance's
151
- // emitters and would otherwise go silent.
152
- let instanceToken = HybridAudioBrowser.instanceChangedEmitter.addListener { [weak self] _ in
153
- Task { @MainActor in
154
- self?.restart()
155
- }
156
- }
157
- listenerRemovals.append {
158
- HybridAudioBrowser.instanceChangedEmitter.removeListener(instanceToken)
159
- }
160
-
161
93
  // Show loading template while waiting
162
94
  showLoadingTemplate()
163
95
 
@@ -167,21 +99,20 @@ public final class RNABCarPlayController: NSObject {
167
99
  guard self.isStarted else { return }
168
100
  self.logger.debug("AudioBrowser and player ready, setting up CarPlay")
169
101
  self.audioBrowser = browser
170
- self.isGated = browser.isGateActive
171
102
  self.trackSelector = TrackSelector(browserManager: browser.browserManager)
172
103
 
173
104
  // Create image loader with CarPlay display traits
174
105
  self.imageLoader = CarPlayImageLoader(
175
106
  carTraitCollection: self.interfaceController.carTraitCollection,
176
- browserManager: browser.browserManager,
107
+ browserManager: browser.browserManager
177
108
  )
178
109
 
179
110
  // Create list item factory
180
111
  let factory = CarPlayListItemFactory(
181
- isActiveTrack: { [weak self] id, src in self?.isActiveTrack(id: id, src: src) ?? false },
112
+ isActiveTrack: { [weak self] src in self?.isActiveTrack(src: src) ?? false },
182
113
  onItemSelected: { [weak self] track, completion in
183
114
  self?.handleItemSelection(track: track, completion: completion)
184
- },
115
+ }
185
116
  )
186
117
  factory.imageLoader = self.imageLoader
187
118
  self.listItemFactory = factory
@@ -203,33 +134,18 @@ public final class RNABCarPlayController: NSObject {
203
134
  logger.info("Stopping CarPlay controller")
204
135
 
205
136
  // Remove all emitter listeners
206
- for removal in listenerRemovals {
207
- removal()
208
- }
137
+ for removal in listenerRemovals { removal() }
209
138
  listenerRemovals.removeAll()
210
139
 
211
140
  // Clear config callback
212
141
  audioBrowser?.browserManager.onConfigChanged = nil
213
- audioBrowser?.browserManager.onFavoritesChanged = nil
214
142
 
215
143
  nowPlayingManager.teardown()
216
144
  listItemFactory = nil
217
145
 
218
- navigatingPaths.removeAll()
219
- loadingTemplates.removeAll()
220
- pendingTabs = nil
221
- }
222
-
223
- /// Full stop/start cycle. Used when the JS runtime reloads: the new
224
- /// HybridAudioBrowser instance replaces the emitters this controller is
225
- /// subscribed to and resets the readiness gate, so everything must be
226
- /// re-subscribed against the live instance (start() waits for it).
227
- @MainActor
228
- private func restart() {
229
- guard isStarted else { return }
230
- logger.info("AudioBrowser instance changed — restarting CarPlay controller")
231
- stop()
232
- start()
146
+ navigationStack.removeAll()
147
+ pendingSelectionKey = nil
148
+ pendingNavigationUrls.removeAll()
233
149
  }
234
150
 
235
151
  // MARK: - Content Subscriptions
@@ -260,35 +176,10 @@ public final class RNABCarPlayController: NSObject {
260
176
  audioBrowser?.contentChangedEmitter.removeListener(contentToken)
261
177
  }
262
178
 
263
- // Subscribe to config changes (for Now Playing buttons and per-track
264
- // button state — e.g. resolveAlbumUrl appearing/disappearing)
179
+ // Subscribe to config changes (for Now Playing buttons)
265
180
  audioBrowser.browserManager.onConfigChanged = { [weak self] _ in
266
181
  Task { @MainActor in
267
182
  self?.nowPlayingManager.setupNowPlayingButtons()
268
- self?.nowPlayingManager.updateNowPlayingButtonStates()
269
- }
270
- }
271
-
272
- // Now-playing buttons + Up Next are runtime-updatable via updateOptions();
273
- // refresh them when player options change (configureBrowser is no longer
274
- // the only path that can change them).
275
- let optionsToken = audioBrowser.playerOptionsChangedEmitter.addListener { [weak self] in
276
- Task { @MainActor in
277
- self?.nowPlayingManager.setupNowPlayingButtons()
278
- self?.nowPlayingManager.updateNowPlayingButtonStates()
279
- }
280
- }
281
- listenerRemovals.append { [weak audioBrowser] in
282
- audioBrowser?.playerOptionsChangedEmitter.removeListener(optionsToken)
283
- }
284
-
285
- // Refresh the Now Playing heart when favorites change externally (app /
286
- // webview). The favorite/active-track emitters only fire for the player's
287
- // own toggles, so without this an in-app favorite leaves the CarPlay heart
288
- // stale.
289
- audioBrowser.browserManager.onFavoritesChanged = { [weak self] in
290
- Task { @MainActor in
291
- self?.nowPlayingManager.updateFavoriteButtonState()
292
183
  }
293
184
  }
294
185
 
@@ -302,54 +193,14 @@ public final class RNABCarPlayController: NSObject {
302
193
  audioBrowser?.favoriteChangedEmitter.removeListener(favoriteToken)
303
194
  }
304
195
 
305
- // Repeat/shuffle mode changes re-stamp the now-playing buttons' serialized
306
- // `isSelected` (a rebuild, skipped when unchanged) so CarPlay re-renders
307
- // draw the correct selected background on the first frame — see
308
- // CarPlayNowPlayingManager.BuiltButtonRow.
309
- let repeatToken = audioBrowser.repeatModeChangedEmitter.addListener { [weak self] _ in
310
- Task { @MainActor in
311
- self?.nowPlayingManager.setupNowPlayingButtons()
312
- }
313
- }
314
- listenerRemovals.append { [weak audioBrowser] in
315
- audioBrowser?.repeatModeChangedEmitter.removeListener(repeatToken)
316
- }
317
- let shuffleToken = audioBrowser.shuffleChangedEmitter.addListener { [weak self] _ in
318
- Task { @MainActor in
319
- self?.nowPlayingManager.setupNowPlayingButtons()
320
- }
321
- }
322
- listenerRemovals.append { [weak audioBrowser] in
323
- audioBrowser?.shuffleChangedEmitter.removeListener(shuffleToken)
324
- }
325
-
326
- // Subscribe to external content changes (notifyContentChanged /
327
- // invalidateAllContent).
196
+ // Subscribe to external content changes (from notifyContentChanged)
328
197
  let externalContentToken = audioBrowser.externalContentChangedEmitter.addListener { [weak self] path in
329
- // Hop to the main actor like the sibling listeners — the emitter runs
330
- // listeners synchronously on the emitting (JS) thread.
331
- Task { @MainActor in
332
- if path == HybridAudioBrowser.invalidateAllSentinel {
333
- self?.invalidateAllContent()
334
- } else {
335
- self?.notifyContentChanged(path: path)
336
- }
337
- }
198
+ self?.notifyContentChanged(path: path)
338
199
  }
339
200
  listenerRemovals.append { [weak audioBrowser] in
340
201
  audioBrowser?.externalContentChangedEmitter.removeListener(externalContentToken)
341
202
  }
342
203
 
343
- // Subscribe to gate changes (set / in-place update / clear)
344
- let gateToken = audioBrowser.gateChangedEmitter.addListener { [weak self] active in
345
- Task { @MainActor in
346
- self?.handleGateChanged(active)
347
- }
348
- }
349
- listenerRemovals.append { [weak audioBrowser] in
350
- audioBrowser?.gateChangedEmitter.removeListener(gateToken)
351
- }
352
-
353
204
  // Subscribe to active track changes (for playing indicator in lists)
354
205
  let activeTrackToken = audioBrowser.activeTrackChangedEmitter.addListener { [weak self] event in
355
206
  Task { @MainActor in
@@ -370,17 +221,6 @@ public final class RNABCarPlayController: NSObject {
370
221
  audioBrowser?.queueChangedEmitter.removeListener(queueToken)
371
222
  }
372
223
 
373
- // A voice media intent started playback — surface Now Playing so the user
374
- // lands on the playing station (Up Next holds the rest of the results).
375
- let showNowPlayingToken = audioBrowser.showNowPlayingRequestedEmitter.addListener { [weak self] _ in
376
- Task { @MainActor in
377
- self?.nowPlayingManager.showNowPlaying()
378
- }
379
- }
380
- listenerRemovals.append { [weak audioBrowser] in
381
- audioBrowser?.showNowPlayingRequestedEmitter.removeListener(showNowPlayingToken)
382
- }
383
-
384
224
  // Subscribe to navigation errors (from browser layer)
385
225
  let navErrorToken = audioBrowser.navigationErrorEmitter.addListener { [weak self] event in
386
226
  Task { @MainActor in
@@ -397,9 +237,8 @@ public final class RNABCarPlayController: NSObject {
397
237
  private func handleNavigationError(_ event: NavigationErrorEvent) {
398
238
  guard let error = event.error else { return }
399
239
  logger.warning("Navigation error: \(error.code.stringValue) - \(error.message)")
400
- // Derive the current path from the live top template (authoritative) rather
401
- // than a hand-maintained stack that goes stale on back navigation.
402
- let path = interfaceController.topTemplate.flatMap { getPath(from: $0) } ?? "/"
240
+ // Use current path from navigation stack, or "/" as fallback
241
+ let path = navigationStack.last ?? "/"
403
242
  showNavigationError(error, path: path)
404
243
  }
405
244
 
@@ -409,9 +248,7 @@ public final class RNABCarPlayController: NSObject {
409
248
  private func buildInitialInterface() async {
410
249
  guard let audioBrowser else {
411
250
  logger.error("AudioBrowser not available")
412
- await showRootNavigationError(
413
- NavigationError(code: .unknownError, message: "", statusCode: nil, statusCodeSuccess: nil),
414
- )
251
+ showErrorTemplate(message: "Audio browser not initialized")
415
252
  return
416
253
  }
417
254
 
@@ -423,65 +260,33 @@ public final class RNABCarPlayController: NSObject {
423
260
  } else {
424
261
  // No tabs yet - query them
425
262
  logger.info("No tabs available, querying...")
426
- // Config exists by now (start() waited for it), so re-show the loading
427
- // root to pick up the app's localized loading title — the first loading
428
- // template was created before config was available.
429
- if config.carPlayLoadingTitle != nil {
430
- showLoadingTemplate()
431
- }
432
263
  do {
433
264
  let queriedTabs = try await audioBrowser.browserManager.queryTabs()
434
265
  if !queriedTabs.isEmpty {
435
266
  await showTabBar(tabs: queriedTabs)
436
267
  } else {
437
- // Empty is a navigation error (.emptyContent) so it formats through
438
- // the app's formatNavigationError like every other failure (ADR 0001).
439
- await showRootNavigationError(
440
- NavigationError(code: .emptyContent, message: "", statusCode: nil, statusCodeSuccess: nil),
441
- )
268
+ showErrorTemplate(message: "No content available")
442
269
  }
443
270
  } catch {
444
271
  logger.error("Failed to query tabs: \(error.localizedDescription)")
445
- await showRootNavigationError(NavigationError.from(error))
272
+ showErrorTemplate(message: "Failed to load content")
446
273
  }
447
274
  }
448
275
  }
449
276
 
450
- /// Shows a loading template as root while waiting for initialization. Before
451
- /// the browser is configured this can only show the spinner (iOS 18.4+) — the
452
- /// app's `carPlayLoadingTitle` isn't known yet; `buildInitialInterface`
453
- /// re-shows it once config is available.
277
+ /// Shows a loading template while waiting for initialization
454
278
  private func showLoadingTemplate() {
455
- let template = makeLoadingTemplate(title: nil, path: nil)
456
- interfaceController.safeSetRoot(template, animated: false)
279
+ let template = CPListTemplate(
280
+ title: nil,
281
+ sections: [],
282
+ )
283
+ interfaceController.setRootTemplate(template, animated: false, completion: nil)
457
284
  }
458
285
 
459
286
  // MARK: - Tab Bar
460
287
 
461
288
  @MainActor
462
289
  private func showTabBar(tabs: [Track]) async {
463
- // Serialize concurrent tab-bar builds across BOTH the gated and non-gated
464
- // paths with one shared generation token: bump at entry, capture, and bail
465
- // before any template mutation once a newer build supersedes us. Both paths
466
- // suspend the main actor at `await` points (per-tab `gateDecision` when
467
- // gated, first-tab `loadContent` when not), so a gate change can interleave
468
- // a build of the opposite kind — a `clearGate` kicking the non-gated build
469
- // while a gated build is mid-await, or vice versa. A single token covers the
470
- // clear-vs-gated interleave too, not just gated-vs-gated: the superseding
471
- // build bumps it, and the in-flight build bails before painting (preventing
472
- // a stale gate page over just-cleared content, or a clobbered root template).
473
- gateBuildGeneration &+= 1
474
- let generation = gateBuildGeneration
475
-
476
- // While gated, each tab path is resolved per-request: a gated tab renders
477
- // the gate page with that request's chrome; an allowed tab shows real
478
- // content. This generalizes the old "all tabs show the gate" — a path the
479
- // resolver allows now shows real content.
480
- if isGated {
481
- await showGatedTabBar(tabs: tabs, generation: generation)
482
- return
483
- }
484
-
485
290
  logger.info("Building tab bar with \(tabs.count) tabs")
486
291
 
487
292
  let maxTabs = CPTabBarTemplate.maximumTabCount
@@ -491,14 +296,10 @@ public final class RNABCarPlayController: NSObject {
491
296
  createTabTemplate(for: tab)
492
297
  }
493
298
 
494
- // A newer build (e.g. a gate raised mid-clear) superseded us — don't clobber
495
- // its root template.
496
- guard gateBuildGeneration == generation else { return }
497
-
498
299
  // Set the tab bar immediately so UI appears fast
499
300
  logger.info("Setting tab bar root template with \(tabTemplates.count) templates")
500
301
  let tabBar = CPTabBarTemplate(templates: tabTemplates)
501
- interfaceController.safeSetRoot(tabBar, animated: true)
302
+ interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
502
303
 
503
304
  // Load content for the first tab only - others load lazily when selected
504
305
  if let firstTemplate = tabTemplates.first, let firstTab = tabs.first, let url = firstTab.url {
@@ -506,93 +307,27 @@ public final class RNABCarPlayController: NSObject {
506
307
  }
507
308
  }
508
309
 
509
- /// Builds the tab bar while a gate is active. Each tab's path is run through
510
- /// the single gate choke point (`gateDecision(for:)`): a gated tab becomes a
511
- /// gate page carrying that decision's chrome (and fires `onGate`); an allowed
512
- /// tab becomes a normal content shell that lazy-loads. With a static gate (no
513
- /// resolver) every tab is gated with the default chrome — today's behaviour.
514
- ///
515
- /// `generation` is the shared `gateBuildGeneration` captured by the calling
516
- /// `showTabBar`; we bail before any template mutation once a newer build (of
517
- /// either kind) bumps it past `generation`.
518
- @MainActor
519
- private func showGatedTabBar(tabs: [Track], generation: UInt) async {
520
- guard let audioBrowser else { return }
521
-
522
- guard !tabs.isEmpty else {
523
- // Tabs unknown (config not loaded yet, or none) — resolve the root path
524
- // and show a single gate page (or, if allowed, fall back to a normal
525
- // build once tabs arrive).
526
- let outcome = await audioBrowser.gateDecision(
527
- for: NativeGateRequest(reason: .browse, path: nil, search: nil),
528
- )
529
- guard gateBuildGeneration == generation else { return } // superseded by a newer build
530
- if outcome.gated {
531
- audioBrowser.onGate(GateEvent(reason: .browse))
532
- interfaceController.safeSetRoot(
533
- makeGateTemplate(gate: outcome.chrome, tab: nil), animated: true,
534
- )
535
- }
536
- return
537
- }
538
-
539
- let limitedTabs = Array(tabs.prefix(CPTabBarTemplate.maximumTabCount))
540
- var templates: [CPListTemplate] = []
541
- for tab in limitedTabs {
542
- let outcome = await audioBrowser.gateDecision(
543
- for: NativeGateRequest(reason: .browse, path: tab.url, search: nil),
544
- )
545
- guard gateBuildGeneration == generation else { return } // superseded by a newer build
546
- if outcome.gated {
547
- audioBrowser.onGate(GateEvent(reason: .browse))
548
- templates.append(makeGateTemplate(gate: outcome.chrome, tab: tab))
549
- } else {
550
- // This path is allowed even though a gate is active — show real content.
551
- templates.append(createTabTemplate(for: tab))
552
- }
553
- }
554
-
555
- if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate,
556
- tabBar.templates.count == templates.count
557
- {
558
- // Equal-count in-place swap keeps the selected tab index.
559
- tabBar.updateTemplates(templates)
560
- } else {
561
- interfaceController.safeSetRoot(
562
- CPTabBarTemplate(templates: templates), animated: true,
563
- )
564
- }
565
-
566
- // Eagerly fill any allowed (content) tabs: templateDidAppear isn't
567
- // guaranteed to re-fire for templates swapped in via updateTemplates.
568
- for (tab, template) in zip(limitedTabs, templates) {
569
- guard getPath(from: template) != nil, let url = tab.url else { continue }
570
- await loadContent(for: url, into: template)
571
- }
572
- }
573
-
574
- /// Creates a tab template shell without loading content (synchronous).
575
- /// The shell carries the loading state (spinner / `carPlayLoadingTitle`)
576
- /// until its content lazy-loads on first appearance.
310
+ /// Creates a tab template shell without loading content (synchronous)
577
311
  private func createTabTemplate(for track: Track) -> CPListTemplate {
578
- // The path stored on the template drives lazy loading and refresh.
579
- let template = makeLoadingTemplate(title: track.title, path: track.url)
580
- applyTabBarEntry(to: template, for: track)
581
- return template
582
- }
312
+ let template = CPListTemplate(
313
+ title: track.title,
314
+ sections: [],
315
+ )
583
316
 
584
- /// Stamps a template's tab-bar entry: title and image (default, SF Symbol,
585
- /// or loaded artwork). Shared by content tabs and gate-page tabs so a gate
586
- /// keeps the familiar tab bar.
587
- private func applyTabBarEntry(to template: CPTemplate, for track: Track) {
588
317
  // Set tab title explicitly (required for tab bar display)
589
318
  template.tabTitle = track.title
590
319
 
591
- // Set tab image only when the tab supplies one (SF Symbol / artwork /
592
- // artworkSource). A tab with no icon stays title-only — CPTemplate renders
593
- // fine with just `tabTitle`. Tab bar icons are 24pt x 24pt per the CarPlay
594
- // Developer Guide: https://developer.apple.com/download/files/CarPlay-Developer-Guide.pdf
595
- if let artwork = track.artwork?.url, SFSymbolRenderer.isSFSymbol(artwork) {
320
+ // Store path for lazy loading and refresh
321
+ if let url = track.url {
322
+ template.userInfo = ["path": url] as [String: Any]
323
+ }
324
+
325
+ // Set tab image - CarPlay requires an image for proper tab display
326
+ // Tab bar icons are 24pt x 24pt per CarPlay Developer Guide
327
+ // https://developer.apple.com/download/files/CarPlay-Developer-Guide.pdf
328
+ template.tabImage = imageLoader?.defaultTabImage()
329
+
330
+ if let artwork = track.artwork, SFSymbolRenderer.isSFSymbol(artwork) {
596
331
  let (symbolName, _, _) = SFSymbolRenderer.parseArtwork(artwork)
597
332
  if let image = imageLoader?.sfSymbolImage(symbolName) {
598
333
  template.tabImage = image
@@ -607,15 +342,28 @@ public final class RNABCarPlayController: NSObject {
607
342
  }
608
343
  }
609
344
  }
610
- } else {
611
- // No icon supplied: clear any prior image so a restamp (handleTabsChanged)
612
- // of an existing template doesn't leave a stale icon on a now title-only tab.
613
- template.tabImage = nil
614
345
  }
346
+
347
+ return template
615
348
  }
616
349
 
617
350
  // MARK: - List Templates
618
351
 
352
+ @MainActor
353
+ private func createListTemplate(
354
+ for resolvedTrack: ResolvedTrack,
355
+ path: String,
356
+ ) -> CPListTemplate {
357
+ let template = CPListTemplate(
358
+ title: resolvedTrack.title,
359
+ sections: [],
360
+ )
361
+ updateTemplate(template, with: resolvedTrack)
362
+ template.userInfo = ["path": path] as [String: Any]
363
+
364
+ return template
365
+ }
366
+
619
367
  /// Finds the path associated with a template, if any
620
368
  private func getPath(from template: CPTemplate) -> String? {
621
369
  (template.userInfo as? [String: Any])?["path"] as? String
@@ -631,6 +379,7 @@ public final class RNABCarPlayController: NSObject {
631
379
  /// Configures the assistant cell ("Ask Siri to Play Audio") on a template
632
380
  /// based on the `carPlaySiriListButton` property of the resolved content.
633
381
  private func configureAssistantCell(on template: CPListTemplate, from resolvedTrack: ResolvedTrack) {
382
+ guard #available(iOS 15.4, *) else { return }
634
383
  guard let position = resolvedTrack.carPlaySiriListButton else {
635
384
  template.assistantCellConfiguration = nil
636
385
  return
@@ -645,96 +394,15 @@ public final class RNABCarPlayController: NSObject {
645
394
 
646
395
  // MARK: - Content Loading
647
396
 
648
- /// Resolves `path` and fills an empty (loading) template. Driven by
649
- /// `templateDidAppear` → `loadContentIfNeeded`, so it runs once the template is
650
- /// on screen — the timing where CarPlay actually applies updates (updates made
651
- /// earlier, e.g. straight after push, are dropped). Shows a centered empty
652
- /// state on empty/error/timeout.
653
397
  @MainActor
654
398
  private func loadContent(for path: String, into template: CPListTemplate) async {
655
399
  guard let audioBrowser else { return }
656
400
 
657
- // Single-flight: don't start a second load if one is already in flight for
658
- // this template (e.g. the user backed out and into a still-loading screen).
659
- let templateId = ObjectIdentifier(template)
660
- guard !loadingTemplates.contains(templateId) else { return }
661
- loadingTemplates.insert(templateId)
662
- defer { loadingTemplates.remove(templateId) }
663
-
664
- // Watchdog: if still loading (no rows) after the timeout, show an error.
665
- let timeout = resolveTimeout
666
- let watchdog = Task { @MainActor [weak self] in
667
- do { try await Task.sleep(for: timeout) } catch { return } // cancelled → abort
668
- guard let self, template.sections.isEmpty else { return }
669
- self.logger.error("loadContent: resolve timed out for \(path)")
670
- // A timeout is a navigation error (code .timeout) too, so it formats through
671
- // the app's formatNavigationError like every other browse failure.
672
- let timedOut = NavigationError(
673
- code: .timeout, message: "", statusCode: nil, statusCodeSuccess: nil,
674
- )
675
- await self.showNavigationErrorView(timedOut, path: path, on: template)
676
- }
677
-
678
401
  do {
679
402
  let resolved = try await audioBrowser.browserManager.resolve(path, useCache: true)
680
- watchdog.cancel()
681
- // A page carrying an assistant cell (carPlaySiriListButton) is meaningful
682
- // even with no rows — the "Ask Siri to Play" cell IS its content — so it
683
- // renders rather than falling through to the empty-content state.
684
- let hasAssistantCell = resolved.carPlaySiriListButton != nil
685
- if resolved.children?.isEmpty ?? true, !hasAssistantCell {
686
- // Empty is modeled as a navigation error (code .emptyContent) so it goes
687
- // through the same path-aware formatter as failures — letting an app give
688
- // an empty Favorites tab different copy than an empty search. ADR 0001.
689
- let empty = NavigationError(
690
- code: .emptyContent, message: "", statusCode: nil, statusCodeSuccess: nil,
691
- )
692
- await showNavigationErrorView(empty, path: path, on: template)
693
- } else {
694
- updateTemplate(template, with: resolved)
695
- }
403
+ updateTemplate(template, with: resolved)
696
404
  } catch {
697
- watchdog.cancel()
698
405
  logger.error("Failed to load content for \(path): \(error.localizedDescription)")
699
- await showNavigationErrorView(NavigationError.from(error), path: path, on: template)
700
- }
701
- }
702
-
703
- /// Renders a navigation error as the template's centered empty/error view,
704
- /// formatted via the app's `formatNavigationError` (or the built-in default):
705
- /// `title → view title`, `message → subtitle`. Used for both real failures and
706
- /// the empty-content case (ADR 0001).
707
- @MainActor
708
- private func showNavigationErrorView(
709
- _ navError: NavigationError,
710
- path: String,
711
- on template: CPListTemplate,
712
- ) async {
713
- let formatted = await formattedNavigationError(navError, path: path)
714
- showMessage(
715
- on: template,
716
- title: formatted.title,
717
- subtitle: formatted.message.flatMap { $0.isEmpty ? nil : $0 },
718
- )
719
- }
720
-
721
- /// Resolves a navigation error to its display form via the app's
722
- /// `formatNavigationError` callback, falling back to the default if unset/failed.
723
- @MainActor
724
- private func formattedNavigationError(
725
- _ navError: NavigationError,
726
- path: String,
727
- ) async -> FormattedNavigationError {
728
- let fallback = navError.defaultFormatted()
729
- guard let formatter = config.formatNavigationError else { return fallback }
730
- let params = FormatNavigationErrorParams(
731
- error: navError, defaultFormatted: fallback, path: path,
732
- )
733
- do {
734
- let custom = try await formatter(params).await()
735
- return custom ?? fallback
736
- } catch {
737
- return fallback
738
406
  }
739
407
  }
740
408
 
@@ -748,8 +416,17 @@ public final class RNABCarPlayController: NSObject {
748
416
  return
749
417
  }
750
418
 
751
- // If this track is already loaded, resume playback and show Now Playing.
752
- if track.src != nil, isActiveTrack(id: track.id, src: track.src) {
419
+ // Deduplicate: ignore if the same item is already being loaded
420
+ let key = track.src ?? track.url
421
+ if let key, key == pendingSelectionKey {
422
+ completion()
423
+ return
424
+ }
425
+ pendingSelectionKey = key
426
+
427
+ // If this track is already loaded, resume playback and show Now Playing
428
+ if let src = track.src, isActiveTrack(src: src) {
429
+ pendingSelectionKey = nil
753
430
  try? audioBrowser.play()
754
431
  nowPlayingManager.showNowPlaying()
755
432
  completion()
@@ -757,171 +434,78 @@ public final class RNABCarPlayController: NSObject {
757
434
  }
758
435
 
759
436
  guard let player = audioBrowser.getPlayer(), let trackSelector else {
437
+ pendingSelectionKey = nil
760
438
  completion()
761
439
  return
762
440
  }
763
441
 
764
- // A playable track always lands on the Now Playing surface (selection
765
- // resolves to .play or .intercepted, never .browse) — push it now and stamp
766
- // the tapped track's metadata, instead of after selection resolves: queue
767
- // expansion and the media URL resolve can both hit the network, and waiting
768
- // animates in a blank Now Playing screen. The load pipeline re-publishes
769
- // the same fields and dedupes.
770
- if track.src != nil {
771
- player.loadNowPlayingMetadata(for: track)
772
- // Derive the album line's destination from the tapped track before the
773
- // push — CarPlay reads isAlbumArtistButtonEnabled at display time.
774
- nowPlayingManager.prepareAlbumArtistButton(for: track)
775
- nowPlayingManager.showNowPlaying()
776
- }
777
-
778
- // Release CarPlay immediately so the list never locks up. Apple's handler
779
- // guidance is to finish processing the tap promptly; for a browse we "finish"
780
- // by pushing the destination and filling it in (see navigateToUrl), and for
781
- // playback the Now Playing surface owns its own loading state.
782
- completion()
783
-
784
- Task { [weak self] in
442
+ Task {
443
+ defer { self.pendingSelectionKey = nil }
785
444
  let result = await trackSelector.select(track: track, player: player)
786
- guard let self else { return }
787
445
  switch result {
788
- case let .play(intent):
446
+ case .play(let intent):
789
447
  self.executePlayback(intent, player: player)
790
- self.nowPlayingManager.showNowPlaying(popToFront: false)
448
+ self.nowPlayingManager.showNowPlaying()
791
449
  case .intercepted:
792
- self.nowPlayingManager.showNowPlaying(popToFront: false)
793
- case let .browse(url):
794
- await self.navigateToUrl(url, title: track.title)
450
+ self.nowPlayingManager.showNowPlaying()
451
+ case .browse(let url):
452
+ self.navigateToUrl(url, completion: completion)
453
+ return // navigateToUrl handles its own completion
795
454
  case .none:
796
455
  break
797
456
  }
457
+ completion()
798
458
  }
799
459
  }
800
460
 
801
461
  private func executePlayback(_ intent: TrackSelector.PlaybackIntent, player: TrackPlayer) {
802
462
  switch intent {
803
- case let .skipTo(index):
463
+ case .skipTo(let index):
804
464
  try? player.skipTo(index, playWhenReady: true)
805
- case let .setQueue(tracks, startIndex, sourcePath):
465
+ case .setQueue(let tracks, let startIndex, let sourcePath):
806
466
  player.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: sourcePath)
807
- case let .loadTrack(track):
467
+ case .loadTrack(let track):
808
468
  player.load(track, playWhenReady: true)
809
469
  }
810
470
  }
811
471
 
812
- /// Pushes a browsable URL's destination immediately as an empty, spinning list
813
- /// template — so the list the user tapped from is never blocked. The content is
814
- /// filled by `templateDidAppear` → `loadContentIfNeeded` → `loadContent`, which
815
- /// runs once the template is on screen (the timing CarPlay needs: updates made
816
- /// right after a push are dropped). Backing out and re-tapping retries.
817
- @MainActor
818
- private func navigateToUrl(_ url: String, title: String) async {
819
- // The gate is resolved per-request: a gated path pushes the gate page
820
- // (with this request's chrome) instead of browsing into content; this also
821
- // blocks indirect entries like the Now Playing album line. An allowed path
822
- // browses normally even while a gate is active.
823
- if isGated, let audioBrowser {
824
- let outcome = await audioBrowser.gateDecision(
825
- for: NativeGateRequest(reason: .browse, path: url, search: nil),
826
- )
827
- if outcome.gated {
828
- audioBrowser.onGate(GateEvent(reason: .browse))
829
- if let top = interfaceController.topTemplate, getPath(from: top) == url { return }
830
- interfaceController.safePush(
831
- makeGateTemplate(gate: outcome.chrome, tab: nil), animated: true,
832
- )
833
- return
834
- }
835
- }
836
- // Avoid pushing a duplicate if the top template already shows this path.
837
- if let top = interfaceController.topTemplate, getPath(from: top) == url {
472
+ /// Navigates to a browsable URL path, showing error action sheet on failure with retry option.
473
+ private func navigateToUrl(_ url: String, completion: @escaping () -> Void) {
474
+ guard let audioBrowser else {
475
+ completion()
838
476
  return
839
477
  }
840
- // …and guard a rapid double-tap whose first push hasn't appeared yet (so the
841
- // check above can't see it). Cleared when the pushed template appears.
842
- guard !navigatingPaths.contains(url) else { return }
843
- navigatingPaths.insert(url)
844
-
845
- let template = makeLoadingTemplate(title: title, path: url)
846
- interfaceController.safePush(template, animated: true) { [weak self] pushed, error in
847
- guard !pushed else { return }
848
- // The push can fail (e.g. CarPlay's template stack depth limit). The
849
- // appear callback that normally clears the guard will never fire, so
850
- // clear it here — otherwise the destination stays unreachable until
851
- // CarPlay reconnects.
852
- self?.navigatingPaths.remove(url)
853
- if let error {
854
- self?.logger.error("pushTemplate failed for \(url): \(error.localizedDescription)")
855
- }
856
- }
857
- }
858
-
859
- /// Builds an empty list template for content that is still resolving.
860
- /// On iOS 18.4+ it shows the system spinner (`showsSpinnerWhileEmpty`). On older
861
- /// iOS it shows the app-localized `carPlayLoadingTitle` as the centered empty
862
- /// state when configured; otherwise it stays blank rather than ship a
863
- /// hardcoded, un-localized "Loading…". The empty view is set at creation —
864
- /// the timing CarPlay renders reliably (see `replaceWithMessage`).
865
- private func makeLoadingTemplate(title: String?, path: String?) -> CPListTemplate {
866
- let template = CPListTemplate(title: title, sections: [])
867
- if let path {
868
- template.userInfo = ["path": path] as [String: Any]
869
- }
870
- if let loadingTitle = config.carPlayLoadingTitle {
871
- // Subtitle slot on purpose: the title slot renders at CarPlay's large
872
- // empty-state size, which is too heavy for a transient loading hint.
873
- template.emptyViewSubtitleVariants = [loadingTitle]
874
- }
875
- if #available(iOS 18.4, *) {
876
- template.showsSpinnerWhileEmpty = true
877
- }
878
- return template
879
- }
880
478
 
881
- /// Shows a centered empty/error message on a loading template. A *pushed*
882
- /// template is replaced (see `replaceWithMessage`); a *tab root* can't be
883
- /// popped, so its empty view is set in place (best-effort — a tab renders its
884
- /// empty view when selected).
885
- private func showMessage(on template: CPListTemplate, title: String, subtitle: String?) {
886
- if isTabRoot(template) {
887
- if #available(iOS 18.4, *) {
888
- template.showsSpinnerWhileEmpty = false
889
- }
890
- template.emptyViewTitleVariants = [title.isEmpty ? "Couldn't load" : title]
891
- template.emptyViewSubtitleVariants = subtitle.map { [$0] } ?? []
892
- template.updateSections([])
893
- } else {
894
- replaceWithMessage(template, title: title, subtitle: subtitle)
479
+ // Deduplicate: ignore if already navigating to this URL
480
+ guard !pendingNavigationUrls.contains(url) else {
481
+ completion()
482
+ return
895
483
  }
896
- }
484
+ pendingNavigationUrls.insert(url)
897
485
 
898
- /// Whether `template` is a tab's root template (a child of the tab bar). Such
899
- /// templates can't be popped, so the replace-by-pop strategy doesn't apply.
900
- private func isTabRoot(_ template: CPTemplate) -> Bool {
901
- guard let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate else { return false }
902
- return tabBar.templates.contains { $0 === template }
903
- }
486
+ Task {
487
+ defer { self.pendingNavigationUrls.remove(url) }
488
+ do {
489
+ let resolved = try await audioBrowser.browserManager.resolve(url, useCache: true)
490
+
491
+ // Guard against duplicate push if top template already shows this path
492
+ if let top = self.interfaceController.topTemplate,
493
+ self.getPath(from: top) == url
494
+ {
495
+ completion()
496
+ return
497
+ }
904
498
 
905
- /// Replaces an on-screen loading template with a fresh list template showing a
906
- /// centered empty-state message (empty result, or an error).
907
- ///
908
- /// We *replace* rather than mutate because CarPlay reliably renders a list
909
- /// template's empty view only as its **initial** state at push time — changing
910
- /// `emptyViewTitleVariants` / `showsSpinnerWhileEmpty` on an already-pushed
911
- /// template is unreliable (it renders late, or not at all). The replacement
912
- /// carries no `path`, so `loadContentIfNeeded` won't lazy-reload it; backing
913
- /// out and re-tapping retries.
914
- private func replaceWithMessage(_ loadingTemplate: CPListTemplate, title: String, subtitle: String?) {
915
- // Only replace if the loading template is still the visible top template
916
- // (the user may have backed out while the resolve was in flight).
917
- guard interfaceController.topTemplate === loadingTemplate else { return }
918
-
919
- let message = CPListTemplate(title: loadingTemplate.title, sections: [])
920
- message.emptyViewTitleVariants = [title.isEmpty ? "Couldn't load" : title]
921
- message.emptyViewSubtitleVariants = subtitle.map { [$0] } ?? []
922
-
923
- interfaceController.safePop(animated: false) { [weak self] _, _ in
924
- self?.interfaceController.safePush(message, animated: false)
499
+ let listTemplate = self.createListTemplate(for: resolved, path: url)
500
+ self.navigationStack.append(url)
501
+ self.interfaceController.pushTemplate(listTemplate, animated: true, completion: nil)
502
+ completion()
503
+ } catch {
504
+ logger.error("Failed to navigate to \(url): \(error.localizedDescription)")
505
+ let navError = NavigationError.from(error)
506
+ self.showNavigationError(navError, path: url)
507
+ completion()
508
+ }
925
509
  }
926
510
  }
927
511
 
@@ -930,49 +514,12 @@ public final class RNABCarPlayController: NSObject {
930
514
  @MainActor
931
515
  private func handleTabsChanged(_ tabs: [Track]) {
932
516
  logger.debug("Tabs changed: \(tabs.count) tabs")
933
- // Same tabs by URL while ungated → only the tab-bar entries changed
934
- // (e.g. titles after a locale switch): restamp them in place. No rebuild,
935
- // so the selected tab and any pushed navigation stack survive. (Gate
936
- // templates carry no path, so a gated tab bar never matches here.)
937
- if !isGated,
938
- let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate,
939
- tabBar.templates.count == min(tabs.count, CPTabBarTemplate.maximumTabCount),
940
- zip(tabBar.templates, tabs).allSatisfy({ getPath(from: $0) == $1.url })
941
- {
942
- for (template, tab) in zip(tabBar.templates, tabs) {
943
- applyTabBarEntry(to: template, for: tab)
944
- }
945
- pendingTabs = nil
946
- return
947
- }
948
- // Rebuilding replaces the root template, tearing down any pushed
949
- // navigation stack. If the user is browsing, defer until they're back at
950
- // the tab bar (applied from templateDidAppear).
951
- guard interfaceController.templates.count <= 1 else {
952
- pendingTabs = tabs
953
- return
954
- }
955
- pendingTabs = nil
517
+ // Rebuild tab bar if we're at the root
956
518
  Task {
957
519
  await showTabBar(tabs: tabs)
958
520
  }
959
521
  }
960
522
 
961
- /// Applies a deferred tab change once the user has popped back to the tab
962
- /// bar. Returns true if a rebuild was kicked off (the appeared template is
963
- /// about to be replaced, so callers should skip further work on it).
964
- @MainActor
965
- fileprivate func applyPendingTabsIfAtRoot() -> Bool {
966
- guard let tabs = pendingTabs, interfaceController.templates.count <= 1 else {
967
- return false
968
- }
969
- pendingTabs = nil
970
- Task {
971
- await showTabBar(tabs: tabs)
972
- }
973
- return true
974
- }
975
-
976
523
  @MainActor
977
524
  private func handleContentChanged(_ content: ResolvedTrack?) {
978
525
  // This callback fires when the main browser's content changes.
@@ -985,9 +532,8 @@ public final class RNABCarPlayController: NSObject {
985
532
  private func handleActiveTrackChanged(_ event: PlaybackActiveTrackChangedEvent) {
986
533
  logger.debug("handleActiveTrackChanged: \(event.lastTrack?.src ?? "nil") → \(event.track?.src ?? "nil")")
987
534
  updatePlayingIndicators()
988
- // Refresh per-track button state: favorite heart, Up Next availability,
989
- // and the album line's pre-resolved destination.
990
- nowPlayingManager.handleActiveTrackChanged()
535
+ // Update favorite button to reflect the new track's favorite state
536
+ nowPlayingManager.updateFavoriteButtonState()
991
537
  }
992
538
 
993
539
  /// Updates the isPlaying state on all list items based on the current active track.
@@ -1010,31 +556,19 @@ public final class RNABCarPlayController: NSObject {
1010
556
  }
1011
557
 
1012
558
  for template in templates {
1013
- var templateChanged = false
1014
559
  for section in template.sections {
1015
560
  for item in section.items {
1016
561
  guard let listItem = item as? CPListItem,
1017
- let info = listItem.carPlayItemInfo,
1018
- info.src != nil
562
+ let itemSrc = listItem.carPlayItemInfo?.src
1019
563
  else { continue }
1020
564
 
1021
- let isPlaying = isActiveTrack(id: info.id, src: info.src)
565
+ let isPlaying = isActiveTrack(src: itemSrc)
1022
566
  if listItem.isPlaying != isPlaying {
1023
- logger.debug(
1024
- "Updating isPlaying for \(info.id ?? info.src ?? ""): \(listItem.isPlaying) → \(isPlaying)")
567
+ logger.debug("Updating isPlaying for \(itemSrc): \(listItem.isPlaying) → \(isPlaying)")
1025
568
  listItem.isPlaying = isPlaying
1026
- templateChanged = true
1027
569
  }
1028
570
  }
1029
571
  }
1030
- // Setting isPlaying on an already-displayed item does not reliably reach
1031
- // the CarPlay screen (rdar-known; the flag only renders when the item is
1032
- // pushed with it already set). Re-pushing the same sections re-serializes
1033
- // every item with its current flags — done only when something actually
1034
- // changed, so routine appear-time passes don't churn the display.
1035
- if templateChanged {
1036
- template.updateSections(template.sections)
1037
- }
1038
572
  }
1039
573
  }
1040
574
 
@@ -1098,108 +632,6 @@ public final class RNABCarPlayController: NSObject {
1098
632
  }
1099
633
  }
1100
634
 
1101
- /// Refreshes every currently-displayed template (tabs + navigation stack) by
1102
- /// re-fetching each path with the cache bypassed. Called when all content was
1103
- /// invalidated app-wide (e.g. a locale change).
1104
- @MainActor
1105
- public func invalidateAllContent() {
1106
- guard isStarted else { return }
1107
- Task { @MainActor in await refreshAllDisplayedTemplates() }
1108
- }
1109
-
1110
- @MainActor
1111
- private func refreshAllDisplayedTemplates() async {
1112
- guard let audioBrowser else { return }
1113
-
1114
- var templates: [CPListTemplate] = []
1115
- if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate {
1116
- for template in tabBar.templates {
1117
- if let listTemplate = template as? CPListTemplate {
1118
- templates.append(listTemplate)
1119
- }
1120
- }
1121
- }
1122
- for template in interfaceController.templates {
1123
- if let listTemplate = template as? CPListTemplate,
1124
- !templates.contains(where: { $0 === listTemplate })
1125
- {
1126
- templates.append(listTemplate)
1127
- }
1128
- }
1129
-
1130
- for template in templates {
1131
- guard let path = getPath(from: template) else { continue }
1132
- do {
1133
- let resolved = try await audioBrowser.browserManager.resolve(path, useCache: false)
1134
- updateTemplate(template, with: resolved)
1135
- } catch {
1136
- logger.error("invalidateAllContent: failed to refresh \(path): \(error.localizedDescription)")
1137
- }
1138
- }
1139
- }
1140
-
1141
- // MARK: - Gate
1142
-
1143
- /// Applies a gate state change: set or clear. The per-request chrome is
1144
- /// obtained at render time via `gateDecision(for:)`, so this just tears down
1145
- /// any pushed navigation and rebuilds the tab bar — `showTabBar` routes to the
1146
- /// gated build while a gate is active and to the normal build once cleared.
1147
- /// Custom Now Playing buttons (e.g. favorite) hide while gated and return on
1148
- /// clear.
1149
- @MainActor
1150
- private func handleGateChanged(_ active: Bool) {
1151
- isGated = active
1152
- let tabs = audioBrowser?.browserManager.getTabs() ?? []
1153
- if tabs.isEmpty, !active {
1154
- // Never had tabs (gate was up since before config) — full initial build.
1155
- Task { await buildInitialInterface() }
1156
- } else {
1157
- // Tear down any pushed navigation — gated content must not stay reachable
1158
- // behind the gate, and on clear we return to the root. (No-op at root.)
1159
- interfaceController.safePopToRoot(animated: false)
1160
- Task { await showTabBar(tabs: tabs) }
1161
- }
1162
- nowPlayingManager.setupNowPlayingButtons()
1163
- nowPlayingManager.updateNowPlayingButtonStates()
1164
- }
1165
-
1166
- /// Builds the gate page for one tab-bar slot, carrying the tab's entry so
1167
- /// the tab bar itself stays familiar.
1168
- ///
1169
- /// A CPListTemplate, NOT a CPInformationTemplate: CarPlay enforces
1170
- /// per-entitlement template allowances at runtime, and the audio
1171
- /// entitlement does not include the information template — handing one to
1172
- /// CPTabBarTemplate throws an unhandled ObjC exception (crash at init).
1173
- ///
1174
- /// Within a list, tab children never show navigation-bar buttons (the tab
1175
- /// bar owns that chrome), and the centered empty view renders only when the
1176
- /// list has NO rows. The gate is a plain centered message — `title` as the
1177
- /// empty-view title and `message` as its subtitle, where newlines collapse
1178
- /// to spaces (the "variants" are width alternatives, not lines). No action
1179
- /// button: a gate withholds content; the consumer surfaces any "subscribe"
1180
- /// affordance in its own UI, not on the car surface.
1181
- private func makeGateTemplate(gate gateChrome: Gate?, tab: Track?) -> CPListTemplate {
1182
- // A gated decision with neither an override nor a stored default chrome
1183
- // (resolver-only `true`) falls back to the built-in minimal gate.
1184
- let gate = gateChrome ?? HybridAudioBrowser.builtInGate
1185
- let template = CPListTemplate(title: gate.title, sections: [])
1186
- // Set at creation — the timing CarPlay renders the empty view reliably
1187
- // (see replaceWithMessage).
1188
- template.emptyViewTitleVariants = [gate.title]
1189
- if let message = gate.message, !message.isEmpty {
1190
- template.emptyViewSubtitleVariants = [
1191
- message.replacingOccurrences(of: "\n", with: " "),
1192
- ]
1193
- }
1194
- // Marks the page as a gate (vs. a content tab, which carries a `path`),
1195
- // so the lazy-loader and refresh paths never try to fill it.
1196
- template.userInfo = ["gate": true] as [String: Any]
1197
- if let tab {
1198
- applyTabBarEntry(to: template, for: tab)
1199
- }
1200
- return template
1201
- }
1202
-
1203
635
  // MARK: - Error Handling
1204
636
 
1205
637
  /// Shows a navigation error using CPActionSheetTemplate.
@@ -1235,7 +667,7 @@ public final class RNABCarPlayController: NSObject {
1235
667
  private func presentErrorActionSheet(customDisplay: FormattedNavigationError) {
1236
668
  // If another template is already presented, dismiss it first
1237
669
  if interfaceController.presentedTemplate != nil {
1238
- interfaceController.safeDismiss(animated: false) { [weak self] _, _ in
670
+ interfaceController.dismissTemplate(animated: false) { [weak self] _, _ in
1239
671
  self?.showErrorActionSheet(customDisplay: customDisplay)
1240
672
  }
1241
673
  } else {
@@ -1248,7 +680,7 @@ public final class RNABCarPlayController: NSObject {
1248
680
  // OK action - dismiss the action sheet (use system-localized "OK")
1249
681
  let okTitle = Bundle(for: UIAlertController.self).localizedString(forKey: "OK", value: "OK", table: nil)
1250
682
  let ok = CPAlertAction(title: okTitle, style: .cancel) { [weak self] _ in
1251
- self?.interfaceController.safeDismiss(animated: true)
683
+ self?.interfaceController.dismissTemplate(animated: true, completion: nil)
1252
684
  }
1253
685
 
1254
686
  let actionSheet = CPActionSheetTemplate(
@@ -1257,20 +689,19 @@ public final class RNABCarPlayController: NSObject {
1257
689
  actions: [ok],
1258
690
  )
1259
691
 
1260
- interfaceController.safePresent(actionSheet, animated: true)
692
+ interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)
1261
693
  }
1262
694
 
1263
- /// Shows an initialization failure as the root template — a centered empty
1264
- /// view formatted via the app's `formatNavigationError` (path "/"), like every
1265
- /// other browse failure. The empty view renders reliably here because it's the
1266
- /// template's initial state at set-root time.
1267
- @MainActor
1268
- private func showRootNavigationError(_ navError: NavigationError) async {
1269
- let formatted = await formattedNavigationError(navError, path: "/")
1270
- let template = CPListTemplate(title: nil, sections: [])
1271
- template.emptyViewTitleVariants = [formatted.title]
1272
- template.emptyViewSubtitleVariants = formatted.message.flatMap { $0.isEmpty ? nil : [$0] } ?? []
1273
- interfaceController.safeSetRoot(template, animated: true)
695
+ /// Shows a simple error template as root (for initialization errors when no other template exists)
696
+ private func showErrorTemplate(message: String) {
697
+ // CPAlertTemplate cannot be set as root - use a list template instead
698
+ let errorItem = CPListItem(text: message, detailText: nil)
699
+ errorItem.isEnabled = false
700
+ let template = CPListTemplate(
701
+ title: "Error",
702
+ sections: [CPListSection(items: [errorItem])],
703
+ )
704
+ interfaceController.setRootTemplate(template, animated: true, completion: nil)
1274
705
  }
1275
706
  }
1276
707
 
@@ -1286,30 +717,13 @@ private final class InterfaceControllerDelegate: NSObject, CPInterfaceController
1286
717
  }
1287
718
 
1288
719
  func templateDidAppear(_ aTemplate: CPTemplate, animated _: Bool) {
1289
- // Popping back from Now Playing surfaces the tab bar (not a list), so the
1290
- // guard must let both through — the playing indicator was toggled while
1291
- // the list was covered, and this appearance is the moment to repaint it.
1292
- guard aTemplate is CPListTemplate || aTemplate is CPTabBarTemplate else { return }
1293
-
1294
- // A tab change deferred while the user was browsing replaces the whole
1295
- // tab bar — no point updating a template that's about to be discarded.
1296
- if controller?.applyPendingTabsIfAtRoot() == true { return }
720
+ guard let listTemplate = aTemplate as? CPListTemplate else { return }
1297
721
 
1298
722
  // Update playing indicators when navigating back to a list template
1299
723
  controller?.updatePlayingIndicators()
1300
724
 
1301
725
  // Lazy load content for tabs that haven't been loaded yet
1302
- if let listTemplate = aTemplate as? CPListTemplate {
1303
- controller?.loadContentIfNeeded(for: listTemplate)
1304
- }
1305
- }
1306
-
1307
- func templateDidDisappear(_ aTemplate: CPTemplate, animated _: Bool) {
1308
- // The complement of the appear hook: whether popping Now Playing reports
1309
- // "tab bar appeared" or only "now playing disappeared" is a platform
1310
- // detail — cover both so the revealed list always repaints its indicator.
1311
- guard aTemplate is CPNowPlayingTemplate else { return }
1312
- controller?.updatePlayingIndicators()
726
+ controller?.loadContentIfNeeded(for: listTemplate)
1313
727
  }
1314
728
  }
1315
729
 
@@ -1318,16 +732,12 @@ private final class InterfaceControllerDelegate: NSObject, CPInterfaceController
1318
732
  private extension RNABCarPlayController {
1319
733
  /// Loads content for a template if it hasn't been loaded yet (lazy loading for tabs)
1320
734
  func loadContentIfNeeded(for template: CPListTemplate) {
735
+ // Skip if already has content
736
+ guard template.sections.isEmpty else { return }
737
+
1321
738
  // Get path from userInfo
1322
739
  guard let path = getPath(from: template) else { return }
1323
740
 
1324
- // The template is now on screen, so a queued duplicate navigation to it is
1325
- // no longer a concern (the top-template check will catch any further taps).
1326
- navigatingPaths.remove(path)
1327
-
1328
- // Skip if already has content (single-flight in loadContent guards re-entry).
1329
- guard template.sections.isEmpty else { return }
1330
-
1331
741
  logger.debug("Lazy loading content for tab: \(path)")
1332
742
 
1333
743
  Task {