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

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 (416) hide show
  1. package/README.md +14 -4
  2. package/android/build.gradle +20 -1
  3. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
  5. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  6. package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
  7. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -58
  8. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  9. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
  10. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -7
  11. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  12. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  13. package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
  14. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  15. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  16. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +109 -74
  17. package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
  18. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  19. package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
  20. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  21. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  22. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
  23. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
  24. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
  25. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  26. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
  27. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  28. package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
  29. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  30. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
  31. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  32. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
  33. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  34. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  35. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  36. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  37. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  38. package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
  39. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  40. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  41. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  42. package/ios/Browser/BrowserConfig.swift +102 -5
  43. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  44. package/ios/Browser/BrowserManager.swift +229 -180
  45. package/ios/Browser/BrowserPathHelper.swift +44 -44
  46. package/ios/Browser/InertStyleDiagnostic.swift +279 -0
  47. package/ios/Browser/JsonModels.swift +258 -106
  48. package/ios/Browser/SearchDrillIn.swift +6 -6
  49. package/ios/Browser/SectionPresentation.swift +64 -0
  50. package/ios/Browser/SectionScope.swift +58 -24
  51. package/ios/Browser/StyleResolver.swift +58 -0
  52. package/ios/Browser/TabBarEntries.swift +9 -3
  53. package/ios/Browser/TrackIdTemplate.swift +37 -0
  54. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  55. package/ios/CarPlay/CarPlayController.swift +227 -85
  56. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  57. package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
  58. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  59. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  60. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  61. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  62. package/ios/Extension/Section+Copying.swift +31 -0
  63. package/ios/Extension/Track+Copying.swift +6 -12
  64. package/ios/Extension/Track+Identity.swift +18 -0
  65. package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
  66. package/ios/Http/HttpClient.swift +8 -2
  67. package/ios/HybridAudioBrowser.swift +166 -45
  68. package/ios/Model/NitroTypeStubs.swift +106 -21
  69. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  70. package/ios/Player/MediaIntentCriteria.swift +13 -5
  71. package/ios/Player/MediaLoader.swift +33 -9
  72. package/ios/Player/NowPlayingUpdater.swift +54 -6
  73. package/ios/Player/PlaybackCoordinator.swift +13 -6
  74. package/ios/Player/PlaybackTimer.swift +8 -2
  75. package/ios/Player/QueueManager.swift +36 -10
  76. package/ios/Player/RetryManager.swift +4 -3
  77. package/ios/Player/ShuffleOrder.swift +23 -0
  78. package/ios/Player/SleepTimerHandling.swift +13 -0
  79. package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
  80. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  81. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  82. package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
  83. package/ios/Tests/JsonModelsTests.swift +150 -1
  84. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  85. package/ios/Tests/QueueManagerTests.swift +72 -6
  86. package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
  87. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  88. package/ios/Tests/SearchDrillInTests.swift +16 -16
  89. package/ios/Tests/SectionPresentationTests.swift +90 -0
  90. package/ios/Tests/SectionScopeTests.swift +87 -55
  91. package/ios/Tests/StyleResolverTests.swift +127 -0
  92. package/ios/Tests/Support/TestTypes.swift +7 -0
  93. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  94. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  95. package/ios/Tests/TrackIdentityTests.swift +20 -0
  96. package/ios/Tests/TrackSelectorTests.swift +168 -33
  97. package/ios/TrackPlayer.swift +57 -34
  98. package/ios/TrackSelector.swift +45 -30
  99. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  100. package/ios/Util/NetworkMonitor.swift +43 -26
  101. package/ios/Util/SFSymbolRenderer.swift +24 -2
  102. package/ios/Util/SVGProcessor.swift +9 -2
  103. package/lib/commonjs/AudioBrowser.js +11 -0
  104. package/lib/commonjs/AudioBrowser.js.map +1 -1
  105. package/lib/commonjs/features/browser-config.js +11 -8
  106. package/lib/commonjs/features/browser-config.js.map +1 -1
  107. package/lib/commonjs/features/browser.js +2 -2
  108. package/lib/commonjs/features/favorites.js +13 -10
  109. package/lib/commonjs/features/favorites.js.map +1 -1
  110. package/lib/commonjs/features/playback/controls.js +4 -2
  111. package/lib/commonjs/features/playback/controls.js.map +1 -1
  112. package/lib/commonjs/features/playback/progress.js +2 -1
  113. package/lib/commonjs/features/playback/progress.js.map +1 -1
  114. package/lib/commonjs/features/playback/rate.js +8 -1
  115. package/lib/commonjs/features/playback/rate.js.map +1 -1
  116. package/lib/commonjs/features/playback/state.js +2 -3
  117. package/lib/commonjs/features/playback/state.js.map +1 -1
  118. package/lib/commonjs/features/player/options.js.map +1 -1
  119. package/lib/commonjs/features/player/setup.js +3 -1
  120. package/lib/commonjs/features/player/setup.js.map +1 -1
  121. package/lib/commonjs/features/player/validateOptions.js +2 -0
  122. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  123. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  124. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  125. package/lib/commonjs/features/queue/queue.js +11 -4
  126. package/lib/commonjs/features/queue/queue.js.map +1 -1
  127. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  128. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  129. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  130. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  131. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  132. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  133. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  134. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  135. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  136. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  137. package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
  138. package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
  139. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  140. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  141. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  142. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  143. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  144. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  145. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  146. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  147. package/lib/commonjs/web/browser/sections.js +89 -0
  148. package/lib/commonjs/web/browser/sections.js.map +1 -0
  149. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  150. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  151. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  152. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  153. package/lib/module/AudioBrowser.js +1 -0
  154. package/lib/module/AudioBrowser.js.map +1 -1
  155. package/lib/module/features/browser-config.js +11 -8
  156. package/lib/module/features/browser-config.js.map +1 -1
  157. package/lib/module/features/browser.js +2 -2
  158. package/lib/module/features/favorites.js +13 -10
  159. package/lib/module/features/favorites.js.map +1 -1
  160. package/lib/module/features/playback/controls.js +4 -2
  161. package/lib/module/features/playback/controls.js.map +1 -1
  162. package/lib/module/features/playback/progress.js +2 -1
  163. package/lib/module/features/playback/progress.js.map +1 -1
  164. package/lib/module/features/playback/rate.js +8 -1
  165. package/lib/module/features/playback/rate.js.map +1 -1
  166. package/lib/module/features/playback/state.js +2 -3
  167. package/lib/module/features/playback/state.js.map +1 -1
  168. package/lib/module/features/player/options.js.map +1 -1
  169. package/lib/module/features/player/setup.js +3 -1
  170. package/lib/module/features/player/setup.js.map +1 -1
  171. package/lib/module/features/player/validateOptions.js +2 -0
  172. package/lib/module/features/player/validateOptions.js.map +1 -1
  173. package/lib/module/features/queue/activeTrack.js +14 -3
  174. package/lib/module/features/queue/activeTrack.js.map +1 -1
  175. package/lib/module/features/queue/queue.js +11 -4
  176. package/lib/module/features/queue/queue.js.map +1 -1
  177. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  178. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  179. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  180. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  181. package/lib/module/utils/getTrackIdentity.js +20 -0
  182. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  183. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  184. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  185. package/lib/module/web/NativeAudioBrowser.js +32 -24
  186. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  187. package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
  188. package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
  189. package/lib/module/web/browser/BrowserManager.js +144 -87
  190. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  191. package/lib/module/web/browser/FavoriteManager.js +31 -21
  192. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  193. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  194. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  195. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  196. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  197. package/lib/module/web/browser/sections.js +83 -0
  198. package/lib/module/web/browser/sections.js.map +1 -0
  199. package/lib/module/web/player/NowPlayingManager.js +4 -3
  200. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  201. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  202. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  203. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  204. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  205. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  206. package/lib/typescript/src/features/browser.d.ts +2 -2
  207. package/lib/typescript/src/features/favorites.d.ts +13 -10
  208. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  209. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  210. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  211. package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
  212. package/lib/typescript/src/features/playback/rate.d.ts +8 -1
  213. package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
  214. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  215. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  216. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  217. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  218. package/lib/typescript/src/features/player/options.d.ts +21 -19
  219. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  220. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  221. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  222. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  223. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  224. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  225. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  226. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  227. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  228. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  229. package/lib/typescript/src/types/browser-nodes.d.ts +361 -126
  230. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  231. package/lib/typescript/src/types/browser.d.ts +33 -53
  232. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  233. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  234. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  235. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  236. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  237. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  238. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  239. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  240. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  241. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  242. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  243. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
  244. package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
  245. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  246. package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
  247. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
  248. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
  249. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
  250. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
  251. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
  252. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
  253. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
  254. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
  255. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
  256. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
  257. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  258. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
  259. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
  260. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
  261. package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
  262. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
  263. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  264. package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
  265. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
  266. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
  267. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
  268. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -7
  269. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  270. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  271. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  272. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
  273. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  274. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
  275. package/nitrogen/generated/android/c++/JSection.hpp +120 -0
  276. package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
  277. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  278. package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
  279. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
  280. package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
  281. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
  282. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  283. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  284. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
  285. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
  286. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  287. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +18 -6
  293. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
  297. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
  298. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
  299. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
  300. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  301. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  302. package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
  303. package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
  304. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  305. package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
  306. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  307. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  308. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  309. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  310. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  311. package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
  312. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  313. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  314. package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
  315. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  316. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  317. package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
  318. package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
  319. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  320. package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
  321. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  322. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  323. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  324. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  325. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  326. package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
  327. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  328. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  329. package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
  330. package/package.json +7 -2
  331. package/src/AudioBrowser.ts +1 -0
  332. package/src/features/browser-config.test.ts +21 -6
  333. package/src/features/browser-config.ts +11 -8
  334. package/src/features/browser.test.ts +1 -1
  335. package/src/features/browser.ts +2 -2
  336. package/src/features/favorites.ts +13 -10
  337. package/src/features/hook-wiring.test.ts +63 -15
  338. package/src/features/playback/controls.ts +4 -2
  339. package/src/features/playback/progress.ts +2 -1
  340. package/src/features/playback/rate.ts +8 -1
  341. package/src/features/playback/state.ts +2 -3
  342. package/src/features/playback/trackPlaybackTime.ts +1 -1
  343. package/src/features/player/options.ts +27 -19
  344. package/src/features/player/setup.test.ts +12 -3
  345. package/src/features/player/setup.ts +3 -1
  346. package/src/features/player/validateOptions.ts +2 -0
  347. package/src/features/queue/activeTrack.ts +14 -3
  348. package/src/features/queue/queue.ts +11 -4
  349. package/src/nitro-stub-drift.test.ts +40 -0
  350. package/src/public-surface.test.ts +40 -0
  351. package/src/specs/audio-browser.nitro.ts +4 -3
  352. package/src/style-resolution.test.ts +210 -0
  353. package/src/types/browser-native.ts +2 -2
  354. package/src/types/browser-nodes.ts +377 -135
  355. package/src/types/browser.ts +34 -55
  356. package/src/utils/LazyNativeEmitter.ts +20 -10
  357. package/src/utils/NativeUpdatedValue.ts +29 -13
  358. package/src/utils/getTrackIdentity.ts +20 -0
  359. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  360. package/src/web/NativeAudioBrowser.ts +37 -22
  361. package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
  362. package/src/web/TrackPlayer/QueueManager.ts +14 -0
  363. package/src/web/browser/BrowserManager.ts +176 -95
  364. package/src/web/browser/FavoriteManager.test.ts +84 -0
  365. package/src/web/browser/FavoriteManager.ts +28 -23
  366. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  367. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  368. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  369. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  370. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  371. package/src/web/browser/sections.test.ts +91 -0
  372. package/src/web/browser/sections.ts +72 -0
  373. package/src/web/player/NowPlayingManager.ts +4 -3
  374. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  375. package/src/web/util/BrowserPathHelper.ts +61 -13
  376. package/website/guide/android-auto.md +114 -0
  377. package/website/guide/android-certificates.md +409 -0
  378. package/website/guide/artwork.md +255 -0
  379. package/website/guide/audio-output.md +135 -0
  380. package/website/guide/automotive.md +86 -0
  381. package/website/guide/basic-usage.md +151 -0
  382. package/website/guide/battery.md +115 -0
  383. package/website/guide/browser.md +581 -0
  384. package/website/guide/carplay.md +620 -0
  385. package/website/guide/configuration.md +173 -0
  386. package/website/guide/equalizer.md +231 -0
  387. package/website/guide/errors.md +310 -0
  388. package/website/guide/favorites.md +154 -0
  389. package/website/guide/gate.md +143 -0
  390. package/website/guide/getting-started.md +46 -0
  391. package/website/guide/hooks.md +238 -0
  392. package/website/guide/live-streams.md +125 -0
  393. package/website/guide/metadata.md +239 -0
  394. package/website/guide/migrating-from-track-player.md +244 -0
  395. package/website/guide/native-callback-fetch.md +127 -0
  396. package/website/guide/network.md +86 -0
  397. package/website/guide/now-playing.md +175 -0
  398. package/website/guide/playback.md +328 -0
  399. package/website/guide/queue.md +300 -0
  400. package/website/guide/remote-controls.md +256 -0
  401. package/website/guide/search.md +255 -0
  402. package/website/guide/sleep-timer.md +268 -0
  403. package/website/guide/track.md +246 -0
  404. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  405. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  406. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  407. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  408. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  409. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  410. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  411. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  412. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  413. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  414. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  415. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  416. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -0,0 +1,154 @@
