react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -21
- package/android/build.gradle +35 -12
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
- package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
- package/android/src/main/res/values/strings.xml +25 -5
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
- package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +230 -205
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/SearchDrillIn.swift +7 -7
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
- package/ios/Extension/Section+Copying.swift +29 -0
- package/ios/Extension/String+IcyCharset.swift +21 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
- package/ios/Extension/Track+Copying.swift +8 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +253 -100
- package/ios/Model/NitroTypeStubs.swift +125 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +17 -9
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +46 -13
- package/ios/Player/RetryManager.swift +80 -18
- package/ios/Player/SeekCompletionHandler.swift +3 -1
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +122 -10
- package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryManagerTests.swift +89 -0
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +8 -1
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackPlayer.swift +63 -35
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/LRUCache.swift +4 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/AudioBrowser.js +8 -8
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +12 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +11 -3
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +14 -11
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +3 -2
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +3 -4
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +18 -9
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +23 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +15 -4
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +13 -6
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +175 -100
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +18 -23
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +2 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +12 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +11 -3
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +14 -11
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +3 -2
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/features/playback/state.js +3 -4
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +17 -8
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +19 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +15 -4
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +13 -6
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +28 -14
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +200 -75
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +254 -39
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +175 -100
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +18 -23
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +10 -2
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +14 -11
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +3 -4
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +108 -56
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +7 -4
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +3 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +82 -57
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
- package/nitrogen/generated/android/c++/JSection.hpp +116 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
- package/nitrogen/generated/ios/swift/Section.swift +82 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +28 -63
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
- package/nitrogen/generated/shared/c++/Section.hpp +102 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +18 -30
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +24 -11
- package/src/AudioBrowser.ts +2 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +12 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +11 -3
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +14 -11
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +302 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +3 -2
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +3 -4
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +1 -6
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +126 -76
- package/src/features/player/setup.test.ts +24 -7
- package/src/features/player/setup.ts +22 -10
- package/src/features/player/validateOptions.ts +21 -0
- package/src/features/queue/activeTrack.ts +15 -4
- package/src/features/queue/queue.ts +13 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +147 -0
- package/src/specs/audio-browser.nitro.ts +5 -4
- package/src/style-resolution.test.ts +210 -0
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +7 -3
- package/src/types/browser-nodes.ts +396 -134
- package/src/types/browser.ts +85 -59
- package/src/utils/LazyNativeEmitter.ts +28 -15
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +239 -82
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +284 -37
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +209 -111
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/SearchManager.test.ts +75 -0
- package/src/web/browser/SearchManager.ts +25 -28
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
- package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +247 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
|
@@ -42,21 +42,32 @@ import type {
|
|
|
42
42
|
AudioBrowser as AudioBrowserSpec,
|
|
43
43
|
Output
|
|
44
44
|
} from '../specs/audio-browser.nitro'
|
|
45
|
-
import type {
|
|
45
|
+
import type {
|
|
46
|
+
ResolvedTrack,
|
|
47
|
+
Track,
|
|
48
|
+
TrackLoadEvent,
|
|
49
|
+
TransformableRequestConfig
|
|
50
|
+
} from '../types'
|
|
46
51
|
import type { NativeBrowserConfiguration } from '../types/browser-native'
|
|
52
|
+
import { getTrackIdentity } from '../utils/getTrackIdentity'
|
|
47
53
|
import { BrowserManager } from './browser/BrowserManager'
|
|
48
54
|
import { classifyTrackNavigation } from './browser/classifyTrackNavigation'
|
|
49
55
|
import { FavoriteManager } from './browser/FavoriteManager'
|
|
50
56
|
import { NavigationErrorManager } from './browser/NavigationErrorManager'
|
|
51
57
|
import { SearchManager } from './browser/SearchManager'
|
|
58
|
+
import { followMediaRedirect } from './http/followMediaRedirect'
|
|
52
59
|
import { HttpClient } from './http/HttpClient'
|
|
53
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
RequestConfigBuilder,
|
|
62
|
+
type ResolvedMediaRequest
|
|
63
|
+
} from './http/RequestConfigBuilder'
|
|
54
64
|
import { NowPlayingManager } from './player/NowPlayingManager'
|
|
55
65
|
import { OptionsManager } from './player/OptionsManager'
|
|
56
66
|
import { RemoteCommandController } from './player/RemoteCommandController'
|
|
57
67
|
import { QueuePlayer, SleepTimerManager, VolumeFader } from './TrackPlayer'
|
|
58
68
|
import { PlaybackTimer } from './TrackPlayer/PlaybackTimer'
|
|
59
69
|
import { derivePlayingState } from './TrackPlayer/PlayingStateFactory'
|
|
70
|
+
import { BrowserPathHelper } from './util/BrowserPathHelper'
|
|
60
71
|
|
|
61
72
|
/**
|
|
62
73
|
* Web implementation of AudioBrowser (unified browser + player)
|
|
@@ -94,7 +105,6 @@ export class NativeAudioBrowser
|
|
|
94
105
|
private remoteCommands: RemoteCommandController
|
|
95
106
|
|
|
96
107
|
// Player state
|
|
97
|
-
private currentLoadId = 0
|
|
98
108
|
private progressTimer = new PlaybackTimer()
|
|
99
109
|
private intervalTimer = new PlaybackTimer()
|
|
100
110
|
private _online: boolean =
|
|
@@ -304,6 +314,11 @@ export class NativeAudioBrowser
|
|
|
304
314
|
|
|
305
315
|
if (newState.state === 'error' && newState.error) {
|
|
306
316
|
this.onPlaybackError({ error: newState.error })
|
|
317
|
+
} else if (oldState.state === 'error') {
|
|
318
|
+
// Leaving the error state clears it, as it does natively. The getter
|
|
319
|
+
// already reads undefined — consumers subscribe to the event, so without
|
|
320
|
+
// this a stale error outlives the next successful load.
|
|
321
|
+
this.onPlaybackError({ error: undefined })
|
|
307
322
|
}
|
|
308
323
|
}
|
|
309
324
|
|
|
@@ -414,11 +429,13 @@ export class NativeAudioBrowser
|
|
|
414
429
|
* Attempts to skip to a track already in the current queue.
|
|
415
430
|
* Used as an optimization to avoid re-expanding the queue.
|
|
416
431
|
*
|
|
417
|
-
* @param
|
|
432
|
+
* @param track The tapped track (its contextual path pins the exact copy)
|
|
433
|
+
* @param trackId The track's identity (id, falling back to src)
|
|
418
434
|
* @param parentPath The parent path to check against queueSourcePath
|
|
419
435
|
* @returns true if successfully skipped to existing track, false otherwise
|
|
420
436
|
*/
|
|
421
437
|
private async trySkipToExistingQueueTrack(
|
|
438
|
+
track: Track,
|
|
422
439
|
trackId: string,
|
|
423
440
|
parentPath: string
|
|
424
441
|
): Promise<boolean> {
|
|
@@ -426,8 +443,21 @@ export class NativeAudioBrowser
|
|
|
426
443
|
return false
|
|
427
444
|
}
|
|
428
445
|
|
|
446
|
+
// Exact-surface match first: a contextual path carries the tapped page
|
|
447
|
+
// position (__index), so path equality pins the exact copy when the page
|
|
448
|
+
// holds the same identity more than once. The identity match remains for
|
|
449
|
+
// index-less paths (e.g. pre-stamp persisted state); an index-stamped path
|
|
450
|
+
// with no exact match falls through to expansion, which re-scopes the
|
|
451
|
+
// queue to the tapped section.
|
|
429
452
|
const queue = this.getQueue()
|
|
430
|
-
|
|
453
|
+
let index = queue.findIndex((t) => t.path != null && t.path === track.path)
|
|
454
|
+
if (
|
|
455
|
+
index < 0 &&
|
|
456
|
+
track.path != null &&
|
|
457
|
+
BrowserPathHelper.extractIndex(track.path) === undefined
|
|
458
|
+
) {
|
|
459
|
+
index = queue.findIndex((t) => getTrackIdentity(t) === trackId)
|
|
460
|
+
}
|
|
431
461
|
|
|
432
462
|
if (index < 0) {
|
|
433
463
|
return false
|
|
@@ -474,6 +504,11 @@ export class NativeAudioBrowser
|
|
|
474
504
|
*/
|
|
475
505
|
private async navigateTrackAsync(track: Track): Promise<void> {
|
|
476
506
|
try {
|
|
507
|
+
// A disabled track is unavailable — it never plays, whichever surface
|
|
508
|
+
// or stale resume path delivered the selection (Track.disabled; mirrors
|
|
509
|
+
// iOS TrackSelector.select and Android navigateTrack).
|
|
510
|
+
if (track.disabled === true) return
|
|
511
|
+
|
|
477
512
|
const nav = classifyTrackNavigation(track)
|
|
478
513
|
switch (nav.kind) {
|
|
479
514
|
case 'contextual':
|
|
@@ -481,6 +516,7 @@ export class NativeAudioBrowser
|
|
|
481
516
|
if (
|
|
482
517
|
nav.trackId &&
|
|
483
518
|
(await this.trySkipToExistingQueueTrack(
|
|
519
|
+
track,
|
|
484
520
|
nav.trackId,
|
|
485
521
|
nav.parentPath
|
|
486
522
|
))
|
|
@@ -504,7 +540,7 @@ export class NativeAudioBrowser
|
|
|
504
540
|
return
|
|
505
541
|
|
|
506
542
|
case 'invalid':
|
|
507
|
-
throw new Error("Track must have either '
|
|
543
|
+
throw new Error("Track must have either 'path' or 'src' property")
|
|
508
544
|
}
|
|
509
545
|
} catch (error) {
|
|
510
546
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
@@ -520,7 +556,7 @@ export class NativeAudioBrowser
|
|
|
520
556
|
})
|
|
521
557
|
}
|
|
522
558
|
|
|
523
|
-
async
|
|
559
|
+
async search(query: string): Promise<Track[]> {
|
|
524
560
|
// Wrap query string in SearchParams (matches Android's search(query: String) overload)
|
|
525
561
|
return this.searchManager.search({ query, reference: 'unknown' })
|
|
526
562
|
}
|
|
@@ -587,71 +623,106 @@ export class NativeAudioBrowser
|
|
|
587
623
|
* Supports the transform callback for URL manipulation.
|
|
588
624
|
* Mirrors Android's MediaFactory.getMediaRequestConfig behavior.
|
|
589
625
|
*/
|
|
590
|
-
private async
|
|
591
|
-
|
|
592
|
-
|
|
626
|
+
private async resolveMediaRequest(
|
|
627
|
+
src: string,
|
|
628
|
+
track: Track
|
|
629
|
+
): Promise<ResolvedMediaRequest> {
|
|
630
|
+
// The *resolved* request layer, not `configuration.request`: a
|
|
631
|
+
// resolver-only config would otherwise reach media with no baseUrl and no
|
|
632
|
+
// headers. Browse, search and browse artwork all read it this way too.
|
|
633
|
+
//
|
|
634
|
+
// Caught, because unlike those three this one cannot fail the load: a
|
|
635
|
+
// resolver hiccup — a token refresh timing out — would otherwise take down
|
|
636
|
+
// a track whose `src` is already absolute and public and needed no layer at
|
|
637
|
+
// all. Native degrades the same way, `getMediaRequestConfig` returning null
|
|
638
|
+
// and the original URL playing on.
|
|
639
|
+
let request: TransformableRequestConfig | undefined
|
|
640
|
+
try {
|
|
641
|
+
request = await this.browserManager.resolvedRequestConfig()
|
|
642
|
+
} catch (e) {
|
|
643
|
+
console.error('Failed to resolve the request layer for media', e)
|
|
644
|
+
}
|
|
645
|
+
const { media } = this.browserManager.configuration
|
|
646
|
+
return RequestConfigBuilder.resolveMediaRequest(src, request, media, track)
|
|
593
647
|
}
|
|
594
648
|
|
|
595
649
|
/**
|
|
596
|
-
*
|
|
597
|
-
* `current`
|
|
598
|
-
*
|
|
599
|
-
* the queue entry with it.
|
|
650
|
+
* The queue's own entry, so the queue stays the source of truth for what is
|
|
651
|
+
* playing. `current` preserves the track as queued — the resolved URL is
|
|
652
|
+
* passed to `load()` separately — so either is safe to re-feed.
|
|
600
653
|
*/
|
|
601
|
-
protected override
|
|
654
|
+
protected override trackToReload(): Track | undefined {
|
|
602
655
|
const index = this.queue.currentIndex
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
} else {
|
|
608
|
-
super.reloadCurrent()
|
|
609
|
-
}
|
|
656
|
+
const queued = index !== undefined ? this.queue.getTrack(index) : undefined
|
|
657
|
+
// The queue entry wins, so it stays the source of truth for what is
|
|
658
|
+
// playing; `current` is the fallback for a player loaded without one.
|
|
659
|
+
return queued ?? super.trackToReload()
|
|
610
660
|
}
|
|
611
661
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
662
|
+
/**
|
|
663
|
+
* Makes `track` the queue's current entry, emitting the change.
|
|
664
|
+
*
|
|
665
|
+
* Matches Android, where `load()` modifies the queue: an empty queue takes
|
|
666
|
+
* the track as its only entry, a populated one has its current entry
|
|
667
|
+
* replaced.
|
|
668
|
+
*/
|
|
669
|
+
private syncQueueTo(track: Track): void {
|
|
620
670
|
if (this.queue.length === 0) {
|
|
621
671
|
this.queue.setTracks([track])
|
|
622
672
|
this.queue.currentIndex = 0
|
|
623
673
|
this.emitQueueChanged()
|
|
624
|
-
|
|
674
|
+
return
|
|
675
|
+
}
|
|
676
|
+
if (
|
|
625
677
|
this.queue.currentIndex !== undefined &&
|
|
626
678
|
this.queue.getTrack(this.queue.currentIndex) !== track
|
|
627
679
|
) {
|
|
628
680
|
this.queue.replaceTrack(this.queue.currentIndex, track)
|
|
629
681
|
this.emitQueueChanged()
|
|
630
682
|
}
|
|
683
|
+
}
|
|
631
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Everything native does in its media-item transition, all of it before any
|
|
687
|
+
* network work: announce the new track, publish its now-playing metadata,
|
|
688
|
+
* cut the outgoing one, and report `loading`.
|
|
689
|
+
*
|
|
690
|
+
* Android's `onMediaItemTransition` fires at `prepare()` and does exactly
|
|
691
|
+
* this, with its LOADING following from `onEvents`; resolution happens later
|
|
692
|
+
* still, inside `TransformingDataSource.open()`. Doing it here rather than
|
|
693
|
+
* after the URL resolves is what gives the resolution window a state, and
|
|
694
|
+
* keeps `activeTrack` ahead of `loading` — the order a consumer that reads
|
|
695
|
+
* `getActiveTrack()` on the loading edge depends on.
|
|
696
|
+
*/
|
|
697
|
+
private transitionTo(track: Track): void {
|
|
698
|
+
const element = this.requireElement()
|
|
632
699
|
const lastTrack = this.current
|
|
633
700
|
const lastPosition = element.currentTime
|
|
634
701
|
const lastIndex = this.queue.lastIndex
|
|
635
702
|
const currentIndex = this.queue.currentIndex
|
|
636
703
|
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
704
|
+
// A re-prepare of the item already current is not a transition, and must
|
|
705
|
+
// not be announced as one: ExoPlayer's `prepare()` does not re-fire
|
|
706
|
+
// `onMediaItemTransition` for an unchanged media item, so a consumer that
|
|
707
|
+
// resets on a track change would have its UI wiped by every retry. The
|
|
708
|
+
// `lastTrack === track` payload was the tell.
|
|
709
|
+
const isTransition = this.current !== track
|
|
710
|
+
|
|
711
|
+
// `current` first, so `getActiveTrack()` already reads the new track inside
|
|
712
|
+
// a handler and so `getNowPlaying()` publishes the new track's metadata. It
|
|
713
|
+
// also stops `getActiveTrackIndex()` — which moved with the queue already,
|
|
714
|
+
// synchronously — from disagreeing with it for the length of the
|
|
715
|
+
// resolution.
|
|
716
|
+
this.current = track
|
|
717
|
+
|
|
718
|
+
// Separately guarded: a throwing consumer handler must not skip the
|
|
719
|
+
// publish, and neither may fail the load.
|
|
720
|
+
if (isTransition) {
|
|
721
|
+
// As Android's onMediaItemTransition does: a new item is a clean slate,
|
|
722
|
+
// a retry of the same one keeps what the consumer set.
|
|
723
|
+
this.nowPlayingManager.clearNowPlayingOverride()
|
|
724
|
+
|
|
725
|
+
try {
|
|
655
726
|
this.onPlaybackActiveTrackChanged({
|
|
656
727
|
lastTrack,
|
|
657
728
|
lastPosition,
|
|
@@ -659,24 +730,118 @@ export class NativeAudioBrowser
|
|
|
659
730
|
index: currentIndex,
|
|
660
731
|
track
|
|
661
732
|
})
|
|
733
|
+
} catch (error) {
|
|
734
|
+
console.error('Failed to announce the active track change:', error)
|
|
735
|
+
}
|
|
662
736
|
|
|
663
|
-
|
|
737
|
+
try {
|
|
738
|
+
// Once, here: duration reaches the media session through
|
|
739
|
+
// `updateProgress()` on each tick, so only metadata needs publishing.
|
|
740
|
+
// Android publishes from the same transition handler that announces.
|
|
664
741
|
const nowPlaying = this.getNowPlaying()
|
|
665
742
|
if (nowPlaying) {
|
|
666
743
|
this.publishNowPlaying(nowPlaying)
|
|
667
744
|
}
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.error('Failed to publish now-playing metadata:', error)
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Stop the outgoing track now rather than when Shaka eventually gets a URL.
|
|
751
|
+
// Announcing the new track while the old one is still audible is worse than
|
|
752
|
+
// the silence, and native has no such window — prepare() cuts the old item
|
|
753
|
+
// at the moment it announces. This adds no teardown: `player.load()`
|
|
754
|
+
// already unloads first (shaka player.js:1757-1762). It only moves when.
|
|
755
|
+
// Verified in a real browser to emit no `pause`, so no state comes of it.
|
|
756
|
+
this.requirePlayer()
|
|
757
|
+
.unload()
|
|
758
|
+
.catch((error: unknown) =>
|
|
759
|
+
console.error('Failed to unload the previous track:', error)
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
// Loading covers the resolution too. Shaka dispatches its own `loading`
|
|
763
|
+
// once `player.load()` is called — after resolution — and the state setter
|
|
764
|
+
// drops that as a no-op. The `buffering` that follows is Shaka's to report.
|
|
765
|
+
this.dispatch({ type: 'trackLoading' })
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
load(track: Track, callback?: (track: Track) => void): void {
|
|
769
|
+
// Before the queue is touched, so a load before `setupPlayer()` throws
|
|
770
|
+
// rather than half-applying.
|
|
771
|
+
this.requireElement()
|
|
772
|
+
|
|
773
|
+
this.syncQueueTo(track)
|
|
774
|
+
|
|
775
|
+
// Begun here, at the call, so a newer load supersedes this one immediately
|
|
776
|
+
// rather than once its own URL has resolved. Both continuations below carry
|
|
777
|
+
// it, as does `super.load()`.
|
|
778
|
+
const attempt = this.beginLoadAttempt()
|
|
779
|
+
// Read now: `reloadCurrent` holds the flag only for this synchronous call,
|
|
780
|
+
// and `super.load()` runs after the URL has resolved.
|
|
781
|
+
const reprepare = this._repreparing
|
|
782
|
+
|
|
783
|
+
// Loading ends being stopped. `dispatch()` drops every event while
|
|
784
|
+
// `_isStopped`, to keep Shaka's teardown noise off the stopped state, so
|
|
785
|
+
// the `trackLoading` in `transitionTo` would be swallowed on the stop → play
|
|
786
|
+
// path and the resolution window would report nothing. `super.load()` clears
|
|
787
|
+
// this too, but only after the URL resolves.
|
|
788
|
+
this._isStopped = false
|
|
668
789
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
790
|
+
// Set loading flag early so seekTo() calls during async URL resolution
|
|
791
|
+
// are captured as pending seeks rather than silently dropped
|
|
792
|
+
this._loadInProgress = true
|
|
793
|
+
this._pendingSeek = undefined
|
|
794
|
+
|
|
795
|
+
this.transitionTo(track)
|
|
796
|
+
|
|
797
|
+
// Resolve the media URL before loading (async but we don't await)
|
|
798
|
+
const doLoad = async () => {
|
|
799
|
+
const resolvedRequest = track.src
|
|
800
|
+
? await this.resolveMediaRequest(track.src, track)
|
|
801
|
+
: undefined
|
|
802
|
+
// Web-only, and forced: a progressive file plays via `mediaElement.src`,
|
|
803
|
+
// which cannot carry headers. Native sends them with the media request
|
|
804
|
+
// and follows the redirect inline, so it needs none of this.
|
|
805
|
+
const resolvedMedia = resolvedRequest
|
|
806
|
+
? await followMediaRedirect(
|
|
807
|
+
resolvedRequest.path ?? track.src!,
|
|
808
|
+
resolvedRequest.headers,
|
|
809
|
+
{ mediaLayerHeaders: resolvedRequest.mediaLayerHeaders }
|
|
810
|
+
)
|
|
811
|
+
: undefined
|
|
812
|
+
|
|
813
|
+
// Something took over while the URL was resolving — discard this result
|
|
814
|
+
if (!attempt.isCurrent) return
|
|
815
|
+
|
|
816
|
+
super.load(
|
|
817
|
+
track,
|
|
818
|
+
(loadedTrack) => {
|
|
819
|
+
// Call the provided callback if any
|
|
820
|
+
if (callback) {
|
|
821
|
+
callback(loadedTrack)
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
headers: resolvedMedia?.headers,
|
|
826
|
+
src: resolvedMedia?.src,
|
|
827
|
+
attempt,
|
|
828
|
+
reprepare
|
|
672
829
|
}
|
|
673
|
-
|
|
830
|
+
)
|
|
674
831
|
}
|
|
675
832
|
|
|
676
833
|
// Execute async load without blocking, with error handling
|
|
677
834
|
doLoad().catch((error: unknown) => {
|
|
678
|
-
//
|
|
679
|
-
|
|
835
|
+
// A backstop with no reachable trigger today, kept because an unhandled
|
|
836
|
+
// rejection would be worse than a dead branch. Every await inside
|
|
837
|
+
// `doLoad` catches internally; `shaka.Player.load` is `async`, so it
|
|
838
|
+
// rejects rather than throwing and its rejection is handled against the
|
|
839
|
+
// attempt inside `Player.load`; and `this.player` is only ever assigned,
|
|
840
|
+
// so `requirePlayer()` cannot start succeeding and then fail mid-load.
|
|
841
|
+
//
|
|
842
|
+
// No attempt check either, for the same reason it would be dead: nothing
|
|
843
|
+
// awaits between the guard above and `super.load()`, so the attempt
|
|
844
|
+
// cannot have changed. Add an await there and this needs the check back.
|
|
680
845
|
this._loadInProgress = false
|
|
681
846
|
this._pendingSeek = undefined
|
|
682
847
|
console.error('Error loading track:', error)
|
|
@@ -856,10 +1021,8 @@ export class NativeAudioBrowser
|
|
|
856
1021
|
}
|
|
857
1022
|
|
|
858
1023
|
override stop(): void {
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
// and revives the state out of Stopped.
|
|
862
|
-
this.currentLoadId++
|
|
1024
|
+
// `Player.stop()` cancels the load attempt, so an in-flight load's
|
|
1025
|
+
// continuation cannot re-arm the player and revive it out of Stopped.
|
|
863
1026
|
super.stop()
|
|
864
1027
|
this.clearSleepTimerIfFading()
|
|
865
1028
|
}
|
|
@@ -880,18 +1043,20 @@ export class NativeAudioBrowser
|
|
|
880
1043
|
/**
|
|
881
1044
|
* Override to check for sleep timer when track ends
|
|
882
1045
|
*/
|
|
883
|
-
protected onTrackEnded():
|
|
1046
|
+
protected onTrackEnded(): boolean {
|
|
884
1047
|
// Check if sleep timer is set to end on track completion
|
|
885
1048
|
if (this.sleepTimer.sleepWhenPlayedToEnd) {
|
|
886
1049
|
console.log('Sleep timer triggered on track end, pausing playback')
|
|
887
1050
|
this.sleepTimer.clear()
|
|
888
1051
|
this.pause()
|
|
889
1052
|
this.onSleepTimerChanged(null)
|
|
890
|
-
|
|
1053
|
+
// Nothing is loading, so the caller must still report the end — the
|
|
1054
|
+
// element has stopped and its own `pause` is swallowed as end-of-track.
|
|
1055
|
+
return false
|
|
891
1056
|
}
|
|
892
1057
|
|
|
893
1058
|
// Otherwise proceed with normal track end behavior
|
|
894
|
-
super.onTrackEnded()
|
|
1059
|
+
return super.onTrackEnded()
|
|
895
1060
|
}
|
|
896
1061
|
|
|
897
1062
|
// MARK: Queue management
|
|
@@ -941,13 +1106,14 @@ export class NativeAudioBrowser
|
|
|
941
1106
|
setActiveTrackFavorited(favorited: boolean): void {
|
|
942
1107
|
const track = this.getActiveTrack()
|
|
943
1108
|
const index = this.getActiveTrackIndex()
|
|
944
|
-
if (!track ||
|
|
1109
|
+
if (!track || getTrackIdentity(track) === undefined || index === undefined)
|
|
1110
|
+
return
|
|
945
1111
|
|
|
946
|
-
// Update favorites set via manager
|
|
1112
|
+
// Update favorites set via manager (keyed by track identity)
|
|
947
1113
|
if (favorited) {
|
|
948
|
-
this.favoriteManager.addFavorite(track
|
|
1114
|
+
this.favoriteManager.addFavorite(track)
|
|
949
1115
|
} else {
|
|
950
|
-
this.favoriteManager.removeFavorite(track
|
|
1116
|
+
this.favoriteManager.removeFavorite(track)
|
|
951
1117
|
}
|
|
952
1118
|
|
|
953
1119
|
// Create updated track with new favorited state
|
|
@@ -959,27 +1125,18 @@ export class NativeAudioBrowser
|
|
|
959
1125
|
// Replace the track in the queue
|
|
960
1126
|
this.queue.replaceTrack(index, updatedTrack)
|
|
961
1127
|
|
|
962
|
-
// Emit favorite changed
|
|
1128
|
+
// Emit favorite changed only. A favorite toggle is an in-place mutation of
|
|
1129
|
+
// the active track, not a transition — onPlaybackActiveTrackChanged and
|
|
1130
|
+
// onPlaybackQueueChanged stay transition-only, and the useActiveTrack /
|
|
1131
|
+
// useQueue hooks subscribe to onFavoriteChanged themselves.
|
|
963
1132
|
this.onFavoriteChanged({ track: updatedTrack, favorited })
|
|
964
|
-
|
|
965
|
-
// Emit active track changed so useActiveTrack() hook updates
|
|
966
|
-
this.onPlaybackActiveTrackChanged({
|
|
967
|
-
lastIndex: index,
|
|
968
|
-
lastTrack: track,
|
|
969
|
-
lastPosition: this.element?.currentTime ?? 0,
|
|
970
|
-
index,
|
|
971
|
-
track: updatedTrack
|
|
972
|
-
})
|
|
973
|
-
|
|
974
|
-
// Emit queue changed so useQueue() hook updates
|
|
975
|
-
this.emitQueueChanged()
|
|
976
1133
|
}
|
|
977
1134
|
|
|
978
1135
|
toggleActiveTrackFavorited(): void {
|
|
979
1136
|
const track = this.getActiveTrack()
|
|
980
|
-
if (!track ||
|
|
1137
|
+
if (!track || getTrackIdentity(track) === undefined) return
|
|
981
1138
|
|
|
982
|
-
const isFavorited = this.favoriteManager.isFavorited(track
|
|
1139
|
+
const isFavorited = this.favoriteManager.isFavorited(track)
|
|
983
1140
|
this.setActiveTrackFavorited(!isFavorited)
|
|
984
1141
|
}
|
|
985
1142
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One attempt at loading a track, from the call that asked for it through to
|
|
3
|
+
* the continuations that finish it.
|
|
4
|
+
*
|
|
5
|
+
* A load on web spans two async boundaries — resolving the URL, then Shaka's
|
|
6
|
+
* own `load()` — and each has a continuation that must do nothing if something
|
|
7
|
+
* else has taken over in the meantime. Handing every continuation the same
|
|
8
|
+
* token turns that into one question, asked the same way in each place.
|
|
9
|
+
*
|
|
10
|
+
* Supersession is registered where it happens: {@link Player.beginLoadAttempt}
|
|
11
|
+
* cancels the previous attempt the moment a new one starts, and `stop()`
|
|
12
|
+
* cancels without starting one. Two integer generations used to stand in for
|
|
13
|
+
* this, incremented at different depths — the outer one at the call, the inner
|
|
14
|
+
* one only once the URL had resolved — so a load superseded during that gap
|
|
15
|
+
* still ran its own success path.
|
|
16
|
+
*/
|
|
17
|
+
export class LoadAttempt {
|
|
18
|
+
private cancelled = false
|
|
19
|
+
|
|
20
|
+
/** Abandons this attempt; every continuation holding it will bail. */
|
|
21
|
+
cancel(): void {
|
|
22
|
+
this.cancelled = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Whether this attempt still owns the player. */
|
|
26
|
+
get isCurrent(): boolean {
|
|
27
|
+
return !this.cancelled
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -47,17 +47,49 @@ describe('nextPlaybackState', () => {
|
|
|
47
47
|
})
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
describe('loadSeekCompleted —
|
|
50
|
+
describe('loadSeekCompleted — the settle after a load', () => {
|
|
51
51
|
it('from loading → ready', () => {
|
|
52
|
-
expect(
|
|
53
|
-
'
|
|
54
|
-
|
|
52
|
+
expect(
|
|
53
|
+
nextPlaybackState('loading', {
|
|
54
|
+
type: 'loadSeekCompleted',
|
|
55
|
+
playWhenReady: false
|
|
56
|
+
})
|
|
57
|
+
).toBe('ready')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('from buffering → ready', () => {
|
|
61
|
+
// every load now passes through buffering, so that is where the settle
|
|
62
|
+
// usually arrives
|
|
63
|
+
expect(
|
|
64
|
+
nextPlaybackState('buffering', {
|
|
65
|
+
type: 'loadSeekCompleted',
|
|
66
|
+
playWhenReady: false
|
|
67
|
+
})
|
|
68
|
+
).toBe('ready')
|
|
55
69
|
})
|
|
56
70
|
|
|
57
71
|
it('from any other state → suppressed', () => {
|
|
58
|
-
for (const state of ALL_STATES.filter(
|
|
72
|
+
for (const state of ALL_STATES.filter(
|
|
73
|
+
(s) => s !== 'loading' && s !== 'buffering'
|
|
74
|
+
)) {
|
|
59
75
|
expect(
|
|
60
|
-
nextPlaybackState(state, {
|
|
76
|
+
nextPlaybackState(state, {
|
|
77
|
+
type: 'loadSeekCompleted',
|
|
78
|
+
playWhenReady: false
|
|
79
|
+
})
|
|
80
|
+
).toBeNull()
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('suppressed while playWhenReady — ready is a transient before playing', () => {
|
|
85
|
+
// Android suppresses STATE_READY the same way; emitting it flashes a
|
|
86
|
+
// settled, non-loading state mid-startup
|
|
87
|
+
for (const state of ['loading', 'buffering'] as const) {
|
|
88
|
+
expect(
|
|
89
|
+
nextPlaybackState(state, {
|
|
90
|
+
type: 'loadSeekCompleted',
|
|
91
|
+
playWhenReady: true
|
|
92
|
+
})
|
|
61
93
|
).toBeNull()
|
|
62
94
|
}
|
|
63
95
|
})
|
|
@@ -103,7 +135,10 @@ describe('nextPlaybackState', () => {
|
|
|
103
135
|
|
|
104
136
|
it('from playing → suppressed', () => {
|
|
105
137
|
expect(
|
|
106
|
-
nextPlaybackState('playing', {
|
|
138
|
+
nextPlaybackState('playing', {
|
|
139
|
+
type: 'bufferingSufficient',
|
|
140
|
+
playWhenReady: false
|
|
141
|
+
})
|
|
107
142
|
).toBeNull()
|
|
108
143
|
})
|
|
109
144
|
|
|
@@ -112,27 +147,39 @@ describe('nextPlaybackState', () => {
|
|
|
112
147
|
// the UI is rendering with nothing recovered. Same guard as iOS/Android.
|
|
113
148
|
it('from error → suppressed (preserves the error)', () => {
|
|
114
149
|
expect(
|
|
115
|
-
nextPlaybackState('error', {
|
|
150
|
+
nextPlaybackState('error', {
|
|
151
|
+
type: 'bufferingSufficient',
|
|
152
|
+
playWhenReady: false
|
|
153
|
+
})
|
|
116
154
|
).toBeNull()
|
|
117
155
|
})
|
|
118
156
|
|
|
119
157
|
it('from ended → suppressed', () => {
|
|
120
158
|
expect(
|
|
121
|
-
nextPlaybackState('ended', {
|
|
159
|
+
nextPlaybackState('ended', {
|
|
160
|
+
type: 'bufferingSufficient',
|
|
161
|
+
playWhenReady: false
|
|
162
|
+
})
|
|
122
163
|
).toBeNull()
|
|
123
164
|
})
|
|
124
165
|
|
|
125
166
|
it('from stopped → suppressed', () => {
|
|
126
167
|
expect(
|
|
127
|
-
nextPlaybackState('stopped', {
|
|
168
|
+
nextPlaybackState('stopped', {
|
|
169
|
+
type: 'bufferingSufficient',
|
|
170
|
+
playWhenReady: false
|
|
171
|
+
})
|
|
128
172
|
).toBeNull()
|
|
129
173
|
})
|
|
130
174
|
|
|
131
175
|
it('from any other state → ready', () => {
|
|
132
176
|
for (const state of ALL_STATES.filter((s) => !suppressed.includes(s))) {
|
|
133
|
-
expect(
|
|
134
|
-
|
|
135
|
-
|
|
177
|
+
expect(
|
|
178
|
+
nextPlaybackState(state, {
|
|
179
|
+
type: 'bufferingSufficient',
|
|
180
|
+
playWhenReady: false
|
|
181
|
+
})
|
|
182
|
+
).toBe('ready')
|
|
136
183
|
}
|
|
137
184
|
})
|
|
138
185
|
})
|