react-native-audio-browser 0.2.0-next.11 → 0.2.0-next.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/android/build.gradle +20 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -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/model/PlayerUpdateOptions.kt +1 -1
- 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 +109 -74
- package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- 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 +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -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/player/ShuffleOrderLedByTest.kt +79 -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 +229 -180
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +258 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -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/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +166 -45
- package/ios/Model/NitroTypeStubs.swift +106 -21
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/MediaIntentCriteria.swift +13 -5
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +36 -10
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Player/ShuffleOrder.swift +23 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +72 -6
- package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +7 -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/TrackPlayer.swift +57 -34
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- 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/progress.js +2 -1
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.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/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.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/progress.js +2 -1
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.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/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.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/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.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 +361 -126
- 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 +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -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 +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -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 +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
- package/nitrogen/generated/android/c++/JSection.hpp +120 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -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/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- 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/{FavoriteConfig.kt → Section.kt} +18 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- 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 +35 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- 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 +133 -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 +86 -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++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- 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 +126 -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 +73 -34
- package/package.json +7 -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/progress.ts +2 -1
- package/src/features/playback/rate.ts +8 -1
- 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 +210 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +377 -135
- 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/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- 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/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +246 -0
- 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
|
@@ -176,15 +176,22 @@ suspend fun BrowserManager.resolveArtworkUrl(
|
|
|
176
176
|
)
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
// `{id}` token substitution
|
|
179
|
+
// `{id}` token substitution. A track without a non-blank id leaves the token LITERALLY in
|
|
180
|
+
// place — the request then visibly 404s, which is intended (self-describing error trail) —
|
|
181
|
+
// with a single warning logged below once the URL is built. Mirrors iOS substituteTrackId.
|
|
180
182
|
track.id
|
|
181
183
|
?.takeIf { it.isNotEmpty() }
|
|
182
184
|
?.let { transformedConfig = substituteTrackId(transformedConfig, it) }
|
|
185
|
+
val hasUnfilledIdToken = configContainsIdToken(transformedConfig)
|
|
183
186
|
|
|
184
187
|
val uri = RequestConfigBuilder.buildUrl(transformedConfig)
|
|
185
188
|
// If URI is empty, there's no valid artwork path
|
|
186
189
|
if (uri.isEmpty()) return null
|
|
187
190
|
|
|
191
|
+
if (hasUnfilledIdToken) {
|
|
192
|
+
Timber.w("nowPlayingArtwork: track has no id — '{id}' left unfilled in %s", uri)
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
ImageSource(
|
|
189
196
|
uri = uri,
|
|
190
197
|
method = transformedConfig.method,
|
|
@@ -265,15 +272,18 @@ private fun applyImageQueryParams(
|
|
|
265
272
|
return config.copy(query = (config.query ?: emptyMap()) + contextQuery)
|
|
266
273
|
}
|
|
267
274
|
|
|
275
|
+
/** The `{id}` template token — see [substituteTrackId] / [configContainsIdToken]. */
|
|
276
|
+
private const val ID_TOKEN = "{id}"
|
|
277
|
+
|
|
268
278
|
/**
|
|
269
279
|
* Replaces the `{id}` token with the track id in a request config's path, query values, and header
|
|
270
280
|
* values. Used so a `nowPlayingArtwork` like `{ path: "/artwork/{id}" }` resolves. Configs without
|
|
271
281
|
* the token are returned unchanged. Mirrors the iOS `substituteTrackId` helper.
|
|
272
282
|
*/
|
|
273
283
|
private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig {
|
|
274
|
-
fun sub(s: String?): String? = s?.replace(
|
|
284
|
+
fun sub(s: String?): String? = s?.replace(ID_TOKEN, id)
|
|
275
285
|
fun subMap(m: Map<String, String>?): Map<String, String>? =
|
|
276
|
-
m?.mapValues { (_, value) -> value.replace(
|
|
286
|
+
m?.mapValues { (_, value) -> value.replace(ID_TOKEN, id) }
|
|
277
287
|
return config.copy(
|
|
278
288
|
path = sub(config.path),
|
|
279
289
|
headers = subMap(config.headers),
|
|
@@ -281,6 +291,16 @@ private fun substituteTrackId(config: RequestConfig, id: String): RequestConfig
|
|
|
281
291
|
)
|
|
282
292
|
}
|
|
283
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Whether the config still carries an unfilled `{id}` token in its path, query values, or header
|
|
296
|
+
* values (checked AFTER substitution, so it matches exactly when the track had no non-blank id and
|
|
297
|
+
* the config uses the token). Mirrors iOS `TrackIdTemplate.containsToken`.
|
|
298
|
+
*/
|
|
299
|
+
private fun configContainsIdToken(config: RequestConfig): Boolean =
|
|
300
|
+
config.path?.contains(ID_TOKEN) == true ||
|
|
301
|
+
config.query?.values?.any { it.contains(ID_TOKEN) } == true ||
|
|
302
|
+
config.headers?.values?.any { it.contains(ID_TOKEN) } == true
|
|
303
|
+
|
|
284
304
|
/** Builds a headers map, merging explicit headers with userAgent and contentType. */
|
|
285
305
|
private fun buildHeadersMap(
|
|
286
306
|
headers: Map<String, String>?,
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
package com.audiobrowser.browser
|
|
2
|
+
|
|
3
|
+
import com.audiobrowser.BuildConfig
|
|
4
|
+
import com.audiobrowser.util.toWireString
|
|
5
|
+
import com.margelo.nitro.audiobrowser.GridTile
|
|
6
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
7
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
8
|
+
import com.margelo.nitro.audiobrowser.StyleDisplay
|
|
9
|
+
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
10
|
+
import timber.log.Timber
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Dev diagnostic (ADR 0011): the declarations on a resolved page that can never render.
|
|
14
|
+
*
|
|
15
|
+
* The block model deliberately gave up compile-time invalid-combination errors — combinations are
|
|
16
|
+
* not invalid, they are *inert*, the way `grid-template` on a non-grid element is inert in CSS.
|
|
17
|
+
* This is the recovery: at page resolution every declaration its own block makes unreadable is
|
|
18
|
+
* reported once, with the effective context and the fix.
|
|
19
|
+
*
|
|
20
|
+
* The line it draws, and never crosses: only **structural** inertness — a declaration the resolved
|
|
21
|
+
* block itself renders unreadable, on every surface (`gridWrap` outside a grid, `cardTint` with no
|
|
22
|
+
* card treatment in scope). *Surface* inertness is never reported, because declarations are
|
|
23
|
+
* aspirational: `imageShape` on Android Auto, or a card treatment on a pre-26 CarPlay, is intended
|
|
24
|
+
* usage — the renderer drops what it can't draw.
|
|
25
|
+
*
|
|
26
|
+
* Runs where every page passes regardless of its source (static config, route callback, or fetched
|
|
27
|
+
* browse JSON), and only on a cache miss, so a mistake is reported once per resolution rather than
|
|
28
|
+
* once per serve. Mirrors `ios/Browser/InertStyleDiagnostic.swift`.
|
|
29
|
+
*
|
|
30
|
+
* Debug builds only: [warn] is the whole seam, so a release build never even walks the page (the
|
|
31
|
+
* trade is that a page served only to a release build — an internal track, production browse JSON —
|
|
32
|
+
* reports nothing; reproduce it against a debug build).
|
|
33
|
+
*/
|
|
34
|
+
object InertStyleDiagnostic {
|
|
35
|
+
/**
|
|
36
|
+
* Reports every inert declaration on a resolved page. The one gate — the [BuildConfig] check is
|
|
37
|
+
* here rather than at the call sites so callers read unconditionally and the rule set stays free
|
|
38
|
+
* of build-configuration knowledge.
|
|
39
|
+
*/
|
|
40
|
+
fun warn(path: String, pageStyle: SectionStyle?, sections: List<Section>) {
|
|
41
|
+
if (!BuildConfig.DEBUG) return
|
|
42
|
+
findings(path, pageStyle, sections).forEach { Timber.w(it) }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The declarations that carry a structural inertness condition.
|
|
47
|
+
*
|
|
48
|
+
* Two keys of the block are deliberately absent — `display` and `artworkRendering`, dispositioned
|
|
49
|
+
* below instead.
|
|
50
|
+
*/
|
|
51
|
+
private enum class Property(val key: String, val isItemProperty: Boolean) {
|
|
52
|
+
// Container properties resolve by scope override (`section ?? page`) and never reach an item;
|
|
53
|
+
// item properties inherit (`track ?? section ?? page`), so they may also be declared per track.
|
|
54
|
+
GRID_WRAP("gridWrap", isItemProperty = false),
|
|
55
|
+
GRID_TILE("gridTile", isItemProperty = false),
|
|
56
|
+
IMAGE_SHAPE("imageShape", isItemProperty = true),
|
|
57
|
+
ACCESSORY_SYMBOL("accessorySymbol", isItemProperty = true),
|
|
58
|
+
CARD_TINT("cardTint", isItemProperty = true),
|
|
59
|
+
CARD_IMAGE("cardImage", isItemProperty = true);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a value of this property, resolved into [context], has any rendering to affect. Each
|
|
63
|
+
* branch is the spec's own "inert unless" clause.
|
|
64
|
+
*/
|
|
65
|
+
fun isInert(context: Context): Boolean =
|
|
66
|
+
when (this) {
|
|
67
|
+
GRID_WRAP,
|
|
68
|
+
GRID_TILE -> !context.isGrid
|
|
69
|
+
IMAGE_SHAPE -> !context.isGrid || context.isCard
|
|
70
|
+
ACCESSORY_SYMBOL -> context.isCard
|
|
71
|
+
CARD_TINT,
|
|
72
|
+
CARD_IMAGE -> !context.isCard
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The rule the declaration falls foul of, stated positively. */
|
|
76
|
+
val rule: String
|
|
77
|
+
get() =
|
|
78
|
+
when (this) {
|
|
79
|
+
GRID_WRAP,
|
|
80
|
+
GRID_TILE -> "`$key` renders only in a grid"
|
|
81
|
+
IMAGE_SHAPE -> "only plain and condensed tiles have a shape"
|
|
82
|
+
ACCESSORY_SYMBOL -> "card tiles have no accessory slot"
|
|
83
|
+
CARD_TINT,
|
|
84
|
+
CARD_IMAGE -> "the card properties render only under `gridTile: 'card'`"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val hint: String
|
|
88
|
+
get() =
|
|
89
|
+
when (this) {
|
|
90
|
+
GRID_WRAP,
|
|
91
|
+
GRID_TILE -> "Declare `display: 'grid'` on the section or the page, or drop `$key`."
|
|
92
|
+
IMAGE_SHAPE -> "Render the items as plain or condensed grid tiles, or drop `imageShape`."
|
|
93
|
+
ACCESSORY_SYMBOL -> "Use `gridTile: 'plain'` or `'condensed'`, or drop `accessorySymbol`."
|
|
94
|
+
CARD_TINT,
|
|
95
|
+
CARD_IMAGE -> "Declare `display: 'grid'` with `gridTile: 'card'`, or drop `$key`."
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun declaredIn(style: SectionStyle?): Boolean =
|
|
99
|
+
when (this) {
|
|
100
|
+
GRID_WRAP -> style?.gridWrap != null
|
|
101
|
+
GRID_TILE -> style?.gridTile != null
|
|
102
|
+
IMAGE_SHAPE -> style?.imageShape != null
|
|
103
|
+
ACCESSORY_SYMBOL -> style?.accessorySymbol != null
|
|
104
|
+
CARD_TINT -> style?.cardTint != null
|
|
105
|
+
CARD_IMAGE -> style?.cardImage != null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Nitro flattens the spec's `extends`, so the two blocks share no type — hence two lookups over
|
|
109
|
+
// the same key set rather than one interface.
|
|
110
|
+
fun declaredIn(style: TrackStyle?): Boolean =
|
|
111
|
+
when (this) {
|
|
112
|
+
// Container properties have no Track slot to be declared in.
|
|
113
|
+
GRID_WRAP,
|
|
114
|
+
GRID_TILE -> false
|
|
115
|
+
IMAGE_SHAPE -> style?.imageShape != null
|
|
116
|
+
ACCESSORY_SYMBOL -> style?.accessorySymbol != null
|
|
117
|
+
CARD_TINT -> style?.cardTint != null
|
|
118
|
+
CARD_IMAGE -> style?.cardImage != null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The rest of the block's keys, dispositioned rather than ruled on: `artworkRendering` has no
|
|
124
|
+
* structural inertness condition (artwork renders in every layout), and `display` is positional —
|
|
125
|
+
* inert only in the one position that opens no page (see [playableDisplayFinding]). Declared here
|
|
126
|
+
* so the spec-level completeness test can hold this file to the whole vocabulary: every key of
|
|
127
|
+
* the block is a rule, always renderable, or positional, and a newly added property is a decision
|
|
128
|
+
* rather than an omission.
|
|
129
|
+
*/
|
|
130
|
+
val ALWAYS_RENDERABLE = listOf("artworkRendering")
|
|
131
|
+
|
|
132
|
+
val POSITIONAL = listOf("display")
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A section's effective container context: the two container values every inertness condition is
|
|
136
|
+
* stated against.
|
|
137
|
+
*
|
|
138
|
+
* Folded through [StyleResolver] rather than re-deriving the scope override: the diagnostic
|
|
139
|
+
* predicts what a renderer will do with these declarations, so it has to resolve them the way the
|
|
140
|
+
* renderers do.
|
|
141
|
+
*/
|
|
142
|
+
private class Context(section: SectionStyle?, page: SectionStyle?) {
|
|
143
|
+
private val folded = StyleResolver.sectionStyle(section, page)
|
|
144
|
+
val isGrid = folded.display == StyleDisplay.GRID
|
|
145
|
+
val tile = folded.gridTile ?: GridTile.PLAIN
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whether the card treatment is what this container renders — the `card*` family's condition.
|
|
149
|
+
*/
|
|
150
|
+
val isCard: Boolean
|
|
151
|
+
get() = isGrid && tile == GridTile.CARD
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The effective style, for the message — in the wire spelling the author types ([toWireString]
|
|
155
|
+
* is the decoder's own mapping). `gridTile` is quoted only where a grid gives it meaning:
|
|
156
|
+
* naming it on a list would be advice to fix the wrong property.
|
|
157
|
+
*/
|
|
158
|
+
val described: String
|
|
159
|
+
get() = if (!isGrid) "display 'list'" else "display 'grid', gridTile '${tile.toWireString()}'"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Every inert declaration on a resolved page, as ready-to-log messages — pure, so the whole rule
|
|
164
|
+
* set is testable off-device; the caller logs.
|
|
165
|
+
*
|
|
166
|
+
* @param path the resolved path, named in every message.
|
|
167
|
+
* @param pageStyle the page's own block (`ResolvedTrack.style`).
|
|
168
|
+
* @param sections the page's normalized sections, blocks *unfolded* — the diagnostic attributes
|
|
169
|
+
* each finding to the level that declared it, so it must see the levels before [StyleResolver]
|
|
170
|
+
* merges them.
|
|
171
|
+
*/
|
|
172
|
+
fun findings(path: String, pageStyle: SectionStyle?, sections: List<Section>): List<String> {
|
|
173
|
+
val findings = mutableListOf<String>()
|
|
174
|
+
// A page-level declaration is judged across the whole page: it is live if it renders in any
|
|
175
|
+
// section it reaches, however many others ignore it.
|
|
176
|
+
val pageReached = mutableSetOf<Property>()
|
|
177
|
+
val pageRendered = mutableSetOf<Property>()
|
|
178
|
+
|
|
179
|
+
sections.forEachIndexed { index, section ->
|
|
180
|
+
val context = Context(section.style, pageStyle)
|
|
181
|
+
val sectionLabel = label(section, index)
|
|
182
|
+
|
|
183
|
+
for (property in Property.entries) {
|
|
184
|
+
val inert = property.isInert(context)
|
|
185
|
+
|
|
186
|
+
if (property.declaredIn(section.style)) {
|
|
187
|
+
if (inert) {
|
|
188
|
+
findings +=
|
|
189
|
+
message(property, path, sectionLabel, "its effective style is ${context.described}")
|
|
190
|
+
}
|
|
191
|
+
} else if (property.declaredIn(pageStyle)) {
|
|
192
|
+
// The section takes the page's value (nothing closer overrode it).
|
|
193
|
+
pageReached += property
|
|
194
|
+
if (!inert) pageRendered += property
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!property.isItemProperty || !inert) continue
|
|
198
|
+
val tracks = section.children.count { property.declaredIn(it.style) }
|
|
199
|
+
if (tracks > 0) {
|
|
200
|
+
findings +=
|
|
201
|
+
message(
|
|
202
|
+
property,
|
|
203
|
+
path,
|
|
204
|
+
"${trackCount(tracks)} in $sectionLabel",
|
|
205
|
+
"the section's effective style is ${context.described}",
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
playableDisplayFinding(section, path, sectionLabel)?.let { findings += it }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (property in Property.entries) {
|
|
214
|
+
if (property in pageReached && property !in pageRendered) {
|
|
215
|
+
findings +=
|
|
216
|
+
message(property, path, "the page block", "no section that inherits it renders one")
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return findings
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* `display` is positional, so it is never inert *in* a container — but on a track that renders
|
|
225
|
+
* playable (`src` wins the rendering) it promises the layout of a page that never opens.
|
|
226
|
+
*/
|
|
227
|
+
private fun playableDisplayFinding(section: Section, path: String, label: String): String? {
|
|
228
|
+
val tracks = section.children.count { it.src != null && it.style?.display != null }
|
|
229
|
+
if (tracks == 0) return null
|
|
230
|
+
return message(
|
|
231
|
+
path = path,
|
|
232
|
+
owner = "${trackCount(tracks)} in $label",
|
|
233
|
+
key = "display",
|
|
234
|
+
reason =
|
|
235
|
+
"`display` is the layout promise for the page a track opens, and a track rendered playable " +
|
|
236
|
+
"opens none",
|
|
237
|
+
hint = "Drop `display`, or give the track a `path` if it should be browsable.",
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private fun message(property: Property, path: String, owner: String, scope: String): String =
|
|
242
|
+
message(
|
|
243
|
+
path = path,
|
|
244
|
+
owner = owner,
|
|
245
|
+
key = property.key,
|
|
246
|
+
reason = "${property.rule}, but $scope",
|
|
247
|
+
hint = property.hint,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The one message skeleton — every finding names the page, the declaration, who declared it, why
|
|
252
|
+
* it can't render, and what to do about it.
|
|
253
|
+
*/
|
|
254
|
+
private fun message(
|
|
255
|
+
path: String,
|
|
256
|
+
owner: String,
|
|
257
|
+
key: String,
|
|
258
|
+
reason: String,
|
|
259
|
+
hint: String,
|
|
260
|
+
): String =
|
|
261
|
+
"Inert style declaration on page '$path': `style.$key` on $owner never renders — $reason. $hint"
|
|
262
|
+
|
|
263
|
+
private fun trackCount(count: Int): String = "$count ${if (count == 1) "track" else "tracks"}"
|
|
264
|
+
|
|
265
|
+
private fun label(section: Section, index: Int): String =
|
|
266
|
+
section.title?.takeIf { it.isNotEmpty() }?.let { "section '$it'" } ?: "section #${index + 1}"
|
|
267
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
+
import com.audiobrowser.util.EMPTY_SECTION_STYLE
|
|
4
|
+
import com.audiobrowser.util.EMPTY_TRACK_STYLE
|
|
5
|
+
import com.audiobrowser.util.toArtworkRendering
|
|
6
|
+
import com.audiobrowser.util.toCardImage
|
|
7
|
+
import com.audiobrowser.util.toGridTile
|
|
8
|
+
import com.audiobrowser.util.toImageShape
|
|
9
|
+
import com.audiobrowser.util.toStyleDisplay
|
|
3
10
|
import com.margelo.nitro.audiobrowser.CarPlaySiriListButtonPosition
|
|
4
|
-
import com.margelo.nitro.audiobrowser.ImageRowItem
|
|
5
11
|
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
12
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
13
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
6
14
|
import com.margelo.nitro.audiobrowser.Track
|
|
7
15
|
import com.margelo.nitro.audiobrowser.TrackRequest
|
|
8
16
|
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
9
17
|
import kotlinx.serialization.Serializable
|
|
18
|
+
import kotlinx.serialization.json.JsonElement
|
|
19
|
+
import kotlinx.serialization.json.JsonObject
|
|
20
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
21
|
+
import kotlinx.serialization.json.booleanOrNull
|
|
10
22
|
|
|
11
23
|
/**
|
|
12
24
|
* JSON serializable models for parsing API responses. These will be converted to Nitro types after
|
|
13
|
-
* parsing.
|
|
25
|
+
* parsing. Legacy `groupTitle`/`imageRow` keys in payloads are simply unknown to these models and
|
|
26
|
+
* decode as ignored dead weight (ADR 0010).
|
|
14
27
|
*/
|
|
15
|
-
@Serializable
|
|
16
|
-
data class JsonImageRowItem(
|
|
17
|
-
val id: String? = null,
|
|
18
|
-
val url: String? = null,
|
|
19
|
-
val src: String? = null,
|
|
20
|
-
val artwork: String? = null,
|
|
21
|
-
val title: String,
|
|
22
|
-
val artist: String? = null,
|
|
23
|
-
val album: String? = null,
|
|
24
|
-
val albumUrl: String? = null,
|
|
25
|
-
val live: Boolean? = null,
|
|
26
|
-
val request: JsonTrackRequest? = null,
|
|
27
|
-
)
|
|
28
28
|
|
|
29
29
|
/** JSON model for a track's per-request HTTP override (identity/auth/signed-url). */
|
|
30
30
|
@Serializable
|
|
@@ -36,25 +36,35 @@ data class JsonTrackRequest(
|
|
|
36
36
|
fun toNitro(): TrackRequest = TrackRequest(userAgent, headers, query)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** JSON model for a page section (ADR 0010). */
|
|
40
|
+
@Serializable
|
|
41
|
+
data class JsonSection(
|
|
42
|
+
val title: String? = null,
|
|
43
|
+
val subtitle: String? = null,
|
|
44
|
+
val style: JsonElement? = null,
|
|
45
|
+
val path: String? = null,
|
|
46
|
+
val children: List<JsonTrack>,
|
|
47
|
+
)
|
|
48
|
+
|
|
39
49
|
@Serializable
|
|
40
50
|
data class JsonResolvedTrack(
|
|
41
51
|
val id: String? = null,
|
|
42
|
-
val
|
|
52
|
+
val path: String,
|
|
43
53
|
val title: String,
|
|
44
54
|
val subtitle: String? = null,
|
|
45
55
|
val icon: String? = null,
|
|
46
56
|
val artwork: JsonArtwork? = null,
|
|
47
57
|
val artist: String? = null,
|
|
48
|
-
val
|
|
58
|
+
val albumPath: String? = null,
|
|
49
59
|
val album: String? = null,
|
|
50
60
|
val description: String? = null,
|
|
51
61
|
val genre: String? = null,
|
|
52
62
|
val duration: Double? = null,
|
|
63
|
+
val sections: List<JsonSection>? = null,
|
|
53
64
|
val children: List<JsonTrack>? = null,
|
|
54
65
|
val src: String? = null,
|
|
55
|
-
val style:
|
|
56
|
-
val
|
|
57
|
-
val groupTitle: String? = null,
|
|
66
|
+
val style: JsonElement? = null,
|
|
67
|
+
val disabled: Boolean? = null,
|
|
58
68
|
val live: Boolean? = null,
|
|
59
69
|
val carPlaySiriListButton: String? = null,
|
|
60
70
|
)
|
|
@@ -62,34 +72,72 @@ data class JsonResolvedTrack(
|
|
|
62
72
|
@Serializable
|
|
63
73
|
data class JsonTrack(
|
|
64
74
|
val id: String? = null,
|
|
65
|
-
val
|
|
75
|
+
val path: String? = null,
|
|
66
76
|
val title: String,
|
|
67
77
|
val subtitle: String? = null,
|
|
68
78
|
val icon: String? = null,
|
|
69
79
|
val artwork: JsonArtwork? = null,
|
|
70
80
|
val artist: String? = null,
|
|
71
|
-
val
|
|
81
|
+
val albumPath: String? = null,
|
|
72
82
|
val album: String? = null,
|
|
73
83
|
val description: String? = null,
|
|
74
84
|
val genre: String? = null,
|
|
75
85
|
val duration: Double? = null,
|
|
76
86
|
val src: String? = null,
|
|
77
87
|
val request: JsonTrackRequest? = null,
|
|
78
|
-
val style:
|
|
79
|
-
val
|
|
80
|
-
val groupTitle: String? = null,
|
|
88
|
+
val style: JsonElement? = null,
|
|
89
|
+
val disabled: Boolean? = null,
|
|
81
90
|
val live: Boolean? = null,
|
|
82
|
-
val imageRow: List<JsonImageRowItem>? = null,
|
|
83
91
|
)
|
|
84
92
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Convert JSON models to Nitro types.
|
|
95
|
+
*
|
|
96
|
+
* `style` rides the models as a raw [JsonElement] and maps leniently here — tolerant decoding by
|
|
97
|
+
* design (ADR 0011): a stale value (the retired string vocabulary), a wrong-typed field, or an
|
|
98
|
+
* unknown enum value is "no declaration", never a parse failure. A typed field would fail the whole
|
|
99
|
+
* page, since this decoder is otherwise strict.
|
|
100
|
+
*/
|
|
101
|
+
private fun JsonObject.stringField(key: String): String? =
|
|
102
|
+
(get(key) as? JsonPrimitive)?.takeIf { it.isString }?.content
|
|
103
|
+
|
|
104
|
+
/** A strict JSON boolean — a quoted "false" is a wrong-typed field, i.e. no declaration. */
|
|
105
|
+
private fun JsonObject.booleanField(key: String): Boolean? =
|
|
106
|
+
(get(key) as? JsonPrimitive)?.takeIf { !it.isString }?.booleanOrNull
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A block that resolves to no declarations at all — `{}` or all-unknown values — maps to null, so
|
|
110
|
+
* "no declaration" has one shape on every platform (iOS makes the same collapse).
|
|
111
|
+
*/
|
|
112
|
+
private fun JsonElement?.toTrackStyle(): TrackStyle? =
|
|
113
|
+
(this as? JsonObject)?.let { obj ->
|
|
114
|
+
TrackStyle(
|
|
115
|
+
display = obj.stringField("display").toStyleDisplay(),
|
|
116
|
+
artworkRendering = obj.stringField("artworkRendering").toArtworkRendering(),
|
|
117
|
+
imageShape = obj.stringField("imageShape").toImageShape(),
|
|
118
|
+
// 'none' is a legitimate accessory value (the inheritance escape);
|
|
119
|
+
// only emptiness collapses.
|
|
120
|
+
accessorySymbol = obj.stringField("accessorySymbol")?.takeIf { it.isNotEmpty() },
|
|
121
|
+
cardTint = obj.stringField("cardTint")?.takeIf { it.isNotEmpty() },
|
|
122
|
+
cardImage = obj.stringField("cardImage").toCardImage(),
|
|
123
|
+
)
|
|
124
|
+
.takeIf { it != EMPTY_TRACK_STYLE }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private fun JsonElement?.toSectionStyle(): SectionStyle? =
|
|
128
|
+
(this as? JsonObject)?.let { obj ->
|
|
129
|
+
SectionStyle(
|
|
130
|
+
display = obj.stringField("display").toStyleDisplay(),
|
|
131
|
+
artworkRendering = obj.stringField("artworkRendering").toArtworkRendering(),
|
|
132
|
+
imageShape = obj.stringField("imageShape").toImageShape(),
|
|
133
|
+
accessorySymbol = obj.stringField("accessorySymbol")?.takeIf { it.isNotEmpty() },
|
|
134
|
+
cardTint = obj.stringField("cardTint")?.takeIf { it.isNotEmpty() },
|
|
135
|
+
cardImage = obj.stringField("cardImage").toCardImage(),
|
|
136
|
+
gridWrap = obj.booleanField("gridWrap"),
|
|
137
|
+
gridTile = obj.stringField("gridTile").toGridTile(),
|
|
138
|
+
)
|
|
139
|
+
.takeIf { it != EMPTY_SECTION_STYLE }
|
|
91
140
|
}
|
|
92
|
-
}
|
|
93
141
|
|
|
94
142
|
private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosition? {
|
|
95
143
|
return when (this?.lowercase()) {
|
|
@@ -99,26 +147,21 @@ private fun String?.toCarPlaySiriListButtonPosition(): CarPlaySiriListButtonPosi
|
|
|
99
147
|
}
|
|
100
148
|
}
|
|
101
149
|
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
id = id,
|
|
105
|
-
url = url,
|
|
106
|
-
src = src,
|
|
107
|
-
artwork = artwork,
|
|
108
|
-
artworkSource = null,
|
|
150
|
+
fun JsonSection.toNitro(): Section {
|
|
151
|
+
return Section(
|
|
109
152
|
title = title,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
request = request?.toNitro(),
|
|
153
|
+
subtitle = subtitle,
|
|
154
|
+
style = style.toSectionStyle(),
|
|
155
|
+
path = path,
|
|
156
|
+
children = children.map { it.toNitro() }.toTypedArray(),
|
|
115
157
|
)
|
|
116
158
|
}
|
|
117
159
|
|
|
118
160
|
fun JsonResolvedTrack.toNitro(): ResolvedTrack {
|
|
119
161
|
return ResolvedTrack(
|
|
120
162
|
id = id,
|
|
121
|
-
|
|
163
|
+
path = path,
|
|
164
|
+
sections = sections?.map { it.toNitro() }?.toTypedArray(),
|
|
122
165
|
children = children?.map { it.toNitro() }?.toTypedArray(),
|
|
123
166
|
carPlaySiriListButton = carPlaySiriListButton.toCarPlaySiriListButtonPosition(),
|
|
124
167
|
title = title,
|
|
@@ -127,45 +170,39 @@ fun JsonResolvedTrack.toNitro(): ResolvedTrack {
|
|
|
127
170
|
artworkSource = null,
|
|
128
171
|
// Containers carry no per-track media-request override (mirrors iOS JsonResolvedTrack).
|
|
129
172
|
request = null,
|
|
130
|
-
artworkCarPlayTinted = null,
|
|
131
173
|
artist = artist,
|
|
132
|
-
|
|
174
|
+
albumPath = albumPath,
|
|
133
175
|
album = album,
|
|
134
176
|
description = description,
|
|
135
177
|
genre = genre,
|
|
136
178
|
duration = duration,
|
|
137
179
|
src = src,
|
|
138
|
-
style = style.
|
|
139
|
-
|
|
180
|
+
style = style.toSectionStyle(),
|
|
181
|
+
disabled = disabled,
|
|
140
182
|
favorited = null,
|
|
141
|
-
groupTitle = groupTitle,
|
|
142
183
|
live = live,
|
|
143
|
-
imageRow = null,
|
|
144
184
|
)
|
|
145
185
|
}
|
|
146
186
|
|
|
147
187
|
fun JsonTrack.toNitro(): Track {
|
|
148
188
|
return Track(
|
|
149
189
|
id = id,
|
|
150
|
-
|
|
190
|
+
path = path,
|
|
151
191
|
title = title,
|
|
152
192
|
subtitle = subtitle,
|
|
153
193
|
artwork = artwork?.toNitro(),
|
|
154
194
|
artworkSource = null,
|
|
155
195
|
request = request?.toNitro(),
|
|
156
|
-
artworkCarPlayTinted = null,
|
|
157
196
|
artist = artist,
|
|
158
|
-
|
|
197
|
+
albumPath = albumPath,
|
|
159
198
|
album = album,
|
|
160
199
|
description = description,
|
|
161
200
|
genre = genre,
|
|
162
201
|
duration = duration,
|
|
163
202
|
src = src,
|
|
164
203
|
style = style.toTrackStyle(),
|
|
165
|
-
|
|
204
|
+
disabled = disabled,
|
|
166
205
|
favorited = null,
|
|
167
|
-
groupTitle = groupTitle,
|
|
168
206
|
live = live,
|
|
169
|
-
imageRow = imageRow?.map { it.toNitro() }?.toTypedArray(),
|
|
170
207
|
)
|
|
171
208
|
}
|