react-native-audio-browser 0.2.0-next.10 → 0.2.0-next.12
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.
- package/README.md +4 -4
- package/android/build.gradle +6 -5
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
- package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +131 -143
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/JsonModels.swift +187 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +34 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +47 -0
- package/ios/Browser/TabBarEntries.swift +2 -2
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +105 -67
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/HybridAudioBrowser.swift +113 -35
- package/ios/Model/NitroTypeStubs.swift +64 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/NowPlayingUpdater.swift +1 -1
- package/ios/Player/PlaybackCoordinator.swift +2 -2
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/JsonModelsTests.swift +75 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +4 -4
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +61 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +67 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
- package/nitrogen/generated/android/c++/JSection.hpp +114 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
- package/package.json +3 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +139 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +268 -136
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -2,30 +2,31 @@ package com.audiobrowser.util
|
|
|
2
2
|
|
|
3
3
|
import android.os.Bundle
|
|
4
4
|
import androidx.media.utils.MediaConstants
|
|
5
|
-
import com.margelo.nitro.audiobrowser.
|
|
5
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
6
|
+
import com.margelo.nitro.audiobrowser.StyleDisplay
|
|
6
7
|
import com.margelo.nitro.audiobrowser.Track
|
|
7
|
-
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
8
8
|
|
|
9
9
|
/** Builds MediaMetadata extras bundle for Android Auto/AAOS content styling. */
|
|
10
10
|
object MediaExtrasBuilder {
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Maps
|
|
14
|
-
* URI, uses CATEGORY_* variants which add margins around icons (better for
|
|
13
|
+
* Maps a display mode to the MediaConstants content style value. When artwork is an
|
|
14
|
+
* android.resource:// URI, uses CATEGORY_* variants which add margins around icons (better for
|
|
15
|
+
* small vector icons).
|
|
15
16
|
*
|
|
16
17
|
* @see <a href="https://developer.android.com/training/cars/media#default-content-style">Default
|
|
17
18
|
* content style</a>
|
|
18
19
|
*/
|
|
19
|
-
private fun
|
|
20
|
+
private fun StyleDisplay.toContentStyleValue(artwork: String?): Int {
|
|
20
21
|
val isAndroidResource = artwork?.startsWith("android.resource://") == true
|
|
21
22
|
return when (this) {
|
|
22
|
-
|
|
23
|
+
StyleDisplay.LIST ->
|
|
23
24
|
if (isAndroidResource) {
|
|
24
25
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM
|
|
25
26
|
} else {
|
|
26
27
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
StyleDisplay.GRID ->
|
|
29
30
|
if (isAndroidResource) {
|
|
30
31
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_GRID_ITEM
|
|
31
32
|
} else {
|
|
@@ -34,39 +35,43 @@ object MediaExtrasBuilder {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Extras for a track rendered on a browse surface. The owning [section] supplies the Android Auto
|
|
40
|
+
* group-title header hint (grouping is a per-item advisory in the flat MediaBrowser protocol —
|
|
41
|
+
* the section dies here, ADR 0010) and, from its page-folded style block, the per-item display
|
|
42
|
+
* hint: per-item hints derive from the section resolution, never from the track's own `display`,
|
|
43
|
+
* which is positional (ADR 0011).
|
|
44
|
+
*
|
|
45
|
+
* The track's own declared `display` is the page-layout *promise* for the page a browsable track
|
|
46
|
+
* opens — emitted as the parent-level BROWSABLE/PLAYABLE hint, the only below-root hint
|
|
47
|
+
* AOSP-derived AAOS media UIs honor. Declared or it doesn't exist: the library never derives a
|
|
48
|
+
* promise from pages it hasn't resolved, and never back-fills from ones it has.
|
|
49
|
+
*/
|
|
50
|
+
fun build(track: Track, section: Section? = null): Bundle =
|
|
38
51
|
build(
|
|
39
|
-
groupTitle =
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
groupTitle = section?.title,
|
|
53
|
+
itemDisplay = section?.style?.display,
|
|
54
|
+
promisedDisplay = if (track.src == null) track.style?.display else null,
|
|
42
55
|
artwork = track.artwork?.url,
|
|
43
56
|
)
|
|
44
57
|
|
|
45
|
-
fun build(resolvedTrack: ResolvedTrack): Bundle =
|
|
46
|
-
build(
|
|
47
|
-
groupTitle = resolvedTrack.groupTitle,
|
|
48
|
-
style = resolvedTrack.style,
|
|
49
|
-
childrenStyle = resolvedTrack.childrenStyle,
|
|
50
|
-
artwork = resolvedTrack.artwork?.url,
|
|
51
|
-
)
|
|
52
|
-
|
|
53
58
|
private fun build(
|
|
54
59
|
groupTitle: String?,
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
itemDisplay: StyleDisplay?,
|
|
61
|
+
promisedDisplay: StyleDisplay?,
|
|
57
62
|
artwork: String?,
|
|
58
63
|
): Bundle =
|
|
59
64
|
Bundle().apply {
|
|
60
65
|
groupTitle?.let {
|
|
61
66
|
putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, it)
|
|
62
67
|
}
|
|
63
|
-
|
|
68
|
+
itemDisplay?.let {
|
|
64
69
|
putInt(
|
|
65
70
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM,
|
|
66
71
|
it.toContentStyleValue(artwork),
|
|
67
72
|
)
|
|
68
73
|
}
|
|
69
|
-
|
|
74
|
+
promisedDisplay?.let {
|
|
70
75
|
val styleValue = it.toContentStyleValue(artwork)
|
|
71
76
|
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE, styleValue)
|
|
72
77
|
putInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE, styleValue)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.audiobrowser.util
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.ArtworkRendering
|
|
4
|
+
import com.margelo.nitro.audiobrowser.StyleDisplay
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The style block's wire strings (ADR 0011) ↔ Nitro enums, shared by the browse decoder and the
|
|
8
|
+
* playback-state store. Inbound mapping is lenient: an unknown or empty value is "no declaration",
|
|
9
|
+
* never an error.
|
|
10
|
+
*/
|
|
11
|
+
internal fun StyleDisplay.toWireString(): String =
|
|
12
|
+
when (this) {
|
|
13
|
+
StyleDisplay.LIST -> "list"
|
|
14
|
+
StyleDisplay.GRID -> "grid"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
internal fun ArtworkRendering.toWireString(): String =
|
|
18
|
+
when (this) {
|
|
19
|
+
ArtworkRendering.ORIGINAL -> "original"
|
|
20
|
+
ArtworkRendering.STENCIL -> "stencil"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
internal fun String?.toStyleDisplay(): StyleDisplay? =
|
|
24
|
+
when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
|
|
25
|
+
"list" -> StyleDisplay.LIST
|
|
26
|
+
"grid" -> StyleDisplay.GRID
|
|
27
|
+
else -> null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
internal fun String?.toArtworkRendering(): ArtworkRendering? =
|
|
31
|
+
when (this?.takeIf { it.isNotEmpty() }?.lowercase()) {
|
|
32
|
+
"original" -> ArtworkRendering.ORIGINAL
|
|
33
|
+
"stencil" -> ArtworkRendering.STENCIL
|
|
34
|
+
else -> null
|
|
35
|
+
}
|
|
@@ -7,17 +7,16 @@ import androidx.media3.common.MediaItem
|
|
|
7
7
|
import androidx.media3.common.MediaMetadata
|
|
8
8
|
import com.audiobrowser.browser.BrowseArtworkRegistry
|
|
9
9
|
import com.audiobrowser.browser.ResolvedArtwork
|
|
10
|
-
import com.margelo.nitro.audiobrowser.
|
|
10
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
11
11
|
import com.margelo.nitro.audiobrowser.Track
|
|
12
12
|
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
13
|
-
import com.margelo.nitro.audiobrowser.Variant_String_ArtworkVariants
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* The single Track → Media3 [MediaItem] conversion. Owns the two easy-to-drift fallbacks: the
|
|
17
16
|
* displayed artwork is the transformed `artworkSource.uri` falling back to the raw `artwork` field,
|
|
18
|
-
* and the mediaId is the stable `id` for playable tracks, falling back to `
|
|
19
|
-
* must have one of the latter two — see the `src` vs `
|
|
20
|
-
* renders from `subtitle` (the now-playing line is re-stamped from `artist` by the now-playing
|
|
17
|
+
* and the mediaId is the stable `id` for playable tracks, falling back to `path` then `src` (a
|
|
18
|
+
* Track must have one of the latter two — see the `src` vs `path` note in CONTEXT.md). The list
|
|
19
|
+
* line renders from `subtitle` (the now-playing line is re-stamped from `artist` by the now-playing
|
|
21
20
|
* pipeline; see the Now Playing Metadata guide).
|
|
22
21
|
*/
|
|
23
22
|
object TrackFactory {
|
|
@@ -26,49 +25,35 @@ object TrackFactory {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* pass through unchanged.
|
|
28
|
+
* A synthetic browsable Track for a section's "view all" surface — the section has a path, title,
|
|
29
|
+
* and label, but no Track (ADR 0010). It carries the section's declared item block as its own,
|
|
30
|
+
* `display` included: the section's declaration about this content's layout is the only declared
|
|
31
|
+
* promise the "view all" page can ever have (there is no consumer-authored handle to declare one
|
|
32
|
+
* on), and without it Android Auto would always render that page as a list.
|
|
35
33
|
*/
|
|
36
|
-
fun
|
|
37
|
-
tracks.flatMap { track ->
|
|
38
|
-
val items = track.imageRow
|
|
39
|
-
if (items.isNullOrEmpty()) return@flatMap listOf(track)
|
|
40
|
-
val expanded = items.map { it.toTrack(groupTitle = track.title) }
|
|
41
|
-
if (track.url != null) {
|
|
42
|
-
expanded + track.copy(imageRow = null, groupTitle = track.title)
|
|
43
|
-
} else {
|
|
44
|
-
expanded
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** The row-item equivalent of a full Track, for surfaces that render items as plain rows. */
|
|
49
|
-
fun ImageRowItem.toTrack(groupTitle: String?): Track =
|
|
34
|
+
fun navigationTrack(section: Section): Track =
|
|
50
35
|
Track(
|
|
51
|
-
id =
|
|
52
|
-
|
|
53
|
-
src =
|
|
54
|
-
artwork =
|
|
55
|
-
artworkSource =
|
|
56
|
-
request =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
album = album,
|
|
36
|
+
id = null,
|
|
37
|
+
path = section.path,
|
|
38
|
+
src = null,
|
|
39
|
+
artwork = null,
|
|
40
|
+
artworkSource = null,
|
|
41
|
+
request = null,
|
|
42
|
+
title = section.title ?: "",
|
|
43
|
+
subtitle = section.subtitle,
|
|
44
|
+
artist = null,
|
|
45
|
+
albumPath = null,
|
|
46
|
+
album = null,
|
|
63
47
|
description = null,
|
|
64
48
|
genre = null,
|
|
65
49
|
duration = null,
|
|
66
|
-
style =
|
|
67
|
-
|
|
50
|
+
style =
|
|
51
|
+
section.style?.let {
|
|
52
|
+
TrackStyle(display = it.display, artworkRendering = it.artworkRendering)
|
|
53
|
+
},
|
|
54
|
+
disabled = null,
|
|
68
55
|
favorited = null,
|
|
69
|
-
|
|
70
|
-
live = live,
|
|
71
|
-
imageRow = null,
|
|
56
|
+
live = null,
|
|
72
57
|
)
|
|
73
58
|
|
|
74
59
|
fun toMedia3(tracks: Array<Track>): List<MediaItem> {
|
|
@@ -90,10 +75,11 @@ object TrackFactory {
|
|
|
90
75
|
track: Track,
|
|
91
76
|
registry: BrowseArtworkRegistry,
|
|
92
77
|
authority: String,
|
|
78
|
+
section: Section? = null,
|
|
93
79
|
): MediaItem {
|
|
94
80
|
val rawUrl = artworkUri(track) // artworkSource.uri ?: artwork
|
|
95
81
|
val scheme = rawUrl?.let { Uri.parse(it).scheme?.lowercase() }
|
|
96
|
-
val builder = metadataBuilder(track)
|
|
82
|
+
val builder = metadataBuilder(track, section)
|
|
97
83
|
if (rawUrl != null && (scheme == "http" || scheme == "https")) {
|
|
98
84
|
val isSvg =
|
|
99
85
|
SvgArtworkRenderer.isSvgUrl(rawUrl) || SvgArtworkRenderer.isSvgUrl(track.artwork?.url)
|
|
@@ -109,8 +95,11 @@ object TrackFactory {
|
|
|
109
95
|
/** The transformed artworkSource wins over the raw artwork field. */
|
|
110
96
|
private fun artworkUri(track: Track): String? = track.artworkSource?.uri ?: track.artwork?.url
|
|
111
97
|
|
|
112
|
-
/**
|
|
113
|
-
|
|
98
|
+
/**
|
|
99
|
+
* All metadata except artwork. The owning [section] (browse surfaces only) supplies the
|
|
100
|
+
* group-title header hint and the per-item style default (ADR 0010).
|
|
101
|
+
*/
|
|
102
|
+
private fun metadataBuilder(track: Track, section: Section? = null): MediaMetadata.Builder =
|
|
114
103
|
MediaMetadata.Builder()
|
|
115
104
|
.setTitle(track.title)
|
|
116
105
|
.setArtist(track.subtitle)
|
|
@@ -119,26 +108,29 @@ object TrackFactory {
|
|
|
119
108
|
.setGenre(track.genre)
|
|
120
109
|
.setIsBrowsable(track.src == null)
|
|
121
110
|
.setIsPlayable(track.src != null)
|
|
122
|
-
.setExtras(MediaExtrasBuilder.build(track))
|
|
111
|
+
.setExtras(MediaExtrasBuilder.build(track, section))
|
|
123
112
|
.apply { track.favorited?.let { setUserRating(HeartRating(it)) } }
|
|
124
113
|
|
|
125
114
|
private fun buildMediaItem(track: Track, metadata: MediaMetadata): MediaItem {
|
|
126
|
-
// A playable track's mediaId is the consumer's stable `id` when it has one
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// `
|
|
115
|
+
// A playable track's mediaId is the consumer's stable `id` when it has one —
|
|
116
|
+
// the identity rule (id when non-blank, else src; see Track.identity /
|
|
117
|
+
// ADR 0008), except that an id-less playable track prefers its contextual
|
|
118
|
+
// `path` over `src` here, so this is deliberately NOT expressed via
|
|
119
|
+
// Track.identity. Android Auto marks the "now playing" browse row by exact
|
|
120
|
+
// mediaId equality between the row and the player's current item (media3
|
|
121
|
+
// announces `currentMediaItem.mediaId` in the legacy playback state), and a
|
|
130
122
|
// consumer-loaded track's `src` can differ textually from the browse row's
|
|
131
123
|
// for the same item (absolute vs relative, extra query params) — the id is
|
|
132
124
|
// the one string both sides share. It is also context-free, so the same
|
|
133
125
|
// station gets the same mediaId in every tab. Browsable-only tracks keep
|
|
134
|
-
// `
|
|
126
|
+
// `path` so navigation parentIds stay resolvable paths.
|
|
135
127
|
val stableId = if (track.src != null) track.id?.takeUnless { it.isBlank() } else null
|
|
136
128
|
val mediaId =
|
|
137
129
|
stableId
|
|
138
|
-
?: track.
|
|
130
|
+
?: track.path
|
|
139
131
|
?: track.src
|
|
140
132
|
?: throw IllegalArgumentException(
|
|
141
|
-
"Track must have either
|
|
133
|
+
"Track must have either path or src defined. Track: title='${track.title}', artist='${track.artist}'"
|
|
142
134
|
)
|
|
143
135
|
return MediaItem.Builder()
|
|
144
136
|
.setMediaId(mediaId)
|
|
@@ -3,9 +3,12 @@ package com.audiobrowser
|
|
|
3
3
|
import com.audiobrowser.util.artworkOf
|
|
4
4
|
import com.margelo.nitro.audiobrowser.ArtworkRequestConfig
|
|
5
5
|
import com.margelo.nitro.audiobrowser.ImageQueryParams
|
|
6
|
-
import com.margelo.nitro.audiobrowser.ImageRowItem
|
|
7
6
|
import com.margelo.nitro.audiobrowser.ImageSource
|
|
8
7
|
import com.margelo.nitro.audiobrowser.MediaRequestConfig
|
|
8
|
+
import com.margelo.nitro.audiobrowser.NativeRouteEntry
|
|
9
|
+
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
10
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
11
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
9
12
|
import com.margelo.nitro.audiobrowser.Track
|
|
10
13
|
import com.margelo.nitro.audiobrowser.TransformableRequestConfig
|
|
11
14
|
|
|
@@ -24,47 +27,37 @@ object TestFixtures {
|
|
|
24
27
|
artist: String? = null,
|
|
25
28
|
album: String? = null,
|
|
26
29
|
favorited: Boolean? = null,
|
|
27
|
-
|
|
28
|
-
imageRow: Array<ImageRowItem>? = null,
|
|
30
|
+
disabled: Boolean? = null,
|
|
29
31
|
) =
|
|
30
32
|
Track(
|
|
31
33
|
id = id,
|
|
32
|
-
|
|
34
|
+
path = null,
|
|
33
35
|
src = src,
|
|
34
36
|
artwork = artworkOf(artwork),
|
|
35
37
|
artworkSource = null,
|
|
36
38
|
request = null,
|
|
37
|
-
artworkCarPlayTinted = null,
|
|
38
39
|
title = title,
|
|
39
40
|
subtitle = null,
|
|
40
41
|
artist = artist,
|
|
41
|
-
|
|
42
|
+
albumPath = null,
|
|
42
43
|
album = album,
|
|
43
44
|
description = null,
|
|
44
45
|
genre = null,
|
|
45
46
|
duration = null,
|
|
46
47
|
style = null,
|
|
47
|
-
|
|
48
|
+
disabled = disabled,
|
|
48
49
|
favorited = favorited,
|
|
49
|
-
groupTitle = groupTitle,
|
|
50
50
|
live = null,
|
|
51
|
-
imageRow = imageRow,
|
|
52
51
|
)
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
artist = null,
|
|
63
|
-
album = null,
|
|
64
|
-
albumUrl = null,
|
|
65
|
-
live = null,
|
|
66
|
-
request = null,
|
|
67
|
-
)
|
|
53
|
+
/** A page [Section] — plain list by default. */
|
|
54
|
+
fun section(
|
|
55
|
+
children: Array<Track>,
|
|
56
|
+
title: String? = null,
|
|
57
|
+
subtitle: String? = null,
|
|
58
|
+
style: SectionStyle? = null,
|
|
59
|
+
path: String? = null,
|
|
60
|
+
) = Section(title = title, subtitle = subtitle, style = style, path = path, children = children)
|
|
68
61
|
|
|
69
62
|
fun transformableConfig(
|
|
70
63
|
baseUrl: String? = null,
|
|
@@ -113,32 +106,78 @@ object TestFixtures {
|
|
|
113
106
|
*/
|
|
114
107
|
fun browseTrack(
|
|
115
108
|
title: String = "T",
|
|
116
|
-
|
|
109
|
+
path: String = "https://api.example.com/channel/abc",
|
|
117
110
|
artwork: String? = null,
|
|
118
111
|
artworkSource: ImageSource? = null,
|
|
119
112
|
) =
|
|
120
113
|
Track(
|
|
121
114
|
id = null,
|
|
122
|
-
|
|
115
|
+
path = path,
|
|
123
116
|
src = null, // browsable, not playable
|
|
124
117
|
artwork = artworkOf(artwork),
|
|
125
118
|
artworkSource = artworkSource,
|
|
126
119
|
request = null,
|
|
127
|
-
artworkCarPlayTinted = null,
|
|
128
120
|
title = title,
|
|
129
121
|
subtitle = null,
|
|
130
122
|
artist = null,
|
|
131
|
-
|
|
123
|
+
albumPath = null,
|
|
132
124
|
album = null,
|
|
133
125
|
description = null,
|
|
134
126
|
genre = null,
|
|
135
127
|
duration = null,
|
|
136
128
|
style = null,
|
|
137
|
-
|
|
129
|
+
disabled = null,
|
|
138
130
|
favorited = null,
|
|
139
|
-
groupTitle = null,
|
|
140
131
|
live = null,
|
|
141
|
-
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
/** A container [ResolvedTrack] — what a browse route hands back for a path. */
|
|
135
|
+
fun resolvedTrack(
|
|
136
|
+
path: String = "/container",
|
|
137
|
+
title: String = "Container",
|
|
138
|
+
children: Array<Track>? = null,
|
|
139
|
+
sections: Array<Section>? = null,
|
|
140
|
+
style: SectionStyle? = null,
|
|
141
|
+
) =
|
|
142
|
+
ResolvedTrack(
|
|
143
|
+
path = path,
|
|
144
|
+
style = style,
|
|
145
|
+
sections = sections,
|
|
146
|
+
children = children,
|
|
147
|
+
carPlaySiriListButton = null,
|
|
148
|
+
id = null,
|
|
149
|
+
src = null,
|
|
150
|
+
artwork = null,
|
|
151
|
+
artworkSource = null,
|
|
152
|
+
request = null,
|
|
153
|
+
title = title,
|
|
154
|
+
subtitle = null,
|
|
155
|
+
artist = null,
|
|
156
|
+
albumPath = null,
|
|
157
|
+
album = null,
|
|
158
|
+
description = null,
|
|
159
|
+
genre = null,
|
|
160
|
+
duration = null,
|
|
161
|
+
disabled = null,
|
|
162
|
+
favorited = null,
|
|
163
|
+
live = null,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A route serving [browseStatic] verbatim. `browseStatic` is the only one of the three browse
|
|
168
|
+
* arms (callback > config > static) that needs neither the JNI bridge nor HTTP, so it is how a
|
|
169
|
+
* unit test gives [com.audiobrowser.browser.BrowserManager] page content to resolve.
|
|
170
|
+
*/
|
|
171
|
+
fun staticRoute(path: String, browseStatic: ResolvedTrack) =
|
|
172
|
+
NativeRouteEntry(
|
|
173
|
+
path = path,
|
|
174
|
+
browseCallback = null,
|
|
175
|
+
browseConfig = null,
|
|
176
|
+
browseStatic = browseStatic,
|
|
177
|
+
searchCallback = null,
|
|
178
|
+
searchConfig = null,
|
|
179
|
+
media = null,
|
|
180
|
+
artwork = null,
|
|
142
181
|
)
|
|
143
182
|
|
|
144
183
|
fun artworkConfig(
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
package com.audiobrowser.browser
|
|
2
|
+
|
|
3
|
+
import com.audiobrowser.TestFixtures.resolvedTrack
|
|
4
|
+
import com.audiobrowser.TestFixtures.staticRoute
|
|
5
|
+
import com.audiobrowser.TestFixtures.track
|
|
6
|
+
import kotlinx.coroutines.runBlocking
|
|
7
|
+
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Before
|
|
9
|
+
import org.junit.Test
|
|
10
|
+
import org.junit.runner.RunWith
|
|
11
|
+
import org.robolectric.RobolectricTestRunner
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Favorite hydration keyed by track identity (id when non-blank, else src — see Track.identity /
|
|
15
|
+
* ADR 0008). Hydration is exercised through [BrowserManager.resolve], the path every browse page
|
|
16
|
+
* takes.
|
|
17
|
+
*
|
|
18
|
+
* Routes serve `browseStatic` (no JNI/HTTP); Robolectric is required for `android.util.LruCache`
|
|
19
|
+
* and `android.net.Uri`.
|
|
20
|
+
*/
|
|
21
|
+
@RunWith(RobolectricTestRunner::class)
|
|
22
|
+
class BrowserManagerFavoritesTest {
|
|
23
|
+
|
|
24
|
+
private lateinit var browserManager: BrowserManager
|
|
25
|
+
|
|
26
|
+
@Before
|
|
27
|
+
fun setup() {
|
|
28
|
+
browserManager = BrowserManager()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private fun servePage(path: String, vararg children: com.margelo.nitro.audiobrowser.Track) {
|
|
32
|
+
browserManager.config =
|
|
33
|
+
BrowserConfig(
|
|
34
|
+
routes =
|
|
35
|
+
arrayOf(staticRoute(path, resolvedTrack(path = path, children = arrayOf(*children))))
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private fun favoritedByTitle(children: Array<com.margelo.nitro.audiobrowser.Track>?) =
|
|
40
|
+
children?.associate { it.title to it.favorited }
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
fun `hydrates by id when the track has one`() = runBlocking {
|
|
44
|
+
servePage(
|
|
45
|
+
"/home",
|
|
46
|
+
track(title = "A", id = "stable-a", src = "https://s/a.mp3"),
|
|
47
|
+
track(title = "B", id = "stable-b", src = "https://s/b.mp3"),
|
|
48
|
+
)
|
|
49
|
+
browserManager.setFavoriteEnabled(true)
|
|
50
|
+
browserManager.setFavorites(listOf("stable-b"))
|
|
51
|
+
|
|
52
|
+
val resolved = browserManager.resolve("/home")
|
|
53
|
+
|
|
54
|
+
assertEquals(
|
|
55
|
+
mapOf("A" to false, "B" to true),
|
|
56
|
+
favoritedByTitle(resolved.flattenedChildren?.toTypedArray()),
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test
|
|
61
|
+
fun `falls back to src for id-less tracks`() = runBlocking {
|
|
62
|
+
servePage(
|
|
63
|
+
"/home",
|
|
64
|
+
track(title = "A", src = "https://s/a.mp3"),
|
|
65
|
+
track(title = "B", src = "https://s/b.mp3"),
|
|
66
|
+
)
|
|
67
|
+
browserManager.setFavoriteEnabled(true)
|
|
68
|
+
browserManager.setFavorites(listOf("https://s/b.mp3"))
|
|
69
|
+
|
|
70
|
+
val resolved = browserManager.resolve("/home")
|
|
71
|
+
|
|
72
|
+
assertEquals(
|
|
73
|
+
mapOf("A" to false, "B" to true),
|
|
74
|
+
favoritedByTitle(resolved.flattenedChildren?.toTypedArray()),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Test
|
|
79
|
+
fun `a favorite stored under src does not match an id-bearing track`() = runBlocking {
|
|
80
|
+
// The identity is the id when present — a src-keyed entry must not light up
|
|
81
|
+
// a track whose identity is its id (one rule, no partial matching).
|
|
82
|
+
servePage("/home", track(title = "A", id = "stable-a", src = "https://s/a.mp3"))
|
|
83
|
+
browserManager.setFavoriteEnabled(true)
|
|
84
|
+
browserManager.setFavorites(listOf("https://s/a.mp3"))
|
|
85
|
+
|
|
86
|
+
val resolved = browserManager.resolve("/home")
|
|
87
|
+
|
|
88
|
+
assertEquals(mapOf("A" to false), favoritedByTitle(resolved.flattenedChildren?.toTypedArray()))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
fun `caller-set favorited wins over hydration`() = runBlocking {
|
|
93
|
+
servePage(
|
|
94
|
+
"/home",
|
|
95
|
+
track(title = "A", id = "stable-a", src = "https://s/a.mp3", favorited = false),
|
|
96
|
+
)
|
|
97
|
+
browserManager.setFavoriteEnabled(true)
|
|
98
|
+
browserManager.setFavorites(listOf("stable-a"))
|
|
99
|
+
|
|
100
|
+
val resolved = browserManager.resolve("/home")
|
|
101
|
+
|
|
102
|
+
// API-provided value is never overwritten, even when the identity is favorited.
|
|
103
|
+
assertEquals(mapOf("A" to false), favoritedByTitle(resolved.flattenedChildren?.toTypedArray()))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test
|
|
107
|
+
fun `disabled favoriting leaves tracks untouched`() = runBlocking {
|
|
108
|
+
servePage("/home", track(title = "A", id = "stable-a", src = "https://s/a.mp3"))
|
|
109
|
+
// favoriteEnabled defaults to false
|
|
110
|
+
browserManager.setFavorites(listOf("stable-a"))
|
|
111
|
+
|
|
112
|
+
val resolved = browserManager.resolve("/home")
|
|
113
|
+
|
|
114
|
+
assertEquals(
|
|
115
|
+
mapOf<String, Boolean?>("A" to null),
|
|
116
|
+
favoritedByTitle(resolved.flattenedChildren?.toTypedArray()),
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@Test
|
|
121
|
+
fun `updateFavorite adds and removes by identity`() = runBlocking {
|
|
122
|
+
servePage("/home", track(title = "A", id = "stable-a", src = "https://s/a.mp3"))
|
|
123
|
+
browserManager.setFavoriteEnabled(true)
|
|
124
|
+
|
|
125
|
+
assertEquals(
|
|
126
|
+
mapOf("A" to false),
|
|
127
|
+
favoritedByTitle(browserManager.resolve("/home").flattenedChildren?.toTypedArray()),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
browserManager.updateFavorite("stable-a", true)
|
|
131
|
+
assertEquals(
|
|
132
|
+
mapOf("A" to true),
|
|
133
|
+
favoritedByTitle(browserManager.resolve("/home").flattenedChildren?.toTypedArray()),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
browserManager.updateFavorite("stable-a", false)
|
|
137
|
+
assertEquals(
|
|
138
|
+
mapOf("A" to false),
|
|
139
|
+
favoritedByTitle(browserManager.resolve("/home").flattenedChildren?.toTypedArray()),
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@Test
|
|
144
|
+
fun `getCachedTrack re-hydrates favorites by identity`() = runBlocking {
|
|
145
|
+
servePage("/home", track(title = "A", id = "stable-a", src = "https://s/a.mp3"))
|
|
146
|
+
browserManager.setFavoriteEnabled(true)
|
|
147
|
+
browserManager.resolve("/home")
|
|
148
|
+
|
|
149
|
+
browserManager.updateFavorite("stable-a", true)
|
|
150
|
+
|
|
151
|
+
assertEquals(true, browserManager.getCachedTrack("stable-a")?.favorited)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -8,6 +8,8 @@ import org.junit.Assert.assertNull
|
|
|
8
8
|
import org.junit.Assert.assertSame
|
|
9
9
|
import org.junit.Before
|
|
10
10
|
import org.junit.Test
|
|
11
|
+
import org.junit.runner.RunWith
|
|
12
|
+
import org.robolectric.RobolectricTestRunner
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Tests the request/browse layer resolution + caching introduced for resolver thunks.
|
|
@@ -26,7 +28,11 @@ import org.junit.Test
|
|
|
26
28
|
* bump, no in-flight cache) is identical for the static and resolver paths — `resolveLayer` only
|
|
27
29
|
* differs in how it produces the `TransformableRequestConfig` — so the guard is fully covered here.
|
|
28
30
|
* The resolver-await path is covered by the TS/web-stub tests and on iOS.
|
|
31
|
+
*
|
|
32
|
+
* Runs under Robolectric for [BrowserManager]'s `android.util.LruCache` fields: `clearContentCache`
|
|
33
|
+
* calls `evictAll()`, which a plain JVM test cannot execute.
|
|
29
34
|
*/
|
|
35
|
+
@RunWith(RobolectricTestRunner::class)
|
|
30
36
|
class BrowserManagerLayerResolutionTest {
|
|
31
37
|
|
|
32
38
|
private lateinit var browserManager: BrowserManager
|