react-native-audio-browser 0.2.0-next.10 → 0.2.0-next.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/android/build.gradle +6 -5
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
- package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +46 -5
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +86 -27
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +20 -6
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +249 -86
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +69 -30
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +345 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +152 -8
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +218 -10
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +131 -143
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/JsonModels.swift +187 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +34 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +47 -0
- package/ios/Browser/TabBarEntries.swift +2 -2
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +105 -67
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/HybridAudioBrowser.swift +113 -35
- package/ios/Model/NitroTypeStubs.swift +64 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/NowPlayingUpdater.swift +1 -1
- package/ios/Player/PlaybackCoordinator.swift +2 -2
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/JsonModelsTests.swift +75 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +4 -4
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +61 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +67 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
- package/nitrogen/generated/android/c++/JSection.hpp +114 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
- package/package.json +3 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +139 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +268 -136
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Track } from '../../types'
|
|
2
2
|
import { nativeBrowser } from '../../native'
|
|
3
3
|
import { NativeUpdatedValue } from '../../utils/NativeUpdatedValue'
|
|
4
|
-
import {
|
|
4
|
+
import { useNativeValueRefreshedBy } from '../../utils/useNativeValueRefreshedBy'
|
|
5
|
+
import { onFavoriteChanged } from '../favorites'
|
|
5
6
|
|
|
6
7
|
// MARK: - Types
|
|
7
8
|
|
|
@@ -165,7 +166,9 @@ export const onQueueEnded =
|
|
|
165
166
|
|
|
166
167
|
/**
|
|
167
168
|
* Subscribes to queue change events.
|
|
168
|
-
* Called when tracks are added, removed, reordered
|
|
169
|
+
* Called when tracks are added, removed, or reordered. An in-place mutation of
|
|
170
|
+
* a queued track's metadata (a favorite toggle) emits `onFavoriteChanged`
|
|
171
|
+
* instead; {@link useQueue} subscribes to both.
|
|
169
172
|
* @param callback - Called with the updated queue
|
|
170
173
|
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
171
174
|
*/
|
|
@@ -175,10 +178,14 @@ export const onQueueChanged = NativeUpdatedValue.emitterize<Track[]>(
|
|
|
175
178
|
|
|
176
179
|
// MARK: - Hooks
|
|
177
180
|
|
|
181
|
+
// Module-level so the hook's subscription identity is stable across renders.
|
|
182
|
+
const queueInvalidators = [onQueueChanged, onFavoriteChanged]
|
|
183
|
+
|
|
178
184
|
/**
|
|
179
|
-
* Hook that returns the current queue
|
|
185
|
+
* Hook that returns the current queue, updating on queue changes AND on
|
|
186
|
+
* in-place mutations of queued tracks (favorite toggles).
|
|
180
187
|
* @returns The current queue
|
|
181
188
|
*/
|
|
182
189
|
export function useQueue(): Track[] {
|
|
183
|
-
return
|
|
190
|
+
return useNativeValueRefreshedBy(getQueue, queueInvalidators)
|
|
184
191
|
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Drift guard for `ios/Model/NitroTypeStubs.swift`.
|
|
7
|
+
*
|
|
8
|
+
* The generated Nitro Swift types are `typealias`es onto C++ structs
|
|
9
|
+
* (`margelo.nitro.audiobrowser.*`), so SwiftPM cannot compile them without the
|
|
10
|
+
* whole Nitro C++ core — which is why the SPM test target substitutes
|
|
11
|
+
* hand-written stubs behind `#if !canImport(NitroModules)`. The cost of that
|
|
12
|
+
* trick is that the stubs can disagree with the real types, and when they do
|
|
13
|
+
* `swift test` still passes while the app build breaks. That has already
|
|
14
|
+
* happened once (1d2edade, "restore the SPM test-stub build after the artwork
|
|
15
|
+
* variants change").
|
|
16
|
+
*
|
|
17
|
+
* Neither native CI job can catch it: the Swift job only ever sees the stubs,
|
|
18
|
+
* and the app build only ever sees the generated types. This test is the one
|
|
19
|
+
* place both files are readable at once, so the check lives here.
|
|
20
|
+
*
|
|
21
|
+
* The rule is subset-with-exact-types. A stub may declare FEWER properties than
|
|
22
|
+
* the real type — shared `ios/` sources can only use what the stub declares, so
|
|
23
|
+
* an omission is safe. What is not safe is a property the real type spells
|
|
24
|
+
* differently: `var id: String` against a generated `id: String?` compiles under
|
|
25
|
+
* test and fails to compile in the app.
|
|
26
|
+
*/
|
|
27
|
+
const ROOT = process.cwd()
|
|
28
|
+
const STUBS = join(ROOT, 'ios', 'Model', 'NitroTypeStubs.swift')
|
|
29
|
+
const GENERATED = join(ROOT, 'nitrogen', 'generated', 'ios', 'swift')
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Stubbed types with no generated counterpart, and why. Anything else missing
|
|
33
|
+
* from `nitrogen/generated/ios/swift/` is a stub for a type that no longer
|
|
34
|
+
* exists — the loudest kind of drift.
|
|
35
|
+
*/
|
|
36
|
+
const NOT_GENERATED: Record<string, string> = {
|
|
37
|
+
// Nitro core type (`nitro::NullType`), shipped by NitroModules rather than
|
|
38
|
+
// emitted per-project, so there is no file of ours to compare against.
|
|
39
|
+
NullType: 'provided by NitroModules core'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Struct = { kind: 'struct'; members: Map<string, string> }
|
|
43
|
+
type Enum = { kind: 'enum'; members: Map<string, string> }
|
|
44
|
+
type Shape = Struct | Enum
|
|
45
|
+
|
|
46
|
+
/** Splits a parameter/case list on top-level commas, ignoring `[]`, `()`, `<>`. */
|
|
47
|
+
function splitTopLevel(text: string): string[] {
|
|
48
|
+
const parts: string[] = []
|
|
49
|
+
let depth = 0
|
|
50
|
+
let current = ''
|
|
51
|
+
for (const char of text) {
|
|
52
|
+
if (char === '[' || char === '(' || char === '<') depth++
|
|
53
|
+
else if (char === ']' || char === ')' || char === '>') depth--
|
|
54
|
+
if (char === ',' && depth === 0) {
|
|
55
|
+
parts.push(current)
|
|
56
|
+
current = ''
|
|
57
|
+
} else current += char
|
|
58
|
+
}
|
|
59
|
+
if (current.trim()) parts.push(current)
|
|
60
|
+
return parts.map((part) => part.trim()).filter(Boolean)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `label: Type` → [label, Type]. Splits on the FIRST colon (`[String: String]`). */
|
|
64
|
+
function splitLabelled(part: string): [string, string] | null {
|
|
65
|
+
const colon = part.indexOf(':')
|
|
66
|
+
if (colon < 0) return null
|
|
67
|
+
const label = part.slice(0, colon).trim()
|
|
68
|
+
// A generated init can carry both an argument label and a parameter name
|
|
69
|
+
// (`for value: String`); the property name is the last word before the colon.
|
|
70
|
+
const name = label.split(/\s+/).pop()!
|
|
71
|
+
return [name, part.slice(colon + 1).trim()]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DECL =
|
|
75
|
+
/^\s*(?:(?:public|@frozen|indirect|final)\s+)*(struct|enum)\s+([A-Za-z_]\w*)\s*(?::[^{]*)?\{/
|
|
76
|
+
|
|
77
|
+
/** Parses every top-level `struct`/`enum` in a Swift file into its member map. */
|
|
78
|
+
function parseSwiftDecls(source: string): Map<string, Shape> {
|
|
79
|
+
const shapes = new Map<string, Shape>()
|
|
80
|
+
const lines = source.split('\n')
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
const decl = DECL.exec(lines[i]!)
|
|
84
|
+
if (!decl) continue
|
|
85
|
+
const keyword = decl[1]!
|
|
86
|
+
const name = decl[2]!
|
|
87
|
+
const members = new Map<string, string>()
|
|
88
|
+
let depth = 1
|
|
89
|
+
|
|
90
|
+
for (let j = i + 1; j < lines.length && depth > 0; j++) {
|
|
91
|
+
const body = lines[j]!.replace(/\/\/.*$/, '')
|
|
92
|
+
|
|
93
|
+
if (depth === 1) {
|
|
94
|
+
// Stored property: `var name: Type`, but not a computed one (`{` after).
|
|
95
|
+
const prop =
|
|
96
|
+
/^\s*(?:public\s+)?var\s+([A-Za-z_]\w*)\s*:\s*([^={]+?)\s*(?:=|$)/.exec(
|
|
97
|
+
body
|
|
98
|
+
)
|
|
99
|
+
if (prop && !/\{\s*$/.test(body)) members.set(prop[1]!, prop[2]!.trim())
|
|
100
|
+
|
|
101
|
+
// `case a`, `case a(T)`, `case a, b`
|
|
102
|
+
const enumCase = /^\s*case\s+(.+)$/.exec(body)
|
|
103
|
+
if (enumCase && keyword === 'enum') {
|
|
104
|
+
for (const one of splitTopLevel(enumCase[1]!)) {
|
|
105
|
+
const withPayload = /^([A-Za-z_]\w*)\s*\((.*)\)$/.exec(one)
|
|
106
|
+
if (withPayload)
|
|
107
|
+
members.set(withPayload[1]!, withPayload[2]!.trim())
|
|
108
|
+
else if (/^[A-Za-z_]\w*$/.test(one)) members.set(one, '')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const char of body) {
|
|
114
|
+
if (char === '{') depth++
|
|
115
|
+
else if (char === '}') depth--
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
shapes.set(name, {
|
|
120
|
+
kind: keyword === 'struct' ? 'struct' : 'enum',
|
|
121
|
+
members
|
|
122
|
+
} as Shape)
|
|
123
|
+
}
|
|
124
|
+
return shapes
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reads the real shape of a generated type. Nitro emits three forms: a struct
|
|
129
|
+
* (memberwise `init`), a string-backed enum (`init?(fromString:)`), and a
|
|
130
|
+
* variant (a plain `enum` with `first`/`second`/… payload cases).
|
|
131
|
+
*/
|
|
132
|
+
function parseGenerated(name: string): Shape | null {
|
|
133
|
+
let source: string
|
|
134
|
+
try {
|
|
135
|
+
source = readFileSync(join(GENERATED, `${name}.swift`), 'utf8')
|
|
136
|
+
} catch {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Variant enums are declared outright, so the generic parser sees them.
|
|
141
|
+
const declared = parseSwiftDecls(source).get(name)
|
|
142
|
+
if (declared && declared.members.size > 0) return declared
|
|
143
|
+
|
|
144
|
+
// String-backed enum. Read the case names off the `stringValue` switch, which
|
|
145
|
+
// spells them out (`case .get: return "GET"`), rather than deriving them from
|
|
146
|
+
// the JS values — nitrogen's casing rule is not ours to reimplement.
|
|
147
|
+
if (source.includes('var stringValue: String')) {
|
|
148
|
+
const members = new Map<string, string>()
|
|
149
|
+
for (const match of source.matchAll(/case\s+\.(\w+)\s*:/g))
|
|
150
|
+
members.set(match[1]!, '')
|
|
151
|
+
return { kind: 'enum', members }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const init = /^\s*init\((.*)\)\s*\{/m.exec(source)
|
|
155
|
+
if (init) {
|
|
156
|
+
const members = new Map<string, string>()
|
|
157
|
+
for (const part of splitTopLevel(init[1]!)) {
|
|
158
|
+
const labelled = splitLabelled(part)
|
|
159
|
+
if (labelled) members.set(labelled[0], labelled[1])
|
|
160
|
+
}
|
|
161
|
+
return { kind: 'struct', members }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sugared and desugared spellings are the same Swift type — nitrogen emits
|
|
169
|
+
* `Dictionary<String, String>` where the stubs write `[String: String]`.
|
|
170
|
+
*/
|
|
171
|
+
function normalizeType(type: string): string {
|
|
172
|
+
let previous: string
|
|
173
|
+
let current = type.replace(/\s+/g, ' ').trim()
|
|
174
|
+
do {
|
|
175
|
+
previous = current
|
|
176
|
+
current = current
|
|
177
|
+
.replace(/\bDictionary<([^<>]+),\s*([^<>]+)>/g, '[$1: $2]')
|
|
178
|
+
.replace(/\bArray<([^<>]+)>/g, '[$1]')
|
|
179
|
+
.replace(/\bOptional<([^<>]+)>/g, '$1?')
|
|
180
|
+
} while (current !== previous)
|
|
181
|
+
return current
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
describe('NitroTypeStubs mirror the generated Nitro types', () => {
|
|
185
|
+
const stubs = parseSwiftDecls(readFileSync(STUBS, 'utf8'))
|
|
186
|
+
|
|
187
|
+
it('parses the stub file', () => {
|
|
188
|
+
// A parser that silently matches nothing would make every check below pass.
|
|
189
|
+
expect(stubs.size).toBeGreaterThan(20)
|
|
190
|
+
expect(stubs.get('Track')?.members.get('src')).toBe('String?')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('stubs only types that Nitro actually generates', () => {
|
|
194
|
+
const orphans = [...stubs.keys()].filter(
|
|
195
|
+
(name) => !(name in NOT_GENERATED) && parseGenerated(name) == null
|
|
196
|
+
)
|
|
197
|
+
expect(
|
|
198
|
+
orphans,
|
|
199
|
+
`Stubbed with no generated counterpart in nitrogen/generated/ios/swift — the type was ` +
|
|
200
|
+
`renamed or removed from the Nitro spec, so the stub is standing in for nothing:\n` +
|
|
201
|
+
orphans.join('\n')
|
|
202
|
+
).toEqual([])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('declares every stubbed property with the generated type', () => {
|
|
206
|
+
const drift: string[] = []
|
|
207
|
+
|
|
208
|
+
for (const [name, stub] of stubs) {
|
|
209
|
+
if (name in NOT_GENERATED) continue
|
|
210
|
+
const real = parseGenerated(name)
|
|
211
|
+
if (real == null) continue // reported by the previous test
|
|
212
|
+
|
|
213
|
+
for (const [member, stubType] of stub.members) {
|
|
214
|
+
const realType = real.members.get(member)
|
|
215
|
+
const what = real.kind === 'enum' ? 'case' : 'property'
|
|
216
|
+
if (realType == null) {
|
|
217
|
+
drift.push(
|
|
218
|
+
`${name}.${member}: stub declares a ${what} the generated type does not have`
|
|
219
|
+
)
|
|
220
|
+
} else if (normalizeType(realType) !== normalizeType(stubType)) {
|
|
221
|
+
drift.push(
|
|
222
|
+
`${name}.${member}: stub \`${stubType || '(no payload)'}\`, ` +
|
|
223
|
+
`generated \`${realType || '(no payload)'}\``
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
expect(
|
|
230
|
+
drift,
|
|
231
|
+
`ios/Model/NitroTypeStubs.swift disagrees with nitrogen/generated/ios/swift. ` +
|
|
232
|
+
`\`swift test\` compiles against the stub and will not catch this; the app build ` +
|
|
233
|
+
`compiles against the generated type and will fail. Fix the stub to match:\n` +
|
|
234
|
+
drift.join('\n')
|
|
235
|
+
).toEqual([])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('declares struct members in the generated initializer order', () => {
|
|
239
|
+
// Name/type parity is not enough: the stub struct's memberwise init must
|
|
240
|
+
// accept the same ARGUMENT ORDER as the generated labelled init, or shared
|
|
241
|
+
// sources compile under `swift test` and fail in the app build with
|
|
242
|
+
// "argument 'x' must precede argument 'y'" (nitrogen flattens `extends`
|
|
243
|
+
// own-properties-first, which is easy to get backwards — SectionStyle did).
|
|
244
|
+
// A stub may omit members; the ones it declares must be a subsequence of
|
|
245
|
+
// the generated order.
|
|
246
|
+
const misordered: string[] = []
|
|
247
|
+
|
|
248
|
+
for (const [name, stub] of stubs) {
|
|
249
|
+
if (name in NOT_GENERATED || stub.kind !== 'struct') continue
|
|
250
|
+
const real = parseGenerated(name)
|
|
251
|
+
if (real == null || real.kind !== 'struct') continue
|
|
252
|
+
|
|
253
|
+
const realOrder = [...real.members.keys()]
|
|
254
|
+
const stubOrder = [...stub.members.keys()].filter((member) =>
|
|
255
|
+
real.members.has(member)
|
|
256
|
+
)
|
|
257
|
+
let cursor = -1
|
|
258
|
+
for (const member of stubOrder) {
|
|
259
|
+
const index = realOrder.indexOf(member)
|
|
260
|
+
if (index < cursor) {
|
|
261
|
+
misordered.push(
|
|
262
|
+
`${name}: stub declares [${stubOrder.join(', ')}], ` +
|
|
263
|
+
`generated init order is [${realOrder.join(', ')}]`
|
|
264
|
+
)
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
cursor = index
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
expect(
|
|
272
|
+
misordered,
|
|
273
|
+
`Stub struct members are out of order relative to the generated init:\n` +
|
|
274
|
+
misordered.join('\n')
|
|
275
|
+
).toEqual([])
|
|
276
|
+
})
|
|
277
|
+
})
|
|
@@ -104,4 +104,44 @@ describe('public surface', () => {
|
|
|
104
104
|
it('does not re-export the raw Nitro object', () => {
|
|
105
105
|
expect(exports.map((e) => e.name)).not.toContain('nativeBrowser')
|
|
106
106
|
})
|
|
107
|
+
|
|
108
|
+
it('every public module is covered by the typedoc entry points', () => {
|
|
109
|
+
// The API reference documents entryPoints, not the export graph — a public
|
|
110
|
+
// module missing from website/typedoc.json ships undocumented.
|
|
111
|
+
const config = JSON.parse(
|
|
112
|
+
readFileSync(join(process.cwd(), 'website', 'typedoc.json'), 'utf8')
|
|
113
|
+
) as { entryPoints: string[] }
|
|
114
|
+
const entries = config.entryPoints.map(
|
|
115
|
+
(glob) =>
|
|
116
|
+
new RegExp(
|
|
117
|
+
'^' +
|
|
118
|
+
glob
|
|
119
|
+
.replace(/^\.\.\/src\//, '')
|
|
120
|
+
.replace(/[.]/g, '\\.')
|
|
121
|
+
.replace(/\*/g, '[^/]+') +
|
|
122
|
+
'$'
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
const covered = (file: string): boolean => {
|
|
126
|
+
if (entries.some((e) => e.test(file))) return true
|
|
127
|
+
// A directory barrel entry (…/index.ts) documents what it re-exports.
|
|
128
|
+
const dir = file.split('/').slice(0, -1)
|
|
129
|
+
while (dir.length > 0) {
|
|
130
|
+
if (entries.some((e) => e.test([...dir, 'index.ts'].join('/'))))
|
|
131
|
+
return true
|
|
132
|
+
dir.pop()
|
|
133
|
+
}
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
const uncovered = [...new Set(exports.map((e) => e.file))].filter(
|
|
137
|
+
(file) => !covered(file)
|
|
138
|
+
)
|
|
139
|
+
expect(
|
|
140
|
+
uncovered,
|
|
141
|
+
'Exported to consumers but absent from the API reference. Add each ' +
|
|
142
|
+
'module (or its directory barrel) to website/typedoc.json ' +
|
|
143
|
+
'entryPoints:\n' +
|
|
144
|
+
uncovered.join('\n')
|
|
145
|
+
).toEqual([])
|
|
146
|
+
})
|
|
107
147
|
})
|
|
@@ -229,9 +229,10 @@ export interface AudioBrowser extends HybridObject<{
|
|
|
229
229
|
seekTo(position: number): void
|
|
230
230
|
seekBy(offset: number): void
|
|
231
231
|
/**
|
|
232
|
-
* Jump to the live edge of the current track
|
|
233
|
-
*
|
|
234
|
-
* window
|
|
232
|
+
* Jump to the live edge of the current track; no-op unless the track
|
|
233
|
+
* declares `live: true`. A stream with a seekable live window (HLS) seeks
|
|
234
|
+
* to the window's edge; a non-seekable stream (e.g. ICY radio) reconnects
|
|
235
|
+
* to rejoin live — an expired URL re-resolves through `media.resolve`.
|
|
235
236
|
*/
|
|
236
237
|
seekToLiveEdge(): void
|
|
237
238
|
setVolume(level: number): void
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Inheritance-completeness guard for the style declaration block (ADR 0011).
|
|
7
|
+
*
|
|
8
|
+
* `SectionStyle extends TrackStyle` in the spec, but Nitro flattens `extends`
|
|
9
|
+
* into independent structs on every platform — so nothing in any type system
|
|
10
|
+
* forces a platform's `StyleResolver` to read a newly added style key. A key
|
|
11
|
+
* the resolver forgets is silently dead: declared by consumers, decoded on the
|
|
12
|
+
* wire, never rendered. This test is the compile-time check the flattening
|
|
13
|
+
* took away:
|
|
14
|
+
*
|
|
15
|
+
* - every `SectionStyle` key must appear in each platform's
|
|
16
|
+
* `sectionStyle(section, page)` merge;
|
|
17
|
+
* - every inherited `TrackStyle` key must appear in each platform's
|
|
18
|
+
* `trackStyle(track, section)` merge;
|
|
19
|
+
* - `display` is positional (the deny-list): the track merge must resolve it
|
|
20
|
+
* to nil/null and must not read it from any level.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const ROOT = process.cwd()
|
|
24
|
+
|
|
25
|
+
const RESOLVERS = [
|
|
26
|
+
{
|
|
27
|
+
file: 'ios/Browser/StyleResolver.swift',
|
|
28
|
+
sectionMarker: 'static func sectionStyle',
|
|
29
|
+
trackMarker: 'static func trackStyle',
|
|
30
|
+
positionalNil: 'display: nil'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
file: 'android/src/main/java/com/audiobrowser/browser/StyleResolver.kt',
|
|
34
|
+
sectionMarker: 'fun sectionStyle',
|
|
35
|
+
trackMarker: 'fun trackStyle',
|
|
36
|
+
positionalNil: 'display = null'
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
/** Keys the spec deliberately excludes from item inheritance. */
|
|
41
|
+
const POSITIONAL = ['display']
|
|
42
|
+
|
|
43
|
+
function interfaceKeys(source: string, name: string): string[] {
|
|
44
|
+
const start = source.indexOf(`export interface ${name} `)
|
|
45
|
+
expect(
|
|
46
|
+
start,
|
|
47
|
+
`interface ${name} not found in browser-nodes.ts`
|
|
48
|
+
).toBeGreaterThan(-1)
|
|
49
|
+
const end = source.indexOf('\n}', start)
|
|
50
|
+
const body = source.slice(start, end)
|
|
51
|
+
return [...body.matchAll(/^ {2}(\w+)\??:/gm)].flatMap((match) =>
|
|
52
|
+
match[1] === undefined ? [] : [match[1]]
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The function's text from `marker` to the next function declaration (or end
|
|
58
|
+
* of file) — brace-agnostic, since Kotlin expression bodies have no braces.
|
|
59
|
+
*/
|
|
60
|
+
function functionBody(source: string, marker: string, file: string): string {
|
|
61
|
+
const start = source.indexOf(marker)
|
|
62
|
+
expect(start, `${marker} not found in ${file}`).toBeGreaterThan(-1)
|
|
63
|
+
const rest = source.slice(start + marker.length)
|
|
64
|
+
const next = rest.search(/\n\s*(?:static func|func|fun)\s/)
|
|
65
|
+
return marker + (next === -1 ? rest : rest.slice(0, next))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Comment-free view, so prose about a key doesn't count as reading it. */
|
|
69
|
+
function stripComments(code: string): string {
|
|
70
|
+
return code
|
|
71
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((line) => line.replace(/\/\/.*$/, ''))
|
|
74
|
+
.join('\n')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const nodes = readFileSync(
|
|
78
|
+
join(ROOT, 'src', 'types', 'browser-nodes.ts'),
|
|
79
|
+
'utf8'
|
|
80
|
+
)
|
|
81
|
+
const trackKeys = interfaceKeys(nodes, 'TrackStyle')
|
|
82
|
+
const sectionKeys = [...trackKeys, ...interfaceKeys(nodes, 'SectionStyle')]
|
|
83
|
+
const inheritedKeys = trackKeys.filter((key) => !POSITIONAL.includes(key))
|
|
84
|
+
|
|
85
|
+
describe('style block shape', () => {
|
|
86
|
+
it('has the positional key and at least one inherited key', () => {
|
|
87
|
+
expect(trackKeys).toContain('display')
|
|
88
|
+
expect(inheritedKeys.length).toBeGreaterThan(0)
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
for (const resolver of RESOLVERS) {
|
|
93
|
+
describe(resolver.file, () => {
|
|
94
|
+
const source = readFileSync(join(ROOT, resolver.file), 'utf8')
|
|
95
|
+
const sectionBody = stripComments(
|
|
96
|
+
functionBody(source, resolver.sectionMarker, resolver.file)
|
|
97
|
+
)
|
|
98
|
+
const trackBody = stripComments(
|
|
99
|
+
functionBody(source, resolver.trackMarker, resolver.file)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
// Containment of `holder?.key` for BOTH holders — `display =
|
|
103
|
+
// section?.display` with the page half dropped must fail — AND in
|
|
104
|
+
// precedence order: the narrower holder must be read first, or the
|
|
105
|
+
// scope-override/inheritance direction is inverted (`page ?? section`
|
|
106
|
+
// would pass a bare containment check).
|
|
107
|
+
it.each(sectionKeys)('sectionStyle merges %s (section ?? page)', (key) => {
|
|
108
|
+
const section = sectionBody.indexOf(`section?.${key}`)
|
|
109
|
+
const page = sectionBody.indexOf(`page?.${key}`)
|
|
110
|
+
expect(section, `section?.${key} not read`).toBeGreaterThan(-1)
|
|
111
|
+
expect(page, `page?.${key} not read`).toBeGreaterThan(-1)
|
|
112
|
+
expect(section, `section?.${key} must precede page?.${key}`).toBeLessThan(
|
|
113
|
+
page
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it.each(inheritedKeys)(
|
|
118
|
+
'trackStyle inherits %s (track ?? section)',
|
|
119
|
+
(key) => {
|
|
120
|
+
const track = trackBody.indexOf(`track?.${key}`)
|
|
121
|
+
const section = trackBody.indexOf(`section?.${key}`)
|
|
122
|
+
expect(track, `track?.${key} not read`).toBeGreaterThan(-1)
|
|
123
|
+
expect(section, `section?.${key} not read`).toBeGreaterThan(-1)
|
|
124
|
+
expect(
|
|
125
|
+
track,
|
|
126
|
+
`track?.${key} must precede section?.${key}`
|
|
127
|
+
).toBeLessThan(section)
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
it('trackStyle excludes the positional display (deny-list)', () => {
|
|
132
|
+
expect(trackBody).toContain(resolver.positionalNil)
|
|
133
|
+
// The nil assignment must be the ONLY mention: reading display from any
|
|
134
|
+
// level here would item-inherit a positional key.
|
|
135
|
+
const mentions = trackBody.match(/display/g) ?? []
|
|
136
|
+
expect(mentions).toHaveLength(1)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type {
|
|
10
10
|
ArtworkRequestConfig,
|
|
11
11
|
BrowserSourceCallback,
|
|
12
|
-
|
|
12
|
+
ResolveAlbumPathCallback,
|
|
13
13
|
FormatNavigationErrorCallback,
|
|
14
14
|
HandleTrackLoadCallback,
|
|
15
15
|
MediaRequestConfig,
|
|
@@ -76,6 +76,6 @@ export interface NativeBrowserConfiguration {
|
|
|
76
76
|
|
|
77
77
|
// CarPlay options
|
|
78
78
|
carPlayLoadingTitle?: string
|
|
79
|
-
|
|
79
|
+
resolveAlbumPath?: ResolveAlbumPathCallback
|
|
80
80
|
formatNavigationError?: FormatNavigationErrorCallback
|
|
81
81
|
}
|