react-native-audio-browser 0.2.0-next.3 → 0.2.0-next.30
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 +36 -22
- 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 +541 -278
- 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 +52 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +27 -9
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +288 -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 +62 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +110 -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 +100 -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 +40 -2
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +301 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +168 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +160 -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 +55 -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/AVPlayerEngine.swift +330 -0
- package/ios/AVPlayerEngineCallbacks.swift +15 -0
- package/ios/Browser/BrowseGate.swift +58 -0
- package/ios/Browser/BrowserConfig.swift +117 -78
- package/ios/Browser/BrowserManager.swift +209 -603
- package/ios/Browser/BrowserPathHelper.swift +50 -46
- package/ios/Browser/InertStyleDiagnostic.swift +296 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/NavigationErrorState.swift +41 -0
- package/ios/Browser/PageResolver.swift +227 -0
- package/ios/Browser/RequestLayer.swift +90 -0
- package/ios/Browser/{BrowserManager+URLResolution.swift → RequestPipeline.swift} +193 -121
- package/ios/Browser/SearchDrillIn.swift +7 -7
- 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/CPInterfaceController+Safe.swift +100 -43
- package/ios/CarPlay/CarPlayArtworkResolver.swift +36 -83
- package/ios/CarPlay/CarPlayController.swift +42 -1296
- package/ios/CarPlay/CarPlayImageLoader.swift +78 -97
- package/ios/CarPlay/CarPlayListItemFactory.swift +303 -92
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +129 -136
- package/ios/CarPlay/CarPlaySession+Navigator.swift +114 -0
- package/ios/CarPlay/CarPlaySession+PageLoader.swift +92 -0
- package/ios/CarPlay/CarPlaySession+Refresher.swift +43 -0
- package/ios/CarPlay/CarPlaySession+TabBar.swift +193 -0
- package/ios/CarPlay/CarPlaySession.swift +209 -0
- package/ios/CarPlay/CarPlayTemplates.swift +63 -0
- package/ios/CarPlay/HybridAudioBrowser+IntentFavorite.swift +20 -0
- package/ios/{Player → CarPlay}/MediaIntentCriteria.swift +17 -9
- package/ios/CarPlay/MediaIntentPlayback.swift +74 -0
- package/ios/CarPlay/RNABMediaAddHandler.swift +8 -24
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +7 -23
- package/ios/CarPlay/RNABMediaIntentHandler.swift +18 -19
- package/ios/CarPlay/SectionPresentation.swift +77 -0
- package/ios/CarPlay/TopTemplateReplacer.swift +96 -0
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/NavigationError+Conversion.swift +14 -4
- 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/Extension/UIImage+Adaptive.swift +12 -0
- package/ios/Extension/URLError+Connectivity.swift +15 -0
- package/ios/Http/HttpClient.swift +66 -142
- package/ios/HybridAudioBrowser+Browser.swift +153 -0
- package/ios/HybridAudioBrowser+Options.swift +144 -0
- package/ios/HybridAudioBrowser+Player.swift +397 -0
- package/ios/HybridAudioBrowser+PlayerCallbacks.swift +190 -0
- package/ios/HybridAudioBrowser+Stubs.swift +46 -0
- package/ios/HybridAudioBrowser.swift +153 -1610
- package/ios/Model/NitroTypeStubs.swift +146 -24
- package/ios/Model/PlayerUpdateOptions.swift +8 -1
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +18 -53
- package/ios/Observer/ItemObserver.swift +90 -0
- package/ios/Observer/PlayerObserver.swift +59 -0
- package/ios/Option/SessionCategories.swift +42 -92
- package/ios/Player/AudioSessionController.swift +177 -0
- package/ios/Player/DeferredSeek.swift +70 -0
- package/ios/Player/ErrorStatus.swift +27 -0
- package/ios/Player/MediaLoader.swift +62 -35
- package/ios/Player/NowPlayingUpdater.swift +197 -50
- package/ios/Player/PlaybackFailure.swift +125 -0
- package/ios/Player/PlaybackPersistence.swift +73 -0
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/Player.swift +1078 -0
- package/ios/Player/{PlaybackCoordinatorCallbacks.swift → PlayerCallbacks.swift} +3 -4
- package/ios/Player/PlayerEngine.swift +87 -0
- package/ios/Player/PlayerStatusTypes.swift +2 -6
- package/ios/Player/QueueManager.swift +93 -67
- package/ios/Player/RetryLoop.swift +261 -0
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerManager.swift +1 -1
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +46 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +87 -331
- package/ios/Tests/BrowserPathHelperTests.swift +56 -15
- package/ios/Tests/CarPlayArtworkResolverTests.swift +60 -373
- package/ios/Tests/DeferredSeekTests.swift +70 -0
- package/ios/Tests/EmitterTests.swift +29 -0
- package/ios/Tests/GenerationMemoTests.swift +121 -0
- package/ios/Tests/InertStyleDiagnosticTests.swift +209 -0
- package/ios/Tests/JsonModelsTests.swift +164 -1
- package/ios/Tests/LRUCacheTests.swift +110 -0
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/MediaLoaderTests.swift +12 -48
- package/ios/Tests/ObserverTests.swift +88 -0
- package/ios/Tests/PlaybackErrorTests.swift +76 -0
- package/ios/Tests/PlaybackFailureTests.swift +189 -0
- package/ios/Tests/PlaybackPersistenceTests.swift +93 -0
- package/ios/Tests/PlayerPolicyTests.swift +420 -0
- package/ios/Tests/{PlaybackCoordinatorTests.swift → PlayerTests.swift} +300 -248
- package/ios/Tests/PlayingStateTests.swift +42 -0
- package/ios/Tests/QueueManagerTests.swift +196 -115
- package/ios/Tests/ReadyValueTests.swift +73 -0
- package/ios/Tests/RemoteCommandControllerTests.swift +152 -0
- package/ios/Tests/RemoteCommandTests.swift +65 -0
- package/ios/Tests/RequestLayerTests.swift +112 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryLoopTests.swift +270 -0
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +98 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +132 -0
- package/ios/Tests/Support/TestTypes.swift +108 -104
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- package/ios/Tests/TopTemplateReplacerTests.swift +172 -0
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/Tests/URLErrorConnectivityTests.swift +20 -0
- package/ios/TrackSelector.swift +46 -31
- package/ios/Util/ArtworkImageFetcher.swift +58 -1
- package/ios/Util/Emitter.swift +42 -0
- package/ios/Util/GenerationMemo.swift +36 -0
- package/ios/Util/LRUCache.swift +23 -159
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/ReadyValue.swift +56 -0
- package/ios/Util/SFSymbolRenderer.swift +59 -31
- package/ios/Util/SVGProcessor.swift +9 -12
- 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 +201 -76
- 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/PlayingStateFactory.js +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/NavigationErrorManager.js +3 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.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 +62 -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 +201 -76
- 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/PlayingStateFactory.js +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/NavigationErrorManager.js +3 -1
- package/lib/module/web/browser/NavigationErrorManager.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 +62 -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 +387 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +93 -61
- 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/resolveAssetSource.d.ts +1 -2
- package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
- 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 +4 -2
- package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +2 -2
- package/nitrogen/generated/android/c++/JAndroidAudioOffloadSettings.hpp +2 -2
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +12 -12
- package/nitrogen/generated/android/c++/JAndroidPlayerWakeMode.hpp +2 -2
- package/nitrogen/generated/android/c++/JAppKilledPlaybackBehavior.hpp +2 -2
- package/nitrogen/generated/android/c++/JArtworkRendering.hpp +58 -0
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +10 -5
- package/nitrogen/generated/android/c++/JArtworkVariants.hpp +2 -2
- package/nitrogen/generated/android/c++/JBatteryOptimizationStatus.hpp +2 -2
- package/nitrogen/generated/android/c++/JBatteryOptimizationStatusChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JBatteryWarningPendingChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JBrowseError.hpp +2 -2
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +19 -7
- package/nitrogen/generated/android/c++/JBrowserSourceCallbackParam.hpp +2 -2
- package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +2 -2
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCarPlaySiriListButtonPosition.hpp +2 -2
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JChapterMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JEqualizerSettings.hpp +10 -10
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +10 -5
- package/nitrogen/generated/android/c++/JFormatNavigationErrorParams.hpp +11 -5
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +10 -5
- package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +9 -9
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.hpp +4 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____MediaTransformParams.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +9 -9
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +18 -12
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +10 -4
- 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 +18 -6
- package/nitrogen/generated/android/c++/JFunc_void.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_BatteryOptimizationStatusChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_BatteryWarningPendingChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_EqualizerSettings.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_NavigationErrorEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +9 -12
- package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +10 -5
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackPlayWhenReadyChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackProgressUpdatedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackQueueEndedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlayingState.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteJumpBackwardEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteJumpForwardEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemotePlayIdEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemotePlaySearchEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSeekEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSkipEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_RepeatModeChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_TimedMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_TrackMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_FormattedNavigationError_.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +17 -5
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__SleepTimerTime__SleepTimerEndOfTrack__.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_ChapterMetadata_.hpp +16 -16
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +24 -18
- package/nitrogen/generated/android/c++/JGate.hpp +2 -2
- package/nitrogen/generated/android/c++/JGateDecision.hpp +2 -2
- package/nitrogen/generated/android/c++/JGateEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JGateReason.hpp +2 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +64 -0
- package/nitrogen/generated/android/c++/JHttpMethod.hpp +2 -2
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +77 -53
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JIOSCategory.hpp +2 -2
- package/nitrogen/generated/android/c++/JIOSCategoryMode.hpp +2 -2
- package/nitrogen/generated/android/c++/JIOSCategoryOptions.hpp +2 -2
- package/nitrogen/generated/android/c++/JIOSCategoryPolicy.hpp +2 -2
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +17 -11
- package/nitrogen/generated/android/c++/JImageContext.hpp +2 -2
- package/nitrogen/generated/android/c++/JImageQueryParams.hpp +2 -2
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JImageSource.hpp +2 -2
- package/nitrogen/generated/android/c++/JMediaReference.hpp +2 -2
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +10 -5
- package/nitrogen/generated/android/c++/JMediaTransformParams.hpp +2 -2
- package/nitrogen/generated/android/c++/JNativeAndroidSetupOptions.hpp +2 -2
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +48 -21
- package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +2 -2
- package/nitrogen/generated/android/c++/JNativeIOSSetupOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +16 -4
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +17 -14
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +9 -12
- package/nitrogen/generated/android/c++/JNavigationError.hpp +2 -2
- package/nitrogen/generated/android/c++/JNavigationErrorEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JNavigationErrorKind.hpp +58 -0
- package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +2 -2
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +12 -12
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +17 -11
- package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +2 -2
- package/nitrogen/generated/android/c++/JOptions.hpp +9 -12
- package/nitrogen/generated/android/c++/JOutput.hpp +2 -2
- package/nitrogen/generated/android/c++/JOutputType.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlayback.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +10 -5
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackPlayWhenReadyChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackProgressUpdatedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackQueueEndedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlaybackState.hpp +2 -2
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +7 -13
- package/nitrogen/generated/android/c++/JPlayingState.hpp +2 -2
- package/nitrogen/generated/android/c++/JProgress.hpp +2 -2
- 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++/JRemoteJumpBackwardEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRemoteJumpForwardEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRemotePlayIdEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRemotePlaySearchEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRemoteSeekEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRemoteSkipEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRepeatMode.hpp +2 -2
- package/nitrogen/generated/android/c++/JRepeatModeChangedEvent.hpp +2 -2
- package/nitrogen/generated/android/c++/JRequestConfig.hpp +2 -2
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +67 -59
- package/nitrogen/generated/android/c++/JRetryConfig.hpp +2 -2
- package/nitrogen/generated/android/c++/JSearchMode.hpp +2 -2
- package/nitrogen/generated/android/c++/JSearchParams.hpp +2 -2
- package/nitrogen/generated/android/c++/JSection.hpp +116 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JSleepTimer.hpp +4 -4
- package/nitrogen/generated/android/c++/JSleepTimerEndOfTrack.hpp +2 -2
- package/nitrogen/generated/android/c++/JSleepTimerTime.hpp +2 -2
- package/nitrogen/generated/android/c++/JStallReason.hpp +2 -2
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTimedMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JTrack.hpp +27 -48
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +18 -12
- package/nitrogen/generated/android/c++/JTrackMetadata.hpp +2 -2
- package/nitrogen/generated/android/c++/JTrackRequest.hpp +2 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +47 -19
- package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +2 -2
- package/nitrogen/generated/android/c++/JVariant_Boolean_AndroidAudioOffloadSettings.hpp +3 -3
- package/nitrogen/generated/android/c++/JVariant_Boolean_RetryConfig.hpp +3 -3
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +3 -3
- 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 +20 -8
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioOffloadSettings.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +20 -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/ArtworkRequestConfig.kt +37 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkVariants.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BatteryOptimizationStatusChangedEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BatteryWarningPendingChangedEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowseError.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowseResult.kt +16 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowserSourceCallbackParam.kt +15 -0
- 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/ChapterMetadata.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/EqualizerSettings.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FavoriteChangedEvent.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNavigationErrorParams.kt +25 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____MediaTransformParams.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_BatteryOptimizationStatusChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_BatteryWarningPendingChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_EqualizerSettings.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_FavoriteChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_GateEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_NavigationErrorEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_NowPlayingMetadata.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_Options.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_Output.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_Playback.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackActiveTrackChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackErrorEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackPlayWhenReadyChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackProgressUpdatedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackQueueEndedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlayingState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteJumpBackwardEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteJumpForwardEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemotePlayIdEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemotePlaySearchEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteSeekEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteSkipEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RepeatModeChangedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_TimedMetadata.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_TrackMetadata.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_FormattedNavigationError_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_ResolvedTrack_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_std__variant_nitro__NullType__SleepTimerTime__SleepTimerEndOfTrack__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__vector_ChapterMetadata_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__vector_Track_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Gate.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GateDecision.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GateEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +3 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +25 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageContext.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageQueryParams.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageSource.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaTransformParams.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeAndroidSetupOptions.kt +31 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +51 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeGateRequest.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeIOSSetupOptions.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationError.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorKind.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +20 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +25 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Output.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Playback.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackActiveTrackChangedEvent.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackPlayWhenReadyChangedEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackProgressUpdatedEvent.kt +19 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackQueueEndedEvent.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +37 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayingState.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Progress.kt +17 -0
- 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 +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteJumpBackwardEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteJumpForwardEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemotePlayIdEvent.kt +15 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemotePlaySearchEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteSeekEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteSkipEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RepeatModeChangedEvent.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RequestConfig.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +67 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimer.kt +18 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimerEndOfTrack.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimerTime.kt +13 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TimedMetadata.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +55 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackLoadEvent.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackMetadata.kt +43 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +17 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +58 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +31 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_AndroidAudioOffloadSettings.kt +16 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_RetryConfig.kt +16 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_NullType_Double.kt +16 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +24 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +18 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +16 -7
- package/nitrogen/generated/ios/AudioBrowser+autolinking.rb +2 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +38 -29
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +779 -665
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +34 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +36 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/BrowseResult.swift +12 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/FormatNavigationErrorParams.swift +7 -2
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +48 -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/NavigationErrorKind.swift +40 -0
- 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/SleepTimer.swift +13 -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_Bool_AndroidAudioOffloadSettings.swift +12 -0
- package/nitrogen/generated/ios/swift/Variant_Bool_RetryConfig.swift +12 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +12 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_RemoteButtonLayout.swift +30 -0
- package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +13 -0
- package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +12 -0
- 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++/FormatNavigationErrorParams.hpp +9 -2
- package/nitrogen/generated/shared/c++/GridTile.hpp +84 -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++/NavigationErrorKind.hpp +76 -0
- 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 +403 -134
- package/src/types/browser.ts +97 -63
- 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 +240 -83
- 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/PlayingStateFactory.test.ts +1 -1
- package/src/web/TrackPlayer/PlayingStateFactory.ts +1 -1
- 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/NavigationErrorManager.ts +3 -1
- 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 +68 -0
- package/src/web/util/BrowserPathHelper.ts +70 -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 +587 -0
- package/website/guide/carplay.md +650 -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/Bridge.h +0 -8
- package/ios/Browser/MediaResolveComposer.swift +0 -114
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/ios/NowPlayingInfo/MediaItemProperty.swift +0 -78
- package/ios/NowPlayingInfo/NowPlayingInfoKeyValue.swift +0 -6
- package/ios/NowPlayingInfo/NowPlayingInfoProperty.swift +0 -220
- package/ios/Observer/PlayerItemNotificationObserver.swift +0 -119
- package/ios/Observer/PlayerItemPropertyObserver.swift +0 -133
- package/ios/Observer/PlayerStateObserver.swift +0 -50
- package/ios/Observer/PlayerTimeObserver.swift +0 -55
- package/ios/Option/PitchAlgorithms.swift +0 -17
- package/ios/Option/TimeEventFrequency.swift +0 -18
- package/ios/Player/LoadSeekCoordinator.swift +0 -99
- package/ios/Player/MediaLoaderDelegate.swift +0 -11
- package/ios/Player/PlaybackCoordinator.swift +0 -754
- package/ios/Player/PlaybackEffectHandler.swift +0 -54
- package/ios/Player/PlaybackErrorHandler.swift +0 -175
- package/ios/Player/PlaybackStateStore.swift +0 -27
- package/ios/Player/PlayingStateManager.swift +0 -33
- package/ios/Player/RetryManager.swift +0 -322
- package/ios/Player/SeekCompletionHandler.swift +0 -3
- package/ios/Player/SleepTimerHandling.swift +0 -15
- package/ios/Support/RNTrackPlayer-Bridging-Header.h +0 -7
- package/ios/Tests/LoadSeekCoordinatorTests.swift +0 -325
- package/ios/Tests/MediaResolveTests.swift +0 -126
- package/ios/Tests/OnceValueTests.swift +0 -79
- package/ios/Tests/PlaybackErrorHandlerTests.swift +0 -547
- package/ios/Tests/PlaybackStateStoreTests.swift +0 -48
- package/ios/Tests/PlayingStateManagerTests.swift +0 -57
- package/ios/Tests/RetryManagerTests.swift +0 -89
- package/ios/TrackPlayer.swift +0 -1014
- package/ios/TrackPlayerCallbacks.swift +0 -86
- package/ios/Util/MetadataAdapter.swift +0 -131
- package/ios/Util/OnceValue.swift +0 -104
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JFavoritesMatchMode.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/FavoriteConfig.kt +0 -38
- 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/ios/swift/Variant_NullType_NotificationButtonLayout.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
|
@@ -2,39 +2,75 @@ package com.audiobrowser.tls
|
|
|
2
2
|
|
|
3
3
|
import java.io.ByteArrayInputStream
|
|
4
4
|
import java.io.ByteArrayOutputStream
|
|
5
|
+
import java.io.InputStream
|
|
5
6
|
import java.net.InetSocketAddress
|
|
6
7
|
import java.net.Socket
|
|
7
8
|
import java.net.URL
|
|
8
9
|
import java.security.cert.CertificateFactory
|
|
9
10
|
import java.security.cert.X509Certificate
|
|
10
|
-
import java.util.concurrent.
|
|
11
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
11
12
|
import javax.net.ssl.HttpsURLConnection
|
|
12
13
|
import javax.net.ssl.SSLContext
|
|
14
|
+
import javax.net.ssl.SSLSocket
|
|
13
15
|
import javax.net.ssl.SSLSocketFactory
|
|
16
|
+
import kotlin.concurrent.thread
|
|
14
17
|
import timber.log.Timber
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* Fetches and caches issuer certificates from AIA "CA Issuers" URLs. Used by
|
|
18
21
|
* [AiaChasingTrustManager] to fill in intermediates a server failed to send.
|
|
19
22
|
*
|
|
20
|
-
* Runs on the TLS handshake thread, so it
|
|
21
|
-
* fetch resolves to an empty list
|
|
22
|
-
* chasing
|
|
23
|
-
*
|
|
23
|
+
* Runs on the TLS handshake thread, so it is bounded on every axis and never throws — a failed,
|
|
24
|
+
* oversized or slow fetch resolves to an empty list, and the handshake then fails exactly as it
|
|
25
|
+
* would have without AIA chasing. Every input here is chosen by a server we are already failing to
|
|
26
|
+
* validate: the AIA extension names the host, port and path, and then that host chooses the
|
|
27
|
+
* response. So:
|
|
28
|
+
* - the URL is rejected unless it is free of control characters ([isSafeUrl]),
|
|
29
|
+
* - a response is capped at [MAX_RESPONSE_BYTES] and [MAX_RESPONSE_MILLIS],
|
|
30
|
+
* - at most [MAX_REDIRECTS] redirects are followed,
|
|
31
|
+
* - the cache holds at most [MAX_CACHE_ENTRIES], and only successful fetches, so a round trip
|
|
32
|
+
* happens at most once per intermediate while transient failures stay retryable,
|
|
33
|
+
* - the whole call — every connect, handshake, read and redirect hop — ends at the caller's
|
|
34
|
+
* `deadlineNanos`: each per-operation timeout is clipped to the time remaining, connect and read
|
|
35
|
+
* by their socket timeouts and the TLS handshake plus the exchange by a watchdog that closes the
|
|
36
|
+
* underlying transport, and once the deadline has passed nothing further is even started.
|
|
24
37
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
38
|
+
* The one step no deadline reaches is DNS: `InetSocketAddress` resolves synchronously and Java
|
|
39
|
+
* exposes no timeout for it, so a lookup begun just inside the deadline can overrun it by its own
|
|
40
|
+
* duration. A lookup is never *started* past the deadline, and [AiaCertChaser.MAX_URLS_PER_CERT]
|
|
41
|
+
* keeps the count down — a certificate can only make us resolve a few names, not a few hundred.
|
|
42
|
+
*
|
|
43
|
+
* Both schemes go over a raw [Socket] rather than `HttpURLConnection`. For `http` that is because
|
|
44
|
+
* CA "CA Issuers" URLs are virtually always plain `http` — the CA/Browser Forum Baseline
|
|
45
|
+
* Requirements mandate it, to avoid a chicken-and-egg TLS dependency when fetching the certificate
|
|
46
|
+
* needed to complete a TLS chain — while Android blocks cleartext `HttpURLConnection` traffic by
|
|
47
|
+
* default on apps targeting API 28+. A raw socket is not subject to `NetworkSecurityPolicy`, so the
|
|
48
|
+
* fetch succeeds regardless of the host app's cleartext setting; safely, because the fetched
|
|
49
|
+
* certificate is still cryptographically verified to be the real issuer before
|
|
50
|
+
* [AiaChasingTrustManager] trusts it.
|
|
51
|
+
*
|
|
52
|
+
* For `https` it is because `HttpURLConnection.getInputStream()` performs the connect, request and
|
|
53
|
+
* response *headers* before returning a stream, so a read cap applied to that stream bounds only
|
|
54
|
+
* the body: a server dripping header bytes just inside the per-read timeout would hold the
|
|
55
|
+
* handshake thread for as long as it liked. Reading the headers ourselves puts them inside the same
|
|
56
|
+
* budget as the body. It also keeps one redirect policy — the platform stack would otherwise follow
|
|
57
|
+
* its own, ignoring [MAX_REDIRECTS].
|
|
32
58
|
*/
|
|
33
59
|
class CachingCertificateFetcher(
|
|
34
60
|
private val connectTimeoutMs: Int = 5_000,
|
|
35
61
|
private val readTimeoutMs: Int = 5_000,
|
|
62
|
+
private val nowNanos: () -> Long = System::nanoTime,
|
|
36
63
|
) {
|
|
37
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Bounded, least-recently-used. An attacker can name any URL and serve any parseable certificate
|
|
66
|
+
* from it, so an unbounded map would retain a megabyte per hostile handshake for the process
|
|
67
|
+
* lifetime. A real app needs a handful of intermediates.
|
|
68
|
+
*/
|
|
69
|
+
private val cache =
|
|
70
|
+
object : LinkedHashMap<String, List<X509Certificate>>(16, 0.75f, true) {
|
|
71
|
+
override fun removeEldestEntry(eldest: Map.Entry<String, List<X509Certificate>>) =
|
|
72
|
+
size > MAX_CACHE_ENTRIES
|
|
73
|
+
}
|
|
38
74
|
|
|
39
75
|
/**
|
|
40
76
|
* A plain system-default socket factory for `https` AIA fetches. Without it the fetch would use
|
|
@@ -45,80 +81,230 @@ class CachingCertificateFetcher(
|
|
|
45
81
|
SSLContext.getInstance("TLS").apply { init(null, null, null) }.socketFactory
|
|
46
82
|
}
|
|
47
83
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Fetches the certificates served at [url], giving up no later than [deadlineNanos] — absolute,
|
|
86
|
+
* on [nowNanos]'s origin. [AiaCertChaser.completeChain] passes its whole-walk deadline here, so
|
|
87
|
+
* one budget bounds the sum of every fetch it makes. The default covers a standalone call.
|
|
88
|
+
*
|
|
89
|
+
* A cache hit is answered even past the deadline — it costs no I/O.
|
|
90
|
+
*/
|
|
91
|
+
fun fetch(
|
|
92
|
+
url: String,
|
|
93
|
+
deadlineNanos: Long = nowNanos() + AiaCertChaser.MAX_CHASE_MILLIS * 1_000_000,
|
|
94
|
+
): List<X509Certificate> {
|
|
95
|
+
synchronized(cache) { cache[url] }
|
|
96
|
+
?.let {
|
|
97
|
+
return it
|
|
98
|
+
}
|
|
99
|
+
val certs = download(url, deadlineNanos)?.let { parseCertificates(it) }.orEmpty()
|
|
100
|
+
if (certs.isNotEmpty()) synchronized(cache) { cache[url] = certs }
|
|
54
101
|
return certs
|
|
55
102
|
}
|
|
56
103
|
|
|
57
|
-
|
|
104
|
+
/** Milliseconds until [deadlineNanos]; zero or negative once it has passed. */
|
|
105
|
+
private fun remainingMs(deadlineNanos: Long): Long = (deadlineNanos - nowNanos()) / 1_000_000
|
|
106
|
+
|
|
107
|
+
private fun download(
|
|
108
|
+
url: String,
|
|
109
|
+
deadlineNanos: Long,
|
|
110
|
+
redirectsLeft: Int = MAX_REDIRECTS,
|
|
111
|
+
): ByteArray? =
|
|
58
112
|
try {
|
|
113
|
+
// Checked before anything is started — DNS included, since it is the one step below that
|
|
114
|
+
// no timeout reaches. Redirects recurse through here, so every hop re-checks.
|
|
115
|
+
require(remainingMs(deadlineNanos) > 0) { "AIA chase budget exhausted" }
|
|
116
|
+
require(isSafeUrl(url)) { "AIA URL contains characters that are not valid in a URL" }
|
|
59
117
|
val parsed = URL(url)
|
|
60
118
|
when (parsed.protocol) {
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
119
|
+
// restrict to http/https — file:, ftp: etc. are never valid CA-issuer sources
|
|
120
|
+
"http" -> fetchOverSocket(parsed, deadlineNanos, redirectsLeft, secure = false)
|
|
121
|
+
"https" -> fetchOverSocket(parsed, deadlineNanos, redirectsLeft, secure = true)
|
|
122
|
+
else -> null
|
|
64
123
|
}
|
|
65
124
|
} catch (e: Exception) {
|
|
66
|
-
|
|
125
|
+
// The URL is attacker-supplied text and this is the one place it survives the safety
|
|
126
|
+
// check, so it is escaped rather than handed to the log verbatim.
|
|
127
|
+
Timber.w(e, "AIA CA-issuer fetch failed: %s", sanitizeForLog(url))
|
|
67
128
|
null
|
|
68
129
|
}
|
|
69
130
|
|
|
70
131
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
132
|
+
* A connected socket to speak HTTP over, plus the plain TCP transport underneath it. They differ
|
|
133
|
+
* for `https`: closing an `SSLSocket` writes a `close_notify` alert, which can itself block
|
|
134
|
+
* against a peer that stopped reading — so anything that needs to *unblock* a stalled call must
|
|
135
|
+
* close [transport], whose close is immediate and fails every read layered above it.
|
|
74
136
|
*/
|
|
75
|
-
private
|
|
76
|
-
val connection = url.openConnection() as? HttpsURLConnection ?: return null
|
|
77
|
-
connection.connectTimeout = connectTimeoutMs
|
|
78
|
-
connection.readTimeout = readTimeoutMs
|
|
79
|
-
connection.sslSocketFactory = plainSslSocketFactory
|
|
80
|
-
return connection.inputStream.use { it.readBytes() }
|
|
81
|
-
}
|
|
137
|
+
private class Connection(val socket: Socket, val transport: Socket)
|
|
82
138
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
139
|
+
/** One HTTP/1.0 exchange over a socket, TLS-wrapped when [secure], then redirects. */
|
|
140
|
+
private fun fetchOverSocket(
|
|
141
|
+
url: URL,
|
|
142
|
+
deadlineNanos: Long,
|
|
143
|
+
redirectsLeft: Int,
|
|
144
|
+
secure: Boolean,
|
|
145
|
+
): ByteArray? {
|
|
88
146
|
val host = url.host
|
|
89
|
-
val port = if (url.port != -1) url.port else 80
|
|
147
|
+
val port = if (url.port != -1) url.port else if (secure) 443 else 80
|
|
90
148
|
val path = url.file.ifEmpty { "/" } // URL.file is path + query
|
|
149
|
+
val connection = connect(host, port, secure, deadlineNanos)
|
|
91
150
|
val raw =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
try {
|
|
152
|
+
withWatchdog(
|
|
153
|
+
minOf(remainingMs(deadlineNanos), connectTimeoutMs + MAX_RESPONSE_MILLIS),
|
|
154
|
+
abort = { connection.transport.close() },
|
|
155
|
+
) {
|
|
156
|
+
val socket = connection.socket
|
|
157
|
+
socket.soTimeout = timeoutMs(deadlineNanos, readTimeoutMs)
|
|
158
|
+
// HTTP/1.0 + `Connection: close` so the server closes after the body and we can read
|
|
159
|
+
// to EOF without parsing Content-Length. A non-default port belongs in Host, or a
|
|
160
|
+
// name-based virtual host answers for the wrong site.
|
|
161
|
+
val hostHeader = if (port == (if (secure) 443 else 80)) host else "$host:$port"
|
|
162
|
+
val request =
|
|
163
|
+
"GET $path HTTP/1.0\r\n" +
|
|
164
|
+
"Host: $hostHeader\r\n" +
|
|
165
|
+
"Connection: close\r\n" +
|
|
166
|
+
"User-Agent: react-native-audio-browser\r\n" +
|
|
167
|
+
"Accept: */*\r\n\r\n"
|
|
168
|
+
socket.getOutputStream().apply {
|
|
169
|
+
write(request.toByteArray(Charsets.ISO_8859_1))
|
|
170
|
+
flush()
|
|
171
|
+
}
|
|
172
|
+
socket.getInputStream().use {
|
|
173
|
+
readCapped(
|
|
174
|
+
it,
|
|
175
|
+
budgetMs = minOf(remainingMs(deadlineNanos), MAX_RESPONSE_MILLIS),
|
|
176
|
+
nowNanos = nowNanos,
|
|
177
|
+
)
|
|
178
|
+
}
|
|
106
179
|
}
|
|
107
|
-
|
|
108
|
-
|
|
180
|
+
} finally {
|
|
181
|
+
// Transport first: its close never blocks and unblocks anything above it, so the
|
|
182
|
+
// SSLSocket's close_notify attempt afterwards fails fast instead of stalling.
|
|
183
|
+
runCatching { connection.transport.close() }
|
|
184
|
+
runCatching { connection.socket.close() }
|
|
185
|
+
} ?: return null
|
|
109
186
|
val response = parseHttpResponse(raw) ?: return null
|
|
110
187
|
return when {
|
|
111
188
|
response.status in 200..299 -> response.body
|
|
112
189
|
response.status in 300..399 && response.location != null && redirectsLeft > 0 ->
|
|
113
|
-
// Follow CA-issuer redirects (rare).
|
|
114
|
-
|
|
190
|
+
// Follow CA-issuer redirects (rare). `download` is the recursion point, so the target
|
|
191
|
+
// goes through the same URL check and the same deadline.
|
|
192
|
+
download(URL(url, response.location).toString(), deadlineNanos, redirectsLeft - 1)
|
|
115
193
|
else -> null
|
|
116
194
|
}
|
|
117
195
|
}
|
|
118
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Connects to [host]:[port], wrapping in TLS when [secure]. Both the connect and the TLS
|
|
199
|
+
* handshake are clipped to the caller's deadline.
|
|
200
|
+
*
|
|
201
|
+
* The TLS socket verifies the hostname explicitly. A raw `SSLSocket` does not do it on its own,
|
|
202
|
+
* and `SSLParameters.setEndpointIdentificationAlgorithm` is API 24+, so the platform's default
|
|
203
|
+
* verifier is applied against the negotiated session instead — which works on every supported
|
|
204
|
+
* level and is the same check `HttpsURLConnection` would have made.
|
|
205
|
+
*/
|
|
206
|
+
private fun connect(host: String, port: Int, secure: Boolean, deadlineNanos: Long): Connection {
|
|
207
|
+
val plain = Socket()
|
|
208
|
+
// Everything from here on closes `plain` if it throws. Android's Socket.connect, unlike
|
|
209
|
+
// modern OpenJDK's, does not close itself on failure once the descriptor is allocated, so
|
|
210
|
+
// every unreachable AIA host would otherwise leak one until finalization.
|
|
211
|
+
var wrapper: SSLSocket? = null
|
|
212
|
+
return try {
|
|
213
|
+
plain.connect(InetSocketAddress(host, port), timeoutMs(deadlineNanos, connectTimeoutMs))
|
|
214
|
+
if (!secure) return Connection(plain, plain)
|
|
215
|
+
// The handshake is bounded by a watchdog, not by soTimeout: soTimeout is per-read, so a
|
|
216
|
+
// server drip-feeding handshake bytes would stretch it indefinitely. The watchdog closes
|
|
217
|
+
// `plain`, the transport — closing the SSLSocket instead would write a close_notify that
|
|
218
|
+
// can block against the very peer that caused the stall.
|
|
219
|
+
withWatchdog(
|
|
220
|
+
minOf(remainingMs(deadlineNanos), connectTimeoutMs + MAX_RESPONSE_MILLIS),
|
|
221
|
+
abort = { plain.close() },
|
|
222
|
+
) {
|
|
223
|
+
val ssl = plainSslSocketFactory.createSocket(plain, host, port, true) as SSLSocket
|
|
224
|
+
wrapper = ssl
|
|
225
|
+
ssl.soTimeout = timeoutMs(deadlineNanos, readTimeoutMs)
|
|
226
|
+
ssl.startHandshake()
|
|
227
|
+
require(HttpsURLConnection.getDefaultHostnameVerifier().verify(host, ssl.session)) {
|
|
228
|
+
"AIA host $host does not match its certificate"
|
|
229
|
+
}
|
|
230
|
+
Connection(ssl, plain)
|
|
231
|
+
}
|
|
232
|
+
} catch (e: Exception) {
|
|
233
|
+
runCatching { plain.close() }
|
|
234
|
+
// The orphaned TLS wrapper too — with the transport already gone its close_notify attempt
|
|
235
|
+
// fails fast, and this releases the wrapper's own buffers rather than waiting on the GC.
|
|
236
|
+
runCatching { wrapper?.close() }
|
|
237
|
+
throw e
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A per-operation socket timeout: [perOperationMs] clipped to the deadline, and never 0 ("no
|
|
243
|
+
* timeout" to a socket) — an already-expired deadline still gets a 1 ms timeout and fails at
|
|
244
|
+
* once.
|
|
245
|
+
*/
|
|
246
|
+
private fun timeoutMs(deadlineNanos: Long, perOperationMs: Int): Int =
|
|
247
|
+
minOf(remainingMs(deadlineNanos), perOperationMs.toLong())
|
|
248
|
+
.coerceIn(1, Int.MAX_VALUE.toLong())
|
|
249
|
+
.toInt()
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Runs [body] with a watchdog thread that invokes [abort] once [budgetMs] elapses — unless the
|
|
253
|
+
* body has already finished, which the done flag makes visible to a watchdog waking at just that
|
|
254
|
+
* moment, so a successful exchange cannot have its socket closed out from under it.
|
|
255
|
+
*
|
|
256
|
+
* The socket timeouts are per-operation and so cannot bound a peer that keeps every individual
|
|
257
|
+
* read just inside them; [abort] — closing the underlying transport — is what actually ends a
|
|
258
|
+
* stalled handshake or exchange: the blocked call throws, and a failed fetch is already handled
|
|
259
|
+
* as "no AIA".
|
|
260
|
+
*/
|
|
261
|
+
private inline fun <T> withWatchdog(
|
|
262
|
+
budgetMs: Long,
|
|
263
|
+
crossinline abort: () -> Unit,
|
|
264
|
+
body: () -> T,
|
|
265
|
+
): T {
|
|
266
|
+
val done = AtomicBoolean(false)
|
|
267
|
+
val watchdog =
|
|
268
|
+
thread(isDaemon = true, name = "aia-fetch-watchdog") {
|
|
269
|
+
runCatching { Thread.sleep(budgetMs.coerceAtLeast(0)) }
|
|
270
|
+
.onSuccess { if (!done.get()) runCatching { abort() } }
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
return body()
|
|
274
|
+
} finally {
|
|
275
|
+
done.set(true)
|
|
276
|
+
watchdog.interrupt()
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
119
280
|
companion object {
|
|
120
281
|
private const val MAX_REDIRECTS = 5
|
|
121
282
|
|
|
283
|
+
/** Ceiling on how many fetched issuer certificates are retained. */
|
|
284
|
+
const val MAX_CACHE_ENTRIES = 32
|
|
285
|
+
|
|
286
|
+
/** Ceiling on a single AIA response, headers included. */
|
|
287
|
+
const val MAX_RESPONSE_BYTES = 1 shl 20 // 1 MiB
|
|
288
|
+
|
|
289
|
+
/** Ceiling on the wall-clock time spent reading one AIA response. */
|
|
290
|
+
const val MAX_RESPONSE_MILLIS = 10_000L
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Whether [url] is free of characters that have no business in a URL.
|
|
294
|
+
*
|
|
295
|
+
* The fetch writes the path into a request line on a raw socket, and the URL is an IA5String
|
|
296
|
+
* copied verbatim out of a certificate a hostile server presented. `java.net.URL` preserves
|
|
297
|
+
* control characters — `URL("http://host:6379/\r\nSET foo bar")` parses to that host and port
|
|
298
|
+
* with the CRLF intact in `file` — so without this check a certificate could inject request
|
|
299
|
+
* lines of its own, to any host and port the device can reach. Space is excluded too: it would
|
|
300
|
+
* split the request line's target from its HTTP version.
|
|
301
|
+
*/
|
|
302
|
+
fun isSafeUrl(url: String): Boolean = url.none { it.code <= 0x20 || it.code == 0x7F }
|
|
303
|
+
|
|
304
|
+
/** [text] with anything non-printable replaced, so a log line cannot be forged. */
|
|
305
|
+
fun sanitizeForLog(text: String): String =
|
|
306
|
+
text.map { if (it.code <= 0x20 || it.code == 0x7F) '?' else it }.joinToString("")
|
|
307
|
+
|
|
122
308
|
/**
|
|
123
309
|
* Parses every certificate from the bytes a CA-Issuers URL serves — a single DER certificate
|
|
124
310
|
* (e.g. Let's Encrypt), a PEM file, or a PKCS#7 "certs-only" bundle. Empty if the bytes are not
|
|
@@ -133,6 +319,37 @@ class CachingCertificateFetcher(
|
|
|
133
319
|
emptyList()
|
|
134
320
|
}
|
|
135
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Reads [input] to EOF, or returns null once it exceeds [limit] bytes or [budgetMs] of
|
|
324
|
+
* wall-clock time. Null rather than a truncated buffer: a cut-off certificate is not parseable
|
|
325
|
+
* anyway, and "too big" or "too slow" is a failed fetch, which the caller already knows how to
|
|
326
|
+
* treat as "no AIA".
|
|
327
|
+
*
|
|
328
|
+
* The byte cap alone would not bound the time. The socket's timeout is per-read, so a server
|
|
329
|
+
* returning one byte just inside it resets the clock on every iteration and holds the handshake
|
|
330
|
+
* thread for as long as it likes — a slow-drip stall in place of the unbounded buffer. Both
|
|
331
|
+
* limits are needed, and the deadline is checked against [nowNanos] (injectable so a test need
|
|
332
|
+
* not actually wait).
|
|
333
|
+
*/
|
|
334
|
+
fun readCapped(
|
|
335
|
+
input: InputStream,
|
|
336
|
+
limit: Int = MAX_RESPONSE_BYTES,
|
|
337
|
+
budgetMs: Long = MAX_RESPONSE_MILLIS,
|
|
338
|
+
nowNanos: () -> Long = System::nanoTime,
|
|
339
|
+
): ByteArray? {
|
|
340
|
+
val deadline = nowNanos() + budgetMs * 1_000_000
|
|
341
|
+
val out = ByteArrayOutputStream()
|
|
342
|
+
val chunk = ByteArray(8 * 1024)
|
|
343
|
+
while (true) {
|
|
344
|
+
val read = input.read(chunk)
|
|
345
|
+
if (read < 0) return out.toByteArray()
|
|
346
|
+
if (out.size() + read > limit) return null
|
|
347
|
+
out.write(chunk, 0, read)
|
|
348
|
+
// Subtraction, not `>`: overflow-safe across nanoTime's arbitrary origin.
|
|
349
|
+
if (nowNanos() - deadline >= 0) return null
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
136
353
|
/** Status code, `Location` header (if any), and decoded body of a raw HTTP response. */
|
|
137
354
|
data class HttpResponse(val status: Int, val location: String?, val body: ByteArray)
|
|
138
355
|
|
|
@@ -175,7 +392,9 @@ class CachingCertificateFetcher(
|
|
|
175
392
|
.substringBefore(';')
|
|
176
393
|
.trim()
|
|
177
394
|
.toIntOrNull(16) ?: break
|
|
178
|
-
|
|
395
|
+
// `<= 0`, not `== 0`: toIntOrNull accepts a leading `-`, and a negative size would move
|
|
396
|
+
// `pos` backwards — the same rewinding-cursor loop the DER reader had.
|
|
397
|
+
if (size <= 0) break // last chunk, or malformed
|
|
179
398
|
val dataStart = eol + 2
|
|
180
399
|
if (dataStart + size > body.size) break
|
|
181
400
|
out.write(body, dataStart, size)
|
|
@@ -5,11 +5,10 @@ import com.margelo.nitro.audiobrowser.Variant_String_ArtworkVariants
|
|
|
5
5
|
/**
|
|
6
6
|
* Reading `Track.artwork`, which is either one URL or a URL per appearance.
|
|
7
7
|
*
|
|
8
|
-
* Android Auto is dark-only, so there is nothing here to choose: a pair always
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `UIImageAsset` can swap them without a re-fetch.
|
|
8
|
+
* Android Auto is dark-only, so there is nothing here to choose: a pair always resolves to its dark
|
|
9
|
+
* URL, which is also what a single-URL track would have shipped. The per-appearance half of the
|
|
10
|
+
* feature is iOS/CarPlay only — see `ios/Extension/Variant+Artwork.swift`, where both URLs are kept
|
|
11
|
+
* so a `UIImageAsset` can swap them without a re-fetch.
|
|
13
12
|
*/
|
|
14
13
|
val Variant_String_ArtworkVariants.url: String
|
|
15
14
|
get() = match(first = { it }, second = { it.dark })
|
|
@@ -17,10 +16,9 @@ val Variant_String_ArtworkVariants.url: String
|
|
|
17
16
|
/**
|
|
18
17
|
* Wraps a plain URL as the single-URL side of the union.
|
|
19
18
|
*
|
|
20
|
-
* For the places that build a `Track` from something that only ever has one
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* Auto would have rendered that one anyway.
|
|
19
|
+
* For the places that build a `Track` from something that only ever has one image — restored
|
|
20
|
+
* persistence, a `MediaMetadata` handed back by the platform. Restoring collapses a pair to its
|
|
21
|
+
* dark URL, which costs nothing here: Android Auto would have rendered that one anyway.
|
|
24
22
|
*/
|
|
25
23
|
fun artworkOf(url: String?): Variant_String_ArtworkVariants? =
|
|
26
24
|
url?.let { Variant_String_ArtworkVariants.First(it) }
|
|
@@ -2,20 +2,26 @@ package com.audiobrowser.util
|
|
|
2
2
|
|
|
3
3
|
import android.net.Uri
|
|
4
4
|
import androidx.core.net.toUri
|
|
5
|
+
import com.margelo.nitro.audiobrowser.NavigationErrorKind
|
|
5
6
|
import java.net.URLEncoder
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Utility for handling browser paths and contextual URLs in the media browser system.
|
|
9
10
|
*
|
|
10
11
|
* Handles two types of special paths:
|
|
11
|
-
* 1. System paths (prefixed with `/__`):
|
|
12
|
+
* 1. System paths (prefixed with `/__`): root, search, offline, error, gate, and empty paths
|
|
12
13
|
* 2. Contextual URLs: Embed parent context in track identifiers for Media3 integration
|
|
13
14
|
*
|
|
14
|
-
* Contextual URL format: `{parentPath}?__trackId={
|
|
15
|
-
*
|
|
15
|
+
* Contextual URL format: `{parentPath}?__trackId={trackIdentity}&__index={childIndex}` (the
|
|
16
|
+
* identity is the track's `id` when non-blank, else its `src`) Example:
|
|
17
|
+
* "/library/radio?__trackId=song.mp3&__index=2"
|
|
18
|
+
*
|
|
19
|
+
* `__trackId` is the identity check; `__index` (the child's position on the page at stamp time) is
|
|
20
|
+
* only a tie-breaker between surfaces that carry the same identity — a stale index never selects a
|
|
21
|
+
* different track.
|
|
16
22
|
*
|
|
17
23
|
* This allows:
|
|
18
|
-
* - Media3 to reference playable-only tracks (tracks with `src` but no `
|
|
24
|
+
* - Media3 to reference playable-only tracks (tracks with `src` but no `path`)
|
|
19
25
|
* - Cache lookup to work consistently
|
|
20
26
|
* - Parent context to be preserved for queue restoration
|
|
21
27
|
*/
|
|
@@ -23,9 +29,6 @@ object BrowserPathHelper {
|
|
|
23
29
|
/** Root path for media browsing */
|
|
24
30
|
const val ROOT_PATH = "/__root"
|
|
25
31
|
|
|
26
|
-
/** Recent media path for playback resumption */
|
|
27
|
-
const val RECENT_PATH = "/__recent"
|
|
28
|
-
|
|
29
32
|
/** Search path prefix (full path is /__search?q=query) */
|
|
30
33
|
const val SEARCH_PATH_PREFIX = "/__search"
|
|
31
34
|
|
|
@@ -38,12 +41,32 @@ object BrowserPathHelper {
|
|
|
38
41
|
/** Browse Gate placeholder media ID (subscription/login/region block) */
|
|
39
42
|
const val GATE_PATH = "/__gate"
|
|
40
43
|
|
|
44
|
+
/** Empty-page placeholder media ID — a level that resolved with zero children (ADR 0001) */
|
|
45
|
+
const val EMPTY_PATH = "/__empty"
|
|
46
|
+
|
|
41
47
|
// Query parameter name for contextual track identifiers
|
|
42
48
|
private const val CONTEXTUAL_TRACK_PARAM = "__trackId"
|
|
43
49
|
|
|
50
|
+
// Query parameter name for the tapped child's page position (tie-breaker)
|
|
51
|
+
private const val CONTEXTUAL_INDEX_PARAM = "__index"
|
|
52
|
+
|
|
53
|
+
/** Whether a navigation error on this path came from a browse or a search */
|
|
54
|
+
fun navigationErrorKind(path: String): NavigationErrorKind =
|
|
55
|
+
if (path.startsWith("$SEARCH_PATH_PREFIX?")) NavigationErrorKind.SEARCH
|
|
56
|
+
else NavigationErrorKind.BROWSE
|
|
57
|
+
|
|
44
58
|
/** Check if a path is a special system path (not a regular navigation path) */
|
|
45
59
|
fun isSpecialPath(path: String): Boolean {
|
|
46
|
-
return path == ROOT_PATH || path
|
|
60
|
+
return path == ROOT_PATH || path.startsWith("$SEARCH_PATH_PREFIX?")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The tile sentinels a drill-in dead-ends on: Android Auto ignores the tiles' non-browsable flag
|
|
65
|
+
* and subscribes anyway, so these serve no children rather than stacking another page.
|
|
66
|
+
* [GATE_PATH] is not one of them — it re-serves the gate tile so the message stays readable.
|
|
67
|
+
*/
|
|
68
|
+
fun isDeadEndPath(path: String): Boolean {
|
|
69
|
+
return path == OFFLINE_PATH || path == ERROR_PATH || path == EMPTY_PATH
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
/** Create a search path for a given query */
|
|
@@ -63,13 +86,13 @@ object BrowserPathHelper {
|
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
/**
|
|
66
|
-
* Strips the __trackId
|
|
67
|
-
* contextual, returns it unchanged.
|
|
89
|
+
* Strips the contextual parameters (__trackId and __index) from a contextual URL to get the
|
|
90
|
+
* parent path. If the URL is not contextual, returns it unchanged.
|
|
68
91
|
*
|
|
69
92
|
* @param url The URL to process
|
|
70
|
-
* @return The URL without the
|
|
93
|
+
* @return The URL without the contextual parameters
|
|
71
94
|
*
|
|
72
|
-
* Example: "/library/radio?__trackId=song.mp3" → "/library/radio" Example:
|
|
95
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → "/library/radio" Example:
|
|
73
96
|
* "/search?q=jazz&__trackId=song.mp3" → "/search?q=jazz"
|
|
74
97
|
*/
|
|
75
98
|
fun stripTrackId(url: String): String {
|
|
@@ -79,13 +102,13 @@ object BrowserPathHelper {
|
|
|
79
102
|
|
|
80
103
|
val uri = url.toUri()
|
|
81
104
|
|
|
82
|
-
// Build URL preserving everything except
|
|
105
|
+
// Build URL preserving everything except the contextual parameters
|
|
83
106
|
val builder = uri.buildUpon()
|
|
84
107
|
builder.clearQuery()
|
|
85
108
|
|
|
86
|
-
// Re-add all query params except __trackId
|
|
109
|
+
// Re-add all query params except __trackId and __index
|
|
87
110
|
uri.queryParameterNames.forEach { paramName ->
|
|
88
|
-
if (paramName != CONTEXTUAL_TRACK_PARAM) {
|
|
111
|
+
if (paramName != CONTEXTUAL_TRACK_PARAM && paramName != CONTEXTUAL_INDEX_PARAM) {
|
|
89
112
|
uri.getQueryParameters(paramName).forEach { value ->
|
|
90
113
|
builder.appendQueryParameter(paramName, value)
|
|
91
114
|
}
|
|
@@ -96,19 +119,21 @@ object BrowserPathHelper {
|
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
/**
|
|
99
|
-
* Builds a contextual URL by appending a track identifier
|
|
100
|
-
* query parameters correctly.
|
|
122
|
+
* Builds a contextual URL by appending a track identifier — and optionally the tapped child's
|
|
123
|
+
* page position — to a parent path. Handles existing query parameters correctly.
|
|
101
124
|
*
|
|
102
125
|
* @param parentPath The parent container path
|
|
103
|
-
* @param trackId The track
|
|
104
|
-
* @
|
|
126
|
+
* @param trackId The track identity (`id` when non-blank, else `src` — see Track.identity)
|
|
127
|
+
* @param index The child's position on the page at stamp time (tie-breaker)
|
|
128
|
+
* @return A contextual URL combining parent path, track ID, and index
|
|
105
129
|
*
|
|
106
|
-
* Example: build("/library", "song.mp3") → "/library?__trackId=song.mp3" Example:
|
|
130
|
+
* Example: build("/library", "song.mp3", 2) → "/library?__trackId=song.mp3&__index=2" Example:
|
|
107
131
|
* build("/search?q=jazz", "song.mp3") → "/search?q=jazz&__trackId=song.mp3"
|
|
108
132
|
*/
|
|
109
|
-
fun build(parentPath: String, trackId: String): String {
|
|
133
|
+
fun build(parentPath: String, trackId: String, index: Int? = null): String {
|
|
110
134
|
val separator = if (parentPath.contains('?')) '&' else '?'
|
|
111
|
-
|
|
135
|
+
val indexParam = if (index != null) "&$CONTEXTUAL_INDEX_PARAM=$index" else ""
|
|
136
|
+
return "$parentPath$separator$CONTEXTUAL_TRACK_PARAM=${Uri.encode(trackId)}$indexParam"
|
|
112
137
|
}
|
|
113
138
|
|
|
114
139
|
/**
|
|
@@ -129,6 +154,21 @@ object BrowserPathHelper {
|
|
|
129
154
|
return uri.getQueryParameter(CONTEXTUAL_TRACK_PARAM)
|
|
130
155
|
}
|
|
131
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Extracts the stamped page index from a contextual URL, or null when the URL is not contextual
|
|
159
|
+
* or carries no (valid, non-negative) index.
|
|
160
|
+
*
|
|
161
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → 2
|
|
162
|
+
*/
|
|
163
|
+
fun extractIndex(path: String): Int? {
|
|
164
|
+
if (!isContextual(path)) {
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
val raw = Uri.parse(path).getQueryParameter(CONTEXTUAL_INDEX_PARAM) ?: return null
|
|
169
|
+
return raw.toIntOrNull()?.takeIf { it >= 0 }
|
|
170
|
+
}
|
|
171
|
+
|
|
132
172
|
/**
|
|
133
173
|
* Combines a base URL with a path, ensuring proper slash handling.
|
|
134
174
|
*
|
|
@@ -158,22 +198,4 @@ object BrowserPathHelper {
|
|
|
158
198
|
val normalizedPath = path.trimStart('/')
|
|
159
199
|
return "$normalizedBase$normalizedPath"
|
|
160
200
|
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* True if [segment] appears in [path] as a complete path segment — bounded on the left by the
|
|
164
|
-
* string start or `/`, and on the right by the string end or one of `/`, `?`, `#`.
|
|
165
|
-
*/
|
|
166
|
-
fun containsSegment(path: String, segment: String): Boolean {
|
|
167
|
-
if (segment.isEmpty()) return false
|
|
168
|
-
var from = 0
|
|
169
|
-
while (true) {
|
|
170
|
-
val idx = path.indexOf(segment, from)
|
|
171
|
-
if (idx < 0) return false
|
|
172
|
-
val beforeOk = idx == 0 || path[idx - 1] == '/'
|
|
173
|
-
val end = idx + segment.length
|
|
174
|
-
val afterOk = end == path.length || path[end] == '/' || path[end] == '?' || path[end] == '#'
|
|
175
|
-
if (beforeOk && afterOk) return true
|
|
176
|
-
from = idx + 1
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
201
|
}
|