react-native-audio-browser 0.2.0-next.11 → 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/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/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 +26 -39
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +228 -50
- 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/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 +46 -21
- 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/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 +1 -1
- 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 +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 +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
package/src/types/browser.ts
CHANGED
|
@@ -15,9 +15,10 @@ export interface TrackLoadEvent {
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Callback for handling track load events.
|
|
18
|
-
* When set on BrowserConfiguration,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* When set on BrowserConfiguration, loading a playable track — a browse-row
|
|
19
|
+
* tap or a `navigate(track)` call — invokes this handler instead of the
|
|
20
|
+
* library auto-loading/playing it. The native side awaits the returned
|
|
21
|
+
* promise before proceeding (e.g., showing Now Playing in CarPlay).
|
|
21
22
|
*/
|
|
22
23
|
export type HandleTrackLoadCallback = (event: TrackLoadEvent) => Promise<void>
|
|
23
24
|
|
|
@@ -187,7 +188,7 @@ export interface RequestConfig {
|
|
|
187
188
|
method?: HttpMethod
|
|
188
189
|
/**
|
|
189
190
|
* The request path.
|
|
190
|
-
* - For browser requests, this is the track's `
|
|
191
|
+
* - For browser requests, this is the track's `path`
|
|
191
192
|
* - For media requests, this is the track's `src` value
|
|
192
193
|
* - For artwork requests, this is the track's `artwork` URL
|
|
193
194
|
*/
|
|
@@ -351,7 +352,7 @@ export type RequestConfigResolver = () =>
|
|
|
351
352
|
/**
|
|
352
353
|
* Configuration for artwork image requests
|
|
353
354
|
*
|
|
354
|
-
*
|
|
355
|
+
* ### Configuration Hierarchy
|
|
355
356
|
*
|
|
356
357
|
* When a request is made, configs are merged in this order (later overrides earlier):
|
|
357
358
|
* 1. `request` (base config) - shared settings like user agent, common headers
|
|
@@ -360,7 +361,7 @@ export type RequestConfigResolver = () =>
|
|
|
360
361
|
* 4. `imageQueryParams` - automatic size query param injection (if configured)
|
|
361
362
|
* 5. `transform(request, context)` result - final modifications (if provided)
|
|
362
363
|
*
|
|
363
|
-
*
|
|
364
|
+
* ### Usage Patterns
|
|
364
365
|
*
|
|
365
366
|
* **Simple CDN configuration:**
|
|
366
367
|
* ```typescript
|
|
@@ -426,7 +427,7 @@ export interface ImageQueryParams {
|
|
|
426
427
|
* Used for both `media` (audio streaming) and `artwork` (image loading) configuration
|
|
427
428
|
* in BrowserConfiguration.
|
|
428
429
|
*
|
|
429
|
-
*
|
|
430
|
+
* ### Configuration Hierarchy
|
|
430
431
|
*
|
|
431
432
|
* When a request is made, configs are merged in this order (later overrides earlier):
|
|
432
433
|
* 1. `request` (base config) - shared settings like user agent, common headers
|
|
@@ -434,7 +435,7 @@ export interface ImageQueryParams {
|
|
|
434
435
|
* 3. `resolve(track)` result - per-track overrides (if provided)
|
|
435
436
|
* 4. `transform(request)` result - final modifications (if provided)
|
|
436
437
|
*
|
|
437
|
-
*
|
|
438
|
+
* ### Usage Patterns
|
|
438
439
|
*
|
|
439
440
|
* **Simple CDN configuration:**
|
|
440
441
|
* ```typescript
|
|
@@ -590,9 +591,9 @@ export interface ArtworkRequestConfig extends RequestConfig {
|
|
|
590
591
|
*
|
|
591
592
|
* **Response shape (HTTP / `TransformableRequestConfig`):** the endpoint must
|
|
592
593
|
* return a single **page object** — a {@link ResolvedTrack}
|
|
593
|
-
* (`{ title,
|
|
594
|
+
* (`{ title, path?, children: Track[] }`). The `children` array holds the rows
|
|
594
595
|
* shown for the container; each child is a playable leaf (`src`) or a navigable
|
|
595
|
-
* sub-container (`
|
|
596
|
+
* sub-container (`path`). A callback / static `ResolvedTrack` returns the same
|
|
596
597
|
* page object directly.
|
|
597
598
|
*
|
|
598
599
|
* {@link SearchSource} and {@link TabsSource} HTTP endpoints return this same
|
|
@@ -656,43 +657,10 @@ export type TabsSource =
|
|
|
656
657
|
*/
|
|
657
658
|
export type SearchSource = SearchSourceCallback | TransformableRequestConfig
|
|
658
659
|
|
|
659
|
-
/**
|
|
660
|
-
* How ids passed to `setFavorites` are matched against a
|
|
661
|
-
* track's `src` to hydrate its `favorited` flag.
|
|
662
|
-
*
|
|
663
|
-
* - `'exact'`: the id must equal `src`.
|
|
664
|
-
* - `'partial'`: the id matches if it appears as a complete path segment within
|
|
665
|
-
* `src` (delimited by `/`, `?`, `#`, or the string boundaries). Useful when
|
|
666
|
-
* favorites are stored as a stable identifier that is embedded in — but not
|
|
667
|
-
* equal to — the playable `src` URL.
|
|
668
|
-
*
|
|
669
|
-
* @example
|
|
670
|
-
* ```text
|
|
671
|
-
* setFavorites(['abc123']) + track.src = '/stream/jazz-fm/abc123'
|
|
672
|
-
*
|
|
673
|
-
* 'exact' 'abc123' === '/stream/jazz-fm/abc123' → not favorited
|
|
674
|
-
* 'partial' 'abc123' is the last segment of the src → favorited
|
|
675
|
-
*
|
|
676
|
-
* setFavorites(['https://cdn.example.com/jazz-fm/abc123.mp3'])
|
|
677
|
-
*
|
|
678
|
-
* 'exact' id === src → favorited
|
|
679
|
-
* 'partial' id is a full segment of src → favorited
|
|
680
|
-
* ```
|
|
681
|
-
*/
|
|
682
|
-
export type FavoritesMatchMode = 'exact' | 'partial'
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Object form of the `favorite` capability — enables favoriting with an explicit
|
|
686
|
-
* {@link FavoritesMatchMode}. (A bare `true` is shorthand for `{ match: 'exact' }`.)
|
|
687
|
-
*/
|
|
688
|
-
export interface FavoriteConfig {
|
|
689
|
-
match: FavoritesMatchMode
|
|
690
|
-
}
|
|
691
|
-
|
|
692
660
|
export type BrowserConfiguration = {
|
|
693
661
|
/**
|
|
694
662
|
* Initial navigation path. Setting this triggers initial navigation to the
|
|
695
|
-
* specified path. When unset, the first tab's
|
|
663
|
+
* specified path. When unset, the first tab's path is used; when there are
|
|
696
664
|
* no tabs either, `/`.
|
|
697
665
|
*/
|
|
698
666
|
path?: string | undefined
|
|
@@ -874,19 +842,19 @@ export type BrowserConfiguration = {
|
|
|
874
842
|
|
|
875
843
|
/**
|
|
876
844
|
* Configuration for navigation tabs in the media browser.
|
|
877
|
-
* The first tab's
|
|
845
|
+
* The first tab's path is automatically loaded when the browser starts.
|
|
878
846
|
*
|
|
879
847
|
* Optional - if not provided, no tab navigation will be available.
|
|
880
848
|
* Limited to maximum 4 tabs for automotive platform compatibility (Android Auto/CarPlay).
|
|
881
849
|
*
|
|
882
|
-
* Can provide static array of Track objects with
|
|
850
|
+
* Can provide static array of Track objects with paths as tabs, API configuration, or custom callback.
|
|
883
851
|
*/
|
|
884
852
|
tabs?: TabsSource
|
|
885
853
|
|
|
886
854
|
/**
|
|
887
|
-
* Route-specific configurations. Maps
|
|
855
|
+
* Route-specific configurations. Maps path patterns to browse sources.
|
|
888
856
|
*
|
|
889
|
-
*
|
|
857
|
+
* ### Matching
|
|
890
858
|
*
|
|
891
859
|
* Patterns match on **exact segment count** — `/artists` does NOT match
|
|
892
860
|
* `/artists/123`. Segments may be:
|
|
@@ -900,7 +868,7 @@ export type BrowserConfiguration = {
|
|
|
900
868
|
* When several patterns match, the most specific wins
|
|
901
869
|
* (constants > parameters > wildcards > tail wildcard).
|
|
902
870
|
*
|
|
903
|
-
*
|
|
871
|
+
* ### Defaults and reserved keys
|
|
904
872
|
*
|
|
905
873
|
* A path matching no route is fetched over HTTP via the `request` →
|
|
906
874
|
* `browse` layers with the path applied — most APIs only need explicit
|
|
@@ -983,22 +951,22 @@ export type BrowserConfiguration = {
|
|
|
983
951
|
|
|
984
952
|
/**
|
|
985
953
|
* Called when the album line on the CarPlay Now Playing screen is tapped
|
|
986
|
-
* and the active track has no {@link Track.
|
|
954
|
+
* and the active track has no {@link Track.albumPath}. Return a browse path
|
|
987
955
|
* to navigate the CarPlay browse stack there, or `undefined` if the tap was
|
|
988
956
|
* handled (or should do nothing).
|
|
989
957
|
*
|
|
990
|
-
* The album line is tappable whenever the active track has an `
|
|
958
|
+
* The album line is tappable whenever the active track has an `albumPath`
|
|
991
959
|
* or this callback is configured.
|
|
992
960
|
*
|
|
993
961
|
* @example
|
|
994
962
|
* ```typescript
|
|
995
|
-
*
|
|
963
|
+
* resolveAlbumPath: (track) =>
|
|
996
964
|
* track.album ? `/album/${slugify(track.album)}` : undefined
|
|
997
965
|
* ```
|
|
998
966
|
*
|
|
999
967
|
* @platform ios
|
|
1000
968
|
*/
|
|
1001
|
-
|
|
969
|
+
resolveAlbumPath?: ResolveAlbumPathCallback
|
|
1002
970
|
|
|
1003
971
|
/**
|
|
1004
972
|
* Callback to customize error messages for navigation errors.
|
|
@@ -1038,6 +1006,17 @@ export type CarPlayNowPlayingButton =
|
|
|
1038
1006
|
| 'favorite'
|
|
1039
1007
|
| 'playback-rate'
|
|
1040
1008
|
|
|
1009
|
+
/**
|
|
1010
|
+
* Where the now-playing indicator draws on a CarPlay list row.
|
|
1011
|
+
*
|
|
1012
|
+
* - `'leading'`: in the artwork slot, replacing the row's artwork while it
|
|
1013
|
+
* plays (the system default, as Apple Music does)
|
|
1014
|
+
* - `'trailing'`: at the end of the row, keeping the artwork visible
|
|
1015
|
+
*
|
|
1016
|
+
* @platform ios
|
|
1017
|
+
*/
|
|
1018
|
+
export type CarPlayPlayingIndicatorLocation = 'leading' | 'trailing'
|
|
1019
|
+
|
|
1041
1020
|
/**
|
|
1042
1021
|
* Position of the "Ask Siri to Play Audio" assistant cell on a CarPlay list template.
|
|
1043
1022
|
*
|
|
@@ -1111,6 +1090,6 @@ export type FormatNavigationErrorCallback = (
|
|
|
1111
1090
|
|
|
1112
1091
|
/**
|
|
1113
1092
|
* Maps the tapped active track to a browse path for the CarPlay album line,
|
|
1114
|
-
* or `undefined` to do nothing. See `
|
|
1093
|
+
* or `undefined` to do nothing. See `resolveAlbumPath`.
|
|
1115
1094
|
*/
|
|
1116
|
-
export type
|
|
1095
|
+
export type ResolveAlbumPathCallback = (track: Track) => string | undefined
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
type Callback<T> = (arg: T) => void
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The subscription object behind the library's **discrete** events — the
|
|
5
|
+
* remote-control exports (`onRemotePlay`, `onRemoteSeek`, …) and other
|
|
6
|
+
* one-shot signals with no "current value" to cache. Subscribe with
|
|
7
|
+
* {@link addListener}; the returned function unsubscribes. The emitter is
|
|
8
|
+
* not callable itself.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* For critical state changes (playback, progress, etc.), use NativeEmitter instead to ensure
|
|
11
|
-
* no events are missed.
|
|
10
|
+
* "Lazy" is an implementation detail you may notice in timing: the native
|
|
11
|
+
* side only starts delivering the event once the first listener is added.
|
|
12
|
+
* State that always has a current reading uses `NativeUpdatedValue`
|
|
13
|
+
* instead.
|
|
12
14
|
*
|
|
13
15
|
* @example
|
|
14
16
|
* ```typescript
|
|
15
|
-
*
|
|
16
|
-
* (cb) => (nativeBrowser.onRemotePlay = cb)
|
|
17
|
-
* )
|
|
17
|
+
* import { onRemotePlay } from 'react-native-audio-browser'
|
|
18
18
|
*
|
|
19
19
|
* const unsubscribe = onRemotePlay.addListener(() => console.log('play'))
|
|
20
|
+
* // later:
|
|
21
|
+
* unsubscribe()
|
|
20
22
|
* ```
|
|
21
23
|
*/
|
|
22
24
|
export class LazyNativeEmitter<T> {
|
|
23
25
|
private setter: (callback: (data: T) => void) => void
|
|
24
26
|
private listeners: Set<(arg: T) => void> | undefined
|
|
25
27
|
|
|
28
|
+
/** @internal */
|
|
26
29
|
constructor(setter: (callback: (data: T) => void) => void) {
|
|
27
30
|
this.setter = setter
|
|
28
31
|
}
|
|
@@ -38,6 +41,12 @@ export class LazyNativeEmitter<T> {
|
|
|
38
41
|
return listeners
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Subscribes to the event.
|
|
46
|
+
*
|
|
47
|
+
* @param callback - Invoked with each event
|
|
48
|
+
* @returns A function that removes the listener
|
|
49
|
+
*/
|
|
41
50
|
addListener(callback: Callback<T>): () => void {
|
|
42
51
|
const listeners = this.getListeners()
|
|
43
52
|
listeners.add(callback)
|
|
@@ -50,6 +59,7 @@ export class LazyNativeEmitter<T> {
|
|
|
50
59
|
* @param setter - Function that sets the native callback property
|
|
51
60
|
* @returns The emitter instance — subscribe with `addListener`, which returns
|
|
52
61
|
* an unsubscribe function
|
|
62
|
+
* @internal
|
|
53
63
|
*/
|
|
54
64
|
static emitterize<T>(
|
|
55
65
|
setter: (callback: (data: T) => void) => void
|
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
type Callback<T> = (arg: T) => void
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* The subscription object behind the library's **state** events — every
|
|
5
|
+
* `on<Thing>Changed`-style export (`onPlaybackChanged`, `onQueueChanged`, …)
|
|
6
|
+
* is one of these. Subscribe with {@link addListener}; the returned function
|
|
7
|
+
* unsubscribes. The emitter is not callable itself.
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* For discrete events without a current value (remote controls), use LazyNativeEmitter instead.
|
|
9
|
+
* It tracks a value that always has a *current* reading (playback state,
|
|
10
|
+
* progress, options): {@link lastValue} caches the most recent one so hooks —
|
|
11
|
+
* and you — can read it synchronously without a getter round-trip. Discrete
|
|
12
|
+
* events with no current value (remote-control presses) use
|
|
13
|
+
* `LazyNativeEmitter` instead.
|
|
12
14
|
*
|
|
13
15
|
* @example
|
|
14
16
|
* ```typescript
|
|
15
|
-
*
|
|
16
|
-
* (cb) => (nativeBrowser.onPlaybackChanged = cb)
|
|
17
|
-
* )
|
|
17
|
+
* import { onPlaybackChanged } from 'react-native-audio-browser'
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* }
|
|
19
|
+
* const unsubscribe = onPlaybackChanged.addListener((p) => {
|
|
20
|
+
* console.log(p.state)
|
|
21
|
+
* })
|
|
22
|
+
* // later:
|
|
23
|
+
* unsubscribe()
|
|
22
24
|
* ```
|
|
23
25
|
*/
|
|
24
26
|
export class NativeUpdatedValue<T> {
|
|
25
27
|
private listeners = new Set<Callback<T>>()
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The most recent value delivered by native, or `undefined` before the
|
|
31
|
+
* first update. Subscription starts at module load, so this is safe to
|
|
32
|
+
* read synchronously at any time.
|
|
33
|
+
*/
|
|
26
34
|
lastValue: T | undefined = undefined
|
|
27
35
|
|
|
36
|
+
/** @internal */
|
|
28
37
|
constructor(setter: (callback: (data: T) => void) => void) {
|
|
29
38
|
// Install native callback immediately at module load
|
|
30
39
|
setter((data: T) => {
|
|
@@ -35,6 +44,12 @@ export class NativeUpdatedValue<T> {
|
|
|
35
44
|
})
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes to value changes.
|
|
49
|
+
*
|
|
50
|
+
* @param callback - Invoked with each new value
|
|
51
|
+
* @returns A function that removes the listener
|
|
52
|
+
*/
|
|
38
53
|
addListener(callback: Callback<T>): () => void {
|
|
39
54
|
this.listeners.add(callback)
|
|
40
55
|
return () => this.listeners.delete(callback)
|
|
@@ -45,6 +60,7 @@ export class NativeUpdatedValue<T> {
|
|
|
45
60
|
*
|
|
46
61
|
* @param setter - Function that sets the native callback property
|
|
47
62
|
* @returns The subscription instance with lastValue and addListener
|
|
63
|
+
* @internal
|
|
48
64
|
*/
|
|
49
65
|
static emitterize<T>(
|
|
50
66
|
setter: (callback: (data: T) => void) => void
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A track's identity: its opaque `id` when present (non-blank), falling back
|
|
3
|
+
* to the playable `src`. Two tracks refer to the same item exactly when their
|
|
4
|
+
* identities are equal. Browsable-only tracks (neither `id` nor `src`) have
|
|
5
|
+
* no identity — they are addressed by their `path` instead.
|
|
6
|
+
*
|
|
7
|
+
* This is the same comparison the library uses everywhere it matches tracks:
|
|
8
|
+
* favorites (`setFavorites`), the CarPlay / Android Auto "now playing" row
|
|
9
|
+
* indicator, and queue expansion and in-place skipping when a track is
|
|
10
|
+
* selected from browse content. Use it for your own bookkeeping — keying a
|
|
11
|
+
* favorites collection, deduplicating history — so your comparisons agree
|
|
12
|
+
* with the library's.
|
|
13
|
+
*/
|
|
14
|
+
// Decision record: ADR 0008 (docs/adr/).
|
|
15
|
+
export function getTrackIdentity(track: {
|
|
16
|
+
id?: string
|
|
17
|
+
src?: string
|
|
18
|
+
}): string | undefined {
|
|
19
|
+
return track.id?.length ? track.id : track.src
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
type Subscribable = {
|
|
4
|
+
addListener(callback: (event: unknown) => void): () => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for a native-fetched value that must stay fresh across SEVERAL event
|
|
9
|
+
* sources — e.g. the active track, which changes on a queue transition
|
|
10
|
+
* (`onActiveTrackChanged`) but is also mutated in place by a favorite toggle
|
|
11
|
+
* (`onFavoriteChanged`).
|
|
12
|
+
*
|
|
13
|
+
* Unlike {@link useNativeUpdatedValue}, this never reads a cached event
|
|
14
|
+
* payload: every trigger re-reads `getter()`, and the mount effect re-reads
|
|
15
|
+
* once after the subscriptions attach (so an event firing between render and
|
|
16
|
+
* effect cannot be missed). The getters are cheap synchronous Nitro calls.
|
|
17
|
+
*
|
|
18
|
+
* @param getter - Synchronous native getter for the current value
|
|
19
|
+
* @param emitters - Event sources that invalidate the value (a stable,
|
|
20
|
+
* module-level array — identity changes resubscribe)
|
|
21
|
+
*/
|
|
22
|
+
export function useNativeValueRefreshedBy<T>(
|
|
23
|
+
getter: () => T,
|
|
24
|
+
emitters: readonly Subscribable[]
|
|
25
|
+
): T {
|
|
26
|
+
const [value, setValue] = useState(getter)
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setValue(getter())
|
|
30
|
+
const cleanups = emitters.map((emitter) =>
|
|
31
|
+
emitter.addListener(() => setValue(getter()))
|
|
32
|
+
)
|
|
33
|
+
return () => cleanups.forEach((cleanup) => cleanup())
|
|
34
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
35
|
+
}, [getter, ...emitters])
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
}
|
|
@@ -44,6 +44,7 @@ import type {
|
|
|
44
44
|
} from '../specs/audio-browser.nitro'
|
|
45
45
|
import type { ResolvedTrack, Track, TrackLoadEvent } from '../types'
|
|
46
46
|
import type { NativeBrowserConfiguration } from '../types/browser-native'
|
|
47
|
+
import { getTrackIdentity } from '../utils/getTrackIdentity'
|
|
47
48
|
import { BrowserManager } from './browser/BrowserManager'
|
|
48
49
|
import { classifyTrackNavigation } from './browser/classifyTrackNavigation'
|
|
49
50
|
import { FavoriteManager } from './browser/FavoriteManager'
|
|
@@ -57,6 +58,7 @@ import { RemoteCommandController } from './player/RemoteCommandController'
|
|
|
57
58
|
import { QueuePlayer, SleepTimerManager, VolumeFader } from './TrackPlayer'
|
|
58
59
|
import { PlaybackTimer } from './TrackPlayer/PlaybackTimer'
|
|
59
60
|
import { derivePlayingState } from './TrackPlayer/PlayingStateFactory'
|
|
61
|
+
import { BrowserPathHelper } from './util/BrowserPathHelper'
|
|
60
62
|
|
|
61
63
|
/**
|
|
62
64
|
* Web implementation of AudioBrowser (unified browser + player)
|
|
@@ -414,11 +416,13 @@ export class NativeAudioBrowser
|
|
|
414
416
|
* Attempts to skip to a track already in the current queue.
|
|
415
417
|
* Used as an optimization to avoid re-expanding the queue.
|
|
416
418
|
*
|
|
417
|
-
* @param
|
|
419
|
+
* @param track The tapped track (its contextual path pins the exact copy)
|
|
420
|
+
* @param trackId The track's identity (id, falling back to src)
|
|
418
421
|
* @param parentPath The parent path to check against queueSourcePath
|
|
419
422
|
* @returns true if successfully skipped to existing track, false otherwise
|
|
420
423
|
*/
|
|
421
424
|
private async trySkipToExistingQueueTrack(
|
|
425
|
+
track: Track,
|
|
422
426
|
trackId: string,
|
|
423
427
|
parentPath: string
|
|
424
428
|
): Promise<boolean> {
|
|
@@ -426,8 +430,21 @@ export class NativeAudioBrowser
|
|
|
426
430
|
return false
|
|
427
431
|
}
|
|
428
432
|
|
|
433
|
+
// Exact-surface match first: a contextual path carries the tapped page
|
|
434
|
+
// position (__index), so path equality pins the exact copy when the page
|
|
435
|
+
// holds the same identity more than once. The identity match remains for
|
|
436
|
+
// index-less paths (e.g. pre-stamp persisted state); an index-stamped path
|
|
437
|
+
// with no exact match falls through to expansion, which re-scopes the
|
|
438
|
+
// queue to the tapped section.
|
|
429
439
|
const queue = this.getQueue()
|
|
430
|
-
|
|
440
|
+
let index = queue.findIndex((t) => t.path != null && t.path === track.path)
|
|
441
|
+
if (
|
|
442
|
+
index < 0 &&
|
|
443
|
+
track.path != null &&
|
|
444
|
+
BrowserPathHelper.extractIndex(track.path) === undefined
|
|
445
|
+
) {
|
|
446
|
+
index = queue.findIndex((t) => getTrackIdentity(t) === trackId)
|
|
447
|
+
}
|
|
431
448
|
|
|
432
449
|
if (index < 0) {
|
|
433
450
|
return false
|
|
@@ -474,6 +491,11 @@ export class NativeAudioBrowser
|
|
|
474
491
|
*/
|
|
475
492
|
private async navigateTrackAsync(track: Track): Promise<void> {
|
|
476
493
|
try {
|
|
494
|
+
// A disabled track is unavailable — it never plays, whichever surface
|
|
495
|
+
// or stale resume path delivered the selection (Track.disabled; mirrors
|
|
496
|
+
// iOS TrackSelector.select and Android navigateTrack).
|
|
497
|
+
if (track.disabled === true) return
|
|
498
|
+
|
|
477
499
|
const nav = classifyTrackNavigation(track)
|
|
478
500
|
switch (nav.kind) {
|
|
479
501
|
case 'contextual':
|
|
@@ -481,6 +503,7 @@ export class NativeAudioBrowser
|
|
|
481
503
|
if (
|
|
482
504
|
nav.trackId &&
|
|
483
505
|
(await this.trySkipToExistingQueueTrack(
|
|
506
|
+
track,
|
|
484
507
|
nav.trackId,
|
|
485
508
|
nav.parentPath
|
|
486
509
|
))
|
|
@@ -504,7 +527,7 @@ export class NativeAudioBrowser
|
|
|
504
527
|
return
|
|
505
528
|
|
|
506
529
|
case 'invalid':
|
|
507
|
-
throw new Error("Track must have either '
|
|
530
|
+
throw new Error("Track must have either 'path' or 'src' property")
|
|
508
531
|
}
|
|
509
532
|
} catch (error) {
|
|
510
533
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
@@ -941,13 +964,14 @@ export class NativeAudioBrowser
|
|
|
941
964
|
setActiveTrackFavorited(favorited: boolean): void {
|
|
942
965
|
const track = this.getActiveTrack()
|
|
943
966
|
const index = this.getActiveTrackIndex()
|
|
944
|
-
if (!track ||
|
|
967
|
+
if (!track || getTrackIdentity(track) === undefined || index === undefined)
|
|
968
|
+
return
|
|
945
969
|
|
|
946
|
-
// Update favorites set via manager
|
|
970
|
+
// Update favorites set via manager (keyed by track identity)
|
|
947
971
|
if (favorited) {
|
|
948
|
-
this.favoriteManager.addFavorite(track
|
|
972
|
+
this.favoriteManager.addFavorite(track)
|
|
949
973
|
} else {
|
|
950
|
-
this.favoriteManager.removeFavorite(track
|
|
974
|
+
this.favoriteManager.removeFavorite(track)
|
|
951
975
|
}
|
|
952
976
|
|
|
953
977
|
// Create updated track with new favorited state
|
|
@@ -959,27 +983,18 @@ export class NativeAudioBrowser
|
|
|
959
983
|
// Replace the track in the queue
|
|
960
984
|
this.queue.replaceTrack(index, updatedTrack)
|
|
961
985
|
|
|
962
|
-
// Emit favorite changed
|
|
986
|
+
// Emit favorite changed only. A favorite toggle is an in-place mutation of
|
|
987
|
+
// the active track, not a transition — onPlaybackActiveTrackChanged and
|
|
988
|
+
// onPlaybackQueueChanged stay transition-only, and the useActiveTrack /
|
|
989
|
+
// useQueue hooks subscribe to onFavoriteChanged themselves.
|
|
963
990
|
this.onFavoriteChanged({ track: updatedTrack, favorited })
|
|
964
|
-
|
|
965
|
-
// Emit active track changed so useActiveTrack() hook updates
|
|
966
|
-
this.onPlaybackActiveTrackChanged({
|
|
967
|
-
lastIndex: index,
|
|
968
|
-
lastTrack: track,
|
|
969
|
-
lastPosition: this.element?.currentTime ?? 0,
|
|
970
|
-
index,
|
|
971
|
-
track: updatedTrack
|
|
972
|
-
})
|
|
973
|
-
|
|
974
|
-
// Emit queue changed so useQueue() hook updates
|
|
975
|
-
this.emitQueueChanged()
|
|
976
991
|
}
|
|
977
992
|
|
|
978
993
|
toggleActiveTrackFavorited(): void {
|
|
979
994
|
const track = this.getActiveTrack()
|
|
980
|
-
if (!track ||
|
|
995
|
+
if (!track || getTrackIdentity(track) === undefined) return
|
|
981
996
|
|
|
982
|
-
const isFavorited = this.favoriteManager.isFavorited(track
|
|
997
|
+
const isFavorited = this.favoriteManager.isFavorited(track)
|
|
983
998
|
this.setActiveTrackFavorited(!isFavorited)
|
|
984
999
|
}
|
|
985
1000
|
|