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
|
@@ -246,7 +246,9 @@ struct ResetTests {
|
|
|
246
246
|
c.capture(position: 25.0)
|
|
247
247
|
|
|
248
248
|
// Let the (immediately-cancelled, itemless) seek completion drain.
|
|
249
|
-
for _ in 0 ..< 20 {
|
|
249
|
+
for _ in 0 ..< 20 {
|
|
250
|
+
await Task.yield()
|
|
251
|
+
}
|
|
250
252
|
|
|
251
253
|
#expect(spy.calls.isEmpty)
|
|
252
254
|
#expect(c.pendingTime == 25.0)
|
|
@@ -57,12 +57,12 @@ struct MediaIntentCriteriaFromTests {
|
|
|
57
57
|
mediaTypeMode: String? = nil,
|
|
58
58
|
reference: MediaIntentCriteria.Reference = .unknown,
|
|
59
59
|
hasMediaType: Bool = false,
|
|
60
|
-
appName: String? = "Radio Garden"
|
|
60
|
+
appName: String? = "Radio Garden",
|
|
61
61
|
) -> MediaIntentCriteria {
|
|
62
62
|
MediaIntentCriteria.from(
|
|
63
63
|
mediaName: mediaName, genreNames: genreNames, artistName: artistName,
|
|
64
64
|
albumName: albumName, mediaTypeMode: mediaTypeMode,
|
|
65
|
-
reference: reference, hasMediaType: hasMediaType, appName: appName
|
|
65
|
+
reference: reference, hasMediaType: hasMediaType, appName: appName,
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -120,7 +120,7 @@ struct TransitionTests {
|
|
|
120
120
|
@MainActor
|
|
121
121
|
private func makeRetrying() -> (
|
|
122
122
|
PlaybackCoordinator, MockRetryHandling, MockPlaybackCoordinatorCallbacks,
|
|
123
|
-
MockPlaybackEffectHandler
|
|
123
|
+
MockPlaybackEffectHandler,
|
|
124
124
|
) {
|
|
125
125
|
let retryHandler = MockRetryHandling()
|
|
126
126
|
retryHandler.isRetryableResult = true
|
|
@@ -14,9 +14,11 @@ private final class MockRetryHandler: RetryHandling {
|
|
|
14
14
|
var attemptRetryResult = false
|
|
15
15
|
var attemptRetryCallCount = 0
|
|
16
16
|
var lastStartFromCurrentTime: Bool?
|
|
17
|
+
var lastHTTPStatusCode: Int??
|
|
17
18
|
var resetCallCount = 0
|
|
18
19
|
|
|
19
|
-
func isRetryable(_ error: Error?) -> Bool {
|
|
20
|
+
func isRetryable(_ error: Error?, httpStatusCode: Int?) -> Bool {
|
|
21
|
+
lastHTTPStatusCode = .some(httpStatusCode)
|
|
20
22
|
guard let error else { return false }
|
|
21
23
|
let code = (error as NSError).code
|
|
22
24
|
return retryableErrors.contains(code)
|
|
@@ -14,10 +14,11 @@ struct PlaybackStateStoreTests {
|
|
|
14
14
|
|
|
15
15
|
private func sampleTrack() -> JsonTrack {
|
|
16
16
|
JsonTrack(
|
|
17
|
-
id: "abc",
|
|
18
|
-
artist: "City, Country",
|
|
17
|
+
id: "abc", path: nil, title: "Test FM", subtitle: nil, artwork: nil,
|
|
18
|
+
artist: "City, Country", albumPath: nil, album: nil, description: nil,
|
|
19
19
|
genre: nil, duration: nil, src: "/listen/abc/channel.mp3", request: nil,
|
|
20
|
-
style:
|
|
20
|
+
style: JsonStyle(display: "grid", artworkRendering: "stencil"),
|
|
21
|
+
disabled: false, live: true, icyCharset: "windows-1251",
|
|
21
22
|
)
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
@testable import AudioBrowserTestable
|
|
5
|
+
|
|
6
|
+
@MainActor
|
|
7
|
+
@Suite("PlayerTimeObserver")
|
|
8
|
+
struct PlayerTimeObserverTests {
|
|
9
|
+
/// An AVPlayer retains its time observers, so one left registered outlives both this object and
|
|
10
|
+
/// the caller's interest in it (#96).
|
|
11
|
+
@Test func unregisteringDropsTheToken() {
|
|
12
|
+
// Held: `avPlayer` is weak, so a temporary is gone before the next line.
|
|
13
|
+
let player = AVPlayer()
|
|
14
|
+
let observer = PlayerTimeObserver(onAudioDidStart: {})
|
|
15
|
+
observer.avPlayer = player
|
|
16
|
+
|
|
17
|
+
observer.registerForBoundaryTimeEvents()
|
|
18
|
+
#expect(observer.boundaryTimeStartObserverToken != nil)
|
|
19
|
+
|
|
20
|
+
observer.unregisterForBoundaryTimeEvents()
|
|
21
|
+
#expect(observer.boundaryTimeStartObserverToken == nil)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Swapping players has to unregister from the outgoing one — it is a different AVPlayer, and
|
|
25
|
+
/// the token cannot be removed from it afterwards.
|
|
26
|
+
@Test func swappingPlayersUnregistersFromThePrevious() {
|
|
27
|
+
let first = AVPlayer()
|
|
28
|
+
let second = AVPlayer()
|
|
29
|
+
let observer = PlayerTimeObserver(onAudioDidStart: {})
|
|
30
|
+
observer.avPlayer = first
|
|
31
|
+
observer.registerForBoundaryTimeEvents()
|
|
32
|
+
|
|
33
|
+
observer.avPlayer = second
|
|
34
|
+
|
|
35
|
+
#expect(observer.boundaryTimeStartObserverToken == nil)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -74,6 +74,87 @@ struct NextTests {
|
|
|
74
74
|
#expect(q.currentIndex == 0)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/// The start track must be first in the shuffle order, not wherever the permutation put it —
|
|
78
|
+
/// otherwise auto-advance ends the queue as soon as it is reached (#96).
|
|
79
|
+
@Test func shuffle_startTrackLeadsTheOrder() {
|
|
80
|
+
for start in 0 ..< 6 {
|
|
81
|
+
let q = QueueManager()
|
|
82
|
+
q.setQueue(tracks("a", "b", "c", "d", "e", "f"), initialIndex: start)
|
|
83
|
+
q.shuffleEnabled = true
|
|
84
|
+
|
|
85
|
+
#expect(q.shuffleOrder.isFirst(start))
|
|
86
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
87
|
+
#expect(q.nextTracks.count == 5)
|
|
88
|
+
#expect(q.previousTracks.isEmpty)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Same hazard on enabling shuffle mid-queue: the existing order has the current track wherever
|
|
93
|
+
/// it landed, so without re-pinning, turning shuffle on can immediately report end-of-queue.
|
|
94
|
+
@Test func shuffle_enablingMidQueueLeadsFromCurrent() {
|
|
95
|
+
for start in 0 ..< 6 {
|
|
96
|
+
let q = QueueManager()
|
|
97
|
+
q.setQueue(tracks("a", "b", "c", "d", "e", "f"))
|
|
98
|
+
_ = try? q.skipTo(start)
|
|
99
|
+
|
|
100
|
+
q.shuffleEnabled = true
|
|
101
|
+
|
|
102
|
+
#expect(q.shuffleOrder.isFirst(start))
|
|
103
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
104
|
+
#expect(q.nextTracks.count == 5)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Re-pinning happens on enable only — flipping it off and on again must not renege on the
|
|
109
|
+
/// order mid-listen, and a redundant `= true` must not reshuffle under the listener.
|
|
110
|
+
@Test func shuffle_redundantEnableKeepsTheOrder() {
|
|
111
|
+
let q = QueueManager()
|
|
112
|
+
q.setQueue(tracks("a", "b", "c", "d", "e", "f"))
|
|
113
|
+
q.shuffleEnabled = true
|
|
114
|
+
let order = q.shuffleOrder.shuffled
|
|
115
|
+
|
|
116
|
+
q.shuffleEnabled = true
|
|
117
|
+
#expect(q.shuffleOrder.shuffled == order)
|
|
118
|
+
|
|
119
|
+
q.shuffleEnabled = false
|
|
120
|
+
#expect(q.shuffleOrder.shuffled == order)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// A queue reorder permutes positions, not identities, so it must leave the
|
|
124
|
+
/// shuffled playback order alone. Re-inserting at a random shuffle position
|
|
125
|
+
/// would silently reshuffle under the listener.
|
|
126
|
+
@Test func shuffle_moveKeepsThePlaybackOrder() {
|
|
127
|
+
for target in 0 ..< 5 {
|
|
128
|
+
let q = QueueManager()
|
|
129
|
+
q.setQueue(tracks("a", "b", "c", "d", "e"))
|
|
130
|
+
q.shuffleEnabled = true
|
|
131
|
+
let before = q.shuffleOrder.shuffled.map { q.tracks[$0].id }
|
|
132
|
+
|
|
133
|
+
_ = try? q.move(fromIndex: 0, toIndex: target)
|
|
134
|
+
|
|
135
|
+
#expect(q.shuffleOrder.shuffled.map { q.tracks[$0].id } == before)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// The same hazard as `shuffle_startTrackLeadsTheOrder`, reached by moving the
|
|
140
|
+
/// *playing* track: a random re-insert can drop it last in the shuffle order,
|
|
141
|
+
/// which reads as `isLastInPlaybackOrder` and ends the queue at the next track
|
|
142
|
+
/// boundary with tracks still unplayed.
|
|
143
|
+
@Test func shuffle_movingCurrentTrackDoesNotEndTheQueue() {
|
|
144
|
+
for target in 1 ..< 6 {
|
|
145
|
+
let q = QueueManager()
|
|
146
|
+
q.setQueue(tracks("a", "b", "c", "d", "e", "f"), initialIndex: 0)
|
|
147
|
+
q.shuffleEnabled = true // pins "a" first in the order
|
|
148
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
149
|
+
|
|
150
|
+
_ = try? q.move(fromIndex: 0, toIndex: target)
|
|
151
|
+
|
|
152
|
+
#expect(q.currentTrack?.id == "a")
|
|
153
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
154
|
+
#expect(q.nextTracks.count == 5)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
77
158
|
@Test func shuffle_visitsAllTracks() {
|
|
78
159
|
let q = QueueManager()
|
|
79
160
|
q.setQueue(tracks("a", "b", "c", "d"))
|
|
@@ -336,6 +417,25 @@ struct AddTests {
|
|
|
336
417
|
#expect(q.tracks[2].id == "c")
|
|
337
418
|
#expect(q.tracks[3].id == "d")
|
|
338
419
|
}
|
|
420
|
+
|
|
421
|
+
/// Filling an empty queue must pin the start track to the head of the shuffle
|
|
422
|
+
/// order, exactly as `setQueue` does — `insert(at:count:)` places new indices
|
|
423
|
+
/// at random positions, so without the pin the start track can land last and
|
|
424
|
+
/// the queue reports end-of-queue as soon as it finishes. `shuffleEnabled`'s
|
|
425
|
+
/// didSet only re-pins on the off→on edge, so shuffle-already-on gets nothing
|
|
426
|
+
/// from it.
|
|
427
|
+
@Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() {
|
|
428
|
+
for start in 0 ..< 6 {
|
|
429
|
+
let q = QueueManager()
|
|
430
|
+
q.shuffleEnabled = true
|
|
431
|
+
q.add(tracks("a", "b", "c", "d", "e", "f"), initialIndex: start)
|
|
432
|
+
|
|
433
|
+
#expect(q.shuffleOrder.isFirst(start))
|
|
434
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
435
|
+
#expect(q.nextTracks.count == 5)
|
|
436
|
+
#expect(q.previousTracks.isEmpty)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
339
439
|
}
|
|
340
440
|
|
|
341
441
|
// MARK: - Mutations: addAt()
|
|
@@ -368,6 +468,18 @@ struct AddAtTests {
|
|
|
368
468
|
#expect(q.currentIndex == 0)
|
|
369
469
|
}
|
|
370
470
|
|
|
471
|
+
/// Same pinning requirement as `add()` into an empty queue — see its twin.
|
|
472
|
+
@Test func toEmptyQueue_withShuffleAlreadyOn_startTrackLeadsTheOrder() throws {
|
|
473
|
+
let q = QueueManager()
|
|
474
|
+
q.shuffleEnabled = true
|
|
475
|
+
try q.addAt(tracks("a", "b", "c", "d", "e", "f"), at: 0)
|
|
476
|
+
|
|
477
|
+
#expect(q.shuffleOrder.isFirst(0))
|
|
478
|
+
#expect(!q.isLastInPlaybackOrder)
|
|
479
|
+
#expect(q.nextTracks.count == 5)
|
|
480
|
+
#expect(q.previousTracks.isEmpty)
|
|
481
|
+
}
|
|
482
|
+
|
|
371
483
|
@Test func invalidIndex_throws() {
|
|
372
484
|
let q = QueueManager()
|
|
373
485
|
q.setQueue(tracks("a"))
|
|
@@ -645,10 +757,10 @@ struct ClearTests {
|
|
|
645
757
|
@Suite("replace & removeUpcomingTracks")
|
|
646
758
|
@MainActor
|
|
647
759
|
struct OtherMutationTests {
|
|
648
|
-
@Test func replace_updatesTrackAtIndex() {
|
|
760
|
+
@Test func replace_updatesTrackAtIndex() throws {
|
|
649
761
|
let q = QueueManager()
|
|
650
762
|
q.setQueue(tracks("a", "b", "c"))
|
|
651
|
-
q.replace(1, Track(id: "B"))
|
|
763
|
+
try q.replace(1, Track(id: "B"))
|
|
652
764
|
#expect(q.tracks[1].id == "B")
|
|
653
765
|
}
|
|
654
766
|
|
|
@@ -692,10 +804,10 @@ struct OtherMutationTests {
|
|
|
692
804
|
#expect(q.tracks[0].id == "a")
|
|
693
805
|
}
|
|
694
806
|
|
|
695
|
-
@Test func replace_currentTrack_reflectsInCurrentTrack() {
|
|
807
|
+
@Test func replace_currentTrack_reflectsInCurrentTrack() throws {
|
|
696
808
|
let q = QueueManager()
|
|
697
809
|
q.setQueue(tracks("a", "b", "c"), initialIndex: 1)
|
|
698
|
-
q.replace(1, Track(id: "B"))
|
|
810
|
+
try q.replace(1, Track(id: "B"))
|
|
699
811
|
#expect(q.currentTrack?.id == "B")
|
|
700
812
|
#expect(q.currentIndex == 1)
|
|
701
813
|
}
|
|
@@ -767,13 +879,13 @@ struct ComputedPropertyTests {
|
|
|
767
879
|
let q = QueueManager()
|
|
768
880
|
q.setQueue(tracks("a", "b", "c", "d"))
|
|
769
881
|
q.shuffleEnabled = true
|
|
770
|
-
let nextFromProp = q.nextTracks.map
|
|
882
|
+
let nextFromProp = q.nextTracks.map { $0.id! }
|
|
771
883
|
// Navigate forward only as many times as nextTracks reports
|
|
772
884
|
// (nextTracks does not include wrapped tracks)
|
|
773
885
|
var nextFromNav: [String] = []
|
|
774
886
|
for _ in 0 ..< nextFromProp.count {
|
|
775
887
|
_ = q.next()
|
|
776
|
-
nextFromNav.append(q.currentTrack!.id)
|
|
888
|
+
nextFromNav.append(q.currentTrack!.id!)
|
|
777
889
|
}
|
|
778
890
|
#expect(nextFromProp == nextFromNav)
|
|
779
891
|
}
|
|
@@ -786,12 +898,12 @@ struct ComputedPropertyTests {
|
|
|
786
898
|
for _ in 0 ..< 3 {
|
|
787
899
|
_ = q.next()
|
|
788
900
|
}
|
|
789
|
-
let prevFromProp = q.previousTracks.map
|
|
901
|
+
let prevFromProp = q.previousTracks.map { $0.id! }
|
|
790
902
|
// Navigate backward the same number of times
|
|
791
903
|
var prevFromNav: [String] = []
|
|
792
904
|
for _ in 0 ..< prevFromProp.count {
|
|
793
905
|
_ = q.previous()
|
|
794
|
-
prevFromNav.insert(q.currentTrack!.id
|
|
906
|
+
prevFromNav.insert(q.currentTrack!.id!, at: 0)
|
|
795
907
|
}
|
|
796
908
|
#expect(prevFromProp == prevFromNav)
|
|
797
909
|
}
|
|
@@ -880,12 +992,12 @@ struct DelegateTests {
|
|
|
880
992
|
#expect(spy.calls.count == 1)
|
|
881
993
|
}
|
|
882
994
|
|
|
883
|
-
@Test func queueDidChangeTracks_firesOnReplace() {
|
|
995
|
+
@Test func queueDidChangeTracks_firesOnReplace() throws {
|
|
884
996
|
let q = QueueManager()
|
|
885
997
|
q.setQueue(tracks("a", "b"))
|
|
886
998
|
let spy = DelegateSpy()
|
|
887
999
|
q.delegate = spy
|
|
888
|
-
q.replace(0, Track(id: "A"))
|
|
1000
|
+
try q.replace(0, Track(id: "A"))
|
|
889
1001
|
#expect(spy.calls.count == 1)
|
|
890
1002
|
}
|
|
891
1003
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import MediaPlayer
|
|
3
|
+
import Testing
|
|
4
|
+
|
|
5
|
+
@testable import AudioBrowserTestable
|
|
6
|
+
|
|
7
|
+
/// Exercises the controller against the real `MPRemoteCommandCenter`.
|
|
8
|
+
///
|
|
9
|
+
/// `MPRemoteCommandCenter`'s `init()` is unavailable, so `shared()` — a
|
|
10
|
+
/// process-wide singleton — is the only center available. The suite is
|
|
11
|
+
/// `.serialized` because of that shared state, and every test resets the
|
|
12
|
+
/// commands it touches through `reset()`.
|
|
13
|
+
@Suite("RemoteCommandController", .serialized)
|
|
14
|
+
@MainActor
|
|
15
|
+
struct RemoteCommandControllerTests {
|
|
16
|
+
final class SpyCallbacks: RemoteCommandCallbacks {
|
|
17
|
+
var events: [String] = []
|
|
18
|
+
func remotePlay() { events.append("play") }
|
|
19
|
+
func remotePause() { events.append("pause") }
|
|
20
|
+
func remoteStop() { events.append("stop") }
|
|
21
|
+
func remotePlayPause() { events.append("playPause") }
|
|
22
|
+
func remoteNext() { events.append("next") }
|
|
23
|
+
func remotePrevious() { events.append("previous") }
|
|
24
|
+
func remoteJumpForward(interval: Double) { events.append("jumpForward:\(interval)") }
|
|
25
|
+
func remoteJumpBackward(interval: Double) { events.append("jumpBackward:\(interval)") }
|
|
26
|
+
func remoteSeek(position: Double) { events.append("seek:\(position)") }
|
|
27
|
+
func remoteChangeRepeatMode(mode: RepeatMode) { events.append("repeat:\(mode)") }
|
|
28
|
+
func remoteChangeShuffleMode(enabled: Bool) { events.append("shuffle:\(enabled)") }
|
|
29
|
+
func remoteChangePlaybackRate(rate: Float) { events.append("rate:\(rate)") }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Clears every command on the shared center so a test starts from a known
|
|
33
|
+
/// state regardless of what ran before it.
|
|
34
|
+
private func makeController() -> (RemoteCommandController, SpyCallbacks) {
|
|
35
|
+
let center = MPRemoteCommandCenter.shared()
|
|
36
|
+
let reset = RemoteCommandController(remoteCommandCenter: center)
|
|
37
|
+
reset.enable(commands: RemoteCommand.all())
|
|
38
|
+
reset.disableAll()
|
|
39
|
+
|
|
40
|
+
let spy = SpyCallbacks()
|
|
41
|
+
return (RemoteCommandController(remoteCommandCenter: center, callbacks: spy), spy)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// The reported bug: a changed jump interval used to disable the very command
|
|
45
|
+
/// it had just re-enabled, removing the skip button from the lock screen,
|
|
46
|
+
/// Control Center and CarPlay while `enabledCommands` still reported it on.
|
|
47
|
+
@Test func changedJumpIntervalKeepsCommandEnabled() {
|
|
48
|
+
let (controller, _) = makeController()
|
|
49
|
+
let center = MPRemoteCommandCenter.shared()
|
|
50
|
+
|
|
51
|
+
controller.enable(commands: [
|
|
52
|
+
.play, .skipForward(preferredIntervals: [15]), .skipBackward(preferredIntervals: [15]),
|
|
53
|
+
])
|
|
54
|
+
#expect(center.skipForwardCommand.isEnabled)
|
|
55
|
+
|
|
56
|
+
controller.enable(commands: [
|
|
57
|
+
.play, .skipForward(preferredIntervals: [30]), .skipBackward(preferredIntervals: [30]),
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
#expect(center.skipForwardCommand.isEnabled)
|
|
61
|
+
#expect(center.skipBackwardCommand.isEnabled)
|
|
62
|
+
#expect(controller.commandTargetPointers["skipForward"] != nil)
|
|
63
|
+
#expect(controller.commandTargetPointers["skipBackward"] != nil)
|
|
64
|
+
#expect(center.skipForwardCommand.preferredIntervals == [30])
|
|
65
|
+
#expect(center.skipBackwardCommand.preferredIntervals == [30])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Test func changedPlaybackRatesKeepCommandEnabled() {
|
|
69
|
+
let (controller, _) = makeController()
|
|
70
|
+
let center = MPRemoteCommandCenter.shared()
|
|
71
|
+
|
|
72
|
+
controller.enable(commands: [.changePlaybackRate(supportedPlaybackRates: [1.0])])
|
|
73
|
+
controller.enable(commands: [.changePlaybackRate(supportedPlaybackRates: [1.0, 1.5, 2.0])])
|
|
74
|
+
|
|
75
|
+
#expect(center.changePlaybackRateCommand.isEnabled)
|
|
76
|
+
#expect(controller.commandTargetPointers["changePlaybackRate"] != nil)
|
|
77
|
+
#expect(center.changePlaybackRateCommand.supportedPlaybackRates == [1.0, 1.5, 2.0])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Test func removedCommandIsDisabledAndUntargeted() {
|
|
81
|
+
let (controller, _) = makeController()
|
|
82
|
+
let center = MPRemoteCommandCenter.shared()
|
|
83
|
+
|
|
84
|
+
controller.enable(commands: [.play, .pause, .next])
|
|
85
|
+
#expect(center.nextTrackCommand.isEnabled)
|
|
86
|
+
|
|
87
|
+
controller.enable(commands: [.play, .pause])
|
|
88
|
+
|
|
89
|
+
#expect(!center.nextTrackCommand.isEnabled)
|
|
90
|
+
#expect(controller.commandTargetPointers["nextTrack"] == nil)
|
|
91
|
+
#expect(center.playCommand.isEnabled)
|
|
92
|
+
#expect(controller.commandTargetPointers["play"] != nil)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test func disableAllClearsEveryTarget() {
|
|
96
|
+
let (controller, _) = makeController()
|
|
97
|
+
let center = MPRemoteCommandCenter.shared()
|
|
98
|
+
|
|
99
|
+
controller.enable(commands: [.play, .pause, .skipForward(preferredIntervals: [15])])
|
|
100
|
+
controller.disableAll()
|
|
101
|
+
|
|
102
|
+
#expect(controller.commandTargetPointers.isEmpty)
|
|
103
|
+
#expect(!center.playCommand.isEnabled)
|
|
104
|
+
#expect(!center.pauseCommand.isEnabled)
|
|
105
|
+
#expect(!center.skipForwardCommand.isEnabled)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Re-enabling the same set must not accumulate handlers: `enableRemoteCommand`
|
|
109
|
+
/// removes the previous target before adding the new one.
|
|
110
|
+
@Test func reEnablingKeepsOneTargetPerCommand() {
|
|
111
|
+
let (controller, _) = makeController()
|
|
112
|
+
|
|
113
|
+
controller.enable(commands: [.play, .pause])
|
|
114
|
+
let first = controller.commandTargetPointers["play"]
|
|
115
|
+
controller.enable(commands: [.play, .pause])
|
|
116
|
+
let second = controller.commandTargetPointers["play"]
|
|
117
|
+
|
|
118
|
+
#expect(controller.commandTargetPointers.count == 2)
|
|
119
|
+
#expect(first as AnyObject? !== second as AnyObject?)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// `setSkipAvailability` greys out next/previous when the queue has nowhere to
|
|
123
|
+
/// go, and the state must survive a later `enable(commands:)`.
|
|
124
|
+
@Test func skipAvailabilitySurvivesReEnable() {
|
|
125
|
+
let (controller, _) = makeController()
|
|
126
|
+
let center = MPRemoteCommandCenter.shared()
|
|
127
|
+
|
|
128
|
+
controller.enable(commands: [.next, .previous])
|
|
129
|
+
controller.setSkipAvailability(canNext: false, canPrevious: true)
|
|
130
|
+
#expect(!center.nextTrackCommand.isEnabled)
|
|
131
|
+
#expect(center.previousTrackCommand.isEnabled)
|
|
132
|
+
|
|
133
|
+
controller.enable(commands: [.next, .previous, .play])
|
|
134
|
+
|
|
135
|
+
#expect(!center.nextTrackCommand.isEnabled)
|
|
136
|
+
#expect(center.previousTrackCommand.isEnabled)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// The handler properties must not capture the controller strongly. Assigning a
|
|
140
|
+
/// bare method reference (`= handlePlayCommandDefault`) curries to
|
|
141
|
+
/// `{ [self] in self.method($0) }`, and storing that back on self is a permanent
|
|
142
|
+
/// cycle — one leaked controller per player instance, across every JS reload.
|
|
143
|
+
@Test func doesNotRetainItselfThroughHandlers() {
|
|
144
|
+
weak var weakController: RemoteCommandController?
|
|
145
|
+
autoreleasepool {
|
|
146
|
+
let (controller, _) = makeController()
|
|
147
|
+
// Forces every lazy handler to initialize, which is what closes the cycle.
|
|
148
|
+
controller.enable(commands: RemoteCommand.all())
|
|
149
|
+
controller.disableAll()
|
|
150
|
+
weakController = controller
|
|
151
|
+
}
|
|
152
|
+
#expect(weakController == nil)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test func shuffleAndRepeatStateSurviveCommandCenterSwitch() {
|
|
156
|
+
let (controller, _) = makeController()
|
|
157
|
+
let center = MPRemoteCommandCenter.shared()
|
|
158
|
+
|
|
159
|
+
controller.enable(commands: [.changeShuffleMode, .changeRepeatMode])
|
|
160
|
+
controller.updateShuffleMode(true)
|
|
161
|
+
controller.updateRepeatMode(.queue)
|
|
162
|
+
|
|
163
|
+
// Same center — switchCommandCenter no-ops, but the restore path is the
|
|
164
|
+
// interesting part: state is re-applied from the stored values.
|
|
165
|
+
controller.switchCommandCenter(center)
|
|
166
|
+
|
|
167
|
+
#expect(center.changeShuffleModeCommand.currentShuffleType == .items)
|
|
168
|
+
#expect(center.changeRepeatModeCommand.currentRepeatType == .all)
|
|
169
|
+
#expect(center.changeShuffleModeCommand.isEnabled)
|
|
170
|
+
#expect(controller.commandTargetPointers["changeShuffleMode"] != nil)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Testing
|
|
3
|
+
|
|
4
|
+
@testable import AudioBrowserTestable
|
|
5
|
+
|
|
6
|
+
@Suite("RemoteCommand")
|
|
7
|
+
struct RemoteCommandTests {
|
|
8
|
+
@Test func removedCommandsAreDisabled() {
|
|
9
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
10
|
+
enabled: [.play, .pause, .next, .previous],
|
|
11
|
+
replacedBy: [.play, .pause],
|
|
12
|
+
)
|
|
13
|
+
#expect(toDisable.map(\.key) == ["nextTrack", "previousTrack"])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test func retainedCommandsAreNotDisabled() {
|
|
17
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
18
|
+
enabled: [.play, .pause],
|
|
19
|
+
replacedBy: [.play, .pause, .stop],
|
|
20
|
+
)
|
|
21
|
+
#expect(toDisable.isEmpty)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// A changed jump interval is the same command with a new associated value —
|
|
25
|
+
/// diffing by `==` would report it as removed and disable the button that the
|
|
26
|
+
/// update had just re-enabled.
|
|
27
|
+
@Test func changedJumpIntervalIsNotDisabled() {
|
|
28
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
29
|
+
enabled: [.skipForward(preferredIntervals: [15]), .skipBackward(preferredIntervals: [15])],
|
|
30
|
+
replacedBy: [.skipForward(preferredIntervals: [30]), .skipBackward(preferredIntervals: [30])],
|
|
31
|
+
)
|
|
32
|
+
#expect(toDisable.isEmpty)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test func changedPlaybackRatesAreNotDisabled() {
|
|
36
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
37
|
+
enabled: [.changePlaybackRate(supportedPlaybackRates: [1.0])],
|
|
38
|
+
replacedBy: [.changePlaybackRate(supportedPlaybackRates: [1.0, 1.5, 2.0])],
|
|
39
|
+
)
|
|
40
|
+
#expect(toDisable.isEmpty)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test func everyCommandIsDisabledWhenNoneRemain() {
|
|
44
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
45
|
+
enabled: RemoteCommand.all(),
|
|
46
|
+
replacedBy: [],
|
|
47
|
+
)
|
|
48
|
+
#expect(toDisable.count == RemoteCommand.all().count)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// `key` is the identity used both for the diff above and for the
|
|
52
|
+
/// `commandTargetPointers` slot each command's handler lives in. Two cases
|
|
53
|
+
/// sharing a key would silently share one slot: enabling one would remove the
|
|
54
|
+
/// other's handler.
|
|
55
|
+
@Test func keysAreUnique() {
|
|
56
|
+
let all = RemoteCommand.all()
|
|
57
|
+
#expect(Set(all.map(\.key)).count == all.count)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test func droppedJumpCommandIsStillDisabled() {
|
|
61
|
+
let toDisable = RemoteCommand.commandsToDisable(
|
|
62
|
+
enabled: [.play, .skipForward(preferredIntervals: [15])],
|
|
63
|
+
replacedBy: [.play],
|
|
64
|
+
)
|
|
65
|
+
#expect(toDisable.map(\.key) == ["skipForward"])
|
|
66
|
+
}
|
|
67
|
+
}
|