react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -21
- package/android/build.gradle +35 -12
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
- package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
- package/android/src/main/res/values/strings.xml +25 -5
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
- package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +230 -205
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/SearchDrillIn.swift +7 -7
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
- package/ios/Extension/Section+Copying.swift +29 -0
- package/ios/Extension/String+IcyCharset.swift +21 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
- package/ios/Extension/Track+Copying.swift +8 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +253 -100
- package/ios/Model/NitroTypeStubs.swift +125 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +17 -9
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +46 -13
- package/ios/Player/RetryManager.swift +80 -18
- package/ios/Player/SeekCompletionHandler.swift +3 -1
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +122 -10
- package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryManagerTests.swift +89 -0
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +8 -1
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackPlayer.swift +63 -35
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/LRUCache.swift +4 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/AudioBrowser.js +8 -8
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +12 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +11 -3
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +14 -11
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +3 -2
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +3 -4
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +18 -9
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +23 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +15 -4
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +13 -6
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +175 -100
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +18 -23
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +2 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +12 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +11 -3
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +14 -11
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +3 -2
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/features/playback/state.js +3 -4
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +17 -8
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +19 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +15 -4
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +13 -6
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +28 -14
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +200 -75
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +254 -39
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +175 -100
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +18 -23
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +10 -2
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +14 -11
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +3 -4
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +108 -56
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +7 -4
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +3 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +82 -57
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
- package/nitrogen/generated/android/c++/JSection.hpp +116 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
- package/nitrogen/generated/ios/swift/Section.swift +82 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +28 -63
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
- package/nitrogen/generated/shared/c++/Section.hpp +102 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +18 -30
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +24 -11
- package/src/AudioBrowser.ts +2 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +12 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +11 -3
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +14 -11
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +302 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +3 -2
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +3 -4
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +1 -6
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +126 -76
- package/src/features/player/setup.test.ts +24 -7
- package/src/features/player/setup.ts +22 -10
- package/src/features/player/validateOptions.ts +21 -0
- package/src/features/queue/activeTrack.ts +15 -4
- package/src/features/queue/queue.ts +13 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +147 -0
- package/src/specs/audio-browser.nitro.ts +5 -4
- package/src/style-resolution.test.ts +210 -0
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +7 -3
- package/src/types/browser-nodes.ts +396 -134
- package/src/types/browser.ts +85 -59
- package/src/utils/LazyNativeEmitter.ts +28 -15
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +239 -82
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +284 -37
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +209 -111
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/SearchManager.test.ts +75 -0
- package/src/web/browser/SearchManager.ts +25 -28
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
- package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +247 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'Stop playback after a fixed time or at the end of the current track, counted down natively so it fires with the app backgrounded or the screen off.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Sleep Timer
|
|
6
|
+
|
|
7
|
+
A **sleep timer** stops playback on its own after a set time — the classic
|
|
8
|
+
"fall asleep to the radio" feature. The library runs the timer natively, so it
|
|
9
|
+
keeps counting down and fires even when your app is backgrounded or the screen
|
|
10
|
+
is off. There are two kinds:
|
|
11
|
+
|
|
12
|
+
- **Time-based** — pause after a fixed number of seconds (e.g. "in 30 minutes").
|
|
13
|
+
- **End of track** — pause when the current track finishes playing.
|
|
14
|
+
|
|
15
|
+
Only one timer is active at a time; setting a new one replaces the old.
|
|
16
|
+
|
|
17
|
+
## Setting a timer
|
|
18
|
+
|
|
19
|
+
Call `setSleepTimer` with a duration in **seconds**. This example stops playback
|
|
20
|
+
after 30 minutes:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { setSleepTimer } from 'react-native-audio-browser'
|
|
24
|
+
|
|
25
|
+
setSleepTimer(30 * 60)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Most UIs offer a few presets rather than a free-form duration — multiply the
|
|
29
|
+
minutes you show by 60:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const PRESETS_MINUTES = [15, 30, 45, 60]
|
|
33
|
+
|
|
34
|
+
setSleepTimer(PRESETS_MINUTES[1] * 60) // 30 minutes
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To pause when the current track ends instead of after a fixed time, use
|
|
38
|
+
`setSleepTimerToEndOfTrack`:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { setSleepTimerToEndOfTrack } from 'react-native-audio-browser'
|
|
42
|
+
|
|
43
|
+
setSleepTimerToEndOfTrack()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When the timer completes, the library **pauses** playback (it doesn't stop or
|
|
47
|
+
tear the player down). Pressing play resumes as normal.
|
|
48
|
+
|
|
49
|
+
## Fading out
|
|
50
|
+
|
|
51
|
+
Pass a `fadeDuration` (in seconds) to ramp the volume down to silence as the
|
|
52
|
+
timer runs out — a gentler finish than an abrupt pause:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// Stop in 30 minutes, fading the last 20 seconds to silence.
|
|
56
|
+
setSleepTimer(30 * 60, { fadeDuration: 20 })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The fade always ends exactly at the deadline (if `fadeDuration` is longer than
|
|
60
|
+
the timer itself, it's clamped to the timer's length). Once playback pauses, the
|
|
61
|
+
library restores the pre-fade volume, so the next time the listener hits play it
|
|
62
|
+
starts at normal loudness.
|
|
63
|
+
|
|
64
|
+
During the fade the timer takes priority: seeking or skipping plays into the
|
|
65
|
+
remaining ramp rather than cancelling it. Only pausing, or clearing/replacing
|
|
66
|
+
the timer, stops the fade. The countdown keeps ticking throughout, so you can
|
|
67
|
+
show a "fading out" hint once `secondsLeft` drops to `fadeDuration` (see the
|
|
68
|
+
[complete example](#a-complete-screen)).
|
|
69
|
+
|
|
70
|
+
## Clearing a timer
|
|
71
|
+
|
|
72
|
+
`clearSleepTimer` cancels the active timer. It returns `true` if a timer was
|
|
73
|
+
actually cleared, or `false` if none was set:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { clearSleepTimer } from 'react-native-audio-browser'
|
|
77
|
+
|
|
78
|
+
clearSleepTimer()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Reading the timer in your UI
|
|
82
|
+
|
|
83
|
+
Use the `useSleepTimer` hook to show a live countdown. It re-renders every
|
|
84
|
+
second and returns:
|
|
85
|
+
|
|
86
|
+
- For a time-based timer: `{ time, secondsLeft }`, where `time` is the deadline
|
|
87
|
+
(epoch milliseconds) and `secondsLeft` counts down to `0`.
|
|
88
|
+
- For an end-of-track timer: `{ sleepWhenPlayedToEnd: true }`.
|
|
89
|
+
- `undefined` when no timer is active.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useSleepTimer } from 'react-native-audio-browser'
|
|
93
|
+
|
|
94
|
+
function SleepTimerLabel() {
|
|
95
|
+
const timer = useSleepTimer()
|
|
96
|
+
|
|
97
|
+
if (!timer) return <Text>No sleep timer</Text>
|
|
98
|
+
if ('sleepWhenPlayedToEnd' in timer) {
|
|
99
|
+
return <Text>Stopping at end of track</Text>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const minutes = Math.floor(timer.secondsLeft / 60)
|
|
103
|
+
const seconds = timer.secondsLeft % 60
|
|
104
|
+
return (
|
|
105
|
+
<Text>
|
|
106
|
+
Sleeping in {minutes}m {seconds}s
|
|
107
|
+
</Text>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The countdown only ticks while the app is in the foreground — `secondsLeft`
|
|
113
|
+
doesn't advance in the background (the native timer still fires on time
|
|
114
|
+
regardless). The only reason to care is to avoid pointless per-second re-renders
|
|
115
|
+
while backgrounded: derive the `inactive` flag from React Native's `AppState`
|
|
116
|
+
and pass it in. You can also tune the refresh rate with `updateInterval`
|
|
117
|
+
(milliseconds, default `1000`):
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { useEffect, useState } from 'react'
|
|
121
|
+
import { AppState } from 'react-native'
|
|
122
|
+
import { useSleepTimer } from 'react-native-audio-browser'
|
|
123
|
+
|
|
124
|
+
function useAppInactive() {
|
|
125
|
+
const [inactive, setInactive] = useState(
|
|
126
|
+
() => AppState.currentState !== 'active'
|
|
127
|
+
)
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const sub = AppState.addEventListener('change', (state) =>
|
|
130
|
+
setInactive(state !== 'active')
|
|
131
|
+
)
|
|
132
|
+
return () => sub.remove()
|
|
133
|
+
}, [])
|
|
134
|
+
return inactive
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// In a component: pause the countdown updates while backgrounded.
|
|
138
|
+
const timer = useSleepTimer({ inactive: useAppInactive() })
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Just need to know if one is set?
|
|
142
|
+
|
|
143
|
+
If you only want to toggle a button's appearance, `useSleepTimerActive` is a
|
|
144
|
+
lighter-weight hook that returns a boolean and doesn't re-render every second:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { useSleepTimerActive } from 'react-native-audio-browser'
|
|
148
|
+
|
|
149
|
+
function SleepTimerButton() {
|
|
150
|
+
const active = useSleepTimerActive()
|
|
151
|
+
return <Icon name={active ? 'timer-on' : 'timer-off'} />
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## A complete screen
|
|
156
|
+
|
|
157
|
+
Putting it together — preset durations, an end-of-track option, an off button,
|
|
158
|
+
and a live countdown that switches to a "fading out" hint as the fade begins:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { View, Button, Text } from 'react-native'
|
|
162
|
+
import {
|
|
163
|
+
setSleepTimer,
|
|
164
|
+
setSleepTimerToEndOfTrack,
|
|
165
|
+
clearSleepTimer,
|
|
166
|
+
useSleepTimer
|
|
167
|
+
} from 'react-native-audio-browser'
|
|
168
|
+
|
|
169
|
+
const PRESETS_MINUTES = [15, 30, 45, 60]
|
|
170
|
+
const FADE_SECONDS = 20
|
|
171
|
+
|
|
172
|
+
function SleepTimerScreen() {
|
|
173
|
+
const timer = useSleepTimer()
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<View>
|
|
177
|
+
{PRESETS_MINUTES.map((minutes) => (
|
|
178
|
+
<Button
|
|
179
|
+
key={minutes}
|
|
180
|
+
title={`${minutes} min`}
|
|
181
|
+
onPress={() =>
|
|
182
|
+
setSleepTimer(minutes * 60, { fadeDuration: FADE_SECONDS })
|
|
183
|
+
}
|
|
184
|
+
/>
|
|
185
|
+
))}
|
|
186
|
+
<Button
|
|
187
|
+
title="End of track"
|
|
188
|
+
onPress={() => setSleepTimerToEndOfTrack()}
|
|
189
|
+
/>
|
|
190
|
+
<Button title="Off" onPress={() => clearSleepTimer()} />
|
|
191
|
+
|
|
192
|
+
<Status timer={timer} fadeSeconds={FADE_SECONDS} />
|
|
193
|
+
</View>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function Status(props: {
|
|
198
|
+
timer: ReturnType<typeof useSleepTimer>
|
|
199
|
+
fadeSeconds: number
|
|
200
|
+
}) {
|
|
201
|
+
const { timer, fadeSeconds } = props
|
|
202
|
+
if (!timer) return <Text>No sleep timer</Text>
|
|
203
|
+
if ('sleepWhenPlayedToEnd' in timer) {
|
|
204
|
+
return <Text>Stopping at end of track</Text>
|
|
205
|
+
}
|
|
206
|
+
if (timer.secondsLeft <= fadeSeconds) return <Text>Fading out…</Text>
|
|
207
|
+
|
|
208
|
+
const minutes = Math.floor(timer.secondsLeft / 60)
|
|
209
|
+
const seconds = timer.secondsLeft % 60
|
|
210
|
+
return (
|
|
211
|
+
<Text>
|
|
212
|
+
Sleeping in {minutes}m {seconds}s
|
|
213
|
+
</Text>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
When the timer fires (or is cleared), `useSleepTimer` returns `undefined` and
|
|
219
|
+
the countdown stops on its own — so `Status` falls back to "No sleep timer"
|
|
220
|
+
without any extra handling on your part.
|
|
221
|
+
|
|
222
|
+
## Outside of React
|
|
223
|
+
|
|
224
|
+
If you're not in a component, read the current timer with `getSleepTimer` or
|
|
225
|
+
subscribe to changes with `onSleepTimerChanged`. Note that `getSleepTimer`
|
|
226
|
+
returns the raw timer state (`{ time }` in epoch milliseconds, or
|
|
227
|
+
`{ sleepWhenPlayedToEnd }`, or `null`) — it doesn't compute `secondsLeft`; that
|
|
228
|
+
convenience is added by the `useSleepTimer` hook.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { getSleepTimer, onSleepTimerChanged } from 'react-native-audio-browser'
|
|
232
|
+
|
|
233
|
+
getSleepTimer() // { time } | { sleepWhenPlayedToEnd } | null
|
|
234
|
+
|
|
235
|
+
// addListener returns a cleanup function — call it to unsubscribe.
|
|
236
|
+
const unsubscribe = onSleepTimerChanged.addListener((timer) => {
|
|
237
|
+
console.log('sleep timer is now', timer)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// later, when you no longer need updates:
|
|
241
|
+
unsubscribe()
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Behavior and edge cases
|
|
245
|
+
|
|
246
|
+
- **Setting replaces, it doesn't stack.** Calling any setter while a timer is
|
|
247
|
+
running swaps it for the new one — there's only ever a single active timer.
|
|
248
|
+
- **Not persisted across launches.** The timer lives in memory. If the app is
|
|
249
|
+
killed (or the OS reclaims it), the timer is gone on next launch — it does not
|
|
250
|
+
resume. Persist your own "timer set at" value if you need to restore the UI.
|
|
251
|
+
- **No input validation.** `seconds` is passed straight through to the native
|
|
252
|
+
scheduler, which isn't clamped — a value of `0` or a negative number fires
|
|
253
|
+
essentially immediately (pausing right away). Validate in your own UI if that
|
|
254
|
+
matters.
|
|
255
|
+
- **Nothing throws.** Every function here is synchronous; the setters return
|
|
256
|
+
`void` and `clearSleepTimer` returns a boolean. No `try/catch` needed.
|
|
257
|
+
|
|
258
|
+
## API summary
|
|
259
|
+
|
|
260
|
+
| API | Purpose |
|
|
261
|
+
| ----------------------------------------------- | --------------------------------------------------------------- |
|
|
262
|
+
| `setSleepTimer(seconds, { fadeDuration? })` | Pause playback after `seconds`, optionally fading out. |
|
|
263
|
+
| `setSleepTimerToEndOfTrack()` | Pause when the current track finishes. |
|
|
264
|
+
| `clearSleepTimer()` | Cancel the active timer; returns `true` if one was cleared. |
|
|
265
|
+
| `getSleepTimer()` | Read the raw timer state (no `secondsLeft`). |
|
|
266
|
+
| `onSleepTimerChanged` | Subscribe to timer changes outside React; returns a cleanup fn. |
|
|
267
|
+
| `useSleepTimer({ updateInterval?, inactive? })` | Live state with a `secondsLeft` countdown. |
|
|
268
|
+
| `useSleepTimerActive()` | Boolean: is any timer set? |
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 'The Track shape — the single unit of content: required fields, identity, metadata, and artwork, used for browse rows, queue items, and lock-screen metadata.'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Track
|
|
6
|
+
|
|
7
|
+
A **Track** is the one unit of content in the library. The same object describes a row in a browse list, an item in the playback queue, and the metadata on the lock screen — so once you know the Track shape, you can read and build content everywhere.
|
|
8
|
+
|
|
9
|
+
A Track is just a plain object. The only required field is `title`, plus **at least one** of `path` (browsable) or `src` (playable):
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const station: Track = {
|
|
13
|
+
title: 'Smooth Floret FM',
|
|
14
|
+
src: 'https://example.com/floret.mp3'
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A real track usually carries more — identity, metadata, and artwork:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const episode: Track = {
|
|
22
|
+
id: 'atp-545',
|
|
23
|
+
title: 'Episode 545',
|
|
24
|
+
artist: 'Accidental Tech Podcast',
|
|
25
|
+
album: 'ATP',
|
|
26
|
+
artwork: 'https://example.com/atp.jpg',
|
|
27
|
+
duration: 7200,
|
|
28
|
+
src: 'https://example.com/atp/545.mp3'
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This guide is the field-by-field reference. For _where_ tracks come from (routes, callbacks, HTTP) see the [Browser](/guide/browser) guide.
|
|
33
|
+
|
|
34
|
+
## Browsable, playable, or both
|
|
35
|
+
|
|
36
|
+
Two fields decide what a Track _does_. A Track must set at least one of them, and may set both:
|
|
37
|
+
|
|
38
|
+
| Field | Makes the track | Effect |
|
|
39
|
+
| ------ | --------------- | ---------------------------------------------------- |
|
|
40
|
+
| `path` | **browsable** | a container — tapping it navigates into its children |
|
|
41
|
+
| `src` | **playable** | a leaf — the player can stream it |
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// Browsable: a folder you navigate into
|
|
45
|
+
{ title: 'Jazz', path: '/browse/jazz' }
|
|
46
|
+
|
|
47
|
+
// Playable: a station you stream
|
|
48
|
+
{ title: 'Smooth Floret FM', src: 'https://example.com/floret.mp3' }
|
|
49
|
+
|
|
50
|
+
// Both: a podcast episode you can open *and* play
|
|
51
|
+
{ title: 'Episode 42', path: '/episodes/42', src: 'https://…/42.mp3' }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`src` is usually an absolute audio URL, but it can be any string — a file path or your own identifier — that you turn into a real request in `media.resolve` (see [Browser → Media and artwork](/guide/browser#media-and-artwork)).
|
|
55
|
+
|
|
56
|
+
## Identity: `id`
|
|
57
|
+
|
|
58
|
+
`id` is an **opaque, stable identifier** you control. The library never parses it — it round-trips verbatim through `setQueue`, the queue, `getActiveTrack`, and `onActiveTrackChanged`.
|
|
59
|
+
|
|
60
|
+
Why it matters: when playback moves by an _external_ control — a lock-screen / CarPlay / Android Auto / Bluetooth next-or-previous — you find out which of _your_ items became active by matching its `id`, without re-parsing the `src`:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// the callback gets a PlaybackActiveTrackChangedEvent — use event.track
|
|
64
|
+
const unsubscribe = onActiveTrackChanged.addListener((event) => {
|
|
65
|
+
const episode = myEpisodes.find((e) => e.id === event.track?.id)
|
|
66
|
+
// …update your UI for `episode`
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The event also carries `index`, `lastTrack`, and `lastIndex`; `addListener` returns an unsubscribe function.
|
|
71
|
+
|
|
72
|
+
`id` is also passed to the per-track `resolve` hooks, so you can set `src` to the same stable identifier and synthesise the real request/artwork from it at request time.
|
|
73
|
+
|
|
74
|
+
::: tip `id` is optional
|
|
75
|
+
A track's identity is its `id`, falling back to `src` — so if the same item's `src` string is identical wherever it appears, you can skip `id` entirely. Set ids consistently across your content or not at all: identity is compared whole, and a row carrying an `id` never matches a track without one.
|
|
76
|
+
:::
|
|
77
|
+
|
|
78
|
+
## Display fields
|
|
79
|
+
|
|
80
|
+
These set the text shown across surfaces. Only `title` is required.
|
|
81
|
+
|
|
82
|
+
| Field | Shown where |
|
|
83
|
+
| ----------------------- | ---------------------------------------------------------------------------- |
|
|
84
|
+
| `title` | primary line — browse rows **and** now-playing (required) |
|
|
85
|
+
| `subtitle` | secondary line in **browse lists** only |
|
|
86
|
+
| `artist` | secondary line on **now-playing / lock screen**; the head-unit "artist" slot |
|
|
87
|
+
| `album` | album name (and gates the tappable album line — see `albumPath`) |
|
|
88
|
+
| `genre` / `description` | extra metadata |
|
|
89
|
+
| `duration` | catalog metadata in seconds, for your UI |
|
|
90
|
+
|
|
91
|
+
::: warning `subtitle` and `artist` are separate — neither falls back to the other
|
|
92
|
+
`subtitle` drives browse-list rows; `artist` drives the now-playing line and Bluetooth metadata. Set each one for the surface you want it on.
|
|
93
|
+
:::
|
|
94
|
+
|
|
95
|
+
`duration` is **catalog metadata only** (e.g. an episode list) — it does _not_ drive the now-playing scrubber. Every surface derives elapsed/remaining from the player itself.
|
|
96
|
+
|
|
97
|
+
## Artwork
|
|
98
|
+
|
|
99
|
+
Set `artwork` to an image URL. It's transformed through your [artwork configuration](/guide/browser#media-and-artwork) and the ready-to-render result comes back on the **output-only** `artworkSource`, shaped for React Native's `<Image>`:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
<Image source={track.artworkSource} />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Don't set `artworkSource` yourself — the library populates it.
|
|
106
|
+
|
|
107
|
+
**SF Symbols (iOS).** Prefix `artwork` with `sf:` to use a system symbol, with optional colors — `bg` (background, transparent if omitted) and `fg` (symbol, black if omitted):
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
{
|
|
111
|
+
title: 'Favorites',
|
|
112
|
+
path: '/favorites',
|
|
113
|
+
artwork: 'sf:heart.fill?bg=#FF0090&fg=#fff'
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
On CarPlay, an SF Symbol with no explicit colors adapts to light/dark mode automatically.
|
|
118
|
+
|
|
119
|
+
**Tinting (`style: { artworkRendering: 'stencil' }`).** For monochrome icons, declare this so CarPlay tints them per appearance — black in light mode, white in dark. Use it for icons, not full-color album art. iOS CarPlay only; Android Auto is dark-only, so ship appropriately-colored (e.g. white) icons there. On Android, an `android.resource://…` artwork URI combines with any declared `display` the library forwards to select Android Auto's icon treatment of that layout (icon margins + system tinting for vector drawables).
|
|
120
|
+
|
|
121
|
+
**Light and dark images.** When the appearances need genuinely different artwork rather than the same shape recolored, set `artwork` to an [`ArtworkVariants`](/api/types/browser-nodes/#artworkvariants) pair — `{ light, dark }`. CarPlay adapts between them in place; everywhere that needs a single image uses `dark`. See [Artwork → Light and dark](/guide/artwork#light-and-dark-artwork).
|
|
122
|
+
|
|
123
|
+
## Per-track request override
|
|
124
|
+
|
|
125
|
+
[`request`](/api/types/browser-nodes/#trackrequest) is a **narrow** per-track override for _how_ this track's audio request is made — merged last, after the shared `request` and `media` layers:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
{
|
|
129
|
+
title: 'Member Stream',
|
|
130
|
+
src: 'https://example.com/members/floret.mp3',
|
|
131
|
+
request: { headers: { authorization: 'Bearer …' } }
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
It carries only `userAgent`, `headers`, and `query` — deliberately **not** `baseUrl` / `path` / `method` / `body`, so a track (often server-sourced) can customise _how_ it's fetched but can't repoint its own host or verb. Like every field, it round-trips verbatim through the queue.
|
|
136
|
+
|
|
137
|
+
## Presentation on native surfaces
|
|
138
|
+
|
|
139
|
+
Optional fields that change how a Track renders on CarPlay / Android Auto. Each surface applies the ones it supports; an unsupported field is a no-op, never an error.
|
|
140
|
+
|
|
141
|
+
| Field | Effect | Platform |
|
|
142
|
+
| ------------------------------------------ | ---------------------------------------------------------------------- | -------------------------- |
|
|
143
|
+
| `style: { display: 'list' \| 'grid' }` | on a browsable track: the layout of the page it opens | Android Auto / AAOS |
|
|
144
|
+
| `style: { artworkRendering: 'stencil' }` | tint monochrome artwork to the surface appearance | CarPlay |
|
|
145
|
+
| `style: { imageShape: 'circular' }` | crop grid-tile artwork circular (people) instead of rounded | CarPlay (iOS 26+) |
|
|
146
|
+
| `style: { accessorySymbol: '…' }` | SF Symbol accessory; `'none'` restores the derived accessory | CarPlay |
|
|
147
|
+
| `style: { cardTint: '…', cardImage: '…' }` | card color and image mode, when the section renders `gridTile: 'card'` | CarPlay (iOS 26+) |
|
|
148
|
+
| `disabled` | unavailable: never plays; grayed where drawable, hidden elsewhere | all |
|
|
149
|
+
| `favorited` | filled/empty heart (needs the `favorite` capability) | Android Auto, notification |
|
|
150
|
+
| `live` | a "live" indicator | iOS now-playing |
|
|
151
|
+
| `albumPath` | make the now-playing album line tappable | CarPlay |
|
|
152
|
+
| `icyCharset` | decode ICY titles a station sends in a legacy code page | iOS, Android |
|
|
153
|
+
|
|
154
|
+
A couple of constraints worth knowing:
|
|
155
|
+
|
|
156
|
+
- **`display` goes on the browsable track as it appears in its parent's list** — Android Auto reads it there, before the page resolves, to decide how to lay out the page once you navigate in. It is only sent when you declare it — the library never infers it from the page's content. The drilled-into page's own sections override it per section via their `style`.
|
|
157
|
+
- **`albumPath` requires `album`** (CarPlay renders the tappable line from album metadata), and pairs with `resolveAlbumPath` in the [Browser config](/guide/browser).
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
// This track as it appears in its parent's list (no children here — the
|
|
161
|
+
// page supplies those): it declares the layout of the page it opens.
|
|
162
|
+
{
|
|
163
|
+
path: '/genres',
|
|
164
|
+
title: 'Genres',
|
|
165
|
+
// the page this opens lays out as tiles in Android Auto
|
|
166
|
+
style: { display: 'grid' }
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Sections — grouping and tile layouts
|
|
171
|
+
|
|
172
|
+
A Track appears in two roles. As a **row** in its parent's list it's a handle: its own `style.display` only advertises the layout of the page it opens (see [above](#presentation-on-native-surfaces)). Once resolved, that same Track **is the page** — a [`ResolvedTrack`](/api/types/browser-nodes/#resolvedtrack) — and that is when it carries `sections`.
|
|
173
|
+
|
|
174
|
+
Headers and layout belong to the container role: declare them on the page's [`Section`](/api/types/browser-nodes/#section)s (`{ title?, subtitle?, style?, path?, children }`), never on the rows. A section groups tracks under a header, and its `style` block's `display` picks their layout: `'list'` rows (the default) or `'grid'` artwork tiles — wrapping unless `gridWrap: false` keeps them to a single line. The section's `path` is the navigation target for its header / "view all" surface; without one the header is a plain label, not a link — the section is self-contained.
|
|
175
|
+
|
|
176
|
+
A tile with `src` **plays immediately on tap** (same selection path as a playable list row — a station app can make tiles play their station directly); otherwise its `path` is navigated. Tiles are ordinary Tracks, so playable ones carry the now-playing fields any track would (`id`, `artist`, `album`, `albumPath`, `live`, `request`). Tapping a playable child queues **its section** on every surface (see [Playback behavior](/guide/browser#playback-behavior)):
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
{
|
|
180
|
+
path: '/home',
|
|
181
|
+
title: 'Home',
|
|
182
|
+
sections: [
|
|
183
|
+
{
|
|
184
|
+
title: 'Featured',
|
|
185
|
+
style: { display: 'grid', gridWrap: false },
|
|
186
|
+
path: '/browse/featured', // header tap → the full list
|
|
187
|
+
children: [
|
|
188
|
+
{ title: 'Jazz', path: '/browse/jazz', artwork: 'https://…/jazz.jpg' },
|
|
189
|
+
{
|
|
190
|
+
title: 'Beacon FM',
|
|
191
|
+
src: 'https://audio.example.com/beacon.mp3', // tap plays it
|
|
192
|
+
artist: 'Springfield',
|
|
193
|
+
live: true,
|
|
194
|
+
artwork: 'https://…/beacon.jpg'
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{ title: 'All stations', children: [...] }
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Each surface renders a declaration as its nearest supported form — CarPlay truncates a single-line grid (`gridWrap: false`) at the tiles that fit, Android Auto's grid always wraps, app UIs typically render a horizontal scroller — see [Browser → Presentation](/guide/browser#presentation) for the full rundown.
|
|
204
|
+
|
|
205
|
+
## ResolvedTrack — a resolved page
|
|
206
|
+
|
|
207
|
+
A [**`ResolvedTrack`**](/api/types/browser-nodes/#resolvedtrack) is the same Track plus its resolved content — the page the browser is currently showing. Its `path` is always present, and `sections` is the [`Section`](/api/types/browser-nodes/#section)`[]` to render (optional — a leaf page has none). A page authored with a plain `children` list resolves to a single untitled section: `children` is authoring sugar, never populated on resolved output — read `sections`.
|
|
208
|
+
|
|
209
|
+
Navigation is **fire-and-forget**: `navigate(path)` returns `void`. Read the resolved page from `useContent()` (or `getContent()` / `onContentChanged`):
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { navigate, useContent } from 'react-native-audio-browser'
|
|
213
|
+
|
|
214
|
+
navigate('/browse/jazz') // moves the browser to this path
|
|
215
|
+
|
|
216
|
+
function JazzPage() {
|
|
217
|
+
const page = useContent() // ResolvedTrack | undefined
|
|
218
|
+
// each section: { title?, style?, path?, children: Track[] }
|
|
219
|
+
return <List sections={page?.sections ?? []} /> // sections may be undefined
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A `ResolvedTrack` may also set [`carPlaySiriListButton`](/api/types/browser/#carplaysirilistbuttonposition)`: 'top' | 'bottom'` to show the "Ask Siri to Play" cell on that page (needs Siri wiring — see [CarPlay](/guide/carplay)).
|
|
224
|
+
|
|
225
|
+
## Using tracks in your code
|
|
226
|
+
|
|
227
|
+
Tracks flow through the queue and playback API. A minimal playable track needs just `title` + `src`:
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { setQueue, play, useActiveTrack } from 'react-native-audio-browser'
|
|
231
|
+
|
|
232
|
+
setQueue([
|
|
233
|
+
{ title: 'Smooth Floret FM', src: 'https://example.com/floret.mp3' },
|
|
234
|
+
{ title: 'The Stalk 88.5', src: 'https://example.com/stalk.mp3' }
|
|
235
|
+
])
|
|
236
|
+
play()
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`load(track)` plays a single track, `add(tracks)` appends to the queue, and `getActiveTrack()` / `useActiveTrack()` read what's playing now (with its `artworkSource` populated for your UI). See [Basic Usage](/guide/basic-usage) for the full queue and playback API.
|
|
240
|
+
|
|
241
|
+
## Where to go next
|
|
242
|
+
|
|
243
|
+
- **[Browser](/guide/browser)** — where tracks come from: tabs, routes, sources.
|
|
244
|
+
- **[Now Playing](/guide/now-playing)** — how a track's fields drive the lock-screen / car metadata.
|
|
245
|
+
- **[Favorites](/guide/favorites)** — the `favorited` field and the heart button.
|
|
246
|
+
|
|
247
|
+
For the exact types — every field and its JSDoc — see [`Track`](/api/types/browser-nodes/#track) and [`ResolvedTrack`](/api/types/browser-nodes/#resolvedtrack) in the API reference.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import NitroModules
|
|
3
|
-
|
|
4
|
-
// MARK: - ImageRowItem → Track Conversion
|
|
5
|
-
|
|
6
|
-
extension ImageRowItem {
|
|
7
|
-
/// Creates a minimal Track from an ImageRowItem for reuse with item selection and artwork loading.
|
|
8
|
-
/// A `src`-bearing item becomes a playable track (thumbnail tap plays it); otherwise the
|
|
9
|
-
/// track only navigates via `url`.
|
|
10
|
-
func toTrack() -> Track {
|
|
11
|
-
Track(
|
|
12
|
-
id: id, url: url, src: src, artwork: artwork.map { .first($0) },
|
|
13
|
-
artworkSource: artworkSource, request: request,
|
|
14
|
-
artworkCarPlayTinted: nil, title: title, subtitle: nil, artist: artist,
|
|
15
|
-
albumUrl: albumUrl, album: album, description: nil, genre: nil, duration: nil, style: nil,
|
|
16
|
-
childrenStyle: nil, favorited: nil, groupTitle: nil, live: live, imageRow: nil,
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
}
|
package/ios/Model/MediaURL.swift
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
struct MediaURL {
|
|
4
|
-
let value: URL
|
|
5
|
-
let isLocal: Bool
|
|
6
|
-
private let originalObject: Any
|
|
7
|
-
|
|
8
|
-
init?(object: Any?) {
|
|
9
|
-
guard let object else { return nil }
|
|
10
|
-
originalObject = object
|
|
11
|
-
|
|
12
|
-
// This is based on logic found in RCTConvert NSURLRequest
|
|
13
|
-
if let localObject = object as? [String: Any] {
|
|
14
|
-
var urlString = localObject["uri"] as? String ?? localObject["url"] as! String
|
|
15
|
-
|
|
16
|
-
if let bundleName = localObject["bundle"] as? String {
|
|
17
|
-
urlString = String(format: "%@.bundle/%@", bundleName, urlString)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
isLocal = !urlString.lowercased().hasPrefix("http")
|
|
21
|
-
guard let converted = Self.convertToURL(urlString) else { return nil }
|
|
22
|
-
value = converted
|
|
23
|
-
} else {
|
|
24
|
-
let urlString = object as! String
|
|
25
|
-
isLocal = urlString.lowercased().hasPrefix("file://")
|
|
26
|
-
guard let converted = Self.convertToURL(urlString) else { return nil }
|
|
27
|
-
value = converted
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/// Convert a string to URL, matching RCTConvert NSURL behavior
|
|
32
|
-
private static func convertToURL(_ string: String) -> URL? {
|
|
33
|
-
// Try as absolute URL first
|
|
34
|
-
if let url = URL(string: string), url.scheme != nil {
|
|
35
|
-
return url
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// If it has a scheme but failed, try percent-encoding
|
|
39
|
-
if string.contains("://") {
|
|
40
|
-
var allowed = CharacterSet()
|
|
41
|
-
allowed.formUnion(.urlUserAllowed)
|
|
42
|
-
allowed.formUnion(.urlPasswordAllowed)
|
|
43
|
-
allowed.formUnion(.urlHostAllowed)
|
|
44
|
-
allowed.formUnion(.urlPathAllowed)
|
|
45
|
-
allowed.formUnion(.urlQueryAllowed)
|
|
46
|
-
allowed.formUnion(.urlFragmentAllowed)
|
|
47
|
-
if let encoded = string.addingPercentEncoding(withAllowedCharacters: allowed),
|
|
48
|
-
let url = URL(string: encoded)
|
|
49
|
-
{
|
|
50
|
-
return url
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Assume it's a local path
|
|
55
|
-
var path = string.removingPercentEncoding ?? string
|
|
56
|
-
|
|
57
|
-
if path.hasPrefix("~") {
|
|
58
|
-
// Expand tilde for user directory
|
|
59
|
-
path = NSString(string: path).expandingTildeInPath
|
|
60
|
-
} else if !path.hasPrefix("/") {
|
|
61
|
-
// Not absolute - assume it's a bundle resource path
|
|
62
|
-
path = (Bundle.main.resourcePath ?? "") + "/" + path
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return URL(fileURLWithPath: path)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
///
|
|
2
|
-
/// JFavoriteConfig.hpp
|
|
3
|
-
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
-
/// https://github.com/mrousavy/nitro
|
|
5
|
-
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
-
///
|
|
7
|
-
|
|
8
|
-
#pragma once
|
|
9
|
-
|
|
10
|
-
#include <fbjni/fbjni.h>
|
|
11
|
-
#include "FavoriteConfig.hpp"
|
|
12
|
-
|
|
13
|
-
#include "FavoritesMatchMode.hpp"
|
|
14
|
-
#include "JFavoritesMatchMode.hpp"
|
|
15
|
-
|
|
16
|
-
namespace margelo::nitro::audiobrowser {
|
|
17
|
-
|
|
18
|
-
using namespace facebook;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The C++ JNI bridge between the C++ struct "FavoriteConfig" and the the Kotlin data class "FavoriteConfig".
|
|
22
|
-
*/
|
|
23
|
-
struct JFavoriteConfig final: public jni::JavaClass<JFavoriteConfig> {
|
|
24
|
-
public:
|
|
25
|
-
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/audiobrowser/FavoriteConfig;";
|
|
26
|
-
|
|
27
|
-
public:
|
|
28
|
-
/**
|
|
29
|
-
* Convert this Java/Kotlin-based struct to the C++ struct FavoriteConfig by copying all values to C++.
|
|
30
|
-
*/
|
|
31
|
-
[[maybe_unused]]
|
|
32
|
-
[[nodiscard]]
|
|
33
|
-
FavoriteConfig toCpp() const {
|
|
34
|
-
static const auto clazz = javaClassStatic();
|
|
35
|
-
static const auto fieldMatch = clazz->getField<JFavoritesMatchMode>("match");
|
|
36
|
-
jni::local_ref<JFavoritesMatchMode> match = this->getFieldValue(fieldMatch);
|
|
37
|
-
return FavoriteConfig(
|
|
38
|
-
match->toCpp()
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public:
|
|
43
|
-
/**
|
|
44
|
-
* Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java.
|
|
45
|
-
*/
|
|
46
|
-
[[maybe_unused]]
|
|
47
|
-
static jni::local_ref<JFavoriteConfig::javaobject> fromCpp(const FavoriteConfig& value) {
|
|
48
|
-
using JSignature = JFavoriteConfig(jni::alias_ref<JFavoritesMatchMode>);
|
|
49
|
-
static const auto clazz = javaClassStatic();
|
|
50
|
-
static const auto create = clazz->getStaticMethod<JSignature>("fromCpp");
|
|
51
|
-
return create(
|
|
52
|
-
clazz,
|
|
53
|
-
JFavoritesMatchMode::fromCpp(value.match)
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
} // namespace margelo::nitro::audiobrowser
|