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

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 (290) hide show
  1. package/README.md +22 -18
  2. package/android/build.gradle +13 -10
  3. package/android/gradle.properties +2 -2
  4. package/android/src/main/AndroidManifest.xml +3 -1
  5. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +76 -21
  6. package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
  7. package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
  8. package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
  9. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +1 -2
  10. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +1 -2
  11. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +6 -7
  12. package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
  13. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +11 -11
  14. package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
  15. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +9 -5
  16. package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
  17. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -1
  18. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +2 -4
  19. package/android/src/main/java/com/audiobrowser/player/Player.kt +30 -8
  20. package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
  21. package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +2 -1
  22. package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
  23. package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
  24. package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
  25. package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
  26. package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
  27. package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
  28. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +8 -7
  29. package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
  30. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +52 -1
  31. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
  32. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +167 -8
  33. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +2 -1
  34. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +2 -4
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +100 -56
  36. package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
  38. package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
  39. package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
  40. package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
  41. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
  42. package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
  43. package/ios/Browser/BrowserManager.swift +2 -27
  44. package/ios/Browser/SearchDrillIn.swift +1 -1
  45. package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
  46. package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
  47. package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
  48. package/ios/HybridAudioBrowser.swift +87 -56
  49. package/ios/Model/NitroTypeStubs.swift +18 -2
  50. package/ios/Model/RemoteCommand.swift +18 -0
  51. package/ios/Model/TrackPlayerError.swift +2 -2
  52. package/ios/Option/SessionCategories.swift +3 -2
  53. package/ios/Player/MediaIntentCriteria.swift +4 -4
  54. package/ios/Player/MediaLoader.swift +3 -1
  55. package/ios/Player/PlaybackCoordinator.swift +4 -1
  56. package/ios/Player/PlaybackErrorHandler.swift +4 -4
  57. package/ios/Player/QueueManager.swift +10 -3
  58. package/ios/Player/RetryManager.swift +79 -18
  59. package/ios/Player/ShuffleOrder.swift +10 -0
  60. package/ios/Player/SleepTimerManager.swift +43 -15
  61. package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
  62. package/ios/RemoteCommand/RemoteCommandController.swift +9 -6
  63. package/ios/Tests/LRUCacheTests.swift +111 -0
  64. package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
  65. package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
  66. package/ios/Tests/MediaLoaderTests.swift +10 -13
  67. package/ios/Tests/PlaybackCoordinatorTests.swift +10 -7
  68. package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
  69. package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
  70. package/ios/Tests/QueueManagerTests.swift +50 -4
  71. package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
  72. package/ios/Tests/RemoteCommandTests.swift +67 -0
  73. package/ios/Tests/RetryManagerTests.swift +89 -0
  74. package/ios/Tests/SectionScopeTests.swift +2 -2
  75. package/ios/Tests/SleepTimerManagerTests.swift +22 -19
  76. package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
  77. package/ios/Tests/Support/TestTypes.swift +1 -1
  78. package/ios/Tests/TabBarEntriesTests.swift +7 -7
  79. package/ios/TrackPlayer.swift +6 -1
  80. package/ios/TrackPlayerCallbacks.swift +5 -38
  81. package/ios/Util/LRUCache.swift +4 -1
  82. package/lib/commonjs/AudioBrowser.js +0 -11
  83. package/lib/commonjs/AudioBrowser.js.map +1 -1
  84. package/lib/commonjs/CLAUDE.md +52 -0
  85. package/lib/commonjs/features/browser.js +1 -1
  86. package/lib/commonjs/features/browser.js.map +1 -1
  87. package/lib/commonjs/features/carConnection.js +1 -1
  88. package/lib/commonjs/features/equalizer.js +1 -1
  89. package/lib/commonjs/features/errors.js +3 -3
  90. package/lib/commonjs/features/favorites.js +1 -1
  91. package/lib/commonjs/features/gate.js +16 -1
  92. package/lib/commonjs/features/gate.js.map +1 -1
  93. package/lib/commonjs/features/metadata.js +20 -3
  94. package/lib/commonjs/features/metadata.js.map +1 -1
  95. package/lib/commonjs/features/network.js +1 -1
  96. package/lib/commonjs/features/nowPlaying.js +1 -1
  97. package/lib/commonjs/features/output.js +1 -1
  98. package/lib/commonjs/features/playback/playWhenReady.js +1 -1
  99. package/lib/commonjs/features/playback/playing.js +1 -1
  100. package/lib/commonjs/features/playback/progress.js +1 -1
  101. package/lib/commonjs/features/playback/state.js +1 -1
  102. package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
  103. package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
  104. package/lib/commonjs/features/playback/volume.js +1 -1
  105. package/lib/commonjs/features/player/options.js +48 -34
  106. package/lib/commonjs/features/player/options.js.map +1 -1
  107. package/lib/commonjs/features/player/setup.js +15 -8
  108. package/lib/commonjs/features/player/setup.js.map +1 -1
  109. package/lib/commonjs/features/player/validateOptions.js +21 -0
  110. package/lib/commonjs/features/player/validateOptions.js.map +1 -0
  111. package/lib/commonjs/features/queue/activeTrack.js +1 -1
  112. package/lib/commonjs/features/queue/queue.js +2 -2
  113. package/lib/commonjs/features/queue/repeatMode.js +1 -1
  114. package/lib/commonjs/features/queue/shuffle.js +1 -1
  115. package/lib/commonjs/features/remoteControls.js +13 -92
  116. package/lib/commonjs/features/remoteControls.js.map +1 -1
  117. package/lib/commonjs/features/sleepTimer.js +13 -1
  118. package/lib/commonjs/features/sleepTimer.js.map +1 -1
  119. package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
  120. package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
  121. package/lib/commonjs/utils/LazyNativeEmitter.js +8 -5
  122. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  123. package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
  124. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  125. package/lib/commonjs/web/browser/BrowserManager.js +1 -1
  126. package/lib/module/AudioBrowser.js +1 -1
  127. package/lib/module/AudioBrowser.js.map +1 -1
  128. package/lib/module/CLAUDE.md +52 -0
  129. package/lib/module/features/browser.js +1 -1
  130. package/lib/module/features/browser.js.map +1 -1
  131. package/lib/module/features/carConnection.js +1 -1
  132. package/lib/module/features/equalizer.js +1 -1
  133. package/lib/module/features/errors.js +3 -3
  134. package/lib/module/features/favorites.js +1 -1
  135. package/lib/module/features/gate.js +16 -1
  136. package/lib/module/features/gate.js.map +1 -1
  137. package/lib/module/features/metadata.js +20 -3
  138. package/lib/module/features/metadata.js.map +1 -1
  139. package/lib/module/features/network.js +1 -1
  140. package/lib/module/features/nowPlaying.js +1 -1
  141. package/lib/module/features/output.js +1 -1
  142. package/lib/module/features/playback/playWhenReady.js +1 -1
  143. package/lib/module/features/playback/playing.js +1 -1
  144. package/lib/module/features/playback/progress.js +1 -1
  145. package/lib/module/features/playback/state.js +1 -1
  146. package/lib/module/features/playback/trackPlaybackTime.js +0 -5
  147. package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
  148. package/lib/module/features/playback/volume.js +1 -1
  149. package/lib/module/features/player/options.js +47 -32
  150. package/lib/module/features/player/options.js.map +1 -1
  151. package/lib/module/features/player/setup.js +14 -7
  152. package/lib/module/features/player/setup.js.map +1 -1
  153. package/lib/module/features/player/validateOptions.js +17 -0
  154. package/lib/module/features/player/validateOptions.js.map +1 -0
  155. package/lib/module/features/queue/activeTrack.js +1 -1
  156. package/lib/module/features/queue/queue.js +2 -2
  157. package/lib/module/features/queue/repeatMode.js +1 -1
  158. package/lib/module/features/queue/shuffle.js +1 -1
  159. package/lib/module/features/remoteControls.js +13 -92
  160. package/lib/module/features/remoteControls.js.map +1 -1
  161. package/lib/module/features/sleepTimer.js +13 -1
  162. package/lib/module/features/sleepTimer.js.map +1 -1
  163. package/lib/module/test-utils/reactNativeStub.js +58 -0
  164. package/lib/module/test-utils/reactNativeStub.js.map +1 -0
  165. package/lib/module/utils/LazyNativeEmitter.js +8 -5
  166. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  167. package/lib/module/web/NativeAudioBrowser.js +1 -1
  168. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  169. package/lib/module/web/browser/BrowserManager.js +1 -1
  170. package/lib/typescript/src/AudioBrowser.d.ts +0 -1
  171. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  172. package/lib/typescript/src/features/carConnection.d.ts +1 -1
  173. package/lib/typescript/src/features/equalizer.d.ts +1 -1
  174. package/lib/typescript/src/features/errors.d.ts +3 -3
  175. package/lib/typescript/src/features/favorites.d.ts +1 -1
  176. package/lib/typescript/src/features/gate.d.ts +18 -2
  177. package/lib/typescript/src/features/gate.d.ts.map +1 -1
  178. package/lib/typescript/src/features/metadata.d.ts +20 -3
  179. package/lib/typescript/src/features/metadata.d.ts.map +1 -1
  180. package/lib/typescript/src/features/network.d.ts +1 -1
  181. package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
  182. package/lib/typescript/src/features/output.d.ts +1 -1
  183. package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
  184. package/lib/typescript/src/features/playback/playing.d.ts +1 -1
  185. package/lib/typescript/src/features/playback/progress.d.ts +1 -1
  186. package/lib/typescript/src/features/playback/state.d.ts +1 -1
  187. package/lib/typescript/src/features/playback/volume.d.ts +1 -1
  188. package/lib/typescript/src/features/player/options.d.ts +87 -37
  189. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  190. package/lib/typescript/src/features/player/setup.d.ts +14 -4
  191. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  192. package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
  193. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
  194. package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
  195. package/lib/typescript/src/features/queue/queue.d.ts +2 -2
  196. package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
  197. package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
  198. package/lib/typescript/src/features/remoteControls.d.ts +23 -22
  199. package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
  200. package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
  201. package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
  202. package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
  203. package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
  204. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -4
  205. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  206. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -1
  207. package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
  208. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +5 -5
  209. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +11 -11
  210. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
  211. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +5 -5
  212. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +5 -5
  213. package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
  214. package/nitrogen/generated/android/c++/JOptions.hpp +5 -5
  215. package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
  216. package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
  217. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
  218. package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
  219. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
  220. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
  221. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
  222. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
  223. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
  224. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
  225. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +36 -51
  226. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +6 -6
  227. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +8 -8
  228. package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
  229. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
  230. package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
  231. package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
  232. package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
  233. package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
  234. package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
  235. package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
  236. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
  237. package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
  238. package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
  239. package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
  240. package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
  241. package/package.json +12 -4
  242. package/src/AudioBrowser.ts +1 -1
  243. package/src/CLAUDE.md +52 -0
  244. package/src/features/battery.test.ts +79 -0
  245. package/src/features/browser.ts +1 -1
  246. package/src/features/carConnection.ts +1 -1
  247. package/src/features/equalizer.ts +1 -1
  248. package/src/features/errors.ts +3 -3
  249. package/src/features/favorites.ts +1 -1
  250. package/src/features/gate.ts +16 -1
  251. package/src/features/hook-wiring.test.ts +254 -0
  252. package/src/features/metadata.ts +20 -3
  253. package/src/features/network.ts +1 -1
  254. package/src/features/nowPlaying.ts +1 -1
  255. package/src/features/output.ts +1 -1
  256. package/src/features/playback/playWhenReady.ts +1 -1
  257. package/src/features/playback/playing.ts +1 -1
  258. package/src/features/playback/progress.test.ts +169 -0
  259. package/src/features/playback/progress.ts +1 -1
  260. package/src/features/playback/state.ts +1 -1
  261. package/src/features/playback/trackPlaybackTime.test.ts +16 -6
  262. package/src/features/playback/trackPlaybackTime.ts +0 -5
  263. package/src/features/playback/volume.ts +1 -1
  264. package/src/features/player/options.ts +99 -57
  265. package/src/features/player/setup.test.ts +12 -4
  266. package/src/features/player/setup.ts +19 -9
  267. package/src/features/player/validateOptions.ts +19 -0
  268. package/src/features/queue/activeTrack.ts +1 -1
  269. package/src/features/queue/queue.ts +2 -2
  270. package/src/features/queue/repeatMode.ts +1 -1
  271. package/src/features/queue/shuffle.ts +1 -1
  272. package/src/features/remoteControls.ts +13 -92
  273. package/src/features/sleepTimer.test.ts +172 -0
  274. package/src/features/sleepTimer.ts +9 -1
  275. package/src/nitro-stub-drift.test.ts +237 -0
  276. package/src/public-surface.test.ts +107 -0
  277. package/src/specs/audio-browser.nitro.ts +1 -1
  278. package/src/test-utils/reactNativeStub.ts +60 -0
  279. package/src/utils/LazyNativeEmitter.ts +9 -6
  280. package/src/utils/useDebug.test.ts +182 -0
  281. package/src/utils/useNativeUpdatedValue.test.ts +103 -0
  282. package/src/web/NativeAudioBrowser.ts +1 -1
  283. package/src/web/browser/BrowserManager.ts +1 -1
  284. package/ios/Model/MediaURL.swift +0 -67
  285. package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
  286. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
  287. package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
  289. package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
  290. package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
