react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.20
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 +35 -21
- package/android/build.gradle +35 -12
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -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/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
- package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -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 +56 -52
- package/android/src/main/res/values/strings.xml +25 -5
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
- 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 +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- 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/model/IcyTitleTest.kt +49 -0
- package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -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 +230 -205
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/SearchDrillIn.swift +7 -7
- 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 +42 -20
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
- package/ios/Extension/Section+Copying.swift +29 -0
- package/ios/Extension/String+IcyCharset.swift +21 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
- package/ios/Extension/Track+Copying.swift +8 -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 +253 -100
- package/ios/Model/NitroTypeStubs.swift +125 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +17 -9
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +46 -13
- package/ios/Player/RetryManager.swift +80 -18
- package/ios/Player/SeekCompletionHandler.swift +3 -1
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
- 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/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +122 -10
- package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryManagerTests.swift +89 -0
- 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 +8 -1
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- 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 +63 -35
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/LRUCache.swift +4 -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 +8 -8
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +12 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +11 -3
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +14 -11
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +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/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +3 -2
- 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 +3 -4
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +18 -9
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +23 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +15 -4
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +13 -6
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
- 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 +200 -75
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
- package/lib/commonjs/web/TrackPlayer/Player.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/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +175 -100
- 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/SearchManager.js +18 -23
- package/lib/commonjs/web/browser/SearchManager.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/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.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 +2 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +12 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +11 -3
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +14 -11
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +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/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +3 -2
- 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 +3 -4
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +17 -8
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +19 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +15 -4
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +13 -6
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +28 -14
- 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 +200 -75
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +254 -39
- package/lib/module/web/TrackPlayer/Player.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/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +175 -100
- 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/SearchManager.js +18 -23
- package/lib/module/web/browser/SearchManager.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/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.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/package.json +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +1 -1
- 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 +10 -2
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +14 -11
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +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/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +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 +3 -4
- 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/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +108 -56
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +7 -4
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +3 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +82 -57
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
- 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 +1 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- 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__string__.hpp +102 -0
- 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 +7 -10
- 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 +45 -25
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- 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 +36 -11
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
- package/nitrogen/generated/android/c++/JSection.hpp +116 -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 +25 -46
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- 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/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- 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 +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- 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/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -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 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- 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/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- 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 +52 -5
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
- package/nitrogen/generated/ios/swift/Section.swift +82 -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 +28 -63
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- 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++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- 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 +10 -6
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
- package/nitrogen/generated/shared/c++/Section.hpp +102 -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 +18 -30
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +24 -11
- package/src/AudioBrowser.ts +2 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +12 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +11 -3
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +14 -11
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +302 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +3 -2
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +3 -4
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +1 -6
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +126 -76
- package/src/features/player/setup.test.ts +24 -7
- package/src/features/player/setup.ts +22 -10
- package/src/features/player/validateOptions.ts +21 -0
- package/src/features/queue/activeTrack.ts +15 -4
- package/src/features/queue/queue.ts +13 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +147 -0
- package/src/specs/audio-browser.nitro.ts +5 -4
- package/src/style-resolution.test.ts +210 -0
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +7 -3
- package/src/types/browser-nodes.ts +396 -134
- package/src/types/browser.ts +85 -59
- package/src/utils/LazyNativeEmitter.ts +28 -15
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +239 -82
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +284 -37
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +209 -111
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/SearchManager.test.ts +75 -0
- package/src/web/browser/SearchManager.ts +25 -28
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
- package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
- 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/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -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 +247 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- 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/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- 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/NotificationButtonLayout.swift +0 -92
- 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/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import type { ResolvedTrack, Track } from '../../types'
|
|
3
|
+
import { flattenSections, normalizePage, sectionContaining } from './sections'
|
|
4
|
+
|
|
5
|
+
const track = (src: string): Track => ({ title: src, src })
|
|
6
|
+
|
|
7
|
+
describe('normalizePage', () => {
|
|
8
|
+
it('wraps plain children into one untitled section', () => {
|
|
9
|
+
const page: ResolvedTrack = {
|
|
10
|
+
path: '/p',
|
|
11
|
+
title: 'P',
|
|
12
|
+
children: [track('a'), track('b')]
|
|
13
|
+
}
|
|
14
|
+
const normalized = normalizePage(page)
|
|
15
|
+
expect(normalized.sections).toEqual([
|
|
16
|
+
{ children: [track('a'), track('b')] }
|
|
17
|
+
])
|
|
18
|
+
expect(normalized.children).toBeUndefined()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('prefers sections when present and strips children', () => {
|
|
22
|
+
const page: ResolvedTrack = {
|
|
23
|
+
path: '/p',
|
|
24
|
+
title: 'P',
|
|
25
|
+
sections: [{ title: 'S', children: [track('a')] }],
|
|
26
|
+
children: [track('zzz')]
|
|
27
|
+
}
|
|
28
|
+
const normalized = normalizePage(page)
|
|
29
|
+
expect(normalized.sections).toEqual([
|
|
30
|
+
{ title: 'S', children: [track('a')] }
|
|
31
|
+
])
|
|
32
|
+
expect(normalized.children).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('leaves a childless page without sections', () => {
|
|
36
|
+
expect(normalizePage({ path: '/p', title: 'P' }).sections).toBeUndefined()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('flattenSections', () => {
|
|
41
|
+
it('concatenates children in section order', () => {
|
|
42
|
+
const flat = flattenSections([
|
|
43
|
+
{ title: 'A', children: [track('a1'), track('a2')] },
|
|
44
|
+
{ children: [track('b1')] }
|
|
45
|
+
])
|
|
46
|
+
expect(flat.map((t) => t.src)).toEqual(['a1', 'a2', 'b1'])
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('sectionContaining', () => {
|
|
51
|
+
const sections = [
|
|
52
|
+
{ title: 'First', children: [track('dup'), track('x')] },
|
|
53
|
+
{ title: 'Second', children: [track('y'), track('dup'), track('z')] }
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
it('falls back to the first section containing the identity', () => {
|
|
57
|
+
const scoped = sectionContaining(sections, 'dup', undefined)
|
|
58
|
+
expect(scoped?.tracks.map((t) => t.src)).toEqual(['dup', 'x'])
|
|
59
|
+
expect(scoped?.tappedOffset).toBeUndefined()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('pins the tapped section via the flat index', () => {
|
|
63
|
+
// Flat index 3 = second section, offset 1.
|
|
64
|
+
const scoped = sectionContaining(sections, 'dup', 3)
|
|
65
|
+
expect(scoped?.tracks.map((t) => t.src)).toEqual(['y', 'dup', 'z'])
|
|
66
|
+
expect(scoped?.tappedOffset).toBe(1)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('ignores a stale index whose child no longer matches', () => {
|
|
70
|
+
const scoped = sectionContaining(sections, 'dup', 1)
|
|
71
|
+
expect(scoped?.tracks.map((t) => t.src)).toEqual(['dup', 'x'])
|
|
72
|
+
expect(scoped?.tappedOffset).toBeUndefined()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('ignores an out-of-range index', () => {
|
|
76
|
+
const scoped = sectionContaining(sections, 'dup', 99)
|
|
77
|
+
expect(scoped?.tracks.map((t) => t.src)).toEqual(['dup', 'x'])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('returns undefined for a vanished identity', () => {
|
|
81
|
+
expect(sectionContaining(sections, 'gone', 0)).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('pins the exact copy of a within-section duplicate', () => {
|
|
85
|
+
const playlist = [
|
|
86
|
+
{ children: [track('a'), track('b'), track('a'), track('c')] }
|
|
87
|
+
]
|
|
88
|
+
const scoped = sectionContaining(playlist, 'a', 2)
|
|
89
|
+
expect(scoped?.tappedOffset).toBe(2)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ResolvedTrack, Section, Track } from '../../types'
|
|
2
|
+
import { getTrackIdentity } from '../../utils/getTrackIdentity'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes a resolved page to the canonical sectioned shape (ADR 0010):
|
|
6
|
+
* `sections` wins when present; plain `children` is authoring sugar for one
|
|
7
|
+
* untitled section. The output never carries `children` — there is exactly
|
|
8
|
+
* one structure downstream code hydrates, transforms, and scopes against.
|
|
9
|
+
*/
|
|
10
|
+
export function normalizePage(content: ResolvedTrack): ResolvedTrack {
|
|
11
|
+
const { children, sections, ...rest } = content
|
|
12
|
+
const normalized =
|
|
13
|
+
sections ?? (children !== undefined ? [{ children }] : undefined)
|
|
14
|
+
return { ...rest, sections: normalized }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The page's children concatenated in section order — the flattening that
|
|
19
|
+
* defines the contextual `__index` positions (ADR 0009/0010) and the flat
|
|
20
|
+
* views (tabs, search) of a sectioned page.
|
|
21
|
+
*/
|
|
22
|
+
export function flattenSections(sections: Section[] | undefined): Track[] {
|
|
23
|
+
return sections?.flatMap((section) => section.children) ?? []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The page's tracks as one flat list, normalizing first — the flat view
|
|
28
|
+
* (tabs, search) of a page in either authored shape. Mirrors the native
|
|
29
|
+
* `flattenedChildren` accessors.
|
|
30
|
+
*/
|
|
31
|
+
export function flattenedChildren(page: ResolvedTrack): Track[] {
|
|
32
|
+
return flattenSections(normalizePage(page).sections)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The section of the page containing the playable `trackId` (a track
|
|
37
|
+
* identity: id when non-blank, else src), or undefined when not found.
|
|
38
|
+
*
|
|
39
|
+
* `tappedIndex` — the flat page position stamped into the contextual URL —
|
|
40
|
+
* is a tie-breaker, never an identifier: when the child at that position
|
|
41
|
+
* still carries the tapped identity, it pins which section (and which copy,
|
|
42
|
+
* via `tappedOffset`) was tapped; when it doesn't (the list shifted),
|
|
43
|
+
* resolution falls back to the first section containing the identity. A
|
|
44
|
+
* stale index can therefore never select a different track — at worst a
|
|
45
|
+
* different copy of the same one.
|
|
46
|
+
*/
|
|
47
|
+
export function sectionContaining(
|
|
48
|
+
sections: Section[],
|
|
49
|
+
trackId: string,
|
|
50
|
+
tappedIndex: number | undefined
|
|
51
|
+
): { tracks: Track[]; tappedOffset?: number } | undefined {
|
|
52
|
+
if (tappedIndex !== undefined && tappedIndex >= 0) {
|
|
53
|
+
let start = 0
|
|
54
|
+
for (const section of sections) {
|
|
55
|
+
const offset = tappedIndex - start
|
|
56
|
+
if (offset < section.children.length) {
|
|
57
|
+
const tapped = section.children[offset]
|
|
58
|
+
if (tapped && getTrackIdentity(tapped) === trackId) {
|
|
59
|
+
return { tracks: section.children, tappedOffset: offset }
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
start += section.children.length
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const section of sections) {
|
|
67
|
+
if (section.children.some((track) => getTrackIdentity(track) === trackId)) {
|
|
68
|
+
return { tracks: section.children }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
RequestConfig,
|
|
4
|
+
Track,
|
|
5
|
+
TransformableRequestConfig
|
|
6
|
+
} from '../../types'
|
|
3
7
|
import { RequestConfigBuilder } from './RequestConfigBuilder'
|
|
4
8
|
|
|
5
9
|
/**
|
|
@@ -48,6 +52,29 @@ describe('RequestConfigBuilder.applyLayer — sync/async transform composition',
|
|
|
48
52
|
expect(out.query).toEqual({ stage: 'sync' })
|
|
49
53
|
})
|
|
50
54
|
|
|
55
|
+
it('falls back to the base when a transform throws', async () => {
|
|
56
|
+
const layer: TransformableRequestConfig = {
|
|
57
|
+
transform: async () => {
|
|
58
|
+
throw new Error('boom')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const out = await RequestConfigBuilder.applyLayer(base, layer)
|
|
62
|
+
expect(out).toEqual(base)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('falls back to the base when the sync stage throws after the async one', async () => {
|
|
66
|
+
const layer: TransformableRequestConfig = {
|
|
67
|
+
transform: async (req) => ({ ...req, headers: { a: '1' } }),
|
|
68
|
+
transformSync: () => {
|
|
69
|
+
throw new Error('boom')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const out = await RequestConfigBuilder.applyLayer(base, layer)
|
|
73
|
+
// the layer's input, not the half-applied config — matches native's single
|
|
74
|
+
// catch around the pair
|
|
75
|
+
expect(out).toEqual(base)
|
|
76
|
+
})
|
|
77
|
+
|
|
51
78
|
it('falls back to a static field merge when no transform is set', async () => {
|
|
52
79
|
const layer: TransformableRequestConfig = {
|
|
53
80
|
baseUrl: 'https://override.example.com'
|
|
@@ -57,3 +84,216 @@ describe('RequestConfigBuilder.applyLayer — sync/async transform composition',
|
|
|
57
84
|
expect(out.path).toBe('/p')
|
|
58
85
|
})
|
|
59
86
|
})
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A media request needs more than its URL. The layers resolve headers (and a
|
|
90
|
+
* user agent) alongside it, and native applies them to the AVURLAsset /
|
|
91
|
+
* ExoPlayer DataSpec — so a resolver that returned only the URL left web unable
|
|
92
|
+
* to play media that authenticated fine on iOS and Android.
|
|
93
|
+
*/
|
|
94
|
+
describe('RequestConfigBuilder.resolveMediaRequest', () => {
|
|
95
|
+
const src = 'https://cdn.example.com/track.m3u8'
|
|
96
|
+
|
|
97
|
+
it('keeps the headers a media transform resolves', async () => {
|
|
98
|
+
const media = {
|
|
99
|
+
transform: async (req: RequestConfig) => ({
|
|
100
|
+
...req,
|
|
101
|
+
headers: { ...req.headers, Authorization: 'Bearer token' }
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
106
|
+
src,
|
|
107
|
+
undefined,
|
|
108
|
+
media
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expect(out.path).toBe(src)
|
|
112
|
+
expect(out.headers).toEqual({ Authorization: 'Bearer token' })
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('keeps the headers of a static media config', async () => {
|
|
116
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(src, undefined, {
|
|
117
|
+
headers: { 'X-Api-Key': 'k' }
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
expect(out.headers).toEqual({ 'X-Api-Key': 'k' })
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('merges the shared request layer under the media layer', async () => {
|
|
124
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
125
|
+
src,
|
|
126
|
+
{ headers: { 'User-Agent': 'shared', 'X-Shared': 'yes' } },
|
|
127
|
+
{ headers: { 'User-Agent': 'media' } }
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
expect(out.headers).toEqual({
|
|
131
|
+
'User-Agent': 'media',
|
|
132
|
+
'X-Shared': 'yes'
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('folds baseUrl into the resolved path', async () => {
|
|
137
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
138
|
+
'/track.m3u8',
|
|
139
|
+
undefined,
|
|
140
|
+
{ baseUrl: 'https://cdn.example.com' }
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
expect(out.path).toBe('https://cdn.example.com/track.m3u8')
|
|
144
|
+
expect(out.baseUrl).toBeUndefined()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('carries the media config query onto the resolved url', async () => {
|
|
148
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(src, undefined, {
|
|
149
|
+
query: { token: 'abc' }
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
expect(out.path).toBe(`${src}?token=abc`)
|
|
153
|
+
// cleared so the resolved url can't be rebuilt and double-append it
|
|
154
|
+
expect(out.query).toBeUndefined()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('appends query to a url that already has one', async () => {
|
|
158
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
159
|
+
'https://cdn.example.com/track.m3u8?v=2',
|
|
160
|
+
undefined,
|
|
161
|
+
{ query: { token: 'abc' } }
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
expect(out.path).toBe('https://cdn.example.com/track.m3u8?v=2&token=abc')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('falls back to the original src when a transform throws', async () => {
|
|
168
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(src, undefined, {
|
|
169
|
+
transform: async () => {
|
|
170
|
+
throw new Error('boom')
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
expect(out.path).toBe(src)
|
|
175
|
+
expect(out.headers).toBeUndefined()
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('keeps the shared layer when the media transform throws', async () => {
|
|
179
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
180
|
+
'/track.mp3',
|
|
181
|
+
{
|
|
182
|
+
baseUrl: 'https://api.example.com',
|
|
183
|
+
headers: { Authorization: 'Bearer token' }
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
transform: async () => {
|
|
187
|
+
throw new Error('boom')
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// the media layer is skipped, the ladder under it stands — dropping it too
|
|
193
|
+
// would play a relative url with no credential
|
|
194
|
+
expect(out.path).toBe('https://api.example.com/track.mp3')
|
|
195
|
+
expect(out.headers).toEqual({ Authorization: 'Bearer token' })
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* `media.resolve(track)` is the final, most-specific layer — the shape native
|
|
201
|
+
* runs (Android `applyMediaResolve`, iOS `applyMediaResolveLayer`) and the one
|
|
202
|
+
* the public docs promise. Web ignored it entirely until these landed, so a
|
|
203
|
+
* config minting per-track signed URLs played on both native platforms and
|
|
204
|
+
* silently played the unsigned src on web.
|
|
205
|
+
*/
|
|
206
|
+
describe('RequestConfigBuilder.resolveMediaRequest — media.resolve', () => {
|
|
207
|
+
const src = '/track.mp3'
|
|
208
|
+
const track: Track = { id: '7', title: 'Track 7', src }
|
|
209
|
+
|
|
210
|
+
it('lets the resolver replace the url', async () => {
|
|
211
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
212
|
+
src,
|
|
213
|
+
undefined,
|
|
214
|
+
{ resolve: async (t) => ({ path: `/signed/${t.id}.mp3` }) },
|
|
215
|
+
track
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
expect(out.path).toBe('/signed/7.mp3')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('runs after the media transform, and wins over it', async () => {
|
|
222
|
+
const order: string[] = []
|
|
223
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
224
|
+
src,
|
|
225
|
+
undefined,
|
|
226
|
+
{
|
|
227
|
+
transform: async (req) => {
|
|
228
|
+
order.push('transform')
|
|
229
|
+
return { ...req, path: '/unsigned.mp3', headers: { A: 'transform' } }
|
|
230
|
+
},
|
|
231
|
+
resolve: async () => {
|
|
232
|
+
order.push('resolve')
|
|
233
|
+
return { path: '/signed.mp3', headers: { A: 'resolve' } }
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
track
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
expect(order).toEqual(['transform', 'resolve'])
|
|
240
|
+
expect(out.path).toBe('/signed.mp3')
|
|
241
|
+
expect(out.headers).toEqual({ A: 'resolve' })
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('merges resolveSync over resolve, and both over the layers', async () => {
|
|
245
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
246
|
+
src,
|
|
247
|
+
{ headers: { 'X-Shared': 'yes' } },
|
|
248
|
+
{
|
|
249
|
+
headers: { 'X-Media': 'yes' },
|
|
250
|
+
resolve: async () => ({ headers: { Authorization: 'async' } }),
|
|
251
|
+
resolveSync: () => ({ headers: { Authorization: 'sync' } })
|
|
252
|
+
},
|
|
253
|
+
track
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
expect(out.headers).toEqual({
|
|
257
|
+
'X-Shared': 'yes',
|
|
258
|
+
'X-Media': 'yes',
|
|
259
|
+
'Authorization': 'sync'
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('reports the resolver contribution as a media-layer header', async () => {
|
|
264
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
265
|
+
src,
|
|
266
|
+
{ headers: { 'X-Client-Version': '1.2.3' } },
|
|
267
|
+
{ resolve: async () => ({ headers: { 'X-Api-Key': 'k' } }) },
|
|
268
|
+
track
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
// per-track and media-specific, so the web redirect probe should run
|
|
272
|
+
expect(out.mediaLayerHeaders).toBe(true)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('keeps the layers when the resolver throws', async () => {
|
|
276
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(
|
|
277
|
+
src,
|
|
278
|
+
{ baseUrl: 'https://api.example.com', headers: { 'X-Shared': 'yes' } },
|
|
279
|
+
{
|
|
280
|
+
resolve: async () => {
|
|
281
|
+
throw new Error('boom')
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
track
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// native loses the per-track shaping, not the layers under it
|
|
288
|
+
expect(out.path).toBe('https://api.example.com/track.mp3')
|
|
289
|
+
expect(out.headers).toEqual({ 'X-Shared': 'yes' })
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('skips the resolver when there is no track', async () => {
|
|
293
|
+
const out = await RequestConfigBuilder.resolveMediaRequest(src, undefined, {
|
|
294
|
+
resolve: async () => ({ path: '/signed.mp3' })
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
expect(out.path).toBe(src)
|
|
298
|
+
})
|
|
299
|
+
})
|
|
@@ -56,6 +56,29 @@ function applyImageQueryParams(
|
|
|
56
56
|
return { ...config, query }
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** Whether `after` carries a header `before` had not already set identically. */
|
|
60
|
+
function addsHeaders(
|
|
61
|
+
before: Record<string, string> | undefined,
|
|
62
|
+
after: Record<string, string> | undefined
|
|
63
|
+
): boolean {
|
|
64
|
+
if (!after) return false
|
|
65
|
+
return Object.entries(after).some(([key, value]) => before?.[key] !== value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ResolvedMediaRequest extends RequestConfig {
|
|
69
|
+
/**
|
|
70
|
+
* Whether the `media` layer set a header the shared `request` layer had not
|
|
71
|
+
* already set identically. A media-specific header is the signal that media
|
|
72
|
+
* is separately authenticated, which is what the web redirect probe gates
|
|
73
|
+
* on.
|
|
74
|
+
*
|
|
75
|
+
* By value, not by declaration: a media layer re-declaring a shared header
|
|
76
|
+
* with the same value reads as no contribution, which is what keeps an
|
|
77
|
+
* identity transform from tripping the probe.
|
|
78
|
+
*/
|
|
79
|
+
mediaLayerHeaders: boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
59
82
|
/**
|
|
60
83
|
* Builds and merges request configurations.
|
|
61
84
|
* Mirrors Android's RequestConfigBuilder.kt
|
|
@@ -98,7 +121,9 @@ export const RequestConfigBuilder = {
|
|
|
98
121
|
* A layer with a transform wins completely — it receives the base (plus route
|
|
99
122
|
* params) and its result replaces the base; the layer's own static fields are
|
|
100
123
|
* ignored. A layer without a transform merges its static fields over the base.
|
|
101
|
-
* `path` is carried from the base — only a transform may change it.
|
|
124
|
+
* `path` is carried from the base — only a transform may change it. A
|
|
125
|
+
* throwing transform falls back to the base, so the layer is skipped and the
|
|
126
|
+
* ladder continues.
|
|
102
127
|
*
|
|
103
128
|
* Mirrors native's BrowserManager.applyLayer so web resolves the shared
|
|
104
129
|
* `request` → `<kind>` → route chain identically across platforms.
|
|
@@ -113,10 +138,24 @@ export const RequestConfigBuilder = {
|
|
|
113
138
|
// its result replaces it. When both are set they run as a pipeline — async
|
|
114
139
|
// first, then sync (mirrors native applyLayer).
|
|
115
140
|
if (layer.transform || layer.transformSync) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
141
|
+
try {
|
|
142
|
+
let cfg = base
|
|
143
|
+
if (layer.transform) cfg = await layer.transform(cfg, params)
|
|
144
|
+
if (layer.transformSync) cfg = layer.transformSync(cfg, params)
|
|
145
|
+
return cfg
|
|
146
|
+
} catch (e) {
|
|
147
|
+
// A throwing transform costs its own layer, not the ladder: the layers
|
|
148
|
+
// under it stand, so a relative path still gets the shared `baseUrl`
|
|
149
|
+
// and a media request keeps the shared credential. One catch around
|
|
150
|
+
// the pair, as native has it, so a sync stage that throws after a
|
|
151
|
+
// successful async one also falls back to the layer's input rather
|
|
152
|
+
// than to a half-applied config.
|
|
153
|
+
console.error(
|
|
154
|
+
'Failed to apply transform function, using base config',
|
|
155
|
+
e
|
|
156
|
+
)
|
|
157
|
+
return base
|
|
158
|
+
}
|
|
120
159
|
}
|
|
121
160
|
return {
|
|
122
161
|
method: layer.method ?? base.method,
|
|
@@ -171,38 +210,115 @@ export const RequestConfigBuilder = {
|
|
|
171
210
|
},
|
|
172
211
|
|
|
173
212
|
/**
|
|
174
|
-
* Resolves a media
|
|
175
|
-
*
|
|
176
|
-
*
|
|
213
|
+
* Resolves a media request from the layered configuration, returning the
|
|
214
|
+
* whole request rather than just its URL.
|
|
215
|
+
*
|
|
216
|
+
* The layers resolve headers (and a user agent) alongside the URL, and the
|
|
217
|
+
* media request needs all of them: native applies them to the AVURLAsset /
|
|
218
|
+
* ExoPlayer DataSpec, so web has to apply them to Shaka's requests or the
|
|
219
|
+
* same configuration authenticates on iOS and Android but 401s here.
|
|
177
220
|
*
|
|
178
|
-
* The
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
221
|
+
* The resolved URL is returned in `path`, with `baseUrl` already folded into
|
|
222
|
+
* it. Best-effort throughout, but at two levels: a throwing transform or
|
|
223
|
+
* resolver costs only its own layer (see {@link applyLayer}), and the catch
|
|
224
|
+
* here is the backstop for anything left — a malformed `baseUrl` reaching
|
|
225
|
+
* `buildUrl`, say — which falls back to the bare `src`.
|
|
226
|
+
*
|
|
227
|
+
* The chain is `request` → `media` → `media.resolve(track)`, matching
|
|
228
|
+
* native's. See {@link applyMediaResolve} for why the per-track resolver goes
|
|
229
|
+
* last.
|
|
183
230
|
*
|
|
184
231
|
* @param src The track's src value (may be relative or absolute)
|
|
185
232
|
* @param requestConfig The shared request configuration (applied first)
|
|
186
233
|
* @param mediaConfig The media request configuration
|
|
187
|
-
* @
|
|
234
|
+
* @param track The track being loaded, for `media.resolve`; without it the
|
|
235
|
+
* per-track layer is skipped and the static layers stand
|
|
236
|
+
* @returns The resolved request, its URL in `path`
|
|
188
237
|
*/
|
|
189
|
-
async
|
|
238
|
+
async resolveMediaRequest(
|
|
190
239
|
src: string,
|
|
191
240
|
requestConfig: TransformableRequestConfig | undefined,
|
|
192
|
-
mediaConfig: MediaRequestConfig | undefined
|
|
193
|
-
|
|
241
|
+
mediaConfig: MediaRequestConfig | undefined,
|
|
242
|
+
track?: Track
|
|
243
|
+
): Promise<ResolvedMediaRequest> {
|
|
194
244
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
// The layers are applied one at a time rather than through `applyLayers`
|
|
246
|
+
// so the media layer's own header contribution stays visible; each
|
|
247
|
+
// transform still runs exactly once.
|
|
248
|
+
const shared = await this.applyLayer({ path: src }, requestConfig)
|
|
249
|
+
const layered = await this.applyLayer(shared, mediaConfig)
|
|
250
|
+
const config = await this.applyMediaResolve(layered, mediaConfig, track)
|
|
251
|
+
// buildUrl folds in baseUrl *and* appends `query`; both are cleared on
|
|
252
|
+
// the way out so the resolved url can't be rebuilt and double-applied.
|
|
253
|
+
return {
|
|
254
|
+
...config,
|
|
255
|
+
baseUrl: undefined,
|
|
256
|
+
query: undefined,
|
|
257
|
+
path: this.buildUrl({ ...config, path: config.path ?? src }),
|
|
258
|
+
mediaLayerHeaders: addsHeaders(shared.headers, config.headers)
|
|
259
|
+
}
|
|
200
260
|
} catch (e) {
|
|
201
261
|
console.error('Failed to resolve media URL, using original src', e)
|
|
202
|
-
return
|
|
262
|
+
return {
|
|
263
|
+
path: BrowserPathHelper.buildUrl(undefined, src),
|
|
264
|
+
mediaLayerHeaders: false
|
|
265
|
+
}
|
|
203
266
|
}
|
|
204
267
|
},
|
|
205
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Applies `media.resolve(track)` / `media.resolveSync(track)` as the final,
|
|
271
|
+
* most-specific layer over an already request+media-layered config. A no-op
|
|
272
|
+
* without a track or a resolver, so the layers stand on their own.
|
|
273
|
+
*
|
|
274
|
+
* Last, not first: native runs the media layer's transform and then lets the
|
|
275
|
+
* per-track resolver win over the result (Android `applyMediaResolve`, iOS
|
|
276
|
+
* `applyMediaResolveLayer`), so a resolver minting a signed URL is not
|
|
277
|
+
* overwritten by the static layer that shaped the unsigned one. Override-wins
|
|
278
|
+
* on every field, `path` included — replacing the URL outright is the point.
|
|
279
|
+
*
|
|
280
|
+
* A throwing resolver falls back to the layered config, as native does: the
|
|
281
|
+
* per-track shaping is lost, the layers under it are not.
|
|
282
|
+
*/
|
|
283
|
+
async applyMediaResolve(
|
|
284
|
+
layered: RequestConfig,
|
|
285
|
+
mediaConfig: MediaRequestConfig | undefined,
|
|
286
|
+
track: Track | undefined
|
|
287
|
+
): Promise<RequestConfig> {
|
|
288
|
+
if (!track || !mediaConfig) return layered
|
|
289
|
+
if (!mediaConfig.resolve && !mediaConfig.resolveSync) return layered
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const resolved = await this.composeResolved(mediaConfig, track)
|
|
293
|
+
return resolved ? this.mergeConfig(layered, resolved) : layered
|
|
294
|
+
} catch (e) {
|
|
295
|
+
console.error('Failed to apply media.resolve, using layered config', e)
|
|
296
|
+
return layered
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Runs a config's per-track resolvers and composes their output: async
|
|
302
|
+
* `resolve` first, then `resolveSync` merged over it, sync winning.
|
|
303
|
+
* `undefined` when neither is set or neither produced a config.
|
|
304
|
+
*
|
|
305
|
+
* Media and artwork share the pairing, so it lives here rather than in both
|
|
306
|
+
* — the same reason native has `composeResolved`. Errors are the callers' to
|
|
307
|
+
* handle: the two paths differ on what a throwing resolver costs.
|
|
308
|
+
*/
|
|
309
|
+
async composeResolved(
|
|
310
|
+
config: Pick<MediaRequestConfig, 'resolve' | 'resolveSync'>,
|
|
311
|
+
track: Track
|
|
312
|
+
): Promise<RequestConfig | undefined> {
|
|
313
|
+
let resolved: RequestConfig | undefined
|
|
314
|
+
if (config.resolve) resolved = await config.resolve(track)
|
|
315
|
+
if (config.resolveSync) {
|
|
316
|
+
const sync = config.resolveSync(track)
|
|
317
|
+
resolved = resolved ? this.mergeConfig(resolved, sync) : sync
|
|
318
|
+
}
|
|
319
|
+
return resolved
|
|
320
|
+
},
|
|
321
|
+
|
|
206
322
|
/**
|
|
207
323
|
* Resolves an artwork URL and creates an ImageSource.
|
|
208
324
|
* Matches Android's artwork URL transformation behavior.
|
|
@@ -297,16 +413,7 @@ export const RequestConfigBuilder = {
|
|
|
297
413
|
|
|
298
414
|
// Step 1: Per-track resolution — async `resolve` first, then `resolveSync`
|
|
299
415
|
// merged over it (mirrors native).
|
|
300
|
-
|
|
301
|
-
if (artworkConfig.resolve) {
|
|
302
|
-
resolvedConfig = await artworkConfig.resolve(track)
|
|
303
|
-
}
|
|
304
|
-
if (artworkConfig.resolveSync) {
|
|
305
|
-
const r = artworkConfig.resolveSync(track)
|
|
306
|
-
resolvedConfig = resolvedConfig
|
|
307
|
-
? this.mergeConfig(resolvedConfig, r)
|
|
308
|
-
: r
|
|
309
|
-
}
|
|
416
|
+
const resolvedConfig = await this.composeResolved(artworkConfig, track)
|
|
310
417
|
// If a resolver ran but produced nothing and there's no artwork URL, no artwork
|
|
311
418
|
if (
|
|
312
419
|
(artworkConfig.resolve || artworkConfig.resolveSync) &&
|