react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -21
- package/android/build.gradle +35 -12
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
- package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
- package/android/src/main/res/values/strings.xml +25 -5
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
- package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +230 -205
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/SearchDrillIn.swift +7 -7
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
- package/ios/Extension/Section+Copying.swift +29 -0
- package/ios/Extension/String+IcyCharset.swift +21 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
- package/ios/Extension/Track+Copying.swift +8 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +253 -100
- package/ios/Model/NitroTypeStubs.swift +125 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +17 -9
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +46 -13
- package/ios/Player/RetryManager.swift +80 -18
- package/ios/Player/SeekCompletionHandler.swift +3 -1
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +122 -10
- package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryManagerTests.swift +89 -0
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +8 -1
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackPlayer.swift +63 -35
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/LRUCache.swift +4 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/AudioBrowser.js +8 -8
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +12 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +11 -3
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +14 -11
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +3 -2
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +3 -4
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +18 -9
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +23 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +15 -4
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +13 -6
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +175 -100
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +18 -23
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +2 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +12 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +11 -3
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +14 -11
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +3 -2
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/features/playback/state.js +3 -4
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +17 -8
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +19 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +15 -4
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +13 -6
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +28 -14
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +200 -75
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +254 -39
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +175 -100
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +18 -23
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +10 -2
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +14 -11
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +3 -4
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +108 -56
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +7 -4
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +3 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +82 -57
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
- package/nitrogen/generated/android/c++/JSection.hpp +116 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
- package/nitrogen/generated/ios/swift/Section.swift +82 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +28 -63
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
- package/nitrogen/generated/shared/c++/Section.hpp +102 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +18 -30
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +24 -11
- package/src/AudioBrowser.ts +2 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +12 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +11 -3
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +14 -11
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +302 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +3 -2
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +3 -4
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +1 -6
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +126 -76
- package/src/features/player/setup.test.ts +24 -7
- package/src/features/player/setup.ts +22 -10
- package/src/features/player/validateOptions.ts +21 -0
- package/src/features/queue/activeTrack.ts +15 -4
- package/src/features/queue/queue.ts +13 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +147 -0
- package/src/specs/audio-browser.nitro.ts +5 -4
- package/src/style-resolution.test.ts +210 -0
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +7 -3
- package/src/types/browser-nodes.ts +396 -134
- package/src/types/browser.ts +85 -59
- package/src/utils/LazyNativeEmitter.ts +28 -15
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +239 -82
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +284 -37
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +209 -111
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/SearchManager.test.ts +75 -0
- package/src/web/browser/SearchManager.ts +25 -28
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
- package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +247 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
* 1. System paths (prefixed with `/__`): Root, recent, and search paths
|
|
7
7
|
* 2. Contextual URLs: Embed parent context in track identifiers for Media3 integration
|
|
8
8
|
*
|
|
9
|
-
* Contextual URL format: `{parentPath}?__trackId={
|
|
10
|
-
* Example: "/library/radio?__trackId=song.mp3"
|
|
9
|
+
* Contextual URL format: `{parentPath}?__trackId={trackIdentity}&__index={childIndex}`
|
|
10
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2"
|
|
11
|
+
*
|
|
12
|
+
* `__trackId` is the identity check; `__index` (the child's position on the
|
|
13
|
+
* page at stamp time) is only a tie-breaker between surfaces that carry the
|
|
14
|
+
* same identity — a stale index never selects a different track.
|
|
11
15
|
*
|
|
12
16
|
* This allows:
|
|
13
17
|
* - Media3 to reference playable-only tracks (tracks with `src` but no `url`)
|
|
@@ -21,6 +25,9 @@ export const BrowserPathHelper = {
|
|
|
21
25
|
/** Query parameter name for contextual track identifiers */
|
|
22
26
|
CONTEXTUAL_TRACK_PARAM: '__trackId',
|
|
23
27
|
|
|
28
|
+
/** Query parameter name for the tapped child's page position (tie-breaker) */
|
|
29
|
+
CONTEXTUAL_INDEX_PARAM: '__index',
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* Create a search path for a given query
|
|
26
33
|
*/
|
|
@@ -43,13 +50,13 @@ export const BrowserPathHelper = {
|
|
|
43
50
|
},
|
|
44
51
|
|
|
45
52
|
/**
|
|
46
|
-
* Strips the __trackId
|
|
47
|
-
* If the URL is not contextual, returns it unchanged.
|
|
53
|
+
* Strips the contextual parameters (__trackId and __index) from a contextual
|
|
54
|
+
* URL to get the parent path. If the URL is not contextual, returns it unchanged.
|
|
48
55
|
*
|
|
49
56
|
* @param url The URL to process
|
|
50
|
-
* @returns The URL without the
|
|
57
|
+
* @returns The URL without the contextual parameters
|
|
51
58
|
*
|
|
52
|
-
* Example: "/library/radio?__trackId=song.mp3" → "/library/radio"
|
|
59
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → "/library/radio"
|
|
53
60
|
* Example: "/search?q=jazz&__trackId=song.mp3" → "/search?q=jazz"
|
|
54
61
|
*/
|
|
55
62
|
stripTrackId(url: string): string {
|
|
@@ -62,8 +69,9 @@ export const BrowserPathHelper = {
|
|
|
62
69
|
const isFullUrl = url.startsWith('http://') || url.startsWith('https://')
|
|
63
70
|
const urlObj = new URL(url, isFullUrl ? undefined : 'http://placeholder')
|
|
64
71
|
|
|
65
|
-
// Remove the
|
|
72
|
+
// Remove the contextual parameters
|
|
66
73
|
urlObj.searchParams.delete(this.CONTEXTUAL_TRACK_PARAM)
|
|
74
|
+
urlObj.searchParams.delete(this.CONTEXTUAL_INDEX_PARAM)
|
|
67
75
|
|
|
68
76
|
// Return the appropriate format
|
|
69
77
|
if (isFullUrl) {
|
|
@@ -75,7 +83,7 @@ export const BrowserPathHelper = {
|
|
|
75
83
|
} catch {
|
|
76
84
|
// Fallback: simple string manipulation
|
|
77
85
|
const paramPattern = new RegExp(
|
|
78
|
-
`[?&]
|
|
86
|
+
`[?&](?:${this.CONTEXTUAL_TRACK_PARAM}|${this.CONTEXTUAL_INDEX_PARAM})=[^&]*`,
|
|
79
87
|
'g'
|
|
80
88
|
)
|
|
81
89
|
let result = url.replace(paramPattern, '')
|
|
@@ -87,6 +95,24 @@ export const BrowserPathHelper = {
|
|
|
87
95
|
}
|
|
88
96
|
},
|
|
89
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Builds a contextual URL by appending a track identifier — and optionally
|
|
100
|
+
* the tapped child's page position — to a parent path.
|
|
101
|
+
*
|
|
102
|
+
* @param parentPath The parent container path
|
|
103
|
+
* @param trackId The track identity (`id` when non-blank, else `src`)
|
|
104
|
+
* @param index The child's position on the page at stamp time (tie-breaker)
|
|
105
|
+
* @returns A contextual URL combining parent path, track ID, and index
|
|
106
|
+
*
|
|
107
|
+
* Example: build("/library", "song.mp3", 2) → "/library?__trackId=song.mp3&__index=2"
|
|
108
|
+
*/
|
|
109
|
+
build(parentPath: string, trackId: string, index?: number): string {
|
|
110
|
+
const separator = parentPath.includes('?') ? '&' : '?'
|
|
111
|
+
const indexParam =
|
|
112
|
+
index === undefined ? '' : `&${this.CONTEXTUAL_INDEX_PARAM}=${index}`
|
|
113
|
+
return `${parentPath}${separator}${this.CONTEXTUAL_TRACK_PARAM}=${encodeURIComponent(trackId)}${indexParam}`
|
|
114
|
+
},
|
|
115
|
+
|
|
90
116
|
/**
|
|
91
117
|
* Extracts the track ID from a contextual URL.
|
|
92
118
|
* Returns undefined if the URL is not contextual or doesn't contain the track ID parameter.
|
|
@@ -100,18 +126,40 @@ export const BrowserPathHelper = {
|
|
|
100
126
|
if (!this.isContextual(path)) {
|
|
101
127
|
return undefined
|
|
102
128
|
}
|
|
129
|
+
return this.extractParam(path, this.CONTEXTUAL_TRACK_PARAM)
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extracts the stamped page index from a contextual URL, or undefined when
|
|
134
|
+
* the URL is not contextual or carries no (valid, non-negative) index.
|
|
135
|
+
*
|
|
136
|
+
* Example: "/library/radio?__trackId=song.mp3&__index=2" → 2
|
|
137
|
+
*/
|
|
138
|
+
extractIndex(path: string): number | undefined {
|
|
139
|
+
if (!this.isContextual(path)) {
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const raw = this.extractParam(path, this.CONTEXTUAL_INDEX_PARAM)
|
|
144
|
+
if (raw === undefined || !/^\d+$/.test(raw)) {
|
|
145
|
+
return undefined
|
|
146
|
+
}
|
|
147
|
+
return Number.parseInt(raw, 10)
|
|
148
|
+
},
|
|
103
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Extracts a single query parameter, handling both full URLs and paths,
|
|
152
|
+
* with a regex fallback for input the URL parser rejects.
|
|
153
|
+
*/
|
|
154
|
+
extractParam(path: string, param: string): string | undefined {
|
|
104
155
|
try {
|
|
105
|
-
// Parse the URL - handle both full URLs and paths
|
|
106
156
|
const isFullUrl =
|
|
107
157
|
path.startsWith('http://') || path.startsWith('https://')
|
|
108
158
|
const urlObj = new URL(path, isFullUrl ? undefined : 'http://placeholder')
|
|
109
|
-
return urlObj.searchParams.get(
|
|
159
|
+
return urlObj.searchParams.get(param) ?? undefined
|
|
110
160
|
} catch {
|
|
111
161
|
// Fallback: regex extraction
|
|
112
|
-
const match = path.match(
|
|
113
|
-
new RegExp(`[?&]${this.CONTEXTUAL_TRACK_PARAM}=([^&]*)`)
|
|
114
|
-
)
|
|
162
|
+
const match = path.match(new RegExp(`[?&]${param}=([^&]*)`))
|
|
115
163
|
return match?.[1] ? decodeURIComponent(match[1]) : undefined
|
|
116
164
|
}
|
|
117
165
|
},
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Put your browse tree and playback on the Android Auto car screen: the automotive app descriptor, the manifest entries the library merges in, and "Hey Google, play…" voice search.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Android Auto
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Android Auto shows your [browse tree](/guide/basic-usage) on the car screen and plays through the same player as your app. Two systems drive it:
|
|
10
|
+
|
|
11
|
+
1. **Browse tree** — Android Auto reads your content from a `MediaBrowserService`, which mirrors your browser routes. The library ships this service.
|
|
12
|
+
2. **Voice search** — "Hey Google, play…" arrives as a `MEDIA_PLAY_FROM_SEARCH` intent, which the library routes to your `search` source.
|
|
13
|
+
|
|
14
|
+
Unlike CarPlay — which needs entitlements and scene delegates — Android Auto needs almost no app-side code. The library's own `AndroidManifest.xml` declares the media-browser service, the media session, the voice-search intent filter, and the foreground-service permissions, and those **merge into your app automatically**. You only have to declare your app as an automotive media app.
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
### 1. Declare the automotive app descriptor
|
|
19
|
+
|
|
20
|
+
Add this `<meta-data>` inside the `<application>` tag of your app's `AndroidManifest.xml`:
|
|
21
|
+
|
|
22
|
+
```xml
|
|
23
|
+
<meta-data
|
|
24
|
+
android:name="com.google.android.gms.car.application"
|
|
25
|
+
android:resource="@xml/automotive_app_desc" />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. Add the descriptor resource
|
|
29
|
+
|
|
30
|
+
Create `android/app/src/main/res/xml/automotive_app_desc.xml`:
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<automotiveApp>
|
|
34
|
+
<uses name="media"/>
|
|
35
|
+
</automotiveApp>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That's the whole setup. The `MediaBrowserService`, media session, `MEDIA_PLAY_FROM_SEARCH` intent filter, headless service for background playback, and `FOREGROUND_SERVICE` / `FOREGROUND_SERVICE_MEDIA_PLAYBACK` permissions all come from the library's merged manifest — you don't declare them yourself.
|
|
39
|
+
|
|
40
|
+
## Voice search
|
|
41
|
+
|
|
42
|
+
Google Assistant requests ("play some jazz", "play the radishes") arrive as a `MEDIA_PLAY_FROM_SEARCH` intent. The library normalizes it into the same structured `SearchParams` your in-app and Siri searches use and calls your `search` source — no extra manifest setup, since the library declares the intent filter.
|
|
43
|
+
|
|
44
|
+
Android sends a subset of what iOS does: the query plus music focuses (genre / artist / album), always with `reference: 'unknown'`. See [Search](/guide/search) for how to resolve these requests and the cross-platform differences.
|
|
45
|
+
|
|
46
|
+
## Browse display
|
|
47
|
+
|
|
48
|
+
Android Auto reads a page's layout from the **parent item** that opens it — before the page resolves. So the layout is declared in two places: on the parent item (a tab, or a row on another page) and on the page itself.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// The parent item — a tab, or a row on another page. Android Auto
|
|
52
|
+
// reads the layout from here, before the page resolves.
|
|
53
|
+
{
|
|
54
|
+
title: 'Stations',
|
|
55
|
+
path: '/browse/stations',
|
|
56
|
+
style: { display: 'grid' }, // layout of the page this opens ('list' is the default)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The page it opens — the ResolvedTrack served for '/browse/stations':
|
|
60
|
+
{
|
|
61
|
+
path: '/browse/stations',
|
|
62
|
+
title: 'Stations',
|
|
63
|
+
style: { display: 'grid' }, // the page-wide declaration
|
|
64
|
+
children: [ /* the tiles */ ]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **`style.display` on the parent item** — `'list'` (rows) or `'grid'` (tiles). Below the root this is the only layout signal Android Auto honors, and it is only sent when you declare it — leave it off and the page renders as a list. In dev mode the library warns when an all-grid page is opened from a parent item with no declared `display`. (A section's "view all" page needs no declaration of its own: it reuses the section's `style`.)
|
|
69
|
+
- **Sections** — declare `sections` on the page to group its tracks under headers; each [`Section`](/api/types/browser-nodes/#section)'s `title` renders as a header above its children. A section that carries a `path` also gets a browsable "view more" row appended after its children, navigating there. No header is tappable on Android Auto at any `display`, so the row is the only affordance the section's `path` has here; its title comes from the [`viewMoreTitle`](/api/types/browser/#viewmoretitle) browser option, defaulting to `"View more"` — one wording for every section. For per-section copy (`"View all 42 albums"`), leave `path` unset and append your own browsable `Track` to `children` instead, titled however you like; this page's content is Android-only anyway, so CarPlay never sees it.
|
|
70
|
+
|
|
71
|
+
::: warning AAOS lays out the whole page from the parent item's `display`
|
|
72
|
+
On AOSP-based Android Automotive units — including mainstream cars' OEM skins — the parent item's `display` is the **only** below-root layout signal honored: the whole page renders as list or grid, uniformly. Per-section `display` differences (a single-line grid above a list, say) render on CarPlay and projected Android Auto, but an AAOS unit shows the page in the single declared layout. There is no way to describe a mixed page there — pick the layout that suits its dominant section, or split the content across pages. `gridWrap: false` also has no single-line container there: the grid wraps, showing more, never less.
|
|
73
|
+
:::
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
sections: [
|
|
77
|
+
{
|
|
78
|
+
title: 'Live now',
|
|
79
|
+
children: [
|
|
80
|
+
{ title: 'Morning Show', src: '…' },
|
|
81
|
+
{ title: 'Afternoon Drive', src: '…' }
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
{ title: 'Up next', children: [{ title: 'Late Night Jazz', src: '…' }] }
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Now Playing buttons
|
|
89
|
+
|
|
90
|
+
The buttons on the Now Playing screen come from the same
|
|
91
|
+
[`remoteButtonLayout`](/guide/remote-controls#button-layout-android) that drives the
|
|
92
|
+
notification — there's no car-specific option. Two behaviors are particular to
|
|
93
|
+
the car, though:
|
|
94
|
+
|
|
95
|
+
- **The head unit has the last word.** Slots are a preference. A unit with a
|
|
96
|
+
spare position may promote the first `overflow` entry onto the main row, so
|
|
97
|
+
a button you placed in overflow can still appear beside play/pause.
|
|
98
|
+
- **Fewer buttons, cleaner row.** If you want just
|
|
99
|
+
`jump-back │ play/pause │ jump-forward`, leave the rest out of the layout
|
|
100
|
+
entirely — they keep working from the steering wheel and headset regardless.
|
|
101
|
+
|
|
102
|
+
## Testing
|
|
103
|
+
|
|
104
|
+
Test in a real car, or in Google's **Desktop Head Unit (DHU)** emulator:
|
|
105
|
+
|
|
106
|
+
1. Install the **Android Auto Desktop Head Unit emulator** from the SDK Manager (_SDK Tools → Android Auto Desktop Head Unit emulator_).
|
|
107
|
+
2. In the phone's **Android Auto** settings, enable Developer Mode (tap the version 10×), then **"Start head unit server"**.
|
|
108
|
+
3. Connect the phone over USB and run the DHU.
|
|
109
|
+
|
|
110
|
+
See Google's [Test Android Auto apps with DHU](https://developer.android.com/training/cars/testing/dhu) for the full walkthrough. Your app appears under the media apps — browse the tree, exercise voice search, and verify artwork, grid/list styles, and section headers.
|
|
111
|
+
|
|
112
|
+
## Reference
|
|
113
|
+
|
|
114
|
+
See the [example app](https://github.com/radio-garden/react-native-audio-browser/tree/main/apps/example-native/android) for a complete working manifest and descriptor.
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Fix Android SSLHandshakeException / "Trust anchor for certification path not found" on HTTPS streams whose server omits its intermediate certificate.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Android: SSL "Trust anchor for certification path not found" (missing intermediate certificates)
|
|
6
|
+
|
|
7
|
+
**If Android playback of a radio stream (or any HTTPS audio source) fails with an
|
|
8
|
+
`SSLHandshakeException` / `CertPathValidatorException` / "Trust anchor for
|
|
9
|
+
certification path not found" — and the same stream plays fine on iOS and in a
|
|
10
|
+
desktop browser — you are in the right place.** This is almost always a server
|
|
11
|
+
that omits an intermediate certificate; the fix is to let the client chase the
|
|
12
|
+
missing certificate via the leaf's Authority Information Access (AIA) pointer, and
|
|
13
|
+
this guide shows the one-time Android setup that does it.
|
|
14
|
+
|
|
15
|
+
## The problem
|
|
16
|
+
|
|
17
|
+
Some HTTPS servers are misconfigured to send only their **leaf** certificate and
|
|
18
|
+
omit the **intermediate** certificate(s) needed to chain up to a trusted root. A
|
|
19
|
+
correctly configured server sends the full chain; a misconfigured one leaves the
|
|
20
|
+
client to find the gap on its own.
|
|
21
|
+
|
|
22
|
+
- **iOS and web browsers** recover automatically: they follow the leaf's
|
|
23
|
+
Authority Information Access (AIA) "CA Issuers" pointer to fetch the missing
|
|
24
|
+
intermediate. (This is why the stream works everywhere except Android.)
|
|
25
|
+
- **Android's default trust manager does not.** It validates only what the server
|
|
26
|
+
sent, so playback of such a stream fails with:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
javax.net.ssl.SSLHandshakeException:
|
|
30
|
+
java.security.cert.CertPathValidatorException:
|
|
31
|
+
Trust anchor for certification path not found.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This affects media playback because the Android player transports audio over
|
|
35
|
+
`HttpURLConnection`, which uses the process-wide default `SSLSocketFactory`.
|
|
36
|
+
|
|
37
|
+
> **iOS needs no setup.** Apple's TLS stack chases AIA intermediates on its own,
|
|
38
|
+
> so there is nothing to configure on iOS — this guide is Android-only.
|
|
39
|
+
|
|
40
|
+
## Is this actually your problem?
|
|
41
|
+
|
|
42
|
+
Before changing process-global TLS state, confirm the failing stream really is a
|
|
43
|
+
missing-intermediate case and not some other TLS failure (an expired cert, an SNI
|
|
44
|
+
mismatch, or a genuinely untrusted root — none of which this fix addresses).
|
|
45
|
+
|
|
46
|
+
Run, against your failing stream's **host** — use just the hostname, not the full
|
|
47
|
+
stream URL. If your stream URL is `https://example.com:8000/live.mp3`, the host is
|
|
48
|
+
`example.com` and the port is `8000` (omit the path); plain `https://` URLs with no
|
|
49
|
+
port use `443`:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
openssl s_client -connect example.com:443 \
|
|
53
|
+
-servername example.com -showcerts </dev/null
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
In the output:
|
|
57
|
+
|
|
58
|
+
- A **complete** chain lists two or more certificates under
|
|
59
|
+
`Certificate chain` (leaf `s:.../CN=host` then one or more issuers `i:...`) and
|
|
60
|
+
ends with `Verify return code: 0 (ok)`.
|
|
61
|
+
- A **missing-intermediate** server typically shows **only one** certificate in
|
|
62
|
+
the chain and a verify error such as
|
|
63
|
+
`Verify return code: 21 (unable to verify the first certificate)` — yet the
|
|
64
|
+
leaf's AIA block contains a `CA Issuers - URI:` pointer. That combination is the
|
|
65
|
+
exact case this fix repairs.
|
|
66
|
+
|
|
67
|
+
Prefer a browser? Paste the host into the [SSL Labs server
|
|
68
|
+
test](https://www.ssllabs.com/ssltest/) — it flags **"Chain issues: Incomplete"**
|
|
69
|
+
for this exact problem — or use [whatsmychaincert.com](https://whatsmychaincert.com/).
|
|
70
|
+
|
|
71
|
+
> **Want to see a broken chain first?** `incomplete-chain.badssl.com` is a public
|
|
72
|
+
> test host that deliberately omits its intermediate — the canonical example of
|
|
73
|
+
> this misconfiguration. Run the command above against
|
|
74
|
+
> `incomplete-chain.badssl.com:443` to see exactly what the "one certificate +
|
|
75
|
+
> verify error + AIA pointer present" signature looks like before you go hunting in
|
|
76
|
+
> your own stream.
|
|
77
|
+
|
|
78
|
+
If the chain is complete, or there is no `CA Issuers` AIA pointer, this fix will
|
|
79
|
+
**not** help — see [Caveats](#caveats).
|
|
80
|
+
|
|
81
|
+
## Opt in
|
|
82
|
+
|
|
83
|
+
The library ships an **additive** AIA-chasing `SSLSocketFactory` but does **not**
|
|
84
|
+
install it for you — installing it sets process-global TLS state, which is a
|
|
85
|
+
decision for the host application to make, not a library. Enable it by setting it
|
|
86
|
+
as the default in your **existing** `MainApplication`'s `onCreate`, before any
|
|
87
|
+
playback begins. `MainApplication.onCreate` runs before React Native loads and
|
|
88
|
+
executes your JS bundle, so it is always early enough — no playback can have
|
|
89
|
+
started yet.
|
|
90
|
+
|
|
91
|
+
> Add the highlighted line to the `MainApplication` you already have (it lives at
|
|
92
|
+
> `android/app/src/main/java/.../MainApplication.kt` or `.java`). **Don't create a
|
|
93
|
+
> new file or a second `MainApplication` class** — just drop the call into the
|
|
94
|
+
> `onCreate` you already have, after `super.onCreate()`.
|
|
95
|
+
|
|
96
|
+
::: code-group
|
|
97
|
+
|
|
98
|
+
```kotlin [MainApplication.kt]
|
|
99
|
+
import com.audiobrowser.tls.AiaTls
|
|
100
|
+
import javax.net.ssl.HttpsURLConnection
|
|
101
|
+
import javax.net.ssl.SSLSocketFactory
|
|
102
|
+
import android.util.Log
|
|
103
|
+
|
|
104
|
+
class MainApplication : Application() {
|
|
105
|
+
override fun onCreate() {
|
|
106
|
+
super.onCreate()
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
val aiaFactory = AiaTls.socketFactory() // [!code ++]
|
|
110
|
+
HttpsURLConnection.setDefaultSSLSocketFactory(aiaFactory) // [!code ++]
|
|
111
|
+
installedSslFactory = aiaFactory // [!code ++]
|
|
112
|
+
} catch (e: Exception) {
|
|
113
|
+
// Never let TLS setup block startup; playback keeps default behaviour.
|
|
114
|
+
Log.w("MainApplication", "Failed to install AIA-chasing TLS", e)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ... the rest of your existing setup
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
companion object {
|
|
121
|
+
/** Kept so you can confirm the install — see "Verify it worked". */
|
|
122
|
+
var installedSslFactory: SSLSocketFactory? = null
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```java [MainApplication.java]
|
|
128
|
+
import com.audiobrowser.tls.AiaTls;
|
|
129
|
+
import javax.net.ssl.HttpsURLConnection;
|
|
130
|
+
import javax.net.ssl.SSLSocketFactory;
|
|
131
|
+
import android.util.Log;
|
|
132
|
+
|
|
133
|
+
public class MainApplication extends Application {
|
|
134
|
+
/** Kept so you can confirm the install — see "Verify it worked". */
|
|
135
|
+
public static SSLSocketFactory installedSslFactory = null;
|
|
136
|
+
|
|
137
|
+
@Override
|
|
138
|
+
public void onCreate() {
|
|
139
|
+
super.onCreate();
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
SSLSocketFactory aiaFactory = AiaTls.socketFactory(); // [!code ++]
|
|
143
|
+
HttpsURLConnection.setDefaultSSLSocketFactory(aiaFactory); // [!code ++]
|
|
144
|
+
installedSslFactory = aiaFactory; // [!code ++]
|
|
145
|
+
} catch (Exception e) {
|
|
146
|
+
// Never let TLS setup block startup; playback keeps default behaviour.
|
|
147
|
+
Log.w("MainApplication", "Failed to install AIA-chasing TLS", e);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ... the rest of your existing setup
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
:::
|
|
156
|
+
|
|
157
|
+
That's all. The player's data source picks up the process default automatically.
|
|
158
|
+
`AiaTls` ships with the `react-native-audio-browser` Android module you already
|
|
159
|
+
installed — the `com.audiobrowser.tls` import resolves with no extra Gradle
|
|
160
|
+
dependency.
|
|
161
|
+
|
|
162
|
+
> **Rebuild after adding this.** `AiaTls` is native Kotlin, so a JS-only reload
|
|
163
|
+
> won't pick up the new call — do a full native rebuild:
|
|
164
|
+
>
|
|
165
|
+
> ```sh
|
|
166
|
+
> npx react-native run-android
|
|
167
|
+
> # if the build seems stale, clear Gradle first:
|
|
168
|
+
> cd android && ./gradlew clean && cd .. && npx react-native run-android
|
|
169
|
+
> ```
|
|
170
|
+
>
|
|
171
|
+
> No manifest or permission change is needed — RN apps already hold the `INTERNET`
|
|
172
|
+
> permission the AIA fetch uses, and the cleartext fetch is handled for you (see
|
|
173
|
+
> [The cleartext fetch is handled for you](#the-cleartext-fetch-is-handled-for-you)).
|
|
174
|
+
|
|
175
|
+
## The cleartext fetch is handled for you
|
|
176
|
+
|
|
177
|
+
The completed-chain check happens over HTTPS — only the side fetch of the _missing
|
|
178
|
+
intermediate_ uses the leaf's `CA Issuers` URL, which CAs publish over plain
|
|
179
|
+
`http://` (mandated by the CA/Browser Forum Baseline Requirements, to avoid a
|
|
180
|
+
chicken-and-egg TLS dependency when fetching the very certificate needed to
|
|
181
|
+
complete a TLS chain).
|
|
182
|
+
|
|
183
|
+
That's a problem on most modern apps: targeting Android 9+ (API 28) **blocks
|
|
184
|
+
cleartext HTTP by default**, which would silently break the fetch on release
|
|
185
|
+
builds. So the library doesn't use Android's HTTP stack for it — the `http`
|
|
186
|
+
intermediate fetch goes over a **raw socket**, which isn't subject to your app's
|
|
187
|
+
`usesCleartextTraffic` / network-security-config policy. **You don't need to enable
|
|
188
|
+
cleartext or add a network security config.**
|
|
189
|
+
|
|
190
|
+
This stays safe: the fetched certificate is cryptographically verified to be the
|
|
191
|
+
genuine issuer and re-validated against the system trust anchors before use, so a
|
|
192
|
+
tampered response over plain HTTP can only fail the chain, never weaken it.
|
|
193
|
+
|
|
194
|
+
## Verify it worked
|
|
195
|
+
|
|
196
|
+
A silent success is hard to tell apart from "my problem was something else," so
|
|
197
|
+
confirm it explicitly:
|
|
198
|
+
|
|
199
|
+
1. **Confirm the factory is installed.** The `catch` above only logs on _failure_,
|
|
200
|
+
so **no warning at startup means it installed without throwing** — enough for a
|
|
201
|
+
simple app. But it is _not_ enough if your app uses other networking libraries:
|
|
202
|
+
`setDefaultSSLSocketFactory` is last-write-wins (see [Caveats](#caveats)), so
|
|
203
|
+
another library setting its own default afterward silently deactivates the fix
|
|
204
|
+
with no warning. The comparison below is the **only reliable check** in that
|
|
205
|
+
case. `AiaTls.socketFactory()` returns a stock TLS socket factory configured
|
|
206
|
+
with the AIA-chasing trust manager, so its _class name_ looks identical to the
|
|
207
|
+
platform default — don't try to recognise it by class. Instead compare the
|
|
208
|
+
process default against the reference the opt-in snippet stored in
|
|
209
|
+
`installedSslFactory`. Put this **after** the install `try`/`catch` (so it
|
|
210
|
+
reflects the real process default, not the value you just set), anywhere that
|
|
211
|
+
runs after startup:
|
|
212
|
+
|
|
213
|
+
::: code-group
|
|
214
|
+
|
|
215
|
+
```kotlin [Kotlin]
|
|
216
|
+
// place OUTSIDE the install try/catch — this tests the live process default
|
|
217
|
+
val active = HttpsURLConnection.getDefaultSSLSocketFactory() ===
|
|
218
|
+
MainApplication.installedSslFactory
|
|
219
|
+
Log.i("MainApplication", "AIA TLS active: $active")
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```java [Java]
|
|
223
|
+
// place OUTSIDE the install try/catch — this tests the live process default
|
|
224
|
+
boolean active = HttpsURLConnection.getDefaultSSLSocketFactory()
|
|
225
|
+
== MainApplication.installedSslFactory;
|
|
226
|
+
Log.i("MainApplication", "AIA TLS active: " + active);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
:::
|
|
230
|
+
|
|
231
|
+
2. **Confirm playback recovers.** Play the exact stream that previously failed.
|
|
232
|
+
It should now start, and the
|
|
233
|
+
`SSLHandshakeException: ... Trust anchor for certification path not found`
|
|
234
|
+
should no longer appear in `adb logcat`. No real broken stream on hand? Point
|
|
235
|
+
the player at a known-broken-chain source such as
|
|
236
|
+
`https://incomplete-chain.badssl.com/` to prove the wiring end-to-end before you
|
|
237
|
+
rely on it. Keep one of these around as a regression check for future upgrades.
|
|
238
|
+
|
|
239
|
+
### Still failing after install?
|
|
240
|
+
|
|
241
|
+
Work through these in order:
|
|
242
|
+
|
|
243
|
+
1. **Is it even a missing-intermediate case?** Re-run the
|
|
244
|
+
[diagnosis](#is-this-actually-your-problem). If the chain is complete, there's
|
|
245
|
+
no `CA Issuers` AIA pointer, or the root is genuinely untrusted/expired, this
|
|
246
|
+
fix can't repair it — that's a different problem.
|
|
247
|
+
2. **Did the AIA fetch fail?** When the chaser tries to fetch the intermediate and
|
|
248
|
+
can't, the library logs a warning:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
AIA CA-issuer fetch failed: http://…/issuer.crt
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Filter `adb logcat` for `AIA CA-issuer fetch failed`. If it appears, the fetch
|
|
255
|
+
was attempted but couldn't be reached — see the next point. (You do _not_ need
|
|
256
|
+
to enable cleartext; the library's fetch already bypasses that policy.)
|
|
257
|
+
|
|
258
|
+
::: warning The logger starts with the playback service
|
|
259
|
+
The library plants its logging trees when its service first starts, so a fetch
|
|
260
|
+
that fails **before** any playback has begun logs nothing — even though the
|
|
261
|
+
socket factory is process-wide and already in use. If you see no line at all,
|
|
262
|
+
start playback once and retry, or plant your own tree at startup.
|
|
263
|
+
:::
|
|
264
|
+
|
|
265
|
+
3. **Reachable from your environment?** The fetch is a plain outbound request to
|
|
266
|
+
the `CA Issuers - URI` during the handshake, so a corporate proxy, VPN,
|
|
267
|
+
firewall, or a locked-down emulator network can block it. Confirm the URL
|
|
268
|
+
resolves by fetching it yourself — `curl -I "http://…/issuer.crt"` — from the
|
|
269
|
+
same network the device is on.
|
|
270
|
+
4. **Custom Network Security Config?** If your app ships a
|
|
271
|
+
`res/xml/network_security_config.xml` with custom trust anchors or pinning,
|
|
272
|
+
that's an unrelated cause of TLS failures — this fix won't override it.
|
|
273
|
+
|
|
274
|
+
## How it works
|
|
275
|
+
|
|
276
|
+
`AiaTls.socketFactory()` wraps the **platform default** trust manager. On the
|
|
277
|
+
happy path the default accepts the server-presented chain on the first try and
|
|
278
|
+
nothing extra happens. Only when the default _rejects_ a chain does it:
|
|
279
|
+
|
|
280
|
+
1. read the leaf's AIA "CA Issuers" URL,
|
|
281
|
+
2. fetch the missing intermediate (over a raw socket for `http` URLs, so it works
|
|
282
|
+
regardless of your cleartext policy; cached for the process lifetime),
|
|
283
|
+
3. re-validate the completed chain **against the same system trust anchors**.
|
|
284
|
+
|
|
285
|
+
A fetched certificate is used only if it is genuinely the issuer of the preceding
|
|
286
|
+
one. Because the final decision is still the system trust manager's, this can
|
|
287
|
+
**only ever add a missing intermediate — it can never weaken trust**. Untrusted,
|
|
288
|
+
expired, or self-signed roots still fail exactly as before.
|
|
289
|
+
|
|
290
|
+
## Caveats
|
|
291
|
+
|
|
292
|
+
- **Scope.** `setDefaultSSLSocketFactory` is process-wide, so it affects every
|
|
293
|
+
`HttpURLConnection`-based TLS connection in your app, not only playback. (It
|
|
294
|
+
does not affect OkHttp or a `WebView`, which keep their own TLS stacks.) It is
|
|
295
|
+
also last-write-wins: if other code sets its own default afterwards, it
|
|
296
|
+
overrides this one.
|
|
297
|
+
- **First-connection latency on broken servers.** When a server omits its
|
|
298
|
+
intermediate, the first connection performs one extra HTTP request for the
|
|
299
|
+
intermediate during the TLS handshake. Well-configured servers pay no cost. The
|
|
300
|
+
fetched intermediate is cached for the **process lifetime only**, so this
|
|
301
|
+
one-time cost recurs once per cold start for each broken server.
|
|
302
|
+
- **Not a cure-all.** It only rescues servers that omit an intermediate _and_
|
|
303
|
+
publish an AIA "CA Issuers" pointer that resolves to a trusted root. Servers
|
|
304
|
+
with no AIA extension, an unreachable intermediate, or a genuinely untrusted
|
|
305
|
+
root still fail.
|
|
306
|
+
- **The AIA fetch needs outbound network reachability.** Completing the chain
|
|
307
|
+
performs a plain HTTP request to the `CA Issuers` host during the handshake; a
|
|
308
|
+
proxy, VPN, or firewall that blocks it makes the fix look broken even on a
|
|
309
|
+
genuinely missing-intermediate server. See
|
|
310
|
+
[Still failing after install?](#still-failing-after-install) to diagnose.
|
|
311
|
+
- **Unrelated cause: custom Network Security Config.** A
|
|
312
|
+
`res/xml/network_security_config.xml` with custom trust anchors or certificate
|
|
313
|
+
pinning is a _different_ source of TLS failures that this fix does not address.
|
|
314
|
+
|
|
315
|
+
## What you are opting into
|
|
316
|
+
|
|
317
|
+
Trust is never weakened. The chain is only ever _extended_, and the completed
|
|
318
|
+
chain is re-validated by the platform's own trust manager against the same
|
|
319
|
+
system anchors, so anything Android would have rejected is still rejected.
|
|
320
|
+
|
|
321
|
+
What you are accepting is an outbound request driven by a certificate that just
|
|
322
|
+
failed validation. Completing a chain means fetching the missing intermediate
|
|
323
|
+
from a URL written into that certificate, so a rejected handshake can cause a
|
|
324
|
+
cleartext request to a **host, port and path of the server's choosing** —
|
|
325
|
+
including a loopback or LAN address — and that request is deliberately not
|
|
326
|
+
subject to your app's `NetworkSecurityPolicy` (see
|
|
327
|
+
[The cleartext fetch is handled for you](#the-cleartext-fetch-is-handled-for-you)).
|
|
328
|
+
|
|
329
|
+
This is inherent to AIA chasing: browsers and Apple's Secure Transport do the
|
|
330
|
+
same thing, which is why the servers in question work everywhere except Android.
|
|
331
|
+
It is bounded rather than absent:
|
|
332
|
+
|
|
333
|
+
- the fetch happens **only** when the chain does not already reach one of your
|
|
334
|
+
trust anchors, so the common non-path failures — an expired leaf, a hostname
|
|
335
|
+
mismatch — do not trigger one. (The test is on the issuer name at the top of
|
|
336
|
+
the chain, so a chain topping at a root your device does _not_ carry, such as
|
|
337
|
+
a server still serving the retired DST Root CA X3 cross-sign, is still chased
|
|
338
|
+
even if its real problem is expiry.)
|
|
339
|
+
- the URL is rejected unless it is free of control characters, so it cannot
|
|
340
|
+
inject request lines of its own;
|
|
341
|
+
- at most 3 distinct `CA Issuers` URLs per certificate are tried, a response is
|
|
342
|
+
capped at 1 MiB, and at most 5 redirects are followed per fetch;
|
|
343
|
+
- the whole chase runs under a single 20-second wall-clock budget — every
|
|
344
|
+
connect, TLS handshake, read and redirect hop is clipped to the time
|
|
345
|
+
remaining, and nothing new starts once it has expired. So the worst case a
|
|
346
|
+
hostile certificate can inflict on one failed handshake is roughly those
|
|
347
|
+
20 seconds, plus at most one DNS lookup that was already in flight when the
|
|
348
|
+
budget expired (Java offers no timeout for name resolution, so a lookup can
|
|
349
|
+
overrun the budget by its own duration — but a new one is never started past
|
|
350
|
+
it);
|
|
351
|
+
- the cache holds at most 32 entries, and only successful fetches;
|
|
352
|
+
- responses are never trusted on their say-so: a fetched certificate is used
|
|
353
|
+
only if it is the genuine issuer of the certificate below it, and the
|
|
354
|
+
completed chain still has to validate.
|
|
355
|
+
|
|
356
|
+
If your threat model does not allow a failed handshake to trigger an outbound
|
|
357
|
+
connection at all, don't install this — or install it on a single client
|
|
358
|
+
(see [Advanced: custom OkHttp client](#advanced-custom-okhttp-client)) rather
|
|
359
|
+
than as the process default.
|
|
360
|
+
|
|
361
|
+
## Advanced: custom OkHttp client
|
|
362
|
+
|
|
363
|
+
If you manage your own HTTP client instead of changing the process default, use
|
|
364
|
+
`AiaTls.trustManager()` to obtain the wrapped `X509TrustManager` and build a
|
|
365
|
+
matching `SSLSocketFactory` from it. OkHttp's `sslSocketFactory(...)` needs
|
|
366
|
+
**both** the factory and the trust manager:
|
|
367
|
+
|
|
368
|
+
::: code-group
|
|
369
|
+
|
|
370
|
+
```kotlin [Kotlin]
|
|
371
|
+
import com.audiobrowser.tls.AiaTls
|
|
372
|
+
import javax.net.ssl.SSLContext
|
|
373
|
+
import okhttp3.OkHttpClient
|
|
374
|
+
|
|
375
|
+
val trustManager = AiaTls.trustManager()
|
|
376
|
+
val sslContext = SSLContext.getInstance("TLS").apply {
|
|
377
|
+
init(null, arrayOf(trustManager), null)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
val client = OkHttpClient.Builder()
|
|
381
|
+
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
|
382
|
+
.build()
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
```java [Java]
|
|
386
|
+
import com.audiobrowser.tls.AiaTls;
|
|
387
|
+
import javax.net.ssl.SSLContext;
|
|
388
|
+
import javax.net.ssl.X509TrustManager;
|
|
389
|
+
import okhttp3.OkHttpClient;
|
|
390
|
+
|
|
391
|
+
X509TrustManager trustManager = AiaTls.trustManager();
|
|
392
|
+
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
393
|
+
sslContext.init(null, new X509TrustManager[] { trustManager }, null);
|
|
394
|
+
|
|
395
|
+
OkHttpClient client = new OkHttpClient.Builder()
|
|
396
|
+
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
|
|
397
|
+
.build();
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
:::
|
|
401
|
+
|
|
402
|
+
This confines AIA chasing to that one client and leaves the process default
|
|
403
|
+
untouched.
|
|
404
|
+
|
|
405
|
+
> **Lifecycle.** Each call to `AiaTls.socketFactory()` / `AiaTls.trustManager()`
|
|
406
|
+
> builds a fresh component with its **own** fetched-intermediate cache, so build
|
|
407
|
+
> once and reuse it (don't construct one per request). The components are
|
|
408
|
+
> thread-safe — the fetch cache is a synchronized, bounded LRU — so a single
|
|
409
|
+
> instance is safe to install as the process default or share across a client.
|