react-native-audio-browser 0.2.0-next.2 → 0.2.0-next.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -21
- package/android/build.gradle +35 -12
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +389 -164
- package/android/src/main/java/com/audiobrowser/AudioBrowserLifecycleModule.kt +38 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +18 -2
- package/android/src/main/java/com/audiobrowser/ServiceBinding.kt +48 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +315 -213
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +24 -5
- package/android/src/main/java/com/audiobrowser/browser/InertStyleDiagnostic.kt +267 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +97 -58
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +70 -29
- package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +60 -0
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +20 -7
- package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
- package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +20 -3
- package/android/src/main/java/com/audiobrowser/model/IcyTitle.kt +24 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +12 -12
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +111 -58
- package/android/src/main/java/com/audiobrowser/player/CrossOriginHeaderStripper.kt +77 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +28 -10
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +509 -230
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +218 -252
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -25
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +113 -78
- package/android/src/main/java/com/audiobrowser/player/Player.kt +81 -38
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +1 -1
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +2 -1
- package/android/src/main/java/com/audiobrowser/player/ShuffleOrderLedBy.kt +30 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +67 -11
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingExtendedTrustManager.kt +168 -0
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +58 -14
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +35 -7
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +278 -59
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +7 -9
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +56 -40
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
- package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +108 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +56 -52
- package/android/src/main/res/values/strings.xml +25 -5
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +87 -0
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +119 -33
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +15 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +346 -8
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
- package/android/src/test/java/com/audiobrowser/browser/InertStyleDiagnosticTest.kt +277 -0
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +3 -2
- package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +147 -0
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -55
- package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +154 -0
- package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
- package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
- package/android/src/test/java/com/audiobrowser/model/IcyTitleTest.kt +49 -0
- package/android/src/test/java/com/audiobrowser/player/BrowseTilesTest.kt +385 -0
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +101 -58
- package/android/src/test/java/com/audiobrowser/player/CrossOriginHeaderStripperTest.kt +94 -0
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +45 -0
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +3 -4
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +3 -3
- package/android/src/test/java/com/audiobrowser/player/ShuffleOrderLedByTest.kt +79 -0
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +197 -7
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +325 -4
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherCallSiteTest.kt +322 -0
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +83 -0
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
- package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +162 -0
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
- package/ios/Browser/BrowserConfig.swift +102 -5
- package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
- package/ios/Browser/BrowserManager.swift +230 -205
- package/ios/Browser/BrowserPathHelper.swift +44 -44
- package/ios/Browser/InertStyleDiagnostic.swift +279 -0
- package/ios/Browser/JsonModels.swift +264 -106
- package/ios/Browser/SearchDrillIn.swift +7 -7
- package/ios/Browser/SectionPresentation.swift +64 -0
- package/ios/Browser/SectionScope.swift +58 -24
- package/ios/Browser/StyleResolver.swift +58 -0
- package/ios/Browser/TabBarEntries.swift +9 -3
- package/ios/Browser/TrackIdTemplate.swift +37 -0
- package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
- package/ios/CarPlay/CarPlayController.swift +227 -85
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -20
- package/ios/CarPlay/CarPlayListItemFactory.swift +289 -84
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
- package/ios/CarPlay/RNABMediaAddHandler.swift +6 -6
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +2 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +16 -16
- package/ios/Extension/Capability+RemoteCommand.swift +1 -1
- package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
- package/ios/Extension/ResolvedTrack+Copying.swift +13 -15
- package/ios/Extension/Section+Copying.swift +29 -0
- package/ios/Extension/String+IcyCharset.swift +21 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +6 -4
- package/ios/Extension/Track+Copying.swift +8 -12
- package/ios/Extension/Track+Identity.swift +18 -0
- package/ios/Extension/UIApplication+ActiveScene.swift +27 -0
- package/ios/Http/HttpClient.swift +8 -2
- package/ios/HybridAudioBrowser.swift +253 -100
- package/ios/Model/NitroTypeStubs.swift +125 -23
- package/ios/Model/PlayerUpdateOptions.swift +7 -0
- package/ios/Model/RemoteCommand.swift +18 -0
- package/ios/Model/TrackPlayerError.swift +2 -2
- package/ios/Option/SessionCategories.swift +3 -2
- package/ios/Player/MediaIntentCriteria.swift +17 -9
- package/ios/Player/MediaLoader.swift +33 -9
- package/ios/Player/NowPlayingUpdater.swift +54 -6
- package/ios/Player/PlaybackCoordinator.swift +13 -6
- package/ios/Player/PlaybackErrorHandler.swift +4 -4
- package/ios/Player/PlaybackTimer.swift +8 -2
- package/ios/Player/QueueManager.swift +46 -13
- package/ios/Player/RetryManager.swift +80 -18
- package/ios/Player/SeekCompletionHandler.swift +3 -1
- package/ios/Player/ShuffleOrder.swift +33 -0
- package/ios/Player/SleepTimerHandling.swift +13 -0
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +47 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +62 -24
- package/ios/Tests/BrowserPathHelperTests.swift +50 -20
- package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
- package/ios/Tests/InertStyleDiagnosticTests.swift +200 -0
- package/ios/Tests/JsonModelsTests.swift +150 -1
- package/ios/Tests/LRUCacheTests.swift +111 -0
- package/ios/Tests/LoadSeekCoordinatorTests.swift +3 -1
- package/ios/Tests/MediaIntentCriteriaTests.swift +2 -2
- package/ios/Tests/PlaybackCoordinatorTests.swift +1 -1
- package/ios/Tests/PlaybackErrorHandlerTests.swift +3 -1
- package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
- package/ios/Tests/PlayerTimeObserverTests.swift +37 -0
- package/ios/Tests/QueueManagerTests.swift +122 -10
- package/ios/Tests/RemoteCommandControllerTests.swift +172 -0
- package/ios/Tests/RemoteCommandTests.swift +67 -0
- package/ios/Tests/ResolvedTrackCopyingTests.swift +39 -35
- package/ios/Tests/RetryManagerTests.swift +89 -0
- package/ios/Tests/SearchDrillInTests.swift +16 -16
- package/ios/Tests/SectionPresentationTests.swift +90 -0
- package/ios/Tests/SectionScopeTests.swift +87 -55
- package/ios/Tests/StyleResolverTests.swift +127 -0
- package/ios/Tests/Support/TestTypes.swift +8 -1
- package/ios/Tests/TabBarEntriesTests.swift +19 -19
- package/ios/Tests/TimedMetadataAVFoundationTests.swift +42 -0
- package/ios/Tests/TrackIdTemplateTests.swift +67 -0
- package/ios/Tests/TrackIdentityTests.swift +20 -0
- package/ios/Tests/TrackSelectorTests.swift +168 -33
- package/ios/TrackPlayer.swift +63 -35
- package/ios/TrackPlayerCallbacks.swift +5 -38
- package/ios/TrackSelector.swift +45 -30
- package/ios/Util/ArtworkImageFetcher.swift +16 -1
- package/ios/Util/LRUCache.swift +4 -1
- package/ios/Util/NetworkMonitor.swift +43 -26
- package/ios/Util/SFSymbolRenderer.swift +24 -2
- package/ios/Util/SVGProcessor.swift +9 -2
- package/lib/commonjs/AudioBrowser.js +8 -8
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/CLAUDE.md +52 -0
- package/lib/commonjs/features/browser-config.js +12 -8
- package/lib/commonjs/features/browser-config.js.map +1 -1
- package/lib/commonjs/features/browser.js +11 -3
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/carConnection.js +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +3 -3
- package/lib/commonjs/features/favorites.js +14 -11
- package/lib/commonjs/features/favorites.js.map +1 -1
- package/lib/commonjs/features/gate.js +16 -1
- package/lib/commonjs/features/gate.js.map +1 -1
- package/lib/commonjs/features/metadata.js +20 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -1
- package/lib/commonjs/features/output.js +1 -1
- package/lib/commonjs/features/playback/controls.js +4 -2
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +3 -2
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/rate.js +8 -1
- package/lib/commonjs/features/playback/rate.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +3 -4
- package/lib/commonjs/features/playback/state.js.map +1 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -6
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/commonjs/features/playback/volume.js +1 -1
- package/lib/commonjs/features/player/options.js +48 -34
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +18 -9
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/player/validateOptions.js +23 -0
- package/lib/commonjs/features/player/validateOptions.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +15 -4
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
- package/lib/commonjs/features/queue/queue.js +13 -6
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/remoteControls.js +13 -92
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +13 -1
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/test-utils/reactNativeStub.js +65 -0
- package/lib/commonjs/test-utils/reactNativeStub.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +28 -14
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/utils/getTrackIdentity.js +24 -0
- package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
- package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +200 -75
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js +37 -0
- package/lib/commonjs/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/Player.js +254 -39
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +175 -100
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +18 -23
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/commonjs/web/browser/sections.js +89 -0
- package/lib/commonjs/web/browser/sections.js.map +1 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js +110 -27
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/http/followMediaRedirect.js +219 -0
- package/lib/commonjs/web/http/followMediaRedirect.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/module/AudioBrowser.js +2 -1
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/CLAUDE.md +52 -0
- package/lib/module/features/browser-config.js +12 -8
- package/lib/module/features/browser-config.js.map +1 -1
- package/lib/module/features/browser.js +11 -3
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/carConnection.js +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +3 -3
- package/lib/module/features/favorites.js +14 -11
- package/lib/module/features/favorites.js.map +1 -1
- package/lib/module/features/gate.js +16 -1
- package/lib/module/features/gate.js.map +1 -1
- package/lib/module/features/metadata.js +20 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -1
- package/lib/module/features/output.js +1 -1
- package/lib/module/features/playback/controls.js +4 -2
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +3 -2
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/rate.js +8 -1
- package/lib/module/features/playback/rate.js.map +1 -1
- package/lib/module/features/playback/state.js +3 -4
- package/lib/module/features/playback/state.js.map +1 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -5
- package/lib/module/features/playback/trackPlaybackTime.js.map +1 -1
- package/lib/module/features/playback/volume.js +1 -1
- package/lib/module/features/player/options.js +47 -32
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +17 -8
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/player/validateOptions.js +19 -0
- package/lib/module/features/player/validateOptions.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +15 -4
- package/lib/module/features/queue/activeTrack.js.map +1 -1
- package/lib/module/features/queue/queue.js +13 -6
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/remoteControls.js +13 -92
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +13 -1
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/test-utils/reactNativeStub.js +58 -0
- package/lib/module/test-utils/reactNativeStub.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +28 -14
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +31 -13
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/utils/getTrackIdentity.js +20 -0
- package/lib/module/utils/getTrackIdentity.js.map +1 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
- package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +200 -75
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/TrackPlayer/LoadAttempt.js +32 -0
- package/lib/module/web/TrackPlayer/LoadAttempt.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +18 -2
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +1 -1
- package/lib/module/web/TrackPlayer/Player.js +254 -39
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +14 -0
- package/lib/module/web/TrackPlayer/QueueManager.js.map +1 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +33 -12
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +175 -100
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/FavoriteManager.js +31 -21
- package/lib/module/web/browser/FavoriteManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +18 -23
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
- package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
- package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
- package/lib/module/web/browser/sections.js +83 -0
- package/lib/module/web/browser/sections.js.map +1 -0
- package/lib/module/web/http/RequestConfigBuilder.js +110 -27
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/http/followMediaRedirect.js +215 -0
- package/lib/module/web/http/followMediaRedirect.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +4 -3
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/util/BrowserPathHelper.js +56 -11
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +10 -2
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/carConnection.d.ts +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +3 -3
- package/lib/typescript/src/features/favorites.d.ts +14 -11
- package/lib/typescript/src/features/favorites.d.ts.map +1 -1
- package/lib/typescript/src/features/gate.d.ts +18 -2
- package/lib/typescript/src/features/gate.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +20 -3
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -1
- package/lib/typescript/src/features/output.d.ts +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +4 -2
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/rate.d.ts +8 -1
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +3 -4
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +1 -1
- package/lib/typescript/src/features/player/options.d.ts +108 -56
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +14 -4
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +2 -0
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +9 -2
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +7 -4
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/remoteControls.d.ts +23 -22
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts +36 -0
- package/lib/typescript/src/test-utils/reactNativeStub.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +3 -2
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +380 -126
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +82 -57
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +19 -16
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
- package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
- package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +1 -2
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +16 -4
- package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
- package/nitrogen/generated/android/c++/JCardImage.hpp +58 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__string__.hpp +102 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +16 -4
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +7 -10
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +15 -3
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +8 -2
- package/nitrogen/generated/android/c++/JGridTile.hpp +61 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +45 -25
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JImageShape.hpp +58 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +8 -3
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +36 -11
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +14 -2
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +15 -12
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +10 -10
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
- package/nitrogen/generated/android/c++/JOptions.hpp +7 -10
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +8 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/{JNotificationButton.hpp → JRemoteButton.hpp} +19 -19
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +86 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +57 -49
- package/nitrogen/generated/android/c++/JSection.hpp +116 -0
- package/nitrogen/generated/android/c++/JSectionStyle.hpp +96 -0
- package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +25 -46
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +8 -2
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +46 -18
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +16 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CardImage.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/GridTile.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageShape.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NotificationButton.kt → RemoteButton.kt} +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -15
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → Section.kt} +15 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SectionStyle.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +35 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_NotificationButtonLayout.kt → Variant_NullType_RemoteButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +9 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +244 -133
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +31 -16
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +33 -18
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
- package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
- package/nitrogen/generated/ios/swift/CardImage.swift +40 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__string__.swift +54 -0
- package/nitrogen/generated/ios/swift/GridTile.swift +44 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2 -2
- package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
- package/nitrogen/generated/ios/swift/ImageShape.swift +40 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +52 -5
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +12 -12
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/{NotificationButton.swift → RemoteButton.swift} +6 -6
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +57 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +62 -73
- package/nitrogen/generated/ios/swift/Section.swift +82 -0
- package/nitrogen/generated/ios/swift/SectionStyle.swift +133 -0
- package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
- package/nitrogen/generated/ios/swift/Track.swift +28 -63
- package/nitrogen/generated/ios/swift/TrackStyle.swift +86 -22
- package/nitrogen/generated/ios/swift/{Variant_NullType_NotificationButtonLayout.swift → Variant_NullType_RemoteButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
- package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
- package/nitrogen/generated/shared/c++/CardImage.hpp +76 -0
- package/nitrogen/generated/shared/c++/GridTile.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +1 -1
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +1 -1
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/ImageShape.hpp +76 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +10 -6
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +8 -8
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/{NotificationButton.hpp → RemoteButton.hpp} +19 -19
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +33 -37
- package/nitrogen/generated/shared/c++/Section.hpp +102 -0
- package/nitrogen/generated/shared/c++/SectionStyle.hpp +126 -0
- package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
- package/nitrogen/generated/shared/c++/Track.hpp +18 -30
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +73 -34
- package/package.json +24 -11
- package/src/AudioBrowser.ts +2 -1
- package/src/CLAUDE.md +52 -0
- package/src/features/battery.test.ts +79 -0
- package/src/features/browser-config.test.ts +21 -6
- package/src/features/browser-config.ts +12 -8
- package/src/features/browser.test.ts +1 -1
- package/src/features/browser.ts +11 -3
- package/src/features/carConnection.ts +1 -1
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +3 -3
- package/src/features/favorites.ts +14 -11
- package/src/features/gate.ts +16 -1
- package/src/features/hook-wiring.test.ts +302 -0
- package/src/features/metadata.ts +20 -3
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -1
- package/src/features/output.ts +1 -1
- package/src/features/playback/controls.ts +4 -2
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.test.ts +169 -0
- package/src/features/playback/progress.ts +3 -2
- package/src/features/playback/rate.ts +8 -1
- package/src/features/playback/state.ts +3 -4
- package/src/features/playback/trackPlaybackTime.test.ts +16 -6
- package/src/features/playback/trackPlaybackTime.ts +1 -6
- package/src/features/playback/volume.ts +1 -1
- package/src/features/player/options.ts +126 -76
- package/src/features/player/setup.test.ts +24 -7
- package/src/features/player/setup.ts +22 -10
- package/src/features/player/validateOptions.ts +21 -0
- package/src/features/queue/activeTrack.ts +15 -4
- package/src/features/queue/queue.ts +13 -6
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/remoteControls.ts +13 -92
- package/src/features/sleepTimer.test.ts +172 -0
- package/src/features/sleepTimer.ts +9 -1
- package/src/nitro-stub-drift.test.ts +277 -0
- package/src/public-surface.test.ts +147 -0
- package/src/specs/audio-browser.nitro.ts +5 -4
- package/src/style-resolution.test.ts +210 -0
- package/src/test-utils/reactNativeStub.ts +60 -0
- package/src/types/browser-native.ts +7 -3
- package/src/types/browser-nodes.ts +396 -134
- package/src/types/browser.ts +85 -59
- package/src/utils/LazyNativeEmitter.ts +28 -15
- package/src/utils/NativeUpdatedValue.ts +29 -13
- package/src/utils/getTrackIdentity.ts +20 -0
- package/src/utils/useDebug.test.ts +182 -0
- package/src/utils/useNativeUpdatedValue.test.ts +103 -0
- package/src/utils/useNativeValueRefreshedBy.ts +38 -0
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +232 -2
- package/src/web/NativeAudioBrowser.ts +239 -82
- package/src/web/TrackPlayer/LoadAttempt.ts +29 -0
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +60 -13
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +22 -6
- package/src/web/TrackPlayer/Player.dispatch.test.ts +5 -3
- package/src/web/TrackPlayer/Player.load.test.ts +137 -1
- package/src/web/TrackPlayer/Player.ts +284 -37
- package/src/web/TrackPlayer/QueueManager.test.ts +29 -0
- package/src/web/TrackPlayer/QueueManager.ts +14 -0
- package/src/web/TrackPlayer/QueuePlayer.ts +33 -14
- package/src/web/__tests__/lifecycle.test.ts +345 -0
- package/src/web/__tests__/lifecycleHarness.ts +351 -0
- package/src/web/__tests__/loadEdgeCases.test.ts +246 -0
- package/src/web/__tests__/loadSupersession.test.ts +175 -0
- package/src/web/__tests__/retry.test.ts +169 -0
- package/src/web/browser/BrowserManager.ts +209 -111
- package/src/web/browser/FavoriteManager.test.ts +84 -0
- package/src/web/browser/FavoriteManager.ts +28 -23
- package/src/web/browser/SearchManager.test.ts +75 -0
- package/src/web/browser/SearchManager.ts +25 -28
- package/src/web/browser/__tests__/artwork-resolution.test.ts +117 -0
- package/src/web/browser/__tests__/layer-resolver.test.ts +35 -0
- package/src/web/browser/__tests__/search-artwork.test.ts +46 -0
- package/src/web/browser/__tests__/search-navigation.test.ts +76 -0
- package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
- package/src/web/browser/assertBrowsePageShape.ts +5 -3
- package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
- package/src/web/browser/classifyTrackNavigation.ts +9 -9
- package/src/web/browser/parseSearchResponse.test.ts +8 -2
- package/src/web/browser/sections.test.ts +91 -0
- package/src/web/browser/sections.ts +72 -0
- package/src/web/http/RequestConfigBuilder.test.ts +241 -1
- package/src/web/http/RequestConfigBuilder.ts +140 -33
- package/src/web/http/followMediaRedirect.test.ts +289 -0
- package/src/web/http/followMediaRedirect.ts +253 -0
- package/src/web/player/NowPlayingManager.ts +4 -3
- package/src/web/util/BrowserPathHelper.test.ts +52 -0
- package/src/web/util/BrowserPathHelper.ts +61 -13
- package/website/guide/android-auto.md +114 -0
- package/website/guide/android-certificates.md +409 -0
- package/website/guide/artwork.md +255 -0
- package/website/guide/audio-output.md +135 -0
- package/website/guide/automotive.md +86 -0
- package/website/guide/basic-usage.md +151 -0
- package/website/guide/battery.md +115 -0
- package/website/guide/browser.md +581 -0
- package/website/guide/carplay.md +620 -0
- package/website/guide/configuration.md +173 -0
- package/website/guide/equalizer.md +231 -0
- package/website/guide/errors.md +310 -0
- package/website/guide/favorites.md +154 -0
- package/website/guide/gate.md +143 -0
- package/website/guide/getting-started.md +46 -0
- package/website/guide/hooks.md +238 -0
- package/website/guide/live-streams.md +125 -0
- package/website/guide/metadata.md +239 -0
- package/website/guide/migrating-from-track-player.md +244 -0
- package/website/guide/native-callback-fetch.md +127 -0
- package/website/guide/network.md +86 -0
- package/website/guide/now-playing.md +175 -0
- package/website/guide/playback.md +328 -0
- package/website/guide/queue.md +300 -0
- package/website/guide/remote-controls.md +256 -0
- package/website/guide/search.md +255 -0
- package/website/guide/sleep-timer.md +268 -0
- package/website/guide/track.md +247 -0
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/Model/MediaURL.swift +0 -67
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +0 -94
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +0 -92
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +0 -102
|
@@ -15,6 +15,12 @@ import type { PlaybackState } from '../../features'
|
|
|
15
15
|
* | `paused` | element `pause` |
|
|
16
16
|
* | `trackEndedNaturally`| element `ended` |
|
|
17
17
|
*
|
|
18
|
+
* `trackLoading` is left to Shaka rather than also dispatched when `load()` is
|
|
19
|
+
* called. Dispatching it up front would give the URL-resolution window a state
|
|
20
|
+
* to report, but it would put `loading` ahead of
|
|
21
|
+
* `onPlaybackActiveTrackChanged`, and native emits those the other way round —
|
|
22
|
+
* see the note in `NativeAudioBrowser.load`.
|
|
23
|
+
*
|
|
18
24
|
* Deliberate *commands* (stop, error) set their terminal state directly rather
|
|
19
25
|
* than flowing through here — they are intentions, not observations. The two
|
|
20
26
|
* iOS-only events `trackUnloaded` and `audioFrameDecoded` are omitted: web has
|
|
@@ -24,11 +30,11 @@ import type { PlaybackState } from '../../features'
|
|
|
24
30
|
export type PlaybackEvent =
|
|
25
31
|
| { type: 'trackLoading' }
|
|
26
32
|
| { type: 'trackEndedNaturally' }
|
|
27
|
-
| { type: 'loadSeekCompleted' }
|
|
33
|
+
| { type: 'loadSeekCompleted'; playWhenReady: boolean }
|
|
28
34
|
| { type: 'paused'; hasAsset: boolean }
|
|
29
35
|
| { type: 'waiting' }
|
|
30
36
|
| { type: 'playing' }
|
|
31
|
-
| { type: 'bufferingSufficient' }
|
|
37
|
+
| { type: 'bufferingSufficient'; playWhenReady: boolean }
|
|
32
38
|
|
|
33
39
|
/**
|
|
34
40
|
* The next playback state for an event from the current state, or `null` to
|
|
@@ -56,7 +62,12 @@ export function nextPlaybackState(
|
|
|
56
62
|
return 'playing'
|
|
57
63
|
case 'loadSeekCompleted':
|
|
58
64
|
// The settle after a load; meaningless from anything but a fresh load.
|
|
59
|
-
|
|
65
|
+
// `buffering` counts as one: a load now always passes through it.
|
|
66
|
+
if (current !== 'loading' && current !== 'buffering') return null
|
|
67
|
+
// Suppressed while the intent is to play, as Android suppresses
|
|
68
|
+
// STATE_READY there — it is a transient before `playing`, and emitting it
|
|
69
|
+
// flashes a settled, non-loading state mid-startup.
|
|
70
|
+
return event.playWhenReady ? null : 'ready'
|
|
60
71
|
case 'paused':
|
|
61
72
|
// A stopped player owns its state; a stray pause must not disturb it.
|
|
62
73
|
if (current === 'stopped') return null
|
|
@@ -72,11 +83,16 @@ export function nextPlaybackState(
|
|
|
72
83
|
// it would silently clear the error the UI is rendering (Shaka's
|
|
73
84
|
// post-error unload emits buffering events without the stopped dispatch
|
|
74
85
|
// gate); ended/stopped own their state until an explicit load or play.
|
|
75
|
-
|
|
86
|
+
if (
|
|
87
|
+
current === 'playing' ||
|
|
76
88
|
current === 'ended' ||
|
|
77
89
|
current === 'stopped' ||
|
|
78
90
|
current === 'error'
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
) {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
// Same suppression as `loadSeekCompleted`: while the intent is to play,
|
|
95
|
+
// `playing` is the next state worth reporting.
|
|
96
|
+
return event.playWhenReady ? null : 'ready'
|
|
81
97
|
}
|
|
82
98
|
}
|
|
@@ -23,8 +23,10 @@ class TestPlayer extends Player {
|
|
|
23
23
|
|
|
24
24
|
class TestQueuePlayer extends QueuePlayer {
|
|
25
25
|
endedCount = 0
|
|
26
|
-
|
|
26
|
+
/** Stands in for a handler that advances, so `ended` stays suppressed. */
|
|
27
|
+
protected onTrackEnded(): boolean {
|
|
27
28
|
this.endedCount++
|
|
29
|
+
return true
|
|
28
30
|
}
|
|
29
31
|
forceStopped(stopped: boolean): void {
|
|
30
32
|
this._isStopped = stopped
|
|
@@ -61,7 +63,7 @@ describe('Player.dispatch', () => {
|
|
|
61
63
|
const player = new TestPlayer()
|
|
62
64
|
player.forceState('playing')
|
|
63
65
|
|
|
64
|
-
player.emit({ type: 'bufferingSufficient' })
|
|
66
|
+
player.emit({ type: 'bufferingSufficient', playWhenReady: false })
|
|
65
67
|
|
|
66
68
|
expect(player.read().state).toBe('playing')
|
|
67
69
|
})
|
|
@@ -70,7 +72,7 @@ describe('Player.dispatch', () => {
|
|
|
70
72
|
const player = new TestPlayer()
|
|
71
73
|
player.forceState('loading')
|
|
72
74
|
|
|
73
|
-
player.emit({ type: 'loadSeekCompleted' })
|
|
75
|
+
player.emit({ type: 'loadSeekCompleted', playWhenReady: false })
|
|
74
76
|
|
|
75
77
|
expect(player.read().state).toBe('ready')
|
|
76
78
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import type { Playback } from '../../features'
|
|
3
3
|
import type { Track } from '../../types'
|
|
4
4
|
import { Player } from './Player'
|
|
@@ -84,3 +84,139 @@ describe('Player.load rejection staleness', () => {
|
|
|
84
84
|
expect(player.read().state).toBe('error')
|
|
85
85
|
})
|
|
86
86
|
})
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The URL handed to Shaka can differ from the one the caller queued — a
|
|
90
|
+
* followed redirect, or a `transform` that rewrites it. Callers address tracks
|
|
91
|
+
* by their own `src` (mapping it back to their own content id, say), so a load
|
|
92
|
+
* must not rewrite the track's identity to a transport detail.
|
|
93
|
+
*/
|
|
94
|
+
describe('Player.load track identity', () => {
|
|
95
|
+
class SrcCapturingPlayer extends Player {
|
|
96
|
+
loadedSrc?: string
|
|
97
|
+
|
|
98
|
+
constructor() {
|
|
99
|
+
super()
|
|
100
|
+
this.element = {
|
|
101
|
+
play: () => Promise.resolve(),
|
|
102
|
+
pause: () => {}
|
|
103
|
+
} as unknown as HTMLMediaElement
|
|
104
|
+
this.player = {
|
|
105
|
+
load: (src: string) => {
|
|
106
|
+
this.loadedSrc = src
|
|
107
|
+
return Promise.resolve()
|
|
108
|
+
},
|
|
109
|
+
unload: () => Promise.resolve()
|
|
110
|
+
} as unknown as typeof this.player
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
headers(): Record<string, string> | undefined {
|
|
114
|
+
return this.mediaHeaders
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const queued = {
|
|
119
|
+
id: 't1',
|
|
120
|
+
src: 'https://api.example.com/stream/content/7',
|
|
121
|
+
title: 'T1'
|
|
122
|
+
} as Track
|
|
123
|
+
const resolved = 'https://cdn.example.com/track.mp3?sig=abc'
|
|
124
|
+
|
|
125
|
+
it('plays the resolved url but keeps the queued track as current', async () => {
|
|
126
|
+
const player = new SrcCapturingPlayer()
|
|
127
|
+
|
|
128
|
+
player.load(queued, undefined, { src: resolved })
|
|
129
|
+
await tick()
|
|
130
|
+
|
|
131
|
+
expect(player.loadedSrc).toBe(resolved)
|
|
132
|
+
expect(player.current?.src).toBe(queued.src)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('reports the queued track to onLoaded', async () => {
|
|
136
|
+
const player = new SrcCapturingPlayer()
|
|
137
|
+
const onLoaded = vi.fn()
|
|
138
|
+
|
|
139
|
+
player.load(queued, onLoaded, { src: resolved })
|
|
140
|
+
await tick()
|
|
141
|
+
|
|
142
|
+
expect(onLoaded).toHaveBeenCalledWith(
|
|
143
|
+
expect.objectContaining({ src: queued.src })
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('falls back to the track src when no override is given', async () => {
|
|
148
|
+
const player = new SrcCapturingPlayer()
|
|
149
|
+
|
|
150
|
+
player.load(queued)
|
|
151
|
+
await tick()
|
|
152
|
+
|
|
153
|
+
expect(player.loadedSrc).toBe(queued.src)
|
|
154
|
+
expect(player.current?.src).toBe(queued.src)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('stores headers for the request filter, ignoring an empty set', async () => {
|
|
158
|
+
const player = new SrcCapturingPlayer()
|
|
159
|
+
|
|
160
|
+
player.load(queued, undefined, { headers: { Authorization: 'Bearer t' } })
|
|
161
|
+
await tick()
|
|
162
|
+
expect(player.headers()).toEqual({ Authorization: 'Bearer t' })
|
|
163
|
+
|
|
164
|
+
player.load(queued, undefined, { headers: {} })
|
|
165
|
+
await tick()
|
|
166
|
+
expect(player.headers()).toBeUndefined()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A track that fails to load is still the active track. Native derives the
|
|
172
|
+
* active track from the queue, so an app always has something to attach a
|
|
173
|
+
* playback error to; web tying it to load success meant a failed track left
|
|
174
|
+
* `getActiveTrack()` undefined, and UIs keyed on it rendered nothing at all —
|
|
175
|
+
* the error had nowhere to appear.
|
|
176
|
+
*/
|
|
177
|
+
describe('Player.load active track on failure', () => {
|
|
178
|
+
const track = (n: number): Track =>
|
|
179
|
+
({
|
|
180
|
+
id: `t${n}`,
|
|
181
|
+
src: `https://example.com/${n}.mp3`,
|
|
182
|
+
title: `T${n}`
|
|
183
|
+
}) as Track
|
|
184
|
+
|
|
185
|
+
it('keeps the failed track as current so an error has a home', async () => {
|
|
186
|
+
const player = new LoadTestPlayer()
|
|
187
|
+
|
|
188
|
+
player.load(track(1))
|
|
189
|
+
expect(player.current?.src).toBe(track(1).src)
|
|
190
|
+
|
|
191
|
+
player.rejectLoad(0, new Error('401'))
|
|
192
|
+
await tick()
|
|
193
|
+
|
|
194
|
+
expect(player.current?.src).toBe(track(1).src)
|
|
195
|
+
expect(player.read().state).toBe('error')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('reflects the newest attempt when a load supersedes another', async () => {
|
|
199
|
+
const player = new LoadTestPlayer()
|
|
200
|
+
|
|
201
|
+
player.load(track(1))
|
|
202
|
+
player.load(track(2))
|
|
203
|
+
|
|
204
|
+
expect(player.current?.src).toBe(track(2).src)
|
|
205
|
+
|
|
206
|
+
// the superseded load rejecting must not resurrect the older track
|
|
207
|
+
player.rejectLoad(0, new Error('stale'))
|
|
208
|
+
await tick()
|
|
209
|
+
|
|
210
|
+
expect(player.current?.src).toBe(track(2).src)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('is set for an unplayable track with no src', async () => {
|
|
214
|
+
const player = new LoadTestPlayer()
|
|
215
|
+
|
|
216
|
+
player.load({ id: 'x', title: 'No Src' } as Track)
|
|
217
|
+
await tick()
|
|
218
|
+
|
|
219
|
+
expect(player.current?.id).toBe('x')
|
|
220
|
+
expect(player.read().state).toBe('error')
|
|
221
|
+
})
|
|
222
|
+
})
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
import type { Track } from '../../types'
|
|
10
10
|
import type { PlaybackEvent } from './PlaybackStateMachine'
|
|
11
11
|
import { playbackErrorKind } from '../playbackErrorKind'
|
|
12
|
+
import { LoadAttempt } from './LoadAttempt'
|
|
12
13
|
import { nextPlaybackState } from './PlaybackStateMachine'
|
|
13
14
|
import { SetupNotCalledError } from './SetupNotCalledError'
|
|
14
15
|
import { State } from './State'
|
|
@@ -32,6 +33,41 @@ interface ShakaError {
|
|
|
32
33
|
data?: unknown[]
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/** Per-load overrides that don't belong on the track itself. */
|
|
37
|
+
export interface LoadOptions {
|
|
38
|
+
/**
|
|
39
|
+
* The attempt this load belongs to, when a subclass began one before
|
|
40
|
+
* resolving the URL. Internal plumbing: without it a direct `load()` is the
|
|
41
|
+
* outermost call and starts its own, so two of them supersede each other.
|
|
42
|
+
*/
|
|
43
|
+
attempt?: LoadAttempt
|
|
44
|
+
/**
|
|
45
|
+
* Whether this load is a re-prepare of the current item, when a subclass
|
|
46
|
+
* captured {@link Player._repreparing} before an async stage and calls
|
|
47
|
+
* `super.load()` after it, once the flag has been reset.
|
|
48
|
+
*/
|
|
49
|
+
reprepare?: boolean
|
|
50
|
+
/** Headers resolved from the media configuration, applied to Shaka's requests. */
|
|
51
|
+
headers?: Record<string, string>
|
|
52
|
+
/**
|
|
53
|
+
* The URL to play, when it differs from `track.src` — a followed redirect,
|
|
54
|
+
* say. Kept here rather than rewritten onto the track so the caller's own
|
|
55
|
+
* identity for it survives the load.
|
|
56
|
+
*/
|
|
57
|
+
src?: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The mutable request Shaka hands to a networking-engine request filter. */
|
|
61
|
+
interface ShakaRequest {
|
|
62
|
+
headers: Record<string, string>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `shaka.util.Error.Code.LOAD_INTERRUPTED`. Inlined because shaka is imported
|
|
67
|
+
* for types only here, as the codes in `playbackErrorKind.ts` are.
|
|
68
|
+
*/
|
|
69
|
+
const LOAD_INTERRUPTED = 7000
|
|
70
|
+
|
|
35
71
|
interface ShakaBufferingEvent extends CustomEvent {
|
|
36
72
|
detail: {
|
|
37
73
|
buffering: boolean
|
|
@@ -48,6 +84,12 @@ export class Player {
|
|
|
48
84
|
protected _isStopped = false
|
|
49
85
|
protected _loadInProgress = false
|
|
50
86
|
protected _pendingSeek: number | undefined
|
|
87
|
+
/**
|
|
88
|
+
* Headers for the track currently loaded, applied by the request filter
|
|
89
|
+
* installed in `setupPlayer`. On the instance rather than per request so the
|
|
90
|
+
* manifest, its segments and any key requests all carry them.
|
|
91
|
+
*/
|
|
92
|
+
protected mediaHeaders?: Record<string, string>
|
|
51
93
|
|
|
52
94
|
// current getter/setter
|
|
53
95
|
public get current(): Track | undefined {
|
|
@@ -125,6 +167,17 @@ export class Player {
|
|
|
125
167
|
await player.attach(element)
|
|
126
168
|
this.player = player
|
|
127
169
|
|
|
170
|
+
// Apply the media config's headers to Shaka's requests, as native does to
|
|
171
|
+
// the AVURLAsset / ExoPlayer DataSpec. Without this they resolve and are
|
|
172
|
+
// then dropped, and authenticated media 401s on web alone.
|
|
173
|
+
player
|
|
174
|
+
.getNetworkingEngine()
|
|
175
|
+
?.registerRequestFilter((_type: unknown, request: ShakaRequest) => {
|
|
176
|
+
const headers = this.mediaHeaders
|
|
177
|
+
if (!headers) return
|
|
178
|
+
request.headers = { ...request.headers, ...headers }
|
|
179
|
+
})
|
|
180
|
+
|
|
128
181
|
// Listen for relevant events
|
|
129
182
|
player.addEventListener('error', (event: Event) => {
|
|
130
183
|
const errorEvent = event as ShakaErrorEvent
|
|
@@ -137,15 +190,27 @@ export class Player {
|
|
|
137
190
|
element.addEventListener('playing', () =>
|
|
138
191
|
this.dispatch({ type: 'playing' })
|
|
139
192
|
)
|
|
140
|
-
element.addEventListener('pause', () =>
|
|
193
|
+
element.addEventListener('pause', () => {
|
|
194
|
+
// A track playing out fires `pause` and then `ended`. Only the `ended`
|
|
195
|
+
// is meaningful — reporting `paused` first flashes a state Android never
|
|
196
|
+
// reports, mid-queue or at the true end.
|
|
197
|
+
if (element.ended) return
|
|
141
198
|
this.dispatch({ type: 'paused', hasAsset: this.current !== undefined })
|
|
142
|
-
)
|
|
199
|
+
})
|
|
143
200
|
|
|
144
|
-
player.addEventListener('loading', () =>
|
|
201
|
+
player.addEventListener('loading', () => {
|
|
145
202
|
this.dispatch({ type: 'trackLoading' })
|
|
146
|
-
|
|
203
|
+
// Every load passes through `buffering`, as ExoPlayer's always does via
|
|
204
|
+
// STATE_BUFFERING. Shaka only fires its own `buffering` event when a load
|
|
205
|
+
// actually stalls, so a load served from cache would otherwise skip a
|
|
206
|
+
// state native always reports.
|
|
207
|
+
this.dispatch({ type: 'waiting' })
|
|
208
|
+
})
|
|
147
209
|
player.addEventListener('loaded', () =>
|
|
148
|
-
this.dispatch({
|
|
210
|
+
this.dispatch({
|
|
211
|
+
type: 'loadSeekCompleted',
|
|
212
|
+
playWhenReady: this.playWhenReady
|
|
213
|
+
})
|
|
149
214
|
)
|
|
150
215
|
|
|
151
216
|
player.addEventListener('buffering', (event: Event) => {
|
|
@@ -153,7 +218,10 @@ export class Player {
|
|
|
153
218
|
this.dispatch(
|
|
154
219
|
bufferingEvent.detail.buffering === true
|
|
155
220
|
? { type: 'waiting' }
|
|
156
|
-
: {
|
|
221
|
+
: {
|
|
222
|
+
type: 'bufferingSufficient',
|
|
223
|
+
playWhenReady: this.playWhenReady
|
|
224
|
+
}
|
|
157
225
|
)
|
|
158
226
|
})
|
|
159
227
|
|
|
@@ -208,6 +276,24 @@ export class Player {
|
|
|
208
276
|
}
|
|
209
277
|
|
|
210
278
|
protected onError(shakaError: ShakaError): void {
|
|
279
|
+
// An interrupted load is not a failure. Shaka raises it when a `load()` is
|
|
280
|
+
// cut short by a newer `load()` or an `unload()` — always something we
|
|
281
|
+
// asked for — and names it accordingly: `createAbortLoadError_()`. Its own
|
|
282
|
+
// code skips its cleanup unload for this code too (player.js:2010).
|
|
283
|
+
//
|
|
284
|
+
// Recognising it here rather than pre-emptively invalidating the load keeps
|
|
285
|
+
// the two independent: any teardown, in any order, can abort a load without
|
|
286
|
+
// the abort reaching a consumer as an error. Eagerly unloading to stop the
|
|
287
|
+
// outgoing track used to flash an error banner on every fast track switch.
|
|
288
|
+
if (shakaError.code === LOAD_INTERRUPTED) {
|
|
289
|
+
console.debug('Load interrupted by a newer load or an unload')
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Before the unload below resets it: a retry resumes from where playback
|
|
294
|
+
// failed, as native does.
|
|
295
|
+
this.rememberResumePosition()
|
|
296
|
+
|
|
211
297
|
// unload the current track to allow for clean playback on other
|
|
212
298
|
this.player?.unload().catch((err) => {
|
|
213
299
|
console.error(`Error unloading player on 'onError'`, err)
|
|
@@ -236,18 +322,60 @@ export class Player {
|
|
|
236
322
|
console.debug('Error code', shakaError.code, 'object', shakaError)
|
|
237
323
|
}
|
|
238
324
|
|
|
239
|
-
/**
|
|
240
|
-
*
|
|
241
|
-
|
|
242
|
-
private
|
|
325
|
+
/**
|
|
326
|
+
* The load attempt that owns the player, or none. See {@link LoadAttempt}.
|
|
327
|
+
*/
|
|
328
|
+
private _attempt: LoadAttempt | undefined
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Starts an attempt, superseding whatever was in flight.
|
|
332
|
+
*
|
|
333
|
+
* Called by the outermost `load()` so supersession is registered when the
|
|
334
|
+
* caller asks for it, not when some inner stage happens to reach it.
|
|
335
|
+
*/
|
|
336
|
+
protected beginLoadAttempt(): LoadAttempt {
|
|
337
|
+
this._attempt?.cancel()
|
|
338
|
+
const attempt = new LoadAttempt()
|
|
339
|
+
this._attempt = attempt
|
|
340
|
+
return attempt
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Abandons the attempt in flight without starting another — `stop()`. */
|
|
344
|
+
protected cancelLoadAttempt(): void {
|
|
345
|
+
this._attempt?.cancel()
|
|
346
|
+
this._attempt = undefined
|
|
347
|
+
}
|
|
243
348
|
|
|
244
|
-
public load(
|
|
349
|
+
public load(
|
|
350
|
+
track: Track,
|
|
351
|
+
onLoaded?: (track: Track) => void,
|
|
352
|
+
options?: LoadOptions
|
|
353
|
+
): void {
|
|
245
354
|
const player = this.requirePlayer()
|
|
246
355
|
this._isStopped = false
|
|
247
356
|
this._loadInProgress = true
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
357
|
+
const headers = options?.headers
|
|
358
|
+
this.mediaHeaders =
|
|
359
|
+
headers && Object.keys(headers).length > 0 ? headers : undefined
|
|
360
|
+
// What Shaka plays may differ from what the caller queued; `track` is left
|
|
361
|
+
// alone so `current` and `getActiveTrack()` keep the caller's identity for
|
|
362
|
+
// it rather than a transport detail.
|
|
363
|
+
const playbackSrc = options?.src ?? track.src
|
|
364
|
+
// Current on the attempt, not on success — native derives the active track
|
|
365
|
+
// from the queue, so a failed load is still the active track there and its
|
|
366
|
+
// error has somewhere to show. `getActiveTrackIndex()` already reported the
|
|
367
|
+
// queue's index either way; this makes the pair agree.
|
|
368
|
+
this.current = track
|
|
369
|
+
// Only a re-prepare carries the remembered position, and `reprepare()`
|
|
370
|
+
// has already read it by now.
|
|
371
|
+
if (!(options?.reprepare ?? this._repreparing)) this._resume = undefined
|
|
372
|
+
// The attempt a subclass began before resolving the URL, so its resolution
|
|
373
|
+
// stage and both continuations below share one token. Passed rather than
|
|
374
|
+
// read off the instance: a direct `load()` is itself the outermost call and
|
|
375
|
+
// must start a fresh attempt, superseding whatever was in flight.
|
|
376
|
+
const attempt = options?.attempt ?? this.beginLoadAttempt()
|
|
377
|
+
|
|
378
|
+
if (!playbackSrc) {
|
|
251
379
|
this._loadInProgress = false
|
|
252
380
|
this._pendingSeek = undefined
|
|
253
381
|
const error: PlaybackError = {
|
|
@@ -263,11 +391,10 @@ export class Player {
|
|
|
263
391
|
}
|
|
264
392
|
|
|
265
393
|
player
|
|
266
|
-
.load(
|
|
394
|
+
.load(playbackSrc)
|
|
267
395
|
.then(() => {
|
|
268
|
-
if (
|
|
396
|
+
if (!attempt.isCurrent) return
|
|
269
397
|
this._loadInProgress = false
|
|
270
|
-
this.current = track
|
|
271
398
|
onLoaded?.(track)
|
|
272
399
|
|
|
273
400
|
// Execute any pending seek that arrived during loading
|
|
@@ -282,7 +409,7 @@ export class Player {
|
|
|
282
409
|
}
|
|
283
410
|
})
|
|
284
411
|
.catch((err: unknown) => {
|
|
285
|
-
if (
|
|
412
|
+
if (!attempt.isCurrent) return
|
|
286
413
|
this._loadInProgress = false
|
|
287
414
|
this._pendingSeek = undefined
|
|
288
415
|
this.onError(this.toNormalizedError(err))
|
|
@@ -292,8 +419,14 @@ export class Player {
|
|
|
292
419
|
public stop(onComplete?: () => void): void {
|
|
293
420
|
const player = this.requirePlayer()
|
|
294
421
|
|
|
422
|
+
// Whatever was loading no longer owns the player: its continuations, at
|
|
423
|
+
// either boundary, must not revive it.
|
|
424
|
+
this.cancelLoadAttempt()
|
|
425
|
+
|
|
295
426
|
// Match Android: stop sets playWhenReady=false and state=stopped,
|
|
296
|
-
// but keeps the current track so play() can resume
|
|
427
|
+
// but keeps the current track so play() can resume — from where it
|
|
428
|
+
// stopped, which ExoPlayer preserves across its own stop/prepare pair.
|
|
429
|
+
this.rememberResumePosition()
|
|
297
430
|
this._isStopped = true
|
|
298
431
|
this._loadInProgress = false
|
|
299
432
|
this._pendingSeek = undefined
|
|
@@ -309,37 +442,151 @@ export class Player {
|
|
|
309
442
|
})
|
|
310
443
|
}
|
|
311
444
|
|
|
445
|
+
/**
|
|
446
|
+
* Where playback was when it failed or was stopped, so a re-prepare can
|
|
447
|
+
* resume rather than restart.
|
|
448
|
+
*
|
|
449
|
+
* Kept against the track it belongs to rather than as a bare number, so it
|
|
450
|
+
* self-invalidates when a different track becomes current and cannot be
|
|
451
|
+
* applied to the wrong one. A fresh `load()` clears it; a re-prepare keeps
|
|
452
|
+
* it, so a retry that fails still knows where the next one resumes.
|
|
453
|
+
*/
|
|
454
|
+
private _resume: { track: Track; position: number } | undefined
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Whether the load in progress is a re-prepare of the current item rather
|
|
458
|
+
* than a new one — set by {@link reloadCurrent} for the duration of its
|
|
459
|
+
* `load()` call. A re-prepare keeps the remembered position; every other
|
|
460
|
+
* load resets it.
|
|
461
|
+
*/
|
|
462
|
+
protected _repreparing = false
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Records the current position, when there is one worth recording.
|
|
466
|
+
*
|
|
467
|
+
* A zero is never recorded: `onError` runs for a *load* failure too, where
|
|
468
|
+
* the element has just been torn down and reads 0, and overwriting a good
|
|
469
|
+
* position with that made a failed retry lose the place a successful one
|
|
470
|
+
* would have resumed from.
|
|
471
|
+
*/
|
|
472
|
+
private rememberResumePosition(): void {
|
|
473
|
+
const position = this.element?.currentTime
|
|
474
|
+
const track = this.current
|
|
475
|
+
if (!track || typeof position !== 'number' || position <= 0) return
|
|
476
|
+
this._resume = { track, position }
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Android's `prepare()`, which both `play()` and `retry()` call.
|
|
481
|
+
*
|
|
482
|
+
* It early-returns unless ExoPlayer is STATE_IDLE, so it reconnects after an
|
|
483
|
+
* error or a stop and never re-buffers a healthy stream; `error`/`_isStopped`
|
|
484
|
+
* is web's equivalent of idle. Re-preparing re-resolves the URL, which is the
|
|
485
|
+
* whole point — a signed URL or token may have expired since the failure, and
|
|
486
|
+
* replaying the cached one just fails again. iOS says the same thing in its
|
|
487
|
+
* own `retry()`: "Re-resolve rather than replay the cached URL".
|
|
488
|
+
*
|
|
489
|
+
* Returns whether it handled the call, so `play()` knows not to also poke the
|
|
490
|
+
* element.
|
|
491
|
+
*/
|
|
492
|
+
protected reprepare(): boolean {
|
|
493
|
+
if (!this.current) return false
|
|
494
|
+
if (this.state.state !== State.Error && !this._isStopped) return false
|
|
495
|
+
|
|
496
|
+
// Not for live: `currentTime` there is elapsed time on a connection that no
|
|
497
|
+
// longer exists, and a fresh connection already *is* the live edge. Seeking
|
|
498
|
+
// to the old value asks for a point the new stream does not have. The
|
|
499
|
+
// track's own `live` declaration is the gate, as it is in `seekToLiveEdge`
|
|
500
|
+
// and on both native platforms.
|
|
501
|
+
const remembered =
|
|
502
|
+
this._resume?.track === this.current ? this._resume.position : undefined
|
|
503
|
+
const resume = this.current.live === true ? undefined : remembered
|
|
504
|
+
|
|
505
|
+
this.reloadCurrent(resume)
|
|
506
|
+
return true
|
|
507
|
+
}
|
|
508
|
+
|
|
312
509
|
public play(): void {
|
|
313
510
|
const element = this.requireElement()
|
|
314
511
|
this.playWhenReady = true
|
|
315
512
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
}
|
|
513
|
+
// Matches Android's play(): set the intent, then prepare — which does
|
|
514
|
+
// something only when idle, i.e. after an error or a stop.
|
|
515
|
+
if (this.reprepare()) return
|
|
320
516
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
517
|
+
element.play().catch((err: unknown) => this.onPlayRejected(err))
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* A rejected `element.play()`. Web-only: neither native platform can refuse
|
|
522
|
+
* a play intent, so there is no parity target here.
|
|
523
|
+
*
|
|
524
|
+
* `AbortError` means a newer load interrupted this one — that load owns the
|
|
525
|
+
* state and the intent stands. So does a rejection while a load is still in
|
|
526
|
+
* flight: `load()` then `play()` is the library's own sequence, and until
|
|
527
|
+
* Shaka has a source the element may refuse (Firefox rejects a source-less
|
|
528
|
+
* element with `NotSupportedError`; Chrome leaves it pending). The load
|
|
529
|
+
* calls `play()` again on success, and a real refusal repeats there.
|
|
530
|
+
* Anything else, autoplay policy above all, is the browser refusing: the
|
|
531
|
+
* intent cannot be honoured, so it is cleared and the state settles rather
|
|
532
|
+
* than stranding on `loading`/`buffering`, which is where suppressing
|
|
533
|
+
* `ready` under `playWhenReady` would otherwise leave it.
|
|
534
|
+
*/
|
|
535
|
+
private onPlayRejected(err: unknown): void {
|
|
536
|
+
console.error(err)
|
|
537
|
+
const name =
|
|
538
|
+
typeof err === 'object' && err !== null && 'name' in err
|
|
539
|
+
? (err as { name?: unknown }).name
|
|
540
|
+
: undefined
|
|
541
|
+
if (name === 'AbortError' || this._loadInProgress) return
|
|
326
542
|
|
|
327
|
-
|
|
543
|
+
this.playWhenReady = false
|
|
544
|
+
this.dispatch({ type: 'paused', hasAsset: this.current !== undefined })
|
|
328
545
|
}
|
|
329
546
|
|
|
330
547
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
548
|
+
* The track a reload re-feeds. `current` holds it as the caller queued it —
|
|
549
|
+
* `load()` takes the resolved URL separately, via `LoadOptions.src` — so
|
|
550
|
+
* re-feeding it is safe.
|
|
551
|
+
*
|
|
552
|
+
* The overridable part of {@link reloadCurrent}, rather than the whole
|
|
553
|
+
* method: a subclass backed by a queue changes only where the track comes
|
|
554
|
+
* from, and the reload itself stays in one place.
|
|
335
555
|
*/
|
|
336
|
-
protected
|
|
337
|
-
|
|
556
|
+
protected trackToReload(): Track | undefined {
|
|
557
|
+
return this.current
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Reloads the track {@link trackToReload} names, resuming if asked to. */
|
|
561
|
+
protected reloadCurrent(resumePosition?: number): void {
|
|
562
|
+
const track = this.trackToReload()
|
|
563
|
+
if (!track) return
|
|
564
|
+
this._repreparing = true
|
|
565
|
+
try {
|
|
566
|
+
this.load(
|
|
567
|
+
track,
|
|
568
|
+
resumePosition === undefined
|
|
569
|
+
? undefined
|
|
570
|
+
: () => this.seekTo(resumePosition)
|
|
571
|
+
)
|
|
572
|
+
} finally {
|
|
573
|
+
this._repreparing = false
|
|
574
|
+
}
|
|
338
575
|
}
|
|
339
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Android's `retry()` is `player.prepare()` — the same call `play()` makes,
|
|
579
|
+
* without setting the intent. So this is `play()` minus the intent.
|
|
580
|
+
*
|
|
581
|
+
* It is emphatically not `shaka.Player.retryStreaming()`, which this used to
|
|
582
|
+
* call: that returns false and does nothing at all unless the load mode is
|
|
583
|
+
* MEDIA_SOURCE, so for a progressive file — the case a signed URL is for — it
|
|
584
|
+
* was a silent no-op, while the `play()` the same docs offer as the
|
|
585
|
+
* alternative recovered properly.
|
|
586
|
+
*/
|
|
340
587
|
public retry(): void {
|
|
341
|
-
|
|
342
|
-
|
|
588
|
+
this.requirePlayer()
|
|
589
|
+
this.reprepare()
|
|
343
590
|
}
|
|
344
591
|
|
|
345
592
|
public pause(): void {
|