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
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
/// These stubs declare `Equatable` for test ergonomics — the generated
|
|
6
6
|
/// Nitro types do NOT. Shared `ios/` sources must not rely on conformances
|
|
7
7
|
/// that exist only here: `swift test` compiles, the app build breaks.
|
|
8
|
+
///
|
|
9
|
+
/// Property names and types ARE checked against `nitrogen/generated/ios/swift`
|
|
10
|
+
/// by `src/nitro-stub-drift.test.ts`, which runs in the JS CI job — neither
|
|
11
|
+
/// native job can see both files at once. Declaring fewer properties than the
|
|
12
|
+
/// real type is fine and deliberate; spelling one differently is what that
|
|
13
|
+
/// test catches. Conformances and default values are still on you.
|
|
8
14
|
|
|
9
15
|
struct ArtworkVariants: Equatable {
|
|
10
16
|
var light: String
|
|
@@ -23,19 +29,20 @@
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
struct Track: Equatable {
|
|
26
|
-
var id: String
|
|
27
|
-
var
|
|
32
|
+
var id: String?
|
|
33
|
+
var path: String?
|
|
28
34
|
var src: String?
|
|
35
|
+
var artwork: Variant_String_ArtworkVariants?
|
|
36
|
+
var artworkSource: ImageSource?
|
|
29
37
|
var request: TrackRequest?
|
|
30
38
|
var title: String = ""
|
|
31
39
|
var artist: String?
|
|
32
|
-
var
|
|
40
|
+
var albumPath: String?
|
|
33
41
|
var album: String?
|
|
42
|
+
var style: TrackStyle?
|
|
43
|
+
var disabled: Bool?
|
|
34
44
|
var live: Bool?
|
|
35
|
-
var
|
|
36
|
-
var artworkSource: ImageSource?
|
|
37
|
-
var groupTitle: String?
|
|
38
|
-
var imageRow: [ImageRowItem]?
|
|
45
|
+
var icyCharset: String?
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
struct TrackLoadEvent {
|
|
@@ -63,14 +70,24 @@
|
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
enum HttpMethod {
|
|
74
|
+
case get
|
|
75
|
+
case post
|
|
76
|
+
case put
|
|
77
|
+
case patch
|
|
78
|
+
case delete
|
|
79
|
+
case head
|
|
80
|
+
case options
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
struct ImageSource: Equatable {
|
|
67
84
|
var uri: String
|
|
68
|
-
var method:
|
|
85
|
+
var method: HttpMethod?
|
|
69
86
|
var headers: [String: String]?
|
|
70
87
|
var body: String?
|
|
71
88
|
}
|
|
72
89
|
|
|
73
|
-
enum
|
|
90
|
+
enum StyleDisplay: Equatable {
|
|
74
91
|
case list
|
|
75
92
|
case grid
|
|
76
93
|
|
|
@@ -83,17 +100,105 @@
|
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
enum ArtworkRendering: Equatable {
|
|
104
|
+
case original
|
|
105
|
+
case stencil
|
|
106
|
+
|
|
107
|
+
init?(fromString string: String) {
|
|
108
|
+
switch string {
|
|
109
|
+
case "original": self = .original
|
|
110
|
+
case "stencil": self = .stencil
|
|
111
|
+
default: return nil
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
enum ImageShape: Equatable {
|
|
117
|
+
case circular
|
|
118
|
+
case roundedRectangle
|
|
119
|
+
|
|
120
|
+
init?(fromString string: String) {
|
|
121
|
+
switch string {
|
|
122
|
+
case "circular": self = .circular
|
|
123
|
+
case "rounded-rectangle": self = .roundedRectangle
|
|
124
|
+
default: return nil
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
enum GridTile: Equatable {
|
|
130
|
+
case plain
|
|
131
|
+
case card
|
|
132
|
+
case condensed
|
|
133
|
+
|
|
134
|
+
init?(fromString string: String) {
|
|
135
|
+
switch string {
|
|
136
|
+
case "plain": self = .plain
|
|
137
|
+
case "card": self = .card
|
|
138
|
+
case "condensed": self = .condensed
|
|
139
|
+
default: return nil
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Mirrors the generated extension (nitrogen emits `stringValue` beside
|
|
144
|
+
// `init?(fromString:)` for every string-backed enum).
|
|
145
|
+
var stringValue: String {
|
|
146
|
+
switch self {
|
|
147
|
+
case .plain: "plain"
|
|
148
|
+
case .card: "card"
|
|
149
|
+
case .condensed: "condensed"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
enum CardImage: Equatable {
|
|
155
|
+
case normal
|
|
156
|
+
case background
|
|
157
|
+
|
|
158
|
+
init?(fromString string: String) {
|
|
159
|
+
switch string {
|
|
160
|
+
case "normal": self = .normal
|
|
161
|
+
case "background": self = .background
|
|
162
|
+
default: return nil
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
struct TrackStyle: Equatable {
|
|
168
|
+
var display: StyleDisplay?
|
|
169
|
+
var artworkRendering: ArtworkRendering?
|
|
170
|
+
var imageShape: ImageShape?
|
|
171
|
+
var accessorySymbol: String?
|
|
172
|
+
var cardTint: String?
|
|
173
|
+
var cardImage: CardImage?
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Member order matches the generated init (own properties before inherited
|
|
177
|
+
// ones — Nitro flattens `extends` own-props-first): the memberwise init this
|
|
178
|
+
// produces must accept the same argument order as the real type, or
|
|
179
|
+
// `swift test` passes while the app build breaks on argument order.
|
|
180
|
+
struct SectionStyle: Equatable {
|
|
181
|
+
var gridWrap: Bool?
|
|
182
|
+
var gridTile: GridTile?
|
|
183
|
+
var display: StyleDisplay?
|
|
184
|
+
var artworkRendering: ArtworkRendering?
|
|
185
|
+
var imageShape: ImageShape?
|
|
186
|
+
var accessorySymbol: String?
|
|
187
|
+
var cardTint: String?
|
|
188
|
+
var cardImage: CardImage?
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
struct Section: Equatable {
|
|
192
|
+
var title: String?
|
|
193
|
+
var style: SectionStyle?
|
|
194
|
+
var path: String?
|
|
195
|
+
var children: [Track]
|
|
93
196
|
}
|
|
94
197
|
|
|
95
198
|
struct ResolvedTrack: Equatable {
|
|
96
|
-
var
|
|
199
|
+
var path: String
|
|
200
|
+
var style: SectionStyle?
|
|
201
|
+
var sections: [Section]?
|
|
97
202
|
var children: [Track]?
|
|
98
203
|
var carPlaySiriListButton: CarPlaySiriListButtonPosition?
|
|
99
204
|
var id: String?
|
|
@@ -101,21 +206,18 @@
|
|
|
101
206
|
var artwork: Variant_String_ArtworkVariants?
|
|
102
207
|
var artworkSource: ImageSource?
|
|
103
208
|
var request: TrackRequest?
|
|
104
|
-
var artworkCarPlayTinted: Bool?
|
|
105
209
|
var title: String
|
|
106
210
|
var subtitle: String?
|
|
107
211
|
var artist: String?
|
|
108
|
-
var
|
|
212
|
+
var albumPath: String?
|
|
109
213
|
var album: String?
|
|
110
214
|
var description: String?
|
|
111
215
|
var genre: String?
|
|
112
216
|
var duration: Double?
|
|
113
|
-
var
|
|
114
|
-
var childrenStyle: TrackStyle?
|
|
217
|
+
var disabled: Bool?
|
|
115
218
|
var favorited: Bool?
|
|
116
|
-
var groupTitle: String?
|
|
117
219
|
var live: Bool?
|
|
118
|
-
var
|
|
220
|
+
var icyCharset: String?
|
|
119
221
|
}
|
|
120
222
|
|
|
121
223
|
enum PlaybackState: Equatable {
|
|
@@ -32,6 +32,9 @@ class PlayerUpdateOptions {
|
|
|
32
32
|
/// Custom buttons for the CarPlay Now Playing screen
|
|
33
33
|
var carPlayNowPlayingButtons: [CarPlayNowPlayingButton] = []
|
|
34
34
|
|
|
35
|
+
/// Where the now-playing indicator draws on CarPlay list rows
|
|
36
|
+
var carPlayPlayingIndicatorLocation: CarPlayPlayingIndicatorLocation = .leading
|
|
37
|
+
|
|
35
38
|
// MARK: - Initialization
|
|
36
39
|
|
|
37
40
|
init() {}
|
|
@@ -74,6 +77,9 @@ class PlayerUpdateOptions {
|
|
|
74
77
|
if let buttons = ios.carPlayNowPlayingButtons {
|
|
75
78
|
carPlayNowPlayingButtons = buttons
|
|
76
79
|
}
|
|
80
|
+
if let location = ios.carPlayPlayingIndicatorLocation {
|
|
81
|
+
carPlayPlayingIndicatorLocation = location
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -92,6 +98,7 @@ class PlayerUpdateOptions {
|
|
|
92
98
|
playbackRates: playbackRates,
|
|
93
99
|
carPlayUpNextButton: carPlayUpNextButton,
|
|
94
100
|
carPlayNowPlayingButtons: carPlayNowPlayingButtons,
|
|
101
|
+
carPlayPlayingIndicatorLocation: carPlayPlayingIndicatorLocation,
|
|
95
102
|
),
|
|
96
103
|
)
|
|
97
104
|
}
|
|
@@ -48,6 +48,24 @@ enum RemoteCommand: CustomStringConvertible, Equatable {
|
|
|
48
48
|
|
|
49
49
|
var key: String { description }
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
Commands in `self` that are absent from `next` and so should be disabled.
|
|
53
|
+
|
|
54
|
+
Compared by `key`, not by `==`. `RemoteCommand` is `Equatable` including its
|
|
55
|
+
associated values, but every case with associated values maps onto a single
|
|
56
|
+
`MPRemoteCommand` addressed by `key`: `.skipForward([15])` and
|
|
57
|
+
`.skipForward([30])` are two different values of one command. Diffing by `==`
|
|
58
|
+
would report the old interval as removed and disable the command that the new
|
|
59
|
+
interval had just re-enabled.
|
|
60
|
+
*/
|
|
61
|
+
static func commandsToDisable(
|
|
62
|
+
enabled: [RemoteCommand],
|
|
63
|
+
replacedBy next: [RemoteCommand],
|
|
64
|
+
) -> [RemoteCommand] {
|
|
65
|
+
let nextKeys = Set(next.map(\.key))
|
|
66
|
+
return enabled.filter { !nextKeys.contains($0.key) }
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
/**
|
|
52
70
|
All values in an array for convenience.
|
|
53
71
|
Don't use for associated values.
|
|
@@ -103,10 +103,10 @@ public extension TrackPlayerError.PlaybackError {
|
|
|
103
103
|
static func kind(forHttpStatus status: Int) -> PlaybackErrorKind {
|
|
104
104
|
switch status {
|
|
105
105
|
case 404, 410: .notFound
|
|
106
|
-
case 500...599: .serverError
|
|
106
|
+
case 500 ... 599: .serverError
|
|
107
107
|
// Every other 4xx is the server refusing us — auth, geo-blocking, a
|
|
108
108
|
// rate limit. All of them mean "you can't have this stream", not "retry".
|
|
109
|
-
case 400...499: .rejected
|
|
109
|
+
case 400 ... 499: .rejected
|
|
110
110
|
default: .unknown
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -99,7 +99,7 @@ extension NativeIOSSetupOptions {
|
|
|
99
99
|
category: AVAudioSession.Category,
|
|
100
100
|
mode: AVAudioSession.Mode,
|
|
101
101
|
policy: AVAudioSession.RouteSharingPolicy,
|
|
102
|
-
options: AVAudioSession.CategoryOptions
|
|
102
|
+
options: AVAudioSession.CategoryOptions,
|
|
103
103
|
) {
|
|
104
104
|
let category = self.category
|
|
105
105
|
.flatMap { SessionCategory(rawValue: $0.stringValue) }?
|
|
@@ -113,7 +113,8 @@ extension NativeIOSSetupOptions {
|
|
|
113
113
|
let options = (self.categoryOptions ?? [])
|
|
114
114
|
.reduce(into: AVAudioSession.CategoryOptions()) { acc, option in
|
|
115
115
|
if let mapped = SessionCategoryOptions(rawValue: option.stringValue)?
|
|
116
|
-
.mapConfigToAVAudioSessionCategoryOptions()
|
|
116
|
+
.mapConfigToAVAudioSessionCategoryOptions()
|
|
117
|
+
{
|
|
117
118
|
acc.insert(mapped)
|
|
118
119
|
}
|
|
119
120
|
}
|
|
@@ -43,7 +43,7 @@ public struct MediaIntentCriteria: Sendable {
|
|
|
43
43
|
artist: String? = nil,
|
|
44
44
|
album: String? = nil,
|
|
45
45
|
title: String? = nil,
|
|
46
|
-
playlist: String? = nil
|
|
46
|
+
playlist: String? = nil,
|
|
47
47
|
) {
|
|
48
48
|
self.query = query
|
|
49
49
|
self.reference = reference
|
|
@@ -90,7 +90,7 @@ public struct MediaIntentCriteria: Sendable {
|
|
|
90
90
|
mediaTypeMode: String?,
|
|
91
91
|
reference: Reference,
|
|
92
92
|
hasMediaType: Bool,
|
|
93
|
-
appName: String
|
|
93
|
+
appName: String?,
|
|
94
94
|
) -> MediaIntentCriteria {
|
|
95
95
|
let name = (mediaName ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
96
|
let trimmedNonEmpty: (String?) -> String? = {
|
|
@@ -115,25 +115,33 @@ public struct MediaIntentCriteria: Sendable {
|
|
|
115
115
|
hasGenres: !genreNames.isEmpty,
|
|
116
116
|
hasMediaType: hasMediaType,
|
|
117
117
|
matchesAppName: queryMatchesAppName(query, appName: appName),
|
|
118
|
-
searchMode: mediaTypeMode,
|
|
118
|
+
searchMode: mediaTypeMode, // mode is the vertical, verbatim — no derivation
|
|
119
119
|
genre: genre,
|
|
120
120
|
artist: artist,
|
|
121
121
|
album: album,
|
|
122
122
|
title: title,
|
|
123
|
-
playlist: playlist
|
|
123
|
+
playlist: playlist,
|
|
124
124
|
)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/// Whether `query` is effectively the host app's own name. Case- and
|
|
128
128
|
/// diacritic-insensitive.
|
|
129
|
+
///
|
|
130
|
+
/// Whole-word subset match in either direction. Siri may deliver only part of
|
|
131
|
+
/// the name ("Name" for «App Name»), so a partial match must count — but a
|
|
132
|
+
/// substring match swallowed any query appearing inside the app name, making
|
|
133
|
+
/// one-word stations unreachable by voice.
|
|
129
134
|
private static func queryMatchesAppName(_ query: String, appName: String?) -> Bool {
|
|
130
135
|
guard let appName else { return false }
|
|
131
|
-
let
|
|
132
|
-
|
|
133
|
-
.
|
|
136
|
+
let tokens: (String) -> Set<String> = {
|
|
137
|
+
Set(
|
|
138
|
+
$0.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: nil)
|
|
139
|
+
.split(whereSeparator: { !$0.isLetter && !$0.isNumber })
|
|
140
|
+
.map(String.init),
|
|
141
|
+
)
|
|
134
142
|
}
|
|
135
|
-
let q =
|
|
143
|
+
let q = tokens(query), a = tokens(appName)
|
|
136
144
|
guard !q.isEmpty, !a.isEmpty else { return false }
|
|
137
|
-
return
|
|
145
|
+
return q.isSubset(of: a) || a.isSubset(of: q)
|
|
138
146
|
}
|
|
139
147
|
}
|
|
@@ -38,19 +38,28 @@ final class MediaLoader {
|
|
|
38
38
|
func resolveAndLoad(src: String, track: Track? = nil) {
|
|
39
39
|
if let resolver = mediaUrlResolver {
|
|
40
40
|
mediaResolverTask?.cancel()
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// `[weak self]` like the sibling load tasks below: the resolver is
|
|
42
|
+
// consumer-supplied and `cancelAll()` cannot force an in-flight `await`
|
|
43
|
+
// to return, so a strong capture let a resolver that never settles pin
|
|
44
|
+
// this loader (which also retains the task) indefinitely.
|
|
45
|
+
// `Logger` is a value type, so capturing it directly keeps the logging
|
|
46
|
+
// off `self` — and `self` weak, so a consumer resolver that never settles
|
|
47
|
+
// can't pin the loader (`cancelAll()` can't force an in-flight `await` to
|
|
48
|
+
// return).
|
|
49
|
+
let logger = logger
|
|
50
|
+
mediaResolverTask = Task { [weak self] in
|
|
51
|
+
logger.debug("resolveAndLoad: starting resolution for \(src)")
|
|
43
52
|
|
|
44
53
|
guard !Task.isCancelled else {
|
|
45
|
-
|
|
54
|
+
logger.debug("resolveAndLoad: cancelled before start")
|
|
46
55
|
return
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
logger.debug("resolveAndLoad: calling resolver...")
|
|
50
59
|
let resolved = await resolver(src, track)
|
|
51
60
|
|
|
52
|
-
guard !Task.isCancelled else {
|
|
53
|
-
|
|
61
|
+
guard !Task.isCancelled, let self else {
|
|
62
|
+
logger.debug("resolveAndLoad: cancelled after resolver returned")
|
|
54
63
|
return
|
|
55
64
|
}
|
|
56
65
|
|
|
@@ -96,7 +105,7 @@ final class MediaLoader {
|
|
|
96
105
|
.availableMetadataFormats,
|
|
97
106
|
) else { return }
|
|
98
107
|
|
|
99
|
-
guard
|
|
108
|
+
guard isCurrent(pendingAsset) else { return }
|
|
100
109
|
|
|
101
110
|
if !commonMetadata.isEmpty {
|
|
102
111
|
delegate?.mediaLoaderDidReceiveCommonMetadata(commonMetadata)
|
|
@@ -104,19 +113,21 @@ final class MediaLoader {
|
|
|
104
113
|
|
|
105
114
|
if !chapterLocales.isEmpty {
|
|
106
115
|
for locale in chapterLocales {
|
|
107
|
-
guard
|
|
116
|
+
guard isCurrent(pendingAsset) else { return }
|
|
108
117
|
if let chapters = try? await pendingAsset.loadChapterMetadataGroups(
|
|
109
118
|
withTitleLocale: locale,
|
|
110
119
|
containingItemsWithCommonKeys: [],
|
|
111
120
|
) {
|
|
121
|
+
guard isCurrent(pendingAsset) else { return }
|
|
112
122
|
delegate?.mediaLoaderDidReceiveChapterMetadata(chapters)
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
} else {
|
|
116
126
|
let duration = await (try? pendingAsset.load(.duration)) ?? .zero
|
|
117
127
|
for format in metadataFormats {
|
|
118
|
-
guard
|
|
128
|
+
guard isCurrent(pendingAsset) else { return }
|
|
119
129
|
if let metadata = try? await pendingAsset.loadMetadata(for: format) {
|
|
130
|
+
guard isCurrent(pendingAsset) else { return }
|
|
120
131
|
let timeRange = CMTimeRange(
|
|
121
132
|
start: CMTime(seconds: 0, preferredTimescale: 1000),
|
|
122
133
|
end: duration,
|
|
@@ -159,6 +170,19 @@ final class MediaLoader {
|
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
|
|
173
|
+
/// Whether `candidate` is still the load this loader cares about — checked
|
|
174
|
+
/// after EVERY await, not just once before a loop.
|
|
175
|
+
///
|
|
176
|
+
/// Cancellation alone is not enough: it only sets a flag, and can't abort a
|
|
177
|
+
/// load already in flight inside AVFoundation (only `cancelLoading()` does,
|
|
178
|
+
/// and the track-change path deliberately doesn't call it — the old asset
|
|
179
|
+
/// still backs the playing item at that point). So a load that lands after
|
|
180
|
+
/// the track changed would otherwise attribute the old asset's metadata to
|
|
181
|
+
/// the newly loaded track.
|
|
182
|
+
private func isCurrent(_ candidate: AVURLAsset) -> Bool {
|
|
183
|
+
!Task.isCancelled && candidate == asset
|
|
184
|
+
}
|
|
185
|
+
|
|
162
186
|
func cancelAll() {
|
|
163
187
|
mediaResolverTask?.cancel()
|
|
164
188
|
mediaResolverTask = nil
|
|
@@ -30,17 +30,51 @@ final class NowPlayingUpdater {
|
|
|
30
30
|
let album: String?
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/// Dedupe baseline for `applyFields`. Only meaningful while it describes the
|
|
34
|
+
/// controller's current info dict, so the two are cleared together in
|
|
35
|
+
/// `clearNowPlaying()` — never separately.
|
|
33
36
|
private var lastPublished: Published?
|
|
34
37
|
|
|
38
|
+
/// Empties the now-playing surface and the dedupe baseline that describes it.
|
|
39
|
+
/// One call, because clearing the dict without the baseline makes the next
|
|
40
|
+
/// publish of the SAME track dedupe against a dict that no longer holds it —
|
|
41
|
+
/// artwork lands (never deduped) but the text doesn't, and `onChanged`
|
|
42
|
+
/// never fires.
|
|
43
|
+
func clearNowPlaying() {
|
|
44
|
+
nowPlayingInfoController.clear()
|
|
45
|
+
lastPublished = nil
|
|
46
|
+
formattedTrackId = nil
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
/// Bumped on every `render`; an async formatter result applies only if its render is still the
|
|
36
50
|
/// latest (latest-render-wins — drops a result whose track skipped or whose state moved on while
|
|
37
51
|
/// the formatter was in flight).
|
|
38
52
|
private var renderGeneration: UInt = 0
|
|
39
53
|
|
|
54
|
+
/// The track whose *formatted* line is currently on the surface. While this
|
|
55
|
+
/// matches the rendering track, `render` skips its interim default write — see
|
|
56
|
+
/// the comment at that site. Cleared whenever the surface stops showing a
|
|
57
|
+
/// formatter result (a flash overwrites it, `clearNowPlaying` empties it).
|
|
58
|
+
private var formattedTrackId: String?
|
|
59
|
+
|
|
40
60
|
init(nowPlayingInfoController: NowPlayingInfoController) {
|
|
41
61
|
self.nowPlayingInfoController = nowPlayingInfoController
|
|
42
62
|
}
|
|
43
63
|
|
|
64
|
+
/// Pixel width to request now-playing artwork at: the active scene's screen
|
|
65
|
+
/// width in pixels, capped at `maxArtworkPixelWidth`. With no foreground
|
|
66
|
+
/// scene (headless CarPlay) the cap stands in — every current device exceeds
|
|
67
|
+
/// it anyway, and this only sizes a CDN request.
|
|
68
|
+
private static var artworkPixelWidth: CGFloat {
|
|
69
|
+
guard let screen = UIApplication.shared.activeWindowScene?.screen else {
|
|
70
|
+
return maxArtworkPixelWidth
|
|
71
|
+
}
|
|
72
|
+
return min(screen.bounds.width * screen.scale, maxArtworkPixelWidth)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Mirrored by the Kotlin twin's `artworkSizePx`.
|
|
76
|
+
private static let maxArtworkPixelWidth: CGFloat = 1200
|
|
77
|
+
|
|
44
78
|
/// Publishes the static, per-track fields + artwork on a track change. The title/artist/album
|
|
45
79
|
/// lines are owned by `render` (routed through `applyFields` here so it dedupes against later
|
|
46
80
|
/// renders).
|
|
@@ -75,6 +109,8 @@ final class NowPlayingUpdater {
|
|
|
75
109
|
let defaultSecondary = override?.artist ?? track.artist
|
|
76
110
|
let defaultAlbum = override?.album ?? track.album
|
|
77
111
|
|
|
112
|
+
let trackId = track.identity ?? track.path
|
|
113
|
+
|
|
78
114
|
if let flash {
|
|
79
115
|
applyFields(
|
|
80
116
|
track: track,
|
|
@@ -82,10 +118,22 @@ final class NowPlayingUpdater {
|
|
|
82
118
|
artist: flash.artist ?? defaultSecondary,
|
|
83
119
|
album: flash.album ?? defaultAlbum,
|
|
84
120
|
)
|
|
121
|
+
// The flash replaced the formatter's line, so the surface no longer shows
|
|
122
|
+
// one: the render that follows the flash reverting must publish the default
|
|
123
|
+
// immediately rather than sit on stale flash text awaiting the formatter.
|
|
124
|
+
formattedTrackId = nil
|
|
85
125
|
return
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
|
|
128
|
+
// Publish the raw default only while no formatter result is on screen for this
|
|
129
|
+
// track. `render` runs on EVERY playback transition (state change, playing/
|
|
130
|
+
// buffering flip, timed-metadata tick, connectivity change); writing the
|
|
131
|
+
// default each time would flash the track title over the formatter's line
|
|
132
|
+
// until the async result lands, since the two differ by construction on a live
|
|
133
|
+
// stream (station name vs. song line).
|
|
134
|
+
if formatter == nil || formattedTrackId != trackId {
|
|
135
|
+
applyFields(track: track, title: defaultTitle, artist: defaultSecondary, album: defaultAlbum)
|
|
136
|
+
}
|
|
89
137
|
|
|
90
138
|
guard let formatter else { return }
|
|
91
139
|
let params = FormatNowPlayingParams(
|
|
@@ -111,6 +159,9 @@ final class NowPlayingUpdater {
|
|
|
111
159
|
artist: formatted.artist ?? defaultSecondary,
|
|
112
160
|
album: formatted.album ?? defaultAlbum,
|
|
113
161
|
)
|
|
162
|
+
// A formatter result now owns the surface for this track, so later
|
|
163
|
+
// renders skip the interim default write.
|
|
164
|
+
self.formattedTrackId = trackId
|
|
114
165
|
}
|
|
115
166
|
}
|
|
116
167
|
.catch { @Sendable [weak self] error in
|
|
@@ -130,7 +181,7 @@ final class NowPlayingUpdater {
|
|
|
130
181
|
let resolvedArtist = artist ?? track.artist
|
|
131
182
|
let resolvedAlbum = album ?? track.album
|
|
132
183
|
let published = Published(
|
|
133
|
-
trackId: track.
|
|
184
|
+
trackId: track.identity ?? track.path,
|
|
134
185
|
title: resolvedTitle,
|
|
135
186
|
artist: resolvedArtist,
|
|
136
187
|
album: resolvedAlbum,
|
|
@@ -152,10 +203,7 @@ final class NowPlayingUpdater {
|
|
|
152
203
|
let artworkUrl = track.artworkSource?.uri ?? track.artwork?.url
|
|
153
204
|
logger.debug("loadArtwork: \(track.title), artworkUrl: \(artworkUrl ?? "nil")")
|
|
154
205
|
|
|
155
|
-
|
|
156
|
-
let screenScale = UIScreen.main.scale
|
|
157
|
-
let screenWidth = UIScreen.main.bounds.width * screenScale
|
|
158
|
-
let artworkSize = min(screenWidth, 1200)
|
|
206
|
+
let artworkSize = Self.artworkPixelWidth
|
|
159
207
|
let nowPlayingSize = ImageContext(width: artworkSize, height: artworkSize)
|
|
160
208
|
|
|
161
209
|
artworkLoadTask?.cancel()
|
|
@@ -526,14 +526,21 @@ class PlaybackCoordinator {
|
|
|
526
526
|
let changed = queue.add([track], initialIndex: 0)
|
|
527
527
|
if changed { handleCurrentTrackChanged() }
|
|
528
528
|
} else {
|
|
529
|
-
|
|
530
|
-
|
|
529
|
+
// `currentIndex` is invariantly -1 or in bounds, and -1 took the branch
|
|
530
|
+
// above, so this cannot fail — but surface it rather than reloading the
|
|
531
|
+
// old track if that invariant is ever broken.
|
|
532
|
+
do {
|
|
533
|
+
try queue.replace(queue.currentIndex, track)
|
|
534
|
+
handleCurrentTrackChanged()
|
|
535
|
+
} catch {
|
|
536
|
+
logger.error("load: replace at \(self.queue.currentIndex) failed: \(error)")
|
|
537
|
+
}
|
|
531
538
|
}
|
|
532
539
|
}
|
|
533
540
|
}
|
|
534
541
|
|
|
535
|
-
func replace(_ index: Int, _ track: Track) {
|
|
536
|
-
queue.replace(index, track)
|
|
542
|
+
func replace(_ index: Int, _ track: Track) throws {
|
|
543
|
+
try queue.replace(index, track)
|
|
537
544
|
}
|
|
538
545
|
|
|
539
546
|
func setQueue(
|
|
@@ -691,14 +698,14 @@ class PlaybackCoordinator {
|
|
|
691
698
|
guard let src = currentTrack.src else {
|
|
692
699
|
logger.error("Failed to load track - no src")
|
|
693
700
|
logger.error(" track.title: \(currentTrack.title)")
|
|
694
|
-
logger.error(" track.
|
|
701
|
+
logger.error(" track.path: \(currentTrack.path ?? "nil")")
|
|
695
702
|
effectHandler?.clearCurrentItem()
|
|
696
703
|
transition(.errorOccurred(.invalidSourceUrl("nil")))
|
|
697
704
|
return
|
|
698
705
|
}
|
|
699
706
|
|
|
700
707
|
logger.debug("Loading track: \(currentTrack.title)")
|
|
701
|
-
logger.debug(" track.
|
|
708
|
+
logger.debug(" track.path: \(currentTrack.path ?? "nil")")
|
|
702
709
|
logger.debug(" track.src: \(src)")
|
|
703
710
|
|
|
704
711
|
effectHandler?.loadTrack(src: src, track: currentTrack)
|
|
@@ -8,7 +8,7 @@ import os.log
|
|
|
8
8
|
var isEnabled: Bool { get }
|
|
9
9
|
/// Whether the current load has produced audio — selects the retry budget.
|
|
10
10
|
var hasPlayed: Bool { get set }
|
|
11
|
-
func isRetryable(_ error: Error?) -> Bool
|
|
11
|
+
func isRetryable(_ error: Error?, httpStatusCode: Int?) -> Bool
|
|
12
12
|
func attemptRetry(startFromCurrentTime: Bool) async -> Bool
|
|
13
13
|
func reset()
|
|
14
14
|
}
|
|
@@ -62,7 +62,7 @@ public enum PlaybackErrorContext {
|
|
|
62
62
|
logger.error("[\(String(describing: context))] failure with nil error")
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if retryHandler.isEnabled, retryHandler.isRetryable(error) {
|
|
65
|
+
if retryHandler.isEnabled, retryHandler.isRetryable(error, httpStatusCode: httpStatusCode) {
|
|
66
66
|
onRetryingError?(PlaybackErrorHandler.classify(
|
|
67
67
|
error: error, fallback: context.fallbackError, httpStatusCode: httpStatusCode,
|
|
68
68
|
))
|
|
@@ -138,7 +138,7 @@ public enum PlaybackErrorContext {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
if let httpStatusCode, !(200...299).contains(httpStatusCode) {
|
|
141
|
+
if let httpStatusCode, !(200 ... 299).contains(httpStatusCode) {
|
|
142
142
|
return .httpStatus(httpStatusCode)
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -155,7 +155,7 @@ public enum PlaybackErrorContext {
|
|
|
155
155
|
/// with HTTP statuses — so only in-range values count, and the newest wins:
|
|
156
156
|
/// a live stream reconnects, and older entries describe earlier attempts.
|
|
157
157
|
public static func httpStatusCode(fromErrorStatusCodes codes: [Int]) -> Int? {
|
|
158
|
-
codes.last { (100...599).contains($0) }
|
|
158
|
+
codes.last { (100 ... 599).contains($0) }
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/// The stream was fetched but cannot be decoded — unknown container,
|
|
@@ -48,10 +48,16 @@ final class PlaybackTimer {
|
|
|
48
48
|
|
|
49
49
|
private func start(_ interval: TimeInterval) {
|
|
50
50
|
guard timer == nil else { return }
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// `.common`, not the `.default` mode `Timer.scheduledTimer` installs: UIKit
|
|
52
|
+
// runs the main run loop in tracking mode while a list is being dragged, and
|
|
53
|
+
// a default-mode timer does not fire there. Missed repeats are dropped rather
|
|
54
|
+
// than replayed, so progress events would freeze and interval ticks would be
|
|
55
|
+
// lost for as long as the user scrolls. (VolumeFader already does this.)
|
|
56
|
+
let timer = Timer(timeInterval: interval, repeats: true) { [weak self] _ in
|
|
53
57
|
MainActor.assumeIsolated { self?.onTick() }
|
|
54
58
|
}
|
|
59
|
+
self.timer = timer
|
|
60
|
+
RunLoop.main.add(timer, forMode: .common)
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
private func stop() {
|