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
|
@@ -53,90 +53,11 @@ export interface RemoteSkipEvent {
|
|
|
53
53
|
index: number
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// MARK: - Default Handlers
|
|
57
|
-
|
|
58
|
-
// Install remote control handlers with default behavior immediately when module loads
|
|
59
|
-
// Custom handlers can override the defaults using handleRemote* functions
|
|
60
|
-
|
|
61
|
-
// Basic playback controls
|
|
62
|
-
// AudioBrowser.onRemotePlay(() => {
|
|
63
|
-
// const customHandler = customHandlers.get('play')
|
|
64
|
-
// if (customHandler) {
|
|
65
|
-
// customHandler()
|
|
66
|
-
// } else {
|
|
67
|
-
// play()
|
|
68
|
-
// }
|
|
69
|
-
// })
|
|
70
|
-
|
|
71
|
-
// AudioBrowser.onRemotePause(() => {
|
|
72
|
-
// const customHandler = customHandlers.get('pause')
|
|
73
|
-
// if (customHandler) {
|
|
74
|
-
// customHandler()
|
|
75
|
-
// } else {
|
|
76
|
-
// pause()
|
|
77
|
-
// }
|
|
78
|
-
// })
|
|
79
|
-
|
|
80
|
-
// AudioBrowser.onRemoteNext(() => {
|
|
81
|
-
// const customHandler = customHandlers.get('next')
|
|
82
|
-
// if (customHandler) {
|
|
83
|
-
// customHandler()
|
|
84
|
-
// } else {
|
|
85
|
-
// skipToNext()
|
|
86
|
-
// }
|
|
87
|
-
// })
|
|
88
|
-
|
|
89
|
-
// AudioBrowser.onRemotePrevious(() => {
|
|
90
|
-
// const customHandler = customHandlers.get('previous')
|
|
91
|
-
// if (customHandler) {
|
|
92
|
-
// customHandler()
|
|
93
|
-
// } else {
|
|
94
|
-
// skipToPrevious()
|
|
95
|
-
// }
|
|
96
|
-
// })
|
|
97
|
-
|
|
98
|
-
// AudioBrowser.onRemoteStop(() => {
|
|
99
|
-
// const customHandler = customHandlers.get('stop')
|
|
100
|
-
// if (customHandler) {
|
|
101
|
-
// customHandler()
|
|
102
|
-
// } else {
|
|
103
|
-
// stop()
|
|
104
|
-
// }
|
|
105
|
-
// })
|
|
106
|
-
|
|
107
|
-
// // Seek controls
|
|
108
|
-
// AudioBrowser.onRemoteSeek((event: any) => {
|
|
109
|
-
// const customHandler = customHandlers.get('seek')
|
|
110
|
-
// if (customHandler) {
|
|
111
|
-
// customHandler(event)
|
|
112
|
-
// } else {
|
|
113
|
-
// seekTo(event.position)
|
|
114
|
-
// }
|
|
115
|
-
// })
|
|
116
|
-
|
|
117
|
-
// AudioBrowser.onRemoteJumpForward((event: any) => {
|
|
118
|
-
// const customHandler = customHandlers.get('jumpForward')
|
|
119
|
-
// if (customHandler) {
|
|
120
|
-
// customHandler(event)
|
|
121
|
-
// } else {
|
|
122
|
-
// seekBy(event.interval)
|
|
123
|
-
// }
|
|
124
|
-
// })
|
|
125
|
-
|
|
126
|
-
// AudioBrowser.onRemoteJumpBackward((event: any) => {
|
|
127
|
-
// const customHandler = customHandlers.get('jumpBackward')
|
|
128
|
-
// if (customHandler) {
|
|
129
|
-
// customHandler(event)
|
|
130
|
-
// } else {
|
|
131
|
-
// seekBy(-event.interval)
|
|
132
|
-
// }
|
|
133
|
-
// })
|
|
134
|
-
|
|
135
56
|
// MARK: - Handler Override Functions
|
|
136
57
|
//
|
|
137
58
|
// Use these functions when you want to OVERRIDE the default remote control behavior.
|
|
138
59
|
// These will replace the default handlers with your custom logic.
|
|
139
|
-
// If you just want to listen to events for debugging/logging, use the onRemote*
|
|
60
|
+
// If you just want to listen to events for debugging/logging, use the onRemote* emitters below.
|
|
140
61
|
|
|
141
62
|
/**
|
|
142
63
|
* Sets a custom handler for remote play events, overriding the default behavior.
|
|
@@ -210,7 +131,7 @@ export function handleRemoteJumpBackward(
|
|
|
210
131
|
|
|
211
132
|
// MARK: - Event Callbacks (for listening/debugging only)
|
|
212
133
|
//
|
|
213
|
-
// Use these
|
|
134
|
+
// Use these emitters when you want to LISTEN to remote control events without overriding
|
|
214
135
|
// the default behavior. These are perfect for logging, analytics, or debugging.
|
|
215
136
|
// Multiple listeners can be registered for the same event.
|
|
216
137
|
// To override the default behavior, use the handleRemote* functions above.
|
|
@@ -218,7 +139,7 @@ export function handleRemoteJumpBackward(
|
|
|
218
139
|
/**
|
|
219
140
|
* Subscribes to remote jump backward events.
|
|
220
141
|
* @param callback - Called when the user presses the jump backward button
|
|
221
|
-
* @returns
|
|
142
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
222
143
|
*/
|
|
223
144
|
export const onRemoteJumpBackward =
|
|
224
145
|
LazyNativeEmitter.emitterize<RemoteJumpBackwardEvent>(
|
|
@@ -228,7 +149,7 @@ export const onRemoteJumpBackward =
|
|
|
228
149
|
/**
|
|
229
150
|
* Subscribes to remote jump forward events.
|
|
230
151
|
* @param callback - Called when the user presses the jump forward button
|
|
231
|
-
* @returns
|
|
152
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
232
153
|
*/
|
|
233
154
|
export const onRemoteJumpForward =
|
|
234
155
|
LazyNativeEmitter.emitterize<RemoteJumpForwardEvent>(
|
|
@@ -238,7 +159,7 @@ export const onRemoteJumpForward =
|
|
|
238
159
|
/**
|
|
239
160
|
* Subscribes to remote next events.
|
|
240
161
|
* @param callback - Called when the user presses the next track button
|
|
241
|
-
* @returns
|
|
162
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
242
163
|
*/
|
|
243
164
|
export const onRemoteNext = LazyNativeEmitter.emitterize<void>(
|
|
244
165
|
(cb) => (nativeBrowser.onRemoteNext = cb)
|
|
@@ -247,7 +168,7 @@ export const onRemoteNext = LazyNativeEmitter.emitterize<void>(
|
|
|
247
168
|
/**
|
|
248
169
|
* Subscribes to remote pause events.
|
|
249
170
|
* @param callback - Called when the user presses the pause button
|
|
250
|
-
* @returns
|
|
171
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
251
172
|
*/
|
|
252
173
|
export const onRemotePause = LazyNativeEmitter.emitterize<void>(
|
|
253
174
|
(cb) => (nativeBrowser.onRemotePause = cb)
|
|
@@ -256,7 +177,7 @@ export const onRemotePause = LazyNativeEmitter.emitterize<void>(
|
|
|
256
177
|
/**
|
|
257
178
|
* Subscribes to remote play events.
|
|
258
179
|
* @param callback - Called when the user presses the play button
|
|
259
|
-
* @returns
|
|
180
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
260
181
|
*/
|
|
261
182
|
export const onRemotePlay = LazyNativeEmitter.emitterize<void>(
|
|
262
183
|
(cb) => (nativeBrowser.onRemotePlay = cb)
|
|
@@ -265,7 +186,7 @@ export const onRemotePlay = LazyNativeEmitter.emitterize<void>(
|
|
|
265
186
|
/**
|
|
266
187
|
* Subscribes to remote play ID events (Android only).
|
|
267
188
|
* @param callback - Called when the user selects a track from an external device
|
|
268
|
-
* @returns
|
|
189
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
269
190
|
*/
|
|
270
191
|
export const onRemotePlayId = LazyNativeEmitter.emitterize<RemotePlayIdEvent>(
|
|
271
192
|
(cb) => (nativeBrowser.onRemotePlayId = cb)
|
|
@@ -274,7 +195,7 @@ export const onRemotePlayId = LazyNativeEmitter.emitterize<RemotePlayIdEvent>(
|
|
|
274
195
|
/**
|
|
275
196
|
* Subscribes to remote play search events (Android only).
|
|
276
197
|
* @param callback - Called when the user searches for a track (usually voice search)
|
|
277
|
-
* @returns
|
|
198
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
278
199
|
*/
|
|
279
200
|
export const onRemotePlaySearch =
|
|
280
201
|
LazyNativeEmitter.emitterize<RemotePlaySearchEvent>(
|
|
@@ -284,7 +205,7 @@ export const onRemotePlaySearch =
|
|
|
284
205
|
/**
|
|
285
206
|
* Subscribes to remote previous events.
|
|
286
207
|
* @param callback - Called when the user presses the previous track button
|
|
287
|
-
* @returns
|
|
208
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
288
209
|
*/
|
|
289
210
|
export const onRemotePrevious = LazyNativeEmitter.emitterize<void>(
|
|
290
211
|
(cb) => (nativeBrowser.onRemotePrevious = cb)
|
|
@@ -293,7 +214,7 @@ export const onRemotePrevious = LazyNativeEmitter.emitterize<void>(
|
|
|
293
214
|
/**
|
|
294
215
|
* Subscribes to remote seek events.
|
|
295
216
|
* @param callback - Called when the user changes the position of the timeline
|
|
296
|
-
* @returns
|
|
217
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
297
218
|
*/
|
|
298
219
|
export const onRemoteSeek = LazyNativeEmitter.emitterize<RemoteSeekEvent>(
|
|
299
220
|
(cb) => (nativeBrowser.onRemoteSeek = cb)
|
|
@@ -302,7 +223,7 @@ export const onRemoteSeek = LazyNativeEmitter.emitterize<RemoteSeekEvent>(
|
|
|
302
223
|
/**
|
|
303
224
|
* Subscribes to remote skip events (Android only).
|
|
304
225
|
* @param callback - Called when the user presses the skip button
|
|
305
|
-
* @returns
|
|
226
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
306
227
|
*/
|
|
307
228
|
export const onRemoteSkip = LazyNativeEmitter.emitterize<RemoteSkipEvent>(
|
|
308
229
|
(cb) => (nativeBrowser.onRemoteSkip = cb)
|
|
@@ -311,7 +232,7 @@ export const onRemoteSkip = LazyNativeEmitter.emitterize<RemoteSkipEvent>(
|
|
|
311
232
|
/**
|
|
312
233
|
* Subscribes to remote stop events.
|
|
313
234
|
* @param callback - Called when the user presses the stop button
|
|
314
|
-
* @returns
|
|
235
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
315
236
|
*/
|
|
316
237
|
export const onRemoteStop = LazyNativeEmitter.emitterize<void>(
|
|
317
238
|
(cb) => (nativeBrowser.onRemoteStop = cb)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/** @vitest-environment happy-dom */
|
|
2
|
+
|
|
3
|
+
import { renderHook, act, cleanup } from '@testing-library/react'
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
5
|
+
import type { SleepTimer } from './sleepTimer'
|
|
6
|
+
|
|
7
|
+
const native = vi.hoisted(() => ({
|
|
8
|
+
getSleepTimer: vi.fn<() => SleepTimer>(() => null),
|
|
9
|
+
// The single callback slot `NativeUpdatedValue` claims at module load.
|
|
10
|
+
onSleepTimerChanged: undefined as ((timer: SleepTimer) => void) | undefined
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../native', () => ({ nativeBrowser: native }))
|
|
14
|
+
|
|
15
|
+
const { onSleepTimerChanged, useSleepTimer, useSleepTimerActive } =
|
|
16
|
+
await import('./sleepTimer')
|
|
17
|
+
|
|
18
|
+
/** Push a value the way native would. */
|
|
19
|
+
const fire = (timer: SleepTimer) =>
|
|
20
|
+
act(() => native.onSleepTimerChanged?.(timer))
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The emitter is a module-level singleton, so `lastValue` outlives each test.
|
|
24
|
+
* `undefined` is its pre-native state and distinct from `null`, which is a real
|
|
25
|
+
* value meaning "no timer" — the distinction the re-sync gate turns on.
|
|
26
|
+
*/
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
onSleepTimerChanged.lastValue = undefined
|
|
29
|
+
native.getSleepTimer.mockReturnValue(null)
|
|
30
|
+
})
|
|
31
|
+
afterEach(cleanup)
|
|
32
|
+
|
|
33
|
+
describe('useSleepTimerActive', () => {
|
|
34
|
+
it('reads the getter when nothing has fired yet', () => {
|
|
35
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 60_000 })
|
|
36
|
+
const { result } = renderHook(() => useSleepTimerActive())
|
|
37
|
+
expect(result.current).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('tracks timers being set and cleared', () => {
|
|
41
|
+
const { result } = renderHook(() => useSleepTimerActive())
|
|
42
|
+
expect(result.current).toBe(false)
|
|
43
|
+
fire({ time: Date.now() + 60_000 })
|
|
44
|
+
expect(result.current).toBe(true)
|
|
45
|
+
fire(null)
|
|
46
|
+
expect(result.current).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('treats an end-of-track timer as active', () => {
|
|
50
|
+
const { result } = renderHook(() => useSleepTimerActive())
|
|
51
|
+
fire({ sleepWhenPlayedToEnd: true })
|
|
52
|
+
expect(result.current).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// The regression from f7a25040. The getter ran during render and saw a live
|
|
56
|
+
// timer; the clear landed before the effect subscribed. Without the re-sync
|
|
57
|
+
// the UI shows a running timer that is already gone — permanently, since
|
|
58
|
+
// nothing reads it again until the next native event.
|
|
59
|
+
it('re-syncs a clear that landed between the render read and the effect', () => {
|
|
60
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 60_000 })
|
|
61
|
+
onSleepTimerChanged.lastValue = null
|
|
62
|
+
const { result } = renderHook(() => useSleepTimerActive())
|
|
63
|
+
expect(result.current).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// The gate is `!== undefined`, not truthiness: `undefined` means native has
|
|
67
|
+
// never spoken, so the getter's answer must stand.
|
|
68
|
+
it('keeps the getter value when native has not fired at all', () => {
|
|
69
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 60_000 })
|
|
70
|
+
const { result } = renderHook(() => useSleepTimerActive())
|
|
71
|
+
expect(result.current).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('stops tracking after unmount', () => {
|
|
75
|
+
const { result, unmount } = renderHook(() => useSleepTimerActive())
|
|
76
|
+
unmount()
|
|
77
|
+
fire({ time: Date.now() + 60_000 })
|
|
78
|
+
expect(result.current).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('useSleepTimer', () => {
|
|
83
|
+
it('is undefined when no timer is set', () => {
|
|
84
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
85
|
+
expect(result.current).toBeUndefined()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('reports secondsLeft for a time-based timer', () => {
|
|
89
|
+
vi.useFakeTimers()
|
|
90
|
+
try {
|
|
91
|
+
vi.setSystemTime(1_000_000_000_000)
|
|
92
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 30_000 })
|
|
93
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
94
|
+
expect(result.current).toEqual({
|
|
95
|
+
time: 1_000_000_030_000,
|
|
96
|
+
secondsLeft: 30
|
|
97
|
+
})
|
|
98
|
+
} finally {
|
|
99
|
+
vi.useRealTimers()
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('passes an end-of-track timer through without secondsLeft', () => {
|
|
104
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
105
|
+
fire({ sleepWhenPlayedToEnd: true })
|
|
106
|
+
expect(result.current).toEqual({ sleepWhenPlayedToEnd: true })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('counts down as time passes', () => {
|
|
110
|
+
vi.useFakeTimers()
|
|
111
|
+
try {
|
|
112
|
+
vi.setSystemTime(1_000_000_000_000)
|
|
113
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 5_000 })
|
|
114
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
115
|
+
expect(result.current).toMatchObject({ secondsLeft: 5 })
|
|
116
|
+
act(() => {
|
|
117
|
+
vi.advanceTimersByTime(2_000)
|
|
118
|
+
})
|
|
119
|
+
expect(result.current).toMatchObject({ secondsLeft: 3 })
|
|
120
|
+
act(() => {
|
|
121
|
+
vi.advanceTimersByTime(3_000)
|
|
122
|
+
})
|
|
123
|
+
expect(result.current).toMatchObject({ secondsLeft: 0 })
|
|
124
|
+
} finally {
|
|
125
|
+
vi.useRealTimers()
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('stops the countdown once it reaches zero', () => {
|
|
130
|
+
vi.useFakeTimers()
|
|
131
|
+
try {
|
|
132
|
+
vi.setSystemTime(1_000_000_000_000)
|
|
133
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 2_000 })
|
|
134
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
135
|
+
act(() => {
|
|
136
|
+
vi.advanceTimersByTime(2_000)
|
|
137
|
+
})
|
|
138
|
+
expect(result.current).toMatchObject({ secondsLeft: 0 })
|
|
139
|
+
// Clamped at zero rather than running negative.
|
|
140
|
+
act(() => {
|
|
141
|
+
vi.advanceTimersByTime(10_000)
|
|
142
|
+
})
|
|
143
|
+
expect(result.current).toMatchObject({ secondsLeft: 0 })
|
|
144
|
+
} finally {
|
|
145
|
+
vi.useRealTimers()
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('does not tick while inactive', () => {
|
|
150
|
+
vi.useFakeTimers()
|
|
151
|
+
try {
|
|
152
|
+
vi.setSystemTime(1_000_000_000_000)
|
|
153
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 60_000 })
|
|
154
|
+
const { result } = renderHook(() => useSleepTimer({ inactive: true }))
|
|
155
|
+
expect(result.current).toMatchObject({ secondsLeft: 60 })
|
|
156
|
+
act(() => {
|
|
157
|
+
vi.advanceTimersByTime(10_000)
|
|
158
|
+
})
|
|
159
|
+
expect(result.current).toMatchObject({ secondsLeft: 60 })
|
|
160
|
+
} finally {
|
|
161
|
+
vi.useRealTimers()
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Same race as useSleepTimerActive, and the reason both hooks carry the gate.
|
|
166
|
+
it('re-syncs a clear that landed between the render read and the effect', () => {
|
|
167
|
+
native.getSleepTimer.mockReturnValue({ time: Date.now() + 60_000 })
|
|
168
|
+
onSleepTimerChanged.lastValue = null
|
|
169
|
+
const { result } = renderHook(() => useSleepTimer())
|
|
170
|
+
expect(result.current).toBeUndefined()
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -86,7 +86,7 @@ export function clearSleepTimer(): boolean {
|
|
|
86
86
|
/**
|
|
87
87
|
* Subscribes to sleep timer changes.
|
|
88
88
|
* @param callback - Called when the sleep timer state changes
|
|
89
|
-
* @returns
|
|
89
|
+
* @returns An emitter — subscribe with `addListener(callback)`, which returns a cleanup function
|
|
90
90
|
*/
|
|
91
91
|
export const onSleepTimerChanged = NativeUpdatedValue.emitterize<SleepTimer>(
|
|
92
92
|
(cb) => (nativeBrowser.onSleepTimerChanged = cb)
|
|
@@ -104,6 +104,11 @@ export function useSleepTimerActive(): boolean {
|
|
|
104
104
|
const [isActive, setIsActive] = useState(() => getSleepTimer() !== null)
|
|
105
105
|
|
|
106
106
|
useEffect(() => {
|
|
107
|
+
// Re-sync before subscribing: a change between the initial read above and this effect would
|
|
108
|
+
// otherwise be missed for good. `undefined` means nothing has fired yet, whereas `null` is a
|
|
109
|
+
// real value — the timer being cleared — so it has to re-sync too.
|
|
110
|
+
const { lastValue } = onSleepTimerChanged
|
|
111
|
+
if (lastValue !== undefined) setIsActive(lastValue !== null)
|
|
107
112
|
return onSleepTimerChanged.addListener((timer) => {
|
|
108
113
|
setIsActive(timer !== null)
|
|
109
114
|
})
|
|
@@ -137,6 +142,9 @@ export function useSleepTimer(params?: {
|
|
|
137
142
|
const time = state && 'time' in state ? state.time : undefined
|
|
138
143
|
|
|
139
144
|
useEffect(() => {
|
|
145
|
+
// Re-sync before subscribing — see useSleepTimerActive.
|
|
146
|
+
const { lastValue } = onSleepTimerChanged
|
|
147
|
+
if (lastValue !== undefined) setState(addSecondsLeft(lastValue))
|
|
140
148
|
return onSleepTimerChanged.addListener((event) => {
|
|
141
149
|
setState(addSecondsLeft(event))
|
|
142
150
|
})
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { describe, it, expect } from 'vitest'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Drift guard for `ios/Model/NitroTypeStubs.swift`.
|
|
7
|
+
*
|
|
8
|
+
* The generated Nitro Swift types are `typealias`es onto C++ structs
|
|
9
|
+
* (`margelo.nitro.audiobrowser.*`), so SwiftPM cannot compile them without the
|
|
10
|
+
* whole Nitro C++ core — which is why the SPM test target substitutes
|
|
11
|
+
* hand-written stubs behind `#if !canImport(NitroModules)`. The cost of that
|
|
12
|
+
* trick is that the stubs can disagree with the real types, and when they do
|
|
13
|
+
* `swift test` still passes while the app build breaks. That has already
|
|
14
|
+
* happened once (1d2edade, "restore the SPM test-stub build after the artwork
|
|
15
|
+
* variants change").
|
|
16
|
+
*
|
|
17
|
+
* Neither native CI job can catch it: the Swift job only ever sees the stubs,
|
|
18
|
+
* and the app build only ever sees the generated types. This test is the one
|
|
19
|
+
* place both files are readable at once, so the check lives here.
|
|
20
|
+
*
|
|
21
|
+
* The rule is subset-with-exact-types. A stub may declare FEWER properties than
|
|
22
|
+
* the real type — shared `ios/` sources can only use what the stub declares, so
|
|
23
|
+
* an omission is safe. What is not safe is a property the real type spells
|
|
24
|
+
* differently: `var id: String` against a generated `id: String?` compiles under
|
|
25
|
+
* test and fails to compile in the app.
|
|
26
|
+
*/
|
|
27
|
+
const ROOT = process.cwd()
|
|
28
|
+
const STUBS = join(ROOT, 'ios', 'Model', 'NitroTypeStubs.swift')
|
|
29
|
+
const GENERATED = join(ROOT, 'nitrogen', 'generated', 'ios', 'swift')
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Stubbed types with no generated counterpart, and why. Anything else missing
|
|
33
|
+
* from `nitrogen/generated/ios/swift/` is a stub for a type that no longer
|
|
34
|
+
* exists — the loudest kind of drift.
|
|
35
|
+
*/
|
|
36
|
+
const NOT_GENERATED: Record<string, string> = {
|
|
37
|
+
// Nitro core type (`nitro::NullType`), shipped by NitroModules rather than
|
|
38
|
+
// emitted per-project, so there is no file of ours to compare against.
|
|
39
|
+
NullType: 'provided by NitroModules core'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Struct = { kind: 'struct'; members: Map<string, string> }
|
|
43
|
+
type Enum = { kind: 'enum'; members: Map<string, string> }
|
|
44
|
+
type Shape = Struct | Enum
|
|
45
|
+
|
|
46
|
+
/** Splits a parameter/case list on top-level commas, ignoring `[]`, `()`, `<>`. */
|
|
47
|
+
function splitTopLevel(text: string): string[] {
|
|
48
|
+
const parts: string[] = []
|
|
49
|
+
let depth = 0
|
|
50
|
+
let current = ''
|
|
51
|
+
for (const char of text) {
|
|
52
|
+
if (char === '[' || char === '(' || char === '<') depth++
|
|
53
|
+
else if (char === ']' || char === ')' || char === '>') depth--
|
|
54
|
+
if (char === ',' && depth === 0) {
|
|
55
|
+
parts.push(current)
|
|
56
|
+
current = ''
|
|
57
|
+
} else current += char
|
|
58
|
+
}
|
|
59
|
+
if (current.trim()) parts.push(current)
|
|
60
|
+
return parts.map((part) => part.trim()).filter(Boolean)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** `label: Type` → [label, Type]. Splits on the FIRST colon (`[String: String]`). */
|
|
64
|
+
function splitLabelled(part: string): [string, string] | null {
|
|
65
|
+
const colon = part.indexOf(':')
|
|
66
|
+
if (colon < 0) return null
|
|
67
|
+
const label = part.slice(0, colon).trim()
|
|
68
|
+
// A generated init can carry both an argument label and a parameter name
|
|
69
|
+
// (`for value: String`); the property name is the last word before the colon.
|
|
70
|
+
const name = label.split(/\s+/).pop()!
|
|
71
|
+
return [name, part.slice(colon + 1).trim()]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DECL =
|
|
75
|
+
/^\s*(?:(?:public|@frozen|indirect|final)\s+)*(struct|enum)\s+([A-Za-z_]\w*)\s*(?::[^{]*)?\{/
|
|
76
|
+
|
|
77
|
+
/** Parses every top-level `struct`/`enum` in a Swift file into its member map. */
|
|
78
|
+
function parseSwiftDecls(source: string): Map<string, Shape> {
|
|
79
|
+
const shapes = new Map<string, Shape>()
|
|
80
|
+
const lines = source.split('\n')
|
|
81
|
+
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
const decl = DECL.exec(lines[i]!)
|
|
84
|
+
if (!decl) continue
|
|
85
|
+
const keyword = decl[1]!
|
|
86
|
+
const name = decl[2]!
|
|
87
|
+
const members = new Map<string, string>()
|
|
88
|
+
let depth = 1
|
|
89
|
+
|
|
90
|
+
for (let j = i + 1; j < lines.length && depth > 0; j++) {
|
|
91
|
+
const body = lines[j]!.replace(/\/\/.*$/, '')
|
|
92
|
+
|
|
93
|
+
if (depth === 1) {
|
|
94
|
+
// Stored property: `var name: Type`, but not a computed one (`{` after).
|
|
95
|
+
const prop =
|
|
96
|
+
/^\s*(?:public\s+)?var\s+([A-Za-z_]\w*)\s*:\s*([^={]+?)\s*(?:=|$)/.exec(
|
|
97
|
+
body
|
|
98
|
+
)
|
|
99
|
+
if (prop && !/\{\s*$/.test(body)) members.set(prop[1]!, prop[2]!.trim())
|
|
100
|
+
|
|
101
|
+
// `case a`, `case a(T)`, `case a, b`
|
|
102
|
+
const enumCase = /^\s*case\s+(.+)$/.exec(body)
|
|
103
|
+
if (enumCase && keyword === 'enum') {
|
|
104
|
+
for (const one of splitTopLevel(enumCase[1]!)) {
|
|
105
|
+
const withPayload = /^([A-Za-z_]\w*)\s*\((.*)\)$/.exec(one)
|
|
106
|
+
if (withPayload)
|
|
107
|
+
members.set(withPayload[1]!, withPayload[2]!.trim())
|
|
108
|
+
else if (/^[A-Za-z_]\w*$/.test(one)) members.set(one, '')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const char of body) {
|
|
114
|
+
if (char === '{') depth++
|
|
115
|
+
else if (char === '}') depth--
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
shapes.set(name, {
|
|
120
|
+
kind: keyword === 'struct' ? 'struct' : 'enum',
|
|
121
|
+
members
|
|
122
|
+
} as Shape)
|
|
123
|
+
}
|
|
124
|
+
return shapes
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reads the real shape of a generated type. Nitro emits three forms: a struct
|
|
129
|
+
* (memberwise `init`), a string-backed enum (`init?(fromString:)`), and a
|
|
130
|
+
* variant (a plain `enum` with `first`/`second`/… payload cases).
|
|
131
|
+
*/
|
|
132
|
+
function parseGenerated(name: string): Shape | null {
|
|
133
|
+
let source: string
|
|
134
|
+
try {
|
|
135
|
+
source = readFileSync(join(GENERATED, `${name}.swift`), 'utf8')
|
|
136
|
+
} catch {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Variant enums are declared outright, so the generic parser sees them.
|
|
141
|
+
const declared = parseSwiftDecls(source).get(name)
|
|
142
|
+
if (declared && declared.members.size > 0) return declared
|
|
143
|
+
|
|
144
|
+
// String-backed enum. Read the case names off the `stringValue` switch, which
|
|
145
|
+
// spells them out (`case .get: return "GET"`), rather than deriving them from
|
|
146
|
+
// the JS values — nitrogen's casing rule is not ours to reimplement.
|
|
147
|
+
if (source.includes('var stringValue: String')) {
|
|
148
|
+
const members = new Map<string, string>()
|
|
149
|
+
for (const match of source.matchAll(/case\s+\.(\w+)\s*:/g))
|
|
150
|
+
members.set(match[1]!, '')
|
|
151
|
+
return { kind: 'enum', members }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const init = /^\s*init\((.*)\)\s*\{/m.exec(source)
|
|
155
|
+
if (init) {
|
|
156
|
+
const members = new Map<string, string>()
|
|
157
|
+
for (const part of splitTopLevel(init[1]!)) {
|
|
158
|
+
const labelled = splitLabelled(part)
|
|
159
|
+
if (labelled) members.set(labelled[0], labelled[1])
|
|
160
|
+
}
|
|
161
|
+
return { kind: 'struct', members }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Sugared and desugared spellings are the same Swift type — nitrogen emits
|
|
169
|
+
* `Dictionary<String, String>` where the stubs write `[String: String]`.
|
|
170
|
+
*/
|
|
171
|
+
function normalizeType(type: string): string {
|
|
172
|
+
let previous: string
|
|
173
|
+
let current = type.replace(/\s+/g, ' ').trim()
|
|
174
|
+
do {
|
|
175
|
+
previous = current
|
|
176
|
+
current = current
|
|
177
|
+
.replace(/\bDictionary<([^<>]+),\s*([^<>]+)>/g, '[$1: $2]')
|
|
178
|
+
.replace(/\bArray<([^<>]+)>/g, '[$1]')
|
|
179
|
+
.replace(/\bOptional<([^<>]+)>/g, '$1?')
|
|
180
|
+
} while (current !== previous)
|
|
181
|
+
return current
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
describe('NitroTypeStubs mirror the generated Nitro types', () => {
|
|
185
|
+
const stubs = parseSwiftDecls(readFileSync(STUBS, 'utf8'))
|
|
186
|
+
|
|
187
|
+
it('parses the stub file', () => {
|
|
188
|
+
// A parser that silently matches nothing would make every check below pass.
|
|
189
|
+
expect(stubs.size).toBeGreaterThan(20)
|
|
190
|
+
expect(stubs.get('Track')?.members.get('src')).toBe('String?')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('stubs only types that Nitro actually generates', () => {
|
|
194
|
+
const orphans = [...stubs.keys()].filter(
|
|
195
|
+
(name) => !(name in NOT_GENERATED) && parseGenerated(name) == null
|
|
196
|
+
)
|
|
197
|
+
expect(
|
|
198
|
+
orphans,
|
|
199
|
+
`Stubbed with no generated counterpart in nitrogen/generated/ios/swift — the type was ` +
|
|
200
|
+
`renamed or removed from the Nitro spec, so the stub is standing in for nothing:\n` +
|
|
201
|
+
orphans.join('\n')
|
|
202
|
+
).toEqual([])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('declares every stubbed property with the generated type', () => {
|
|
206
|
+
const drift: string[] = []
|
|
207
|
+
|
|
208
|
+
for (const [name, stub] of stubs) {
|
|
209
|
+
if (name in NOT_GENERATED) continue
|
|
210
|
+
const real = parseGenerated(name)
|
|
211
|
+
if (real == null) continue // reported by the previous test
|
|
212
|
+
|
|
213
|
+
for (const [member, stubType] of stub.members) {
|
|
214
|
+
const realType = real.members.get(member)
|
|
215
|
+
const what = real.kind === 'enum' ? 'case' : 'property'
|
|
216
|
+
if (realType == null) {
|
|
217
|
+
drift.push(
|
|
218
|
+
`${name}.${member}: stub declares a ${what} the generated type does not have`
|
|
219
|
+
)
|
|
220
|
+
} else if (normalizeType(realType) !== normalizeType(stubType)) {
|
|
221
|
+
drift.push(
|
|
222
|
+
`${name}.${member}: stub \`${stubType || '(no payload)'}\`, ` +
|
|
223
|
+
`generated \`${realType || '(no payload)'}\``
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
expect(
|
|
230
|
+
drift,
|
|
231
|
+
`ios/Model/NitroTypeStubs.swift disagrees with nitrogen/generated/ios/swift. ` +
|
|
232
|
+
`\`swift test\` compiles against the stub and will not catch this; the app build ` +
|
|
233
|
+
`compiles against the generated type and will fail. Fix the stub to match:\n` +
|
|
234
|
+
drift.join('\n')
|
|
235
|
+
).toEqual([])
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('declares struct members in the generated initializer order', () => {
|
|
239
|
+
// Name/type parity is not enough: the stub struct's memberwise init must
|
|
240
|
+
// accept the same ARGUMENT ORDER as the generated labelled init, or shared
|
|
241
|
+
// sources compile under `swift test` and fail in the app build with
|
|
242
|
+
// "argument 'x' must precede argument 'y'" (nitrogen flattens `extends`
|
|
243
|
+
// own-properties-first, which is easy to get backwards — SectionStyle did).
|
|
244
|
+
// A stub may omit members; the ones it declares must be a subsequence of
|
|
245
|
+
// the generated order.
|
|
246
|
+
const misordered: string[] = []
|
|
247
|
+
|
|
248
|
+
for (const [name, stub] of stubs) {
|
|
249
|
+
if (name in NOT_GENERATED || stub.kind !== 'struct') continue
|
|
250
|
+
const real = parseGenerated(name)
|
|
251
|
+
if (real == null || real.kind !== 'struct') continue
|
|
252
|
+
|
|
253
|
+
const realOrder = [...real.members.keys()]
|
|
254
|
+
const stubOrder = [...stub.members.keys()].filter((member) =>
|
|
255
|
+
real.members.has(member)
|
|
256
|
+
)
|
|
257
|
+
let cursor = -1
|
|
258
|
+
for (const member of stubOrder) {
|
|
259
|
+
const index = realOrder.indexOf(member)
|
|
260
|
+
if (index < cursor) {
|
|
261
|
+
misordered.push(
|
|
262
|
+
`${name}: stub declares [${stubOrder.join(', ')}], ` +
|
|
263
|
+
`generated init order is [${realOrder.join(', ')}]`
|
|
264
|
+
)
|
|
265
|
+
break
|
|
266
|
+
}
|
|
267
|
+
cursor = index
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
expect(
|
|
272
|
+
misordered,
|
|
273
|
+
`Stub struct members are out of order relative to the generated init:\n` +
|
|
274
|
+
misordered.join('\n')
|
|
275
|
+
).toEqual([])
|
|
276
|
+
})
|
|
277
|
+
})
|