@@ -3,6 +3,7 @@ import type { CarPlayNowPlayingButton } from '../../types/browser'
3
3
  import { nativeBrowser } from '../../native'
4
4
  import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
5
5
  import { useNativeUpdatedValue } from '../../utils/useNativeUpdatedValue'
6
+ import { validateIOSUpdateOptions } from './validateOptions'
6
7
 
7
8
  // MARK: - Types
8
9
 
@@ -70,7 +71,8 @@ export interface PlayerCapabilities {
70
71
  *
71
72
  * Turns on the favorite/like heart across all surfaces:
72
73
  * - iOS: Control Center + CarPlay now-playing.
73
- * - Android: notification button slot + Android Auto now-playing, and an
74
+ * - Android: a place in the {@link RemoteButtonLayout} — the notification,
75
+ * Android Auto and the Android 13+ system media controls — and an
74
76
  * (empty or filled) heart on playable browse rows.
75
77
  *
76
78
  * `match` controls how the ids from `setFavorites` are
@@ -117,10 +119,11 @@ export interface PlayerCapabilities {
117
119
  }
118
120
 
119
121
  /**
120
- * Buttons that can be assigned to Android notification button slots.
121
- * These represent the interactive buttons users can tap in the notification.
122
+ * Buttons that can be placed in the Android button layout — the interactive
123
+ * controls a listener taps in the notification, on the Android Auto Now Playing
124
+ * screen, and in the Android 13+ system media controls.
122
125
  */
123
- export type NotificationButton =
126
+ export type RemoteButton =
124
127
  | 'skip-to-previous'
125
128
  | 'skip-to-next'
126
129
  | 'jump-backward'
@@ -128,43 +131,71 @@ export type NotificationButton =
128
131
  | 'favorite'
129
132
 
130
133
  /**
131
- * Configuration for notification button layout on Android.
132
- * Allows explicit control over which buttons appear in which slots.
134
+ * Where each button sits on Android.
133
135
  *
134
- * Slot behavior:
135
- * - **Omit a slot**: Derive from capabilities (smart default)
136
- * - **Set to null**: Explicitly empty slot
137
- * - **Set to button**: Show that button in that slot
136
+ * Android offers exactly three positions, and play/pause always occupies the
137
+ * centre — you cannot move it, and you cannot put two buttons on the same side
138
+ * of it:
139
+ *
140
+ * ```
141
+ * back │ ▶ play/pause │ forward overflow ⋯
142
+ * ```
143
+ *
144
+ * - **`back`** — the single position left of play/pause
145
+ * - **`forward`** — the single position right of play/pause
146
+ * - **`overflow`** — everything else, in priority order
147
+ *
148
+ * **A layout describes the whole arrangement.** All three fields are required —
149
+ * there is no per-field merge with the capability defaults, so what you write is
150
+ * exactly what renders. Use `undefined` for an empty position and `[]` for no
151
+ * overflow. To go back to the derived defaults, omit `remoteButtonLayout`
152
+ * entirely (or set it to `null`); that is the only switch.
153
+ *
154
+ * **Overflow order is priority, not coordinates.** Each surface renders as many
155
+ * buttons as it has room for, taking them from the front — a phone
156
+ * notification, the Android 13+ system media controls, and a car head unit all
157
+ * have different budgets. A head unit with a spare slot may promote the first
158
+ * overflow entry onto the main row, and a long list gets truncated by whichever
159
+ * surface is showing it. Put what matters most first.
160
+ *
161
+ * Placement never changes what a control *can* do: a button left out entirely
162
+ * still responds to a Bluetooth remote or headset, as long as its
163
+ * {@link PlayerCapabilities} entry is enabled.
138
164
  *
139
165
  * @example
140
166
  * ```typescript
141
- * // Podcast-style: jump buttons as primary
142
- * notificationButtons: {
167
+ * // Podcast-style: jump either side of play/pause
168
+ * remoteButtonLayout: {
143
169
  * back: 'jump-backward',
144
170
  * forward: 'jump-forward',
145
- * overflow: ['favorite']
171
+ * overflow: ['skip-to-previous', 'skip-to-next', 'favorite']
146
172
  * }
147
173
  *
148
- * // Music-style: skip as primary, jump as secondary
149
- * notificationButtons: {
174
+ * // Music-style: skip either side of play/pause
175
+ * remoteButtonLayout: {
150
176
  * back: 'skip-to-previous',
151
177
  * forward: 'skip-to-next',
152
- * backSecondary: 'jump-backward',
153
- * forwardSecondary: 'jump-forward'
178
+ * overflow: ['favorite']
179
+ * }
180
+ *
181
+ * // Live radio: forward only, nothing to the left
182
+ * remoteButtonLayout: {
183
+ * back: undefined,
184
+ * forward: 'skip-to-next',
185
+ * overflow: []
154
186
  * }
155
187
  * ```
156
188
  */
157
- export type NotificationButtonLayout = {
158
- /** Primary back position (SLOT_BACK) - typically previous track or jump backward */
159
- back?: NotificationButton | null
160
- /** Primary forward position (SLOT_FORWARD) - typically next track or jump forward */
161
- forward?: NotificationButton | null
162
- /** Secondary back position (SLOT_BACK_SECONDARY) */
163
- backSecondary?: NotificationButton | null
164
- /** Secondary forward position (SLOT_FORWARD_SECONDARY) */
165
- forwardSecondary?: NotificationButton | null
166
- /** Additional buttons in overflow area (SLOT_OVERFLOW) */
167
- overflow?: NotificationButton[]
189
+ export type RemoteButtonLayout = {
190
+ /** The single position left of play/pause. `undefined` leaves it empty. */
191
+ back: RemoteButton | undefined
192
+ /** The single position right of play/pause. `undefined` leaves it empty. */
193
+ forward: RemoteButton | undefined
194
+ /**
195
+ * Everything else, most important first — surfaces promote and truncate from
196
+ * the front. `[]` for none.
197
+ */
198
+ overflow: RemoteButton[]
168
199
  }
169
200
 
170
201
  /**
@@ -204,12 +235,22 @@ export interface Options {
204
235
 
205
236
  /**
206
237
  * Jump forward interval in seconds when using jump forward controls.
238
+ *
239
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
240
+ * showing that number; any other interval gets an icon without one. Other
241
+ * values are perfectly valid — the interval also sets the seek distance.
242
+ *
207
243
  * @default 15
208
244
  */
209
245
  forwardJumpInterval: number
210
246
 
211
247
  /**
212
248
  * Jump backward interval in seconds when using jump backward controls.
249
+ *
250
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
251
+ * showing that number; any other interval gets an icon without one. Other
252
+ * values are perfectly valid — the interval also sets the seek distance.
253
+ *
213
254
  * @default 15
214
255
  */
215
256
  backwardJumpInterval: number
@@ -273,14 +314,14 @@ export interface AndroidOptions {
273
314
  skipSilence: boolean
274
315
 
275
316
  /**
276
- * Slot-based button layout for Android notifications.
277
- * Provides explicit control over which buttons appear in which positions.
317
+ * Ordered button layout, applied to the notification, Android Auto, and the
318
+ * Android 13+ system media controls. See {@link RemoteButtonLayout}.
278
319
  *
279
- * When not specified, button layout is derived from capabilities.
320
+ * When not specified, the layout is derived from capabilities.
280
321
  *
281
322
  * @platform android
282
323
  */
283
- notificationButtons: NotificationButtonLayout | null
324
+ remoteButtonLayout: RemoteButtonLayout | null
284
325
  }
285
326
 
286
327
  export interface AndroidUpdateOptions {
@@ -300,14 +341,14 @@ export interface AndroidUpdateOptions {
300
341
  skipSilence?: boolean
301
342
 
302
343
  /**
303
- * Slot-based button layout for Android notifications.
304
- * Provides explicit control over which buttons appear in which positions.
344
+ * Ordered button layout, applied to the notification, Android Auto, and the
345
+ * Android 13+ system media controls. See {@link RemoteButtonLayout}.
305
346
  *
306
- * When not specified, button layout is derived from capabilities.
347
+ * When not specified, the layout is derived from capabilities.
307
348
  *
308
349
  * @platform android
309
350
  */
310
- notificationButtons?: NotificationButtonLayout | null
351
+ remoteButtonLayout?: RemoteButtonLayout | null
311
352
  }
312
353
 
313
354
  /**
@@ -317,7 +358,7 @@ export interface AndroidUpdateOptions {
317
358
  export interface NitroAndroidUpdateOptions {
318
359
  appKilledPlaybackBehavior?: AppKilledPlaybackBehavior
319
360
  skipSilence?: boolean
320
- notificationButtons?: NotificationButtonLayout | null
361
+ remoteButtonLayout?: RemoteButtonLayout | null
321
362
  }
322
363
 
323
364
  /**
@@ -394,12 +435,22 @@ export interface UpdateOptions {
394
435
 
395
436
  /**
396
437
  * Jump forward interval in seconds when using jump forward controls.
438
+ *
439
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
440
+ * showing that number; any other interval gets an icon without one. Other
441
+ * values are perfectly valid — the interval also sets the seek distance.
442
+ *
397
443
  * @default 15
398
444
  */
399
445
  forwardJumpInterval?: number
400
446
 
401
447
  /**
402
448
  * Jump backward interval in seconds when using jump backward controls.
449
+ *
450
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
451
+ * showing that number; any other interval gets an icon without one. Other
452
+ * values are perfectly valid — the interval also sets the seek distance.
453
+ *
403
454
  * @default 15
404
455
  */
405
456
  backwardJumpInterval?: number
@@ -442,12 +493,22 @@ export interface NativeUpdateOptions {
442
493
 
443
494
  /**
444
495
  * Jump forward interval in seconds when using jump forward controls.
496
+ *
497
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
498
+ * showing that number; any other interval gets an icon without one. Other
499
+ * values are perfectly valid — the interval also sets the seek distance.
500
+ *
445
501
  * @default 15
446
502
  */
447
503
  forwardJumpInterval?: number
448
504
 
449
505
  /**
450
506
  * Jump backward interval in seconds when using jump backward controls.
507
+ *
508
+ * On Android, an interval of exactly 5, 10, 15 or 30 seconds gets an icon
509
+ * showing that number; any other interval gets an icon without one. Other
510
+ * values are perfectly valid — the interval also sets the seek distance.
511
+ *
451
512
  * @default 15
452
513
  */
453
514
  backwardJumpInterval?: number
@@ -464,25 +525,6 @@ export interface NativeUpdateOptions {
464
525
 
465
526
  // MARK: - Functions
466
527
 
467
- const MAX_CARPLAY_NOW_PLAYING_BUTTONS = 5
468
-
469
- /**
470
- * Warns when more CarPlay now-playing buttons are configured than CarPlay renders.
471
- * Shared by {@link updateOptions} and `setupPlayer` (both can carry `ios` options).
472
- *
473
- * @internal Validation helper, not public API — `stripInternal` removes it from
474
- * the published types and typedoc hides it from the docs.
475
- */
476
- export function validateIOSUpdateOptions(ios?: IOSUpdateOptions): void {
477
- const buttons = ios?.carPlayNowPlayingButtons
478
- if (buttons && buttons.length > MAX_CARPLAY_NOW_PLAYING_BUTTONS) {
479
- console.warn(
480
- `[react-native-audio-browser] ${buttons.length} CarPlay now-playing ` +
481
- `buttons configured; CarPlay shows at most ${MAX_CARPLAY_NOW_PLAYING_BUTTONS}.`
482
- )
483
- }
484
- }
485
-
486
528
  /**
487
529
  * Updates the configuration for the components.
488
530
  * Pass only the properties you want to change - all properties are optional.
@@ -527,7 +569,7 @@ export function getOptions(): Options {
527
569
  /**
528
570
  * Subscribes to player options changes.
529
571
  * @param callback - Called when the player options change
530
- * @returns Cleanup function to unsubscribe
572
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
531
573
  */
532
574
  export const onOptionsChanged = NativeUpdatedValue.emitterize<Options>(
533
575
  (cb) => (nativeBrowser.onOptionsChanged = cb)
@@ -52,7 +52,11 @@ describe('setupPlayer wire regrouping', () => {
52
52
  minBuffer: 50_000,
53
53
  wakeMode: 'network',
54
54
  skipSilence: true,
55
- notificationButtons: { overflow: ['favorite'] }
55
+ remoteButtonLayout: {
56
+ back: 'jump-backward',
57
+ forward: 'jump-forward',
58
+ overflow: ['favorite']
59
+ }
56
60
  }
57
61
  })
58
62
 
@@ -61,7 +65,11 @@ describe('setupPlayer wire regrouping', () => {
61
65
  expect(sent.options).toEqual({
62
66
  android: {
63
67
  skipSilence: true,
64
- notificationButtons: { overflow: ['favorite'] }
68
+ remoteButtonLayout: {
69
+ back: 'jump-backward',
70
+ forward: 'jump-forward',
71
+ overflow: ['favorite']
72
+ }
65
73
  }
66
74
  })
67
75
  })
@@ -85,12 +93,12 @@ describe('setupPlayer wire regrouping', () => {
85
93
  it('forwards meaningful nulls (progress disabled, empty button layout)', async () => {
86
94
  await setupPlayer({
87
95
  progressUpdateEventInterval: null,
88
- android: { notificationButtons: null }
96
+ android: { remoteButtonLayout: null }
89
97
  })
90
98
 
91
99
  expect(payload().options).toEqual({
92
100
  progressUpdateEventInterval: null,
93
- android: { notificationButtons: null }
101
+ android: { remoteButtonLayout: null }
94
102
  })
95
103
  })
96
104
 
@@ -11,7 +11,7 @@ import type {
11
11
  UpdateOptions
12
12
  } from './options'
13
13
  import { nativeBrowser } from '../../native'
14
- import { validateIOSUpdateOptions } from './options'
14
+ import { validateIOSUpdateOptions } from './validateOptions'
15
15
 
16
16
  /**
17
17
  * Parameters passed to the {@link FormatNowPlayingCallback}.
@@ -25,7 +25,10 @@ import { validateIOSUpdateOptions } from './options'
25
25
  export type FormatNowPlayingParams = {
26
26
  /** The currently playing track. */
27
27
  track: Track
28
- /** Timed metadata (ICY / ID3 "now playing song") received during playback, if any. */
28
+ /**
29
+ * Timed metadata (ICY / ID3 "now playing song") received during playback, if any.
30
+ * Always `undefined` on web — see `onTimedMetadata` for why.
31
+ */
29
32
  timedMetadata?: TimedMetadata
30
33
  /** The play/pause intent — `false` while paused. Stays `true` through buffers, so the song line won't flicker. */
31
34
  playWhenReady: boolean
@@ -326,7 +329,7 @@ export type AndroidPlayerWakeMode = 'none' | 'local' | 'network'
326
329
 
327
330
  /**
328
331
  * Android setup options: the engine-construction fields plus the Android runtime options
329
- * ({@link AndroidUpdateOptions} — `notificationButtons`, `skipSilence`, …), all expressible at
332
+ * ({@link AndroidUpdateOptions} — `remoteButtonLayout`, `skipSilence`, …), all expressible at
330
333
  * launch. The consumer never has to know which is which; `setupPlayer` routes each field to
331
334
  * where it's applied.
332
335
  */
@@ -496,7 +499,7 @@ export interface NativeSetupPlayerOptions extends Pick<
496
499
 
497
500
  /**
498
501
  * Player options to apply atomically with setup. They're stored before the player is
499
- * constructed, so e.g. Android's media session derives its notification buttons from
502
+ * constructed, so e.g. Android's media session derives its remote button layout from
500
503
  * `capabilities` the moment the service connects — no window with default controls.
501
504
  * Normalized from the flat fields on {@link SetupPlayerOptions}.
502
505
  */
@@ -658,7 +661,7 @@ function wrapNowPlayingFormatter(
658
661
 
659
662
  /**
660
663
  * The object's entries whose values aren't `undefined`. `null` stays: it's meaningful on some
661
- * fields (`progressUpdateEventInterval: null` disables progress events, `notificationButtons:
664
+ * fields (`progressUpdateEventInterval: null` disables progress events, `remoteButtonLayout:
662
665
  * null` empties the layout), so only `undefined` means "not provided".
663
666
  */
664
667
  function definedFields<T extends object>(obj: T): Partial<T> {
@@ -682,7 +685,7 @@ function nonEmpty<K extends string, T extends object>(
682
685
  /**
683
686
  * Initializes the player with the specified options — the full launch description in one
684
687
  * declarative, atomic call. Runtime options (`capabilities`, jump intervals,
685
- * `notificationButtons`, …) and the initial `playWhenReady` / `repeatMode` are applied
688
+ * `remoteButtonLayout`, …) and the initial `playWhenReady` / `repeatMode` are applied
686
689
  * natively as part of setup itself, so there is no setup-then-command ordering for consumers
687
690
  * to know about, and no window where the player exists without its options.
688
691
  *
@@ -699,7 +702,14 @@ function nonEmpty<K extends string, T extends object>(
699
702
  * playWhenReady: true,
700
703
  * repeatMode: 'queue',
701
704
  * capabilities: { favorite: true },
702
- * android: { audioContentType: 'music', notificationButtons: { overflow: ['favorite'] } },
705
+ * android: {
706
+ * audioContentType: 'music',
707
+ * remoteButtonLayout: {
708
+ * back: 'jump-backward',
709
+ * forward: 'jump-forward',
710
+ * overflow: ['favorite']
711
+ * }
712
+ * },
703
713
  * ios: { category: 'playback' }
704
714
  * })
705
715
  * ```
@@ -724,7 +734,7 @@ export async function setupPlayer(
724
734
  const {
725
735
  appKilledPlaybackBehavior,
726
736
  skipSilence,
727
- notificationButtons,
737
+ remoteButtonLayout,
728
738
  ...androidSetup
729
739
  } = android
730
740
  const {
@@ -750,7 +760,7 @@ export async function setupPlayer(
750
760
  definedFields({
751
761
  appKilledPlaybackBehavior,
752
762
  skipSilence,
753
- notificationButtons
763
+ remoteButtonLayout
754
764
  })
755
765
  ),
756
766
  ...nonEmpty('ios', iosUpdate)
@@ -0,0 +1,19 @@
1
+ import type { IOSUpdateOptions } from './options'
2
+
3
+ const MAX_CARPLAY_NOW_PLAYING_BUTTONS = 5
4
+
5
+ /**
6
+ * Warns when more CarPlay now-playing buttons are configured than CarPlay renders.
7
+ * Shared by `updateOptions` and `setupPlayer` (both can carry `ios` options).
8
+ * Kept out of `options.ts`, which the barrel re-exports — see
9
+ * public-surface.test.ts.
10
+ */
11
+ export function validateIOSUpdateOptions(ios?: IOSUpdateOptions): void {
12
+ const buttons = ios?.carPlayNowPlayingButtons
13
+ if (buttons && buttons.length > MAX_CARPLAY_NOW_PLAYING_BUTTONS) {
14
+ console.warn(
15
+ `[react-native-audio-browser] ${buttons.length} CarPlay now-playing ` +
16
+ `buttons configured; CarPlay shows at most ${MAX_CARPLAY_NOW_PLAYING_BUTTONS}.`
17
+ )
18
+ }
19
+ }
@@ -41,7 +41,7 @@ export function getActiveTrackIndex(): number | undefined {
41
41
  /**
42
42
  * Subscribes to active track change events.
43
43
  * @param callback - Called when the active track changes
44
- * @returns Cleanup function to unsubscribe
44
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
45
45
  */
46
46
  export const onActiveTrackChanged =
47
47
  NativeUpdatedValue.emitterize<PlaybackActiveTrackChangedEvent>(
@@ -156,7 +156,7 @@ export function getQueue(): Track[] {
156
156
  /**
157
157
  * Subscribes to playback queue ended events.
158
158
  * @param callback - Called when playback has paused due to reaching the end of the queue
159
- * @returns Cleanup function to unsubscribe
159
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
160
160
  */
161
161
  export const onQueueEnded =
162
162
  NativeUpdatedValue.emitterize<PlaybackQueueEndedEvent>(
@@ -167,7 +167,7 @@ export const onQueueEnded =
167
167
  * Subscribes to queue change events.
168
168
  * Called when tracks are added, removed, reordered, or when track metadata changes.
169
169
  * @param callback - Called with the updated queue
170
- * @returns Cleanup function to unsubscribe
170
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
171
171
  */
172
172
  export const onQueueChanged = NativeUpdatedValue.emitterize<Track[]>(
173
173
  (cb) => (nativeBrowser.onPlaybackQueueChanged = cb)
@@ -40,7 +40,7 @@ export interface RepeatModeChangedEvent {
40
40
  /**
41
41
  * Subscribes to repeat mode changes.
42
42
  * @param callback - Called when repeat mode changes
43
- * @returns Cleanup function to unsubscribe
43
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
44
44
  */
45
45
  export const onRepeatModeChanged =
46
46
  NativeUpdatedValue.emitterize<RepeatModeChangedEvent>(
@@ -32,7 +32,7 @@ export function toggleShuffle(): void {
32
32
  /**
33
33
  * Subscribes to shuffle mode changes.
34
34
  * @param callback - Called when shuffle mode changes
35
- * @returns Cleanup function to unsubscribe
35
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
36
36
  */
37
37
  export const onShuffleChanged = NativeUpdatedValue.emitterize<boolean>(
38
38
  (cb) => (nativeBrowser.onPlaybackShuffleModeChanged = cb)
@@ -53,90 +53,11 @@ export interface RemoteSkipEvent {
53
53
  index: number
54
54
  }
55
55
 
56
- // MARK: - Default Handlers
57
-
58
- // Install remote control handlers with default behavior immediately when module loads
59
- // Custom handlers can override the defaults using handleRemote* functions
60
-
61
- // Basic playback controls
62
- // AudioBrowser.onRemotePlay(() => {
63
- // const customHandler = customHandlers.get('play')
64
- // if (customHandler) {
65
- // customHandler()
66
- // } else {
67
- // play()
68
- // }
69
- // })
70
-
71
- // AudioBrowser.onRemotePause(() => {
72
- // const customHandler = customHandlers.get('pause')
73
- // if (customHandler) {
74
- // customHandler()
75
- // } else {
76
- // pause()
77
- // }
78
- // })
79
-
80
- // AudioBrowser.onRemoteNext(() => {
81
- // const customHandler = customHandlers.get('next')
82
- // if (customHandler) {
83
- // customHandler()
84
- // } else {
85
- // skipToNext()
86
- // }
87
- // })
88
-
89
- // AudioBrowser.onRemotePrevious(() => {
90
- // const customHandler = customHandlers.get('previous')
91
- // if (customHandler) {
92
- // customHandler()
93
- // } else {
94
- // skipToPrevious()
95
- // }
96
- // })
97
-
98
- // AudioBrowser.onRemoteStop(() => {
99
- // const customHandler = customHandlers.get('stop')
100
- // if (customHandler) {
101
- // customHandler()
102
- // } else {
103
- // stop()
104
- // }
105
- // })
106
-
107
- // // Seek controls
108
- // AudioBrowser.onRemoteSeek((event: any) => {
109
- // const customHandler = customHandlers.get('seek')
110
- // if (customHandler) {
111
- // customHandler(event)
112
- // } else {
113
- // seekTo(event.position)
114
- // }
115
- // })
116
-
117
- // AudioBrowser.onRemoteJumpForward((event: any) => {
118
- // const customHandler = customHandlers.get('jumpForward')
119
- // if (customHandler) {
120
- // customHandler(event)
121
- // } else {
122
- // seekBy(event.interval)
123
- // }
124
- // })
125
-
126
- // AudioBrowser.onRemoteJumpBackward((event: any) => {
127
- // const customHandler = customHandlers.get('jumpBackward')
128
- // if (customHandler) {
129
- // customHandler(event)
130
- // } else {
131
- // seekBy(-event.interval)
132
- // }
133
- // })
134
-
135
56
  // MARK: - Handler Override Functions
136
57
  //
137
58
  // Use these functions when you want to OVERRIDE the default remote control behavior.
138
59
  // These will replace the default handlers with your custom logic.
139
- // If you just want to listen to events for debugging/logging, use the onRemote* functions below.
60
+ // If you just want to listen to events for debugging/logging, use the onRemote* emitters below.
140
61
 
141
62
  /**
142
63
  * Sets a custom handler for remote play events, overriding the default behavior.
@@ -210,7 +131,7 @@ export function handleRemoteJumpBackward(
210
131
 
211
132
  // MARK: - Event Callbacks (for listening/debugging only)
212
133
  //
213
- // Use these functions when you want to LISTEN to remote control events without overriding
134
+ // Use these emitters when you want to LISTEN to remote control events without overriding
214
135
  // the default behavior. These are perfect for logging, analytics, or debugging.
215
136
  // Multiple listeners can be registered for the same event.
216
137
  // To override the default behavior, use the handleRemote* functions above.
@@ -218,7 +139,7 @@ export function handleRemoteJumpBackward(
218
139
  /**
219
140
  * Subscribes to remote jump backward events.
220
141
  * @param callback - Called when the user presses the jump backward button
221
- * @returns Cleanup function to unsubscribe
142
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
222
143
  */
223
144
  export const onRemoteJumpBackward =
224
145
  LazyNativeEmitter.emitterize<RemoteJumpBackwardEvent>(
@@ -228,7 +149,7 @@ export const onRemoteJumpBackward =
228
149
  /**
229
150
  * Subscribes to remote jump forward events.
230
151
  * @param callback - Called when the user presses the jump forward button
231
- * @returns Cleanup function to unsubscribe
152
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
232
153
  */
233
154
  export const onRemoteJumpForward =
234
155
  LazyNativeEmitter.emitterize<RemoteJumpForwardEvent>(
@@ -238,7 +159,7 @@ export const onRemoteJumpForward =
238
159
  /**
239
160
  * Subscribes to remote next events.
240
161
  * @param callback - Called when the user presses the next track button
241
- * @returns Cleanup function to unsubscribe
162
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
242
163
  */
243
164
  export const onRemoteNext = LazyNativeEmitter.emitterize<void>(
244
165
  (cb) => (nativeBrowser.onRemoteNext = cb)
@@ -247,7 +168,7 @@ export const onRemoteNext = LazyNativeEmitter.emitterize<void>(
247
168
  /**
248
169
  * Subscribes to remote pause events.
249
170
  * @param callback - Called when the user presses the pause button
250
- * @returns Cleanup function to unsubscribe
171
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
251
172
  */
252
173
  export const onRemotePause = LazyNativeEmitter.emitterize<void>(
253
174
  (cb) => (nativeBrowser.onRemotePause = cb)
@@ -256,7 +177,7 @@ export const onRemotePause = LazyNativeEmitter.emitterize<void>(
256
177
  /**
257
178
  * Subscribes to remote play events.
258
179
  * @param callback - Called when the user presses the play button
259
- * @returns Cleanup function to unsubscribe
180
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
260
181
  */
261
182
  export const onRemotePlay = LazyNativeEmitter.emitterize<void>(
262
183
  (cb) => (nativeBrowser.onRemotePlay = cb)
@@ -265,7 +186,7 @@ export const onRemotePlay = LazyNativeEmitter.emitterize<void>(
265
186
  /**
266
187
  * Subscribes to remote play ID events (Android only).
267
188
  * @param callback - Called when the user selects a track from an external device
268
- * @returns Cleanup function to unsubscribe
189
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
269
190
  */
270
191
  export const onRemotePlayId = LazyNativeEmitter.emitterize<RemotePlayIdEvent>(
271
192
  (cb) => (nativeBrowser.onRemotePlayId = cb)
@@ -274,7 +195,7 @@ export const onRemotePlayId = LazyNativeEmitter.emitterize<RemotePlayIdEvent>(
274
195
  /**
275
196
  * Subscribes to remote play search events (Android only).
276
197
  * @param callback - Called when the user searches for a track (usually voice search)
277
- * @returns Cleanup function to unsubscribe
198
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
278
199
  */
279
200
  export const onRemotePlaySearch =
280
201
  LazyNativeEmitter.emitterize<RemotePlaySearchEvent>(
@@ -284,7 +205,7 @@ export const onRemotePlaySearch =
284
205
  /**
285
206
  * Subscribes to remote previous events.
286
207
  * @param callback - Called when the user presses the previous track button
287
- * @returns Cleanup function to unsubscribe
208
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
288
209
  */
289
210
  export const onRemotePrevious = LazyNativeEmitter.emitterize<void>(
290
211
  (cb) => (nativeBrowser.onRemotePrevious = cb)
@@ -293,7 +214,7 @@ export const onRemotePrevious = LazyNativeEmitter.emitterize<void>(
293
214
  /**
294
215
  * Subscribes to remote seek events.
295
216
  * @param callback - Called when the user changes the position of the timeline
296
- * @returns Cleanup function to unsubscribe
217
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
297
218
  */
298
219
  export const onRemoteSeek = LazyNativeEmitter.emitterize<RemoteSeekEvent>(
299
220
  (cb) => (nativeBrowser.onRemoteSeek = cb)
@@ -302,7 +223,7 @@ export const onRemoteSeek = LazyNativeEmitter.emitterize<RemoteSeekEvent>(
302
223
  /**
303
224
  * Subscribes to remote skip events (Android only).
304
225
  * @param callback - Called when the user presses the skip button
305
- * @returns Cleanup function to unsubscribe
226
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
306
227
  */
307
228
  export const onRemoteSkip = LazyNativeEmitter.emitterize<RemoteSkipEvent>(
308
229
  (cb) => (nativeBrowser.onRemoteSkip = cb)
@@ -311,7 +232,7 @@ export const onRemoteSkip = LazyNativeEmitter.emitterize<RemoteSkipEvent>(
311
232
  /**
312
233
  * Subscribes to remote stop events.
313
234
  * @param callback - Called when the user presses the stop button
314
- * @returns Cleanup function to unsubscribe
235
+ * @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
315
236
  */
316
237
  export const onRemoteStop = LazyNativeEmitter.emitterize<void>(
317
238
  (cb) => (nativeBrowser.onRemoteStop = cb)