react-native-audio-browser 0.2.0-next.10 → 0.2.0-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/android/build.gradle +6 -5
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
- package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +131 -143
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/JsonModels.swift +187 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +34 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +47 -0
- package/ios/Browser/TabBarEntries.swift +2 -2
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +105 -67
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/HybridAudioBrowser.swift +113 -35
- package/ios/Model/NitroTypeStubs.swift +64 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/NowPlayingUpdater.swift +1 -1
- package/ios/Player/PlaybackCoordinator.swift +2 -2
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/JsonModelsTests.swift +75 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +4 -4
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +61 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +67 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
- package/nitrogen/generated/android/c++/JSection.hpp +114 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
- package/package.json +3 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +139 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +268 -136
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -12,15 +12,15 @@ private final class BrowserMock: TrackSelectionBrowser {
|
|
|
12
12
|
|
|
13
13
|
// Record calls for verification
|
|
14
14
|
var trackLoadEvents: [TrackLoadEvent] = []
|
|
15
|
-
var
|
|
15
|
+
var expandedPaths: [String] = []
|
|
16
16
|
|
|
17
17
|
func awaitTrackLoadHandler(event: TrackLoadEvent) async -> Bool {
|
|
18
18
|
trackLoadEvents.append(event)
|
|
19
19
|
return trackLoadHandlerResult
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
func
|
|
23
|
-
|
|
22
|
+
func expandQueueFromContextualPath(_ path: String) async throws -> (tracks: [Track], selectedIndex: Int)? {
|
|
23
|
+
expandedPaths.append(path)
|
|
24
24
|
if let error = expandQueueError {
|
|
25
25
|
throw error
|
|
26
26
|
}
|
|
@@ -53,23 +53,23 @@ private func makeSelector() -> (TrackSelector, BrowserMock, PlayerMock) {
|
|
|
53
53
|
@Suite("browsable track")
|
|
54
54
|
@MainActor
|
|
55
55
|
struct BrowsableTrackTests {
|
|
56
|
-
@Test func
|
|
56
|
+
@Test func pathOnly_returnsBrowse() async {
|
|
57
57
|
let (selector, _, player) = makeSelector()
|
|
58
|
-
let track = Track(id: "t1",
|
|
58
|
+
let track = Track(id: "t1", path: "/some/path")
|
|
59
59
|
let result = await selector.select(track: track, player: player)
|
|
60
|
-
guard case let .browse(
|
|
60
|
+
guard case let .browse(path) = result else {
|
|
61
61
|
Issue.record("expected .browse, got \(result)")
|
|
62
62
|
return
|
|
63
63
|
}
|
|
64
|
-
#expect(
|
|
64
|
+
#expect(path == "/some/path")
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// MARK: - No src or
|
|
68
|
+
// MARK: - No src or path
|
|
69
69
|
|
|
70
|
-
@Suite("no src or
|
|
70
|
+
@Suite("no src or path")
|
|
71
71
|
@MainActor
|
|
72
|
-
struct
|
|
72
|
+
struct NoSrcOrPathTests {
|
|
73
73
|
@Test func returnsNone() async {
|
|
74
74
|
let (selector, _, player) = makeSelector()
|
|
75
75
|
let track = Track(id: "t1")
|
|
@@ -115,25 +115,56 @@ struct PlayableTrackTests {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// MARK: - Contextual
|
|
118
|
+
// MARK: - Contextual path Queue Reuse
|
|
119
119
|
|
|
120
|
-
@Suite("contextual
|
|
120
|
+
@Suite("contextual path queue reuse")
|
|
121
121
|
@MainActor
|
|
122
|
-
struct
|
|
122
|
+
struct ContextualPathQueueReuseTests {
|
|
123
123
|
@Test func matchingQueueSourcePath_returnsSkipTo() async {
|
|
124
124
|
let (selector, browser, player) = makeSelector()
|
|
125
125
|
let parentPath = "/library/radio"
|
|
126
126
|
let trackSrc = "song.mp3"
|
|
127
|
-
let
|
|
127
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
128
|
+
|
|
129
|
+
// Id-less queue tracks: identity falls back to src.
|
|
130
|
+
player.queueSourcePath = parentPath
|
|
131
|
+
player.tracks = [
|
|
132
|
+
Track(src: "other.mp3"),
|
|
133
|
+
Track(src: "song.mp3"),
|
|
134
|
+
Track(src: "another.mp3"),
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
let track = Track(path: contextualPath, src: trackSrc)
|
|
138
|
+
let result = await selector.select(track: track, player: player)
|
|
139
|
+
guard case let .play(intent) = result else {
|
|
140
|
+
Issue.record("expected .play, got \(result)")
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
guard case let .skipTo(index) = intent else {
|
|
144
|
+
Issue.record("expected .skipTo, got \(intent)")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
#expect(index == 1)
|
|
148
|
+
#expect(browser.trackLoadEvents.count == 1)
|
|
149
|
+
#expect(browser.expandedPaths.isEmpty)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Skip-in-place matches by id when queue tracks carry ids: the contextual
|
|
153
|
+
// trackId is the row's identity (its id), and the queue row is found even
|
|
154
|
+
// though its src differs textually from the tapped track's.
|
|
155
|
+
@Test func matchingQueueSourcePath_skipsToTrackByIdWhenSrcDiffers() async {
|
|
156
|
+
let (selector, browser, player) = makeSelector()
|
|
157
|
+
let parentPath = "/library/radio"
|
|
158
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: "b")
|
|
128
159
|
|
|
129
160
|
player.queueSourcePath = parentPath
|
|
130
161
|
player.tracks = [
|
|
131
162
|
Track(id: "a", src: "other.mp3"),
|
|
132
|
-
Track(id: "b", src: "song.mp3"),
|
|
163
|
+
Track(id: "b", src: "https://cdn.example/song.mp3?token=queue"),
|
|
133
164
|
Track(id: "c", src: "another.mp3"),
|
|
134
165
|
]
|
|
135
166
|
|
|
136
|
-
let track = Track(id: "
|
|
167
|
+
let track = Track(id: "b", path: contextualPath, src: "song.mp3")
|
|
137
168
|
let result = await selector.select(track: track, player: player)
|
|
138
169
|
guard case let .play(intent) = result else {
|
|
139
170
|
Issue.record("expected .play, got \(result)")
|
|
@@ -144,8 +175,7 @@ struct ContextualUrlQueueReuseTests {
|
|
|
144
175
|
return
|
|
145
176
|
}
|
|
146
177
|
#expect(index == 1)
|
|
147
|
-
#expect(browser.
|
|
148
|
-
#expect(browser.expandedUrls.isEmpty)
|
|
178
|
+
#expect(browser.expandedPaths.isEmpty)
|
|
149
179
|
}
|
|
150
180
|
|
|
151
181
|
@Test func handlerIntercepts_returnsIntercepted() async {
|
|
@@ -153,32 +183,93 @@ struct ContextualUrlQueueReuseTests {
|
|
|
153
183
|
browser.trackLoadHandlerResult = true
|
|
154
184
|
let parentPath = "/library/radio"
|
|
155
185
|
let trackSrc = "song.mp3"
|
|
156
|
-
let
|
|
186
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
157
187
|
|
|
158
188
|
player.queueSourcePath = parentPath
|
|
159
189
|
player.tracks = [
|
|
160
|
-
Track(
|
|
190
|
+
Track(src: "song.mp3"),
|
|
161
191
|
]
|
|
162
192
|
|
|
163
|
-
let track = Track(
|
|
193
|
+
let track = Track(path: contextualPath, src: trackSrc)
|
|
164
194
|
let result = await selector.select(track: track, player: player)
|
|
165
195
|
guard case .intercepted = result else {
|
|
166
196
|
Issue.record("expected .intercepted, got \(result)")
|
|
167
197
|
return
|
|
168
198
|
}
|
|
199
|
+
#expect(browser.expandedPaths.isEmpty)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// A queue holding the same identity twice (normal for music playlists):
|
|
203
|
+
// exact contextual-path equality — which carries the stamped page index —
|
|
204
|
+
// pins the tapped copy instead of the first one.
|
|
205
|
+
@Test func duplicateIdentityInQueue_pathMatchPinsTheTappedCopy() async {
|
|
206
|
+
let (selector, browser, player) = makeSelector()
|
|
207
|
+
let parentPath = "/playlist"
|
|
208
|
+
func stamped(_ id: String, _ index: Int) -> String {
|
|
209
|
+
BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
player.queueSourcePath = parentPath
|
|
213
|
+
player.tracks = [
|
|
214
|
+
Track(path: stamped("a", 0), src: "a"),
|
|
215
|
+
Track(path: stamped("b", 1), src: "b"),
|
|
216
|
+
Track(path: stamped("a", 2), src: "a"),
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
let track = Track(path: stamped("a", 2), src: "a")
|
|
220
|
+
let result = await selector.select(track: track, player: player)
|
|
221
|
+
guard case let .play(.skipTo(index)) = result else {
|
|
222
|
+
Issue.record("expected .play(.skipTo), got \(result)")
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
#expect(index == 2)
|
|
226
|
+
#expect(browser.expandedPaths.isEmpty)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// A tap in section B whose identity also sits in the active section-A
|
|
230
|
+
// queue: the exact surface is not in the queue, so the selection
|
|
231
|
+
// re-expands (re-scoping to the tapped section) instead of silently
|
|
232
|
+
// skipping inside the old section's queue.
|
|
233
|
+
@Test func indexStampedPathNotInQueue_reExpandsInsteadOfIdentitySkip() async {
|
|
234
|
+
let (selector, browser, player) = makeSelector()
|
|
235
|
+
let parentPath = "/city"
|
|
236
|
+
func stamped(_ id: String, _ index: Int) -> String {
|
|
237
|
+
BrowserPathHelper.build(parentPath: parentPath, trackId: id, index: index)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
player.queueSourcePath = parentPath
|
|
241
|
+
player.tracks = [
|
|
242
|
+
Track(path: stamped("dup", 0), src: "dup"),
|
|
243
|
+
Track(path: stamped("x", 1), src: "x"),
|
|
244
|
+
]
|
|
245
|
+
browser.expandQueueResult = (
|
|
246
|
+
tracks: [Track(path: stamped("y", 4), src: "y"), Track(path: stamped("dup", 5), src: "dup")],
|
|
247
|
+
selectedIndex: 1,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
let track = Track(path: stamped("dup", 5), src: "dup")
|
|
251
|
+
let result = await selector.select(track: track, player: player)
|
|
252
|
+
guard case let .play(.setQueue(tracks, startIndex, sourcePath)) = result else {
|
|
253
|
+
Issue.record("expected .play(.setQueue), got \(result)")
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
#expect(tracks.count == 2)
|
|
257
|
+
#expect(startIndex == 1)
|
|
258
|
+
#expect(sourcePath == parentPath)
|
|
259
|
+
#expect(browser.expandedPaths == [stamped("dup", 5)])
|
|
169
260
|
}
|
|
170
261
|
}
|
|
171
262
|
|
|
172
|
-
// MARK: - Contextual
|
|
263
|
+
// MARK: - Contextual path Expansion
|
|
173
264
|
|
|
174
|
-
@Suite("contextual
|
|
265
|
+
@Suite("contextual path expansion")
|
|
175
266
|
@MainActor
|
|
176
|
-
struct
|
|
267
|
+
struct ContextualPathExpansionTests {
|
|
177
268
|
@Test func expands_returnsSetQueue() async {
|
|
178
269
|
let (selector, browser, player) = makeSelector()
|
|
179
270
|
let parentPath = "/library/radio"
|
|
180
271
|
let trackSrc = "song.mp3"
|
|
181
|
-
let
|
|
272
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
182
273
|
|
|
183
274
|
let expandedTracks = [
|
|
184
275
|
Track(id: "a", src: "first.mp3"),
|
|
@@ -187,7 +278,7 @@ struct ContextualUrlExpansionTests {
|
|
|
187
278
|
]
|
|
188
279
|
browser.expandQueueResult = (tracks: expandedTracks, selectedIndex: 1)
|
|
189
280
|
|
|
190
|
-
let track = Track(id: "t1",
|
|
281
|
+
let track = Track(id: "t1", path: contextualPath, src: trackSrc)
|
|
191
282
|
let result = await selector.select(track: track, player: player)
|
|
192
283
|
guard case let .play(intent) = result else {
|
|
193
284
|
Issue.record("expected .play, got \(result)")
|
|
@@ -200,7 +291,7 @@ struct ContextualUrlExpansionTests {
|
|
|
200
291
|
#expect(tracks.count == 3)
|
|
201
292
|
#expect(startIndex == 1)
|
|
202
293
|
#expect(sourcePath == parentPath)
|
|
203
|
-
#expect(browser.
|
|
294
|
+
#expect(browser.expandedPaths == [contextualPath])
|
|
204
295
|
#expect(browser.trackLoadEvents.count == 1)
|
|
205
296
|
}
|
|
206
297
|
|
|
@@ -209,14 +300,14 @@ struct ContextualUrlExpansionTests {
|
|
|
209
300
|
browser.trackLoadHandlerResult = true
|
|
210
301
|
let parentPath = "/library/radio"
|
|
211
302
|
let trackSrc = "song.mp3"
|
|
212
|
-
let
|
|
303
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
213
304
|
|
|
214
305
|
browser.expandQueueResult = (
|
|
215
306
|
tracks: [Track(id: "a", src: "song.mp3")],
|
|
216
307
|
selectedIndex: 0,
|
|
217
308
|
)
|
|
218
309
|
|
|
219
|
-
let track = Track(id: "t1",
|
|
310
|
+
let track = Track(id: "t1", path: contextualPath, src: trackSrc)
|
|
220
311
|
let result = await selector.select(track: track, player: player)
|
|
221
312
|
guard case .intercepted = result else {
|
|
222
313
|
Issue.record("expected .intercepted, got \(result)")
|
|
@@ -228,11 +319,11 @@ struct ContextualUrlExpansionTests {
|
|
|
228
319
|
let (selector, browser, player) = makeSelector()
|
|
229
320
|
let parentPath = "/library/radio"
|
|
230
321
|
let trackSrc = "song.mp3"
|
|
231
|
-
let
|
|
322
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
232
323
|
|
|
233
324
|
browser.expandQueueResult = nil
|
|
234
325
|
|
|
235
|
-
let track = Track(id: "t1",
|
|
326
|
+
let track = Track(id: "t1", path: contextualPath, src: trackSrc)
|
|
236
327
|
let result = await selector.select(track: track, player: player)
|
|
237
328
|
guard case let .play(intent) = result else {
|
|
238
329
|
Issue.record("expected .play, got \(result)")
|
|
@@ -249,11 +340,11 @@ struct ContextualUrlExpansionTests {
|
|
|
249
340
|
let (selector, browser, player) = makeSelector()
|
|
250
341
|
let parentPath = "/library/radio"
|
|
251
342
|
let trackSrc = "song.mp3"
|
|
252
|
-
let
|
|
343
|
+
let contextualPath = BrowserPathHelper.build(parentPath: parentPath, trackId: trackSrc)
|
|
253
344
|
|
|
254
345
|
browser.expandQueueError = MockError.expansion
|
|
255
346
|
|
|
256
|
-
let track = Track(id: "t1",
|
|
347
|
+
let track = Track(id: "t1", path: contextualPath, src: trackSrc)
|
|
257
348
|
let result = await selector.select(track: track, player: player)
|
|
258
349
|
guard case let .play(intent) = result else {
|
|
259
350
|
Issue.record("expected .play, got \(result)")
|
|
@@ -266,3 +357,47 @@ struct ContextualUrlExpansionTests {
|
|
|
266
357
|
#expect(loaded.src == trackSrc)
|
|
267
358
|
}
|
|
268
359
|
}
|
|
360
|
+
|
|
361
|
+
// MARK: - Disabled Track
|
|
362
|
+
|
|
363
|
+
@Suite("disabled track")
|
|
364
|
+
@MainActor
|
|
365
|
+
struct DisabledTrackTests {
|
|
366
|
+
@Test func disabledPlayable_returnsNone() async {
|
|
367
|
+
let (selector, browser, player) = makeSelector()
|
|
368
|
+
let track = Track(id: "t1", src: "https://example.com/a.mp3", disabled: true)
|
|
369
|
+
let result = await selector.select(track: track, player: player)
|
|
370
|
+
guard case .none = result else {
|
|
371
|
+
Issue.record("Expected .none for a disabled track, got \(result)")
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
// Refused outright: not even the JS load handler hears about it.
|
|
375
|
+
#expect(browser.trackLoadEvents.isEmpty)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@Test func disabledBrowsable_returnsNone() async {
|
|
379
|
+
// Unavailable means unavailable — a disabled browsable doesn't navigate
|
|
380
|
+
// either (the ladder grays or hides it; a delivered tap stays inert).
|
|
381
|
+
let (selector, _, player) = makeSelector()
|
|
382
|
+
let track = Track(id: "t1", path: "/gone", disabled: true)
|
|
383
|
+
let result = await selector.select(track: track, player: player)
|
|
384
|
+
guard case .none = result else {
|
|
385
|
+
Issue.record("Expected .none for a disabled browsable, got \(result)")
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
@Test func disabledContextual_neverExpands() async {
|
|
391
|
+
let (selector, browser, player) = makeSelector()
|
|
392
|
+
browser.expandQueueResult = (tracks: [Track(id: "a", src: "a")], selectedIndex: 0)
|
|
393
|
+
let track = Track(
|
|
394
|
+
id: "t1", path: "/home?__trackId=t1", src: "https://example.com/a.mp3", disabled: true,
|
|
395
|
+
)
|
|
396
|
+
let result = await selector.select(track: track, player: player)
|
|
397
|
+
guard case .none = result else {
|
|
398
|
+
Issue.record("Expected .none, got \(result)")
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
#expect(browser.expandedPaths.isEmpty)
|
|
402
|
+
}
|
|
403
|
+
}
|
package/ios/TrackSelector.swift
CHANGED
|
@@ -5,7 +5,7 @@ import os.log
|
|
|
5
5
|
|
|
6
6
|
@MainActor protocol TrackSelectionBrowser {
|
|
7
7
|
func awaitTrackLoadHandler(event: TrackLoadEvent) async -> Bool
|
|
8
|
-
func
|
|
8
|
+
func expandQueueFromContextualPath(_ path: String) async throws -> (tracks: [Track], selectedIndex: Int)?
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
@MainActor protocol TrackSelectionPlayer {
|
|
@@ -36,9 +36,9 @@ class TrackSelector {
|
|
|
36
36
|
case play(PlaybackIntent)
|
|
37
37
|
/// JS handleTrackLoad handler intercepted — caller should not play.
|
|
38
38
|
case intercepted
|
|
39
|
-
/// Track is browsable — caller should navigate to this
|
|
40
|
-
case browse(
|
|
41
|
-
/// Nothing to do (no src, no
|
|
39
|
+
/// Track is browsable — caller should navigate to this path.
|
|
40
|
+
case browse(path: String)
|
|
41
|
+
/// Nothing to do (no src, no path).
|
|
42
42
|
case none
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -54,16 +54,22 @@ class TrackSelector {
|
|
|
54
54
|
// MARK: - Selection
|
|
55
55
|
|
|
56
56
|
/// Resolves a track selection to a concrete action.
|
|
57
|
-
/// Handles contextual
|
|
57
|
+
/// Handles contextual path expansion, queue reuse, and handler interception.
|
|
58
58
|
func select(
|
|
59
59
|
track: Track,
|
|
60
60
|
player: some TrackSelectionPlayer,
|
|
61
61
|
) async -> SelectionResult {
|
|
62
|
-
|
|
62
|
+
// A disabled track is unavailable — it never plays, whichever surface or
|
|
63
|
+
// stale resume path delivered the selection (Track.disabled).
|
|
64
|
+
if track.disabled == true {
|
|
65
|
+
return .none
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let path = track.path
|
|
63
69
|
|
|
64
|
-
// 1. Contextual
|
|
65
|
-
if let
|
|
66
|
-
return await
|
|
70
|
+
// 1. Contextual path (playable-only track with queue context)
|
|
71
|
+
if let path, BrowserPathHelper.isContextual(path) {
|
|
72
|
+
return await handleContextualPath(path, track: track, player: player)
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
// 2. Has src — single playable track
|
|
@@ -71,9 +77,9 @@ class TrackSelector {
|
|
|
71
77
|
return await handlePlayableTrack(track, player: player)
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
// 3. Has
|
|
75
|
-
if let
|
|
76
|
-
return .browse(
|
|
80
|
+
// 3. Has path — browsable
|
|
81
|
+
if let path {
|
|
82
|
+
return .browse(path: path)
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// 4. Neither
|
|
@@ -82,31 +88,40 @@ class TrackSelector {
|
|
|
82
88
|
|
|
83
89
|
// MARK: - Private Helpers
|
|
84
90
|
|
|
85
|
-
private func
|
|
86
|
-
_
|
|
91
|
+
private func handleContextualPath(
|
|
92
|
+
_ path: String,
|
|
87
93
|
track: Track,
|
|
88
94
|
player: some TrackSelectionPlayer,
|
|
89
95
|
) async -> SelectionResult {
|
|
90
|
-
let parentPath = BrowserPathHelper.stripTrackId(
|
|
91
|
-
let trackId = BrowserPathHelper.extractTrackId(
|
|
92
|
-
|
|
93
|
-
// Check if queue already came from this parent path — just skip to the
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
let parentPath = BrowserPathHelper.stripTrackId(path)
|
|
97
|
+
let trackId = BrowserPathHelper.extractTrackId(path)
|
|
98
|
+
|
|
99
|
+
// Check if queue already came from this parent path — just skip to the
|
|
100
|
+
// track. Exact-surface match first: a contextual path carries the tapped
|
|
101
|
+
// page position (`__index`), so path equality pins the exact copy when
|
|
102
|
+
// the page holds the same identity more than once. The identity match
|
|
103
|
+
// remains for index-less paths (e.g. pre-stamp persisted state); an
|
|
104
|
+
// index-stamped path with no exact match falls through to expansion,
|
|
105
|
+
// which re-scopes the queue to the tapped section.
|
|
106
|
+
if parentPath == player.queueSourcePath {
|
|
107
|
+
var skipIndex = player.tracks.firstIndex(where: { $0.path == path })
|
|
108
|
+
if skipIndex == nil, BrowserPathHelper.extractIndex(path) == nil, let trackId {
|
|
109
|
+
skipIndex = player.tracks.firstIndex(where: { $0.identity == trackId })
|
|
110
|
+
}
|
|
111
|
+
if let index = skipIndex {
|
|
112
|
+
logger.debug("Queue already from \(parentPath), skipping to index \(index)")
|
|
113
|
+
let queue = player.tracks
|
|
114
|
+
let event = TrackLoadEvent(track: track, queue: queue, startIndex: Double(index))
|
|
115
|
+
if await browserManager.awaitTrackLoadHandler(event: event) {
|
|
116
|
+
return .intercepted
|
|
117
|
+
}
|
|
118
|
+
return .play(.skipTo(index: index))
|
|
103
119
|
}
|
|
104
|
-
return .play(.skipTo(index: index))
|
|
105
120
|
}
|
|
106
121
|
|
|
107
|
-
// Expand the queue from the contextual
|
|
122
|
+
// Expand the queue from the contextual path
|
|
108
123
|
do {
|
|
109
|
-
if let expanded = try await browserManager.
|
|
124
|
+
if let expanded = try await browserManager.expandQueueFromContextualPath(path) {
|
|
110
125
|
let (tracks, startIndex) = expanded
|
|
111
126
|
let event = TrackLoadEvent(track: track, queue: tracks, startIndex: Double(startIndex))
|
|
112
127
|
if await browserManager.awaitTrackLoadHandler(event: event) {
|
|
@@ -8,7 +8,11 @@ enum ArtworkImageFetcher {
|
|
|
8
8
|
private static let logger = Logger(subsystem: "com.audiobrowser", category: "ArtworkImageFetcher")
|
|
9
9
|
|
|
10
10
|
@MainActor
|
|
11
|
-
static func fetchImage(
|
|
11
|
+
static func fetchImage(
|
|
12
|
+
from source: ImageSource,
|
|
13
|
+
svgScale: CGFloat? = nil,
|
|
14
|
+
downsampleTo: (size: CGSize, scale: CGFloat)? = nil,
|
|
15
|
+
) async -> UIImage? {
|
|
12
16
|
guard let url = URL(string: source.uri) else { return nil }
|
|
13
17
|
|
|
14
18
|
var options: KingfisherOptionsInfo = []
|
|
@@ -24,6 +28,17 @@ enum ArtworkImageFetcher {
|
|
|
24
28
|
}
|
|
25
29
|
if let svgScale {
|
|
26
30
|
options.append(.processor(SVGProcessor(size: nil, scale: svgScale)))
|
|
31
|
+
} else if let downsampleTo {
|
|
32
|
+
// Downsample rasters at decode time. Any size hint passed upstream (the
|
|
33
|
+
// resolver's ImageContext) is advisory — a CDN can ignore it and return
|
|
34
|
+
// the original — so this is the only sizing that is guaranteed to
|
|
35
|
+
// happen. The processed image is cached under a size-qualified key;
|
|
36
|
+
// cacheOriginalImage keeps the untouched original in the disk cache so
|
|
37
|
+
// the same URL at another target size re-processes instead of
|
|
38
|
+
// re-downloading.
|
|
39
|
+
options.append(.processor(DownsamplingImageProcessor(size: downsampleTo.size)))
|
|
40
|
+
options.append(.scaleFactor(downsampleTo.scale))
|
|
41
|
+
options.append(.cacheOriginalImage)
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
do {
|
|
@@ -25,6 +25,17 @@ Object.keys(_types).forEach(function (key) {
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
});
|
|
28
|
+
var _getTrackIdentity = require("./utils/getTrackIdentity");
|
|
29
|
+
Object.keys(_getTrackIdentity).forEach(function (key) {
|
|
30
|
+
if (key === "default" || key === "__esModule") return;
|
|
31
|
+
if (key in exports && exports[key] === _getTrackIdentity[key]) return;
|
|
32
|
+
Object.defineProperty(exports, key, {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function () {
|
|
35
|
+
return _getTrackIdentity[key];
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
28
39
|
var _useDebug = require("./utils/useDebug");
|
|
29
40
|
Object.keys(_useDebug).forEach(function (key) {
|
|
30
41
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_features","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_types","_useDebug"],"sourceRoot":"../../src","sources":["AudioBrowser.ts"],"mappings":";;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,MAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,MAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,MAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,MAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,
|
|
1
|
+
{"version":3,"names":["_features","require","Object","keys","forEach","key","exports","defineProperty","enumerable","get","_types","_getTrackIdentity","_useDebug"],"sourceRoot":"../../src","sources":["AudioBrowser.ts"],"mappings":";;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,SAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAL,SAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAT,SAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAK,MAAA,GAAAT,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAO,MAAA,EAAAN,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAK,MAAA,CAAAL,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,MAAA,CAAAL,GAAA;IAAA;EAAA;AAAA;AACA,IAAAM,iBAAA,GAAAV,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAQ,iBAAA,EAAAP,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAM,iBAAA,CAAAN,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAE,iBAAA,CAAAN,GAAA;IAAA;EAAA;AAAA;AACA,IAAAO,SAAA,GAAAX,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAS,SAAA,EAAAR,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAA,GAAA,IAAAC,OAAA,IAAAA,OAAA,CAAAD,GAAA,MAAAO,SAAA,CAAAP,GAAA;EAAAH,MAAA,CAAAK,cAAA,CAAAD,OAAA,EAAAD,GAAA;IAAAG,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAG,SAAA,CAAAP,GAAA;IAAA;EAAA;AAAA","ignoreList":[]}
|
|
@@ -22,9 +22,12 @@ function isObjectValue(value) {
|
|
|
22
22
|
}
|
|
23
23
|
function isTransformableRequestConfig(source) {
|
|
24
24
|
if (!isObjectValue(source)) return false;
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
25
|
+
// A static ResolvedTrack page also carries a top-level `path`, so `path`
|
|
26
|
+
// alone cannot mark a request config. A page is recognised by its required
|
|
27
|
+
// `title` — no RequestConfig field is named `title`.
|
|
28
|
+
if ('title' in source) return false;
|
|
29
|
+
// Any RequestConfig key marks a request config. (A static ResolvedTrack's
|
|
30
|
+
// per-track request override is nested under `request`, not top-level.)
|
|
28
31
|
return 'baseUrl' in source || 'path' in source || 'headers' in source || 'query' in source || 'method' in source || 'body' in source || 'contentType' in source || 'userAgent' in source || 'transform' in source || 'transformSync' in source;
|
|
29
32
|
}
|
|
30
33
|
function isRouteConfig(source) {
|
|
@@ -87,7 +90,7 @@ function searchSourceToRouteEntry(source) {
|
|
|
87
90
|
*/
|
|
88
91
|
function wrapTracksAsResolvedTrack(tracks) {
|
|
89
92
|
return {
|
|
90
|
-
|
|
93
|
+
path: TABS_ROUTE_PATH,
|
|
91
94
|
title: 'Tabs',
|
|
92
95
|
children: tracks
|
|
93
96
|
};
|
|
@@ -191,10 +194,10 @@ function validateBrowserConfiguration(config) {
|
|
|
191
194
|
continue;
|
|
192
195
|
}
|
|
193
196
|
if (!isCallback(source) && !isRouteConfig(source) && !isTransformableRequestConfig(source)) {
|
|
194
|
-
// Falls through to the static-page branch; a page needs
|
|
197
|
+
// Falls through to the static-page branch; a page needs path + title.
|
|
195
198
|
const page = source;
|
|
196
|
-
if (typeof page.
|
|
197
|
-
warn(`route "${path}" matched no source shape — expected a callback, ` + `a request config ({ baseUrl, path, headers, ... }), a ` + `RouteConfig ({ browse, media, artwork }), or a static page ` + `({
|
|
199
|
+
if (typeof page.path !== 'string' || typeof page.title !== 'string') {
|
|
200
|
+
warn(`route "${path}" matched no source shape — expected a callback, ` + `a request config ({ baseUrl, path, headers, ... }), a ` + `RouteConfig ({ browse, media, artwork }), or a static page ` + `({ path, title, children? }). It will be served as static ` + `content and likely render a blank screen.`);
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
}
|
|
@@ -220,7 +223,7 @@ function toNativeConfig(config) {
|
|
|
220
223
|
handleTrackLoad: config.handleTrackLoad,
|
|
221
224
|
androidControllerOfflineError: config.androidControllerOfflineError,
|
|
222
225
|
carPlayLoadingTitle: config.carPlayLoadingTitle,
|
|
223
|
-
|
|
226
|
+
resolveAlbumPath: config.resolveAlbumPath,
|
|
224
227
|
formatNavigationError: config.formatNavigationError
|
|
225
228
|
};
|
|
226
229
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["isCallback","source","isObjectValue","value","isTransformableRequestConfig","isRouteConfig","artwork","splitLayer","layer","resolve","resolver","Promise","config","flattenBrowseSource","browseCallback","browseConfig","browseStatic","searchSourceToRouteEntry","path","SEARCH_ROUTE_PATH","searchCallback","searchConfig","wrapTracksAsResolvedTrack","tracks","
|
|
1
|
+
{"version":3,"names":["isCallback","source","isObjectValue","value","isTransformableRequestConfig","isRouteConfig","artwork","splitLayer","layer","resolve","resolver","Promise","config","flattenBrowseSource","browseCallback","browseConfig","browseStatic","searchSourceToRouteEntry","path","SEARCH_ROUTE_PATH","searchCallback","searchConfig","wrapTracksAsResolvedTrack","tracks","TABS_ROUTE_PATH","title","children","tabsSourceToRouteEntry","Array","isArray","wrappedCallback","flattenRouteEntry","browse","media","DEFAULT_ROUTE_PATH","exports","flattenRoutes","routes","tabs","search","entries","Object","push","length","undefined","MAX_TABS","validateBrowserConfiguration","warn","message","console","startsWith","page","toNativeConfig","request","requestResolver","browseResolver","nowPlayingArtwork","singleTrack","handleTrackLoad","androidControllerOfflineError","carPlayLoadingTitle","resolveAlbumPath","formatNavigationError"],"sourceRoot":"../../../src","sources":["features/browser-config.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -44,26 +44,29 @@ function toggleActiveTrackFavorited() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Sets the list of favorited track
|
|
47
|
+
* Sets the list of favorited track identifiers.
|
|
48
48
|
*
|
|
49
49
|
* This syncs your app's favorites with the native favorites cache, enabling
|
|
50
50
|
* the heart button in media controllers (notification, Android Auto, CarPlay)
|
|
51
51
|
* to show the correct state.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* Each identifier is matched **exactly** against a track's *identity* — its
|
|
54
|
+
* `id` when set, falling back to `src`. Store favorites as the same stable
|
|
55
|
+
* identifier you put in `Track.id` (or as the full `src` for id-less tracks).
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* A track whose response already carries `favorited` keeps that value on
|
|
58
|
+
* display surfaces — a caller-set flag wins over cache hydration. The cache
|
|
59
|
+
* itself is written only by this call and by heart toggles
|
|
60
|
+
* (`setActiveTrackFavorited()` / media-controller taps), so you only need to
|
|
61
|
+
* call this on app launch — and again whenever the collection changes outside
|
|
62
|
+
* the player (a sync from another device, say).
|
|
60
63
|
*
|
|
61
|
-
* @param favorites - Array of favorited track `src`
|
|
64
|
+
* @param favorites - Array of favorited track identities (`id`, or `src` for id-less tracks)
|
|
62
65
|
*
|
|
63
66
|
* @example
|
|
64
67
|
* ```ts
|
|
65
|
-
* const
|
|
66
|
-
* setFavorites(
|
|
68
|
+
* const favoriteIds = await loadFavoritesFromStorage()
|
|
69
|
+
* setFavorites(favoriteIds)
|
|
67
70
|
* ```
|
|
68
71
|
*/
|
|
69
72
|
function setFavorites(favorites) {
|