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
|
@@ -120,6 +120,41 @@ struct NextTests {
|
|
|
120
120
|
#expect(q.shuffleOrder.shuffled == order)
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/// A queue reorder permutes positions, not identities, so it must leave the
|
|
124
|
+
/// shuffled playback order alone. Re-inserting at a random shuffle position
|
|
125
|
+
/// would silently reshuffle under the listener.
|
|
126
|
+
@Test func shuffle_moveKeepsThePlaybackOrder() {
|
|
127
|
+
for target in 0 ..< 5 {
|
|
128
|
+
let q = QueueManager()
|
|
129
|
+
q.setQueue(tracks("a", "b", "c", "d", "e"))
|
|
130
|
+
q.shuffleEnabled = true
|
|
131
|
+
let before = q.shuffleOrder.shuffled.map { q.tracks[$0].id }
|
|
132
|
+
|
|
133
|
+
_ = try? q.move(fromIndex: 0, toIndex: target)
|
|
134
|
+
|
|
135
|
+
#expect(q.shuffleOrder.shuffled.map { q.tracks[$0].id } == before)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// The same hazard as `shuffle_startTrackLeadsTheOrder`, reached by moving the
|
|
140
|
+
/// *playing* track: a random re-insert can drop it last in the shuffle order,
|
|
141
|
+
/// which reads as `isLastInPlaybackOrder` and ends the queue at the next track
|
|
142
|
+
/// boundary with tracks still unplayed.
|
|
143
|
+
@Test func shuffle_movingCurrentTrackDoesNotEndTheQueue() {
|
|
144
|
+
for target in 1 ..< 6 {
|
|
145
|
+
let q = QueueManager()
|
|
146
|
+
q.setQueue(tracks("a", "b", "c", "d", "e", "f"), initialIndex: 0)
|
|
147
|
+
q.shuffleEnabled = true // pins "a" first in the order
|
|
148
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
149
|
+
|
|
150
|
+
_ = try? q.move(fromIndex: 0, toIndex: target)
|
|
151
|
+
|
|
152
|
+
#expect(q.currentTrack?.id == "a")
|
|
153
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
154
|
+
#expect(q.nextTracks.count == 5)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
@Test func shuffle_visitsAllTracks() {
|
|
124
159
|
let q = QueueManager()
|
|
125
160
|
q.setQueue(tracks("a", "b", "c", "d"))
|
|
@@ -382,6 +417,25 @@ struct AddTests {
|
|
|
382
417
|
#expect(q.tracks[2].id == "c")
|
|
383
418
|
#expect(q.tracks[3].id == "d")
|
|
384
419
|
}
|
|
420
|
+
|
|
421
|
+
/// Filling an empty queue must pin the start track to the head of the shuffle
|
|
422
|
+
/// order, exactly as `setQueue` does — `insert(at:count:)` places new indices
|
|
423
|
+
/// at random positions, so without the pin the start track can land last and
|
|
424
|
+
/// the queue reports end-of-queue as soon as it finishes. `shuffleEnabled`'s
|
|
425
|
+
/// didSet only re-pins on the off→on edge, so shuffle-already-on gets nothing
|
|
426
|
+
/// from it.
|
|
427
|
+
@Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() {
|
|
428
|
+
for start in 0 ..< 6 {
|
|
429
|
+
let q = QueueManager()
|
|
430
|
+
q.shuffleEnabled = true
|
|
431
|
+
q.add(tracks("a", "b", "c", "d", "e", "f"), initialIndex: start)
|
|
432
|
+
|
|
433
|
+
#expect(q.shuffleOrder.isFirst(start))
|
|
434
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
435
|
+
#expect(q.nextTracks.count == 5)
|
|
436
|
+
#expect(q.previousTracks.isEmpty)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
385
439
|
}
|
|
386
440
|
|
|
387
441
|
// MARK: - Mutations: addAt()
|
|
@@ -414,6 +468,18 @@ struct AddAtTests {
|
|
|
414
468
|
#expect(q.currentIndex == 0)
|
|
415
469
|
}
|
|
416
470
|
|
|
471
|
+
/// Same pinning requirement as `add()` into an empty queue — see its twin.
|
|
472
|
+
@Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() throws {
|
|
473
|
+
let q = QueueManager()
|
|
474
|
+
q.shuffleEnabled = true
|
|
475
|
+
try q.addAt(tracks("a", "b", "c", "d", "e", "f"), at: 0)
|
|
476
|
+
|
|
477
|
+
#expect(q.shuffleOrder.isFirst(0))
|
|
478
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
479
|
+
#expect(q.nextTracks.count == 5)
|
|
480
|
+
#expect(q.previousTracks.isEmpty)
|
|
481
|
+
}
|
|
482
|
+
|
|
417
483
|
@Test func invalidIndex_throws() {
|
|
418
484
|
let q = QueueManager()
|
|
419
485
|
q.setQueue(tracks("a"))
|
|
@@ -691,10 +757,10 @@ struct ClearTests {
|
|
|
691
757
|
@Suite("replace & removeUpcomingTracks")
|
|
692
758
|
@MainActor
|
|
693
759
|
struct OtherMutationTests {
|
|
694
|
-
@Test func replace_updatesTrackAtIndex() {
|
|
760
|
+
@Test func replace_updatesTrackAtIndex() throws {
|
|
695
761
|
let q = QueueManager()
|
|
696
762
|
q.setQueue(tracks("a", "b", "c"))
|
|
697
|
-
q.replace(1, Track(id: "B"))
|
|
763
|
+
try q.replace(1, Track(id: "B"))
|
|
698
764
|
#expect(q.tracks[1].id == "B")
|
|
699
765
|
}
|
|
700
766
|
|
|
@@ -738,10 +804,10 @@ struct OtherMutationTests {
|
|
|
738
804
|
#expect(q.tracks[0].id == "a")
|
|
739
805
|
}
|
|
740
806
|
|
|
741
|
-
@Test func replace_currentTrack_reflectsInCurrentTrack() {
|
|
807
|
+
@Test func replace_currentTrack_reflectsInCurrentTrack() throws {
|
|
742
808
|
let q = QueueManager()
|
|
743
809
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
744
|
-
q.replace(1, Track(id: "B"))
|
|
810
|
+
try q.replace(1, Track(id: "B"))
|
|
745
811
|
#expect(q.currentTrack?.id == "B")
|
|
746
812
|
#expect(q.currentIndex == 1)
|
|
747
813
|
}
|
|
@@ -926,12 +992,12 @@ struct DelegateTests {
|
|
|
926
992
|
#expect(spy.calls.count == 1)
|
|
927
993
|
}
|
|
928
994
|
|
|
929
|
-
@Test func queueDidChangeTracks_firesOnReplace() {
|
|
995
|
+
@Test func queueDidChangeTracks_firesOnReplace() throws {
|
|
930
996
|
let q = QueueManager()
|
|
931
997
|
q.setQueue(tracks("a", "b"))
|
|
932
998
|
let spy = DelegateSpy()
|
|
933
999
|
q.delegate = spy
|
|
934
|
-
q.replace(0, Track(id: "A"))
|
|
1000
|
+
try q.replace(0, Track(id: "A"))
|
|
935
1001
|
#expect(spy.calls.count == 1)
|
|
936
1002
|
}
|
|
937
1003
|
|
|
@@ -136,6 +136,22 @@ struct RemoteCommandControllerTests {
|
|
|
136
136
|
#expect(center.previousTrackCommand.isEnabled)
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/// The handler properties must not capture the controller strongly. Assigning a
|
|
140
|
+
/// bare method reference (`= handlePlayCommandDefault`) curries to
|
|
141
|
+
/// `{ [self] in self.method($0) }`, and storing that back on self is a permanent
|
|
142
|
+
/// cycle — one leaked controller per player instance, across every JS reload.
|
|
143
|
+
@Test func doesNotRetainItselfThroughHandlers() {
|
|
144
|
+
weak var weakController: RemoteCommandController?
|
|
145
|
+
autoreleasepool {
|
|
146
|
+
let (controller, _) = makeController()
|
|
147
|
+
// Forces every lazy handler to initialize, which is what closes the cycle.
|
|
148
|
+
controller.enable(commands: RemoteCommand.all())
|
|
149
|
+
controller.disableAll()
|
|
150
|
+
weakController = controller
|
|
151
|
+
}
|
|
152
|
+
#expect(weakController == nil)
|
|
153
|
+
}
|
|
154
|
+
|
|
139
155
|
@Test func shuffleAndRepeatStateSurviveCommandCenterSwitch() {
|
|
140
156
|
let (controller, _) = makeController()
|
|
141
157
|
let center = MPRemoteCommandCenter.shared()
|
|
@@ -3,17 +3,18 @@ import Testing
|
|
|
3
3
|
@testable import AudioBrowserTestable
|
|
4
4
|
|
|
5
5
|
private func makeResolvedTrack(
|
|
6
|
-
|
|
6
|
+
path: String = "/test",
|
|
7
7
|
title: String = "Test Track",
|
|
8
8
|
) -> ResolvedTrack {
|
|
9
9
|
ResolvedTrack(
|
|
10
|
-
|
|
10
|
+
path: path,
|
|
11
|
+
style: nil,
|
|
12
|
+
sections: nil,
|
|
11
13
|
children: nil,
|
|
12
14
|
carPlaySiriListButton: nil,
|
|
13
15
|
src: nil,
|
|
14
16
|
artwork: nil,
|
|
15
17
|
artworkSource: nil,
|
|
16
|
-
artworkCarPlayTinted: nil,
|
|
17
18
|
title: title,
|
|
18
19
|
subtitle: nil,
|
|
19
20
|
artist: nil,
|
|
@@ -21,25 +22,23 @@ private func makeResolvedTrack(
|
|
|
21
22
|
description: nil,
|
|
22
23
|
genre: nil,
|
|
23
24
|
duration: nil,
|
|
24
|
-
|
|
25
|
-
childrenStyle: nil,
|
|
25
|
+
disabled: nil,
|
|
26
26
|
favorited: nil,
|
|
27
|
-
groupTitle: nil,
|
|
28
27
|
live: nil,
|
|
29
|
-
imageRow: nil,
|
|
30
28
|
)
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
34
32
|
ResolvedTrack(
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
path: "/original",
|
|
34
|
+
style: SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil),
|
|
35
|
+
sections: [Section(title: "Group A", subtitle: nil, style: nil, path: nil, children: [])],
|
|
36
|
+
children: [Track(id: "t1", path: "/t1")],
|
|
37
37
|
carPlaySiriListButton: .top,
|
|
38
38
|
id: "original-id",
|
|
39
39
|
src: "src.mp3",
|
|
40
40
|
artwork: .first("art.jpg"),
|
|
41
41
|
artworkSource: ImageSource(uri: "resolved-art.jpg"),
|
|
42
|
-
artworkCarPlayTinted: true,
|
|
43
42
|
title: "Original Title",
|
|
44
43
|
subtitle: "Original Subtitle",
|
|
45
44
|
artist: "Original Artist",
|
|
@@ -47,12 +46,9 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
47
46
|
description: "Original Description",
|
|
48
47
|
genre: "Rock",
|
|
49
48
|
duration: 180.0,
|
|
50
|
-
|
|
51
|
-
childrenStyle: .grid,
|
|
49
|
+
disabled: false,
|
|
52
50
|
favorited: true,
|
|
53
|
-
groupTitle: "Group A",
|
|
54
51
|
live: false,
|
|
55
|
-
imageRow: [ImageRowItem(title: "Row 1")],
|
|
56
52
|
)
|
|
57
53
|
}
|
|
58
54
|
|
|
@@ -64,12 +60,12 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
64
60
|
#expect(copy == original)
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
// MARK: - non-optional fields (
|
|
63
|
+
// MARK: - non-optional fields (path, title)
|
|
68
64
|
|
|
69
|
-
@Test func
|
|
70
|
-
let original = makeResolvedTrack(
|
|
71
|
-
let copy = original.copying(
|
|
72
|
-
#expect(copy.
|
|
65
|
+
@Test func copyingOverridesPath() {
|
|
66
|
+
let original = makeResolvedTrack(path: "/old")
|
|
67
|
+
let copy = original.copying(path: "/new")
|
|
68
|
+
#expect(copy.path == "/new")
|
|
73
69
|
#expect(copy.title == "Test Track")
|
|
74
70
|
}
|
|
75
71
|
|
|
@@ -77,14 +73,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
77
73
|
let original = makeResolvedTrack(title: "Old")
|
|
78
74
|
let copy = original.copying(title: "New")
|
|
79
75
|
#expect(copy.title == "New")
|
|
80
|
-
#expect(copy.
|
|
76
|
+
#expect(copy.path == "/test")
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
// MARK: - optional fields: set value
|
|
84
80
|
|
|
85
81
|
@Test func copyingOverridesChildren() {
|
|
86
82
|
let original = makeResolvedTrack()
|
|
87
|
-
let children = [Track(id: "c1",
|
|
83
|
+
let children = [Track(id: "c1", path: "/c1"), Track(id: "c2", path: "/c2")]
|
|
88
84
|
let copy = original.copying(children: children)
|
|
89
85
|
#expect(copy.children?.count == 2)
|
|
90
86
|
#expect(copy.children?[0].id == "c1")
|
|
@@ -135,8 +131,14 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
135
131
|
|
|
136
132
|
@Test func copyingOverridesStyle() {
|
|
137
133
|
let original = makeResolvedTrack()
|
|
138
|
-
let copy = original.copying(style: .grid)
|
|
139
|
-
#expect(copy.style == .grid)
|
|
134
|
+
let copy = original.copying(style: SectionStyle(gridWrap: nil, display: .grid, artworkRendering: nil))
|
|
135
|
+
#expect(copy.style?.display == .grid)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Test func copyingOverridesDisabled() {
|
|
139
|
+
let original = makeResolvedTrack()
|
|
140
|
+
let copy = original.copying(disabled: true)
|
|
141
|
+
#expect(copy.disabled == true)
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
@Test func copyingOverridesFavorited() {
|
|
@@ -157,11 +159,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
157
159
|
#expect(copy.carPlaySiriListButton == .bottom)
|
|
158
160
|
}
|
|
159
161
|
|
|
160
|
-
@Test func
|
|
162
|
+
@Test func copyingOverridesSections() {
|
|
161
163
|
let original = makeResolvedTrack()
|
|
162
|
-
let
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
let sections = [
|
|
165
|
+
Section(title: "A", subtitle: nil, style: nil, path: nil, children: [Track(id: "c1", src: "c1")]),
|
|
166
|
+
]
|
|
167
|
+
let copy = original.copying(sections: sections)
|
|
168
|
+
#expect(copy.sections == sections)
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
// MARK: - optional fields: set to nil via .some(nil)
|
|
@@ -206,13 +210,13 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
206
210
|
@Test func copyingOverridesMultipleFields() {
|
|
207
211
|
let original = makeResolvedTrack()
|
|
208
212
|
let copy = original.copying(
|
|
209
|
-
|
|
213
|
+
path: "/updated",
|
|
210
214
|
title: "Updated Title",
|
|
211
215
|
artist: "New Artist",
|
|
212
216
|
duration: 240.0,
|
|
213
217
|
favorited: true,
|
|
214
218
|
)
|
|
215
|
-
#expect(copy.
|
|
219
|
+
#expect(copy.path == "/updated")
|
|
216
220
|
#expect(copy.title == "Updated Title")
|
|
217
221
|
#expect(copy.artist == "New Artist")
|
|
218
222
|
#expect(copy.duration == 240.0)
|
|
@@ -230,23 +234,21 @@ private func makeFullResolvedTrack() -> ResolvedTrack {
|
|
|
230
234
|
let copy = original.copying(title: "Changed Only Title")
|
|
231
235
|
#expect(copy.title == "Changed Only Title")
|
|
232
236
|
// All other fields unchanged
|
|
233
|
-
#expect(copy.
|
|
237
|
+
#expect(copy.path == "/original")
|
|
234
238
|
#expect(copy.children?.count == 1)
|
|
235
239
|
#expect(copy.carPlaySiriListButton == .top)
|
|
236
240
|
#expect(copy.src == "src.mp3")
|
|
237
241
|
#expect(copy.artwork?.url == "art.jpg")
|
|
238
242
|
#expect(copy.artworkSource == ImageSource(uri: "resolved-art.jpg"))
|
|
239
|
-
#expect(copy.artworkCarPlayTinted == true)
|
|
240
243
|
#expect(copy.subtitle == "Original Subtitle")
|
|
241
244
|
#expect(copy.artist == "Original Artist")
|
|
242
245
|
#expect(copy.album == "Original Album")
|
|
243
246
|
#expect(copy.description == "Original Description")
|
|
244
247
|
#expect(copy.genre == "Rock")
|
|
245
248
|
#expect(copy.duration == 180.0)
|
|
246
|
-
#expect(copy.style == .
|
|
247
|
-
#expect(copy.
|
|
249
|
+
#expect(copy.style == SectionStyle(gridWrap: false, display: .grid, artworkRendering: .stencil))
|
|
250
|
+
#expect(copy.disabled == false)
|
|
248
251
|
#expect(copy.favorited == true)
|
|
249
|
-
#expect(copy.
|
|
252
|
+
#expect(copy.sections?.first?.title == "Group A")
|
|
250
253
|
#expect(copy.live == false)
|
|
251
|
-
#expect(copy.imageRow == [ImageRowItem(title: "Row 1")])
|
|
252
254
|
}
|
|
@@ -4,14 +4,14 @@ import Testing
|
|
|
4
4
|
|
|
5
5
|
private struct Item: PlayableSearchItem {
|
|
6
6
|
let src: String?
|
|
7
|
-
let
|
|
7
|
+
let path: String?
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@Suite("SearchDrillIn.playable")
|
|
11
11
|
struct SearchDrillInTests {
|
|
12
12
|
/// A resolver that fails the test if the drill-in path calls it.
|
|
13
|
-
private func unusedResolver(_
|
|
14
|
-
Issue.record("resolveChildren should not have been called (
|
|
13
|
+
private func unusedResolver(_ path: String) async throws -> [Item] {
|
|
14
|
+
Issue.record("resolveChildren should not have been called (path: \(path))")
|
|
15
15
|
return []
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -22,9 +22,9 @@ struct SearchDrillInTests {
|
|
|
22
22
|
|
|
23
23
|
@Test func firstPlayable_returnsFlatPlayable_inOrder_withoutResolving() async throws {
|
|
24
24
|
let items = [
|
|
25
|
-
Item(src: "/a.mp3",
|
|
26
|
-
Item(src: nil,
|
|
27
|
-
Item(src: "/b.mp3",
|
|
25
|
+
Item(src: "/a.mp3", path: nil),
|
|
26
|
+
Item(src: nil, path: "/page/x"), // browsable, filtered out
|
|
27
|
+
Item(src: "/b.mp3", path: nil),
|
|
28
28
|
]
|
|
29
29
|
let result = try await SearchDrillIn.playable(from: items, resolveChildren: unusedResolver)
|
|
30
30
|
#expect(result?.compactMap(\.src) == ["/a.mp3", "/b.mp3"])
|
|
@@ -32,31 +32,31 @@ struct SearchDrillInTests {
|
|
|
32
32
|
|
|
33
33
|
@Test func firstBrowsable_drillsIntoIt_andPlaysItsChildren() async throws {
|
|
34
34
|
let items = [
|
|
35
|
-
Item(src: nil,
|
|
36
|
-
Item(src: "/later.mp3",
|
|
35
|
+
Item(src: nil, path: "/page/place"), // browsable first result
|
|
36
|
+
Item(src: "/later.mp3", path: nil), // would win without drill-in
|
|
37
37
|
]
|
|
38
|
-
let result = try await SearchDrillIn.playable(from: items) {
|
|
39
|
-
#expect(
|
|
40
|
-
return [Item(src: "/station1.mp3",
|
|
38
|
+
let result = try await SearchDrillIn.playable(from: items) { path in
|
|
39
|
+
#expect(path == "/page/place")
|
|
40
|
+
return [Item(src: "/station1.mp3", path: nil), Item(src: "/station2.mp3", path: nil)]
|
|
41
41
|
}
|
|
42
42
|
#expect(result?.compactMap(\.src) == ["/station1.mp3", "/station2.mp3"])
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
@Test func browsableDrillYieldsNothing_fallsBackToFlat() async throws {
|
|
46
46
|
let items = [
|
|
47
|
-
Item(src: nil,
|
|
48
|
-
Item(src: "/fallback.mp3",
|
|
47
|
+
Item(src: nil, path: "/page/empty"),
|
|
48
|
+
Item(src: "/fallback.mp3", path: nil),
|
|
49
49
|
]
|
|
50
50
|
let result = try await SearchDrillIn.playable(from: items) { _ in
|
|
51
|
-
[Item(src: nil,
|
|
51
|
+
[Item(src: nil, path: "/page/nested")] // nothing playable inside
|
|
52
52
|
}
|
|
53
53
|
#expect(result?.compactMap(\.src) == ["/fallback.mp3"])
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
@Test func nothingPlayableAnywhere_returnsNil() async throws {
|
|
57
|
-
let items = [Item(src: nil,
|
|
57
|
+
let items = [Item(src: nil, path: "/page/a"), Item(src: nil, path: "/page/b")]
|
|
58
58
|
let result = try await SearchDrillIn.playable(from: items) { _ in
|
|
59
|
-
[Item(src: nil,
|
|
59
|
+
[Item(src: nil, path: "/page/nested")]
|
|
60
60
|
}
|
|
61
61
|
#expect(result == nil)
|
|
62
62
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Testing
|
|
2
|
+
|
|
3
|
+
@testable import AudioBrowserTestable
|
|
4
|
+
|
|
5
|
+
// The (resolved style, platform capability) → rendered-form mapping (ADR
|
|
6
|
+
// 0011): degradation drops decorations before layout, and the one layout drop
|
|
7
|
+
// is a wrapping grid on a platform without a wrapping tile container.
|
|
8
|
+
|
|
9
|
+
@Suite("SectionPresentation")
|
|
10
|
+
struct SectionPresentationTests {
|
|
11
|
+
private func style(display: StyleDisplay?, gridWrap: Bool? = nil) -> SectionStyle {
|
|
12
|
+
SectionStyle(gridWrap: gridWrap, display: display, artworkRendering: nil)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Test func noStyleRendersAList() {
|
|
16
|
+
#expect(SectionPresentation(for: nil, supportsWrappingGrid: true) == .list)
|
|
17
|
+
#expect(SectionPresentation(for: nil, supportsWrappingGrid: false) == .list)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test func listDisplayRendersAList() {
|
|
21
|
+
#expect(SectionPresentation(for: style(display: .list), supportsWrappingGrid: true) == .list)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Test func wrappingGridNeedsTheWrappingContainer() {
|
|
25
|
+
#expect(
|
|
26
|
+
SectionPresentation(for: style(display: .grid), supportsWrappingGrid: true) == .wrappingGrid)
|
|
27
|
+
// The one layout drop: a wrapping grid has nowhere to go but a list —
|
|
28
|
+
// every item stays reachable instead of truncating at an unknowable width.
|
|
29
|
+
#expect(SectionPresentation(for: style(display: .grid), supportsWrappingGrid: false) == .list)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test func singleLineGridRendersOnEveryOS() {
|
|
33
|
+
// gridWrap: false is the legacy image row's own shape, so it never
|
|
34
|
+
// degrades — declaring it INCREASES pre-26 fidelity.
|
|
35
|
+
#expect(
|
|
36
|
+
SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: true)
|
|
37
|
+
== .singleLineRow)
|
|
38
|
+
#expect(
|
|
39
|
+
SectionPresentation(for: style(display: .grid, gridWrap: false), supportsWrappingGrid: false)
|
|
40
|
+
== .singleLineRow)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test func explicitWrapTrueBehavesLikeTheDefault() {
|
|
44
|
+
#expect(
|
|
45
|
+
SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: true)
|
|
46
|
+
== .wrappingGrid)
|
|
47
|
+
#expect(
|
|
48
|
+
SectionPresentation(for: style(display: .grid, gridWrap: true), supportsWrappingGrid: false)
|
|
49
|
+
== .list)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test func gridWrapIsInertOnLists() {
|
|
53
|
+
// Declaring a decoration never changes the layout you'd get without it.
|
|
54
|
+
#expect(
|
|
55
|
+
SectionPresentation(for: style(display: .list, gridWrap: false), supportsWrappingGrid: true)
|
|
56
|
+
== .list)
|
|
57
|
+
#expect(
|
|
58
|
+
SectionPresentation(for: style(display: nil, gridWrap: false), supportsWrappingGrid: true)
|
|
59
|
+
== .list)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test func tileFamilyFollowsGridTileWithThePlainSplit() {
|
|
63
|
+
// Card/condensed follow the declaration in either wrap mode; the plain
|
|
64
|
+
// default splits on the wrap: single-line keeps row elements (the
|
|
65
|
+
// subtitle slot), wrapping renders the image grid.
|
|
66
|
+
#expect(
|
|
67
|
+
SectionPresentation.tileFamily(for: SectionStyle(gridTile: .card), singleLine: true)
|
|
68
|
+
== .cardElements)
|
|
69
|
+
#expect(
|
|
70
|
+
SectionPresentation.tileFamily(for: SectionStyle(gridTile: .condensed), singleLine: false)
|
|
71
|
+
== .condensedElements)
|
|
72
|
+
#expect(SectionPresentation.tileFamily(for: nil, singleLine: true) == .rowElements)
|
|
73
|
+
#expect(SectionPresentation.tileFamily(for: nil, singleLine: false) == .imageGridElements)
|
|
74
|
+
#expect(
|
|
75
|
+
SectionPresentation.tileFamily(for: SectionStyle(gridTile: .plain), singleLine: true)
|
|
76
|
+
== .rowElements)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test func effectiveAccessorySymbol_noneAndAbsenceDrawNothing() {
|
|
80
|
+
// A declared symbol draws; 'none' (the inheritance escape) and absence
|
|
81
|
+
// both fall back to the derived accessory.
|
|
82
|
+
#expect(
|
|
83
|
+
SectionPresentation.effectiveAccessorySymbol(TrackStyle(accessorySymbol: "lock.fill"))
|
|
84
|
+
== "lock.fill")
|
|
85
|
+
#expect(
|
|
86
|
+
SectionPresentation.effectiveAccessorySymbol(TrackStyle(accessorySymbol: "none")) == nil)
|
|
87
|
+
#expect(SectionPresentation.effectiveAccessorySymbol(TrackStyle()) == nil)
|
|
88
|
+
#expect(SectionPresentation.effectiveAccessorySymbol(nil) == nil)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -2,81 +2,113 @@
|
|
|
2
2
|
import Testing
|
|
3
3
|
|
|
4
4
|
struct SectionScopeTests {
|
|
5
|
-
private func track(_ src: String,
|
|
6
|
-
Track(id: src, src: src
|
|
5
|
+
private func track(_ src: String, id: String? = nil) -> Track {
|
|
6
|
+
Track(id: id ?? src, src: src)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
private func
|
|
10
|
-
|
|
11
|
-
return tracks.map(\.src)
|
|
9
|
+
private func section(_ title: String? = nil, _ tracks: [Track]) -> Section {
|
|
10
|
+
Section(title: title, children: tracks)
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
private func
|
|
15
|
-
|
|
16
|
-
return items.map(\.src)
|
|
13
|
+
private func srcs(_ scoped: SectionScope.Scoped?) -> [String?]? {
|
|
14
|
+
scoped.map { $0.section.children.map(\.src) }
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
track("
|
|
25
|
-
track("
|
|
17
|
+
// Two sections sharing an identity — the duplicate-heavy shape the flat
|
|
18
|
+
// model had to disambiguate by precedence; sections + the stamped flat
|
|
19
|
+
// index make it exact.
|
|
20
|
+
private var sections: [Section] {
|
|
21
|
+
[
|
|
22
|
+
section("First", [track("dup"), track("x")]),
|
|
23
|
+
section("Second", [track("y"), track("dup"), track("z")]),
|
|
26
24
|
]
|
|
27
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "c")) == ["c", "d"])
|
|
28
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a", "b"])
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
@Test func
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
track("c"),
|
|
36
|
-
track("d", group: "Second"),
|
|
37
|
-
]
|
|
38
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "b")) == ["b", "c"])
|
|
27
|
+
@Test func fallsBackToTheFirstSectionContainingTheIdentity() {
|
|
28
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup")
|
|
29
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
30
|
+
#expect(scoped?.tappedOffset == nil)
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
@Test func
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
]
|
|
47
|
-
#expect(runSrcs(SectionScope.section(of: children, containing: "a")) == ["a"])
|
|
33
|
+
@Test func flatIndexPinsTheTappedSection() {
|
|
34
|
+
// Flat index 3 = second section, offset 1.
|
|
35
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 3)
|
|
36
|
+
#expect(srcs(scoped) == ["y", "dup", "z"])
|
|
37
|
+
#expect(scoped?.tappedOffset == 1)
|
|
48
38
|
}
|
|
49
39
|
|
|
50
|
-
@Test func
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
@Test func flatIndexPinsTheExactCopyWithinASection() {
|
|
41
|
+
let playlist = [section(nil, [track("a"), track("b"), track("a"), track("c")])]
|
|
42
|
+
let scoped = SectionScope.scoped(in: playlist, containing: "a", tappedIndex: 2)
|
|
43
|
+
#expect(scoped?.tappedOffset == 2)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Test func staleIndexFallsBackToTheFirstIdentityMatch() {
|
|
47
|
+
// The child at the stamped index no longer carries the tapped identity
|
|
48
|
+
// (the list shifted) — resolution ignores the index and pins nothing.
|
|
49
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 1)
|
|
50
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
51
|
+
#expect(scoped?.tappedOffset == nil)
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
@Test func
|
|
62
|
-
|
|
54
|
+
@Test func outOfRangeIndexIsIgnored() {
|
|
55
|
+
let scoped = SectionScope.scoped(in: sections, containing: "dup", tappedIndex: 99)
|
|
56
|
+
#expect(srcs(scoped) == ["dup", "x"])
|
|
57
|
+
#expect(scoped?.tappedOffset == nil)
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@Test func imageRowWinsOverAFlatListDuplicate() {
|
|
68
|
-
var row = Track(id: "row", title: "Row")
|
|
69
|
-
row.imageRow = [ImageRowItem(src: "dup", title: "Dup")]
|
|
70
|
-
let children = [row, track("dup"), track("b")]
|
|
71
|
-
#expect(imageRowSrcs(SectionScope.section(of: children, containing: "dup")) == ["dup"])
|
|
60
|
+
@Test func vanishedIdentityReturnsNil() {
|
|
61
|
+
#expect(SectionScope.scoped(in: sections, containing: "gone") == nil)
|
|
72
62
|
}
|
|
73
63
|
|
|
74
|
-
|
|
64
|
+
// MARK: - Identity (id when non-blank, else src)
|
|
65
|
+
|
|
66
|
+
@Test func matchesChildByIdWhenSrcDiffers() {
|
|
75
67
|
let children = [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
track("dup", group: "Second"),
|
|
68
|
+
Track(id: "stable-a", src: "https://cdn.example/a?token=1"),
|
|
69
|
+
Track(id: "stable-b", src: "https://cdn.example/b?token=2"),
|
|
79
70
|
]
|
|
80
|
-
|
|
71
|
+
let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "stable-b")
|
|
72
|
+
#expect(srcs(scoped) == ["https://cdn.example/a?token=1", "https://cdn.example/b?token=2"])
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Test func blankIdFallsBackToSrc() {
|
|
76
|
+
let children = [Track(id: "", src: "s1"), Track(id: "", src: "s2")]
|
|
77
|
+
let scoped = SectionScope.scoped(in: [section(nil, children)], containing: "s2")
|
|
78
|
+
#expect(srcs(scoped) == ["s1", "s2"])
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MARK: - Page shape helpers
|
|
82
|
+
|
|
83
|
+
@Test func childrenSugarNormalizesToOneUntitledSection() {
|
|
84
|
+
let page = ResolvedTrack(path: "/p", children: [track("a"), track("b")], title: "P")
|
|
85
|
+
let sections = page.normalizedSections
|
|
86
|
+
#expect(sections?.count == 1)
|
|
87
|
+
#expect(sections?.first?.title == nil)
|
|
88
|
+
#expect(sections?.first?.children.map(\.src) == ["a", "b"])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test func sectionsWinOverChildren() {
|
|
92
|
+
let page = ResolvedTrack(
|
|
93
|
+
path: "/p",
|
|
94
|
+
sections: [section("S", [track("a")])],
|
|
95
|
+
children: [track("zzz")],
|
|
96
|
+
title: "P",
|
|
97
|
+
)
|
|
98
|
+
#expect(page.normalizedSections?.count == 1)
|
|
99
|
+
#expect(page.normalizedSections?.first?.title == "S")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Test func flattenedChildrenConcatenateInSectionOrder() {
|
|
103
|
+
let page = ResolvedTrack(
|
|
104
|
+
path: "/p",
|
|
105
|
+
sections: [section("A", [track("a1"), track("a2")]), section(nil, [track("b1")])],
|
|
106
|
+
title: "P",
|
|
107
|
+
)
|
|
108
|
+
#expect(page.flattenedChildren?.map(\.src) == ["a1", "a2", "b1"])
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Test func childlessPageHasNoSections() {
|
|
112
|
+
#expect(ResolvedTrack(path: "/p", title: "P").normalizedSections == nil)
|
|
81
113
|
}
|
|
82
114
|
}
|