react-native-audio-browser 0.2.0-next.11 → 0.2.0-next.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/android/build.gradle +20 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +115 -26
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +262 -188
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +95 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +19 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +109 -74
- package/android/src/main/java/com/audiobrowser/player/Player.kt +51 -30
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +52 -53
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +74 -41
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +229 -50
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +229 -180
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +258 -106
- package/ios/Browser/SearchDrillIn.swift +6 -6
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
- package/ios/Extension/Section+Copying.swift +31 -0
- package/ios/Extension/Track+Copying.swift +6 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +166 -45
- package/ios/Model/NitroTypeStubs.swift +106 -21
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Player/MediaIntentCriteria.swift +13 -5
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +36 -10
- package/ios/Player/RetryManager.swift +4 -3
- package/ios/Player/ShuffleOrder.swift +23 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +53 -18
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/QueueManagerTests.swift +72 -6
- package/ios/Tests/RemoteCommandControllerTests.swift +16 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +7 -0
- package/ios/Tests/TabBarEntriesTests.swift +18 -18
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackPlayer.swift +57 -34
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser-config.js +11 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +2 -2
- package/lib/commonjs/features/favorites.js +13 -10
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/progress.js +2 -1
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +2 -3
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -1
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +2 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +14 -3
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +11 -4
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +144 -87
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -0
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser-config.js +11 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +2 -2
- package/lib/module/features/favorites.js +13 -10
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/progress.js +2 -1
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/features/playback/state.js +2 -3
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -1
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +2 -0
- package/lib/module/features/player/validateOptions.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +14 -3
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +11 -4
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/utils/LazyNativeEmitter.js +21 -10
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +32 -24
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +144 -87
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +2 -2
- package/lib/typescript/src/features/favorites.d.ts +13 -10
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +2 -3
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/player/options.d.ts +21 -19
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +5 -2
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +2 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +361 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +33 -53
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +33 -14
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +22 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +10 -7
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +55 -51
- package/nitrogen/generated/android/c++/JSection.hpp +120 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +23 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +18 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +158 -82
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +25 -10
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +25 -10
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
- package/nitrogen/generated/ios/swift/Section.swift +100 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +20 -73
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
- package/nitrogen/generated/shared/c++/Section.hpp +106 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +15 -31
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +7 -2
- package/src/AudioBrowser.ts +1 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +11 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +2 -2
- package/src/features/favorites.ts +13 -10
- package/src/features/hook-wiring.test.ts +63 -15
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/progress.ts +2 -1
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +2 -3
- package/src/features/playback/trackPlaybackTime.ts +1 -1
- package/src/features/player/options.ts +27 -19
- package/src/features/player/setup.test.ts +12 -3
- package/src/features/player/setup.ts +3 -1
- package/src/features/player/validateOptions.ts +2 -0
- package/src/features/queue/activeTrack.ts +14 -3
- package/src/features/queue/queue.ts +11 -4
- package/src/nitro-stub-drift.test.ts +40 -0
- package/src/public-surface.test.ts +40 -0
- package/src/specs/audio-browser.nitro.ts +4 -3
- package/src/style-resolution.test.ts +210 -0
- package/src/types/browser-native.ts +2 -2
- package/src/types/browser-nodes.ts +377 -135
- package/src/types/browser.ts +34 -55
- package/src/utils/LazyNativeEmitter.ts +20 -10
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.ts +37 -22
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/browser/BrowserManager.ts +176 -95
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +246 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
|
@@ -34,6 +34,24 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
34
34
|
/// silent.
|
|
35
35
|
static let instanceChangedEmitter = Emitter<Void>()
|
|
36
36
|
|
|
37
|
+
/// Installed once, on first instance creation. React Native posts this
|
|
38
|
+
/// notification (from `RCTReloadCommand`) when a JS reload — dev or
|
|
39
|
+
/// OTA-update restart — is triggered, *before* the old runtime is destroyed:
|
|
40
|
+
/// the earliest signal that this instance's JS callbacks are about to go
|
|
41
|
+
/// stale. `init` of the replacement instance drops them too, but only after
|
|
42
|
+
/// the new runtime is up; this observer closes the window in between.
|
|
43
|
+
/// Observed by name so the library needs no React-Core dependency (and
|
|
44
|
+
/// stays inert under SPM tests, where nothing posts it).
|
|
45
|
+
@MainActor private static let reloadObserver: NSObjectProtocol = NotificationCenter.default.addObserver(
|
|
46
|
+
forName: Notification.Name("RCTTriggerReloadCommandNotification"),
|
|
47
|
+
object: nil,
|
|
48
|
+
queue: .main,
|
|
49
|
+
) { _ in
|
|
50
|
+
MainActor.assumeIsolated {
|
|
51
|
+
HybridAudioBrowser.shared?.dropStaleJSCallbacks()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
// MARK: - Private Properties
|
|
38
56
|
|
|
39
57
|
/// Main-confined by convention, not annotation: `deinit` reads it as a
|
|
@@ -70,6 +88,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
70
88
|
playerOptions.carPlayNowPlayingButtons
|
|
71
89
|
}
|
|
72
90
|
|
|
91
|
+
@MainActor var carPlayPlayingIndicatorLocation: CarPlayPlayingIndicatorLocation {
|
|
92
|
+
playerOptions.carPlayPlayingIndicatorLocation
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/// Written from the JS thread, the cooperative pool and MainActor, with a
|
|
74
96
|
/// `didSet` that calls back into Nitro. MainActor-isolated so the compiler
|
|
75
97
|
/// requires an `onMainActor` hop at every access.
|
|
@@ -164,7 +186,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
164
186
|
public var configuration: NativeBrowserConfiguration = .init(
|
|
165
187
|
path: nil, request: nil, requestResolver: nil, browse: nil, browseResolver: nil, media: nil, artwork: nil, nowPlayingArtwork: nil, routes: nil,
|
|
166
188
|
singleTrack: nil, handleTrackLoad: nil,
|
|
167
|
-
androidControllerOfflineError: nil, carPlayLoadingTitle: nil,
|
|
189
|
+
androidControllerOfflineError: nil, carPlayLoadingTitle: nil, resolveAlbumPath: nil, formatNavigationError: nil,
|
|
168
190
|
) {
|
|
169
191
|
didSet {
|
|
170
192
|
// Copied here, on the setter's thread: the Task below outlives this call,
|
|
@@ -181,7 +203,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
181
203
|
Task {
|
|
182
204
|
let tabs = try? await browserManager.queryTabs()
|
|
183
205
|
// Navigate to configured path, first tab, or "/"
|
|
184
|
-
let initialPath = configuredPath ?? tabs?.first?.
|
|
206
|
+
let initialPath = configuredPath ?? tabs?.first?.path ?? "/"
|
|
185
207
|
// Clear error before navigation (matches Kotlin clearNavigationError())
|
|
186
208
|
clearNavigationError()
|
|
187
209
|
do {
|
|
@@ -213,8 +235,14 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
213
235
|
/// state, e.g. CarPlay re-seeding `isGated` at scene connect). Serving content
|
|
214
236
|
/// in that window would be a fail-open leak — same direction as the
|
|
215
237
|
/// resolver-error path in `gateDecision`. Matches Android's default.
|
|
216
|
-
public var resolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> =
|
|
217
|
-
|
|
238
|
+
public var resolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> = HybridAudioBrowser.defaultResolveGate
|
|
239
|
+
|
|
240
|
+
// Computed, not stored: a nonisolated static holding a non-Sendable closure
|
|
241
|
+
// is rejected under Swift 6 concurrency checking.
|
|
242
|
+
static var defaultResolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> {
|
|
243
|
+
{ _ in
|
|
244
|
+
Promise.resolved(withResult: Promise.resolved(withResult: GateDecision(gated: true, gate: nil)))
|
|
245
|
+
}
|
|
218
246
|
}
|
|
219
247
|
|
|
220
248
|
/// Fired when a request is gated (served the gate). Set by JS.
|
|
@@ -306,7 +334,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
306
334
|
// playerAndConfiguredBrowser OnceValue, so we must explicitly stop
|
|
307
335
|
// its player to prevent two audio streams running simultaneously.
|
|
308
336
|
onMainActor {
|
|
337
|
+
// Statics are lazy — touch the observer so it's installed from the first
|
|
338
|
+
// instance onward.
|
|
339
|
+
_ = HybridAudioBrowser.reloadObserver
|
|
309
340
|
HybridAudioBrowser.shared?.player?.destroy()
|
|
341
|
+
HybridAudioBrowser.shared?.dropStaleJSCallbacks()
|
|
310
342
|
playerAndConfiguredBrowser.reset()
|
|
311
343
|
HybridAudioBrowser.shared = self
|
|
312
344
|
// Tell connected external controllers (CarPlay) to re-subscribe against
|
|
@@ -319,7 +351,52 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
319
351
|
setupBrowserCallbacks()
|
|
320
352
|
}
|
|
321
353
|
|
|
354
|
+
/// Drops every JS-provided callback that native code can invoke on its own
|
|
355
|
+
/// initiative (browse resolvers, `handleTrackLoad`, `resolveAlbumPath`,
|
|
356
|
+
/// `formatNavigationError`, `resolveGate`, the now-playing formatter).
|
|
357
|
+
///
|
|
358
|
+
/// Called on the *previous* instance when a new one takes over: its JS runtime
|
|
359
|
+
/// is gone, and invoking a callback from a dead runtime aborts the process —
|
|
360
|
+
/// Nitro's `AsyncJSCallback` throws on a destroyed Dispatcher, and the throw
|
|
361
|
+
/// crosses the generated bridge wrapper's `noexcept` boundary. Native code
|
|
362
|
+
/// (e.g. a CarPlay controller still bound to this instance mid-reload) keeps
|
|
363
|
+
/// working off the data-only config with built-in fallbacks; `resolveGate`
|
|
364
|
+
/// reverts to the fail-closed default it documents for exactly this window.
|
|
365
|
+
/// One-shot: on a normal reload both triggers hit the same dying instance
|
|
366
|
+
/// (the reload notification, then the replacement's `init`), and the second
|
|
367
|
+
/// strip would re-fire the config-setter cascade — another `onConfigChanged`
|
|
368
|
+
/// → CarPlay now-playing button rebuild — for nothing.
|
|
369
|
+
@MainActor private var didDropJSCallbacks = false
|
|
370
|
+
|
|
371
|
+
@MainActor
|
|
372
|
+
private func dropStaleJSCallbacks() {
|
|
373
|
+
guard !didDropJSCallbacks else { return }
|
|
374
|
+
didDropJSCallbacks = true
|
|
375
|
+
// Guarded: assigning `config` marks the browser configured (the setter's
|
|
376
|
+
// didSet), and a never-configured browser must keep cold-start waiters
|
|
377
|
+
// (CarPlay, Siri) blocked on the readiness gate rather than resolve them
|
|
378
|
+
// against an empty config.
|
|
379
|
+
if browserManager.isConfigured {
|
|
380
|
+
browserManager.config = browserManager.config.strippingJSCallbacks()
|
|
381
|
+
}
|
|
382
|
+
resolveGate = Self.defaultResolveGate
|
|
383
|
+
nowPlayingMetadataFormatter = nil
|
|
384
|
+
}
|
|
385
|
+
|
|
322
386
|
deinit {
|
|
387
|
+
// Block-based notification observers are retained by the center until an
|
|
388
|
+
// explicit removeObserver — observer death isn't detected — so without this
|
|
389
|
+
// every JS reload strands another token + closure. They capture `self`
|
|
390
|
+
// weakly, so a stranded one no-ops rather than misbehaving; what accumulates
|
|
391
|
+
// is the tokens and a wasted main-queue dispatch per interruption / route
|
|
392
|
+
// change / media-services reset. (`volumeObservation` needs no counterpart:
|
|
393
|
+
// NSKeyValueObservation invalidates itself when this instance releases it.)
|
|
394
|
+
for observer in [interruptionObserver, routeChangeObserver, mediaServicesResetObserver]
|
|
395
|
+
.compactMap(\.self)
|
|
396
|
+
{
|
|
397
|
+
NotificationCenter.default.removeObserver(observer)
|
|
398
|
+
}
|
|
399
|
+
|
|
323
400
|
// Safety net: ensure the AVPlayer is stopped if this instance is deallocated.
|
|
324
401
|
let player = self.player
|
|
325
402
|
if Thread.isMainThread {
|
|
@@ -447,8 +524,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
447
524
|
executePlayback(intent)
|
|
448
525
|
case .intercepted:
|
|
449
526
|
break
|
|
450
|
-
case let .browse(
|
|
451
|
-
|
|
527
|
+
case let .browse(path):
|
|
528
|
+
navigateToBrowsablePath(path)
|
|
452
529
|
case .none:
|
|
453
530
|
break
|
|
454
531
|
}
|
|
@@ -472,13 +549,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
472
549
|
}
|
|
473
550
|
}
|
|
474
551
|
|
|
475
|
-
private func
|
|
552
|
+
private func navigateToBrowsablePath(_ path: String) {
|
|
476
553
|
clearNavigationError()
|
|
477
554
|
Task {
|
|
478
555
|
do {
|
|
479
|
-
try await browserManager.navigate(
|
|
556
|
+
try await browserManager.navigate(path)
|
|
480
557
|
} catch {
|
|
481
|
-
handleNavigationError(error, path:
|
|
558
|
+
handleNavigationError(error, path: path)
|
|
482
559
|
}
|
|
483
560
|
}
|
|
484
561
|
}
|
|
@@ -487,7 +564,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
487
564
|
Promise.async { [weak self] in
|
|
488
565
|
guard let self else { return [] }
|
|
489
566
|
let resolved = try await browserManager.search(query)
|
|
490
|
-
return resolved.
|
|
567
|
+
return resolved.flattenedChildren ?? []
|
|
491
568
|
}
|
|
492
569
|
}
|
|
493
570
|
|
|
@@ -510,7 +587,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
510
587
|
static let invalidateAllSentinel = "__rnab_invalidate_all__"
|
|
511
588
|
|
|
512
589
|
public func notifyContentChanged(path: String) throws {
|
|
513
|
-
|
|
590
|
+
// Throws on a contextual path (matching Android's `require`) — the caller
|
|
591
|
+
// must pass the container path, or the invalidation silently hits nothing.
|
|
592
|
+
try onMainActor { try browserManager.invalidateContentCache(path) }
|
|
514
593
|
|
|
515
594
|
// Notify external controllers (CarPlay) that content changed
|
|
516
595
|
externalContentChangedEmitter.emit(path)
|
|
@@ -690,6 +769,16 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
690
769
|
guard let self else { return }
|
|
691
770
|
self.applyAudioSessionCategory = applyCategory
|
|
692
771
|
|
|
772
|
+
// A re-setup REPLACES the player, so the outgoing one has to be torn down
|
|
773
|
+
// and un-published first. `TrackPlayer` has no deinit: dropping the
|
|
774
|
+
// reference alone would leave its AVPlayer and MPNowPlayingSession alive.
|
|
775
|
+
// And `playerAndConfiguredBrowser` holds the old player strongly — its
|
|
776
|
+
// `resolve` is a one-shot, so without the reset the gate would keep
|
|
777
|
+
// handing the dead player to the CarPlay controller and Siri intents,
|
|
778
|
+
// which both await it. No-op on first setup.
|
|
779
|
+
player?.destroy()
|
|
780
|
+
playerAndConfiguredBrowser.reset()
|
|
781
|
+
|
|
693
782
|
// Create player with self as callbacks delegate
|
|
694
783
|
player = TrackPlayer(callbacks: self)
|
|
695
784
|
|
|
@@ -739,11 +828,16 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
739
828
|
return await browserManager.resolveMediaUrl(src, track: track)
|
|
740
829
|
}
|
|
741
830
|
|
|
742
|
-
// Configure artwork URL resolver for Now Playing (with size context)
|
|
831
|
+
// Configure artwork URL resolver for Now Playing (with size context). The
|
|
832
|
+
// `nowPlayingArtwork` config runs for EVERY playing track, id or not — an id-less track
|
|
833
|
+
// leaves any `{id}` token unfilled (resolveArtworkUrl warns and the request visibly
|
|
834
|
+
// 404s) rather than skipping the config.
|
|
743
835
|
player?.nowPlayingUpdater.artworkUrlResolver = { [weak self] track, imageContext in
|
|
744
836
|
guard let self else { return nil }
|
|
745
|
-
|
|
746
|
-
|
|
837
|
+
return await browserManager.resolveArtworkUrl(
|
|
838
|
+
track: track, perRouteConfig: browserManager.config.nowPlayingArtwork,
|
|
839
|
+
imageContext: imageContext,
|
|
840
|
+
)
|
|
747
841
|
}
|
|
748
842
|
|
|
749
843
|
// Emit the JS `onNowPlayingChanged` event when the rendered text lines change. The updater
|
|
@@ -784,6 +878,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
784
878
|
if let intent = options.playWhenReady { pendingPlayWhenReady = intent }
|
|
785
879
|
applyPendingPlayerState()
|
|
786
880
|
|
|
881
|
+
// Push the resolved event cadence unconditionally. `updateOptions` applies
|
|
882
|
+
// the interval only when it *differs* from the stored one, but the store
|
|
883
|
+
// outlives the player — so on a re-setup with unchanged options the new
|
|
884
|
+
// player would come up with no progress timer at all.
|
|
885
|
+
player?.setProgressUpdateInterval(playerOptions.progressUpdateEventInterval)
|
|
886
|
+
player?.setPlaybackIntervalEnabled(playbackIntervalEnabled)
|
|
887
|
+
|
|
787
888
|
// Notify listeners that player is ready (e.g., CarPlay)
|
|
788
889
|
playerAndConfiguredBrowser.check()
|
|
789
890
|
}
|
|
@@ -813,9 +914,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
813
914
|
// Update stored options
|
|
814
915
|
playerOptions.update(from: options)
|
|
815
916
|
|
|
816
|
-
// Propagate the favorite
|
|
917
|
+
// Propagate the favorite flag to the browser so it can hydrate
|
|
817
918
|
// row hearts (the `favorite` capability is the single favoriting switch).
|
|
818
|
-
browserManager.
|
|
919
|
+
browserManager.setFavoriteEnabled(playerOptions.capabilities.favoriteEnabled)
|
|
819
920
|
|
|
820
921
|
// Apply remote commands to player
|
|
821
922
|
applyRemoteCommands()
|
|
@@ -848,8 +949,16 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
848
949
|
onMainActor { playerOptions.toOptions() }
|
|
849
950
|
}
|
|
850
951
|
|
|
952
|
+
/// Stored so a re-setup can re-apply it: unlike the other runtime options this
|
|
953
|
+
/// one has no home in `playerOptions`, and the player it was pushed to does not
|
|
954
|
+
/// survive `setupPlayer`.
|
|
955
|
+
@MainActor private var playbackIntervalEnabled = false
|
|
956
|
+
|
|
851
957
|
public func setPlaybackIntervalEnabled(enabled: Bool) {
|
|
852
|
-
onMainActor {
|
|
958
|
+
onMainActor {
|
|
959
|
+
playbackIntervalEnabled = enabled
|
|
960
|
+
player?.setPlaybackIntervalEnabled(enabled)
|
|
961
|
+
}
|
|
853
962
|
}
|
|
854
963
|
|
|
855
964
|
// MARK: - Playback Control
|
|
@@ -1063,24 +1172,25 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1063
1172
|
}
|
|
1064
1173
|
|
|
1065
1174
|
public func setActiveTrackFavorited(favorited: Bool) throws {
|
|
1066
|
-
onMainActor {
|
|
1067
|
-
guard let track = player?.currentTrack, let
|
|
1175
|
+
try onMainActor {
|
|
1176
|
+
guard let track = player?.currentTrack, let identity = track.identity else { return }
|
|
1068
1177
|
guard let index = player?.currentIndex, index >= 0 else { return }
|
|
1069
|
-
// Optimistically reflect in the authoritative
|
|
1070
|
-
// heart (hydrated from it in getActiveTrack) flips
|
|
1071
|
-
// reconciles to its canonical
|
|
1072
|
-
|
|
1178
|
+
// Optimistically reflect in the authoritative favorite set so the
|
|
1179
|
+
// now-playing heart (hydrated from it in getActiveTrack) flips
|
|
1180
|
+
// immediately; the consumer reconciles to its canonical identities on
|
|
1181
|
+
// the next setFavorites.
|
|
1182
|
+
browserManager.setFavorited(identity: identity, favorited: favorited)
|
|
1073
1183
|
let updatedTrack = track.copying(favorited: favorited)
|
|
1184
|
+
// Hydration never overrides a caller-set `favorited`, so the queued
|
|
1185
|
+
// track must itself carry the latest explicit value (as Android's tag
|
|
1186
|
+
// swap and web's replaceTrack do).
|
|
1187
|
+
try player?.replace(index, updatedTrack)
|
|
1188
|
+
// The ONLY emit: a favorite toggle is an in-place mutation of the active
|
|
1189
|
+
// track, not a transition — onPlaybackActiveTrackChanged stays
|
|
1190
|
+
// transition-only, and the useActiveTrack / useQueue hooks subscribe to
|
|
1191
|
+
// onFavoriteChanged themselves. Native surfaces (CarPlay heart) observe
|
|
1192
|
+
// favoriteChangedEmitter / onFavoritesChanged directly.
|
|
1074
1193
|
favoriteChangedEmitter.emit(FavoriteChangedEvent(track: updatedTrack, favorited: favorited))
|
|
1075
|
-
// Fire active track changed so the now-playing heart + useActiveTrack() refresh.
|
|
1076
|
-
let position = player?.currentTime ?? 0
|
|
1077
|
-
activeTrackChangedEmitter.emit(PlaybackActiveTrackChangedEvent(
|
|
1078
|
-
lastIndex: Double(index),
|
|
1079
|
-
lastTrack: track,
|
|
1080
|
-
lastPosition: position,
|
|
1081
|
-
index: Double(index),
|
|
1082
|
-
track: updatedTrack,
|
|
1083
|
-
))
|
|
1084
1194
|
}
|
|
1085
1195
|
}
|
|
1086
1196
|
|
|
@@ -1220,7 +1330,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1220
1330
|
duration: track.duration,
|
|
1221
1331
|
artwork: nowPlayingArtwork(for: track),
|
|
1222
1332
|
description: track.description,
|
|
1223
|
-
mediaId: track.src ?? track.
|
|
1333
|
+
mediaId: track.src ?? track.path,
|
|
1224
1334
|
genre: track.genre,
|
|
1225
1335
|
)
|
|
1226
1336
|
}
|
|
@@ -1233,7 +1343,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1233
1343
|
// Classify a stall by connectivity so the formatter can show "No internet connection" vs
|
|
1234
1344
|
// "Reconnecting…" without a separate read. nil while not stalled.
|
|
1235
1345
|
let stalled: StallReason? =
|
|
1236
|
-
player.isStalled ? (networkMonitor.
|
|
1346
|
+
player.isStalled ? (networkMonitor.isOnline ? .buffering : .offline) : nil
|
|
1237
1347
|
player.nowPlayingUpdater.render(
|
|
1238
1348
|
track: track,
|
|
1239
1349
|
timedMetadata: latestTimedMetadata,
|
|
@@ -1249,7 +1359,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1249
1359
|
// MARK: - Network
|
|
1250
1360
|
|
|
1251
1361
|
public func getOnline() throws -> Bool {
|
|
1252
|
-
|
|
1362
|
+
// No `onMainActor` hop: `isOnline` is lock-protected, so this stays a
|
|
1363
|
+
// cheap read on the JS thread instead of blocking on the main queue.
|
|
1364
|
+
networkMonitor.isOnline
|
|
1253
1365
|
}
|
|
1254
1366
|
|
|
1255
1367
|
// MARK: - System Volume
|
|
@@ -1429,11 +1541,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1429
1541
|
public func openOutputPicker() throws {
|
|
1430
1542
|
DispatchQueue.main.async { [weak self] in
|
|
1431
1543
|
guard let self else { return }
|
|
1432
|
-
guard let windowScene = UIApplication.shared.
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
let window = windowScene.windows.first(where: { $0.isKeyWindow }),
|
|
1436
|
-
var topController = window.rootViewController
|
|
1544
|
+
guard let windowScene = UIApplication.shared.activeWindowScene,
|
|
1545
|
+
let window = windowScene.windows.first(where: { $0.isKeyWindow }),
|
|
1546
|
+
var topController = window.rootViewController
|
|
1437
1547
|
else {
|
|
1438
1548
|
self.logger.error("openOutputPicker: no active window scene / root view controller")
|
|
1439
1549
|
return
|
|
@@ -1809,14 +1919,25 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1809
1919
|
browser.showNowPlayingRequestedEmitter.emit(())
|
|
1810
1920
|
completion(true)
|
|
1811
1921
|
} else if let state = browser.playbackStateStore.load() {
|
|
1812
|
-
browser.logger.info("resume: cold — restoring persisted track")
|
|
1813
1922
|
let track = state.track.toNitro()
|
|
1923
|
+
// The persisted track can carry `disabled` — it became unavailable
|
|
1924
|
+
// after it was saved. A disabled track never plays, resumption
|
|
1925
|
+
// included (Track.disabled).
|
|
1926
|
+
if track.disabled == true {
|
|
1927
|
+
browser.logger.info("resume: persisted track is disabled — refusing")
|
|
1928
|
+
completion(false)
|
|
1929
|
+
return
|
|
1930
|
+
}
|
|
1931
|
+
browser.logger.info("resume: cold — restoring persisted track")
|
|
1932
|
+
// Repeat / shuffle / speed come back before the queue is built — see
|
|
1933
|
+
// `restorePlaybackSettings(from:)` for why that ordering matters.
|
|
1934
|
+
player.restorePlaybackSettings(from: state)
|
|
1814
1935
|
let startMs = (track.live == true) ? nil : state.positionMs
|
|
1815
1936
|
// Match Android resume: re-expand the full queue from the track's contextual
|
|
1816
|
-
//
|
|
1817
|
-
// track when the
|
|
1818
|
-
if let
|
|
1819
|
-
let expanded = try? await browser.browserManager.
|
|
1937
|
+
// path (parent container → siblings + selected index). Fall back to the single
|
|
1938
|
+
// track when the path isn't contextual or expansion fails.
|
|
1939
|
+
if let path = state.track.path,
|
|
1940
|
+
let expanded = try? await browser.browserManager.expandQueueFromContextualPath(path)
|
|
1820
1941
|
{
|
|
1821
1942
|
player.setQueue(expanded.tracks, initialIndex: expanded.selectedIndex, startPositionMs: startMs, playWhenReady: true)
|
|
1822
1943
|
} else {
|
|
@@ -30,18 +30,18 @@
|
|
|
30
30
|
|
|
31
31
|
struct Track: Equatable {
|
|
32
32
|
var id: String?
|
|
33
|
-
var
|
|
33
|
+
var path: String?
|
|
34
34
|
var src: String?
|
|
35
|
+
var artwork: Variant_String_ArtworkVariants?
|
|
36
|
+
var artworkSource: ImageSource?
|
|
35
37
|
var request: TrackRequest?
|
|
36
38
|
var title: String = ""
|
|
37
39
|
var artist: String?
|
|
38
|
-
var
|
|
40
|
+
var albumPath: String?
|
|
39
41
|
var album: String?
|
|
42
|
+
var style: TrackStyle?
|
|
43
|
+
var disabled: Bool?
|
|
40
44
|
var live: Bool?
|
|
41
|
-
var artwork: Variant_String_ArtworkVariants?
|
|
42
|
-
var artworkSource: ImageSource?
|
|
43
|
-
var groupTitle: String?
|
|
44
|
-
var imageRow: [ImageRowItem]?
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
struct TrackLoadEvent {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
var body: String?
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
enum
|
|
89
|
+
enum StyleDisplay: Equatable {
|
|
90
90
|
case list
|
|
91
91
|
case grid
|
|
92
92
|
|
|
@@ -99,17 +99,106 @@
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
enum ArtworkRendering: Equatable {
|
|
103
|
+
case original
|
|
104
|
+
case stencil
|
|
105
|
+
|
|
106
|
+
init?(fromString string: String) {
|
|
107
|
+
switch string {
|
|
108
|
+
case "original": self = .original
|
|
109
|
+
case "stencil": self = .stencil
|
|
110
|
+
default: return nil
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
enum ImageShape: Equatable {
|
|
116
|
+
case circular
|
|
117
|
+
case roundedRectangle
|
|
118
|
+
|
|
119
|
+
init?(fromString string: String) {
|
|
120
|
+
switch string {
|
|
121
|
+
case "circular": self = .circular
|
|
122
|
+
case "rounded-rectangle": self = .roundedRectangle
|
|
123
|
+
default: return nil
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
enum GridTile: Equatable {
|
|
129
|
+
case plain
|
|
130
|
+
case card
|
|
131
|
+
case condensed
|
|
132
|
+
|
|
133
|
+
init?(fromString string: String) {
|
|
134
|
+
switch string {
|
|
135
|
+
case "plain": self = .plain
|
|
136
|
+
case "card": self = .card
|
|
137
|
+
case "condensed": self = .condensed
|
|
138
|
+
default: return nil
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Mirrors the generated extension (nitrogen emits `stringValue` beside
|
|
143
|
+
// `init?(fromString:)` for every string-backed enum).
|
|
144
|
+
var stringValue: String {
|
|
145
|
+
switch self {
|
|
146
|
+
case .plain: "plain"
|
|
147
|
+
case .card: "card"
|
|
148
|
+
case .condensed: "condensed"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
enum CardImage: Equatable {
|
|
154
|
+
case normal
|
|
155
|
+
case background
|
|
156
|
+
|
|
157
|
+
init?(fromString string: String) {
|
|
158
|
+
switch string {
|
|
159
|
+
case "normal": self = .normal
|
|
160
|
+
case "background": self = .background
|
|
161
|
+
default: return nil
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
struct TrackStyle: Equatable {
|
|
167
|
+
var display: StyleDisplay?
|
|
168
|
+
var artworkRendering: ArtworkRendering?
|
|
169
|
+
var imageShape: ImageShape?
|
|
170
|
+
var accessorySymbol: String?
|
|
171
|
+
var cardTint: String?
|
|
172
|
+
var cardImage: CardImage?
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Member order matches the generated init (own properties before inherited
|
|
176
|
+
// ones — Nitro flattens `extends` own-props-first): the memberwise init this
|
|
177
|
+
// produces must accept the same argument order as the real type, or
|
|
178
|
+
// `swift test` passes while the app build breaks on argument order.
|
|
179
|
+
struct SectionStyle: Equatable {
|
|
180
|
+
var gridWrap: Bool?
|
|
181
|
+
var gridTile: GridTile?
|
|
182
|
+
var display: StyleDisplay?
|
|
183
|
+
var artworkRendering: ArtworkRendering?
|
|
184
|
+
var imageShape: ImageShape?
|
|
185
|
+
var accessorySymbol: String?
|
|
186
|
+
var cardTint: String?
|
|
187
|
+
var cardImage: CardImage?
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
struct Section: Equatable {
|
|
191
|
+
var title: String?
|
|
192
|
+
var subtitle: String?
|
|
193
|
+
var style: SectionStyle?
|
|
194
|
+
var path: String?
|
|
195
|
+
var children: [Track]
|
|
109
196
|
}
|
|
110
197
|
|
|
111
198
|
struct ResolvedTrack: Equatable {
|
|
112
|
-
var
|
|
199
|
+
var path: String
|
|
200
|
+
var style: SectionStyle?
|
|
201
|
+
var sections: [Section]?
|
|
113
202
|
var children: [Track]?
|
|
114
203
|
var carPlaySiriListButton: CarPlaySiriListButtonPosition?
|
|
115
204
|
var id: String?
|
|
@@ -117,21 +206,17 @@
|
|
|
117
206
|
var artwork: Variant_String_ArtworkVariants?
|
|
118
207
|
var artworkSource: ImageSource?
|
|
119
208
|
var request: TrackRequest?
|
|
120
|
-
var artworkCarPlayTinted: Bool?
|
|
121
209
|
var title: String
|
|
122
210
|
var subtitle: String?
|
|
123
211
|
var artist: String?
|
|
124
|
-
var
|
|
212
|
+
var albumPath: String?
|
|
125
213
|
var album: String?
|
|
126
214
|
var description: String?
|
|
127
215
|
var genre: String?
|
|
128
216
|
var duration: Double?
|
|
129
|
-
var
|
|
130
|
-
var childrenStyle: TrackStyle?
|
|
217
|
+
var disabled: Bool?
|
|
131
218
|
var favorited: Bool?
|
|
132
|
-
var groupTitle: String?
|
|
133
219
|
var live: Bool?
|
|
134
|
-
var imageRow: [ImageRowItem]?
|
|
135
220
|
}
|
|
136
221
|
|
|
137
222
|
enum PlaybackState: Equatable {
|
|
@@ -32,6 +32,9 @@ class PlayerUpdateOptions {
|
|
|
32
32
|
/// Custom buttons for the CarPlay Now Playing screen
|
|
33
33
|
var carPlayNowPlayingButtons: [CarPlayNowPlayingButton] = []
|
|
34
34
|
|
|
35
|
+
/// Where the now-playing indicator draws on CarPlay list rows
|
|
36
|
+
var carPlayPlayingIndicatorLocation: CarPlayPlayingIndicatorLocation = .leading
|
|
37
|
+
|
|
35
38
|
// MARK: - Initialization
|
|
36
39
|
|
|
37
40
|
init() {}
|
|
@@ -74,6 +77,9 @@ class PlayerUpdateOptions {
|
|
|
74
77
|
if let buttons = ios.carPlayNowPlayingButtons {
|
|
75
78
|
carPlayNowPlayingButtons = buttons
|
|
76
79
|
}
|
|
80
|
+
if let location = ios.carPlayPlayingIndicatorLocation {
|
|
81
|
+
carPlayPlayingIndicatorLocation = location
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -92,6 +98,7 @@ class PlayerUpdateOptions {
|
|
|
92
98
|
playbackRates: playbackRates,
|
|
93
99
|
carPlayUpNextButton: carPlayUpNextButton,
|
|
94
100
|
carPlayNowPlayingButtons: carPlayNowPlayingButtons,
|
|
101
|
+
carPlayPlayingIndicatorLocation: carPlayPlayingIndicatorLocation,
|
|
95
102
|
),
|
|
96
103
|
)
|
|
97
104
|
}
|
|
@@ -126,14 +126,22 @@ public struct MediaIntentCriteria: Sendable {
|
|
|
126
126
|
|
|
127
127
|
/// Whether `query` is effectively the host app's own name. Case- and
|
|
128
128
|
/// diacritic-insensitive.
|
|
129
|
+
///
|
|
130
|
+
/// Whole-word subset match in either direction. Siri may deliver only part of
|
|
131
|
+
/// the name ("Name" for «App Name»), so a partial match must count — but a
|
|
132
|
+
/// substring match swallowed any query appearing inside the app name, making
|
|
133
|
+
/// one-word stations unreachable by voice.
|
|
129
134
|
private static func queryMatchesAppName(_ query: String, appName: String?) -> Bool {
|
|
130
135
|
guard let appName else { return false }
|
|
131
|
-
let
|
|
132
|
-
|
|
133
|
-
.
|
|
136
|
+
let tokens: (String) -> Set<String> = {
|
|
137
|
+
Set(
|
|
138
|
+
$0.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: nil)
|
|
139
|
+
.split(whereSeparator: { !$0.isLetter && !$0.isNumber })
|
|
140
|
+
.map(String.init),
|
|
141
|
+
)
|
|
134
142
|
}
|
|
135
|
-
let q =
|
|
143
|
+
let q = tokens(query), a = tokens(appName)
|
|
136
144
|
guard !q.isEmpty, !a.isEmpty else { return false }
|
|
137
|
-
return
|
|
145
|
+
return q.isSubset(of: a) || a.isSubset(of: q)
|
|
138
146
|
}
|
|
139
147
|
}
|