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
|
@@ -4,26 +4,50 @@ import android.os.Bundle
|
|
|
4
4
|
import androidx.media3.common.Player as MediaPlayer
|
|
5
5
|
import androidx.media3.session.CommandButton
|
|
6
6
|
import androidx.media3.session.MediaSession
|
|
7
|
-
import androidx.media3.session.R
|
|
8
7
|
import androidx.media3.session.SessionCommand
|
|
9
8
|
import androidx.media3.session.SessionCommands
|
|
10
|
-
import com.margelo.nitro.audiobrowser.NotificationButton
|
|
11
|
-
import com.margelo.nitro.audiobrowser.NotificationButtonLayout
|
|
12
9
|
import com.margelo.nitro.audiobrowser.PlayerCapabilities
|
|
10
|
+
import com.margelo.nitro.audiobrowser.RemoteButton
|
|
11
|
+
import com.margelo.nitro.audiobrowser.RemoteButtonLayout
|
|
13
12
|
import timber.log.Timber
|
|
14
13
|
|
|
14
|
+
/**
|
|
15
|
+
* A slot's Media3 preference list: the requested slot, then OVERFLOW as a fallback.
|
|
16
|
+
*
|
|
17
|
+
* `slots` is a preference order, not a coordinate — Media3 places a button in the first slot in the
|
|
18
|
+
* list "that exists, isn't already occupied and that allows this type of button". Naming OVERFLOW
|
|
19
|
+
* second means a button whose primary position is unavailable on some surface demotes instead of
|
|
20
|
+
* disappearing, because every flattening
|
|
21
|
+
* (`CommandButton.getCustomLayoutFromMediaButtonPreferences`, run by both `MediaSessionLegacyStub`
|
|
22
|
+
* for Android Auto and the Android 13+ controls, and `DefaultMediaNotificationProvider` for the
|
|
23
|
+
* notification) keeps a button only if it won BACK, won FORWARD, or *contains* OVERFLOW.
|
|
24
|
+
*/
|
|
25
|
+
internal fun media3SlotsFor(slot: ButtonSlot): IntArray =
|
|
26
|
+
when (slot) {
|
|
27
|
+
ButtonSlot.BACK -> intArrayOf(CommandButton.SLOT_BACK, CommandButton.SLOT_OVERFLOW)
|
|
28
|
+
ButtonSlot.FORWARD -> intArrayOf(CommandButton.SLOT_FORWARD, CommandButton.SLOT_OVERFLOW)
|
|
29
|
+
ButtonSlot.OVERFLOW -> intArrayOf(CommandButton.SLOT_OVERFLOW)
|
|
30
|
+
}
|
|
31
|
+
|
|
15
32
|
/**
|
|
16
33
|
* MediaSession manager that handles command configuration and execution.
|
|
17
34
|
*
|
|
18
35
|
* Responsibilities:
|
|
19
|
-
* - Maps player capabilities to MediaSession commands
|
|
36
|
+
* - Maps player capabilities to MediaSession commands
|
|
37
|
+
* - Builds the single, slot-annotated button layout published to every surface
|
|
20
38
|
* - Updates MediaSession configuration and applies changes immediately
|
|
21
39
|
* - Builds connection results for new MediaSession controllers
|
|
22
40
|
* - Handles execution of custom MediaSession commands (jump actions)
|
|
23
|
-
* - Maintains proper separation between global capabilities and notification-specific controls
|
|
24
41
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
42
|
+
* Capabilities decide what the player can do; the button layout decides where those buttons sit.
|
|
43
|
+
* Placement never revokes a capability — a control left out of the layout still works from a
|
|
44
|
+
* Bluetooth remote or headset.
|
|
45
|
+
*
|
|
46
|
+
* There is ONE layout, published session-wide via `setMediaButtonPreferences`. Media3 fans it out:
|
|
47
|
+
* `MediaNotificationManager` renders it as the MediaStyle notification (Android 12 and below), and
|
|
48
|
+
* `MediaSessionLegacyStub` converts it into the platform PlaybackState plus the slot-reservation
|
|
49
|
+
* extras that Android Auto and the Android 13+ system media controls read. Publishing
|
|
50
|
+
* per-controller instead would reach neither, since Android Auto connects as a legacy controller.
|
|
27
51
|
*/
|
|
28
52
|
class MediaSessionCommandManager {
|
|
29
53
|
|
|
@@ -31,31 +55,51 @@ class MediaSessionCommandManager {
|
|
|
31
55
|
private const val CUSTOM_ACTION_JUMP_BACKWARD = "JUMP_BACKWARD"
|
|
32
56
|
private const val CUSTOM_ACTION_JUMP_FORWARD = "JUMP_FORWARD"
|
|
33
57
|
const val CUSTOM_ACTION_FAVORITE = "FAVORITE"
|
|
34
|
-
}
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
/** Matches PlayerUpdateOptions.forwardJumpInterval/backwardJumpInterval */
|
|
60
|
+
private const val DEFAULT_JUMP_INTERVAL = 15.0
|
|
61
|
+
}
|
|
39
62
|
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Media3 ships numbered icons for 5, 10, 15 and 30 seconds; every other interval falls back to
|
|
65
|
+
* the unnumbered arrow.
|
|
66
|
+
*
|
|
67
|
+
* The match is exact, not rounded. A numbered icon is a claim about how far the button seeks, so
|
|
68
|
+
* a 14.6s interval must not draw "15" — it would promise a jump the player never makes. Better an
|
|
69
|
+
* honest arrow than a wrong number.
|
|
70
|
+
*
|
|
71
|
+
* The icon constant — not the drawable — is what a controller that renders its own UI reads, and
|
|
72
|
+
* it is carried through the legacy conversion Android Auto consumes. Building a button with only
|
|
73
|
+
* `setIconResId` leaves the constant at ICON_UNDEFINED, so the car has nothing to draw.
|
|
74
|
+
*/
|
|
75
|
+
private fun jumpForwardIcon(seconds: Double): Int =
|
|
76
|
+
when (seconds) {
|
|
77
|
+
5.0 -> CommandButton.ICON_SKIP_FORWARD_5
|
|
78
|
+
10.0 -> CommandButton.ICON_SKIP_FORWARD_10
|
|
79
|
+
15.0 -> CommandButton.ICON_SKIP_FORWARD_15
|
|
80
|
+
30.0 -> CommandButton.ICON_SKIP_FORWARD_30
|
|
81
|
+
else -> CommandButton.ICON_SKIP_FORWARD
|
|
82
|
+
}
|
|
44
83
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
84
|
+
private fun jumpBackwardIcon(seconds: Double): Int =
|
|
85
|
+
when (seconds) {
|
|
86
|
+
5.0 -> CommandButton.ICON_SKIP_BACK_5
|
|
87
|
+
10.0 -> CommandButton.ICON_SKIP_BACK_10
|
|
88
|
+
15.0 -> CommandButton.ICON_SKIP_BACK_15
|
|
89
|
+
30.0 -> CommandButton.ICON_SKIP_BACK_30
|
|
90
|
+
else -> CommandButton.ICON_SKIP_BACK
|
|
91
|
+
}
|
|
48
92
|
|
|
49
|
-
/**
|
|
50
|
-
|
|
93
|
+
/** Player commands available to every controller, derived from capabilities alone */
|
|
94
|
+
var playerCommands: MediaPlayer.Commands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS
|
|
51
95
|
private set
|
|
52
96
|
|
|
53
|
-
/**
|
|
54
|
-
lateinit var
|
|
97
|
+
/** Session commands available to every controller, derived from capabilities alone */
|
|
98
|
+
lateinit var sessionCommands: SessionCommands
|
|
55
99
|
private set
|
|
56
100
|
|
|
57
|
-
/**
|
|
58
|
-
lateinit var
|
|
101
|
+
/** The single slot-annotated button layout, published to every surface */
|
|
102
|
+
lateinit var buttonPreferences: List<CommandButton>
|
|
59
103
|
private set
|
|
60
104
|
|
|
61
105
|
/** Stored state for rebuilding layout */
|
|
@@ -74,41 +118,37 @@ class MediaSessionCommandManager {
|
|
|
74
118
|
repeatMode = null,
|
|
75
119
|
playbackRate = null,
|
|
76
120
|
)
|
|
77
|
-
private var
|
|
78
|
-
private var currentSearchAvailable: Boolean = false
|
|
121
|
+
private var currentRemoteButtonLayout: RemoteButtonLayout? = null
|
|
79
122
|
private var currentFavorited: Boolean? = null
|
|
123
|
+
private var currentForwardJumpInterval: Double = DEFAULT_JUMP_INTERVAL
|
|
124
|
+
private var currentBackwardJumpInterval: Double = DEFAULT_JUMP_INTERVAL
|
|
80
125
|
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
*/
|
|
84
|
-
private val controlPlayerCommands: Map<Control, @MediaPlayer.Command Int> =
|
|
126
|
+
/** The Media3 player command behind each Capability (FAVORITE is a session command). */
|
|
127
|
+
private val capabilityPlayerCommands: Map<Capability, @MediaPlayer.Command Int> =
|
|
85
128
|
mapOf(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
Capability.PLAY_PAUSE to MediaPlayer.COMMAND_PLAY_PAUSE,
|
|
130
|
+
Capability.STOP to MediaPlayer.COMMAND_STOP,
|
|
131
|
+
Capability.SEEK_TO to MediaPlayer.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
|
132
|
+
Capability.SKIP_TO_NEXT to MediaPlayer.COMMAND_SEEK_TO_NEXT,
|
|
133
|
+
Capability.SKIP_TO_PREVIOUS to MediaPlayer.COMMAND_SEEK_TO_PREVIOUS,
|
|
134
|
+
Capability.JUMP_FORWARD to MediaPlayer.COMMAND_SEEK_FORWARD,
|
|
135
|
+
Capability.JUMP_BACKWARD to MediaPlayer.COMMAND_SEEK_BACK,
|
|
93
136
|
)
|
|
94
137
|
|
|
95
138
|
// Declared above `init` deliberately: `init` calls `buildPlayerCommands`, which reads
|
|
96
|
-
// `
|
|
139
|
+
// `capabilityPlayerCommands`. Kotlin initializes properties in textual order, so this val must
|
|
97
140
|
// precede the init block or it would still be null when the constructor runs (NPE).
|
|
98
141
|
init {
|
|
99
142
|
// Initialize with defaults matching PlayerUpdateOptions (all capabilities enabled)
|
|
100
143
|
val defaultCapabilities = currentCapabilities
|
|
101
144
|
|
|
102
145
|
playerCommands = buildPlayerCommands(defaultCapabilities)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
notificationSessionCommands = extSessionCommands
|
|
111
|
-
notificationCustomLayout = extCustomLayout
|
|
146
|
+
sessionCommands = buildSessionCommands(defaultCapabilities, searchAvailable = false)
|
|
147
|
+
buttonPreferences =
|
|
148
|
+
buildButtonPreferences(
|
|
149
|
+
deriveButtonSlots(defaultCapabilities, layout = null),
|
|
150
|
+
favorited = null,
|
|
151
|
+
)
|
|
112
152
|
}
|
|
113
153
|
|
|
114
154
|
/**
|
|
@@ -118,61 +158,72 @@ class MediaSessionCommandManager {
|
|
|
118
158
|
* @param capabilities Global capabilities that enable commands for ALL MediaSession controllers
|
|
119
159
|
* (Bluetooth, Android Auto, lock screen, notification, etc.). All capabilities are enabled by
|
|
120
160
|
* default - only false values disable them.
|
|
121
|
-
* @param
|
|
122
|
-
*
|
|
161
|
+
* @param remoteButtonLayout Button layout, published to every surface — the notification, Android
|
|
162
|
+
* Auto, and the Android 13+ system media controls. When null the layout is derived from
|
|
163
|
+
* capabilities using smart defaults.
|
|
123
164
|
* @param searchAvailable Whether search functionality is configured and available
|
|
165
|
+
* @param forwardJumpInterval Seconds per forward jump; selects the button's icon constant
|
|
166
|
+
* @param backwardJumpInterval Seconds per backward jump; selects the button's icon constant
|
|
124
167
|
*
|
|
125
|
-
* Manager initializes with defaults: all
|
|
168
|
+
* Manager initializes with defaults: all capabilities enabled, default placement.
|
|
126
169
|
*/
|
|
127
170
|
fun updateMediaSession(
|
|
128
171
|
mediaSession: MediaSession,
|
|
129
172
|
capabilities: PlayerCapabilities,
|
|
130
|
-
|
|
173
|
+
remoteButtonLayout: RemoteButtonLayout?,
|
|
131
174
|
searchAvailable: Boolean,
|
|
132
|
-
|
|
175
|
+
forwardJumpInterval: Double = DEFAULT_JUMP_INTERVAL,
|
|
176
|
+
backwardJumpInterval: Double = DEFAULT_JUMP_INTERVAL,
|
|
133
177
|
) {
|
|
134
|
-
// Store state for future rebuilds
|
|
178
|
+
// Store state for future rebuilds. The favorite state is deliberately NOT reset here: options
|
|
179
|
+
// and favorites change independently, and rebuilding the layout as un-favorited would blank the
|
|
180
|
+
// heart on every updateOptions() call until the next track change happened to refresh it.
|
|
135
181
|
currentCapabilities = capabilities
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
currentRemoteButtonLayout = remoteButtonLayout
|
|
183
|
+
currentForwardJumpInterval = forwardJumpInterval
|
|
184
|
+
currentBackwardJumpInterval = backwardJumpInterval
|
|
139
185
|
|
|
140
|
-
//
|
|
186
|
+
// Commands gate on capabilities only — placement never revokes one, so a control left out of
|
|
187
|
+
// the layout still works from a Bluetooth remote or headset.
|
|
141
188
|
playerCommands = buildPlayerCommands(capabilities)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
notificationSessionCommands = notifSessionCommands
|
|
153
|
-
notificationCustomLayout = notifButtonPrefs
|
|
154
|
-
notificationPlayerCommands = buildNotificationPlayerCommands(capabilities, slots)
|
|
155
|
-
|
|
156
|
-
// Apply media button preferences to notification controller
|
|
157
|
-
mediaSession.mediaNotificationControllerInfo?.let { controllerInfo ->
|
|
158
|
-
mediaSession.setMediaButtonPreferences(controllerInfo, notifButtonPrefs)
|
|
159
|
-
mediaSession.setAvailableCommands(
|
|
160
|
-
controllerInfo,
|
|
161
|
-
notificationSessionCommands,
|
|
162
|
-
notificationPlayerCommands,
|
|
163
|
-
)
|
|
189
|
+
sessionCommands = buildSessionCommands(capabilities, searchAvailable)
|
|
190
|
+
buttonPreferences =
|
|
191
|
+
buildButtonPreferences(deriveButtonSlots(capabilities, remoteButtonLayout), currentFavorited)
|
|
192
|
+
|
|
193
|
+
// Push the rebuilt commands to already-connected controllers — buildConnectionResult only
|
|
194
|
+
// serves controllers that connect later, and an Android Auto session lives until the car shuts
|
|
195
|
+
// down. Granted before the layout broadcast so a newly-added button arrives with its command
|
|
196
|
+
// already available; Media3 disables buttons whose backing command is missing.
|
|
197
|
+
mediaSession.connectedControllers.forEach {
|
|
198
|
+
mediaSession.setAvailableCommands(it, sessionCommands, playerCommands)
|
|
164
199
|
}
|
|
165
200
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// Android Auto session lives until the car shuts down. Granted before the layout broadcast
|
|
169
|
-
// so a newly-added button arrives with its command already available.
|
|
170
|
-
mediaSession.connectedControllers
|
|
171
|
-
.filterNot { mediaSession.isMediaNotificationController(it) }
|
|
172
|
-
.forEach { mediaSession.setAvailableCommands(it, sessionCommands, playerCommands) }
|
|
201
|
+
publishButtonPreferences(mediaSession)
|
|
202
|
+
}
|
|
173
203
|
|
|
174
|
-
|
|
175
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Publishes the layout. Media3 fans it out to the MediaStyle notification (Android 12 and below)
|
|
206
|
+
* and to the platform PlaybackState plus slot-reservation extras (Android Auto and the Android
|
|
207
|
+
* 13+ system media controls).
|
|
208
|
+
*
|
|
209
|
+
* Both calls are required. The session-wide call is the only one that reaches Android Auto, which
|
|
210
|
+
* connects as a legacy controller rather than a Media3 one. But it stops at
|
|
211
|
+
* `setPlatformMediaButtonPreferences`, which recomputes the legacy custom layout and only
|
|
212
|
+
* re-broadcasts when the reservation flags themselves change — it never refreshes the
|
|
213
|
+
* PlaybackState carrying the custom actions. Only the notification-controller overload calls
|
|
214
|
+
* `updateLegacySessionPlaybackState`, so without it a reordering that leaves the flags untouched
|
|
215
|
+
* sits invisible until some unrelated playback event republishes the state.
|
|
216
|
+
*/
|
|
217
|
+
private fun publishButtonPreferences(mediaSession: MediaSession) {
|
|
218
|
+
Timber.Forest.d(
|
|
219
|
+
"Publishing ${buttonPreferences.size} button preferences: " +
|
|
220
|
+
"${buttonPreferences.map { it.displayName }}"
|
|
221
|
+
)
|
|
222
|
+
mediaSession.setMediaButtonPreferences(buttonPreferences)
|
|
223
|
+
|
|
224
|
+
mediaSession.mediaNotificationControllerInfo?.let { controllerInfo ->
|
|
225
|
+
mediaSession.setMediaButtonPreferences(controllerInfo, buttonPreferences)
|
|
226
|
+
}
|
|
176
227
|
}
|
|
177
228
|
|
|
178
229
|
/**
|
|
@@ -190,32 +241,16 @@ class MediaSessionCommandManager {
|
|
|
190
241
|
|
|
191
242
|
currentFavorited = favorited
|
|
192
243
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
customLayout = extCustomLayout
|
|
198
|
-
Timber.Forest.d("Built external customLayout with ${customLayout.size} buttons")
|
|
199
|
-
|
|
200
|
-
// Rebuild notification button preferences with new favorite state
|
|
201
|
-
val (notifSessionCommands, notifButtonPrefs) =
|
|
202
|
-
buildNotificationButtonPreferences(
|
|
203
|
-
deriveNotificationSlots(currentCapabilities, currentNotificationButtons),
|
|
204
|
-
currentSearchAvailable,
|
|
244
|
+
// Only the heart's icon and label change, so the commands stay as they are.
|
|
245
|
+
buttonPreferences =
|
|
246
|
+
buildButtonPreferences(
|
|
247
|
+
deriveButtonSlots(currentCapabilities, currentRemoteButtonLayout),
|
|
205
248
|
favorited,
|
|
206
249
|
)
|
|
207
|
-
notificationSessionCommands = notifSessionCommands
|
|
208
|
-
notificationCustomLayout = notifButtonPrefs
|
|
209
|
-
|
|
210
|
-
// Apply updated button preferences to notification controller
|
|
211
|
-
mediaSession.mediaNotificationControllerInfo?.let { controllerInfo ->
|
|
212
|
-
Timber.Forest.d("Updating notification controller button preferences")
|
|
213
|
-
mediaSession.setMediaButtonPreferences(controllerInfo, notificationCustomLayout)
|
|
214
|
-
}
|
|
215
250
|
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
mediaSession
|
|
251
|
+
// Republish so already-connected surfaces (Android Auto especially) flip the heart — without
|
|
252
|
+
// this it stays stale for the life of the connection.
|
|
253
|
+
publishButtonPreferences(mediaSession)
|
|
219
254
|
}
|
|
220
255
|
|
|
221
256
|
/**
|
|
@@ -226,7 +261,7 @@ class MediaSessionCommandManager {
|
|
|
226
261
|
*/
|
|
227
262
|
fun buildConnectionResult(session: MediaSession): MediaSession.ConnectionResult {
|
|
228
263
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
|
229
|
-
.
|
|
264
|
+
.setMediaButtonPreferences(buttonPreferences)
|
|
230
265
|
.setAvailableSessionCommands(sessionCommands)
|
|
231
266
|
.setAvailablePlayerCommands(playerCommands)
|
|
232
267
|
.build()
|
|
@@ -274,22 +309,26 @@ class MediaSessionCommandManager {
|
|
|
274
309
|
playerCommandsBuilder.remove(MediaPlayer.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
|
|
275
310
|
playerCommandsBuilder.remove(MediaPlayer.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
|
|
276
311
|
|
|
277
|
-
|
|
278
|
-
if (!capabilities.isEnabled(
|
|
312
|
+
capabilityPlayerCommands.forEach { (capability, command) ->
|
|
313
|
+
if (!capabilities.isEnabled(capability)) {
|
|
279
314
|
playerCommandsBuilder.remove(command)
|
|
280
|
-
Timber.Forest.d("Removed command: $command ($
|
|
315
|
+
Timber.Forest.d("Removed command: $command ($capability disabled)")
|
|
281
316
|
}
|
|
282
317
|
}
|
|
283
318
|
|
|
284
319
|
return playerCommandsBuilder.build()
|
|
285
320
|
}
|
|
286
321
|
|
|
287
|
-
|
|
322
|
+
/**
|
|
323
|
+
* Session commands for every controller. Registered for each capability-allowed custom button
|
|
324
|
+
* whether or not the layout places it — placement governs where a button appears, not whether its
|
|
325
|
+
* command exists. Media3 disables a button whose backing command is missing, so every button the
|
|
326
|
+
* layout can produce must have its command here.
|
|
327
|
+
*/
|
|
328
|
+
private fun buildSessionCommands(
|
|
288
329
|
capabilities: PlayerCapabilities,
|
|
289
330
|
searchAvailable: Boolean,
|
|
290
|
-
|
|
291
|
-
): Pair<SessionCommands, List<CommandButton>> {
|
|
292
|
-
val customLayoutButtons = mutableListOf<CommandButton>()
|
|
331
|
+
): SessionCommands {
|
|
293
332
|
val sessionCommandsBuilder =
|
|
294
333
|
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
|
295
334
|
|
|
@@ -300,157 +339,84 @@ class MediaSessionCommandManager {
|
|
|
300
339
|
Timber.Forest.d("Removed search commands - search not configured")
|
|
301
340
|
}
|
|
302
341
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
NotificationButton.JUMP_BACKWARD,
|
|
308
|
-
NotificationButton.JUMP_FORWARD,
|
|
309
|
-
NotificationButton.FAVORITE,
|
|
310
|
-
)
|
|
311
|
-
.filter { capabilities.allows(it) }
|
|
312
|
-
for (button in externalButtons) {
|
|
313
|
-
val spec = buttonFor(button, favorited)
|
|
314
|
-
customLayoutButtons.add(spec.builder.build())
|
|
315
|
-
spec.sessionCommand?.let { sessionCommandsBuilder.add(it) }
|
|
316
|
-
}
|
|
342
|
+
RemoteButton.entries
|
|
343
|
+
.filter { capabilities.allows(it) }
|
|
344
|
+
.mapNotNull { customCommandFor(it) }
|
|
345
|
+
.forEach { sessionCommandsBuilder.add(it) }
|
|
317
346
|
|
|
318
|
-
return
|
|
347
|
+
return sessionCommandsBuilder.build()
|
|
319
348
|
}
|
|
320
349
|
|
|
321
350
|
/**
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
351
|
+
* The custom command behind a session-command-backed button, or null for the player-command ones
|
|
352
|
+
* (skip). Single source for both the command a button carries and the SessionCommands registry,
|
|
353
|
+
* so a published button can never be missing the command that makes it work — Media3 silently
|
|
354
|
+
* disables buttons whose backing command is unavailable.
|
|
325
355
|
*/
|
|
326
|
-
private
|
|
327
|
-
val builder: CommandButton.Builder,
|
|
328
|
-
val sessionCommand: SessionCommand?,
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* The single definition of a control's display name, icon, and action, shared by the
|
|
333
|
-
* external-controller layout and the notification layout.
|
|
334
|
-
*/
|
|
335
|
-
private fun buttonFor(button: NotificationButton, favorited: Boolean?): ButtonSpec =
|
|
356
|
+
private fun customCommandFor(button: RemoteButton): SessionCommand? =
|
|
336
357
|
when (button) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
NotificationButton.SKIP_TO_NEXT ->
|
|
343
|
-
CommandButton.Builder(CommandButton.ICON_NEXT)
|
|
344
|
-
.setDisplayName("Next")
|
|
345
|
-
.setPlayerCommand(MediaPlayer.COMMAND_SEEK_TO_NEXT)
|
|
346
|
-
.let { ButtonSpec(it, sessionCommand = null) }
|
|
347
|
-
NotificationButton.JUMP_BACKWARD -> {
|
|
348
|
-
val command = SessionCommand(CUSTOM_ACTION_JUMP_BACKWARD, Bundle())
|
|
349
|
-
CommandButton.Builder()
|
|
350
|
-
.setDisplayName("Jump Backward")
|
|
351
|
-
.setSessionCommand(command)
|
|
352
|
-
.setIconResId(R.drawable.media3_icon_skip_back)
|
|
353
|
-
.let { ButtonSpec(it, command) }
|
|
354
|
-
}
|
|
355
|
-
NotificationButton.JUMP_FORWARD -> {
|
|
356
|
-
val command = SessionCommand(CUSTOM_ACTION_JUMP_FORWARD, Bundle())
|
|
357
|
-
CommandButton.Builder()
|
|
358
|
-
.setDisplayName("Jump Forward")
|
|
359
|
-
.setSessionCommand(command)
|
|
360
|
-
.setIconResId(R.drawable.media3_icon_skip_forward)
|
|
361
|
-
.let { ButtonSpec(it, command) }
|
|
362
|
-
}
|
|
363
|
-
NotificationButton.FAVORITE -> {
|
|
364
|
-
val heartIcon =
|
|
365
|
-
if (favorited == true) CommandButton.ICON_HEART_FILLED
|
|
366
|
-
else CommandButton.ICON_HEART_UNFILLED
|
|
367
|
-
val displayName = if (favorited == true) "Remove from favorites" else "Add to favorites"
|
|
368
|
-
val command = SessionCommand(CUSTOM_ACTION_FAVORITE, Bundle())
|
|
369
|
-
ButtonSpec(
|
|
370
|
-
CommandButton.Builder(heartIcon).setDisplayName(displayName).setSessionCommand(command),
|
|
371
|
-
command,
|
|
372
|
-
)
|
|
373
|
-
}
|
|
358
|
+
RemoteButton.JUMP_BACKWARD -> SessionCommand(CUSTOM_ACTION_JUMP_BACKWARD, Bundle())
|
|
359
|
+
RemoteButton.JUMP_FORWARD -> SessionCommand(CUSTOM_ACTION_JUMP_FORWARD, Bundle())
|
|
360
|
+
RemoteButton.FAVORITE -> SessionCommand(CUSTOM_ACTION_FAVORITE, Bundle())
|
|
361
|
+
RemoteButton.SKIP_TO_PREVIOUS,
|
|
362
|
+
RemoteButton.SKIP_TO_NEXT -> null
|
|
374
363
|
}
|
|
375
364
|
|
|
376
365
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
366
|
+
* The Media3 player command behind a player-command-backed button; null for the custom ones. Both
|
|
367
|
+
* this and [customCommandFor] enumerate every button rather than using `else`, so adding one
|
|
368
|
+
* fails the build until it has been given an action on both sides.
|
|
379
369
|
*/
|
|
380
|
-
private fun
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Remove search commands if search is not configured
|
|
389
|
-
if (!searchAvailable) {
|
|
390
|
-
sessionCommandsBuilder.remove(SessionCommand.COMMAND_CODE_LIBRARY_SEARCH)
|
|
391
|
-
sessionCommandsBuilder.remove(SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT)
|
|
370
|
+
private fun playerCommandFor(button: RemoteButton): Int? =
|
|
371
|
+
when (button) {
|
|
372
|
+
RemoteButton.SKIP_TO_PREVIOUS -> MediaPlayer.COMMAND_SEEK_TO_PREVIOUS
|
|
373
|
+
RemoteButton.SKIP_TO_NEXT -> MediaPlayer.COMMAND_SEEK_TO_NEXT
|
|
374
|
+
RemoteButton.JUMP_BACKWARD,
|
|
375
|
+
RemoteButton.JUMP_FORWARD,
|
|
376
|
+
RemoteButton.FAVORITE -> null
|
|
392
377
|
}
|
|
393
378
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
379
|
+
/** The single definition of a control's icon and label, shared by every surface. */
|
|
380
|
+
private fun buttonFor(button: RemoteButton, favorited: Boolean?): CommandButton.Builder {
|
|
381
|
+
val builder =
|
|
382
|
+
when (button) {
|
|
383
|
+
RemoteButton.SKIP_TO_PREVIOUS ->
|
|
384
|
+
CommandButton.Builder(CommandButton.ICON_PREVIOUS).setDisplayName("Previous")
|
|
385
|
+
RemoteButton.SKIP_TO_NEXT ->
|
|
386
|
+
CommandButton.Builder(CommandButton.ICON_NEXT).setDisplayName("Next")
|
|
387
|
+
RemoteButton.JUMP_BACKWARD ->
|
|
388
|
+
CommandButton.Builder(jumpBackwardIcon(currentBackwardJumpInterval))
|
|
389
|
+
.setDisplayName("Jump Backward")
|
|
390
|
+
RemoteButton.JUMP_FORWARD ->
|
|
391
|
+
CommandButton.Builder(jumpForwardIcon(currentForwardJumpInterval))
|
|
392
|
+
.setDisplayName("Jump Forward")
|
|
393
|
+
RemoteButton.FAVORITE ->
|
|
394
|
+
CommandButton.Builder(
|
|
395
|
+
if (favorited == true) CommandButton.ICON_HEART_FILLED
|
|
396
|
+
else CommandButton.ICON_HEART_UNFILLED
|
|
397
|
+
)
|
|
398
|
+
.setDisplayName(if (favorited == true) "Remove from favorites" else "Add to favorites")
|
|
408
399
|
}
|
|
409
400
|
|
|
410
|
-
|
|
411
|
-
|
|
401
|
+
return customCommandFor(button)?.let { builder.setSessionCommand(it) }
|
|
402
|
+
?: playerCommandFor(button)?.let { builder.setPlayerCommand(it) }
|
|
403
|
+
?: builder
|
|
412
404
|
}
|
|
413
405
|
|
|
414
406
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
407
|
+
* Assembles the CommandButtons for an already-derived slot layout (see [deriveButtonSlots] — the
|
|
408
|
+
* slots are pre-filtered by Capability).
|
|
409
|
+
*
|
|
410
|
+
* Each button gets its requested slot followed by OVERFLOW as a fallback. `slots` is an ordered
|
|
411
|
+
* preference list, and every surface caps how many buttons it renders — a head unit, the
|
|
412
|
+
* collapsed notification, the Android 13+ media controls all truncate differently. Without a
|
|
413
|
+
* fallback a button that loses its preferred slot disappears entirely instead of demoting.
|
|
419
414
|
*/
|
|
420
|
-
private fun
|
|
421
|
-
capabilities: PlayerCapabilities,
|
|
415
|
+
private fun buildButtonPreferences(
|
|
422
416
|
slots: List<SlottedButton>,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
|
|
428
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
|
|
429
|
-
|
|
430
|
-
val shown = slots.mapTo(mutableSetOf()) { it.button }
|
|
431
|
-
if (NotificationButton.SKIP_TO_PREVIOUS !in shown) {
|
|
432
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_TO_PREVIOUS)
|
|
433
|
-
}
|
|
434
|
-
if (NotificationButton.SKIP_TO_NEXT !in shown) {
|
|
435
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_TO_NEXT)
|
|
436
|
-
}
|
|
437
|
-
if (NotificationButton.JUMP_BACKWARD !in shown) {
|
|
438
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_BACK)
|
|
439
|
-
}
|
|
440
|
-
if (NotificationButton.JUMP_FORWARD !in shown) {
|
|
441
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_FORWARD)
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (!capabilities.isEnabled(Control.PLAY_PAUSE)) {
|
|
445
|
-
builder.remove(MediaPlayer.COMMAND_PLAY_PAUSE)
|
|
446
|
-
}
|
|
447
|
-
if (!capabilities.isEnabled(Control.STOP)) {
|
|
448
|
-
builder.remove(MediaPlayer.COMMAND_STOP)
|
|
449
|
-
}
|
|
450
|
-
if (!capabilities.isEnabled(Control.SEEK_TO)) {
|
|
451
|
-
builder.remove(MediaPlayer.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM)
|
|
417
|
+
favorited: Boolean?,
|
|
418
|
+
): List<CommandButton> =
|
|
419
|
+
slots.map { (button, slot) ->
|
|
420
|
+
buttonFor(button, favorited).setSlots(*media3SlotsFor(slot)).build()
|
|
452
421
|
}
|
|
453
|
-
|
|
454
|
-
return builder.build()
|
|
455
|
-
}
|
|
456
422
|
}
|