1
+ package com.audiobrowser.browser
2
+
3
+ import com.audiobrowser.TestFixtures.sectionStyle
4
+ import com.audiobrowser.TestFixtures.trackStyle
5
+ import com.margelo.nitro.audiobrowser.ArtworkRendering
6
+ import com.margelo.nitro.audiobrowser.CardImage
7
+ import com.margelo.nitro.audiobrowser.GridTile
8
+ import com.margelo.nitro.audiobrowser.ImageShape
9
+ import com.margelo.nitro.audiobrowser.StyleDisplay
10
+ import org.junit.Assert.assertEquals
11
+ import org.junit.Assert.assertNull
12
+ import org.junit.Test
13
+
14
+ /**
15
+ * Resolution semantics (ADR 0011): container properties and `display` resolve by scope override
16
+ * (`section ?? page`), inherited item properties resolve `track ?? section ?? page` — and `display`
17
+ * never inherits onto an item.
18
+ */
19
+ class StyleResolverTest {
20
+
21
+ @Test
22
+ fun `section overrides the page per property`() {
23
+ val resolved =
24
+ StyleResolver.sectionStyle(
25
+ section =
26
+ sectionStyle(display = StyleDisplay.LIST, artworkRendering = null, gridWrap = null),
27
+ page =
28
+ sectionStyle(
29
+ display = StyleDisplay.GRID,
30
+ artworkRendering = ArtworkRendering.STENCIL,
31
+ gridWrap = false,
32
+ ),
33
+ )
34
+ // Declared on the section: wins. Undeclared: the page's scope-wide value.
35
+ assertEquals(StyleDisplay.LIST, resolved.display)
36
+ assertEquals(ArtworkRendering.STENCIL, resolved.artworkRendering)
37
+ assertEquals(false, resolved.gridWrap)
38
+ }
39
+
40
+ @Test
41
+ fun `a styleless section takes the page block`() {
42
+ val resolved =
43
+ StyleResolver.sectionStyle(
44
+ section = null,
45
+ page = sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = true),
46
+ )
47
+ assertEquals(StyleDisplay.GRID, resolved.display)
48
+ assertEquals(true, resolved.gridWrap)
49
+ }
50
+
51
+ @Test
52
+ fun `track overrides the section per property`() {
53
+ val resolved =
54
+ StyleResolver.trackStyle(
55
+ track = trackStyle(display = null, artworkRendering = ArtworkRendering.ORIGINAL),
56
+ section =
57
+ sectionStyle(
58
+ display = StyleDisplay.GRID,
59
+ artworkRendering = ArtworkRendering.STENCIL,
60
+ gridWrap = null,
61
+ ),
62
+ )
63
+ assertEquals(ArtworkRendering.ORIGINAL, resolved.artworkRendering)
64
+ }
65
+
66
+ @Test
67
+ fun `a styleless track inherits the section value`() {
68
+ val resolved =
69
+ StyleResolver.trackStyle(
70
+ track = null,
71
+ section =
72
+ sectionStyle(display = null, artworkRendering = ArtworkRendering.STENCIL, gridWrap = null),
73
+ )
74
+ assertEquals(ArtworkRendering.STENCIL, resolved.artworkRendering)
75
+ }
76
+
77
+ @Test
78
+ fun `display never inherits onto an item`() {
79
+ // The track's own display is the page promise for the page IT opens; the
80
+ // section's describes its children's layout. Neither may surface as the
81
+ // item's resolved display.
82
+ val resolved =
83
+ StyleResolver.trackStyle(
84
+ track = trackStyle(display = StyleDisplay.GRID, artworkRendering = null),
85
+ section =
86
+ sectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = null),
87
+ )
88
+ assertNull(resolved.display)
89
+ }
90
+
91
+ @Test
92
+ fun `imageShape inherits and overrides per item`() {
93
+ // Section-wide circular (an artists shelf); one album overrides.
94
+ val inherited =
95
+ StyleResolver.trackStyle(
96
+ track = null,
97
+ section = sectionStyle(imageShape = ImageShape.CIRCULAR),
98
+ )
99
+ assertEquals(ImageShape.CIRCULAR, inherited.imageShape)
100
+ val overridden =
101
+ StyleResolver.trackStyle(
102
+ track = trackStyle(imageShape = ImageShape.ROUNDED_RECTANGLE),
103
+ section = sectionStyle(imageShape = ImageShape.CIRCULAR),
104
+ )
105
+ assertEquals(ImageShape.ROUNDED_RECTANGLE, overridden.imageShape)
106
+ }
107
+
108
+ @Test
109
+ fun `accessorySymbol inherits and 'none' resolves as a value`() {
110
+ val inherited =
111
+ StyleResolver.trackStyle(track = null, section = sectionStyle(accessorySymbol = "lock.fill"))
112
+ assertEquals("lock.fill", inherited.accessorySymbol)
113
+ // 'none' is the inheritance escape — it must survive resolution intact
114
+ // (the renderer, not the resolver, maps it to "no accessory").
115
+ val escaped =
116
+ StyleResolver.trackStyle(
117
+ track = trackStyle(accessorySymbol = "none"),
118
+ section = sectionStyle(accessorySymbol = "lock.fill"),
119
+ )
120
+ assertEquals("none", escaped.accessorySymbol)
121
+ }
122
+
123
+ @Test
124
+ fun `gridTile resolves by scope override`() {
125
+ // A page-wide card treatment; one section opts back to plain tiles.
126
+ val inherited =
127
+ StyleResolver.sectionStyle(section = null, page = sectionStyle(gridTile = GridTile.CARD))
128
+ assertEquals(GridTile.CARD, inherited.gridTile)
129
+ val overridden =
130
+ StyleResolver.sectionStyle(
131
+ section = sectionStyle(gridTile = GridTile.PLAIN),
132
+ page = sectionStyle(gridTile = GridTile.CARD),
133
+ )
134
+ assertEquals(GridTile.PLAIN, overridden.gridTile)
135
+ }
136
+
137
+ @Test
138
+ fun `card properties inherit and override per item`() {
139
+ val inherited =
140
+ StyleResolver.trackStyle(
141
+ track = null,
142
+ section = sectionStyle(cardTint = "#1e3a8a", cardImage = CardImage.BACKGROUND),
143
+ )
144
+ assertEquals("#1e3a8a", inherited.cardTint)
145
+ assertEquals(CardImage.BACKGROUND, inherited.cardImage)
146
+ val overridden =
147
+ StyleResolver.trackStyle(
148
+ track = trackStyle(cardImage = CardImage.NORMAL),
149
+ section = sectionStyle(cardTint = "#1e3a8a", cardImage = CardImage.BACKGROUND),
150
+ )
151
+ assertEquals(CardImage.NORMAL, overridden.cardImage)
152
+ assertEquals("#1e3a8a", overridden.cardTint)
153
+ }
154
+ }
@@ -0,0 +1,43 @@
1
+ package com.audiobrowser.extension
2
+
3
+ import com.audiobrowser.TestFixtures.track
4
+ import com.audiobrowser.util.BrowserPathHelper
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Test
7
+ import org.junit.runner.RunWith
8
+ import org.robolectric.RobolectricTestRunner
9
+
10
+ /**
11
+ * The skip-in-place queue match (ADR 0009): exact contextual-path equality pins the tapped copy;
12
+ * the identity match remains for index-less paths; an index-stamped path with no exact match
13
+ * misses, so the caller re-expands. Robolectric for `android.net.Uri` (contextual-path parsing).
14
+ */
15
+ @RunWith(RobolectricTestRunner::class)
16
+ class TappedTrackTest {
17
+
18
+ private fun stamped(id: String, index: Int) = BrowserPathHelper.build("/page", id, index)
19
+
20
+ private val queue =
21
+ arrayOf(
22
+ track(src = "a").copy(path = stamped("a", 0)),
23
+ track(src = "b").copy(path = stamped("b", 1)),
24
+ track(src = "a").copy(path = stamped("a", 2)),
25
+ )
26
+
27
+ @Test
28
+ fun `exact path match pins the tapped copy of a duplicate identity`() {
29
+ assertEquals(2, queue.indexOfTappedTrack(stamped("a", 2), "a"))
30
+ }
31
+
32
+ @Test
33
+ fun `index-less path falls back to the identity match`() {
34
+ assertEquals(0, queue.indexOfTappedTrack(BrowserPathHelper.build("/page", "a"), "a"))
35
+ }
36
+
37
+ @Test
38
+ fun `index-stamped path with no exact match misses instead of identity-skipping`() {
39
+ // A tap in another section whose identity also sits in this queue: the
40
+ // caller must re-expand (re-scoping to the tapped section), not skip.
41
+ assertEquals(-1, queue.indexOfTappedTrack(stamped("a", 7), "a"))
42
+ }
43
+ }
@@ -0,0 +1,41 @@
1
+ package com.audiobrowser.extension
2
+
3
+ import com.audiobrowser.TestFixtures.track
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertNull
6
+ import org.junit.Test
7
+
8
+ /**
9
+ * THE identity rule (ADR 0008): a track's identity is its `id` when non-blank, else its `src`.
10
+ * Everything that compares tracks — favorites, section scoping, skip-in-place, `__trackId` — goes
11
+ * through this extension.
12
+ */
13
+ class TrackIdentityTest {
14
+
15
+ @Test
16
+ fun `id wins over src`() {
17
+ assertEquals("stable-id", track(id = "stable-id", src = "https://s/a.mp3").identity)
18
+ }
19
+
20
+ @Test
21
+ fun `blank id falls back to src`() {
22
+ assertEquals("https://s/a.mp3", track(id = "", src = "https://s/a.mp3").identity)
23
+ assertEquals("https://s/a.mp3", track(id = " ", src = "https://s/a.mp3").identity)
24
+ }
25
+
26
+ @Test
27
+ fun `missing id falls back to src`() {
28
+ assertEquals("https://s/a.mp3", track(id = null, src = "https://s/a.mp3").identity)
29
+ }
30
+
31
+ @Test
32
+ fun `id alone is an identity`() {
33
+ assertEquals("stable-id", track(id = "stable-id", src = null).identity)
34
+ }
35
+
36
+ @Test
37
+ fun `neither id nor src means no identity`() {
38
+ assertNull(track(id = null, src = null).identity)
39
+ assertNull(track(id = "", src = null).identity)
40
+ }
41
+ }
@@ -3,7 +3,6 @@ package com.audiobrowser.player
3
3
  import com.margelo.nitro.audiobrowser.PlayerCapabilities
