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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
|
|
3
|
+
@testable import AudioBrowserTestable
|
|
4
|
+
|
|
5
|
+
// Resolution semantics (ADR 0011): container properties and `display` resolve
|
|
6
|
+
// by scope override (`section ?? page`), inherited item properties resolve
|
|
7
|
+
// `track ?? section ?? page` — and `display` never inherits onto an item.
|
|
8
|
+
|
|
9
|
+
@Suite("StyleResolver.sectionStyle")
|
|
10
|
+
struct SectionStyleResolutionTests {
|
|
11
|
+
@Test func sectionOverridesThePagePerProperty() {
|
|
12
|
+
let resolved = StyleResolver.sectionStyle(
|
|
13
|
+
section: SectionStyle(gridWrap: nil, display: .list, artworkRendering: nil),
|
|
14
|
+
page: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
|
|
15
|
+
)
|
|
16
|
+
// Declared on the section: wins. Undeclared: the page's scope-wide value.
|
|
17
|
+
#expect(resolved.display == .list)
|
|
18
|
+
#expect(resolved.artworkRendering == .stencil)
|
|
19
|
+
#expect(resolved.gridWrap == false)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test func aStylelessSectionTakesThePageBlock() {
|
|
23
|
+
let resolved = StyleResolver.sectionStyle(
|
|
24
|
+
section: nil,
|
|
25
|
+
page: SectionStyle(gridWrap: true, display: .grid, artworkRendering: nil),
|
|
26
|
+
)
|
|
27
|
+
#expect(resolved.display == .grid)
|
|
28
|
+
#expect(resolved.gridWrap == true)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test func noDeclarationsResolveToAnEmptyBlock() {
|
|
32
|
+
let resolved = StyleResolver.sectionStyle(section: nil, page: nil)
|
|
33
|
+
#expect(resolved.display == nil)
|
|
34
|
+
#expect(resolved.artworkRendering == nil)
|
|
35
|
+
#expect(resolved.gridWrap == nil)
|
|
36
|
+
#expect(resolved.gridTile == nil)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Test func gridTileResolvesByScopeOverride() {
|
|
40
|
+
// A page-wide card treatment; one section opts back to plain tiles.
|
|
41
|
+
let inherited = StyleResolver.sectionStyle(
|
|
42
|
+
section: nil,
|
|
43
|
+
page: SectionStyle(gridTile: .card),
|
|
44
|
+
)
|
|
45
|
+
#expect(inherited.gridTile == .card)
|
|
46
|
+
let overridden = StyleResolver.sectionStyle(
|
|
47
|
+
section: SectionStyle(gridTile: .plain),
|
|
48
|
+
page: SectionStyle(gridTile: .card),
|
|
49
|
+
)
|
|
50
|
+
#expect(overridden.gridTile == .plain)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Suite("StyleResolver.trackStyle")
|
|
55
|
+
struct TrackStyleResolutionTests {
|
|
56
|
+
@Test func trackOverridesTheSectionPerProperty() {
|
|
57
|
+
let resolved = StyleResolver.trackStyle(
|
|
58
|
+
track: TrackStyle(display: nil, artworkRendering: .original),
|
|
59
|
+
section: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: .stencil),
|
|
60
|
+
)
|
|
61
|
+
#expect(resolved.artworkRendering == .original)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test func aStylelessTrackInheritsTheSectionValue() {
|
|
65
|
+
let resolved = StyleResolver.trackStyle(
|
|
66
|
+
track: nil,
|
|
67
|
+
section: SectionStyle(gridWrap: nil, display: nil, artworkRendering: .stencil),
|
|
68
|
+
)
|
|
69
|
+
#expect(resolved.artworkRendering == .stencil)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test func displayNeverInheritsOntoAnItem() {
|
|
73
|
+
// The track's own `display` is the page promise for the page IT opens;
|
|
74
|
+
// the section's describes its children's layout. Neither may surface as
|
|
75
|
+
// the item's resolved display.
|
|
76
|
+
let resolved = StyleResolver.trackStyle(
|
|
77
|
+
track: TrackStyle(display: .grid, artworkRendering: nil),
|
|
78
|
+
section: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil),
|
|
79
|
+
)
|
|
80
|
+
#expect(resolved.display == nil)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Test func imageShapeInheritsAndOverridesPerItem() {
|
|
84
|
+
// Section-wide circular (an artists shelf); one album overrides.
|
|
85
|
+
let inherited = StyleResolver.trackStyle(
|
|
86
|
+
track: nil,
|
|
87
|
+
section: SectionStyle(imageShape: .circular),
|
|
88
|
+
)
|
|
89
|
+
#expect(inherited.imageShape == .circular)
|
|
90
|
+
let overridden = StyleResolver.trackStyle(
|
|
91
|
+
track: TrackStyle(imageShape: .roundedRectangle),
|
|
92
|
+
section: SectionStyle(imageShape: .circular),
|
|
93
|
+
)
|
|
94
|
+
#expect(overridden.imageShape == .roundedRectangle)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Test func accessorySymbolInheritsAndNoneResolvesAsAValue() {
|
|
98
|
+
let inherited = StyleResolver.trackStyle(
|
|
99
|
+
track: nil,
|
|
100
|
+
section: SectionStyle(accessorySymbol: "lock.fill"),
|
|
101
|
+
)
|
|
102
|
+
#expect(inherited.accessorySymbol == "lock.fill")
|
|
103
|
+
// 'none' is the inheritance escape — it must survive resolution intact
|
|
104
|
+
// (the renderer, not the resolver, maps it to "no accessory").
|
|
105
|
+
let escaped = StyleResolver.trackStyle(
|
|
106
|
+
track: TrackStyle(accessorySymbol: "none"),
|
|
107
|
+
section: SectionStyle(accessorySymbol: "lock.fill"),
|
|
108
|
+
)
|
|
109
|
+
#expect(escaped.accessorySymbol == "none")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Test func cardPropertiesInheritAndOverridePerItem() {
|
|
113
|
+
// Section-wide tint/mode; one card overrides its image mode.
|
|
114
|
+
let inherited = StyleResolver.trackStyle(
|
|
115
|
+
track: nil,
|
|
116
|
+
section: SectionStyle(accessorySymbol: nil, cardTint: "#1e3a8a", cardImage: .background),
|
|
117
|
+
)
|
|
118
|
+
#expect(inherited.cardTint == "#1e3a8a")
|
|
119
|
+
#expect(inherited.cardImage == .background)
|
|
120
|
+
let overridden = StyleResolver.trackStyle(
|
|
121
|
+
track: TrackStyle(cardImage: .normal),
|
|
122
|
+
section: SectionStyle(cardTint: "#1e3a8a", cardImage: .background),
|
|
123
|
+
)
|
|
124
|
+
#expect(overridden.cardImage == .normal)
|
|
125
|
+
#expect(overridden.cardTint == "#1e3a8a")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -185,6 +185,7 @@ final class MockSleepTimerHandling: SleepTimerHandling {
|
|
|
185
185
|
var onComplete: (() -> Void)?
|
|
186
186
|
var onFadeStart: ((_ duration: TimeInterval) -> Void)?
|
|
187
187
|
var onFadeCancel: (() -> Void)?
|
|
188
|
+
var onChanged: ((SleepTimerState) -> Void)?
|
|
188
189
|
var trackChangedCallCount = 0
|
|
189
190
|
var trackPlayedToEndCallCount = 0
|
|
190
191
|
var clearCallCount = 0
|
|
@@ -196,6 +197,12 @@ final class MockSleepTimerHandling: SleepTimerHandling {
|
|
|
196
197
|
|
|
197
198
|
func onTrackChanged() { trackChangedCallCount += 1 }
|
|
198
199
|
func onTrackPlayedToEnd() { trackPlayedToEndCallCount += 1 }
|
|
200
|
+
|
|
201
|
+
// Consumer-facing surface — unused by the coordinator tests, present only to
|
|
202
|
+
// satisfy the protocol. Add recorders alongside the tests that need them.
|
|
203
|
+
func get() -> SleepTimerState? { nil }
|
|
204
|
+
func set(seconds _: TimeInterval, fadeDuration _: TimeInterval?) {}
|
|
205
|
+
func setToEndOfTrack() {}
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
// MARK: - Mock RetryHandling
|
|
@@ -4,59 +4,59 @@ import Testing
|
|
|
4
4
|
|
|
5
5
|
struct TabBarEntriesTests {
|
|
6
6
|
private func tab(
|
|
7
|
-
_ title: String,
|
|
7
|
+
_ title: String, path: String, artwork: String? = nil,
|
|
8
8
|
artworkSource: ImageSource? = nil, artist: String? = nil,
|
|
9
9
|
) -> Track {
|
|
10
10
|
Track(
|
|
11
|
-
id:
|
|
12
|
-
artworkSource: artworkSource,
|
|
11
|
+
id: path, path: path, artwork: artwork.map { .first($0) },
|
|
12
|
+
artworkSource: artworkSource, title: title, artist: artist,
|
|
13
13
|
)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
@Test func identicalListsAreSame() {
|
|
17
|
-
let tabs = [tab("Explore",
|
|
17
|
+
let tabs = [tab("Explore", path: "/explore"), tab("Browse", path: "/browse")]
|
|
18
18
|
#expect(TabBarEntries.same(tabs, tabs))
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
@Test func titleChangeDiffers() {
|
|
22
22
|
#expect(
|
|
23
23
|
!TabBarEntries.same(
|
|
24
|
-
[tab("Explore",
|
|
25
|
-
[tab("Verkennen",
|
|
24
|
+
[tab("Explore", path: "/explore")],
|
|
25
|
+
[tab("Verkennen", path: "/explore")],
|
|
26
26
|
))
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
@Test func
|
|
29
|
+
@Test func pathChangeDiffers() {
|
|
30
30
|
#expect(
|
|
31
31
|
!TabBarEntries.same(
|
|
32
|
-
[tab("Explore",
|
|
33
|
-
[tab("Explore",
|
|
32
|
+
[tab("Explore", path: "/explore")],
|
|
33
|
+
[tab("Explore", path: "/discover")],
|
|
34
34
|
))
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
@Test func artworkChangeDiffers() {
|
|
38
38
|
#expect(
|
|
39
39
|
!TabBarEntries.same(
|
|
40
|
-
[tab("Explore",
|
|
41
|
-
[tab("Explore",
|
|
40
|
+
[tab("Explore", path: "/explore", artwork: "sf:globe")],
|
|
41
|
+
[tab("Explore", path: "/explore", artwork: "sf:map")],
|
|
42
42
|
))
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@Test func artworkSourceUriChangeDiffers() {
|
|
46
46
|
#expect(
|
|
47
47
|
!TabBarEntries.same(
|
|
48
|
-
[tab("Explore",
|
|
49
|
-
[tab("Explore",
|
|
48
|
+
[tab("Explore", path: "/explore", artworkSource: ImageSource(uri: "https://a/1.png"))],
|
|
49
|
+
[tab("Explore", path: "/explore", artworkSource: ImageSource(uri: "https://a/2.png"))],
|
|
50
50
|
))
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
@Test func countChangeDiffers() {
|
|
54
|
-
let tabs = [tab("Explore",
|
|
55
|
-
#expect(!TabBarEntries.same(tabs, tabs + [tab("Browse",
|
|
54
|
+
let tabs = [tab("Explore", path: "/explore")]
|
|
55
|
+
#expect(!TabBarEntries.same(tabs, tabs + [tab("Browse", path: "/browse")]))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
@Test func nilOldDiffers() {
|
|
59
|
-
#expect(!TabBarEntries.same(nil, [tab("Explore",
|
|
59
|
+
#expect(!TabBarEntries.same(nil, [tab("Explore", path: "/explore")]))
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
@Test func nonRenderedFieldChangeIsSame() {
|
|
@@ -64,8 +64,8 @@ struct TabBarEntriesTests {
|
|
|
64
64
|
// drift without churning the tab bar.
|
|
65
65
|
#expect(
|
|
66
66
|
TabBarEntries.same(
|
|
67
|
-
[tab("Explore",
|
|
68
|
-
[tab("Explore",
|
|
67
|
+
[tab("Explore", path: "/explore", artist: "a")],
|
|
68
|
+
[tab("Explore", path: "/explore", artist: "b")],
|
|
69
69
|
))
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
@testable import AudioBrowserTestable
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
/// `{id}` template substitution used by artwork URL resolution
|
|
5
|
+
/// (BrowserManager+URLResolution.substituteTrackId). A track without a non-blank id must leave
|
|
6
|
+
/// the token LITERALLY in place — never substitute an empty string, never skip — so the
|
|
7
|
+
/// resulting request 404s with a self-describing URL (the resolver logs a warning keyed off
|
|
8
|
+
/// `containsToken`). Mirrors Android's `substituteTrackId` / `configContainsIdToken`.
|
|
9
|
+
struct TrackIdTemplateTests {
|
|
10
|
+
// MARK: substitution with an id
|
|
11
|
+
|
|
12
|
+
@Test func substitutesTokenInString() {
|
|
13
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: "abc") == "/artwork/abc")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test func substitutesEveryOccurrence() {
|
|
17
|
+
#expect(TrackIdTemplate.substitute("/{id}/art/{id}.png", id: "x") == "/x/art/x.png")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test func substitutesTokenInDictionaryValues() {
|
|
21
|
+
let subbed = TrackIdTemplate.substitute(["x-track": "{id}", "plain": "v"], id: "abc")
|
|
22
|
+
#expect(subbed == ["x-track": "abc", "plain": "v"])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test func valueWithoutTokenIsUnchanged() {
|
|
26
|
+
#expect(TrackIdTemplate.substitute("/artwork/static.png", id: "abc") == "/artwork/static.png")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: token left literal without an id
|
|
30
|
+
|
|
31
|
+
@Test func nilIdLeavesTokenLiteral() {
|
|
32
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: nil) == "/artwork/{id}")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test func blankIdLeavesTokenLiteral() {
|
|
36
|
+
#expect(TrackIdTemplate.substitute("/artwork/{id}", id: "") == "/artwork/{id}")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Test func nilIdLeavesDictionaryValuesLiteral() {
|
|
40
|
+
#expect(TrackIdTemplate.substitute(["x-track": "{id}"], id: nil) == ["x-track": "{id}"])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test func nilValuePassesThrough() {
|
|
44
|
+
#expect(TrackIdTemplate.substitute(String?.none, id: "abc") == nil)
|
|
45
|
+
#expect(TrackIdTemplate.substitute([String: String]?.none, id: nil) == nil)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MARK: unfilled-token detection (drives the single warning)
|
|
49
|
+
|
|
50
|
+
@Test func detectsUnfilledTokenInString() {
|
|
51
|
+
#expect(TrackIdTemplate.containsToken("/artwork/{id}"))
|
|
52
|
+
#expect(!TrackIdTemplate.containsToken("/artwork/abc"))
|
|
53
|
+
#expect(!TrackIdTemplate.containsToken(String?.none))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test func detectsUnfilledTokenInDictionaryValues() {
|
|
57
|
+
#expect(TrackIdTemplate.containsToken(["x-track": "{id}"]))
|
|
58
|
+
#expect(!TrackIdTemplate.containsToken(["x-track": "abc"]))
|
|
59
|
+
#expect(!TrackIdTemplate.containsToken([String: String]?.none))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test func substitutionThenDetectionNeverBothMatch() {
|
|
63
|
+
// With an id, substitution clears the token, so no warning fires.
|
|
64
|
+
let subbed = TrackIdTemplate.substitute("/artwork/{id}", id: "abc")
|
|
65
|
+
#expect(!TrackIdTemplate.containsToken(subbed))
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
@testable import AudioBrowserTestable
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
struct TrackIdentityTests {
|
|
5
|
+
@Test func idWinsOverSrc() {
|
|
6
|
+
#expect(Track(id: "stable", src: "https://cdn.example/stream").identity == "stable")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@Test func blankIdFallsBackToSrc() {
|
|
10
|
+
#expect(Track(id: "", src: "https://cdn.example/stream").identity == "https://cdn.example/stream")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Test func nilIdFallsBackToSrc() {
|
|
14
|
+
#expect(Track(src: "https://cdn.example/stream").identity == "https://cdn.example/stream")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Test func neitherIdNorSrcHasNoIdentity() {
|
|
18
|
+
#expect(Track(path: "/browse/only").identity == nil)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -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
|
+
}
|