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
|
@@ -1,42 +1,84 @@
|
|
|
1
1
|
package com.audiobrowser.browser
|
|
2
2
|
|
|
3
|
-
import com.
|
|
3
|
+
import com.audiobrowser.extension.identity
|
|
4
|
+
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
5
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
4
6
|
import com.margelo.nitro.audiobrowser.Track
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Queue scope is the tapped section, not the whole page (ADR 0006): the playback context a listener
|
|
8
10
|
* expects is the list they tapped in — a page aggregating several sections must not leak
|
|
9
|
-
* next/previous across them.
|
|
11
|
+
* next/previous across them. Sections are structural (ADR 0010), so scoping is a lookup, not a
|
|
12
|
+
* groupTitle-run derivation.
|
|
10
13
|
*/
|
|
11
14
|
object SectionScope {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* The contiguous `groupTitle` run around the tapped child (items with no group title form runs
|
|
18
|
-
* of their own).
|
|
19
|
-
*/
|
|
20
|
-
data class Run(val tracks: List<Track>) : Section()
|
|
21
|
-
}
|
|
15
|
+
/**
|
|
16
|
+
* The section containing the tapped identity, plus the tapped child's offset within it — non-null
|
|
17
|
+
* only when the stamped flat index pinned the exact copy.
|
|
18
|
+
*/
|
|
19
|
+
data class Scoped(val section: Section, val tappedOffset: Int?)
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
|
-
* The section of [
|
|
22
|
+
* The section of [sections] containing the playable [trackId] (a track identity — id when
|
|
23
|
+
* non-blank, else src), or null when not found.
|
|
24
|
+
*
|
|
25
|
+
* [tappedIndex] — the flat page position stamped into the contextual URL (children concatenated
|
|
26
|
+
* in section order) — is a tie-breaker, never an identifier: when the child at that position
|
|
27
|
+
* still carries the tapped identity, it pins which section (and which copy) was tapped; when it
|
|
28
|
+
* doesn't (the list shifted), resolution falls back to the first section containing the identity.
|
|
29
|
+
* A stale index can therefore never select a different track — at worst a different copy of the
|
|
30
|
+
* same one.
|
|
25
31
|
*/
|
|
26
|
-
fun
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
fun scoped(sections: List<Section>, trackId: String, tappedIndex: Int? = null): Scoped? {
|
|
33
|
+
if (tappedIndex != null && tappedIndex >= 0) {
|
|
34
|
+
var start = 0
|
|
35
|
+
for (section in sections) {
|
|
36
|
+
val offset = tappedIndex - start
|
|
37
|
+
if (offset < section.children.size) {
|
|
38
|
+
if (section.children[offset].identity == trackId) {
|
|
39
|
+
return Scoped(section, offset)
|
|
40
|
+
}
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
start += section.children.size
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (section in sections) {
|
|
47
|
+
if (section.children.any { it.identity == trackId }) {
|
|
48
|
+
return Scoped(section, null)
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
|
-
|
|
34
|
-
if (index < 0) return null
|
|
35
|
-
val group = children[index].groupTitle
|
|
36
|
-
var start = index
|
|
37
|
-
while (start > 0 && children[start - 1].groupTitle == group) start--
|
|
38
|
-
var end = index
|
|
39
|
-
while (end + 1 < children.size && children[end + 1].groupTitle == group) end++
|
|
40
|
-
return Section.Run(children.subList(start, end + 1).toList())
|
|
51
|
+
return null
|
|
41
52
|
}
|
|
42
53
|
}
|
|
54
|
+
|
|
55
|
+
// Page shape helpers (ADR 0010)
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* One untitled section — the canonical wrap of a flat track list (`children` authoring sugar,
|
|
59
|
+
* search results — ADR 0010).
|
|
60
|
+
*/
|
|
61
|
+
fun untitledSection(children: Array<Track>): Section =
|
|
62
|
+
Section(title = null, subtitle = null, style = null, path = null, children = children)
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The canonical sectioned shape: `sections` wins when present; plain `children` is authoring sugar
|
|
66
|
+
* for one untitled section.
|
|
67
|
+
*/
|
|
68
|
+
val ResolvedTrack.normalizedSections: List<Section>?
|
|
69
|
+
get() = sections?.toList() ?: children?.let { listOf(untitledSection(it)) }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* [normalizedSections] with the page block folded in: each section's style resolves `section ??
|
|
73
|
+
* page` per property ([StyleResolver], ADR 0011), so everything downstream sees only effective
|
|
74
|
+
* section blocks — a page is a Track that is also the container of its sections.
|
|
75
|
+
*/
|
|
76
|
+
fun ResolvedTrack.styleResolvedSections(): List<Section>? =
|
|
77
|
+
normalizedSections?.map { it.copy(style = StyleResolver.sectionStyle(it.style, style)) }
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The page's children concatenated in section order — the flattening that defines contextual
|
|
81
|
+
* `__index` positions and the flat views (tabs, search) of a sectioned page.
|
|
82
|
+
*/
|
|
83
|
+
val ResolvedTrack.flattenedChildren: List<Track>?
|
|
84
|
+
get() = normalizedSections?.flatMap { it.children.asList() }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package com.audiobrowser.browser
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.SectionStyle
|
|
4
|
+
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The one place style declarations resolve to effective values (ADR 0011).
|
|
8
|
+
*
|
|
9
|
+
* Two mechanisms, deliberately distinct:
|
|
10
|
+
* - **Inherited item properties** resolve `track ?? section ?? page`, per property — a track's own
|
|
11
|
+
* declaration wins over the blocks that contain it.
|
|
12
|
+
* - **Container properties and the positional `display`** resolve by scope override: `section ??
|
|
13
|
+
* page` — the page declares for its whole scope, a section overrides for its own children.
|
|
14
|
+
* `display` is never inherited item-to-container: each holder describes its own children, and a
|
|
15
|
+
* track's `display` is the promise for the page *it* opens, not its rendering inside this
|
|
16
|
+
* section.
|
|
17
|
+
*
|
|
18
|
+
* Every key of `SectionStyle` must be read by [sectionStyle] and every inherited key of
|
|
19
|
+
* `TrackStyle` by [trackStyle] — Nitro flattens the spec's `extends`, so
|
|
20
|
+
* `src/style-resolution.test.ts` enforces this completeness, not the type system.
|
|
21
|
+
*/
|
|
22
|
+
object StyleResolver {
|
|
23
|
+
/**
|
|
24
|
+
* Folds the page block into a section's block: every `SectionStyle` key resolves `section ??
|
|
25
|
+
* page`. The result is the section's effective block; item resolution then only needs `track ??
|
|
26
|
+
* section`.
|
|
27
|
+
*/
|
|
28
|
+
fun sectionStyle(section: SectionStyle?, page: SectionStyle?): SectionStyle =
|
|
29
|
+
SectionStyle(
|
|
30
|
+
display = section?.display ?: page?.display,
|
|
31
|
+
artworkRendering = section?.artworkRendering ?: page?.artworkRendering,
|
|
32
|
+
imageShape = section?.imageShape ?: page?.imageShape,
|
|
33
|
+
accessorySymbol = section?.accessorySymbol ?: page?.accessorySymbol,
|
|
34
|
+
cardTint = section?.cardTint ?: page?.cardTint,
|
|
35
|
+
cardImage = section?.cardImage ?: page?.cardImage,
|
|
36
|
+
gridWrap = section?.gridWrap ?: page?.gridWrap,
|
|
37
|
+
gridTile = section?.gridTile ?: page?.gridTile,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolves a track's effective item properties against its (page-folded) section block.
|
|
42
|
+
*
|
|
43
|
+
* No Android renderer consumes the result yet — today's only inherited property
|
|
44
|
+
* (`artworkRendering`) is CarPlay-rendered — but the resolver ships on both platforms so the
|
|
45
|
+
* completeness test can hold every future inherited key to the same rule from day one.
|
|
46
|
+
*/
|
|
47
|
+
fun trackStyle(track: TrackStyle?, section: SectionStyle?): TrackStyle =
|
|
48
|
+
TrackStyle(
|
|
49
|
+
// Positional deny-list: `display` is never inherited onto an item — resolved null, so no
|
|
50
|
+
// renderer can mistake the handle's page promise for this item's own layout.
|
|
51
|
+
display = null,
|
|
52
|
+
artworkRendering = track?.artworkRendering ?: section?.artworkRendering,
|
|
53
|
+
imageShape = track?.imageShape ?: section?.imageShape,
|
|
54
|
+
// 'none' resolves like any value — a renderer treats it as "no accessory, derived
|
|
55
|
+
// behavior" (the inheritance escape). No Android surface draws these properties today.
|
|
56
|
+
accessorySymbol = track?.accessorySymbol ?: section?.accessorySymbol,
|
|
57
|
+
cardTint = track?.cardTint ?: section?.cardTint,
|
|
58
|
+
cardImage = track?.cardImage ?: section?.cardImage,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -2,6 +2,7 @@ package com.audiobrowser.extension
|
|
|
2
2
|
|
|
3
3
|
import com.margelo.nitro.audiobrowser.ResolvedTrack
|
|
4
4
|
import com.margelo.nitro.audiobrowser.Track
|
|
5
|
+
import com.margelo.nitro.audiobrowser.TrackStyle
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* The Track view of a ResolvedTrack: every display/playback field carried over, the browse-only
|
|
@@ -12,24 +13,35 @@ import com.margelo.nitro.audiobrowser.Track
|
|
|
12
13
|
fun ResolvedTrack.toTrack(): Track =
|
|
13
14
|
Track(
|
|
14
15
|
id = id,
|
|
15
|
-
|
|
16
|
+
path = path,
|
|
16
17
|
src = src,
|
|
17
18
|
artwork = artwork,
|
|
18
19
|
artworkSource = artworkSource,
|
|
19
20
|
request = request,
|
|
20
|
-
artworkCarPlayTinted = artworkCarPlayTinted,
|
|
21
21
|
title = title,
|
|
22
22
|
subtitle = subtitle,
|
|
23
23
|
artist = artist,
|
|
24
|
-
|
|
24
|
+
albumPath = albumPath,
|
|
25
25
|
album = album,
|
|
26
26
|
description = description,
|
|
27
27
|
genre = genre,
|
|
28
28
|
duration = duration,
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// An explicit SectionStyle → TrackStyle projection: the page's container
|
|
30
|
+
// properties (gridWrap) have no meaning on a plain Track, so they drop —
|
|
31
|
+
// Nitro flattens the spec's `extends`, making this narrowing a hand-written
|
|
32
|
+
// field list rather than an upcast (ADR 0011).
|
|
33
|
+
style =
|
|
34
|
+
style?.let {
|
|
35
|
+
TrackStyle(
|
|
36
|
+
display = it.display,
|
|
37
|
+
artworkRendering = it.artworkRendering,
|
|
38
|
+
imageShape = it.imageShape,
|
|
39
|
+
accessorySymbol = it.accessorySymbol,
|
|
40
|
+
cardTint = it.cardTint,
|
|
41
|
+
cardImage = it.cardImage,
|
|
42
|
+
)
|
|
43
|
+
},
|
|
44
|
+
disabled = disabled,
|
|
31
45
|
favorited = favorited,
|
|
32
|
-
groupTitle = groupTitle,
|
|
33
46
|
live = live,
|
|
34
|
-
imageRow = imageRow,
|
|
35
47
|
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.audiobrowser.extension
|
|
2
|
+
|
|
3
|
+
import com.audiobrowser.util.BrowserPathHelper
|
|
4
|
+
import com.margelo.nitro.audiobrowser.Track
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The queue position of the tapped surface, for skip-in-place. Exact-surface match first: a
|
|
8
|
+
* contextual path carries the tapped page position (`__index`), so path equality pins the exact
|
|
9
|
+
* copy when the page holds the same identity more than once. The identity match remains for
|
|
10
|
+
* index-less paths (e.g. pre-stamp persisted state); an index-stamped path with no exact match
|
|
11
|
+
* returns -1, and the caller falls through to expansion, which re-scopes the queue to the tapped
|
|
12
|
+
* section (ADR 0009).
|
|
13
|
+
*/
|
|
14
|
+
fun Array<Track>.indexOfTappedTrack(contextualPath: String, trackId: String?): Int {
|
|
15
|
+
val index = indexOfFirst { it.path == contextualPath }
|
|
16
|
+
if (index >= 0 || BrowserPathHelper.extractIndex(contextualPath) != null || trackId == null) {
|
|
17
|
+
return index
|
|
18
|
+
}
|
|
19
|
+
return indexOfFirst { it.identity == trackId }
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.audiobrowser.extension
|
|
2
|
+
|
|
3
|
+
import com.margelo.nitro.audiobrowser.Track
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A track's identity: the opaque `id` when present (non-blank), else the playable `src`. Two tracks
|
|
7
|
+
* refer to the same item iff their identities are equal. Browsable-only tracks (neither `id` nor
|
|
8
|
+
* `src`) have no identity — they are addressed by `path` instead.
|
|
9
|
+
*
|
|
10
|
+
* This is THE comparison rule for favorites matching, section scoping, skip-in-place, the car
|
|
11
|
+
* now-playing row indicator, and the contextual `__trackId` — see ADR 0008. Mirrors the TS
|
|
12
|
+
* `trackIdentity` helper.
|
|
13
|
+
*/
|
|
14
|
+
val Track.identity: String?
|
|
15
|
+
get() = id?.takeUnless { it.isBlank() } ?: src
|
|
@@ -20,7 +20,7 @@ data class PlayerUpdateOptions(
|
|
|
20
20
|
var progressUpdateEventInterval: Double? = null,
|
|
21
21
|
|
|
22
22
|
// Player capabilities - most enabled by default, only false values disable
|
|
23
|
-
// Exceptions:
|
|
23
|
+
// Exceptions: jumpForward, jumpBackward default to false
|
|
24
24
|
var capabilities: PlayerCapabilities =
|
|
25
25
|
PlayerCapabilities(
|
|
26
26
|
play = null,
|
|
@@ -12,6 +12,10 @@ import androidx.media3.session.SessionCommand
|
|
|
12
12
|
import androidx.media3.session.SessionError
|
|
13
13
|
import androidx.media3.session.SessionResult
|
|
14
14
|
import com.audiobrowser.browser.handleTrackLoad
|
|
15
|
+
import com.audiobrowser.browser.normalizedSections
|
|
16
|
+
import com.audiobrowser.browser.styleResolvedSections
|
|
17
|
+
import com.audiobrowser.browser.untitledSection
|
|
18
|
+
import com.audiobrowser.extension.indexOfTappedTrack
|
|
15
19
|
import com.audiobrowser.extension.toTrack
|
|
16
20
|
import com.audiobrowser.util.BrowserPathHelper
|
|
17
21
|
import com.audiobrowser.util.RatingFavorites
|
|
@@ -27,6 +31,8 @@ import com.margelo.nitro.audiobrowser.NativeGateRequest
|
|
|
27
31
|
import com.margelo.nitro.audiobrowser.PlayerCapabilities
|
|
28
32
|
import com.margelo.nitro.audiobrowser.RemoteButtonLayout
|
|
29
33
|
import com.margelo.nitro.audiobrowser.SearchParams
|
|
34
|
+
import com.margelo.nitro.audiobrowser.Section
|
|
35
|
+
import com.margelo.nitro.audiobrowser.StyleDisplay
|
|
30
36
|
import com.margelo.nitro.audiobrowser.Track
|
|
31
37
|
import kotlinx.coroutines.CancellationException
|
|
32
38
|
import kotlinx.coroutines.CoroutineScope
|
|
@@ -191,12 +197,17 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
191
197
|
command: SessionCommand,
|
|
192
198
|
args: Bundle,
|
|
193
199
|
): ListenableFuture<SessionResult> {
|
|
194
|
-
// Handle favorite button tap
|
|
200
|
+
// Handle favorite button tap. Report an honest result: a success here makes
|
|
201
|
+
// the controller flip its heart optimistically, so a no-op (no current
|
|
202
|
+
// track) must say INVALID_STATE instead of success-then-revert.
|
|
195
203
|
if (command.customAction == MediaSessionCommandManager.CUSTOM_ACTION_FAVORITE) {
|
|
196
|
-
val
|
|
197
|
-
Timber.d("Favorite button tapped -
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
val applied = player.toggleActiveTrackFavorited()
|
|
205
|
+
Timber.d("Favorite button tapped - toggle applied=$applied")
|
|
206
|
+
return Futures.immediateFuture(
|
|
207
|
+
SessionResult(
|
|
208
|
+
if (applied) SessionResult.RESULT_SUCCESS else SessionResult.RESULT_ERROR_INVALID_STATE
|
|
209
|
+
)
|
|
210
|
+
)
|
|
200
211
|
}
|
|
201
212
|
|
|
202
213
|
if (commandManager.handleCustomCommand(command, player)) {
|
|
@@ -212,8 +223,16 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
212
223
|
): ListenableFuture<SessionResult> {
|
|
213
224
|
// A heart rating from a controller (e.g. Google Assistant "I like this") toggles the
|
|
214
225
|
// now-playing favorite. setActiveTrackFavorited fires onFavoriteChanged so the consumer
|
|
215
|
-
// persists it — the same path as the notification / CarPlay heart button.
|
|
216
|
-
|
|
226
|
+
// persists it — the same path as the notification / CarPlay heart button. Report an honest
|
|
227
|
+
// result (see onCustomCommand): INVALID_STATE when there is no current track to favorite.
|
|
228
|
+
RatingFavorites.favoritedFor(rating)?.let { favorited ->
|
|
229
|
+
val applied = player.setActiveTrackFavorited(favorited)
|
|
230
|
+
return Futures.immediateFuture(
|
|
231
|
+
SessionResult(
|
|
232
|
+
if (applied) SessionResult.RESULT_SUCCESS else SessionResult.RESULT_ERROR_INVALID_STATE
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
}
|
|
217
236
|
return super.onSetRating(session, controller, rating)
|
|
218
237
|
}
|
|
219
238
|
|
|
@@ -238,13 +257,11 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
238
257
|
}
|
|
239
258
|
|
|
240
259
|
if (params?.isRecent == true) {
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
// an error of type 'RESULT_ERROR_NOT_SUPPORTED' for a `params.isRecent` request. See
|
|
247
|
-
// https://github.com/androidx/media/issues/355
|
|
260
|
+
// Playback resumption is served by media3 itself: it intercepts System UI's EXTRA_RECENT
|
|
261
|
+
// root request internally and answers through onPlaybackResumption, so that request never
|
|
262
|
+
// reaches this callback. Any other browser asking for a recent root gets
|
|
263
|
+
// ERROR_NOT_SUPPORTED — there is no browsable recents tree; recents are ordinary consumer
|
|
264
|
+
// content (a tab or route).
|
|
248
265
|
return Futures.immediateFuture(LibraryResult.ofError(SessionError.ERROR_NOT_SUPPORTED))
|
|
249
266
|
}
|
|
250
267
|
|
|
@@ -271,7 +288,7 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
271
288
|
params: MediaLibraryService.LibraryParams?,
|
|
272
289
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
|
273
290
|
Timber.d(
|
|
274
|
-
"onGetChildren: {parentId: $parentId, page: $page, pageSize: $pageSize,
|
|
291
|
+
"onGetChildren: {parentId: $parentId, page: $page, pageSize: $pageSize, isSpecialPath: ${BrowserPathHelper.isSpecialPath(parentId)} }"
|
|
275
292
|
)
|
|
276
293
|
return scope.future {
|
|
277
294
|
// Wait for browser to be registered if it's not available yet
|
|
@@ -325,30 +342,28 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
325
342
|
|
|
326
343
|
try {
|
|
327
344
|
val children =
|
|
328
|
-
if (parentId == BrowserPathHelper.
|
|
329
|
-
// TODO: implement recent media items
|
|
330
|
-
emptyList<MediaItem>()
|
|
331
|
-
} else if (parentId == BrowserPathHelper.ROOT_PATH) {
|
|
345
|
+
if (parentId == BrowserPathHelper.ROOT_PATH) {
|
|
332
346
|
// Return tabs as root children (limited to 4 for automotive platform compatibility)
|
|
333
347
|
// TODO: Check what Android Auto does with empty tabs list - may need to return error?
|
|
334
|
-
|
|
348
|
+
// Disabled tabs hide (Track.disabled) — filtered before the cap so
|
|
349
|
+
// an unavailable tab never costs an available one its slot.
|
|
350
|
+
val tabs = browserManager.queryTabs().filterNot { it.disabled == true }
|
|
335
351
|
if (tabs.size > 4) {
|
|
336
352
|
Timber.w(
|
|
337
353
|
"Root has ${tabs.size} tabs; dropping ${tabs.size - 4} (Android Auto root limit)"
|
|
338
354
|
)
|
|
339
355
|
}
|
|
340
|
-
|
|
356
|
+
toFlatMediaItems(tabs.take(4))
|
|
341
357
|
} else {
|
|
342
|
-
// Resolve the specific path and
|
|
358
|
+
// Resolve the specific path and flatten its sections to MediaItems
|
|
359
|
+
// (paths are already set to contextual paths). Section styles come
|
|
360
|
+
// page-folded (`section ?? page`, ADR 0011).
|
|
343
361
|
val resolvedTrack = browserManager.resolve(parentId)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
"Expected browsed ResolvedTrack to have a children array"
|
|
350
|
-
)
|
|
351
|
-
toMediaItems(trackChildren.toList())
|
|
362
|
+
val sections =
|
|
363
|
+
resolvedTrack.styleResolvedSections()
|
|
364
|
+
?: throw IllegalStateException("Expected browsed ResolvedTrack to have sections")
|
|
365
|
+
browserManager.warnIfGridPageLacksPromise(parentId, sections)
|
|
366
|
+
toMediaItems(sections)
|
|
352
367
|
}
|
|
353
368
|
|
|
354
369
|
LibraryResult.ofItemList(ImmutableList.copyOf(children.paginate(page, pageSize)), params)
|
|
@@ -402,19 +417,45 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
402
417
|
}
|
|
403
418
|
|
|
404
419
|
/**
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
420
|
+
* Flattens a page's sections to MediaItems for browse delivery — the Media3 boundary where
|
|
421
|
+
* sections die (ADR 0010): the MediaBrowser protocol has no section node, so each child is
|
|
422
|
+
* stamped with its owning section's title/style hints. A grid-displayed section with a `path`
|
|
423
|
+
* gains a browsable "view all" link under the same header. Http(s) artwork routes through the
|
|
424
|
+
* content:// provider so Android Auto can load it via the ArtworkContentProvider.
|
|
409
425
|
*/
|
|
410
|
-
private fun toMediaItems(
|
|
426
|
+
private fun toMediaItems(sections: List<Section>): List<MediaItem> {
|
|
411
427
|
val registry = player.browseArtworkRegistry
|
|
412
428
|
val authority = com.audiobrowser.util.ArtworkUris.authorityFor(player.context.packageName)
|
|
413
|
-
return
|
|
414
|
-
|
|
429
|
+
return sections.flatMap { section ->
|
|
430
|
+
// Android Auto has no disabled affordance, so an unavailable track hides
|
|
431
|
+
// — never a normal-looking dead row (Track.disabled's rendering ladder).
|
|
432
|
+
val visibleChildren = section.children.filter { it.disabled != true }
|
|
433
|
+
val items =
|
|
434
|
+
visibleChildren.map { TrackFactory.toBrowseMediaItem(it, registry, authority, section) }
|
|
435
|
+
val isTileSection = section.style?.display == StyleDisplay.GRID
|
|
436
|
+
// No "view all" under an empty section — CarPlay skips empty sections
|
|
437
|
+
// outright, and a lone navigation tile under a header is a dead end.
|
|
438
|
+
if (isTileSection && section.path != null && visibleChildren.isNotEmpty()) {
|
|
439
|
+
items +
|
|
440
|
+
TrackFactory.toBrowseMediaItem(
|
|
441
|
+
TrackFactory.navigationTrack(section),
|
|
442
|
+
registry,
|
|
443
|
+
authority,
|
|
444
|
+
section,
|
|
445
|
+
)
|
|
446
|
+
} else {
|
|
447
|
+
items
|
|
448
|
+
}
|
|
415
449
|
}
|
|
416
450
|
}
|
|
417
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Flat lists (tabs, search results) convert as one untitled list section — which contributes no
|
|
454
|
+
* group or style hints — so exactly one Track→MediaItem path exists at the Media3 boundary.
|
|
455
|
+
*/
|
|
456
|
+
private fun toFlatMediaItems(tracks: List<Track>): List<MediaItem> =
|
|
457
|
+
toMediaItems(listOf(untitledSection(tracks.toTypedArray())))
|
|
458
|
+
|
|
418
459
|
override fun onGetItem(
|
|
419
460
|
session: MediaLibraryService.MediaLibrarySession,
|
|
420
461
|
browser: MediaSession.ControllerInfo,
|
|
@@ -451,7 +492,7 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
451
492
|
return@future LibraryResult.ofItem(createGateMediaItem(outcome.chrome!!), null)
|
|
452
493
|
}
|
|
453
494
|
|
|
454
|
-
if (mediaId == BrowserPathHelper.ROOT_PATH
|
|
495
|
+
if (mediaId == BrowserPathHelper.ROOT_PATH) {
|
|
455
496
|
return@future LibraryResult.ofItem(
|
|
456
497
|
MediaItem.Builder()
|
|
457
498
|
.setMediaId(mediaId)
|
|
@@ -466,7 +507,7 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
466
507
|
// Wait for browser to be registered if it's not available yet
|
|
467
508
|
val browserManager = player.awaitBrowser().browserManager
|
|
468
509
|
|
|
469
|
-
// Serve tracks from the track cache first (keyed by
|
|
510
|
+
// Serve tracks from the track cache first (keyed by path and src). Besides avoiding an HTTP
|
|
470
511
|
// resolve, this keeps item identity correct for contextual mediaIds: resolve() strips
|
|
471
512
|
// __trackId and would return the *parent container's* metadata as the item.
|
|
472
513
|
val browseAuthority =
|
|
@@ -628,7 +669,12 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
628
669
|
try {
|
|
629
670
|
// Execute search (automatically caches results at /__search?q=query)
|
|
630
671
|
val searchResults = browserManager.search(query)
|
|
631
|
-
|
|
672
|
+
// Count what onGetSearchResult will actually serve: disabled tracks
|
|
673
|
+
// hide on Android Auto, so they must not inflate the announced count.
|
|
674
|
+
val resultCount =
|
|
675
|
+
searchResults.normalizedSections?.sumOf { section ->
|
|
676
|
+
section.children.count { it.disabled != true }
|
|
677
|
+
} ?: 0
|
|
632
678
|
|
|
633
679
|
Timber.d("Search completed: $resultCount results for query '$query'")
|
|
634
680
|
|
|
@@ -681,14 +727,9 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
681
727
|
try {
|
|
682
728
|
// Get cached search results from BrowserManager
|
|
683
729
|
browserManager.getCachedSearchResults(query)?.let { tracks ->
|
|
684
|
-
//
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
val mediaItems =
|
|
688
|
-
tracks.map { track ->
|
|
689
|
-
Timber.d("Search result: ${track.title} (url=${track.url}, src=${track.src})")
|
|
690
|
-
TrackFactory.toBrowseMediaItem(track, player.browseArtworkRegistry, searchAuthority)
|
|
691
|
-
}
|
|
730
|
+
// Through the shared browse conversion, so search results get the
|
|
731
|
+
// same artwork routing as browse rows.
|
|
732
|
+
val mediaItems = toFlatMediaItems(tracks.toList())
|
|
692
733
|
|
|
693
734
|
val paginatedItems = mediaItems.paginate(page, pageSize)
|
|
694
735
|
Timber.d("Returning ${paginatedItems.size} search results")
|
|
@@ -737,26 +778,28 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
737
778
|
|
|
738
779
|
val browserManager = audioBrowser.browserManager
|
|
739
780
|
|
|
740
|
-
// A single tapped item resolves to the contextual
|
|
781
|
+
// A single tapped item resolves to the contextual path of the list it was
|
|
741
782
|
// tapped in: directly for contextual mediaIds, via the track cache for
|
|
742
|
-
// stable-id mediaIds (see BrowserManager.
|
|
783
|
+
// stable-id mediaIds (see BrowserManager.contextualPathFor). A search
|
|
743
784
|
// selection is not a list tap — its queue comes from the search results
|
|
744
785
|
// (resolveMediaItemsForPlayback), never from a browsed container.
|
|
745
|
-
val
|
|
786
|
+
val singleContextualPath =
|
|
746
787
|
mediaItems
|
|
747
788
|
.singleOrNull()
|
|
748
789
|
?.takeIf { it.requestMetadata.searchQuery == null }
|
|
749
|
-
?.let { browserManager.
|
|
790
|
+
?.let { browserManager.contextualPathFor(it.mediaId) }
|
|
750
791
|
|
|
751
792
|
// Check if this is a single item from the current queue source
|
|
752
|
-
if (
|
|
753
|
-
val parentPath = BrowserPathHelper.stripTrackId(
|
|
754
|
-
val trackId = BrowserPathHelper.extractTrackId(
|
|
793
|
+
if (singleContextualPath != null) {
|
|
794
|
+
val parentPath = BrowserPathHelper.stripTrackId(singleContextualPath)
|
|
795
|
+
val trackId = BrowserPathHelper.extractTrackId(singleContextualPath)
|
|
755
796
|
|
|
756
|
-
// Check if queue already came from this parent path - just skip to the
|
|
797
|
+
// Check if queue already came from this parent path - just skip to the
|
|
798
|
+
// tapped surface (exact path first, identity for index-less paths —
|
|
799
|
+
// see indexOfTappedTrack)
|
|
757
800
|
if (trackId != null && parentPath == player.queueSourcePath) {
|
|
758
801
|
val queueTracks = withContext(Dispatchers.Main) { player.tracks }
|
|
759
|
-
val index = queueTracks.
|
|
802
|
+
val index = queueTracks.indexOfTappedTrack(singleContextualPath, trackId)
|
|
760
803
|
if (index >= 0) {
|
|
761
804
|
Timber.d("Queue already from $parentPath, skipping to index $index")
|
|
762
805
|
val track = queueTracks[index]
|
|
@@ -788,9 +831,10 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
788
831
|
result.startIndex.toDouble(),
|
|
789
832
|
::currentPlayerState,
|
|
790
833
|
) {
|
|
791
|
-
// If this was a contextual
|
|
792
|
-
|
|
793
|
-
|
|
834
|
+
// If this was a contextual path expansion, track the source path (only for default
|
|
835
|
+
// behavior)
|
|
836
|
+
if (singleContextualPath != null) {
|
|
837
|
+
val parentPath = BrowserPathHelper.stripTrackId(singleContextualPath)
|
|
794
838
|
withContext(Dispatchers.Main) { player.queueSourcePath = parentPath }
|
|
795
839
|
}
|
|
796
840
|
result
|
|
@@ -829,6 +873,15 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
829
873
|
throw IllegalStateException("No playback state to resume")
|
|
830
874
|
}
|
|
831
875
|
|
|
876
|
+
// The persisted track can carry `disabled` — it became unavailable after
|
|
877
|
+
// it was saved. A disabled track never plays, resumption included
|
|
878
|
+
// (Track.disabled); failing the future refuses without touching the
|
|
879
|
+
// player.
|
|
880
|
+
if (state.track.disabled == true) {
|
|
881
|
+
Timber.w("Refusing resumption of disabled track: ${state.track.title}")
|
|
882
|
+
throw IllegalStateException("Persisted track is disabled")
|
|
883
|
+
}
|
|
884
|
+
|
|
832
885
|
// isForPlayback == false is the device-boot-time notification case: network may be
|
|
833
886
|
// unavailable and we must not start playback. Return just the locally-stored track with its
|
|
834
887
|
// already-local metadata; skip the settings restore and network queue expansion below.
|
|
@@ -844,14 +897,14 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
844
897
|
// restore() sets player properties which must happen on main thread
|
|
845
898
|
withContext(Dispatchers.Main) { player.playbackStateStore.restore() }
|
|
846
899
|
|
|
847
|
-
val
|
|
848
|
-
Timber.d("Resuming from
|
|
900
|
+
val path = state.track.path
|
|
901
|
+
Timber.d("Resuming from path=$path, positionMs=${state.positionMs}")
|
|
849
902
|
|
|
850
903
|
// Wait for browser to be available (JS needs to have configured it)
|
|
851
904
|
val browserManager = player.awaitBrowser().browserManager
|
|
852
905
|
|
|
853
|
-
// Try to expand the
|
|
854
|
-
val expanded =
|
|
906
|
+
// Try to expand the path into a full queue
|
|
907
|
+
val expanded = path?.let { browserManager.expandQueueFromContextualPath(it) }
|
|
855
908
|
|
|
856
909
|
if (expanded != null) {
|
|
857
910
|
val (tracks, selectedIndex) = expanded
|
|
@@ -859,9 +912,9 @@ class MediaSessionCallback(private val player: Player) :
|
|
|
859
912
|
"Restored ${tracks.size} tracks, starting at index $selectedIndex at ${state.positionMs}ms"
|
|
860
913
|
)
|
|
861
914
|
|
|
862
|
-
// Track the source path if this was a contextual
|
|
863
|
-
if (BrowserPathHelper.isContextual(
|
|
864
|
-
val parentPath = BrowserPathHelper.stripTrackId(
|
|
915
|
+
// Track the source path if this was a contextual path expansion
|
|
916
|
+
if (BrowserPathHelper.isContextual(path)) {
|
|
917
|
+
val parentPath = BrowserPathHelper.stripTrackId(path)
|
|
865
918
|
withContext(Dispatchers.Main) { player.queueSourcePath = parentPath }
|
|
866
919
|
}
|
|
867
920
|
|