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
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'CarPlay for audio apps: browse templates generated from your browse tree, the UIScene and headless React Native host setup, and Siri voice search via INPlayMediaIntent.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# CarPlay
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
CarPlay integration for audio apps works through two systems:
|
|
10
|
+
|
|
11
|
+
1. **Browse templates** — Tab-based navigation generated from the **browse tree** you declare with `configureBrowser`, handled automatically by the library. If you haven't set that up yet, see [Basic Usage](/guide/basic-usage) for tabs, routes, and sources — this page assumes it's already configured. Every config key used below is documented in [`BrowserConfiguration`](/api/types/browser/#browserconfiguration).
|
|
12
|
+
2. **Siri voice search** — The search button on CarPlay launches Siri, which creates an `INPlayMediaIntent`. The library resolves it through the same [`search`](/guide/search) source that powers your in-app search, then queues and plays the results. This requires additional setup in your app (see [Siri Voice Search](#siri-voice-search) below).
|
|
13
|
+
|
|
14
|
+
On CarPlay's audio interface the search button always triggers Siri — audio apps don't use `CPSearchTemplate` (that's a navigation-app API).
|
|
15
|
+
|
|
16
|
+
The two systems are independent, but **both** require the [React Native host setup](#react-native-host-uiscene-headless) (UIScene lifecycle + headless RN boot) — that's the one prerequisite every CarPlay app needs, even browse-only. Siri then adds its own entitlement, Info.plist keys, and intent handler on top. The **Loading States** and **Album Line Navigation** sections are optional browse polish — skip them to get a minimal integration working.
|
|
17
|
+
|
|
18
|
+
::: info Before you start
|
|
19
|
+
This page assumes you've installed the library and have the player running: add `react-native-audio-browser`, run `pod install` in `ios/`, and call `setupPlayer()` + `configureBrowser()` at startup. See [Getting Started](/guide/getting-started) and [Basic Usage](/guide/basic-usage) first.
|
|
20
|
+
:::
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### Entitlement
|
|
25
|
+
|
|
26
|
+
Your app needs the CarPlay audio entitlement in your `.entitlements` file:
|
|
27
|
+
|
|
28
|
+
```xml
|
|
29
|
+
<key>com.apple.developer.carplay-audio</key>
|
|
30
|
+
<true/>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If your target has no `.entitlements` file yet, add one via Xcode (**Signing & Capabilities → + Capability**) and make sure the target's **`CODE_SIGN_ENTITLEMENTS`** build setting points at it — otherwise the key is ignored at build time.
|
|
34
|
+
|
|
35
|
+
The CarPlay audio entitlement also must be **granted by Apple** via the [CarPlay entitlement request](https://developer.apple.com/contact/carplay/) before it works on a device — approval can take a while, so request it early. You can still develop against the [CarPlay Simulator](#testing) meanwhile.
|
|
36
|
+
|
|
37
|
+
### Scene manifest (Info.plist)
|
|
38
|
+
|
|
39
|
+
Your scene manifest needs **two** scene configurations: the CarPlay template scene (driven by the library's `RNABCarPlaySceneDelegate`) and your phone window scene (driven by your own `PhoneSceneDelegate`, shown in the host setup below). If your app isn't scene-based yet, this whole `UIApplicationSceneManifest` is new:
|
|
40
|
+
|
|
41
|
+
```xml
|
|
42
|
+
<key>UIApplicationSceneManifest</key>
|
|
43
|
+
<dict>
|
|
44
|
+
<key>UIApplicationSupportsMultipleScenes</key>
|
|
45
|
+
<true/>
|
|
46
|
+
<key>UISceneConfigurations</key>
|
|
47
|
+
<dict>
|
|
48
|
+
<key>CPTemplateApplicationSceneSessionRoleApplication</key>
|
|
49
|
+
<array>
|
|
50
|
+
<dict>
|
|
51
|
+
<key>UISceneConfigurationName</key>
|
|
52
|
+
<string>CarPlay</string>
|
|
53
|
+
<key>UISceneDelegateClassName</key>
|
|
54
|
+
<string>RNABCarPlaySceneDelegate</string>
|
|
55
|
+
</dict>
|
|
56
|
+
</array>
|
|
57
|
+
<key>UIWindowSceneSessionRoleApplication</key>
|
|
58
|
+
<array>
|
|
59
|
+
<dict>
|
|
60
|
+
<key>UISceneConfigurationName</key>
|
|
61
|
+
<string>Phone</string>
|
|
62
|
+
<!-- $(PRODUCT_MODULE_NAME) = your app's Swift module -->
|
|
63
|
+
<key>UISceneDelegateClassName</key>
|
|
64
|
+
<string>$(PRODUCT_MODULE_NAME).PhoneSceneDelegate</string>
|
|
65
|
+
</dict>
|
|
66
|
+
</array>
|
|
67
|
+
</dict>
|
|
68
|
+
</dict>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The two `UISceneDelegateClassName` strings name the delegates for each scene: the library's `RNABCarPlaySceneDelegate` (ships with the library — you never implement it; it links in once the `AudioBrowser` pod is installed) and your own `PhoneSceneDelegate`. These manifest strings are only a fallback — once you add `configurationForConnecting` (next section), your code picks each scene's delegate. So `PhoneSceneDelegate` not resolving yet, and the CarPlay value being a bare class name, are both expected until you finish the host setup next.
|
|
72
|
+
|
|
73
|
+
## React Native host (UIScene + headless)
|
|
74
|
+
|
|
75
|
+
This section is **required for any CarPlay integration** — browse-only or Siri. CarPlay connects through scenes and can launch your app in the background with no phone window, so React Native has to be startable both on the foreground path (your phone scene) and headless (a hidden window). Set this up once; both the browse templates and Siri depend on it.
|
|
76
|
+
|
|
77
|
+
::: warning Two prerequisites
|
|
78
|
+
**UIScene lifecycle** — CarPlay requires a `UIWindowSceneDelegate` for the phone window. The default `npx react-native init` app isn't scene-based; if yours still sets up its window directly in `AppDelegate`, migrate to scenes first (the sections below build all the pieces).
|
|
79
|
+
|
|
80
|
+
**New Architecture host, Swift** — these snippets assume `RCTReactNativeFactory` (present on every React Native version the library supports) and a Swift `AppDelegate`. If yours is still Objective-C (`.mm`), migrating it to Swift first is simpler than translating this wiring by hand — scaffold a throwaway app (`npx @react-native-community/cli init Tmp`) and copy its `AppDelegate.swift` as a reference. ObjC works too (the bridging header is then unnecessary, see [Siri step 3](#_3-bridging-header-swift-appdelegate-only)) but isn't shown here.
|
|
81
|
+
:::
|
|
82
|
+
|
|
83
|
+
### The factory
|
|
84
|
+
|
|
85
|
+
These AppDelegate snippets need the New-Architecture React host imports:
|
|
86
|
+
|
|
87
|
+
```swift
|
|
88
|
+
import UIKit
|
|
89
|
+
import Intents
|
|
90
|
+
import React
|
|
91
|
+
import React_RCTAppDelegate
|
|
92
|
+
import ReactAppDependencyProvider
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Create the `RCTReactNativeFactory` in `didFinishLaunchingWithOptions`, but **don't** start RN there — the phone scene starts it on the foreground path, and the headless helper starts it on the background path:
|
|
96
|
+
|
|
97
|
+
```swift
|
|
98
|
+
var reactNativeFactory: RCTReactNativeFactory?
|
|
99
|
+
private var reactNativeDelegate: ReactNativeDelegate?
|
|
100
|
+
|
|
101
|
+
func application(
|
|
102
|
+
_ application: UIApplication,
|
|
103
|
+
didFinishLaunchingWithOptions launchOptions:
|
|
104
|
+
[UIApplication.LaunchOptionsKey: Any]? = nil
|
|
105
|
+
) -> Bool {
|
|
106
|
+
let delegate = ReactNativeDelegate()
|
|
107
|
+
delegate.dependencyProvider = RCTAppDependencyProvider()
|
|
108
|
+
reactNativeDelegate = delegate
|
|
109
|
+
reactNativeFactory = RCTReactNativeFactory(delegate: delegate)
|
|
110
|
+
// Note: do NOT start RN here — the scene delegates own startup.
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`ReactNativeDelegate` is a `RCTDefaultReactNativeFactoryDelegate` subclass **you** define (standard New-Architecture AppDelegate boilerplate — it supplies your JS bundle URL); `RCTAppDependencyProvider` is a React-core class. Neither comes from this library. The default New-Arch template generates both — if you scaffolded with it you already have them; otherwise this is the whole subclass:
|
|
116
|
+
|
|
117
|
+
```swift
|
|
118
|
+
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
119
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? { bundleURL() }
|
|
120
|
+
|
|
121
|
+
override func bundleURL() -> URL? {
|
|
122
|
+
#if DEBUG
|
|
123
|
+
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
124
|
+
#else
|
|
125
|
+
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
126
|
+
#endif
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### The headless helper
|
|
132
|
+
|
|
133
|
+
Two things call this helper, which is why it's required even for **browse-only** apps:
|
|
134
|
+
|
|
135
|
+
- The library's `RNABCarPlaySceneDelegate` calls `startReactNativeHeadless` on your `AppDelegate` (via an optional protocol) when CarPlay connects with no phone scene running — so a browse-only CarPlay launch boots RN automatically.
|
|
136
|
+
- Your Siri intent handler calls it (step 4) for a background intent launch.
|
|
137
|
+
|
|
138
|
+
::: warning Name it exactly `startReactNativeHeadless`
|
|
139
|
+
The library finds this method **by selector** — it must be named exactly `startReactNativeHeadless` (no args) on your `AppDelegate`, or the library's CarPlay delegate won't be able to boot RN and browse screens will hang empty.
|
|
140
|
+
:::
|
|
141
|
+
|
|
142
|
+
```swift
|
|
143
|
+
// Properties on your AppDelegate.
|
|
144
|
+
private var reactNativeStarted = false
|
|
145
|
+
private var headlessWindow: UIWindow?
|
|
146
|
+
|
|
147
|
+
/// Boots React Native into a hidden window when no UI scene exists yet.
|
|
148
|
+
/// Called by the library's CarPlay delegate (browse) and your Siri handler.
|
|
149
|
+
/// Safe to call repeatedly — it starts RN at most once.
|
|
150
|
+
func startReactNativeHeadless() {
|
|
151
|
+
guard !reactNativeStarted else { return }
|
|
152
|
+
reactNativeStarted = true
|
|
153
|
+
|
|
154
|
+
// RCTReactNativeFactory needs a window to host the root view.
|
|
155
|
+
headlessWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
|
156
|
+
headlessWindow?.isHidden = true
|
|
157
|
+
|
|
158
|
+
// `startReactNative(withModuleName:in:launchOptions:)` is a React-core method
|
|
159
|
+
// on RCTReactNativeFactory — not a library API. "YourAppModuleName" is your
|
|
160
|
+
// app.json `name` (= the AppRegistry.registerComponent string); the phone
|
|
161
|
+
// scene and this headless path must use that same name.
|
|
162
|
+
reactNativeFactory?.startReactNative(
|
|
163
|
+
// ⚠️ replace with your app.json `name`
|
|
164
|
+
withModuleName: "YourAppModuleName",
|
|
165
|
+
in: headlessWindow,
|
|
166
|
+
launchOptions: nil
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// Called by the phone scene after it starts RN on the foreground path, so the
|
|
171
|
+
/// headless helper never double-starts it. Both paths gate on the same
|
|
172
|
+
/// `reactNativeStarted` flag, so RN starts exactly once.
|
|
173
|
+
func markReactNativeStarted() {
|
|
174
|
+
reactNativeStarted = true
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### The phone scene delegate
|
|
179
|
+
|
|
180
|
+
Create this as a **new `.swift` file in your app target** (e.g. `PhoneSceneDelegate.swift`, next to `AppDelegate.swift`). It must compile into your app's own module — that's why the Info.plist refers to it as `$(PRODUCT_MODULE_NAME).PhoneSceneDelegate`. It starts RN on the foreground path and flips the shared flag via `markReactNativeStarted()`:
|
|
181
|
+
|
|
182
|
+
```swift
|
|
183
|
+
class PhoneSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|
184
|
+
var window: UIWindow?
|
|
185
|
+
|
|
186
|
+
func scene(
|
|
187
|
+
_ scene: UIScene,
|
|
188
|
+
willConnectTo session: UISceneSession,
|
|
189
|
+
options connectionOptions: UIScene.ConnectionOptions
|
|
190
|
+
) {
|
|
191
|
+
guard session.role == .windowApplication,
|
|
192
|
+
let appDelegate = UIApplication.shared.delegate as? AppDelegate,
|
|
193
|
+
let windowScene = scene as? UIWindowScene else { return }
|
|
194
|
+
|
|
195
|
+
let window = UIWindow(windowScene: windowScene)
|
|
196
|
+
appDelegate.window = window
|
|
197
|
+
appDelegate.markReactNativeStarted() // foreground path owns RN startup
|
|
198
|
+
|
|
199
|
+
// Module name must exactly match the headless helper / registerComponent.
|
|
200
|
+
appDelegate.reactNativeFactory?.startReactNative(
|
|
201
|
+
// ⚠️ replace with your app.json `name`
|
|
202
|
+
withModuleName: "YourAppModuleName",
|
|
203
|
+
in: window,
|
|
204
|
+
launchOptions: nil
|
|
205
|
+
)
|
|
206
|
+
self.window = window
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
See the example app's [`AppDelegate.swift`](https://github.com/radio-garden/react-native-audio-browser/blob/main/apps/example-native/ios/AudioBrowserExample/AppDelegate.swift) and [`PhoneSceneDelegate.swift`](https://github.com/radio-garden/react-native-audio-browser/blob/main/apps/example-native/ios/AudioBrowserExample/PhoneSceneDelegate.swift) for the complete, copy-pasteable versions.
|
|
212
|
+
|
|
213
|
+
### Routing scene connections
|
|
214
|
+
|
|
215
|
+
Now that both delegates exist, route each incoming scene to the right one from your `AppDelegate` — the library's `RNABCarPlaySceneDelegate` for CarPlay, your `PhoneSceneDelegate` for the phone window:
|
|
216
|
+
|
|
217
|
+
```swift
|
|
218
|
+
func application(
|
|
219
|
+
_ application: UIApplication,
|
|
220
|
+
configurationForConnecting connectingSceneSession: UISceneSession,
|
|
221
|
+
options: UIScene.ConnectionOptions
|
|
222
|
+
) -> UISceneConfiguration {
|
|
223
|
+
if connectingSceneSession.role == UISceneSession.Role.carTemplateApplication {
|
|
224
|
+
let config = UISceneConfiguration(
|
|
225
|
+
name: "CarPlay", sessionRole: connectingSceneSession.role)
|
|
226
|
+
// RNABCarPlaySceneDelegate is an ObjC class the library ships, so it's
|
|
227
|
+
// resolved by name rather than imported.
|
|
228
|
+
config.delegateClass =
|
|
229
|
+
NSClassFromString("RNABCarPlaySceneDelegate") as? UIResponder.Type
|
|
230
|
+
return config
|
|
231
|
+
}
|
|
232
|
+
// Phone scene — your own delegate, defined above.
|
|
233
|
+
let config = UISceneConfiguration(
|
|
234
|
+
name: "Phone", sessionRole: connectingSceneSession.role)
|
|
235
|
+
config.delegateClass = PhoneSceneDelegate.self
|
|
236
|
+
return config
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
As noted in the scene manifest above, the `delegateClass` you set here is what actually routes each scene; the Info.plist names are just the fallback. Seeing each delegate in both places is expected.
|
|
241
|
+
|
|
242
|
+
One coupling that **is** load-bearing: the `name:` you pass to `UISceneConfiguration` (`"CarPlay"` / `"Phone"`) must match a `UISceneConfigurationName` in the manifest. Rename one without the other and iOS crashes at connect time with _"no UISceneConfiguration named …"_.
|
|
243
|
+
|
|
244
|
+
### The assembled `AppDelegate`
|
|
245
|
+
|
|
246
|
+
The fragments above all live on one `AppDelegate`. Here is the whole class in one piece — the authoritative copy-paste. It **replaces** the `@main class AppDelegate` your RN template generated in `ios/<app>/AppDelegate.swift` (you keep one `@main`; don't add a second). The Siri `handlerFor` method at the end is needed only if you add [Siri Voice Search](#siri-voice-search):
|
|
247
|
+
|
|
248
|
+
```swift
|
|
249
|
+
import UIKit
|
|
250
|
+
import Intents
|
|
251
|
+
import React
|
|
252
|
+
import React_RCTAppDelegate
|
|
253
|
+
import ReactAppDependencyProvider
|
|
254
|
+
|
|
255
|
+
@main
|
|
256
|
+
class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
257
|
+
var window: UIWindow?
|
|
258
|
+
|
|
259
|
+
var reactNativeFactory: RCTReactNativeFactory?
|
|
260
|
+
private var reactNativeDelegate: ReactNativeDelegate?
|
|
261
|
+
private var reactNativeStarted = false
|
|
262
|
+
private var headlessWindow: UIWindow?
|
|
263
|
+
|
|
264
|
+
func application(
|
|
265
|
+
_ application: UIApplication,
|
|
266
|
+
didFinishLaunchingWithOptions launchOptions:
|
|
267
|
+
[UIApplication.LaunchOptionsKey: Any]? = nil
|
|
268
|
+
) -> Bool {
|
|
269
|
+
let delegate = ReactNativeDelegate()
|
|
270
|
+
delegate.dependencyProvider = RCTAppDependencyProvider()
|
|
271
|
+
reactNativeDelegate = delegate
|
|
272
|
+
reactNativeFactory = RCTReactNativeFactory(delegate: delegate)
|
|
273
|
+
return true // don't start RN here — the scene delegates own startup
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Route each connecting scene to its delegate.
|
|
277
|
+
func application(
|
|
278
|
+
_ application: UIApplication,
|
|
279
|
+
configurationForConnecting connectingSceneSession: UISceneSession,
|
|
280
|
+
options: UIScene.ConnectionOptions
|
|
281
|
+
) -> UISceneConfiguration {
|
|
282
|
+
if connectingSceneSession.role
|
|
283
|
+
== UISceneSession.Role.carTemplateApplication {
|
|
284
|
+
let config = UISceneConfiguration(
|
|
285
|
+
name: "CarPlay", sessionRole: connectingSceneSession.role)
|
|
286
|
+
config.delegateClass =
|
|
287
|
+
NSClassFromString("RNABCarPlaySceneDelegate") as? UIResponder.Type
|
|
288
|
+
return config
|
|
289
|
+
}
|
|
290
|
+
let config = UISceneConfiguration(
|
|
291
|
+
name: "Phone", sessionRole: connectingSceneSession.role)
|
|
292
|
+
config.delegateClass = PhoneSceneDelegate.self
|
|
293
|
+
return config
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Boots RN into a hidden window when there's no UI scene (background launch).
|
|
297
|
+
func startReactNativeHeadless() {
|
|
298
|
+
guard !reactNativeStarted else { return }
|
|
299
|
+
reactNativeStarted = true
|
|
300
|
+
headlessWindow = UIWindow(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
|
301
|
+
headlessWindow?.isHidden = true
|
|
302
|
+
reactNativeFactory?.startReactNative(
|
|
303
|
+
// ⚠️ replace with your app.json `name`
|
|
304
|
+
withModuleName: "YourAppModuleName",
|
|
305
|
+
in: headlessWindow,
|
|
306
|
+
launchOptions: nil
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Called by PhoneSceneDelegate so the headless path won't double-start RN.
|
|
311
|
+
func markReactNativeStarted() { reactNativeStarted = true }
|
|
312
|
+
|
|
313
|
+
// Siri only — needed when you add Siri Voice Search (step 4). Requires the
|
|
314
|
+
// bridging header so `RNABAudioBrowser` is visible in Swift.
|
|
315
|
+
func application(
|
|
316
|
+
_ application: UIApplication,
|
|
317
|
+
handlerFor intent: INIntent
|
|
318
|
+
) -> Any? {
|
|
319
|
+
startReactNativeHeadless()
|
|
320
|
+
return RNABAudioBrowser.handler(for: intent)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Same file as AppDelegate. Standard New-Arch host delegate —
|
|
325
|
+
// supplies the bundle URL.
|
|
326
|
+
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
327
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? { bundleURL() }
|
|
328
|
+
|
|
329
|
+
override func bundleURL() -> URL? {
|
|
330
|
+
#if DEBUG
|
|
331
|
+
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
332
|
+
#else
|
|
333
|
+
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
334
|
+
#endif
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
This is the complete `AppDelegate.swift` (the `@main class AppDelegate` plus its `ReactNativeDelegate` in the same file). `PhoneSceneDelegate` lives in its **own** `PhoneSceneDelegate.swift` (shown above). Those two files are the complete native side.
|
|
340
|
+
|
|
341
|
+
::: warning Don't blindly overwrite an AppDelegate you've customized
|
|
342
|
+
This block is the minimal shape. If your existing `AppDelegate` / `ReactNativeDelegate` already has extra overrides — bundle customization, linking setup, push/deep-link handlers, `customize(rootView:)`, extra modules — **keep them**. Merge the CarPlay additions (the factory split, `configurationForConnecting`, the headless helper, `markReactNativeStarted`, and the Siri `handlerFor`) into your file rather than replacing it wholesale.
|
|
343
|
+
:::
|
|
344
|
+
|
|
345
|
+
### Where to call `configureBrowser`
|
|
346
|
+
|
|
347
|
+
A headless launch boots RN into a hidden window with **no components mounted**, so a `configureBrowser` call inside a component's `useEffect` will never run on a CarPlay/Siri cold launch. Call it (and `setupPlayer`) as a **top-level side-effect in `index.js`** — alongside `AppRegistry.registerComponent` — so it executes when the JS bundle loads, mounted UI or not.
|
|
348
|
+
|
|
349
|
+
First, because `configureBrowser` **replaces** the whole config, build one object with everything — tabs, routes, and (for Siri) search — together:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const browserConfig = {
|
|
353
|
+
// Tabs along the top of the CarPlay browser (max 4 — capped for
|
|
354
|
+
// Android Auto + CarPlay compatibility; extra tabs dropped with a warning).
|
|
355
|
+
tabs: [{ title: 'Home', path: '/' }],
|
|
356
|
+
|
|
357
|
+
// routes: path → BrowserSource (static page, callback, or HTTP).
|
|
358
|
+
routes: {
|
|
359
|
+
'/': async () => ({
|
|
360
|
+
title: 'Home',
|
|
361
|
+
children: [{ title: 'Crunchy Greens FM', src: 'https://...' }]
|
|
362
|
+
})
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// search: required for Siri voice search (omit for browse-only).
|
|
366
|
+
search: { baseUrl: 'https://api.example.com/search' }
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Then apply it as a top-level side-effect in `index.js`:
|
|
371
|
+
|
|
372
|
+
```js
|
|
373
|
+
// index.js
|
|
374
|
+
import { AppRegistry } from 'react-native'
|
|
375
|
+
import { configureBrowser, setupPlayer } from 'react-native-audio-browser'
|
|
376
|
+
import App from './App'
|
|
377
|
+
import { name as appName } from './app.json'
|
|
378
|
+
import { browserConfig } from './browserConfig' // the object above
|
|
379
|
+
|
|
380
|
+
AppRegistry.registerComponent(appName, () => App)
|
|
381
|
+
|
|
382
|
+
// Runs on every launch — including headless CarPlay / Siri — because it's a
|
|
383
|
+
// module side-effect, not tied to a mounted component.
|
|
384
|
+
void (async () => {
|
|
385
|
+
await setupPlayer()
|
|
386
|
+
configureBrowser(browserConfig)
|
|
387
|
+
})()
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The `"YourAppModuleName"` in the Swift snippets above is exactly this `appName` — your `app.json` `name`, identical to the string passed to `AppRegistry.registerComponent` (the same in the default RN template). The headless helper and the phone scene must both start RN with that name.
|
|
391
|
+
|
|
392
|
+
## Siri Voice Search
|
|
393
|
+
|
|
394
|
+
Siri voice search builds on the [React Native host setup](#react-native-host-uiscene-headless) above — make sure that's in place first. Then complete the steps below, in order:
|
|
395
|
+
|
|
396
|
+
- **Step 0 — configure a `search` source** (the warning box just below; required, easy to forget)
|
|
397
|
+
- **Steps 1–4** — Siri entitlement, Info.plist keys, bridging header, intent handler
|
|
398
|
+
|
|
399
|
+
::: warning Step 0 (required) — a configured `search` source
|
|
400
|
+
Siri voice search resolves spoken queries through the **same [`search`](/guide/search) source as your in-app search**. If you haven't set one, the mic will open, hear "play jazz", and return **nothing** — steps 1–4 will look wired up but produce silence. Configure it once:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
configureBrowser({
|
|
404
|
+
// …your tabs / routes …
|
|
405
|
+
search: {
|
|
406
|
+
// GET https://api.example.com/search?q=jazz — the library appends
|
|
407
|
+
// `q` plus structured voice params (`mode`, `genre`, `artist`,
|
|
408
|
+
// `album`, `reference`) and expects a page object back:
|
|
409
|
+
// { title?, children: Track[] }, where each playable child Track
|
|
410
|
+
// needs at least `title` + `src`.
|
|
411
|
+
baseUrl: 'https://api.example.com/search'
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
`configureBrowser` **replaces** the entire configuration each time it's called — it doesn't merge. So this isn't a second call: fold `search` into the single `configureBrowser({ tabs, routes, search, … })` you make at startup (see [Where to call `configureBrowser`](#where-to-call-configurebrowser)). The snippets on this page show one key at a time for brevity.
|
|
417
|
+
|
|
418
|
+
Naming note: in the **callback** form the spoken text arrives as `query` (`search: async ({ query }) => …`); on the **HTTP** form the library sends it as the wire param **`q`** (`?q=jazz`). Same value, two names depending on which form you use.
|
|
419
|
+
|
|
420
|
+
See [Search](/guide/search) for the full `SearchParams` (genre / artist / album / `mode` / `reference`), the callback form (return [`Track`](/api/types/browser-nodes/#track)`[]` yourself), and `transform` for rewriting requests. This is required for Siri; the browse templates work without it.
|
|
421
|
+
:::
|
|
422
|
+
|
|
423
|
+
CarPlay's tab bar already has a **search button that launches Siri automatically** — you get that for free once the steps below are wired up. Separately, the library can also show an **"Ask Siri to Play Audio" cell inside a list** as an extra entry point. This `carPlaySiriListButton` is optional polish — it's not what enables Siri search. To add it, set [`carPlaySiriListButton`](/api/types/browser/#carplaysirilistbuttonposition) on the resolved content returned by your route source:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
configureBrowser({
|
|
427
|
+
// `routes` maps a path → a `BrowserSource`: a static page object, an async
|
|
428
|
+
// callback returning one (as here), or an HTTP request config. See
|
|
429
|
+
// Basic Usage and the BrowserSource type: /api/types/browser/#browsersource
|
|
430
|
+
routes: {
|
|
431
|
+
'/': async () => ({
|
|
432
|
+
title: 'Home',
|
|
433
|
+
carPlaySiriListButton: 'top',
|
|
434
|
+
children: [{ title: 'Crunchy Greens FM', src: 'https://...' }]
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
(Tabs themselves come from your separate [`tabs`](/api/types/browser/#tabs) config, not from this route — the `'/'` route here is just where the button is attached. See [Basic Usage](/guide/basic-usage) for how `tabs` and `routes` fit together.)
|
|
441
|
+
|
|
442
|
+
The `carPlaySiriListButton` property accepts `'top'` or `'bottom'` to control where the system assistant cell appears. It only affects CarPlay — it has no effect on the phone UI.
|
|
443
|
+
|
|
444
|
+
::: tip
|
|
445
|
+
The button just launches Siri — it does **nothing** until the entitlement, Info.plist keys, and intent handler in the steps below are in place. Wire those up first, or tapping it will appear to do nothing.
|
|
446
|
+
:::
|
|
447
|
+
|
|
448
|
+
To complete the setup, your app needs the Siri entitlement, a couple of Info.plist keys, a bridging header, and a one-line handler in your AppDelegate. Media intents are handled in-app: Siri delivers the `INPlayMediaIntent` straight to your `AppDelegate`.
|
|
449
|
+
|
|
450
|
+
::: info iOS version
|
|
451
|
+
The library's minimum is **iOS 16**. The other version numbers on this page are finer-grained capability notes within that floor: the in-app handler API (`application(_:handlerFor:)`) exists from iOS 14 so you never need to gate on it, and the automatic CarPlay loading spinner is iOS 18.4+.
|
|
452
|
+
:::
|
|
453
|
+
|
|
454
|
+
### 1. Siri entitlement
|
|
455
|
+
|
|
456
|
+
Add the Siri entitlement to the **same `.entitlements` file** as the CarPlay key — both live in one `<dict>`:
|
|
457
|
+
|
|
458
|
+
```xml
|
|
459
|
+
<!-- YourApp.entitlements -->
|
|
460
|
+
<plist version="1.0">
|
|
461
|
+
<dict>
|
|
462
|
+
<key>com.apple.developer.carplay-audio</key>
|
|
463
|
+
<true/>
|
|
464
|
+
<key>com.apple.developer.siri</key>
|
|
465
|
+
<true/>
|
|
466
|
+
</dict>
|
|
467
|
+
</plist>
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The reliable way to add this is Xcode → **Signing & Capabilities → + Capability → Siri**, which writes the key and enables **SiriKit on your App ID** (via automatic signing or the developer portal). Unlike the CarPlay audio entitlement, Siri does **not** require a manual Apple approval request — enabling the capability is enough.
|
|
471
|
+
|
|
472
|
+
### 2. Siri Info.plist keys
|
|
473
|
+
|
|
474
|
+
Add Siri usage description and supported media categories to your app's Info.plist:
|
|
475
|
+
|
|
476
|
+
```xml
|
|
477
|
+
<key>NSSiriUsageDescription</key>
|
|
478
|
+
<string>Siri is used for voice-controlled media playback via CarPlay.</string>
|
|
479
|
+
<key>INIntentsSupported</key>
|
|
480
|
+
<array>
|
|
481
|
+
<string>INPlayMediaIntent</string>
|
|
482
|
+
<!-- Optional — only for "like/dislike" / "add to favorites": -->
|
|
483
|
+
<string>INUpdateMediaAffinityIntent</string>
|
|
484
|
+
<string>INAddMediaIntent</string>
|
|
485
|
+
</array>
|
|
486
|
+
<key>INSupportedMediaCategories</key>
|
|
487
|
+
<array>
|
|
488
|
+
<!-- PLACEHOLDER — replace with the categories matching YOUR content.
|
|
489
|
+
E.g. INMediaCategoryRadio for live radio, INMediaCategoryPodcasts
|
|
490
|
+
for podcasts, INMediaCategoryMusic for music. List every one you
|
|
491
|
+
support. -->
|
|
492
|
+
<string>INMediaCategoryRadio</string>
|
|
493
|
+
</array>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
`INIntentsSupported` registers the intents your app can handle. The library's handler vends a handler for three intents:
|
|
497
|
+
|
|
498
|
+
- **`INPlayMediaIntent`** — search / resume / play (the core voice-search flow).
|
|
499
|
+
- **`INUpdateMediaAffinityIntent`** — "like" / "dislike" the current track.
|
|
500
|
+
- **`INAddMediaIntent`** — add the current track to favorites.
|
|
501
|
+
|
|
502
|
+
Register only the ones you want; `INPlayMediaIntent` alone is enough for voice search. The [`INSupportedMediaCategories`](https://developer.apple.com/documentation/sirikit/inmediacategory) values tell Siri which spoken requests to route to your app — if they don't cover your content, Siri may hand the request to a **different app**, so list every category you actually support.
|
|
503
|
+
|
|
504
|
+
### 3. Bridging Header (Swift AppDelegate only)
|
|
505
|
+
|
|
506
|
+
The intent API lives in the library's CocoaPod, **`AudioBrowser`** (the umbrella header for the `react-native-audio-browser` package). An **Objective-C** AppDelegate just `#import <AudioBrowser/RNABAudioBrowser.h>` directly and skips this step.
|
|
507
|
+
|
|
508
|
+
A **Swift** AppDelegate reaches it through a bridging header. If your app already has one (check `SWIFT_OBJC_BRIDGING_HEADER`), add the import to it; otherwise create one and point that build setting at it:
|
|
509
|
+
|
|
510
|
+
**YourApp-Bridging-Header.h:**
|
|
511
|
+
|
|
512
|
+
```objc
|
|
513
|
+
#import <AudioBrowser/RNABAudioBrowser.h>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
::: tip If the import won't resolve
|
|
517
|
+
`<AudioBrowser/...>` resolves only once the `AudioBrowser` pod is linked. Run `pod install` (from `ios/`) after adding the npm dependency, and confirm the pod is named **`AudioBrowser`** in your `Podfile.lock`. A "module 'AudioBrowser' not found" error almost always means `pod install` hasn't run.
|
|
518
|
+
:::
|
|
519
|
+
|
|
520
|
+
### 4. AppDelegate intent handler
|
|
521
|
+
|
|
522
|
+
For in-app intent handling, implement `application(_:handlerFor:)` on your `AppDelegate`, boot RN headless (the [`startReactNativeHeadless()`](#the-headless-helper) helper from the host setup), and return the library's handler object. This is the same method already shown (commented "Siri only") in the [assembled `AppDelegate`](#the-assembled-appdelegate) — add it once, not twice; it's repeated here in isolation:
|
|
523
|
+
|
|
524
|
+
```swift
|
|
525
|
+
import Intents
|
|
526
|
+
|
|
527
|
+
func application(
|
|
528
|
+
_ application: UIApplication,
|
|
529
|
+
handlerFor intent: INIntent
|
|
530
|
+
) -> Any? {
|
|
531
|
+
// The intent may have cold-launched us with no scene — boot RN first;
|
|
532
|
+
// the library's handler waits for RN before resolving.
|
|
533
|
+
startReactNativeHeadless()
|
|
534
|
+
return RNABAudioBrowser.handler(for: intent)
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
`RNABAudioBrowser.handler(for:)` is the library's **only** intent entry point. It returns an opaque handler object for intents it supports (`INPlayMediaIntent`, `INUpdateMediaAffinityIntent`, `INAddMediaIntent`) and `nil` for anything else — there is no separate registration or setup call. The library resolves the spoken query through your configured [`search`](/guide/search) source (the same one your in-app search uses — it is **not** a browse route), queues the results, and starts playback automatically.
|
|
539
|
+
|
|
540
|
+
::: tip Cold-launch ordering is handled for you
|
|
541
|
+
You don't need to race `configureBrowser` against the intent. The handler **waits for your configured `search` source (and any [Gate](/guide/gate) — a paywall/region check that can intercept playback — if you use one) to be ready** before resolving — so even on a cold launch with RN not yet booted, the query resolves only after your JS has run `configureBrowser`. Just call it as a top-level side-effect (see [Where to call `configureBrowser`](#where-to-call-configurebrowser)).
|
|
542
|
+
:::
|
|
543
|
+
|
|
544
|
+
> **Why a handler object, not `application(_:handle:completionHandler:)`?** The latter is not a real `UIApplicationDelegate` method, so iOS never calls it. With no in-app handler registered, the system falls back to a `UISIntentForwardingAction`, which crashes in `-[INHandleIntentForwardingActionResponse isSuccess]`. Returning a handler from `application(_:handlerFor:)` keeps iOS on the in-app path. **No separate Intents Extension is required** — everything runs in-app.
|
|
545
|
+
|
|
546
|
+
### How It Works
|
|
547
|
+
|
|
548
|
+
1. User taps the search button on a CarPlay tab
|
|
549
|
+
2. Siri activates and listens for a voice query
|
|
550
|
+
3. iOS creates an `INPlayMediaIntent` carrying the parsed query
|
|
551
|
+
4. The system asks your app for a handler via `application(_:handlerFor:)` and drives the intent against it
|
|
552
|
+
5. The library resolves the request and starts playback — **resuming** the last session when the user just said your app's name, otherwise **searching** your content (genre/artist/album/etc. are forwarded as structured search params)
|
|
553
|
+
6. CarPlay's Now Playing screen updates automatically
|
|
554
|
+
|
|
555
|
+
## Loading States
|
|
556
|
+
|
|
557
|
+
::: tip Optional browse polish
|
|
558
|
+
This and Album Line Navigation below are optional refinements to the browse templates — skip them for a minimal integration.
|
|
559
|
+
:::
|
|
560
|
+
|
|
561
|
+
Browse screens show a loading state while their content resolves — pushed destinations, tabs loading lazily, and the startup screen while tabs are queried. On iOS 18.4+ the system loading spinner is shown automatically. Older iOS versions have no spinner API; those screens stay blank unless you supply a [`carPlayLoadingTitle`](/api/types/browser/#carplayloadingtitle):
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
configureBrowser({
|
|
565
|
+
carPlayLoadingTitle: t('loading')
|
|
566
|
+
// ...
|
|
567
|
+
})
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
The title renders as the list's centered empty state and disappears as soon as content arrives.
|
|
571
|
+
|
|
572
|
+
## Album Line Navigation
|
|
573
|
+
|
|
574
|
+
The album line on the Now Playing screen can navigate into the browse tree. Two things are needed for it to become tappable: the track must have an **`album`** string (CarPlay builds the tappable line from it — see the warning below), and an **`albumPath`** pointing at a browse path (a `path` in your [`routes`](/guide/basic-usage), so the destination must exist there). Set `albumPath` to make the album line tappable while that track is active — tapping pushes that path:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
{
|
|
578
|
+
title: 'Sweet Pea',
|
|
579
|
+
artist: 'The Radishes',
|
|
580
|
+
album: 'Greens',
|
|
581
|
+
src: 'https://…',
|
|
582
|
+
albumPath: '/album/greens'
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
For tracks without an `albumPath`, configure a [`resolveAlbumPath`](/api/types/browser/#resolvealbumpath) resolver. It runs when the active track changes (not at tap time), so the album line only becomes tappable when there is actually somewhere to go:
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
configureBrowser({
|
|
590
|
+
// `slugify` here is your own helper (illustrative) — turn an album name
|
|
591
|
+
// into a browse path that exists in your `routes`.
|
|
592
|
+
resolveAlbumPath: (track) =>
|
|
593
|
+
track.album ? `/album/${slugify(track.album)}` : undefined
|
|
594
|
+
// ...
|
|
595
|
+
})
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Return a browse path to enable the album line for that track, or `undefined` to leave it untappable.
|
|
599
|
+
|
|
600
|
+
::: warning Album metadata required
|
|
601
|
+
CarPlay renders the tappable album/artist button as a **separate line built from the track's `album`** (visible as the third metadata line, with a chevron). Tracks without an `album` have no string for CarPlay to turn into a button — the artist line alone is never tappable. Set `album` on any track that should offer album-line navigation. See [Now Playing](/guide/now-playing) for the full field-by-surface rendering matrix.
|
|
602
|
+
:::
|
|
603
|
+
|
|
604
|
+
## Testing
|
|
605
|
+
|
|
606
|
+
Test your app in the `CarPlay Simulator.app`, which is part of the [Additional Tools for Xcode](https://developer.apple.com/download/all/?q=additional%20tools%20for%20xcode). Siri voice search works there, so you can exercise the full intent flow without a car. CarPlay support on the iOS Simulator can be a little flaky — if something looks broken, retry or fall back to a physical device for final verification.
|
|
607
|
+
|
|
608
|
+
### Troubleshooting
|
|
609
|
+
|
|
610
|
+
| Symptom | Likely cause |
|
|
611
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
612
|
+
| Siri mic opens but returns nothing | No [`search` source](#siri-voice-search) configured (Step 0). |
|
|
613
|
+
| The "Ask Siri" cell does nothing | Siri entitlement / Info.plist keys / intent handler not wired (steps 1–4). |
|
|
614
|
+
| CarPlay browse screens hang empty | `startReactNativeHeadless` missing or misnamed — the library calls it by exact selector. |
|
|
615
|
+
| Build error: _module 'AudioBrowser' not found_ | `pod install` hasn't linked the `AudioBrowser` pod. |
|
|
616
|
+
| Crash: _no UISceneConfiguration named …_ | The `name:` in `configurationForConnecting` doesn't match `UISceneConfigurationName` in the manifest. |
|
|
617
|
+
|
|
618
|
+
## Reference
|
|
619
|
+
|
|
620
|
+
See the [example app](https://github.com/radio-garden/react-native-audio-browser/tree/main/apps/example-native/ios/AudioBrowserExample) for a complete working implementation.
|