react-native-audio-browser 0.1.1 → 0.2.0-next.10
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/AudioBrowser.podspec +1 -1
- package/README.md +184 -2
- package/android/build.gradle +17 -5
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +18 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +473 -158
- 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/Callbacks.kt +4 -6
- package/android/src/main/java/com/audiobrowser/Service.kt +67 -18
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +37 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +34 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +442 -178
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +299 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +79 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +41 -4
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +42 -0
- package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +35 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +109 -97
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +6 -10
- package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +40 -8
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +40 -46
- package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +4 -1
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +172 -0
- package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +18 -1
- package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +153 -0
- package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +141 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +15 -3
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +422 -134
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +248 -404
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +347 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +129 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +85 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +25 -5
- package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +56 -0
- package/android/src/main/java/com/audiobrowser/player/Player.kt +475 -577
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +20 -0
- package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +130 -0
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +190 -67
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +118 -53
- package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +37 -3
- package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +80 -0
- package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +50 -0
- package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +85 -40
- package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +81 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +156 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +109 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +37 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +55 -0
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +265 -0
- package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +161 -0
- package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +28 -0
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +24 -0
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +24 -0
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +49 -0
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +36 -0
- package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +23 -300
- package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +13 -23
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
- package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +4 -3
- package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +211 -0
- package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +58 -0
- package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +2 -1
- package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +14 -0
- package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +6 -111
- package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +22 -8
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +112 -80
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +165 -0
- package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +46 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +39 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +114 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +141 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +4 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +177 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +1 -2
- package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +72 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +203 -0
- package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +56 -0
- package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +216 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +442 -0
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +190 -0
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +198 -0
- package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +137 -0
- package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +36 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +268 -0
- package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +68 -0
- package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +40 -0
- package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +193 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +186 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +158 -0
- package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +18 -0
- package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +163 -0
- package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +34 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +28 -0
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +53 -0
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +58 -0
- package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +57 -0
- package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +40 -0
- package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +34 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +50 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +63 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +35 -0
- package/android/src/test/resources/certs/isrg-root-x1.pem +31 -0
- package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
- package/android/src/test/resources/certs/letsencrypt-r13.pem +29 -0
- package/android/src/test/resources/certs/stationplaylist-leaf.pem +34 -0
- package/ios/Browser/BrowserConfig.swift +41 -11
- package/ios/Browser/BrowserManager+URLResolution.swift +213 -93
- package/ios/Browser/BrowserManager.swift +303 -135
- package/ios/Browser/BrowserPathHelper.swift +32 -3
- package/ios/Browser/JsonModels.swift +287 -66
- package/ios/Browser/MediaResolveComposer.swift +114 -0
- package/ios/Browser/SearchDrillIn.swift +44 -0
- package/ios/Browser/SectionScope.swift +35 -0
- package/ios/Browser/TabBarEntries.swift +15 -0
- package/ios/CarPlay/CPInterfaceController+Safe.swift +126 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
- package/ios/CarPlay/CarPlayController.swift +739 -149
- package/ios/CarPlay/CarPlayImageLoader.swift +57 -8
- package/ios/CarPlay/CarPlayListItemFactory.swift +44 -35
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +180 -36
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +19 -0
- package/ios/CarPlay/RNABAudioBrowser.h +15 -10
- package/ios/CarPlay/RNABAudioBrowser.m +15 -14
- package/ios/CarPlay/RNABCarPlaySceneDelegate.m +2 -0
- package/ios/CarPlay/RNABMediaAddHandler.swift +75 -0
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +69 -0
- package/ios/CarPlay/RNABMediaIntentHandler.swift +53 -43
- package/ios/Extension/Capability+RemoteCommand.swift +7 -9
- package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/NavigationError+Conversion.swift +8 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +20 -0
- package/ios/Extension/RequestConfig+Copying.swift +31 -0
- package/ios/Extension/ResolvedTrack+Copying.swift +10 -4
- package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/Track+AVPlayer.swift +4 -4
- package/ios/Extension/Track+Copying.swift +9 -3
- package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/Variant+Artwork.swift +49 -0
- package/ios/HybridAudioBrowser.swift +805 -197
- package/ios/Model/NitroTypeStubs.swift +299 -224
- package/ios/Model/PlayerUpdateOptions.swift +23 -23
- package/ios/Model/RemoteCommand.swift +24 -6
- package/ios/Model/TrackPlayerError.swift +82 -18
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +99 -121
- package/ios/NowPlayingInfo/SilentAudioPrimer.swift +73 -0
- package/ios/Observer/PlayerItemNotificationObserver.swift +45 -5
- package/ios/Observer/PlayerItemPropertyObserver.swift +27 -4
- package/ios/Observer/PlayerTimeObserver.swift +3 -53
- package/ios/Option/SessionCategories.swift +34 -11
- package/ios/Player/AirPlayStallJudgement.swift +25 -0
- package/ios/Player/EndOfTrackJudgement.swift +32 -0
- package/ios/Player/LoadSeekCoordinator.swift +16 -1
- package/ios/Player/MediaIntentCriteria.swift +139 -0
- package/ios/Player/MediaLoader.swift +31 -11
- package/ios/Player/NowPlayingUpdater.swift +119 -26
- package/ios/Player/PersistedPlaybackState.swift +11 -0
- package/ios/Player/PlaybackCoordinator.swift +285 -51
- package/ios/Player/PlaybackCoordinatorCallbacks.swift +6 -1
- package/ios/Player/PlaybackEffectHandler.swift +19 -9
- package/ios/Player/PlaybackErrorHandler.swift +98 -14
- package/ios/Player/PlaybackStateMachine.swift +18 -13
- package/ios/Player/PlaybackStateStore.swift +27 -0
- package/ios/Player/PlaybackTimer.swift +61 -0
- package/ios/Player/PlayingStateManager.swift +3 -2
- package/ios/Player/QueueManager.swift +77 -64
- package/ios/Player/RetryManager.swift +175 -38
- package/ios/Player/ShuffleOrder.swift +10 -0
- package/ios/Player/SleepTimerHandling.swift +8 -0
- package/ios/Player/SleepTimerManager.swift +72 -11
- package/ios/Player/VolumeFader.swift +77 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +40 -21
- package/ios/Tests/AirPlayStallJudgementTests.swift +52 -0
- package/ios/Tests/BrowserPathHelperTests.swift +28 -0
- package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
- package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
- package/ios/Tests/EndOfTrackJudgementTests.swift +35 -0
- package/ios/Tests/JsonModelsTests.swift +58 -0
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +40 -7
- package/ios/Tests/MediaIntentCriteriaTests.swift +170 -0
- package/ios/Tests/MediaLoaderAssetOptionsTests.swift +46 -0
- package/ios/Tests/MediaLoaderTests.swift +32 -20
- package/ios/Tests/MediaResolveTests.swift +126 -0
- package/ios/Tests/OnceValueTests.swift +79 -0
- package/ios/Tests/PlaybackCoordinatorTests.swift +787 -13
- package/ios/Tests/PlaybackErrorHandlerTests.swift +383 -47
- package/ios/Tests/PlaybackStateMachineTests.swift +33 -8
- package/ios/Tests/PlaybackStateStoreTests.swift +48 -0
- package/ios/Tests/PlaybackTimerTests.swift +55 -0
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/PlayingStateManagerTests.swift +57 -0
- package/ios/Tests/QueueManagerTests.swift +195 -18
- package/ios/Tests/RemoteCommandControllerTests.swift +156 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +15 -8
- package/ios/Tests/RetryManagerTests.swift +178 -0
- package/ios/Tests/SearchDrillInTests.swift +63 -0
- package/ios/Tests/SectionScopeTests.swift +82 -0
- package/ios/Tests/SleepTimerManagerTests.swift +107 -0
- package/ios/Tests/Support/ManualSleepTimerScheduler.swift +47 -0
- package/ios/Tests/Support/TestTypes.swift +61 -20
- package/ios/Tests/TabBarEntriesTests.swift +71 -0
- package/ios/Tests/TrackSelectorTests.swift +15 -15
- package/ios/TrackPlayer.swift +385 -58
- package/ios/TrackPlayerCallbacks.swift +5 -44
- package/ios/TrackSelector.swift +5 -5
- package/ios/Util/ArtworkImageFetcher.swift +4 -2
- package/ios/Util/LRUCache.swift +4 -1
- package/ios/Util/NetworkMonitor.swift +7 -3
- package/ios/Util/OnceValue.swift +34 -0
- package/lib/commonjs/AudioBrowser.js +0 -11
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +227 -0
- package/lib/commonjs/features/browser-config.js.map +1 -0
- package/lib/commonjs/features/browser.js +60 -159
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +42 -0
- package/lib/commonjs/features/carConnection.js.map +1 -0
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +37 -5
- package/lib/commonjs/features/errors.js.map +1 -1
- package/lib/commonjs/features/favorites.js +1 -1
- package/lib/commonjs/features/gate.js +164 -0
- package/lib/commonjs/features/gate.js.map +1 -0
- package/lib/commonjs/features/index.js +22 -0
- package/lib/commonjs/features/index.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 +36 -1
- package/lib/commonjs/features/nowPlaying.js.map +1 -1
- package/lib/commonjs/features/output.js +37 -23
- package/lib/commonjs/features/output.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +9 -0
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/index.js +11 -0
- package/lib/commonjs/features/playback/index.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 +29 -13
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +86 -0
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -0
- package/lib/commonjs/features/playback/volume.js +2 -2
- package/lib/commonjs/features/player/options.js +79 -29
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +256 -3
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +21 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +1 -1
- package/lib/commonjs/features/queue/queue.js +12 -5
- 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 +14 -104
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +22 -5
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/native.js +5 -0
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.web.js +4 -1
- package/lib/commonjs/native.web.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 +8 -8
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +0 -3
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/artwork.js +31 -0
- package/lib/commonjs/utils/artwork.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +302 -125
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +71 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +34 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/Player.js +90 -26
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +21 -0
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +273 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +158 -0
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js +34 -3
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js +75 -0
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/index.js +13 -5
- package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +176 -50
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.js +3 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +11 -9
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +22 -0
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -0
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +41 -0
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -0
- package/lib/commonjs/web/browser/parseSearchResponse.js +20 -0
- package/lib/commonjs/web/browser/parseSearchResponse.js.map +1 -0
- package/lib/commonjs/web/http/HttpClient.js +4 -23
- package/lib/commonjs/web/http/HttpClient.js.map +1 -1
- package/lib/commonjs/web/http/RequestConfigBuilder.js +107 -58
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/playbackErrorKind.js +57 -0
- package/lib/commonjs/web/playbackErrorKind.js.map +1 -0
- package/lib/commonjs/web/player/MediaSessionManager.js +121 -0
- package/lib/commonjs/web/player/MediaSessionManager.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +6 -5
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/player/OptionsManager.js +9 -17
- package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
- package/lib/commonjs/web/player/RemoteCommandController.js +96 -0
- package/lib/commonjs/web/player/RemoteCommandController.js.map +1 -0
- package/lib/module/AudioBrowser.js +1 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +221 -0
- package/lib/module/features/browser-config.js.map +1 -0
- package/lib/module/features/browser.js +54 -157
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +37 -0
- package/lib/module/features/carConnection.js.map +1 -0
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +37 -5
- package/lib/module/features/errors.js.map +1 -1
- package/lib/module/features/favorites.js +1 -1
- package/lib/module/features/gate.js +159 -0
- package/lib/module/features/gate.js.map +1 -0
- package/lib/module/features/index.js +2 -0
- package/lib/module/features/index.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 +34 -1
- package/lib/module/features/nowPlaying.js.map +1 -1
- package/lib/module/features/output.js +32 -19
- package/lib/module/features/output.js.map +1 -1
- package/lib/module/features/playback/controls.js +8 -0
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/index.js +1 -0
- package/lib/module/features/playback/index.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 +29 -13
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/state.js +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +83 -0
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -0
- package/lib/module/features/playback/volume.js +2 -2
- package/lib/module/features/player/options.js +79 -29
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +256 -3
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +17 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +1 -1
- package/lib/module/features/queue/queue.js +12 -5
- 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 -103
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +22 -5
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/native.js +5 -0
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.web.js +4 -1
- package/lib/module/native.web.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 +8 -8
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +0 -3
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/artwork.js +26 -0
- package/lib/module/utils/artwork.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +303 -126
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +67 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackTimer.js +29 -0
- package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +1 -0
- package/lib/module/web/TrackPlayer/Player.js +90 -26
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js +17 -0
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +1 -0
- package/lib/module/web/TrackPlayer/QueueManager.js +269 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -0
- package/lib/module/web/TrackPlayer/QueuePlayer.js +154 -0
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -0
- package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/module/web/TrackPlayer/SleepTimer.js +34 -3
- package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/module/web/TrackPlayer/VolumeFader.js +70 -0
- package/lib/module/web/TrackPlayer/VolumeFader.js.map +1 -0
- package/lib/module/web/TrackPlayer/index.js +2 -1
- package/lib/module/web/TrackPlayer/index.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +176 -50
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/NavigationErrorManager.js +3 -1
- package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +11 -9
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +18 -0
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -0
- package/lib/module/web/browser/classifyTrackNavigation.js +38 -0
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -0
- package/lib/module/web/browser/parseSearchResponse.js +16 -0
- package/lib/module/web/browser/parseSearchResponse.js.map +1 -0
- package/lib/module/web/http/HttpClient.js +4 -23
- package/lib/module/web/http/HttpClient.js.map +1 -1
- package/lib/module/web/http/RequestConfigBuilder.js +107 -58
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/playbackErrorKind.js +52 -0
- package/lib/module/web/playbackErrorKind.js.map +1 -0
- package/lib/module/web/player/MediaSessionManager.js +116 -0
- package/lib/module/web/player/MediaSessionManager.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +7 -5
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/player/OptionsManager.js +9 -17
- package/lib/module/web/player/OptionsManager.js.map +1 -1
- package/lib/module/web/player/RemoteCommandController.js +92 -0
- package/lib/module/web/player/RemoteCommandController.js.map +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +0 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts +12 -0
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -0
- package/lib/typescript/src/features/browser.d.ts +40 -1
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +20 -0
- package/lib/typescript/src/features/carConnection.d.ts.map +1 -0
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +61 -6
- package/lib/typescript/src/features/errors.d.ts.map +1 -1
- package/lib/typescript/src/features/favorites.d.ts +1 -1
- package/lib/typescript/src/features/gate.d.ts +93 -0
- package/lib/typescript/src/features/gate.d.ts.map +1 -0
- package/lib/typescript/src/features/index.d.ts +2 -0
- package/lib/typescript/src/features/index.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +26 -7
- 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 +28 -1
- package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
- package/lib/typescript/src/features/output.d.ts +28 -18
- package/lib/typescript/src/features/output.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +5 -0
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/index.d.ts +1 -0
- package/lib/typescript/src/features/playback/index.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 +4 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +34 -0
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/volume.d.ts +2 -2
- package/lib/typescript/src/features/player/options.d.ts +176 -127
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +216 -167
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +9 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +11 -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 -35
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +19 -4
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native.d.ts +1 -2
- package/lib/typescript/src/native.d.ts.map +1 -1
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts +17 -225
- package/lib/typescript/src/specs/audio-browser.nitro.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 +9 -3
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +145 -13
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +349 -85
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +8 -6
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +0 -2
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/artwork.d.ts +19 -0
- package/lib/typescript/src/utils/artwork.d.ts.map +1 -0
- package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +4 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +24 -10
- package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -20
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +33 -1
- package/nitrogen/generated/android/c++/JArtworkVariants.hpp +61 -0
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +5 -0
- package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +6 -0
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +58 -0
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +100 -0
- package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +3 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +112 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +121 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +123 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +133 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +121 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +139 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +134 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +6 -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 +131 -0
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +0 -10
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +14 -9
- package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +6 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +6 -0
- package/nitrogen/generated/android/c++/JGate.hpp +62 -0
- package/nitrogen/generated/android/c++/JGateDecision.hpp +64 -0
- package/nitrogen/generated/android/c++/{JThumbsRating.hpp → JGateEvent.hpp} +16 -15
- package/nitrogen/generated/android/c++/JGateReason.hpp +58 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +247 -223
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +26 -23
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +95 -0
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +33 -3
- package/nitrogen/generated/android/c++/JMediaReference.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +33 -1
- package/nitrogen/generated/android/c++/{JPartialAndroidSetupPlayerOptions.hpp → JNativeAndroidSetupOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +61 -28
- package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +74 -0
- package/nitrogen/generated/android/c++/{JPartialIOSSetupPlayerOptions.hpp → JNativeIOSSetupOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +20 -9
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +182 -0
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +21 -24
- package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +6 -0
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -20
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +96 -0
- package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +3 -17
- package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +7 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +19 -14
- package/nitrogen/generated/android/c++/{JIosOutput.hpp → JOutput.hpp} +15 -15
- package/nitrogen/generated/android/c++/JOutputType.hpp +82 -0
- package/nitrogen/generated/android/c++/JPlayback.hpp +2 -0
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +6 -0
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +18 -3
- package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +2 -0
- package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +76 -0
- package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +11 -5
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +20 -20
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +23 -5
- package/nitrogen/generated/android/c++/JRetryConfig.hpp +11 -7
- package/nitrogen/generated/android/c++/JSearchMode.hpp +27 -9
- package/nitrogen/generated/android/c++/JSearchParams.hpp +9 -3
- package/nitrogen/generated/android/c++/JStallReason.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +23 -5
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +6 -0
- package/nitrogen/generated/android/c++/JTrackRequest.hpp +93 -0
- package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +14 -1
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +71 -0
- 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_.cpp +41 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +121 -0
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkVariants.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{HeartRating.kt → FavoriteConfig.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RatingType.kt → FavoritesMatchMode.kt} +5 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt} +12 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_RemoteSetRatingEvent.kt → Func_void_GateEvent.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_IosOutput.kt → Func_void_Output.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ThumbsRating.kt → Gate.kt} +9 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PercentageRating.kt → GateDecision.kt} +9 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{StarRating.kt → GateEvent.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → GateReason.kt} +5 -8
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +104 -110
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +24 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +10 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialAndroidSetupPlayerOptions.kt → NativeAndroidSetupOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +18 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{RemoteSetRatingEvent.kt → NativeGateRequest.kt} +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{PartialIOSSetupPlayerOptions.kt → NativeIOSSetupOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +3 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +3 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{IosOutput.kt → Output.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +31 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +12 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +26 -0
- 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 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +7 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +12 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +12 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +7 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +53 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +139 -28
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +1126 -490
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +66 -45
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +178 -120
- package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -22
- package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +95 -1
- package/nitrogen/generated/ios/swift/ArtworkVariants.swift +34 -0
- package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +29 -0
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +40 -0
- package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +67 -0
- package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +17 -4
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +69 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +60 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +60 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +61 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +61 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +77 -0
- package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +46 -0
- package/nitrogen/generated/ios/swift/{Func_void_RemoteSetRatingEvent.swift → Func_void_GateEvent.swift} +10 -10
- package/nitrogen/generated/ios/swift/{Func_void_IosOutput.swift → Func_void_Output.swift} +10 -10
- package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
- package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift → Func_void_std__shared_ptr_Promise_GateDecision__.swift} +17 -17
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +66 -0
- package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +81 -0
- package/nitrogen/generated/ios/swift/Gate.swift +47 -0
- package/nitrogen/generated/ios/swift/GateDecision.swift +40 -0
- package/nitrogen/generated/ios/swift/GateEvent.swift +29 -0
- package/nitrogen/generated/ios/swift/GateReason.swift +40 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +21 -15
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +232 -199
- package/nitrogen/generated/ios/swift/IOSOptions.swift +51 -0
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +121 -2
- package/nitrogen/generated/ios/swift/MediaReference.swift +40 -0
- package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +107 -1
- package/nitrogen/generated/ios/swift/{PartialAndroidSetupPlayerOptions.swift → NativeAndroidSetupOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +161 -22
- package/nitrogen/generated/ios/swift/NativeGateRequest.swift +58 -0
- package/nitrogen/generated/ios/swift/{PartialIOSSetupPlayerOptions.swift → NativeIOSSetupOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
- package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +206 -0
- package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +12 -25
- package/nitrogen/generated/ios/swift/NavigationErrorType.swift +8 -0
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -41
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +90 -0
- package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +1 -48
- package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +19 -1
- package/nitrogen/generated/ios/swift/Options.swift +10 -4
- package/nitrogen/generated/ios/swift/{IosOutput.swift → Output.swift} +7 -7
- package/nitrogen/generated/ios/swift/OutputType.swift +72 -0
- package/nitrogen/generated/ios/swift/PlaybackError.swift +43 -2
- package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +64 -0
- package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +27 -8
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +10 -10
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +74 -8
- package/nitrogen/generated/ios/swift/RetryConfig.swift +35 -4
- package/nitrogen/generated/ios/swift/SearchMode.swift +36 -12
- package/nitrogen/generated/ios/swift/SearchParams.swift +7 -2
- package/nitrogen/generated/ios/swift/StallReason.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +74 -8
- package/nitrogen/generated/ios/swift/TrackRequest.swift +106 -0
- package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +60 -1
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +18 -0
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +19 -0
- package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +18 -0
- package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -19
- package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +10 -2
- package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +87 -0
- package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
- package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +0 -2
- package/nitrogen/generated/shared/c++/{StarRating.hpp → FavoriteConfig.hpp} +18 -17
- package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +76 -0
- package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +110 -0
- package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +6 -5
- package/nitrogen/generated/shared/c++/{PercentageRating.hpp → Gate.hpp} +21 -16
- package/nitrogen/generated/shared/c++/{HeartRating.hpp → GateDecision.hpp} +23 -17
- package/nitrogen/generated/shared/c++/{ThumbsRating.hpp → GateEvent.hpp} +18 -17
- package/nitrogen/generated/shared/c++/GateReason.hpp +76 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +22 -19
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +45 -36
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +91 -0
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +33 -2
- package/nitrogen/generated/shared/c++/MediaReference.hpp +76 -0
- package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +10 -2
- package/nitrogen/generated/shared/c++/{PartialAndroidSetupPlayerOptions.hpp → NativeAndroidSetupOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +31 -15
- package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +97 -0
- package/nitrogen/generated/shared/c++/{PartialIOSSetupPlayerOptions.hpp → NativeIOSSetupOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
- package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +138 -0
- package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +9 -7
- package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +8 -0
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -19
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +92 -0
- package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +3 -19
- package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +6 -2
- package/nitrogen/generated/shared/c++/Options.hpp +8 -8
- package/nitrogen/generated/shared/c++/{IosOutput.hpp → Output.hpp} +18 -18
- package/nitrogen/generated/shared/c++/OutputType.hpp +108 -0
- package/nitrogen/generated/shared/c++/PlaybackError.hpp +18 -3
- package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +100 -0
- package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +9 -6
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +25 -25
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +24 -5
- package/nitrogen/generated/shared/c++/RetryConfig.hpp +10 -6
- package/nitrogen/generated/shared/c++/SearchMode.hpp +39 -15
- package/nitrogen/generated/shared/c++/SearchParams.hpp +9 -2
- package/nitrogen/generated/shared/c++/StallReason.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +24 -5
- package/nitrogen/generated/shared/c++/TrackRequest.hpp +93 -0
- package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +6 -2
- package/package.json +20 -8
- package/src/AudioBrowser.ts +1 -1
- package/src/CLAUDE.md +52 -0
- package/src/codegen-no-config-variant.test.ts +70 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +110 -0
- package/src/features/browser-config.ts +293 -0
- package/src/features/browser.test.ts +19 -0
- package/src/features/browser.ts +59 -202
- package/src/features/carConnection.ts +36 -0
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +71 -5
- package/src/features/favorites.ts +1 -1
- package/src/features/gate.test.ts +72 -0
- package/src/features/gate.ts +170 -0
- package/src/features/hook-wiring.test.ts +254 -0
- package/src/features/index.ts +2 -0
- package/src/features/metadata.ts +26 -15
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +37 -1
- package/src/features/output.ts +35 -22
- package/src/features/playback/controls.ts +8 -0
- package/src/features/playback/index.ts +1 -0
- 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 +32 -13
- package/src/features/playback/state.ts +1 -1
- package/src/features/playback/trackPlaybackTime.test.ts +76 -0
- package/src/features/playback/trackPlaybackTime.ts +101 -0
- package/src/features/playback/volume.ts +2 -2
- package/src/features/player/options.test.ts +38 -0
- package/src/features/player/options.ts +217 -110
- package/src/features/player/setup.test.ts +125 -0
- package/src/features/player/setup.ts +400 -175
- package/src/features/player/validateOptions.ts +19 -0
- package/src/features/queue/activeTrack.ts +1 -1
- package/src/features/queue/queue.test.ts +36 -0
- package/src/features/queue/queue.ts +17 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -115
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +32 -5
- package/src/native.ts +5 -0
- package/src/native.web.ts +4 -1
- package/src/public-surface.test.ts +107 -0
- package/src/specs/audio-browser.nitro.ts +132 -42
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +19 -6
- package/src/types/browser-nodes.ts +160 -13
- package/src/types/browser.ts +377 -89
- package/src/utils/LazyNativeEmitter.ts +9 -8
- package/src/utils/NativeUpdatedValue.ts +0 -2
- package/src/utils/artwork.ts +30 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +301 -0
- package/src/web/NativeAudioBrowser.ts +366 -159
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +139 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +82 -0
- package/src/web/TrackPlayer/PlaybackTimer.test.ts +67 -0
- package/src/web/TrackPlayer/PlaybackTimer.ts +33 -0
- package/src/web/TrackPlayer/Player.dispatch.test.ts +229 -0
- package/src/web/TrackPlayer/Player.load.test.ts +86 -0
- package/src/web/TrackPlayer/Player.ts +113 -39
- package/src/web/TrackPlayer/PlayingStateFactory.test.ts +51 -0
- package/src/web/TrackPlayer/PlayingStateFactory.ts +24 -0
- package/src/web/TrackPlayer/QueueManager.test.ts +289 -0
- package/src/web/TrackPlayer/QueueManager.ts +302 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +188 -0
- package/src/web/TrackPlayer/RepeatMode.ts +1 -1
- package/src/web/TrackPlayer/SleepTimer.ts +38 -3
- package/src/web/TrackPlayer/VolumeFader.ts +74 -0
- package/src/web/TrackPlayer/index.ts +2 -1
- package/src/web/browser/BrowserManager.ts +231 -62
- package/src/web/browser/NavigationErrorManager.ts +3 -1
- package/src/web/browser/SearchManager.ts +11 -15
- package/src/web/browser/__tests__/layer-resolver.test.ts +69 -0
- package/src/web/browser/assertBrowsePageShape.test.ts +27 -0
- package/src/web/browser/assertBrowsePageShape.ts +27 -0
- package/src/web/browser/classifyTrackNavigation.test.ts +41 -0
- package/src/web/browser/classifyTrackNavigation.ts +35 -0
- package/src/web/browser/parseSearchResponse.test.ts +25 -0
- package/src/web/browser/parseSearchResponse.ts +15 -0
- package/src/web/http/HttpClient.ts +5 -35
- package/src/web/http/RequestConfigBuilder.test.ts +59 -0
- package/src/web/http/RequestConfigBuilder.ts +120 -63
- package/src/web/playbackErrorKind.test.ts +55 -0
- package/src/web/playbackErrorKind.ts +56 -0
- package/src/web/player/MediaSessionManager.test.ts +220 -0
- package/src/web/player/MediaSessionManager.ts +151 -0
- package/src/web/player/NowPlayingManager.test.ts +46 -0
- package/src/web/player/NowPlayingManager.ts +6 -5
- package/src/web/player/OptionsManager.ts +12 -18
- package/src/web/player/RemoteCommandController.test.ts +171 -0
- package/src/web/player/RemoteCommandController.ts +166 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +0 -59
- package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +0 -72
- package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +0 -36
- package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +0 -146
- package/ios/CarPlay/Track+CarPlay.swift +0 -63
- package/ios/Model/MediaURL.swift +0 -67
- package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +0 -9
- package/ios/Player/PlaybackProgressUpdateManager.swift +0 -68
- package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +0 -166
- package/lib/commonjs/features/rating.js +0 -2
- package/lib/commonjs/features/rating.js.map +0 -1
- package/lib/commonjs/types/player.js +0 -2
- package/lib/commonjs/types/player.js.map +0 -1
- package/lib/commonjs/web/SimpleRouter.test.js +0 -174
- package/lib/commonjs/web/SimpleRouter.test.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +0 -293
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
- package/lib/module/features/rating.js +0 -2
- package/lib/module/features/rating.js.map +0 -1
- package/lib/module/types/player.js +0 -2
- package/lib/module/types/player.js.map +0 -1
- package/lib/module/web/SimpleRouter.test.js +0 -174
- package/lib/module/web/SimpleRouter.test.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js +0 -288
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +0 -1
- package/lib/typescript/src/features/rating.d.ts +0 -14
- package/lib/typescript/src/features/rating.d.ts.map +0 -1
- package/lib/typescript/src/native.web.d.ts +0 -3
- package/lib/typescript/src/native.web.d.ts.map +0 -1
- package/lib/typescript/src/types/player.d.ts +0 -64
- package/lib/typescript/src/types/player.d.ts.map +0 -1
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts +0 -188
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +0 -1
- package/lib/typescript/src/web/SimpleRouter.d.ts +0 -40
- package/lib/typescript/src/web/SimpleRouter.d.ts.map +0 -1
- package/lib/typescript/src/web/SimpleRouter.test.d.ts +0 -2
- package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts +0 -11
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts +0 -59
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +0 -39
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +0 -6
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +0 -4
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +0 -34
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/State.d.ts +0 -13
- package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +0 -1
- package/lib/typescript/src/web/TrackPlayer/index.d.ts +0 -5
- package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +0 -1
- package/lib/typescript/src/web/browser/BrowserManager.d.ts +0 -154
- package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +0 -1
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts +0 -42
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +0 -1
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +0 -40
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +0 -1
- package/lib/typescript/src/web/browser/SearchManager.d.ts +0 -22
- package/lib/typescript/src/web/browser/SearchManager.d.ts.map +0 -1
- package/lib/typescript/src/web/http/HttpClient.d.ts +0 -25
- package/lib/typescript/src/web/http/HttpClient.d.ts.map +0 -1
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +0 -98
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +0 -1
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts +0 -34
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +0 -1
- package/lib/typescript/src/web/player/OptionsManager.d.ts +0 -32
- package/lib/typescript/src/web/player/OptionsManager.d.ts.map +0 -1
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +0 -69
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +0 -1
- package/lib/typescript/src/web/util/shuffle.d.ts +0 -8
- package/lib/typescript/src/web/util/shuffle.d.ts.map +0 -1
- package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +0 -86
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +0 -147
- package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +0 -80
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +0 -87
- package/nitrogen/generated/android/c++/JHeartRating.hpp +0 -57
- package/nitrogen/generated/android/c++/JIosOutputType.hpp +0 -85
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +0 -96
- package/nitrogen/generated/android/c++/JPercentageRating.hpp +0 -57
- package/nitrogen/generated/android/c++/JRatingType.hpp +0 -73
- package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +0 -66
- package/nitrogen/generated/android/c++/JStarRating.hpp +0 -57
- package/nitrogen/generated/android/c++/JUpdateOptions.hpp +0 -105
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +0 -34
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +0 -105
- 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/AndroidUpdateOptions.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +0 -32
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +0 -47
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +0 -53
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +0 -77
- package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +0 -119
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +0 -68
- package/nitrogen/generated/ios/swift/HeartRating.swift +0 -29
- package/nitrogen/generated/ios/swift/IosOutputType.swift +0 -76
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +0 -101
- package/nitrogen/generated/ios/swift/PercentageRating.swift +0 -29
- package/nitrogen/generated/ios/swift/RatingType.swift +0 -60
- package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +0 -58
- package/nitrogen/generated/ios/swift/StarRating.swift +0 -29
- package/nitrogen/generated/ios/swift/ThumbsRating.swift +0 -29
- package/nitrogen/generated/ios/swift/UpdateOptions.swift +0 -143
- package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +0 -20
- package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +0 -109
- package/nitrogen/generated/shared/c++/IosOutputType.hpp +0 -112
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
- package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +0 -104
- package/nitrogen/generated/shared/c++/RatingType.hpp +0 -96
- package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +0 -94
- package/nitrogen/generated/shared/c++/UpdateOptions.hpp +0 -111
- package/src/features/rating.ts +0 -17
- package/src/types/player.ts +0 -69
- package/src/web/TrackPlayer/PlaylistPlayer.ts +0 -346
|
@@ -2,61 +2,59 @@ package com.audiobrowser.player
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import androidx.core.net.toUri
|
|
6
8
|
import androidx.media3.common.C
|
|
7
|
-
import androidx.media3.common.ForwardingPlayer
|
|
8
9
|
import androidx.media3.common.HeartRating
|
|
9
|
-
import androidx.media3.common.
|
|
10
|
-
import androidx.media3.common.TrackSelectionParameters
|
|
10
|
+
import androidx.media3.common.MediaMetadata
|
|
11
11
|
import androidx.media3.database.StandaloneDatabaseProvider
|
|
12
12
|
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
|
13
13
|
import androidx.media3.datasource.cache.SimpleCache
|
|
14
|
-
import androidx.media3.exoplayer.DefaultRenderersFactory
|
|
15
14
|
import androidx.media3.exoplayer.ExoPlayer
|
|
16
|
-
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
|
|
17
15
|
import androidx.media3.session.MediaLibraryService
|
|
18
16
|
import androidx.media3.session.MediaSession
|
|
19
17
|
import com.audiobrowser.AudioBrowser
|
|
20
18
|
import com.audiobrowser.Callbacks
|
|
19
|
+
import com.audiobrowser.browser.displayArtworkSource
|
|
20
|
+
import com.audiobrowser.browser.resolveArtworkUrl
|
|
21
|
+
import com.audiobrowser.browser.unattributedArtworkSource
|
|
21
22
|
import com.audiobrowser.extension.NumberExt.Companion.toSeconds
|
|
22
|
-
import com.margelo.nitro.audiobrowser.ImageContext
|
|
23
23
|
import com.audiobrowser.model.PlayerSetupOptions
|
|
24
24
|
import com.audiobrowser.model.PlayerUpdateOptions
|
|
25
|
-
import com.audiobrowser.util.AndroidAudioContentTypeFactory
|
|
26
25
|
import com.audiobrowser.util.CoilBitmapLoader
|
|
27
|
-
import com.audiobrowser.util.EqualizerManager
|
|
28
26
|
import com.audiobrowser.util.NetworkConnectivityMonitor
|
|
29
27
|
import com.audiobrowser.util.PlayingStateFactory
|
|
30
28
|
import com.audiobrowser.util.RepeatModeFactory
|
|
31
29
|
import com.audiobrowser.util.TrackFactory
|
|
32
|
-
import com.margelo.nitro.audiobrowser.AndroidPlayerWakeMode
|
|
33
30
|
import com.margelo.nitro.audiobrowser.AppKilledPlaybackBehavior
|
|
34
31
|
import com.margelo.nitro.audiobrowser.FavoriteChangedEvent
|
|
32
|
+
import com.margelo.nitro.audiobrowser.GateEvent
|
|
33
|
+
import com.margelo.nitro.audiobrowser.GateReason
|
|
34
|
+
import com.margelo.nitro.audiobrowser.ImageContext
|
|
35
|
+
import com.margelo.nitro.audiobrowser.ImageSource
|
|
36
|
+
import com.margelo.nitro.audiobrowser.NativeGateRequest
|
|
35
37
|
import com.margelo.nitro.audiobrowser.NowPlayingMetadata
|
|
36
|
-
import com.margelo.nitro.audiobrowser.NowPlayingUpdate
|
|
37
38
|
import com.margelo.nitro.audiobrowser.Playback
|
|
38
39
|
import com.margelo.nitro.audiobrowser.PlaybackActiveTrackChangedEvent
|
|
39
40
|
import com.margelo.nitro.audiobrowser.PlaybackError
|
|
41
|
+
import com.margelo.nitro.audiobrowser.PlaybackPlayWhenReadyChangedEvent
|
|
40
42
|
import com.margelo.nitro.audiobrowser.PlaybackProgressUpdatedEvent
|
|
41
43
|
import com.margelo.nitro.audiobrowser.PlaybackQueueEndedEvent
|
|
42
44
|
import com.margelo.nitro.audiobrowser.PlaybackState
|
|
43
45
|
import com.margelo.nitro.audiobrowser.PlayingState
|
|
44
|
-
import com.margelo.nitro.audiobrowser.RatingType
|
|
45
|
-
import com.margelo.nitro.audiobrowser.RemoteJumpBackwardEvent
|
|
46
|
-
import com.margelo.nitro.audiobrowser.RemoteJumpForwardEvent
|
|
47
|
-
import com.margelo.nitro.audiobrowser.RemoteSeekEvent
|
|
48
46
|
import com.margelo.nitro.audiobrowser.RepeatMode
|
|
49
47
|
import com.margelo.nitro.audiobrowser.SearchParams
|
|
50
|
-
import com.margelo.nitro.audiobrowser.SleepTimer as NitroSleepTimer
|
|
51
|
-
import com.margelo.nitro.audiobrowser.SleepTimerEndOfTrack
|
|
52
|
-
import com.margelo.nitro.audiobrowser.SleepTimerTime
|
|
53
48
|
import com.margelo.nitro.audiobrowser.Track
|
|
54
|
-
import com.margelo.nitro.core.NullType
|
|
55
49
|
import java.io.File
|
|
50
|
+
import java.io.IOException
|
|
56
51
|
import java.util.concurrent.TimeUnit
|
|
57
52
|
import kotlin.time.Duration.Companion.seconds
|
|
58
53
|
import kotlinx.coroutines.CompletableDeferred
|
|
54
|
+
import kotlinx.coroutines.Dispatchers
|
|
55
|
+
import kotlinx.coroutines.MainScope
|
|
59
56
|
import kotlinx.coroutines.TimeoutCancellationException
|
|
57
|
+
import kotlinx.coroutines.withContext
|
|
60
58
|
import kotlinx.coroutines.withTimeout
|
|
61
59
|
import timber.log.Timber
|
|
62
60
|
|
|
@@ -66,25 +64,50 @@ class Player(internal val context: Context) {
|
|
|
66
64
|
get() = options.appKilledPlaybackBehavior
|
|
67
65
|
|
|
68
66
|
private var options = PlayerUpdateOptions()
|
|
67
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
69
68
|
internal var callbacks: Callbacks? = null
|
|
70
69
|
private lateinit var mediaSession: MediaSession
|
|
71
70
|
val networkMonitor: NetworkConnectivityMonitor = NetworkConnectivityMonitor(context)
|
|
72
|
-
|
|
71
|
+
internal val equalizer =
|
|
72
|
+
EqualizerController(onSettingsChanged = { settings -> callbacks?.onEqualizerChanged(settings) })
|
|
73
73
|
private val mediaSessionCallback = MediaSessionCallback(this)
|
|
74
74
|
internal val playbackStateStore = PlaybackStateStore(this)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
75
|
+
internal val volumeFader = VolumeFader(getVolume = { volume }, setVolume = { volume = it })
|
|
76
|
+
internal val sleepTimer =
|
|
77
|
+
SleepTimerManager(
|
|
78
|
+
volumeFader,
|
|
79
|
+
pause = { pause() },
|
|
80
|
+
onChanged = { callbacks?.onSleepTimerChanged(it) },
|
|
81
|
+
)
|
|
83
82
|
|
|
84
83
|
lateinit var exoPlayer: ExoPlayer
|
|
85
84
|
lateinit var forwardingPlayer: androidx.media3.common.Player
|
|
86
85
|
/** Thread-safe cache of playWhenReady for access from non-main threads (e.g., retry policy) */
|
|
87
86
|
@Volatile internal var playWhenReadyCache = false
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether the current load has ever produced audio — selects between the retry policy's
|
|
90
|
+
* first-connect and recovery budgets. Volatile: read from ExoPlayer's loader thread. Set when
|
|
91
|
+
* playback starts, cleared on media-item transition (a new load starts unproven); retry reloads
|
|
92
|
+
* of the same load keep it.
|
|
93
|
+
*/
|
|
94
|
+
@Volatile internal var hasPlayedCache = false
|
|
95
|
+
|
|
96
|
+
/** Last playWhenReady value emitted to JS — see [emitPlayWhenReadyChanged]. */
|
|
97
|
+
private var lastEmittedPlayWhenReady: Boolean? = null
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Emits the playWhenReady change to JS exactly once per value. Two producers exist: the
|
|
101
|
+
* synchronous clear at ENDED in [setPlaybackState] (ordered before the state / queue-ended
|
|
102
|
+
* callbacks, matching iOS) and the async media3 listener (authoritative for changes ExoPlayer
|
|
103
|
+
* makes on its own, e.g. audio focus); the guard deduplicates their overlap.
|
|
104
|
+
*/
|
|
105
|
+
internal fun emitPlayWhenReadyChanged(value: Boolean) {
|
|
106
|
+
if (lastEmittedPlayWhenReady == value) return
|
|
107
|
+
lastEmittedPlayWhenReady = value
|
|
108
|
+
callbacks?.onPlaybackPlayWhenReadyChanged(PlaybackPlayWhenReadyChangedEvent(value))
|
|
109
|
+
}
|
|
110
|
+
|
|
88
111
|
private lateinit var mediaFactory: MediaFactory
|
|
89
112
|
private lateinit var loadControl: DynamicLoadControl
|
|
90
113
|
private var automaticBufferManager: AutomaticBufferManager? = null
|
|
@@ -95,49 +118,39 @@ class Player(internal val context: Context) {
|
|
|
95
118
|
*/
|
|
96
119
|
@Volatile private var pendingNetworkRetry = false
|
|
97
120
|
|
|
98
|
-
private var _browser: AudioBrowser? = null
|
|
99
121
|
private var browserRegistered = CompletableDeferred<AudioBrowser>()
|
|
100
122
|
private var _coilBitmapLoader: CoilBitmapLoader? = null
|
|
101
123
|
|
|
102
124
|
/**
|
|
103
|
-
* Coil ImageLoader for SVG pre-rendering in Android Auto browse items.
|
|
104
|
-
*
|
|
125
|
+
* Coil ImageLoader for SVG pre-rendering in Android Auto browse items. Set by Service after
|
|
126
|
+
* creation.
|
|
105
127
|
*/
|
|
106
128
|
var imageLoader: coil3.ImageLoader? = null
|
|
107
129
|
|
|
130
|
+
/** Registry that maps content:// tokens to artwork URLs for the browse content provider. */
|
|
131
|
+
val browseArtworkRegistry = com.audiobrowser.browser.BrowseArtworkRegistry()
|
|
132
|
+
|
|
108
133
|
/**
|
|
109
|
-
* Set the CoilBitmapLoader for
|
|
134
|
+
* Set the CoilBitmapLoader for display-time bitmap loading. Called from Service after creation.
|
|
110
135
|
*/
|
|
111
136
|
var coilBitmapLoader: CoilBitmapLoader?
|
|
112
137
|
get() = _coilBitmapLoader
|
|
113
138
|
set(value) {
|
|
114
139
|
_coilBitmapLoader = value
|
|
115
|
-
// If browser is already set, wire up the resolver
|
|
116
|
-
_browser?.let { audioBrowser -> wireUpArtworkResolver(audioBrowser, value) }
|
|
117
140
|
}
|
|
118
141
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
value?.let { audioBrowser ->
|
|
124
|
-
// Wire up artwork URL resolver
|
|
125
|
-
// This ensures the resolver is available when onGetChildren resumes
|
|
126
|
-
wireUpArtworkResolver(audioBrowser, _coilBitmapLoader)
|
|
127
|
-
|
|
128
|
-
// NOTE: We do NOT complete browserRegistered here.
|
|
129
|
-
// Configuration (routes/tabs) must be set before Android Auto can browse.
|
|
130
|
-
// Call notifyBrowserConfigurationReady() after configuration is set.
|
|
131
|
-
}
|
|
132
|
-
}
|
|
142
|
+
// NOTE: setting the browser does NOT complete browserRegistered.
|
|
143
|
+
// Configuration (routes/tabs) must be set before Android Auto can browse.
|
|
144
|
+
// Call notifyBrowserConfigurationReady() after configuration is set.
|
|
145
|
+
var browser: AudioBrowser? = null
|
|
133
146
|
|
|
134
147
|
/**
|
|
135
|
-
* Notifies that the browser configuration is ready (routes/tabs are configured).
|
|
136
|
-
*
|
|
137
|
-
*
|
|
148
|
+
* Notifies that the browser configuration is ready (routes/tabs are configured). This should be
|
|
149
|
+
* called after setting the browser AND its configuration. Only then will Android Auto be able to
|
|
150
|
+
* browse content.
|
|
138
151
|
*/
|
|
139
152
|
fun notifyBrowserConfigurationReady() {
|
|
140
|
-
val audioBrowser =
|
|
153
|
+
val audioBrowser = browser ?: return
|
|
141
154
|
|
|
142
155
|
if (!browserRegistered.isCompleted) {
|
|
143
156
|
Timber.d("Browser configuration ready - completing deferred")
|
|
@@ -155,31 +168,23 @@ class Player(internal val context: Context) {
|
|
|
155
168
|
mediaSessionCallback.updateMediaSession(
|
|
156
169
|
mediaSession,
|
|
157
170
|
options.capabilities,
|
|
158
|
-
options.
|
|
171
|
+
options.remoteButtonLayout,
|
|
159
172
|
searchAvailable,
|
|
173
|
+
options.forwardJumpInterval,
|
|
174
|
+
options.backwardJumpInterval,
|
|
160
175
|
)
|
|
161
176
|
}
|
|
162
177
|
}
|
|
163
178
|
}
|
|
164
179
|
|
|
165
180
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
181
|
+
* Drops a completed browser registration so [awaitBrowser] waits for the replacement instead of
|
|
182
|
+
* handing out the disposed browser. A [CompletableDeferred] completes once, so it would otherwise
|
|
183
|
+
* pin the first browser for the life of the process.
|
|
168
184
|
*/
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Timber.d("Wiring up artwork URL resolver")
|
|
173
|
-
browserManager.artworkUrlResolver = { track, perRouteConfig, imageContext ->
|
|
174
|
-
loader.transformArtworkUrlForTrack(track, perRouteConfig, imageContext)
|
|
175
|
-
}
|
|
176
|
-
// Clear content cache so that content resolved before the resolver was wired up
|
|
177
|
-
// will be re-fetched with artwork transformation applied
|
|
178
|
-
browserManager.clearContentCache()
|
|
179
|
-
Timber.d("Cleared content cache after wiring artwork resolver")
|
|
180
|
-
} else {
|
|
181
|
-
Timber.d("CoilBitmapLoader not available - artwork URL transformation disabled")
|
|
182
|
-
browserManager.artworkUrlResolver = null
|
|
185
|
+
fun forgetBrowserRegistration() {
|
|
186
|
+
if (browserRegistered.isCompleted) {
|
|
187
|
+
browserRegistered = CompletableDeferred()
|
|
183
188
|
}
|
|
184
189
|
}
|
|
185
190
|
|
|
@@ -200,133 +205,16 @@ class Player(internal val context: Context) {
|
|
|
200
205
|
throw e
|
|
201
206
|
}
|
|
202
207
|
|
|
203
|
-
/**
|
|
204
|
-
* ForwardingPlayer that intercepts external player actions and dispatches them to callbacks.
|
|
205
|
-
*
|
|
206
|
-
* This class blocks all external media item modifications to delegate control to RNAB. These
|
|
207
|
-
* overrides prevent media controllers (like Android Auto, notifications) from directly modifying
|
|
208
|
-
* the queue, ensuring all queue changes go through the RNAB API for proper state management and
|
|
209
|
-
* event handling.
|
|
210
|
-
*/
|
|
211
|
-
private inner class InterceptingPlayer(player: ExoPlayer) : ForwardingPlayer(player) {
|
|
212
|
-
|
|
213
|
-
override fun setMediaItems(mediaItems: MutableList<MediaItem>, resetPosition: Boolean) {
|
|
214
|
-
return super.setMediaItems(mediaItems, resetPosition)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
override fun addMediaItems(mediaItems: MutableList<MediaItem>) {
|
|
218
|
-
return super.addMediaItems(mediaItems)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
override fun addMediaItems(index: Int, mediaItems: MutableList<MediaItem>) {
|
|
222
|
-
return super.addMediaItems(index, mediaItems)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
override fun setMediaItems(
|
|
226
|
-
mediaItems: MutableList<MediaItem>,
|
|
227
|
-
startIndex: Int,
|
|
228
|
-
startPositionMs: Long,
|
|
229
|
-
) {
|
|
230
|
-
return super.setMediaItems(mediaItems, startIndex, startPositionMs)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
override fun setMediaItems(mediaItems: MutableList<MediaItem>) {
|
|
234
|
-
return super.setMediaItems(mediaItems)
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Intercept playback controls and dispatch to callbacks or fall back to default behavior
|
|
238
|
-
override fun play() {
|
|
239
|
-
if (callbacks?.handleRemotePlay() != true) {
|
|
240
|
-
super.play()
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
override fun pause() {
|
|
245
|
-
if (callbacks?.handleRemotePause() != true) {
|
|
246
|
-
super.pause()
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
override fun seekToNext() {
|
|
251
|
-
Timber.Forest.d("InterceptingPlayer.seekToNext() called")
|
|
252
|
-
if (callbacks?.handleRemoteNext() != true) {
|
|
253
|
-
super.seekToNext()
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
override fun seekToNextMediaItem() {
|
|
258
|
-
Timber.Forest.d("InterceptingPlayer.seekToNextMediaItem() called")
|
|
259
|
-
if (callbacks?.handleRemoteNext() != true) {
|
|
260
|
-
super.seekToNextMediaItem()
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
override fun seekToPrevious() {
|
|
265
|
-
Timber.Forest.d("InterceptingPlayer.seekToPrevious() called")
|
|
266
|
-
if (callbacks?.handleRemotePrevious() != true) {
|
|
267
|
-
super.seekToPrevious()
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
override fun seekToPreviousMediaItem() {
|
|
272
|
-
Timber.Forest.d("InterceptingPlayer.seekToPreviousMediaItem() called")
|
|
273
|
-
if (callbacks?.handleRemotePrevious() != true) {
|
|
274
|
-
super.seekToPreviousMediaItem()
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
override fun seekForward() {
|
|
279
|
-
Timber.Forest.d("InterceptingPlayer.seekForward() called")
|
|
280
|
-
if (
|
|
281
|
-
callbacks?.handleRemoteJumpForward(
|
|
282
|
-
RemoteJumpForwardEvent(interval = options.forwardJumpInterval)
|
|
283
|
-
) != true
|
|
284
|
-
) {
|
|
285
|
-
super.seekForward()
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
override fun seekBack() {
|
|
290
|
-
if (
|
|
291
|
-
callbacks?.handleRemoteJumpBackward(
|
|
292
|
-
RemoteJumpBackwardEvent(interval = options.backwardJumpInterval)
|
|
293
|
-
) != true
|
|
294
|
-
) {
|
|
295
|
-
super.seekBack()
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
override fun stop() {
|
|
300
|
-
if (callbacks?.handleRemoteStop() != true) {
|
|
301
|
-
super.stop()
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
override fun seekTo(mediaItemIndex: Int, positionMs: Long) {
|
|
306
|
-
if (
|
|
307
|
-
callbacks?.handleRemoteSeek(RemoteSeekEvent(position = positionMs.toDouble() / 1000.0)) !=
|
|
308
|
-
true
|
|
309
|
-
) {
|
|
310
|
-
super.seekTo(mediaItemIndex, positionMs)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
override fun seekTo(positionMs: Long) {
|
|
315
|
-
if (
|
|
316
|
-
callbacks?.handleRemoteSeek(RemoteSeekEvent(position = positionMs.toDouble() / 1000.0)) !=
|
|
317
|
-
true
|
|
318
|
-
) {
|
|
319
|
-
super.seekTo(positionMs)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
208
|
private lateinit var playerListener: PlayerListener
|
|
325
209
|
private var cache: SimpleCache? = null
|
|
326
210
|
|
|
327
|
-
private val
|
|
328
|
-
|
|
329
|
-
|
|
211
|
+
private val progressTimer: PlaybackTimer by lazy {
|
|
212
|
+
PlaybackTimer(
|
|
213
|
+
isActive = {
|
|
214
|
+
it == PlaybackState.LOADING || it == PlaybackState.BUFFERING || it == PlaybackState.PLAYING
|
|
215
|
+
}
|
|
216
|
+
) {
|
|
217
|
+
val index = currentIndex ?: return@PlaybackTimer
|
|
330
218
|
val event =
|
|
331
219
|
PlaybackProgressUpdatedEvent(
|
|
332
220
|
position = position.toSeconds(),
|
|
@@ -338,7 +226,12 @@ class Player(internal val context: Context) {
|
|
|
338
226
|
}
|
|
339
227
|
}
|
|
340
228
|
|
|
229
|
+
private val intervalTimer: PlaybackTimer by lazy {
|
|
230
|
+
PlaybackTimer(isActive = { it == PlaybackState.PLAYING }) { callbacks?.onPlaybackInterval() }
|
|
231
|
+
}
|
|
232
|
+
|
|
341
233
|
internal var playingState: PlayingState = PlayingState(false, false)
|
|
234
|
+
private set
|
|
342
235
|
|
|
343
236
|
val currentTrack: Track?
|
|
344
237
|
get() = exoPlayer.currentMediaItem?.let { TrackFactory.fromMedia3(it) }
|
|
@@ -352,9 +245,6 @@ class Player(internal val context: Context) {
|
|
|
352
245
|
internal var playbackState: PlaybackState = PlaybackState.NONE
|
|
353
246
|
private set
|
|
354
247
|
|
|
355
|
-
/** Current now playing metadata override (null = use track metadata) */
|
|
356
|
-
private var nowPlayingOverride: NowPlayingUpdate? = null
|
|
357
|
-
|
|
358
248
|
fun getPlayback(): Playback {
|
|
359
249
|
return Playback(playbackState, playbackError)
|
|
360
250
|
}
|
|
@@ -363,9 +253,33 @@ class Player(internal val context: Context) {
|
|
|
363
253
|
return playingState
|
|
364
254
|
}
|
|
365
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Re-derives [playingState] from the current `playWhenReady` + [playbackState] and emits
|
|
258
|
+
* `onPlaybackPlayingState` when it changed. The only writer of [playingState] — called from the
|
|
259
|
+
* state machine ([setPlaybackState]) and from [PlayerListener.onPlayWhenReadyChanged], the change
|
|
260
|
+
* points of its two inputs.
|
|
261
|
+
*/
|
|
262
|
+
internal fun refreshPlayingState() {
|
|
263
|
+
val newPlayingState = PlayingStateFactory.derive(playWhenReady, playbackState)
|
|
264
|
+
if (newPlayingState == playingState) return
|
|
265
|
+
Timber.d(
|
|
266
|
+
"PlayingState changed: playing=${newPlayingState.playing}, buffering=${newPlayingState.buffering}"
|
|
267
|
+
)
|
|
268
|
+
playingState = newPlayingState
|
|
269
|
+
callbacks?.onPlaybackPlayingState(playingState)
|
|
270
|
+
}
|
|
271
|
+
|
|
366
272
|
var playWhenReady: Boolean
|
|
367
273
|
get() = exoPlayer.playWhenReady
|
|
368
274
|
set(value) {
|
|
275
|
+
// Raising the intent at STATE_ENDED must replay, not silently no-op:
|
|
276
|
+
// ExoPlayer's setPlayWhenReady(true) never restarts an ended player (the
|
|
277
|
+
// ENDED→seekToDefaultPosition replay lives in media3's media-button
|
|
278
|
+
// path, not the Player API). In the setter so every writer gets it —
|
|
279
|
+
// play(), togglePlayback() and JS setPlayWhenReady alike.
|
|
280
|
+
if (value && exoPlayer.playbackState == ExoPlayer.STATE_ENDED) {
|
|
281
|
+
exoPlayer.seekToDefaultPosition()
|
|
282
|
+
}
|
|
369
283
|
playWhenReadyCache = value
|
|
370
284
|
exoPlayer.playWhenReady = value
|
|
371
285
|
}
|
|
@@ -398,8 +312,6 @@ class Player(internal val context: Context) {
|
|
|
398
312
|
val isPlaying
|
|
399
313
|
get() = exoPlayer.isPlaying
|
|
400
314
|
|
|
401
|
-
var ratingType: RatingType = RatingType.NONE
|
|
402
|
-
|
|
403
315
|
var repeatMode: RepeatMode
|
|
404
316
|
get() = RepeatModeFactory.fromMedia3(exoPlayer.repeatMode)
|
|
405
317
|
internal set(value) {
|
|
@@ -429,8 +341,14 @@ class Player(internal val context: Context) {
|
|
|
429
341
|
.map { index -> TrackFactory.fromMedia3(exoPlayer.getMediaItemAt(index)) }
|
|
430
342
|
.toTypedArray()
|
|
431
343
|
|
|
432
|
-
|
|
433
|
-
|
|
344
|
+
/**
|
|
345
|
+
* Whether nothing follows the current item in *playback order* — shuffle- and repeat-aware via
|
|
346
|
+
* media3's own order computation (the linear `currentMediaItemIndex == count - 1` check missed a
|
|
347
|
+
* shuffled queue whose playback order ends on a different linear index). Matches iOS's
|
|
348
|
+
* `isLastInPlaybackOrder`.
|
|
349
|
+
*/
|
|
350
|
+
val isLastInPlaybackOrder: Boolean
|
|
351
|
+
get() = exoPlayer.nextMediaItemIndex == C.INDEX_UNSET
|
|
434
352
|
|
|
435
353
|
/**
|
|
436
354
|
* The source path from which the current queue was expanded (e.g., from a contextual URL). Used
|
|
@@ -467,6 +385,12 @@ class Player(internal val context: Context) {
|
|
|
467
385
|
fun setup(setupOptions: PlayerSetupOptions) {
|
|
468
386
|
Timber.Forest.d("Setting up player with new options")
|
|
469
387
|
|
|
388
|
+
nowPlaying.enabled = setupOptions.autoUpdateNowPlayingMetadata
|
|
389
|
+
// Wrap the Nitro Func into a plain suspend call so the updater (and its tests) never touch
|
|
390
|
+
// bridge types; the double-await depth is unchanged (invoke -> bridge Promise -> JS promise).
|
|
391
|
+
nowPlaying.formatter =
|
|
392
|
+
setupOptions.nowPlayingMetadataFormatter?.let { f -> { params -> f.invoke(params).await() } }
|
|
393
|
+
|
|
470
394
|
val isInitialSetup = !::exoPlayer.isInitialized
|
|
471
395
|
|
|
472
396
|
if (!isInitialSetup) {
|
|
@@ -476,6 +400,9 @@ class Player(internal val context: Context) {
|
|
|
476
400
|
}
|
|
477
401
|
|
|
478
402
|
if (setupOptions.maxCacheSize > 0) {
|
|
403
|
+
// SimpleCache locks its folder — re-setup must release the previous
|
|
404
|
+
// instance first or the constructor throws.
|
|
405
|
+
cache?.release()
|
|
479
406
|
cache =
|
|
480
407
|
SimpleCache(
|
|
481
408
|
File(context.cacheDir, "RNAB"),
|
|
@@ -489,94 +416,46 @@ class Player(internal val context: Context) {
|
|
|
489
416
|
cache = null
|
|
490
417
|
}
|
|
491
418
|
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// Create bandwidth meter for adaptive bitrate selection in HLS/DASH
|
|
497
|
-
val bandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
|
|
498
|
-
|
|
499
|
-
loadControl = run {
|
|
500
|
-
val minBuffer = setupOptions.minBuffer.toInt()
|
|
501
|
-
val maxBuffer = setupOptions.maxBuffer.toInt()
|
|
502
|
-
val playBuffer = setupOptions.playBuffer.toInt()
|
|
503
|
-
// When automatic (rebufferBuffer is null), start at playBuffer and let AutomaticBufferManager
|
|
504
|
-
// adjust
|
|
505
|
-
// When fixed (rebufferBuffer is set), use that value
|
|
506
|
-
val playAfterRebuffer = setupOptions.rebufferBuffer?.toInt() ?: playBuffer
|
|
507
|
-
val backBuffer = setupOptions.backBuffer.toInt()
|
|
508
|
-
val config =
|
|
509
|
-
BufferConfig(
|
|
510
|
-
minBufferMs = minBuffer,
|
|
511
|
-
maxBufferMs = maxBuffer,
|
|
512
|
-
bufferForPlaybackMs = playBuffer,
|
|
513
|
-
bufferForPlaybackAfterRebufferMs = playAfterRebuffer,
|
|
514
|
-
backBufferMs = backBuffer,
|
|
515
|
-
)
|
|
516
|
-
DynamicLoadControl(initialConfig = config)
|
|
517
|
-
}
|
|
518
|
-
// Create MediaFactory with reference to browser for media URL transformation
|
|
519
|
-
// shouldRetry checks playWhenReady to avoid retrying when paused (e.g., another app took audio
|
|
520
|
-
// focus). Uses thread-safe cache since this is called from ExoPlayer's playback thread.
|
|
521
|
-
// isOnline and onRetryPending enable network-aware retry acceleration.
|
|
522
|
-
mediaFactory =
|
|
523
|
-
MediaFactory(
|
|
419
|
+
// One engine generation: load control, media factory, ExoPlayer (pure construction —
|
|
420
|
+
// see buildPlayerEngine; lifecycle and wiring stay here).
|
|
421
|
+
val engine =
|
|
422
|
+
buildPlayerEngine(
|
|
524
423
|
context,
|
|
424
|
+
setupOptions,
|
|
525
425
|
cache,
|
|
526
|
-
setupOptions.retryPolicy,
|
|
527
426
|
shouldRetry = { playWhenReadyCache },
|
|
528
427
|
isOnline = { networkMonitor.getOnline() },
|
|
529
|
-
|
|
428
|
+
hasPlayed = { hasPlayedCache },
|
|
429
|
+
onRetryPending = { exception, isNetworkError ->
|
|
530
430
|
// Track pending network retries for acceleration when connectivity returns
|
|
531
431
|
pendingNetworkRetry = isNetworkError
|
|
532
432
|
if (isNetworkError) {
|
|
533
433
|
Timber.d("Network retry pending, will accelerate on connectivity restoration")
|
|
534
434
|
}
|
|
435
|
+
reportRetryingError(exception)
|
|
535
436
|
},
|
|
536
|
-
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
exoPlayer =
|
|
542
|
-
ExoPlayer.Builder(context)
|
|
543
|
-
.setRenderersFactory(renderer)
|
|
544
|
-
.setBandwidthMeter(bandwidthMeter)
|
|
545
|
-
.setHandleAudioBecomingNoisy(setupOptions.handleAudioBecomingNoisy)
|
|
546
|
-
.setMediaSourceFactory(mediaFactory)
|
|
547
|
-
.setWakeMode(
|
|
548
|
-
when (setupOptions.wakeMode) {
|
|
549
|
-
AndroidPlayerWakeMode.NONE -> C.WAKE_MODE_NONE
|
|
550
|
-
AndroidPlayerWakeMode.LOCAL -> C.WAKE_MODE_LOCAL
|
|
551
|
-
AndroidPlayerWakeMode.NETWORK -> C.WAKE_MODE_NETWORK
|
|
552
|
-
}
|
|
553
|
-
)
|
|
554
|
-
.setLoadControl(loadControl)
|
|
555
|
-
.setName("AudioBrowser")
|
|
556
|
-
.build()
|
|
557
|
-
exoPlayer.setAudioAttributes(
|
|
558
|
-
AudioAttributes.Builder()
|
|
559
|
-
.setUsage(C.USAGE_MEDIA)
|
|
560
|
-
.setContentType(AndroidAudioContentTypeFactory.toMedia3(setupOptions.audioContentType))
|
|
561
|
-
.build(),
|
|
562
|
-
true, // handle audio focus
|
|
563
|
-
)
|
|
437
|
+
resolveMediaConfig = { url -> browser?.getMediaRequestConfig(url) },
|
|
438
|
+
)
|
|
439
|
+
loadControl = engine.loadControl
|
|
440
|
+
mediaFactory = engine.mediaFactory
|
|
441
|
+
exoPlayer = engine.exoPlayer
|
|
564
442
|
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
)
|
|
572
|
-
.setIsGaplessSupportRequired(it.gaplessSupportRequired)
|
|
573
|
-
.setIsSpeedChangeSupportRequired(it.rateChangeSupportRequired)
|
|
574
|
-
.build()
|
|
575
|
-
exoPlayer.trackSelectionParameters.audioOffloadPreferences = audioOffloadPreferences
|
|
443
|
+
// A rebuilt engine starts paused and fires no change event — sync the
|
|
444
|
+
// eager cache and tell JS, or the stale true sticks (and the emit dedupe
|
|
445
|
+
// would swallow the next real rise).
|
|
446
|
+
if (!isInitialSetup) {
|
|
447
|
+
playWhenReadyCache = false
|
|
448
|
+
emitPlayWhenReadyChanged(false)
|
|
576
449
|
}
|
|
577
450
|
|
|
578
451
|
// Recreate forwarding player with new ExoPlayer
|
|
579
|
-
forwardingPlayer =
|
|
452
|
+
forwardingPlayer =
|
|
453
|
+
InterceptingPlayer(
|
|
454
|
+
exoPlayer,
|
|
455
|
+
{ callbacks },
|
|
456
|
+
{ options },
|
|
457
|
+
keepSessionAliveOnError = setupOptions.keepSessionAliveOnError,
|
|
458
|
+
)
|
|
580
459
|
|
|
581
460
|
if (isInitialSetup) {
|
|
582
461
|
// Initial setup - create player listener and emit initial state
|
|
@@ -585,7 +464,7 @@ class Player(internal val context: Context) {
|
|
|
585
464
|
callbacks?.onPlaybackChanged(Playback(PlaybackState.NONE, null))
|
|
586
465
|
|
|
587
466
|
// Initialize equalizer with audio session ID
|
|
588
|
-
|
|
467
|
+
equalizer.initialize(exoPlayer.audioSessionId)
|
|
589
468
|
} else {
|
|
590
469
|
// Re-setup - re-add listener and update MediaSession
|
|
591
470
|
forwardingPlayer.addListener(playerListener)
|
|
@@ -620,8 +499,8 @@ class Player(internal val context: Context) {
|
|
|
620
499
|
}
|
|
621
500
|
|
|
622
501
|
/**
|
|
623
|
-
* Starts observing network connectivity changes and invokes the callback when state changes.
|
|
624
|
-
*
|
|
502
|
+
* Starts observing network connectivity changes and invokes the callback when state changes. Also
|
|
503
|
+
* handles accelerating pending network retries when connectivity is restored.
|
|
625
504
|
*
|
|
626
505
|
* @param scope The coroutine scope to use for observation
|
|
627
506
|
*/
|
|
@@ -629,6 +508,10 @@ class Player(internal val context: Context) {
|
|
|
629
508
|
networkMonitor.observeOnline(scope) { isOnline ->
|
|
630
509
|
callbacks?.onOnlineChanged(isOnline)
|
|
631
510
|
|
|
511
|
+
// Re-render now-playing so the formatter's stall classification (buffering vs offline) tracks
|
|
512
|
+
// connectivity immediately, not only on the next playback transition.
|
|
513
|
+
nowPlaying.render()
|
|
514
|
+
|
|
632
515
|
// Accelerate pending network retry when connectivity is restored
|
|
633
516
|
if (isOnline && pendingNetworkRetry) {
|
|
634
517
|
Timber.d("Network restored with pending retry, triggering immediate retry")
|
|
@@ -804,36 +687,18 @@ class Player(internal val context: Context) {
|
|
|
804
687
|
* controllers without interrupting playback.
|
|
805
688
|
*/
|
|
806
689
|
fun setActiveTrackFavorited(favorited: Boolean) {
|
|
807
|
-
val index = exoPlayer.currentMediaItemIndex
|
|
808
|
-
if (index == C.INDEX_UNSET) return
|
|
809
|
-
|
|
810
690
|
val currentTrack = this.currentTrack ?: return
|
|
811
691
|
|
|
812
|
-
//
|
|
692
|
+
// Persist to the native favorites cache regardless of player state — the
|
|
693
|
+
// user's gesture is durable even when there's no active media item to
|
|
694
|
+
// update (queue tear-down, between-track gaps, etc.).
|
|
813
695
|
currentTrack.src?.let { src -> browser?.browserManager?.updateFavorite(src, favorited) }
|
|
814
696
|
|
|
697
|
+
val index = exoPlayer.currentMediaItemIndex
|
|
698
|
+
if (index == C.INDEX_UNSET) return
|
|
699
|
+
|
|
815
700
|
// Create updated Track with new favorited state
|
|
816
|
-
val updatedTrack =
|
|
817
|
-
Track(
|
|
818
|
-
url = currentTrack.url,
|
|
819
|
-
src = currentTrack.src,
|
|
820
|
-
artwork = currentTrack.artwork,
|
|
821
|
-
artworkSource = currentTrack.artworkSource,
|
|
822
|
-
artworkCarPlayTinted = currentTrack.artworkCarPlayTinted,
|
|
823
|
-
title = currentTrack.title,
|
|
824
|
-
subtitle = currentTrack.subtitle,
|
|
825
|
-
artist = currentTrack.artist,
|
|
826
|
-
album = currentTrack.album,
|
|
827
|
-
description = currentTrack.description,
|
|
828
|
-
genre = currentTrack.genre,
|
|
829
|
-
duration = currentTrack.duration,
|
|
830
|
-
style = currentTrack.style,
|
|
831
|
-
childrenStyle = currentTrack.childrenStyle,
|
|
832
|
-
favorited = favorited,
|
|
833
|
-
groupTitle = currentTrack.groupTitle,
|
|
834
|
-
live = currentTrack.live,
|
|
835
|
-
imageRow = currentTrack.imageRow,
|
|
836
|
-
)
|
|
701
|
+
val updatedTrack = currentTrack.copy(favorited = favorited)
|
|
837
702
|
|
|
838
703
|
// Use buildUpon() on the existing MediaItem to update only the metadata
|
|
839
704
|
// This preserves internal references and avoids playback interruption
|
|
@@ -881,78 +746,152 @@ class Player(internal val context: Context) {
|
|
|
881
746
|
// MARK: - Now Playing Metadata
|
|
882
747
|
|
|
883
748
|
/**
|
|
884
|
-
*
|
|
885
|
-
*
|
|
749
|
+
* Now Playing rendering (flash/override/formatter precedence, dedupe, stale guards, artwork
|
|
750
|
+
* keying) lives in [NowPlayingUpdater]; this object is its platform surface — current playback
|
|
751
|
+
* reads plus MediaItem stamping via [replaceMediaItem], and artwork resolution via the browser.
|
|
886
752
|
*/
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
753
|
+
val nowPlaying: NowPlayingUpdater =
|
|
754
|
+
NowPlayingUpdater(
|
|
755
|
+
object : NowPlayingSurface {
|
|
756
|
+
override val currentIndex
|
|
757
|
+
get() = this@Player.currentIndex
|
|
758
|
+
|
|
759
|
+
override val currentTrack
|
|
760
|
+
get() = this@Player.currentTrack
|
|
761
|
+
|
|
762
|
+
override val playbackState
|
|
763
|
+
get() = this@Player.playbackState
|
|
764
|
+
|
|
765
|
+
override val playbackError
|
|
766
|
+
get() = this@Player.playbackError
|
|
767
|
+
|
|
768
|
+
override val playWhenReady
|
|
769
|
+
get() = exoPlayer.playWhenReady
|
|
770
|
+
|
|
771
|
+
override val isRebuffering
|
|
772
|
+
get() = loadControl.isRebuffering
|
|
773
|
+
|
|
774
|
+
override val isOnline
|
|
775
|
+
get() = networkMonitor.getOnline()
|
|
776
|
+
|
|
777
|
+
override val hasNowPlayingArtworkConfig
|
|
778
|
+
get() = browser?.browserManager?.config?.nowPlayingArtwork != null
|
|
779
|
+
|
|
780
|
+
override fun stampFields(
|
|
781
|
+
index: Int,
|
|
782
|
+
track: Track,
|
|
783
|
+
title: String?,
|
|
784
|
+
secondaryLine: String?,
|
|
785
|
+
album: String?,
|
|
786
|
+
) =
|
|
787
|
+
restampMediaItem(index, track) {
|
|
788
|
+
setTitle(title)
|
|
789
|
+
.setDisplayTitle(title)
|
|
790
|
+
.setArtist(secondaryLine)
|
|
791
|
+
// Android Auto reads DISPLAY_SUBTITLE (not ARTIST) once DISPLAY_TITLE is set, so
|
|
792
|
+
// mirror the line into `subtitle`; `artist` still drives the lock screen /
|
|
793
|
+
// notification.
|
|
794
|
+
.setSubtitle(secondaryLine)
|
|
795
|
+
.setAlbumTitle(album)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
override fun stampArtwork(index: Int, track: Track, uri: String) =
|
|
799
|
+
restampMediaItem(index, track) { setArtworkUri(uri.toUri()) }
|
|
800
|
+
|
|
801
|
+
override suspend fun resolveNowPlayingArtwork(track: Track, sizePx: Double): String? {
|
|
802
|
+
val browserManager = browser?.browserManager ?: return null
|
|
803
|
+
val config = browserManager.config.nowPlayingArtwork ?: return null
|
|
804
|
+
val resolved =
|
|
805
|
+
try {
|
|
806
|
+
browserManager.resolveArtworkUrl(track, config, ImageContext(sizePx, sizePx))
|
|
807
|
+
} catch (e: Exception) {
|
|
808
|
+
Timber.e(e, "Failed to resolve now-playing artwork for track id=${track.id}")
|
|
809
|
+
null
|
|
810
|
+
}
|
|
811
|
+
val uri = resolved?.uri?.takeIf { it.isNotEmpty() } ?: return null
|
|
812
|
+
// Remember how this URI was produced: Media3 hands it back to the bitmap loader, which
|
|
813
|
+
// re-resolves Track-first (with the nowPlayingArtwork kind, not the global artwork
|
|
814
|
+
// config).
|
|
815
|
+
browserManager.artworkResolutions.register(uri, track, config)
|
|
816
|
+
return uri
|
|
817
|
+
}
|
|
891
818
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
return NowPlayingMetadata(
|
|
898
|
-
elapsedTime = null,
|
|
899
|
-
title = override?.title ?: track.title,
|
|
900
|
-
album = track.album,
|
|
901
|
-
artist = override?.artist ?: track.artist,
|
|
902
|
-
duration = track.duration,
|
|
903
|
-
artwork = track.artwork,
|
|
904
|
-
description = track.description,
|
|
905
|
-
mediaId = track.src ?: track.url,
|
|
906
|
-
genre = track.genre,
|
|
907
|
-
rating = null, // TODO: map track.favorited to rating if needed
|
|
819
|
+
override fun emitNowPlayingChanged(metadata: NowPlayingMetadata) {
|
|
820
|
+
callbacks?.onNowPlayingChanged(metadata)
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
MainScope(),
|
|
908
824
|
)
|
|
909
|
-
}
|
|
910
825
|
|
|
911
|
-
/**
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
826
|
+
/** Republishes the item at [index] with mutated metadata, preserving uri and Track tag. */
|
|
827
|
+
private fun restampMediaItem(
|
|
828
|
+
index: Int,
|
|
829
|
+
track: Track,
|
|
830
|
+
mutate: MediaMetadata.Builder.() -> MediaMetadata.Builder,
|
|
831
|
+
) {
|
|
832
|
+
val currentMediaItem = exoPlayer.getMediaItemAt(index)
|
|
833
|
+
val updatedMediaItem =
|
|
834
|
+
currentMediaItem
|
|
835
|
+
.buildUpon()
|
|
836
|
+
.setUri(currentMediaItem.localConfiguration?.uri)
|
|
837
|
+
.setMediaMetadata(currentMediaItem.mediaMetadata.buildUpon().mutate().build())
|
|
838
|
+
.setTag(track)
|
|
839
|
+
.build()
|
|
840
|
+
exoPlayer.replaceMediaItem(index, updatedMediaItem)
|
|
917
841
|
}
|
|
918
842
|
|
|
919
843
|
/**
|
|
920
844
|
* Resets the retry timer when track changes. Called from PlayerListener.onMediaItemTransition.
|
|
921
845
|
*/
|
|
846
|
+
/**
|
|
847
|
+
* A restart from a terminal error begins a new load of the same track: fresh retry budgets and
|
|
848
|
+
* unproven playback, so the user's tap yields a visible first-connect retry window instead of a
|
|
849
|
+
* single silent attempt — the same behavior as re-selecting the track (ADR 0004). Hooked off the
|
|
850
|
+
* player error clearing: every recovery path (retry(), play(), the transport controls' idle-error
|
|
851
|
+
* recovery) goes through exoPlayer.prepare(), which clears the error.
|
|
852
|
+
*/
|
|
853
|
+
internal fun resetForNewLoad() {
|
|
854
|
+
resetRetryTimer()
|
|
855
|
+
hasPlayedCache = false
|
|
856
|
+
}
|
|
857
|
+
|
|
922
858
|
internal fun resetRetryTimer() {
|
|
923
859
|
mediaFactory.resetRetryTimer()
|
|
924
860
|
}
|
|
925
861
|
|
|
926
862
|
/**
|
|
927
|
-
*
|
|
928
|
-
*
|
|
863
|
+
* Finds the queue Track whose published artwork URI matches [uri], for display-time artwork
|
|
864
|
+
* resolution of app-supplied queue tracks that never went through browse (so they are not in the
|
|
865
|
+
* resolution registry). Must run on the main thread (ExoPlayer access).
|
|
929
866
|
*/
|
|
930
|
-
private fun
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
.setTitle(override?.title ?: track.title)
|
|
940
|
-
.setDisplayTitle(override?.title ?: track.title)
|
|
941
|
-
.setArtist(override?.artist ?: track.artist)
|
|
942
|
-
.build()
|
|
943
|
-
|
|
944
|
-
val updatedMediaItem =
|
|
945
|
-
currentMediaItem
|
|
946
|
-
.buildUpon()
|
|
947
|
-
.setUri(currentMediaItem.localConfiguration?.uri)
|
|
948
|
-
.setMediaMetadata(updatedMetadata)
|
|
949
|
-
.setTag(track)
|
|
950
|
-
.build()
|
|
951
|
-
|
|
952
|
-
exoPlayer.replaceMediaItem(index, updatedMediaItem)
|
|
867
|
+
private fun findQueueTrackByArtworkUri(uri: String): Track? {
|
|
868
|
+
for (i in 0 until exoPlayer.mediaItemCount) {
|
|
869
|
+
val item = exoPlayer.getMediaItemAt(i)
|
|
870
|
+
if (item.mediaMetadata.artworkUri?.toString() == uri) {
|
|
871
|
+
return item.localConfiguration?.tag as? Track
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return null
|
|
875
|
+
}
|
|
953
876
|
|
|
954
|
-
|
|
955
|
-
|
|
877
|
+
/**
|
|
878
|
+
* Track-first display-time artwork resolution for [CoilBitmapLoader]: registry hit
|
|
879
|
+
* (browse/now-playing-resolved URIs) → queue-tag lookup (app-supplied tracks with raw artwork) →
|
|
880
|
+
* header-only fallback for unattributable URIs (registry eviction / process-death restore). Null
|
|
881
|
+
* means "fetch the URI as-is".
|
|
882
|
+
*/
|
|
883
|
+
suspend fun resolveDisplayArtwork(uri: String, sizeHintPixels: Int?): ImageSource? {
|
|
884
|
+
val browserManager = browser?.browserManager ?: return null
|
|
885
|
+
val imageContext =
|
|
886
|
+
sizeHintPixels?.takeIf { it > 0 }?.let { ImageContext(it.toDouble(), it.toDouble()) }
|
|
887
|
+
browserManager.displayArtworkSource(uri, imageContext)?.let {
|
|
888
|
+
return it
|
|
889
|
+
}
|
|
890
|
+
withContext(Dispatchers.Main) { findQueueTrackByArtworkUri(uri) }
|
|
891
|
+
?.let { track ->
|
|
892
|
+
return browserManager.resolveArtworkUrl(track, null, imageContext)
|
|
893
|
+
}
|
|
894
|
+
return browserManager.unattributedArtworkSource(uri)
|
|
956
895
|
}
|
|
957
896
|
|
|
958
897
|
/**
|
|
@@ -975,12 +914,25 @@ class Player(internal val context: Context) {
|
|
|
975
914
|
}
|
|
976
915
|
|
|
977
916
|
fun play() {
|
|
978
|
-
|
|
917
|
+
// Through the setter so the STATE_ENDED replay handling applies
|
|
918
|
+
// (ExoPlayer.play() is only setPlayWhenReady(true)).
|
|
919
|
+
playWhenReady = true
|
|
979
920
|
if (currentTrack != null) {
|
|
921
|
+
// No-op unless the player is STATE_IDLE (ExoPlayer.prepare early-returns
|
|
922
|
+
// otherwise), so this only reconnects after a stop() or error and never
|
|
923
|
+
// re-buffers a healthy stream. Reconnecting is also how live streams
|
|
924
|
+
// rejoin the live edge on resume.
|
|
980
925
|
exoPlayer.prepare()
|
|
981
926
|
}
|
|
982
927
|
}
|
|
983
928
|
|
|
929
|
+
/** Jump to the live edge (default position) of a live item; no-op otherwise. */
|
|
930
|
+
fun seekToLiveEdge() {
|
|
931
|
+
if (exoPlayer.isCurrentMediaItemLive) {
|
|
932
|
+
exoPlayer.seekToDefaultPosition()
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
984
936
|
/**
|
|
985
937
|
* Executes a search and plays the results. Used for voice commands with structured search
|
|
986
938
|
* parameters.
|
|
@@ -990,7 +942,20 @@ class Player(internal val context: Context) {
|
|
|
990
942
|
*/
|
|
991
943
|
suspend fun playFromSearch(params: SearchParams): Boolean {
|
|
992
944
|
return try {
|
|
993
|
-
val
|
|
945
|
+
val audioBrowser = awaitBrowser()
|
|
946
|
+
// A gate blocks external-surface "play" search too — otherwise voice search is a way around
|
|
947
|
+
// the gate (mirrors the iOS play-media intent guard). Resolved per request; a gated decision
|
|
948
|
+
// fires onGate(search) and refuses.
|
|
949
|
+
val gateOutcome =
|
|
950
|
+
audioBrowser.gateDecision(
|
|
951
|
+
NativeGateRequest(reason = GateReason.SEARCH, path = null, search = params)
|
|
952
|
+
)
|
|
953
|
+
if (gateOutcome.gated) {
|
|
954
|
+
Timber.i("playFromSearch refused — request is gated")
|
|
955
|
+
audioBrowser.onGate(GateEvent(GateReason.SEARCH))
|
|
956
|
+
return false
|
|
957
|
+
}
|
|
958
|
+
val browserManager = audioBrowser.browserManager
|
|
994
959
|
|
|
995
960
|
Timber.d(
|
|
996
961
|
"Executing voice search: mode=${params.mode}, query='${params.query}', artist='${params.artist}', album='${params.album}'"
|
|
@@ -1019,7 +984,10 @@ class Player(internal val context: Context) {
|
|
|
1019
984
|
}
|
|
1020
985
|
|
|
1021
986
|
fun pause() {
|
|
1022
|
-
|
|
987
|
+
// Through the setter so the eager playWhenReadyCache write holds for the
|
|
988
|
+
// drop direction too — the retry policy reads it off-main before
|
|
989
|
+
// ExoPlayer's listener round-trip syncs it.
|
|
990
|
+
playWhenReady = false
|
|
1023
991
|
}
|
|
1024
992
|
|
|
1025
993
|
fun togglePlayback() {
|
|
@@ -1036,8 +1004,11 @@ class Player(internal val context: Context) {
|
|
|
1036
1004
|
* required for playback.
|
|
1037
1005
|
*/
|
|
1038
1006
|
fun stop() {
|
|
1039
|
-
|
|
1040
|
-
|
|
1007
|
+
// State first so the machine's STOPPED guard suppresses the PAUSED transition
|
|
1008
|
+
// from the playWhenReady drop below; through setPlaybackState so the change
|
|
1009
|
+
// is emitted (direct field assignment bypasses events — see its docstring).
|
|
1010
|
+
setPlaybackState(PlaybackState.STOPPED)
|
|
1011
|
+
playWhenReady = false
|
|
1041
1012
|
exoPlayer.stop()
|
|
1042
1013
|
}
|
|
1043
1014
|
|
|
@@ -1051,7 +1022,15 @@ class Player(internal val context: Context) {
|
|
|
1051
1022
|
* use [pause].
|
|
1052
1023
|
*/
|
|
1053
1024
|
fun destroy() {
|
|
1025
|
+
// Cancel without a final save: the loop would tick against the released
|
|
1026
|
+
// player and persist position 0 over the real resumption position.
|
|
1027
|
+
playbackStateStore.cancelPeriodicSave()
|
|
1028
|
+
// An armed timer's Runnable would outlive the release and fire pause() into the
|
|
1029
|
+
// released player. Cleared here, while a mid-ramp fade can still restore volume.
|
|
1030
|
+
sleepTimer.clear()
|
|
1054
1031
|
stop()
|
|
1032
|
+
nowPlaying.destroy()
|
|
1033
|
+
mediaSessionCallback.destroy()
|
|
1055
1034
|
forwardingPlayer.removeListener(playerListener)
|
|
1056
1035
|
automaticBufferManager?.detach()
|
|
1057
1036
|
automaticBufferManager = null
|
|
@@ -1059,8 +1038,7 @@ class Player(internal val context: Context) {
|
|
|
1059
1038
|
cache?.release()
|
|
1060
1039
|
cache = null
|
|
1061
1040
|
networkMonitor.destroy()
|
|
1062
|
-
|
|
1063
|
-
equalizerManager = null
|
|
1041
|
+
equalizer.release()
|
|
1064
1042
|
}
|
|
1065
1043
|
|
|
1066
1044
|
fun seekTo(duration: Long, unit: TimeUnit) {
|
|
@@ -1073,6 +1051,57 @@ class Player(internal val context: Context) {
|
|
|
1073
1051
|
exoPlayer.seekTo(positionMs)
|
|
1074
1052
|
}
|
|
1075
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Invalidates in-flight [reportRetryingError] posts: bumped on track change so a report
|
|
1056
|
+
* classified for the outgoing track can't land on the incoming one's spinner. Written on main,
|
|
1057
|
+
* read on ExoPlayer's loader thread.
|
|
1058
|
+
*/
|
|
1059
|
+
@Volatile private var retryingErrorGeneration = 0
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Surfaces a load failure the retry policy is still working on: attached to the current
|
|
1063
|
+
* (non-terminal) state via [Callbacks.onPlaybackChanged], so UIs can show the cause over the
|
|
1064
|
+
* spinner. Not an ERROR transition, and not a [Callbacks.onPlaybackError] — that event stays
|
|
1065
|
+
* reserved for terminal errors. Called from ExoPlayer's loader thread, hence the main hop.
|
|
1066
|
+
*/
|
|
1067
|
+
private fun reportRetryingError(exception: IOException) {
|
|
1068
|
+
val error = PlaybackErrorClassifier.retryingLoadError(exception, networkMonitor.getOnline())
|
|
1069
|
+
val generation = retryingErrorGeneration
|
|
1070
|
+
mainHandler.post {
|
|
1071
|
+
// The track changed while this post was in flight — the error belongs to the old one.
|
|
1072
|
+
if (generation != retryingErrorGeneration) return@post
|
|
1073
|
+
// Only attach while the failure is what the listener is looking at. During PLAYING/READY
|
|
1074
|
+
// buffered audio is still audibly fine, and ExoPlayer's in-place retry can succeed without
|
|
1075
|
+
// any state change — an advisory attached there would never clear.
|
|
1076
|
+
if (
|
|
1077
|
+
playbackState != PlaybackState.LOADING &&
|
|
1078
|
+
playbackState != PlaybackState.BUFFERING &&
|
|
1079
|
+
playbackState != PlaybackState.PAUSED
|
|
1080
|
+
) {
|
|
1081
|
+
return@post
|
|
1082
|
+
}
|
|
1083
|
+
// Each failed attempt re-reports; identical repeats add nothing (and would
|
|
1084
|
+
// re-render now-playing every backoff tick).
|
|
1085
|
+
if (playbackError == error) return@post
|
|
1086
|
+
playbackError = error
|
|
1087
|
+
callbacks?.onPlaybackChanged(Playback(playbackState, error))
|
|
1088
|
+
nowPlaying.render()
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Drops a retrying error whose track is gone. The new track may keep the state on
|
|
1094
|
+
* LOADING/BUFFERING — no state-change emit — so the clear is emitted here, or the old track's
|
|
1095
|
+
* error would linger over the new one's spinner.
|
|
1096
|
+
*/
|
|
1097
|
+
internal fun clearRetryingError() {
|
|
1098
|
+
retryingErrorGeneration++
|
|
1099
|
+
if (playbackError?.retrying != true) return
|
|
1100
|
+
playbackError = null
|
|
1101
|
+
callbacks?.onPlaybackChanged(Playback(playbackState, null))
|
|
1102
|
+
nowPlaying.render()
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1076
1105
|
/**
|
|
1077
1106
|
* Updates the player state and emits a state change event if the state has changed. Only emits an
|
|
1078
1107
|
* event if the new state differs from the current state.
|
|
@@ -1088,39 +1117,78 @@ class Player(internal val context: Context) {
|
|
|
1088
1117
|
val oldState = playbackState
|
|
1089
1118
|
playbackState = state
|
|
1090
1119
|
|
|
1120
|
+
// Audio rendered: this load has proven itself — retries from here on get
|
|
1121
|
+
// the retry policy's full recovery budget, not the short first-connect one.
|
|
1122
|
+
if (state == PlaybackState.PLAYING) {
|
|
1123
|
+
hasPlayedCache = true
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// A natural end exhausts the play intent — nothing is left to play. Keeping
|
|
1127
|
+
// playWhenReady true inverted the play/pause toggle, held audio focus forever
|
|
1128
|
+
// (ExoPlayer abandons it only at IDLE or on the intent dropping), and kept the
|
|
1129
|
+
// periodic position save running. Cleared and emitted before the state /
|
|
1130
|
+
// queue-ended callbacks so JS observes the native order (intent → state →
|
|
1131
|
+
// queueEnded); the media3 listener's later echo is deduplicated by
|
|
1132
|
+
// emitPlayWhenReadyChanged. The state machine's ENDED guard keeps the drop
|
|
1133
|
+
// from re-reporting the state as PAUSED.
|
|
1134
|
+
if (state == PlaybackState.ENDED && playWhenReady) {
|
|
1135
|
+
playWhenReady = false
|
|
1136
|
+
emitPlayWhenReadyChanged(false)
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// The last track fires no AUTO media-item transition, so an armed
|
|
1140
|
+
// end-of-track sleep timer would stay set forever and pause the next
|
|
1141
|
+
// track played instead.
|
|
1142
|
+
if (state == PlaybackState.ENDED) {
|
|
1143
|
+
sleepTimer.onTrackEnd()
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1091
1146
|
// Clear error when transitioning away from error state
|
|
1092
1147
|
if (oldState == PlaybackState.ERROR) {
|
|
1093
1148
|
playbackError = null
|
|
1094
1149
|
callbacks?.onPlaybackError(null)
|
|
1095
1150
|
}
|
|
1096
1151
|
|
|
1152
|
+
// A retrying error rides along through LOADING/BUFFERING/PAUSED; a state that
|
|
1153
|
+
// proves recovery (READY/PLAYING — data flowed) or abandons the attempt clears
|
|
1154
|
+
// it, before the Playback below is built so the emitted event is already clean.
|
|
1155
|
+
if (
|
|
1156
|
+
playbackError?.retrying == true &&
|
|
1157
|
+
state != PlaybackState.LOADING &&
|
|
1158
|
+
state != PlaybackState.BUFFERING &&
|
|
1159
|
+
state != PlaybackState.PAUSED
|
|
1160
|
+
) {
|
|
1161
|
+
playbackError = null
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1097
1164
|
val playback = Playback(state, playbackError)
|
|
1098
1165
|
callbacks?.onPlaybackChanged(playback)
|
|
1099
1166
|
|
|
1100
|
-
//
|
|
1167
|
+
// Re-render the now-playing on every state change: the metadata formatter receives
|
|
1168
|
+
// `playbackState` (plus isRebuffering / isOnline / error), so any transition can change its
|
|
1169
|
+
// output — the live song while paused, a "Reconnecting…" line on a rebuffer, an offline/error
|
|
1170
|
+
// line. The publish-dedupe in applyNowPlayingFields drops redundant updates, so re-running
|
|
1171
|
+
// through the rapid startup sequence (none→loading→buffering→ready→playing) is cheap.
|
|
1172
|
+
nowPlaying.render()
|
|
1173
|
+
|
|
1174
|
+
// Emit queue ended event when playback ends on the last track. Repeat modes never
|
|
1175
|
+
// conceptually end the queue (matches web's endsQueue()): an ENDED that surfaces while
|
|
1176
|
+
// repeating (e.g. a seek to the end) must not read as "playlist over".
|
|
1101
1177
|
// This coupling ensures queue ended events are always triggered consistently with state
|
|
1102
1178
|
// changes
|
|
1103
|
-
if (state == PlaybackState.ENDED &&
|
|
1179
|
+
if (state == PlaybackState.ENDED && isLastInPlaybackOrder && repeatMode == RepeatMode.OFF) {
|
|
1104
1180
|
currentIndex?.let { index ->
|
|
1105
1181
|
val event =
|
|
1106
1182
|
PlaybackQueueEndedEvent(track = index.toDouble(), position = position.toSeconds())
|
|
1107
1183
|
callbacks?.onPlaybackQueueEnded(event)
|
|
1108
1184
|
}
|
|
1109
|
-
// Reset saved position to 0 so resumption starts from beginning
|
|
1110
|
-
|
|
1111
|
-
playbackStateStore.savePositionZero()
|
|
1112
|
-
}
|
|
1185
|
+
// Reset saved position to 0 so resumption starts from beginning
|
|
1186
|
+
playbackStateStore.savePositionZero()
|
|
1113
1187
|
}
|
|
1114
1188
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
Timber.d(
|
|
1119
|
-
"PlayingState changed: playing=${newPlayingState.playing}, buffering=${newPlayingState.buffering}"
|
|
1120
|
-
)
|
|
1121
|
-
playingState = newPlayingState
|
|
1122
|
-
callbacks?.onPlaybackPlayingState(playingState)
|
|
1123
|
-
}
|
|
1189
|
+
progressTimer.onPlaybackStateChanged(state)
|
|
1190
|
+
intervalTimer.onPlaybackStateChanged(state)
|
|
1191
|
+
refreshPlayingState()
|
|
1124
1192
|
}
|
|
1125
1193
|
}
|
|
1126
1194
|
|
|
@@ -1130,7 +1198,11 @@ class Player(internal val context: Context) {
|
|
|
1130
1198
|
* @param interval The interval in seconds, or null to disable progress updates
|
|
1131
1199
|
*/
|
|
1132
1200
|
fun setProgressUpdateInterval(interval: Double?) {
|
|
1133
|
-
|
|
1201
|
+
progressTimer.setInterval(interval)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
fun setPlaybackIntervalEnabled(enabled: Boolean) {
|
|
1205
|
+
intervalTimer.setInterval(if (enabled) 1.0 else null)
|
|
1134
1206
|
}
|
|
1135
1207
|
|
|
1136
1208
|
/**
|
|
@@ -1149,8 +1221,6 @@ class Player(internal val context: Context) {
|
|
|
1149
1221
|
|
|
1150
1222
|
// Check what changed
|
|
1151
1223
|
val skipSilenceChanged = previousOptions.skipSilence != options.skipSilence
|
|
1152
|
-
val ratingTypeChanged = previousOptions.ratingType != options.ratingType
|
|
1153
|
-
val shuffleChanged = previousOptions.shuffle != options.shuffle
|
|
1154
1224
|
val progressUpdateEventIntervalChanged =
|
|
1155
1225
|
previousOptions.progressUpdateEventInterval != options.progressUpdateEventInterval
|
|
1156
1226
|
val forwardJumpIntervalChanged =
|
|
@@ -1158,20 +1228,18 @@ class Player(internal val context: Context) {
|
|
|
1158
1228
|
val backwardJumpIntervalChanged =
|
|
1159
1229
|
previousOptions.backwardJumpInterval != options.backwardJumpInterval
|
|
1160
1230
|
val capabilitiesChanged = previousOptions.capabilities != options.capabilities
|
|
1161
|
-
val
|
|
1162
|
-
previousOptions.
|
|
1231
|
+
val remoteButtonLayoutChanged =
|
|
1232
|
+
!previousOptions.remoteButtonLayout.sameAs(options.remoteButtonLayout)
|
|
1163
1233
|
val appKilledPlaybackBehaviorChanged =
|
|
1164
1234
|
previousOptions.appKilledPlaybackBehavior != options.appKilledPlaybackBehavior
|
|
1165
1235
|
|
|
1166
1236
|
val hasChanged =
|
|
1167
1237
|
skipSilenceChanged ||
|
|
1168
|
-
ratingTypeChanged ||
|
|
1169
|
-
shuffleChanged ||
|
|
1170
1238
|
progressUpdateEventIntervalChanged ||
|
|
1171
1239
|
forwardJumpIntervalChanged ||
|
|
1172
1240
|
backwardJumpIntervalChanged ||
|
|
1173
1241
|
capabilitiesChanged ||
|
|
1174
|
-
|
|
1242
|
+
remoteButtonLayoutChanged ||
|
|
1175
1243
|
appKilledPlaybackBehaviorChanged
|
|
1176
1244
|
|
|
1177
1245
|
// Apply only changed properties
|
|
@@ -1179,25 +1247,32 @@ class Player(internal val context: Context) {
|
|
|
1179
1247
|
skipSilence = options.skipSilence
|
|
1180
1248
|
}
|
|
1181
1249
|
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
if (shuffleChanged) {
|
|
1187
|
-
shuffleMode = options.shuffle
|
|
1250
|
+
if (capabilitiesChanged) {
|
|
1251
|
+
// The `favorite` capability is the single favoriting switch — propagate
|
|
1252
|
+
// its match mode to the browser so it can hydrate row hearts.
|
|
1253
|
+
browser?.browserManager?.setFavoriteMatch(options.capabilities.favoriteMatch)
|
|
1188
1254
|
}
|
|
1189
1255
|
|
|
1190
1256
|
if (progressUpdateEventIntervalChanged) {
|
|
1191
1257
|
setProgressUpdateInterval(options.progressUpdateEventInterval)
|
|
1192
1258
|
}
|
|
1193
1259
|
|
|
1194
|
-
|
|
1260
|
+
// The jump intervals pick the button icons (ICON_SKIP_BACK_15 and friends), so a changed
|
|
1261
|
+
// interval has to rebuild the layout even when capabilities and placement are untouched.
|
|
1262
|
+
if (
|
|
1263
|
+
capabilitiesChanged ||
|
|
1264
|
+
remoteButtonLayoutChanged ||
|
|
1265
|
+
forwardJumpIntervalChanged ||
|
|
1266
|
+
backwardJumpIntervalChanged
|
|
1267
|
+
) {
|
|
1195
1268
|
val searchAvailable = browser?.browserManager?.config?.hasSearch ?: false
|
|
1196
1269
|
mediaSessionCallback.updateMediaSession(
|
|
1197
1270
|
mediaSession,
|
|
1198
1271
|
options.capabilities,
|
|
1199
|
-
options.
|
|
1272
|
+
options.remoteButtonLayout,
|
|
1200
1273
|
searchAvailable,
|
|
1274
|
+
options.forwardJumpInterval,
|
|
1275
|
+
options.backwardJumpInterval,
|
|
1201
1276
|
)
|
|
1202
1277
|
}
|
|
1203
1278
|
|
|
@@ -1241,187 +1316,6 @@ class Player(internal val context: Context) {
|
|
|
1241
1316
|
return networkMonitor.getOnline()
|
|
1242
1317
|
}
|
|
1243
1318
|
|
|
1244
|
-
/** Initializes the equalizer with the player's audio session ID. */
|
|
1245
|
-
private fun initializeEqualizer() {
|
|
1246
|
-
val audioSessionId = exoPlayer.audioSessionId
|
|
1247
|
-
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
|
|
1248
|
-
Timber.d("Skipping equalizer init - no audio session yet, will init on first playback")
|
|
1249
|
-
return
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
try {
|
|
1253
|
-
equalizerManager =
|
|
1254
|
-
EqualizerManager(audioSessionId).apply {
|
|
1255
|
-
setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
|
|
1256
|
-
}
|
|
1257
|
-
Timber.d("Equalizer initialized with session ID: $audioSessionId")
|
|
1258
|
-
} catch (e: Exception) {
|
|
1259
|
-
Timber.e(e, "Failed to initialize equalizer")
|
|
1260
|
-
equalizerManager = null
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Reinitializes the equalizer when the audio session ID changes. Preserves current settings
|
|
1266
|
-
* (enabled state, preset, or custom levels). Also handles first-time initialization when the
|
|
1267
|
-
* equalizer was skipped at startup due to unset audio session ID.
|
|
1268
|
-
*/
|
|
1269
|
-
internal fun reinitializeEqualizer(newAudioSessionId: Int) {
|
|
1270
|
-
val oldManager = equalizerManager
|
|
1271
|
-
|
|
1272
|
-
// First-time initialization (skipped at startup because session was unset)
|
|
1273
|
-
if (oldManager == null) {
|
|
1274
|
-
Timber.d("First-time equalizer initialization with session ID: $newAudioSessionId")
|
|
1275
|
-
try {
|
|
1276
|
-
equalizerManager =
|
|
1277
|
-
EqualizerManager(newAudioSessionId).apply {
|
|
1278
|
-
setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
|
|
1279
|
-
}
|
|
1280
|
-
Timber.d("Equalizer initialized with session ID: $newAudioSessionId")
|
|
1281
|
-
} catch (e: Exception) {
|
|
1282
|
-
Timber.e(e, "Failed to initialize equalizer")
|
|
1283
|
-
equalizerManager = null
|
|
1284
|
-
}
|
|
1285
|
-
return
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// Same session ID, no need to reinitialize
|
|
1289
|
-
if (oldManager.audioSessionId == newAudioSessionId) {
|
|
1290
|
-
return
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
try {
|
|
1294
|
-
// Capture current settings before releasing old equalizer
|
|
1295
|
-
val currentSettings = oldManager.getSettings()
|
|
1296
|
-
|
|
1297
|
-
// Release old equalizer
|
|
1298
|
-
oldManager.release()
|
|
1299
|
-
|
|
1300
|
-
// Create new equalizer with new session ID
|
|
1301
|
-
equalizerManager =
|
|
1302
|
-
EqualizerManager(newAudioSessionId).apply {
|
|
1303
|
-
setOnSettingsChanged { settings -> callbacks?.onEqualizerChanged(settings) }
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// Restore previous settings if available
|
|
1307
|
-
currentSettings?.let { settings ->
|
|
1308
|
-
if (settings.activePreset != null) {
|
|
1309
|
-
// Restore preset
|
|
1310
|
-
equalizerManager?.setPreset(settings.activePreset)
|
|
1311
|
-
} else if (settings.enabled) {
|
|
1312
|
-
// Restore custom levels
|
|
1313
|
-
equalizerManager?.setLevels(settings.bandLevels)
|
|
1314
|
-
}
|
|
1315
|
-
// Restore enabled state
|
|
1316
|
-
equalizerManager?.setEnabled(settings.enabled)
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
Timber.d("Equalizer reinitialized for new session ID: $newAudioSessionId")
|
|
1320
|
-
} catch (e: Exception) {
|
|
1321
|
-
Timber.e(e, "Failed to reinitialize equalizer")
|
|
1322
|
-
equalizerManager = null
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
/**
|
|
1327
|
-
* Gets the current equalizer settings.
|
|
1328
|
-
*
|
|
1329
|
-
* @return Current equalizer settings or null if not available
|
|
1330
|
-
*/
|
|
1331
|
-
fun getEqualizerSettings(): com.margelo.nitro.audiobrowser.EqualizerSettings? {
|
|
1332
|
-
return equalizerManager?.getSettings()
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
/**
|
|
1336
|
-
* Enables or disables the equalizer.
|
|
1337
|
-
*
|
|
1338
|
-
* @param enabled true to enable, false to disable
|
|
1339
|
-
*/
|
|
1340
|
-
fun setEqualizerEnabled(enabled: Boolean) {
|
|
1341
|
-
equalizerManager?.setEnabled(enabled)
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
/**
|
|
1345
|
-
* Applies a preset to the equalizer.
|
|
1346
|
-
*
|
|
1347
|
-
* @param preset Name of the preset to apply
|
|
1348
|
-
*/
|
|
1349
|
-
fun setEqualizerPreset(preset: String) {
|
|
1350
|
-
equalizerManager?.setPreset(preset)
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
/**
|
|
1354
|
-
* Sets custom band levels for the equalizer.
|
|
1355
|
-
*
|
|
1356
|
-
* @param levels Array of level values in millibels for each band
|
|
1357
|
-
*/
|
|
1358
|
-
fun setEqualizerLevels(levels: DoubleArray) {
|
|
1359
|
-
equalizerManager?.setLevels(levels)
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
// MARK: - Sleep Timer
|
|
1363
|
-
|
|
1364
|
-
/**
|
|
1365
|
-
* Gets the current sleep timer state.
|
|
1366
|
-
*
|
|
1367
|
-
* @return Sleep timer state or null if no timer is active
|
|
1368
|
-
*/
|
|
1369
|
-
fun getSleepTimer(): NitroSleepTimer {
|
|
1370
|
-
return when {
|
|
1371
|
-
sleepTimer.time != null -> {
|
|
1372
|
-
NitroSleepTimer.create(SleepTimerTime(sleepTimer.time!!))
|
|
1373
|
-
}
|
|
1374
|
-
sleepTimer.sleepWhenPlayedToEnd -> {
|
|
1375
|
-
NitroSleepTimer.create(SleepTimerEndOfTrack(true))
|
|
1376
|
-
}
|
|
1377
|
-
else -> {
|
|
1378
|
-
NitroSleepTimer.create(NullType.NULL)
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/**
|
|
1384
|
-
* Sets a sleep timer to stop playback after the specified duration.
|
|
1385
|
-
*
|
|
1386
|
-
* @param seconds Number of seconds until playback stops
|
|
1387
|
-
*/
|
|
1388
|
-
fun setSleepTimer(seconds: Double) {
|
|
1389
|
-
sleepTimer.sleepAfter(seconds)
|
|
1390
|
-
callbacks?.onSleepTimerChanged(getSleepTimer())
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/** Sets a sleep timer to stop playback when the current track finishes playing. */
|
|
1394
|
-
fun setSleepTimerToEndOfTrack() {
|
|
1395
|
-
sleepTimer.sleepWhenPlayedToEnd()
|
|
1396
|
-
callbacks?.onSleepTimerChanged(getSleepTimer())
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Clears the active sleep timer.
|
|
1401
|
-
*
|
|
1402
|
-
* @return true if a timer was cleared, false if no timer was active
|
|
1403
|
-
*/
|
|
1404
|
-
fun clearSleepTimer(): Boolean {
|
|
1405
|
-
val wasRunning = sleepTimer.clear()
|
|
1406
|
-
if (wasRunning) {
|
|
1407
|
-
callbacks?.onSleepTimerChanged(NitroSleepTimer.create(NullType.NULL))
|
|
1408
|
-
}
|
|
1409
|
-
return wasRunning
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
/**
|
|
1413
|
-
* Checks if the sleep timer is set to end on track completion and stops playback if so. Called
|
|
1414
|
-
* when a track naturally finishes playing.
|
|
1415
|
-
*/
|
|
1416
|
-
internal fun checkSleepTimerOnTrackEnd() {
|
|
1417
|
-
if (sleepTimer.sleepWhenPlayedToEnd) {
|
|
1418
|
-
Timber.d("Sleep timer triggered on track end, stopping playback")
|
|
1419
|
-
sleepTimer.clear()
|
|
1420
|
-
stop()
|
|
1421
|
-
callbacks?.onSleepTimerChanged(NitroSleepTimer.create(NullType.NULL))
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
1319
|
/**
|
|
1426
1320
|
* Validates that an index is within bounds [0, trackCount).
|
|
1427
1321
|
*
|
|
@@ -1469,6 +1363,10 @@ class Player(internal val context: Context) {
|
|
|
1469
1363
|
mediaSessionCallback.notifyContentChanged(path)
|
|
1470
1364
|
}
|
|
1471
1365
|
|
|
1366
|
+
fun invalidateAllContent() {
|
|
1367
|
+
mediaSessionCallback.invalidateAllContent()
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1472
1370
|
/**
|
|
1473
1371
|
* Returns the recommended artwork size in pixels from the connected media browser (e.g., Android
|
|
1474
1372
|
* Auto), or null if not provided.
|