react-native-audio-browser 0.2.0-next.11 → 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/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/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 +26 -39
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +228 -50
- 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/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 +46 -21
- 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/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 +1 -1
- 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 +40 -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
|
@@ -20,6 +20,7 @@ import com.audiobrowser.browser.displayArtworkSource
|
|
|
20
20
|
import com.audiobrowser.browser.resolveArtworkUrl
|
|
21
21
|
import com.audiobrowser.browser.unattributedArtworkSource
|
|
22
22
|
import com.audiobrowser.extension.NumberExt.Companion.toSeconds
|
|
23
|
+
import com.audiobrowser.extension.identity
|
|
23
24
|
import com.audiobrowser.model.PlayerSetupOptions
|
|
24
25
|
import com.audiobrowser.model.PlayerUpdateOptions
|
|
25
26
|
import com.audiobrowser.util.CoilBitmapLoader
|
|
@@ -36,7 +37,6 @@ import com.margelo.nitro.audiobrowser.ImageSource
|
|
|
36
37
|
import com.margelo.nitro.audiobrowser.NativeGateRequest
|
|
37
38
|
import com.margelo.nitro.audiobrowser.NowPlayingMetadata
|
|
38
39
|
import com.margelo.nitro.audiobrowser.Playback
|
|
39
|
-
import com.margelo.nitro.audiobrowser.PlaybackActiveTrackChangedEvent
|
|
40
40
|
import com.margelo.nitro.audiobrowser.PlaybackError
|
|
41
41
|
import com.margelo.nitro.audiobrowser.PlaybackPlayWhenReadyChangedEvent
|
|
42
42
|
import com.margelo.nitro.audiobrowser.PlaybackProgressUpdatedEvent
|
|
@@ -351,7 +351,7 @@ class Player(internal val context: Context) {
|
|
|
351
351
|
get() = exoPlayer.nextMediaItemIndex == C.INDEX_UNSET
|
|
352
352
|
|
|
353
353
|
/**
|
|
354
|
-
* The source path from which the current queue was expanded (e.g., from a contextual
|
|
354
|
+
* The source path from which the current queue was expanded (e.g., from a contextual path). Used
|
|
355
355
|
* to avoid re-expanding the queue when selecting tracks from the same source.
|
|
356
356
|
*/
|
|
357
357
|
var queueSourcePath: String? = null
|
|
@@ -657,7 +657,7 @@ class Player(internal val context: Context) {
|
|
|
657
657
|
* @param tracks The tracks to set as the new queue.
|
|
658
658
|
* @param startIndex The index to start playback from (default: 0).
|
|
659
659
|
* @param startPositionMs The position in milliseconds to start from (default: 0).
|
|
660
|
-
* @param sourcePath Optional path from which this queue was expanded (for contextual
|
|
660
|
+
* @param sourcePath Optional path from which this queue was expanded (for contextual path
|
|
661
661
|
* optimization).
|
|
662
662
|
*/
|
|
663
663
|
fun setQueue(
|
|
@@ -685,17 +685,27 @@ class Player(internal val context: Context) {
|
|
|
685
685
|
/**
|
|
686
686
|
* Sets the favorited state of the currently playing track. Updates the heart icon in media
|
|
687
687
|
* controllers without interrupting playback.
|
|
688
|
+
*
|
|
689
|
+
* @return whether the gesture applied — false only when there is no current track (nothing to
|
|
690
|
+
* favorite), so MediaSession callers can report an invalid state instead of a success the
|
|
691
|
+
* controller then watches revert.
|
|
688
692
|
*/
|
|
689
|
-
fun setActiveTrackFavorited(favorited: Boolean) {
|
|
690
|
-
val currentTrack = this.currentTrack ?: return
|
|
693
|
+
fun setActiveTrackFavorited(favorited: Boolean): Boolean {
|
|
694
|
+
val currentTrack = this.currentTrack ?: return false
|
|
691
695
|
|
|
692
696
|
// Persist to the native favorites cache regardless of player state — the
|
|
693
697
|
// user's gesture is durable even when there's no active media item to
|
|
694
|
-
// update (queue tear-down, between-track gaps, etc.).
|
|
695
|
-
|
|
698
|
+
// update (queue tear-down, between-track gaps, etc.). Keyed by the track
|
|
699
|
+
// identity (id when non-blank, else src).
|
|
700
|
+
currentTrack.identity?.let { identity ->
|
|
701
|
+
browser?.browserManager?.updateFavorite(identity, favorited)
|
|
702
|
+
}
|
|
696
703
|
|
|
704
|
+
// Cache written above: the gesture is durably recorded even without a media
|
|
705
|
+
// item to restamp (queue tear-down, between-track gaps) — that counts as
|
|
706
|
+
// applied; there is also no visible heart to revert in that state.
|
|
697
707
|
val index = exoPlayer.currentMediaItemIndex
|
|
698
|
-
if (index == C.INDEX_UNSET) return
|
|
708
|
+
if (index == C.INDEX_UNSET) return true
|
|
699
709
|
|
|
700
710
|
// Create updated Track with new favorited state
|
|
701
711
|
val updatedTrack = currentTrack.copy(favorited = favorited)
|
|
@@ -719,28 +729,23 @@ class Player(internal val context: Context) {
|
|
|
719
729
|
// Update the heart button icon in notification/Android Auto
|
|
720
730
|
updateFavoriteButtonState(favorited)
|
|
721
731
|
|
|
722
|
-
// Notify JS of the favorite state change
|
|
732
|
+
// Notify JS of the favorite state change. This is the ONLY emit: a favorite
|
|
733
|
+
// toggle is an in-place mutation of the active track, not a transition —
|
|
734
|
+
// onPlaybackActiveTrackChanged / onPlaybackQueueChanged stay
|
|
735
|
+
// transition-only, and the useActiveTrack / useQueue hooks subscribe to
|
|
736
|
+
// onFavoriteChanged themselves.
|
|
723
737
|
callbacks?.onFavoriteChanged(FavoriteChangedEvent(updatedTrack, favorited))
|
|
724
|
-
|
|
725
|
-
// Emit active track changed so useActiveTrack() hook updates
|
|
726
|
-
val activeTrackEvent =
|
|
727
|
-
PlaybackActiveTrackChangedEvent(
|
|
728
|
-
lastIndex = index.toDouble(),
|
|
729
|
-
lastTrack = currentTrack,
|
|
730
|
-
lastPosition = exoPlayer.currentPosition.toSeconds(),
|
|
731
|
-
index = index.toDouble(),
|
|
732
|
-
track = updatedTrack,
|
|
733
|
-
)
|
|
734
|
-
callbacks?.onPlaybackActiveTrackChanged(activeTrackEvent)
|
|
735
|
-
|
|
736
|
-
// Emit queue changed so useQueue() hook updates
|
|
737
|
-
callbacks?.onPlaybackQueueChanged(tracks)
|
|
738
|
+
return true
|
|
738
739
|
}
|
|
739
740
|
|
|
740
|
-
/**
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
741
|
+
/**
|
|
742
|
+
* Toggles the favorited state of the currently playing track.
|
|
743
|
+
*
|
|
744
|
+
* @return whether the toggle applied — false when there is no current track.
|
|
745
|
+
*/
|
|
746
|
+
fun toggleActiveTrackFavorited(): Boolean {
|
|
747
|
+
val currentTrack = this.currentTrack ?: return false
|
|
748
|
+
return setActiveTrackFavorited(currentTrack.favorited != true)
|
|
744
749
|
}
|
|
745
750
|
|
|
746
751
|
// MARK: - Now Playing Metadata
|
|
@@ -926,10 +931,23 @@ class Player(internal val context: Context) {
|
|
|
926
931
|
}
|
|
927
932
|
}
|
|
928
933
|
|
|
929
|
-
/**
|
|
934
|
+
/**
|
|
935
|
+
* Jump to the live edge of the current track; no-op unless it declares `live` — the track's own
|
|
936
|
+
* declaration is the gate (matching iOS and the spec), not ExoPlayer's stream-derived liveness,
|
|
937
|
+
* which never marks progressive ICY radio live. A stream with a live window seeks to its edge;
|
|
938
|
+
* anything else reconnects — for a non-seekable stream a fresh connection IS the live edge. A
|
|
939
|
+
* stale URL recovers through the failure-armed re-resolve in [TransformingDataSource].
|
|
940
|
+
*/
|
|
930
941
|
fun seekToLiveEdge() {
|
|
942
|
+
if (currentTrack?.live != true) return
|
|
931
943
|
if (exoPlayer.isCurrentMediaItemLive) {
|
|
932
944
|
exoPlayer.seekToDefaultPosition()
|
|
945
|
+
} else {
|
|
946
|
+
// ExoPlayer.prepare() only re-prepares from STATE_IDLE, hence the raw stop() — not
|
|
947
|
+
// Player.stop(), which would drop the play intent this reconnect must preserve.
|
|
948
|
+
exoPlayer.stop()
|
|
949
|
+
exoPlayer.seekToDefaultPosition()
|
|
950
|
+
exoPlayer.prepare()
|
|
933
951
|
}
|
|
934
952
|
}
|
|
935
953
|
|
|
@@ -1249,8 +1267,8 @@ class Player(internal val context: Context) {
|
|
|
1249
1267
|
|
|
1250
1268
|
if (capabilitiesChanged) {
|
|
1251
1269
|
// The `favorite` capability is the single favoriting switch — propagate
|
|
1252
|
-
//
|
|
1253
|
-
browser?.browserManager?.
|
|
1270
|
+
// it to the browser so it can hydrate row hearts.
|
|
1271
|
+
browser?.browserManager?.setFavoriteEnabled(options.capabilities.favoriteEnabled)
|
|
1254
1272
|
}
|
|
1255
1273
|
|
|
1256
1274
|
if (progressUpdateEventIntervalChanged) {
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
package com.audiobrowser.player
|
|
2
2
|
|
|
3
|
-
import com.margelo.nitro.audiobrowser.FavoritesMatchMode
|
|
4
3
|
import com.margelo.nitro.audiobrowser.PlayerCapabilities
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
* Resolved favorite match mode, or null when favoriting is disabled.
|
|
8
|
-
*
|
|
9
|
-
* `false`/unset → null; `true` → EXACT; `{ match }` → that match.
|
|
10
|
-
*/
|
|
11
|
-
val PlayerCapabilities.favoriteMatch: FavoritesMatchMode?
|
|
12
|
-
get() =
|
|
13
|
-
favorite?.match(
|
|
14
|
-
first = { enabled -> if (enabled) FavoritesMatchMode.EXACT else null },
|
|
15
|
-
second = { config -> config.match },
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
/** Whether the favorite/like control is enabled. */
|
|
5
|
+
/** Whether the favorite/like control is enabled. Opt-in: only an explicit `true` enables it. */
|
|
19
6
|
val PlayerCapabilities.favoriteEnabled: Boolean
|
|
20
|
-
get() =
|
|
7
|
+
get() = favorite == true
|
|
@@ -8,14 +8,19 @@ import java.net.URLEncoder
|
|
|
8
8
|
* Utility for handling browser paths and contextual URLs in the media browser system.
|
|
9
9
|
*
|
|
10
10
|
* Handles two types of special paths:
|
|
11
|
-
* 1. System paths (prefixed with `/__`):
|
|
11
|
+
* 1. System paths (prefixed with `/__`): root, search, offline, error, and gate paths
|
|
12
12
|
* 2. Contextual URLs: Embed parent context in track identifiers for Media3 integration
|
|
13
13
|
*
|
|
14
|
-
* Contextual URL format: `{parentPath}?__trackId={
|
|
15
|
-
*
|
|
14
|
+
* Contextual URL format: `{parentPath}?__trackId={trackIdentity}&__index={childIndex}` (the
|
|
15
|
+
* identity is the track's `id` when non-blank, else its `src`) Example:
|
|
16
|
+
* "/library/radio?__trackId=song.mp3&__index=2"
|
|
17
|
+
*
|
|
18
|
+
* `__trackId` is the identity check; `__index` (the child's position on the page at stamp time) is
|
|
19
|
+
* only a tie-breaker between surfaces that carry the same identity — a stale index never selects a
|
|
20
|
+
* different track.
|
|
16
21
|
*
|
|
17
22
|
* This allows:
|
|
18
|
-
* - Media3 to reference playable-only tracks (tracks with `src` but no `
|
|
23
|
+
* - Media3 to reference playable-only tracks (tracks with `src` but no `path`)
|
|
19
24
|
* - Cache lookup to work consistently
|
|
20
25
|
* - Parent context to be preserved for queue restoration
|
|
21
26
|
*/
|
|
@@ -23,9 +28,6 @@ object BrowserPathHelper {
|
|
|
23
28
|
/** Root path for media browsing */
|
|
24
29
|
const val ROOT_PATH = "/__root"
|
|
25
30
|
|
|
26
|
-
/** Recent media path for playback resumption */
|
|
27
|
-
const val RECENT_PATH = "/__recent"
|
|
28
|
-
|
|
29
31
|
/** Search path prefix (full path is /__search?q=query) */
|
|
30
32
|
const val SEARCH_PATH_PREFIX = "/__search"
|
|
31
33
|
|
|
@@ -41,9 +43,12 @@ object BrowserPathHelper {
|
|
|
41
43
|
// Query parameter name for contextual track identifiers
|
|
42
44
|
private const val CONTEXTUAL_TRACK_PARAM = "__trackId"
|
|
43
45
|
|
|
46
|
+
// Query parameter name for the tapped child's page position (tie-breaker)
|
|
47
|
+
private const val CONTEXTUAL_INDEX_PARAM = "__index"
|
|
48
|
+
|
|
44
49
|
/** Check if a path is a special system path (not a regular navigation path) */
|
|
45
50
|
fun isSpecialPath(path: String): Boolean {
|
|
46
|
-
return path == ROOT_PATH || path
|
|
51
|
+
return path == ROOT_PATH || path.startsWith("$SEARCH_PATH_PREFIX?")
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** Create a search path for a given query */
|
|
@@ -63,13 +68,13 @@ object BrowserPathHelper {
|
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
66
|
-
* Strips the __trackId
|
|
67
|
-
* contextual, returns it unchanged.
|
|
71
|
+
* Strips the contextual parameters (__trackId and __index) from a contextual URL to get the
|
|
72
|
+
* parent path. If the URL is not contextual, returns it unchanged.
|
|
68
73
|
*
|
|
69
74
|
* @param url The URL to process
|
|
70
|
-
* @return The URL without the
|
|
75
|
+
* @return The URL without the contextual parameters
|
|
71
76
|
*
|
|
72
|
-
* Example: "/library/radio?__trackId=song.mp3" → "/library/radio" Example:
|
|
77
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → "/library/radio" Example:
|
|
73
78
|
* "/search?q=jazz&__trackId=song.mp3" → "/search?q=jazz"
|
|
74
79
|
*/
|
|
75
80
|
fun stripTrackId(url: String): String {
|
|
@@ -79,13 +84,13 @@ object BrowserPathHelper {
|
|
|
79
84
|
|
|
80
85
|
val uri = url.toUri()
|
|
81
86
|
|
|
82
|
-
// Build URL preserving everything except
|
|
87
|
+
// Build URL preserving everything except the contextual parameters
|
|
83
88
|
val builder = uri.buildUpon()
|
|
84
89
|
builder.clearQuery()
|
|
85
90
|
|
|
86
|
-
// Re-add all query params except __trackId
|
|
91
|
+
// Re-add all query params except __trackId and __index
|
|
87
92
|
uri.queryParameterNames.forEach { paramName ->
|
|
88
|
-
if (paramName != CONTEXTUAL_TRACK_PARAM) {
|
|
93
|
+
if (paramName != CONTEXTUAL_TRACK_PARAM && paramName != CONTEXTUAL_INDEX_PARAM) {
|
|
89
94
|
uri.getQueryParameters(paramName).forEach { value ->
|
|
90
95
|
builder.appendQueryParameter(paramName, value)
|
|
91
96
|
}
|
|
@@ -96,19 +101,21 @@ object BrowserPathHelper {
|
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
/**
|
|
99
|
-
* Builds a contextual URL by appending a track identifier
|
|
100
|
-
* query parameters correctly.
|
|
104
|
+
* Builds a contextual URL by appending a track identifier — and optionally the tapped child's
|
|
105
|
+
* page position — to a parent path. Handles existing query parameters correctly.
|
|
101
106
|
*
|
|
102
107
|
* @param parentPath The parent container path
|
|
103
|
-
* @param trackId The track
|
|
104
|
-
* @
|
|
108
|
+
* @param trackId The track identity (`id` when non-blank, else `src` — see Track.identity)
|
|
109
|
+
* @param index The child's position on the page at stamp time (tie-breaker)
|
|
110
|
+
* @return A contextual URL combining parent path, track ID, and index
|
|
105
111
|
*
|
|
106
|
-
* Example: build("/library", "song.mp3") → "/library?__trackId=song.mp3" Example:
|
|
112
|
+
* Example: build("/library", "song.mp3", 2) → "/library?__trackId=song.mp3&__index=2" Example:
|
|
107
113
|
* build("/search?q=jazz", "song.mp3") → "/search?q=jazz&__trackId=song.mp3"
|
|
108
114
|
*/
|
|
109
|
-
fun build(parentPath: String, trackId: String): String {
|
|
115
|
+
fun build(parentPath: String, trackId: String, index: Int? = null): String {
|
|
110
116
|
val separator = if (parentPath.contains('?')) '&' else '?'
|
|
111
|
-
|
|
117
|
+
val indexParam = if (index != null) "&$CONTEXTUAL_INDEX_PARAM=$index" else ""
|
|
118
|
+
return "$parentPath$separator$CONTEXTUAL_TRACK_PARAM=${Uri.encode(trackId)}$indexParam"
|
|
112
119
|
}
|
|
113
120
|
|
|
114
121
|
/**
|
|
@@ -129,6 +136,21 @@ object BrowserPathHelper {
|
|
|
129
136
|
return uri.getQueryParameter(CONTEXTUAL_TRACK_PARAM)
|
|
130
137
|
}
|
|
131
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Extracts the stamped page index from a contextual URL, or null when the URL is not contextual
|
|
141
|
+
* or carries no (valid, non-negative) index.
|
|
142
|
+
*
|
|
143
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → 2
|
|
144
|
+
*/
|
|
145
|
+
fun extractIndex(path: String): Int? {
|
|
146
|
+
if (!isContextual(path)) {
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
val raw = Uri.parse(path).getQueryParameter(CONTEXTUAL_INDEX_PARAM) ?: return null
|
|
151
|
+
return raw.toIntOrNull()?.takeIf { it >= 0 }
|
|
152
|
+
}
|
|
153
|
+
|
|
132
154
|
/**
|
|
133
155
|
* Combines a base URL with a path, ensuring proper slash handling.
|
|
134
156
|
*
|
|
@@ -158,22 +180,4 @@ object BrowserPathHelper {
|
|
|
158
180
|
val normalizedPath = path.trimStart('/')
|
|
159
181
|
return "$normalizedBase$normalizedPath"
|
|
160
182
|
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* True if [segment] appears in [path] as a complete path segment — bounded on the left by the
|
|
164
|
-
* string start or `/`, and on the right by the string end or one of `/`, `?`, `#`.
|
|
165
|
-
*/
|
|
166
|
-
fun containsSegment(path: String, segment: String): Boolean {
|
|
167
|
-
if (segment.isEmpty()) return false
|
|
168
|
-
var from = 0
|
|
169
|
-
while (true) {
|
|
170
|
-
val idx = path.indexOf(segment, from)
|
|
171
|
-
if (idx < 0) return false
|
|
172
|
-
val beforeOk = idx == 0 || path[idx - 1] == '/'
|
|
173
|
-
val end = idx + segment.length
|
|
174
|
-
val afterOk = end == path.length || path[end] == '/' || path[end] == '?' || path[end] == '#'
|
|
175
|
-
if (beforeOk && afterOk) return true
|
|
176
|
-
from = idx + 1
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
183
|
}
|
|
@@ -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)
|