4
4
  import com.margelo.nitro.audiobrowser.RemoteButton
5
5
  import com.margelo.nitro.audiobrowser.RemoteButtonLayout
6
- import com.margelo.nitro.audiobrowser.Variant_Boolean_FavoriteConfig
7
6
  import org.junit.Assert.assertEquals
8
7
  import org.junit.Assert.assertFalse
9
8
  import org.junit.Assert.assertTrue
@@ -36,7 +35,7 @@ class CapabilityControlsTest {
36
35
  skipToPrevious = skipToPrevious,
37
36
  jumpForward = jumpForward,
38
37
  jumpBackward = jumpBackward,
39
- favorite = favorite?.let { Variant_Boolean_FavoriteConfig.First(it) },
38
+ favorite = favorite,
40
39
  shuffleMode = null,
41
40
  repeatMode = null,
42
41
  playbackRate = null,
@@ -377,7 +377,7 @@ class NowPlayingUpdaterTest {
377
377
  }
378
378
 
379
379
  @Test
380
- fun `artwork re-resolves for a new track id and skips without an id`() = runTest {
380
+ fun `artwork re-resolves for a new track identity including id-less tracks`() = runTest {
381
381
  val surface =
382
382
  FakeSurface().apply {
383
383
  currentTrack = track("A", id = "a")
@@ -393,10 +393,31 @@ class NowPlayingUpdaterTest {
393
393
  runCurrent()
394
394
  assertEquals(2, surface.artworkResolveCount)
395
395
 
396
+ // The config runs for id-less tracks too — no blanket skip.
396
397
  surface.currentTrack = track("C", id = null, src = "https://s/c.mp3")
397
398
  updater.render()
398
399
  runCurrent()
399
- assertEquals(2, surface.artworkResolveCount) // no id → no resolve
400
+ assertEquals(3, surface.artworkResolveCount)
401
+ assertEquals(0 to "https://img/a.png", surface.artworkStamps.last())
402
+ }
403
+
404
+ @Test
405
+ fun `artwork for an id-less track resolves once keyed on src identity`() = runTest {
406
+ val surface =
407
+ FakeSurface().apply {
408
+ currentTrack = track("C", id = null, src = "https://s/c.mp3")
409
+ hasNowPlayingArtworkConfig = true
410
+ artworkResult = "https://img/c.png"
411
+ }
412
+ val updater = NowPlayingUpdater(surface, backgroundScope)
413
+
414
+ updater.render()
415
+ runCurrent()
416
+ updater.updateNowPlaying(update(title = "Song")) // re-render, same track
417
+ runCurrent()
418
+
419
+ assertEquals(1, surface.artworkResolveCount)
420
+ assertEquals(listOf(0 to "https://img/c.png"), surface.artworkStamps)
400
421
  }
401
422
 
402
423
  @Test
@@ -439,4 +460,23 @@ class NowPlayingUpdaterTest {
439
460
 
440
461
  assertTrue(surface.artworkStamps.isEmpty())
441
462
  }
463
+
464
+ @Test
465
+ fun `a stale artwork result for an id-less track is not stamped after a track change`() =
466
+ runTest {
467
+ val surface =
468
+ FakeSurface().apply {
469
+ currentTrack = track("A", id = null, src = "https://s/a.mp3")
470
+ hasNowPlayingArtworkConfig = true
471
+ artworkResult = "https://img/a.png"
472
+ }
473
+ val updater = NowPlayingUpdater(surface, backgroundScope)
474
+
475
+ updater.render() // schedules the artwork resolve
476
+ // Skip to another id-less track before it lands: identity (src) keying must catch it.
477
+ surface.currentTrack = track("B", id = null, src = "https://s/b.mp3")
478
+ runCurrent()
479
+
480
+ assertTrue(surface.artworkStamps.isEmpty())
481
+ }
442
482
  }
@@ -0,0 +1,55 @@
1
+ package com.audiobrowser.player
2
+
3
+ import com.audiobrowser.TestFixtures
4
+ import com.audiobrowser.TestFixtures.trackStyle
5
+ import com.margelo.nitro.audiobrowser.ArtworkRendering
6
+ import com.margelo.nitro.audiobrowser.StyleDisplay
7
+ import org.junit.Assert.assertEquals
8
+ import org.junit.Assert.assertNull
9
+ import org.junit.Test
10
+ import org.junit.runner.RunWith
11
+ import org.robolectric.RobolectricTestRunner
12
+
13
+ /**
14
+ * The bespoke org.json persistence of the resumption track. ADR 0011: the style block and
15
+ * `disabled` survive process death (a restored queue keeps its visible fidelity), and a snapshot
16
+ * written before the block model — `style` as an enum-name string — restores without a declaration
17
+ * rather than failing.
18
+ */
19
+ @RunWith(RobolectricTestRunner::class)
20
+ class PlaybackStateStoreTrackJsonTest {
21
+
22
+ @Test
23
+ fun `style and disabled survive the round-trip`() {
24
+ val track =
25
+ TestFixtures.track(title = "Night Mix", src = "https://s/a.mp3", disabled = true)
26
+ .copy(
27
+ style =
28
+ trackStyle(display = StyleDisplay.GRID, artworkRendering = ArtworkRendering.STENCIL)
29
+ )
30
+
31
+ val restored = PlaybackStateStore.trackFromJson(PlaybackStateStore.trackToJson(track))
32
+
33
+ assertEquals(StyleDisplay.GRID, restored?.style?.display)
34
+ assertEquals(ArtworkRendering.STENCIL, restored?.style?.artworkRendering)
35
+ assertEquals(true, restored?.disabled)
36
+ }
37
+
38
+ @Test
39
+ fun `a track without style round-trips without one`() {
40
+ val track = TestFixtures.track(title = "Plain", src = "https://s/a.mp3")
41
+
42
+ val restored = PlaybackStateStore.trackFromJson(PlaybackStateStore.trackToJson(track))
43
+
44
+ assertNull(restored?.style)
45
+ assertNull(restored?.disabled)
46
+ }
47
+
48
+ @Test
49
+ fun `a pre-block snapshot with a string style restores without a declaration`() {
50
+ val restored = PlaybackStateStore.trackFromJson("""{"title":"X","src":"s","style":"LIST"}""")
51
+
52
+ assertEquals("X", restored?.title)
53
+ assertNull(restored?.style)
54
+ }
55
+ }
@@ -0,0 +1,79 @@
1
+ package com.audiobrowser.player
2
+
3
+ import androidx.media3.common.C
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertNotEquals
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Test
8
+
9
+ /**
10
+ * Media3's `ShuffleOrder.cloneAndSet(insertionCount, startIndex)` discards the start index (the
11
+ * interface default clears and re-inserts from 0, and `DefaultShuffleOrder` does not override it),
12
+ * so `setMediaItems(items, startIndex, …)` leaves the starting track at a random shuffle position —
13
+ * last, for a 1-in-N share of queues, which reads as end-of-queue from the very first track.
14
+ */
15
+ class ShuffleOrderLedByTest {
16
+ @Test
17
+ fun `the start index leads the order`() {
18
+ for (start in 0 until 6) {
19
+ val order = shuffleOrderLedBy(startIndex = start, count = 6)
20
+ assertEquals("start index must be at shuffle position 0", start, order.firstIndex)
21
+ assertNotEquals(
22
+ "a queue of 6 must have somewhere to advance to from its start track",
23
+ C.INDEX_UNSET.toLong(),
24
+ order.getNextIndex(start).toLong(),
25
+ )
26
+ }
27
+ }
28
+
29
+ @Test
30
+ fun `every index appears exactly once`() {
31
+ val order = shuffleOrderLedBy(startIndex = 3, count = 8)
32
+ assertEquals(8, order.length)
33
+ val seen = mutableListOf<Int>()
34
+ var index = order.firstIndex
35
+ while (index != C.INDEX_UNSET) {
36
+ seen.add(index)
37
+ index = order.getNextIndex(index)
38
+ }
39
+ assertEquals((0 until 8).toList(), seen.sorted())
40
+ }
41
+
42
+ @Test
43
+ fun `a single-track queue leads with its only track and ends there`() {
44
+ val order = shuffleOrderLedBy(startIndex = 0, count = 1)
45
+ assertEquals(0, order.firstIndex)
46
+ assertEquals(C.INDEX_UNSET, order.getNextIndex(0))
47
+ }
48
+
49
+ @Test
50
+ fun `an out-of-range start index is clamped rather than throwing`() {
51
+ assertEquals(4, shuffleOrderLedBy(startIndex = 99, count = 5).firstIndex)
52
+ assertEquals(0, shuffleOrderLedBy(startIndex = -3, count = 5).firstIndex)
53
+ }
54
+
55
+ @Test
56
+ fun `an empty queue produces an empty order`() {
57
+ assertEquals(0, shuffleOrderLedBy(startIndex = 0, count = 0).length)
58
+ }
59
+
60
+ @Test
61
+ fun `the tail is actually shuffled`() {
62
+ // Not a strict guarantee of any single draw — but across many draws an
63
+ // unshuffled tail would be impossible to miss.
64
+ val tails =
65
+ (0 until 50)
66
+ .map { shuffleOrderLedBy(startIndex = 0, count = 8) }
67
+ .map { order ->
68
+ buildList {
69
+ var index = order.getNextIndex(order.firstIndex)
70
+ while (index != C.INDEX_UNSET) {
71
+ add(index)
72
+ index = order.getNextIndex(index)
73
+ }
74
+ }
75
+ }
76
+ .toSet()
77
+ assertTrue("50 draws produced one identical tail — the order is not shuffled", tails.size > 1)
78
+ }
79
+ }
@@ -1,28 +1,53 @@
1
1
  package com.audiobrowser.util
2
2
 
3
- import org.junit.Assert.assertFalse
4
- import org.junit.Assert.assertTrue
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Assert.assertNull
5
5
  import org.junit.Test
6
+ import org.junit.runner.RunWith
7
+ import org.robolectric.RobolectricTestRunner
6
8
 
9
+ @RunWith(RobolectricTestRunner::class)
7
10
  class BrowserPathHelperTest {
8
11
 
9
12
  @Test
10
- fun `containsSegment matches a uid as a full path segment`() {
11
- val path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
12
- assertTrue(BrowserPathHelper.containsSegment(path, "Gw0LGB8j"))
13
- assertTrue(BrowserPathHelper.containsSegment(path, "amsterdam-funk-channel"))
14
- assertTrue(BrowserPathHelper.containsSegment(path, "listen"))
15
- assertTrue(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j?hl=en", "Gw0LGB8j"))
16
- assertTrue(BrowserPathHelper.containsSegment("/listen/x/Gw0LGB8j#frag", "Gw0LGB8j"))
13
+ fun `build round-trips a src carrying its own query params`() {
14
+ // A src carrying its own query string (signed CDN URL) must survive the
15
+ // build → extract/strip round-trip: an unescaped `&` would split the src
16
+ // into stray query params, truncating the trackId and polluting the
17
+ // parent path.
18
+ val src = "https://cdn.example.com/stream.mp3?token=abc&exp=1699999999"
19
+ val url = BrowserPathHelper.build("/library", src)
20
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
21
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
17
22
  }
18
23
 
19
24
  @Test
20
- fun `containsSegment rejects partial-segment and missing matches`() {
21
- val path = "/listen/amsterdam-funk-channel/Gw0LGB8j"
22
- assertFalse(BrowserPathHelper.containsSegment(path, "Gw0LGB8"))
23
- assertFalse(BrowserPathHelper.containsSegment(path, "funk"))
24
- assertFalse(BrowserPathHelper.containsSegment(path, "msterdam-funk-channe"))
25
- assertFalse(BrowserPathHelper.containsSegment(path, "NopeNope"))
26
- assertFalse(BrowserPathHelper.containsSegment(path, ""))
25
+ fun `build round-trips a src with equals and plus`() {
26
+ val src = "https://cdn.example.com/a+b.mp3?sig=x=y"
27
+ val url = BrowserPathHelper.build("/library", src)
28
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
29
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
30
+ }
31
+
32
+ @Test
33
+ fun `build stamps the index and round-trips it`() {
34
+ val src = "https://cdn.example.com/stream.mp3?token=abc&exp=1"
35
+ val url = BrowserPathHelper.build("/library", src, 3)
36
+ assertEquals(src, BrowserPathHelper.extractTrackId(url))
37
+ assertEquals(3, BrowserPathHelper.extractIndex(url))
38
+ assertEquals("/library", BrowserPathHelper.stripTrackId(url))
39
+ }
40
+
41
+ @Test
42
+ fun `extractIndex returns null without a stamped index`() {
43
+ val url = BrowserPathHelper.build("/library", "song.mp3")
44
+ assertNull(BrowserPathHelper.extractIndex(url))
45
+ }
46
+
47
+ @Test
48
+ fun `extractIndex returns null for non-contextual or malformed-index URLs`() {
49
+ assertNull(BrowserPathHelper.extractIndex("/library"))
50
+ assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=x"))
51
+ assertNull(BrowserPathHelper.extractIndex("/library?__trackId=song.mp3&__index=-1"))
27
52
  }
28
53
  }