react-native-audio-browser 0.2.0-next.8 → 0.2.0
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/AudioBrowser.podspec +1 -1
- package/README.md +2 -180
- package/android/build.gradle +1 -17
- package/android/gradle.properties +1 -1
- package/android/src/main/AndroidManifest.xml +0 -15
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +141 -401
- package/android/src/main/java/com/audiobrowser/Callbacks.kt +6 -4
- package/android/src/main/java/com/audiobrowser/Service.kt +18 -67
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +178 -442
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +4 -41
- package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
- package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +97 -109
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +8 -40
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +46 -40
- package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +1 -4
- package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +1 -18
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +3 -15
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +134 -422
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +404 -248
- package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +59 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +5 -25
- package/android/src/main/java/com/audiobrowser/player/Player.kt +582 -469
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +67 -190
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +53 -118
- package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +3 -37
- package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +40 -85
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +0 -24
- package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +300 -23
- package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +23 -13
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
- package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +3 -4
- package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +1 -2
- package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +72 -0
- package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +36 -0
- package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +111 -6
- package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +8 -22
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +80 -112
- package/android/src/main/res/values/strings.xml +0 -4
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +0 -4
- package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +2 -1
- package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +0 -72
- package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +146 -0
- package/ios/Browser/BrowserConfig.swift +11 -41
- package/ios/Browser/BrowserManager+URLResolution.swift +93 -213
- package/ios/Browser/BrowserManager.swift +110 -303
- package/ios/Browser/BrowserPathHelper.swift +3 -32
- package/ios/Browser/JsonModels.swift +66 -287
- package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
- package/ios/CarPlay/CarPlayController.swift +149 -739
- package/ios/CarPlay/CarPlayImageLoader.swift +8 -57
- package/ios/CarPlay/CarPlayListItemFactory.swift +35 -44
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +36 -180
- package/ios/CarPlay/RNABAudioBrowser.h +10 -15
- package/ios/CarPlay/RNABAudioBrowser.m +14 -15
- package/ios/CarPlay/RNABCarPlaySceneDelegate.m +0 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +43 -53
- package/ios/CarPlay/Track+CarPlay.swift +63 -0
- package/ios/Extension/Capability+RemoteCommand.swift +9 -7
- package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/NavigationError+Conversion.swift +1 -8
- package/ios/Extension/ResolvedTrack+Copying.swift +4 -10
- package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/Track+AVPlayer.swift +4 -4
- package/ios/Extension/Track+Copying.swift +3 -9
- package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
- package/ios/HybridAudioBrowser.swift +173 -751
- package/ios/Model/NitroTypeStubs.swift +224 -299
- package/ios/Model/PlayerUpdateOptions.swift +23 -23
- package/ios/Model/RemoteCommand.swift +6 -24
- package/ios/Model/TrackPlayerError.swift +18 -82
- package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +9 -0
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +121 -99
- package/ios/Observer/PlayerItemNotificationObserver.swift +5 -45
- package/ios/Observer/PlayerItemPropertyObserver.swift +4 -27
- package/ios/Observer/PlayerTimeObserver.swift +53 -3
- package/ios/Option/SessionCategories.swift +11 -34
- package/ios/Player/LoadSeekCoordinator.swift +1 -16
- package/ios/Player/MediaLoader.swift +11 -31
- package/ios/Player/NowPlayingUpdater.swift +26 -119
- package/ios/Player/PlaybackCoordinator.swift +51 -285
- package/ios/Player/PlaybackCoordinatorCallbacks.swift +1 -6
- package/ios/Player/PlaybackEffectHandler.swift +9 -19
- package/ios/Player/PlaybackErrorHandler.swift +13 -97
- package/ios/Player/PlaybackProgressUpdateManager.swift +68 -0
- package/ios/Player/PlaybackStateMachine.swift +13 -18
- package/ios/Player/PlayingStateManager.swift +2 -3
- package/ios/Player/QueueManager.swift +61 -67
- package/ios/Player/RetryManager.swift +21 -98
- package/ios/Player/SleepTimerHandling.swift +0 -8
- package/ios/Player/SleepTimerManager.swift +11 -72
- package/ios/RemoteCommand/RemoteCommandController.swift +21 -40
- package/ios/Tests/BrowserPathHelperTests.swift +0 -28
- package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
- package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
- package/ios/Tests/LoadSeekCoordinatorTests.swift +7 -40
- package/ios/Tests/MediaLoaderTests.swift +20 -32
- package/ios/Tests/PlaybackCoordinatorTests.swift +13 -787
- package/ios/Tests/PlaybackErrorHandlerTests.swift +46 -380
- package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +166 -0
- package/ios/Tests/PlaybackStateMachineTests.swift +8 -33
- package/ios/Tests/QueueManagerTests.swift +18 -149
- package/ios/Tests/ResolvedTrackCopyingTests.swift +8 -15
- package/ios/Tests/Support/TestTypes.swift +20 -61
- package/ios/Tests/TrackSelectorTests.swift +15 -15
- package/ios/TrackPlayer.swift +58 -380
- package/ios/TrackPlayerCallbacks.swift +44 -5
- package/ios/TrackSelector.swift +5 -5
- package/ios/Util/ArtworkImageFetcher.swift +2 -4
- package/ios/Util/NetworkMonitor.swift +3 -7
- package/ios/Util/OnceValue.swift +0 -34
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser.js +158 -59
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +5 -37
- package/lib/commonjs/features/errors.js.map +1 -1
- package/lib/commonjs/features/favorites.js +1 -1
- package/lib/commonjs/features/index.js +0 -22
- package/lib/commonjs/features/index.js.map +1 -1
- package/lib/commonjs/features/metadata.js +3 -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 -36
- package/lib/commonjs/features/nowPlaying.js.map +1 -1
- package/lib/commonjs/features/output.js +23 -37
- package/lib/commonjs/features/output.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +0 -9
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/index.js +0 -11
- package/lib/commonjs/features/playback/index.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 +13 -29
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +1 -1
- package/lib/commonjs/features/playback/volume.js +2 -2
- package/lib/commonjs/features/player/options.js +29 -79
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -256
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +1 -1
- package/lib/commonjs/features/queue/queue.js +5 -12
- 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/rating.js +2 -0
- package/lib/commonjs/features/rating.js.map +1 -0
- package/lib/commonjs/features/remoteControls.js +104 -14
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +5 -10
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/native.js +0 -5
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.web.js +1 -4
- package/lib/commonjs/native.web.js.map +1 -1
- package/lib/commonjs/types/player.js +2 -0
- package/lib/commonjs/types/player.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +8 -8
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +3 -0
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/web/NativeAudioBrowser.js +124 -301
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/SimpleRouter.test.js +174 -0
- package/lib/commonjs/web/SimpleRouter.test.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/Player.js +26 -90
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +293 -0
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js +3 -34
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/index.js +5 -13
- package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +50 -176
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.js +1 -3
- package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +9 -11
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/http/HttpClient.js +23 -4
- package/lib/commonjs/web/http/HttpClient.js.map +1 -1
- package/lib/commonjs/web/http/RequestConfigBuilder.js +58 -107
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/player/NowPlayingManager.js +5 -6
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/player/OptionsManager.js +17 -9
- package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -4
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser.js +156 -53
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +5 -37
- package/lib/module/features/errors.js.map +1 -1
- package/lib/module/features/favorites.js +1 -1
- package/lib/module/features/index.js +0 -2
- package/lib/module/features/index.js.map +1 -1
- package/lib/module/features/metadata.js +3 -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 -34
- package/lib/module/features/nowPlaying.js.map +1 -1
- package/lib/module/features/output.js +19 -32
- package/lib/module/features/output.js.map +1 -1
- package/lib/module/features/playback/controls.js +0 -8
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/index.js +0 -1
- package/lib/module/features/playback/index.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 +13 -29
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/state.js +1 -1
- package/lib/module/features/playback/volume.js +2 -2
- package/lib/module/features/player/options.js +29 -79
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -256
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +1 -1
- package/lib/module/features/queue/queue.js +5 -12
- 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/rating.js +2 -0
- package/lib/module/features/rating.js.map +1 -0
- package/lib/module/features/remoteControls.js +103 -13
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +5 -10
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/native.js +0 -5
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.web.js +1 -4
- package/lib/module/native.web.js.map +1 -1
- package/lib/module/types/player.js +2 -0
- package/lib/module/types/player.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +8 -8
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +3 -0
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/web/NativeAudioBrowser.js +125 -302
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/SimpleRouter.test.js +174 -0
- package/lib/module/web/SimpleRouter.test.js.map +1 -0
- package/lib/module/web/TrackPlayer/Player.js +26 -90
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js +288 -0
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/module/web/TrackPlayer/SleepTimer.js +3 -34
- package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/module/web/TrackPlayer/index.js +1 -2
- package/lib/module/web/TrackPlayer/index.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +50 -176
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/NavigationErrorManager.js +1 -3
- package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +9 -11
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/http/HttpClient.js +23 -4
- package/lib/module/web/http/HttpClient.js.map +1 -1
- package/lib/module/web/http/RequestConfigBuilder.js +58 -107
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/player/NowPlayingManager.js +5 -7
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/player/OptionsManager.js +17 -9
- package/lib/module/web/player/OptionsManager.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +1 -40
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +6 -61
- package/lib/typescript/src/features/errors.d.ts.map +1 -1
- package/lib/typescript/src/features/favorites.d.ts +1 -1
- package/lib/typescript/src/features/index.d.ts +0 -2
- package/lib/typescript/src/features/index.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +7 -9
- 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 -28
- package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
- package/lib/typescript/src/features/output.d.ts +18 -28
- package/lib/typescript/src/features/output.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +0 -5
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/index.d.ts +0 -1
- package/lib/typescript/src/features/playback/index.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 -4
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +2 -2
- package/lib/typescript/src/features/player/options.d.ts +127 -176
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +167 -213
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +4 -11
- 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/rating.d.ts +14 -0
- package/lib/typescript/src/features/rating.d.ts.map +1 -0
- package/lib/typescript/src/features/remoteControls.d.ts +35 -23
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +4 -19
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native.d.ts +2 -1
- package/lib/typescript/src/native.d.ts.map +1 -1
- package/lib/typescript/src/native.web.d.ts +3 -0
- package/lib/typescript/src/native.web.d.ts.map +1 -0
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts +225 -17
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +3 -9
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +13 -145
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +85 -349
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/types/player.d.ts +64 -0
- package/lib/typescript/src/types/player.d.ts.map +1 -0
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +6 -8
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +2 -0
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts +188 -0
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +1 -0
- package/lib/typescript/src/web/SimpleRouter.d.ts +40 -0
- package/lib/typescript/src/web/SimpleRouter.d.ts.map +1 -0
- package/lib/typescript/src/web/SimpleRouter.test.d.ts +2 -0
- package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts +11 -0
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts +59 -0
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +39 -0
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +6 -0
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +4 -0
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +34 -0
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/State.d.ts +13 -0
- package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/index.d.ts +5 -0
- package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/BrowserManager.d.ts +154 -0
- package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts +42 -0
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +40 -0
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/SearchManager.d.ts +22 -0
- package/lib/typescript/src/web/browser/SearchManager.d.ts.map +1 -0
- package/lib/typescript/src/web/http/HttpClient.d.ts +25 -0
- package/lib/typescript/src/web/http/HttpClient.d.ts.map +1 -0
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +98 -0
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +1 -0
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts +34 -0
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +1 -0
- package/lib/typescript/src/web/player/OptionsManager.d.ts +32 -0
- package/lib/typescript/src/web/player/OptionsManager.d.ts.map +1 -0
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +69 -0
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +1 -0
- package/lib/typescript/src/web/util/shuffle.d.ts +8 -0
- package/lib/typescript/src/web/util/shuffle.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +2 -4
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +10 -24
- package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +20 -10
- package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +86 -0
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +1 -33
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +0 -5
- package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +2 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +147 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +0 -8
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +10 -0
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +9 -14
- package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +0 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +0 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +87 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +0 -6
- package/nitrogen/generated/android/c++/JHeartRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +221 -245
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +22 -25
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +3 -33
- package/nitrogen/generated/android/c++/{JOutput.hpp → JIosOutput.hpp} +15 -15
- package/nitrogen/generated/android/c++/JIosOutputType.hpp +85 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +1 -33
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +28 -61
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +9 -20
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +24 -21
- package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +0 -6
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +20 -10
- package/nitrogen/generated/android/c++/{JRemoteButton.hpp → JNotificationButton.hpp} +20 -20
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +94 -0
- package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +17 -3
- package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +3 -7
- package/nitrogen/generated/android/c++/JOptions.hpp +14 -19
- package/nitrogen/generated/android/c++/{JNativeAndroidSetupOptions.hpp → JPartialAndroidSetupPlayerOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/{JNativeIOSSetupOptions.hpp → JPartialIOSSetupPlayerOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +96 -0
- package/nitrogen/generated/android/c++/JPercentageRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JPlayback.hpp +0 -2
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +3 -18
- package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +0 -2
- package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JRatingType.hpp +73 -0
- package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +66 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +5 -23
- package/nitrogen/generated/android/c++/JRetryConfig.hpp +7 -11
- package/nitrogen/generated/android/c++/JSearchMode.hpp +9 -27
- package/nitrogen/generated/android/c++/JSearchParams.hpp +3 -9
- package/nitrogen/generated/android/c++/JStarRating.hpp +57 -0
- package/nitrogen/generated/android/c++/{JGateEvent.hpp → JThumbsRating.hpp} +15 -16
- package/nitrogen/generated/android/c++/JTrack.hpp +5 -23
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +1 -14
- package/nitrogen/generated/android/c++/JUpdateOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +34 -0
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +105 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +9 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +4 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt} +12 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_Output.kt → Func_void_IosOutput.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_GateEvent.kt → Func_void_RemoteSetRatingEvent.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Gate.kt → HeartRating.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +109 -103
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +3 -24
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Output.kt → IosOutput.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +4 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +6 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +1 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +9 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → NotificationButton.kt} +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +3 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeAndroidSetupOptions.kt → PartialAndroidSetupPlayerOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeIOSSetupOptions.kt → PartialIOSSetupPlayerOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateDecision.kt → PercentageRating.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateReason.kt → RatingType.kt} +10 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ArtworkVariants.kt → RemoteSetRatingEvent.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +4 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +3 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateEvent.kt → StarRating.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → ThumbsRating.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +4 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +77 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_RemoteButtonLayout.kt → Variant_NullType_NotificationButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +28 -139
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +489 -1125
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +45 -66
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +118 -176
- package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +22 -12
- package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +119 -0
- package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +1 -95
- package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
- package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +4 -17
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +68 -0
- package/nitrogen/generated/ios/swift/{Func_void_Output.swift → Func_void_IosOutput.swift} +10 -10
- package/nitrogen/generated/ios/swift/{Func_void_GateEvent.swift → Func_void_RemoteSetRatingEvent.swift} +10 -10
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
- package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_GateDecision__.swift → Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift} +17 -17
- package/nitrogen/generated/ios/swift/HeartRating.swift +29 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +14 -20
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +197 -230
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +2 -121
- package/nitrogen/generated/ios/swift/{Output.swift → IosOutput.swift} +7 -7
- package/nitrogen/generated/ios/swift/IosOutputType.swift +76 -0
- package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +1 -107
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +22 -161
- package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
- package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +25 -12
- package/nitrogen/generated/ios/swift/NavigationErrorType.swift +0 -8
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +41 -12
- package/nitrogen/generated/ios/swift/{RemoteButton.swift → NotificationButton.swift} +10 -10
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +92 -0
- package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +48 -1
- package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +1 -19
- package/nitrogen/generated/ios/swift/Options.swift +4 -10
- package/nitrogen/generated/ios/swift/{NativeAndroidSetupOptions.swift → PartialAndroidSetupPlayerOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/{NativeIOSSetupOptions.swift → PartialIOSSetupPlayerOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +101 -0
- package/nitrogen/generated/ios/swift/PercentageRating.swift +29 -0
- package/nitrogen/generated/ios/swift/PlaybackError.swift +2 -43
- package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/RatingType.swift +60 -0
- package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +58 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +8 -74
- package/nitrogen/generated/ios/swift/RetryConfig.swift +4 -35
- package/nitrogen/generated/ios/swift/SearchMode.swift +12 -36
- package/nitrogen/generated/ios/swift/SearchParams.swift +2 -7
- package/nitrogen/generated/ios/swift/StarRating.swift +29 -0
- package/nitrogen/generated/ios/swift/ThumbsRating.swift +29 -0
- package/nitrogen/generated/ios/swift/Track.swift +8 -74
- package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +1 -60
- package/nitrogen/generated/ios/swift/UpdateOptions.swift +143 -0
- package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +20 -0
- package/nitrogen/generated/ios/swift/{Variant_NullType_RemoteButtonLayout.swift → Variant_NullType_NotificationButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +19 -8
- package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +109 -0
- package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +2 -10
- package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
- package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +2 -0
- package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +5 -6
- package/nitrogen/generated/shared/c++/{GateEvent.hpp → HeartRating.hpp} +17 -18
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +18 -21
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +35 -44
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +2 -33
- package/nitrogen/generated/shared/c++/{Output.hpp → IosOutput.hpp} +18 -18
- package/nitrogen/generated/shared/c++/IosOutputType.hpp +112 -0
- package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +2 -10
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +15 -31
- package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
- package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +7 -9
- package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +0 -8
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +19 -8
- package/nitrogen/generated/shared/c++/{RemoteButton.hpp → NotificationButton.hpp} +25 -25
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +102 -0
- package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +19 -3
- package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +2 -6
- package/nitrogen/generated/shared/c++/Options.hpp +8 -8
- package/nitrogen/generated/shared/c++/{NativeAndroidSetupOptions.hpp → PartialAndroidSetupPlayerOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/{NativeIOSSetupOptions.hpp → PartialIOSSetupPlayerOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +104 -0
- package/nitrogen/generated/shared/c++/{FavoriteConfig.hpp → PercentageRating.hpp} +17 -18
- package/nitrogen/generated/shared/c++/PlaybackError.hpp +3 -18
- package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/RatingType.hpp +96 -0
- package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +5 -24
- package/nitrogen/generated/shared/c++/RetryConfig.hpp +6 -10
- package/nitrogen/generated/shared/c++/SearchMode.hpp +15 -39
- package/nitrogen/generated/shared/c++/SearchParams.hpp +2 -9
- package/nitrogen/generated/shared/c++/{Gate.hpp → StarRating.hpp} +16 -21
- package/nitrogen/generated/shared/c++/{GateDecision.hpp → ThumbsRating.hpp} +17 -23
- package/nitrogen/generated/shared/c++/Track.hpp +5 -24
- package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +2 -6
- package/nitrogen/generated/shared/c++/UpdateOptions.hpp +111 -0
- package/package.json +5 -12
- package/src/AudioBrowser.ts +1 -4
- package/src/features/browser.ts +201 -58
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +5 -71
- package/src/features/favorites.ts +1 -1
- package/src/features/index.ts +0 -2
- package/src/features/metadata.ts +15 -9
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -37
- package/src/features/output.ts +22 -35
- package/src/features/playback/controls.ts +0 -8
- package/src/features/playback/index.ts +0 -1
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.ts +13 -32
- package/src/features/playback/state.ts +1 -1
- package/src/features/playback/volume.ts +2 -2
- package/src/features/player/options.ts +110 -217
- package/src/features/player/setup.ts +175 -397
- package/src/features/queue/activeTrack.ts +1 -1
- package/src/features/queue/queue.ts +6 -17
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/rating.ts +17 -0
- package/src/features/remoteControls.ts +115 -13
- package/src/features/sleepTimer.ts +5 -24
- package/src/native.ts +0 -5
- package/src/native.web.ts +1 -4
- package/src/specs/audio-browser.nitro.ts +41 -131
- package/src/types/browser-native.ts +6 -19
- package/src/types/browser-nodes.ts +13 -160
- package/src/types/browser.ts +89 -377
- package/src/types/player.ts +69 -0
- package/src/utils/LazyNativeEmitter.ts +8 -9
- package/src/utils/NativeUpdatedValue.ts +2 -0
- package/src/web/NativeAudioBrowser.ts +158 -365
- package/src/web/TrackPlayer/Player.ts +39 -113
- package/src/web/TrackPlayer/PlaylistPlayer.ts +346 -0
- package/src/web/TrackPlayer/RepeatMode.ts +1 -1
- package/src/web/TrackPlayer/SleepTimer.ts +3 -38
- package/src/web/TrackPlayer/index.ts +1 -2
- package/src/web/browser/BrowserManager.ts +62 -231
- package/src/web/browser/NavigationErrorManager.ts +1 -3
- package/src/web/browser/SearchManager.ts +15 -11
- package/src/web/http/HttpClient.ts +35 -5
- package/src/web/http/RequestConfigBuilder.ts +63 -120
- package/src/web/player/NowPlayingManager.ts +5 -6
- package/src/web/player/OptionsManager.ts +18 -12
- package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +0 -37
- package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +0 -34
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +0 -299
- package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +0 -79
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +0 -42
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +0 -35
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +0 -172
- package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +0 -153
- package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +0 -141
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -347
- package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +0 -129
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +0 -85
- package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +0 -56
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +0 -20
- package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +0 -130
- package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +0 -80
- package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +0 -50
- package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +0 -81
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +0 -141
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +0 -37
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +0 -41
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +0 -209
- package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +0 -161
- package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +0 -28
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +0 -24
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +0 -49
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +0 -36
- package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +0 -211
- package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +0 -58
- package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +0 -14
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +0 -165
- package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +0 -46
- package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +0 -39
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +0 -114
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +0 -141
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +0 -177
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +0 -55
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +0 -94
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +0 -203
- package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +0 -56
- package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +0 -216
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +0 -45
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +0 -442
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +0 -190
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +0 -198
- package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +0 -137
- package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +0 -36
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +0 -268
- package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +0 -68
- package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +0 -40
- package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +0 -193
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +0 -107
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +0 -73
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +0 -75
- package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +0 -18
- package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +0 -163
- package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +0 -34
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +0 -28
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +0 -53
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +0 -58
- package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +0 -57
- package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +0 -40
- package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +0 -34
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +0 -50
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +0 -63
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +0 -35
- package/android/src/test/resources/certs/isrg-root-x1.pem +0 -31
- package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
- package/android/src/test/resources/certs/letsencrypt-r13.pem +0 -29
- package/android/src/test/resources/certs/stationplaylist-leaf.pem +0 -34
- package/ios/Browser/MediaResolveComposer.swift +0 -114
- package/ios/Browser/SearchDrillIn.swift +0 -44
- package/ios/Browser/SectionScope.swift +0 -35
- package/ios/Browser/TabBarEntries.swift +0 -15
- package/ios/CarPlay/CPInterfaceController+Safe.swift +0 -126
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/CarPlay/RNABMediaAddHandler.swift +0 -75
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +0 -69
- package/ios/Extension/PlayerCapabilities+Favorite.swift +0 -20
- package/ios/Extension/RequestConfig+Copying.swift +0 -31
- package/ios/Extension/Variant+Artwork.swift +0 -49
- package/ios/NowPlayingInfo/SilentAudioPrimer.swift +0 -73
- package/ios/Player/AirPlayStallJudgement.swift +0 -25
- package/ios/Player/EndOfTrackJudgement.swift +0 -32
- package/ios/Player/MediaIntentCriteria.swift +0 -139
- package/ios/Player/PersistedPlaybackState.swift +0 -11
- package/ios/Player/PlaybackStateStore.swift +0 -27
- package/ios/Player/PlaybackTimer.swift +0 -61
- package/ios/Player/VolumeFader.swift +0 -77
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +0 -47
- package/ios/Tests/AirPlayStallJudgementTests.swift +0 -52
- package/ios/Tests/EndOfTrackJudgementTests.swift +0 -35
- package/ios/Tests/JsonModelsTests.swift +0 -58
- package/ios/Tests/MediaIntentCriteriaTests.swift +0 -170
- package/ios/Tests/MediaLoaderAssetOptionsTests.swift +0 -46
- package/ios/Tests/MediaResolveTests.swift +0 -126
- package/ios/Tests/OnceValueTests.swift +0 -79
- package/ios/Tests/PlaybackStateStoreTests.swift +0 -48
- package/ios/Tests/PlaybackTimerTests.swift +0 -55
- package/ios/Tests/PlayingStateManagerTests.swift +0 -57
- package/ios/Tests/RemoteCommandControllerTests.swift +0 -156
- package/ios/Tests/RemoteCommandTests.swift +0 -67
- package/ios/Tests/RetryManagerTests.swift +0 -89
- package/ios/Tests/SearchDrillInTests.swift +0 -63
- package/ios/Tests/SectionScopeTests.swift +0 -82
- package/ios/Tests/SleepTimerManagerTests.swift +0 -107
- package/ios/Tests/Support/ManualSleepTimerScheduler.swift +0 -47
- package/ios/Tests/TabBarEntriesTests.swift +0 -71
- package/lib/commonjs/features/browser-config.js +0 -227
- package/lib/commonjs/features/browser-config.js.map +0 -1
- package/lib/commonjs/features/carConnection.js +0 -42
- package/lib/commonjs/features/carConnection.js.map +0 -1
- package/lib/commonjs/features/gate.js +0 -149
- package/lib/commonjs/features/gate.js.map +0 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -86
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +0 -1
- package/lib/commonjs/features/player/validateOptions.js +0 -24
- package/lib/commonjs/features/player/validateOptions.js.map +0 -1
- package/lib/commonjs/utils/artwork.js +0 -31
- package/lib/commonjs/utils/artwork.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +0 -71
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +0 -34
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +0 -21
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +0 -273
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +0 -158
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js +0 -75
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +0 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +0 -22
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +0 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +0 -41
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +0 -1
- package/lib/commonjs/web/browser/parseSearchResponse.js +0 -20
- package/lib/commonjs/web/browser/parseSearchResponse.js.map +0 -1
- package/lib/commonjs/web/playbackErrorKind.js +0 -57
- package/lib/commonjs/web/playbackErrorKind.js.map +0 -1
- package/lib/commonjs/web/player/MediaSessionManager.js +0 -121
- package/lib/commonjs/web/player/MediaSessionManager.js.map +0 -1
- package/lib/commonjs/web/player/RemoteCommandController.js +0 -96
- package/lib/commonjs/web/player/RemoteCommandController.js.map +0 -1
- package/lib/module/features/browser-config.js +0 -221
- package/lib/module/features/browser-config.js.map +0 -1
- package/lib/module/features/carConnection.js +0 -37
- package/lib/module/features/carConnection.js.map +0 -1
- package/lib/module/features/gate.js +0 -144
- package/lib/module/features/gate.js.map +0 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -83
- package/lib/module/features/playback/trackPlaybackTime.js.map +0 -1
- package/lib/module/features/player/validateOptions.js +0 -20
- package/lib/module/features/player/validateOptions.js.map +0 -1
- package/lib/module/utils/artwork.js +0 -26
- package/lib/module/utils/artwork.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +0 -67
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlaybackTimer.js +0 -29
- package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js +0 -17
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +0 -269
- package/lib/module/web/TrackPlayer/QueueManager.js.map +0 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +0 -154
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +0 -1
- package/lib/module/web/TrackPlayer/VolumeFader.js +0 -70
- package/lib/module/web/TrackPlayer/VolumeFader.js.map +0 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +0 -18
- package/lib/module/web/browser/assertBrowsePageShape.js.map +0 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +0 -38
- package/lib/module/web/browser/classifyTrackNavigation.js.map +0 -1
- package/lib/module/web/browser/parseSearchResponse.js +0 -16
- package/lib/module/web/browser/parseSearchResponse.js.map +0 -1
- package/lib/module/web/playbackErrorKind.js +0 -52
- package/lib/module/web/playbackErrorKind.js.map +0 -1
- package/lib/module/web/player/MediaSessionManager.js +0 -116
- package/lib/module/web/player/MediaSessionManager.js.map +0 -1
- package/lib/module/web/player/RemoteCommandController.js +0 -92
- package/lib/module/web/player/RemoteCommandController.js.map +0 -1
- package/lib/typescript/src/features/browser-config.d.ts +0 -12
- package/lib/typescript/src/features/browser-config.d.ts.map +0 -1
- package/lib/typescript/src/features/carConnection.d.ts +0 -20
- package/lib/typescript/src/features/carConnection.d.ts.map +0 -1
- package/lib/typescript/src/features/gate.d.ts +0 -78
- package/lib/typescript/src/features/gate.d.ts.map +0 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +0 -34
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +0 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +0 -2
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +0 -1
- package/lib/typescript/src/utils/artwork.d.ts +0 -19
- package/lib/typescript/src/utils/artwork.d.ts.map +0 -1
- package/nitrogen/generated/android/c++/JArtworkVariants.hpp +0 -61
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +0 -58
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +0 -100
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +0 -112
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +0 -121
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +0 -123
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +0 -133
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +0 -121
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +0 -139
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +0 -134
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +0 -131
- package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +0 -79
- package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +0 -80
- package/nitrogen/generated/android/c++/JGate.hpp +0 -62
- package/nitrogen/generated/android/c++/JGateDecision.hpp +0 -64
- package/nitrogen/generated/android/c++/JGateReason.hpp +0 -58
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +0 -95
- package/nitrogen/generated/android/c++/JMediaReference.hpp +0 -58
- package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +0 -74
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +0 -182
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +0 -96
- package/nitrogen/generated/android/c++/JOutputType.hpp +0 -82
- package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +0 -76
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +0 -86
- package/nitrogen/generated/android/c++/JStallReason.hpp +0 -58
- package/nitrogen/generated/android/c++/JTrackRequest.hpp +0 -93
- 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_RemoteButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +0 -41
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +0 -121
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +0 -70
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +0 -23
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeGateRequest.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +0 -66
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +0 -31
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +0 -29
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +0 -26
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +0 -23
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +0 -65
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +0 -53
- package/nitrogen/generated/ios/swift/ArtworkVariants.swift +0 -34
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +0 -67
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +0 -54
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +0 -69
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +0 -54
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +0 -60
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +0 -60
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +0 -61
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +0 -61
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +0 -77
- package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +0 -53
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +0 -66
- package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +0 -81
- package/nitrogen/generated/ios/swift/Gate.swift +0 -47
- package/nitrogen/generated/ios/swift/GateDecision.swift +0 -40
- package/nitrogen/generated/ios/swift/GateEvent.swift +0 -29
- package/nitrogen/generated/ios/swift/GateReason.swift +0 -40
- package/nitrogen/generated/ios/swift/IOSOptions.swift +0 -51
- package/nitrogen/generated/ios/swift/MediaReference.swift +0 -40
- package/nitrogen/generated/ios/swift/NativeGateRequest.swift +0 -58
- package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +0 -206
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +0 -90
- package/nitrogen/generated/ios/swift/OutputType.swift +0 -72
- package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +0 -64
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +0 -57
- package/nitrogen/generated/ios/swift/StallReason.swift +0 -40
- package/nitrogen/generated/ios/swift/TrackRequest.swift +0 -106
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +0 -19
- package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +0 -18
- package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +0 -87
- package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +0 -76
- package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +0 -110
- package/nitrogen/generated/shared/c++/GateReason.hpp +0 -76
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +0 -91
- package/nitrogen/generated/shared/c++/MediaReference.hpp +0 -76
- package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +0 -97
- package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +0 -138
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +0 -92
- package/nitrogen/generated/shared/c++/OutputType.hpp +0 -108
- package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +0 -100
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +0 -94
- package/nitrogen/generated/shared/c++/StallReason.hpp +0 -76
- package/nitrogen/generated/shared/c++/TrackRequest.hpp +0 -93
- package/src/codegen-no-config-variant.test.ts +0 -70
- package/src/features/browser-config.test.ts +0 -110
- package/src/features/browser-config.ts +0 -293
- package/src/features/browser.test.ts +0 -19
- package/src/features/carConnection.ts +0 -36
- package/src/features/gate.test.ts +0 -72
- package/src/features/gate.ts +0 -155
- package/src/features/playback/trackPlaybackTime.test.ts +0 -80
- package/src/features/playback/trackPlaybackTime.ts +0 -101
- package/src/features/player/options.test.ts +0 -38
- package/src/features/player/setup.test.ts +0 -125
- package/src/features/player/validateOptions.ts +0 -22
- package/src/features/queue/queue.test.ts +0 -36
- package/src/public-surface.test.ts +0 -116
- package/src/utils/artwork.ts +0 -30
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +0 -301
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +0 -139
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +0 -82
- package/src/web/TrackPlayer/PlaybackTimer.test.ts +0 -67
- package/src/web/TrackPlayer/PlaybackTimer.ts +0 -33
- package/src/web/TrackPlayer/Player.dispatch.test.ts +0 -229
- package/src/web/TrackPlayer/Player.load.test.ts +0 -86
- package/src/web/TrackPlayer/PlayingStateFactory.test.ts +0 -51
- package/src/web/TrackPlayer/PlayingStateFactory.ts +0 -24
- package/src/web/TrackPlayer/QueueManager.test.ts +0 -289
- package/src/web/TrackPlayer/QueueManager.ts +0 -302
- package/src/web/TrackPlayer/QueuePlayer.ts +0 -188
- package/src/web/TrackPlayer/VolumeFader.ts +0 -74
- package/src/web/browser/__tests__/layer-resolver.test.ts +0 -69
- package/src/web/browser/assertBrowsePageShape.test.ts +0 -27
- package/src/web/browser/assertBrowsePageShape.ts +0 -27
- package/src/web/browser/classifyTrackNavigation.test.ts +0 -41
- package/src/web/browser/classifyTrackNavigation.ts +0 -35
- package/src/web/browser/parseSearchResponse.test.ts +0 -25
- package/src/web/browser/parseSearchResponse.ts +0 -15
- package/src/web/http/RequestConfigBuilder.test.ts +0 -59
- package/src/web/playbackErrorKind.test.ts +0 -55
- package/src/web/playbackErrorKind.ts +0 -56
- package/src/web/player/MediaSessionManager.test.ts +0 -220
- package/src/web/player/MediaSessionManager.ts +0 -151
- package/src/web/player/NowPlayingManager.test.ts +0 -46
- package/src/web/player/RemoteCommandController.test.ts +0 -171
- package/src/web/player/RemoteCommandController.ts +0 -166
|
@@ -29,48 +29,16 @@ public final class RNABCarPlayController: NSObject {
|
|
|
29
29
|
private var isStarted = false
|
|
30
30
|
private var listenerRemovals: [() -> Void] = []
|
|
31
31
|
|
|
32
|
-
///
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
///
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/// Tabs received while the user had templates pushed. Rebuilding the tab bar
|
|
43
|
-
/// replaces the root template, which tears down the pushed navigation stack —
|
|
44
|
-
/// so the rebuild is deferred until the user is back at the tab bar.
|
|
45
|
-
private var pendingTabs: [Track]?
|
|
46
|
-
|
|
47
|
-
/// Whether a gate is active. While set, tabs keep their tab-bar entries but
|
|
48
|
-
/// each path is resolved per-request via `gateDecision(for:)`: a gated path
|
|
49
|
-
/// renders the gate page instead of content; an allowed path shows real
|
|
50
|
-
/// content. Mirrors `audioBrowser.isGateActive`; seeded in start() so a gate
|
|
51
|
-
/// set before the scene connects renders at connect.
|
|
52
|
-
private var isGated = false
|
|
53
|
-
|
|
54
|
-
/// Drops a stale tab-bar build when a newer build supersedes it
|
|
55
|
-
/// (latest-build-wins). Shared across BOTH the gated and non-gated `showTabBar`
|
|
56
|
-
/// paths: each bumps and captures this at entry and bails before any
|
|
57
|
-
/// `setRootTemplate` / `updateTemplates` once it's been bumped past their
|
|
58
|
-
/// captured value. A build suspends the main actor at its `await` points
|
|
59
|
-
/// (per-tab `gateDecision` when gated, first-tab `loadContent` when not), so a
|
|
60
|
-
/// gate change can interleave a build of the opposite kind — most importantly a
|
|
61
|
-
/// `clearGate` kicking the non-gated build while a gated build is mid-await.
|
|
62
|
-
/// Covering both paths with one token means that clear-vs-gated interleave is
|
|
63
|
-
/// caught too (not just gated-vs-gated), so a stale gated build can't paint a
|
|
64
|
-
/// gate page over just-cleared content. Mirrors `albumArtistGeneration` in
|
|
65
|
-
/// `CarPlayNowPlayingManager`.
|
|
66
|
-
private var gateBuildGeneration: UInt = 0
|
|
67
|
-
|
|
68
|
-
/// How long a browse resolve may run before the destination's loading spinner
|
|
69
|
-
/// is replaced with an error state. The selection completion is fired
|
|
70
|
-
/// immediately (so CarPlay never blocks the list — per Apple's async handler
|
|
71
|
-
/// guidance we push the destination and fill it in), so this only bounds how
|
|
72
|
-
/// long the *destination screen* spins. Backing out and re-tapping retries.
|
|
73
|
-
private let resolveTimeout: Duration = .seconds(15)
|
|
32
|
+
/// Current navigation stack paths (for back navigation context)
|
|
33
|
+
private var navigationStack: [String] = []
|
|
34
|
+
|
|
35
|
+
/// Key (src ?? url) of the item currently being selected.
|
|
36
|
+
/// Prevents duplicate taps from triggering parallel async operations.
|
|
37
|
+
private var pendingSelectionKey: String?
|
|
38
|
+
|
|
39
|
+
/// URLs currently being resolved for navigation.
|
|
40
|
+
/// Prevents duplicate taps from pushing the same template twice.
|
|
41
|
+
private var pendingNavigationUrls: Set<String> = []
|
|
74
42
|
|
|
75
43
|
/// Helper object for CPInterfaceControllerDelegate conformance
|
|
76
44
|
private var interfaceDelegate: InterfaceControllerDelegate?
|
|
@@ -89,21 +57,9 @@ public final class RNABCarPlayController: NSObject {
|
|
|
89
57
|
audioBrowser?.browserManager.config ?? BrowserConfig()
|
|
90
58
|
}
|
|
91
59
|
|
|
92
|
-
///
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/// track may have been loaded outside the browse tree (the consumer's own
|
|
96
|
-
/// `load`/`setQueue`) with a `src` that differs textually from the browse
|
|
97
|
-
/// row's (absolute vs relative, extra query params) while being the same
|
|
98
|
-
/// item. Exact `src` equality remains the fallback for consumers that don't
|
|
99
|
-
/// assign ids.
|
|
100
|
-
private func isActiveTrack(id: String?, src: String?) -> Bool {
|
|
101
|
-
guard let currentTrack = audioBrowser?.getPlayer()?.currentTrack else { return false }
|
|
102
|
-
if let id, !id.isEmpty, let currentId = currentTrack.id, !currentId.isEmpty {
|
|
103
|
-
return id == currentId
|
|
104
|
-
}
|
|
105
|
-
guard let src else { return false }
|
|
106
|
-
return currentTrack.src == src
|
|
60
|
+
/// Checks if the given src matches the currently active (loaded) track
|
|
61
|
+
private func isActiveTrack(src: String) -> Bool {
|
|
62
|
+
audioBrowser?.getPlayer()?.currentTrack?.src == src
|
|
107
63
|
}
|
|
108
64
|
|
|
109
65
|
// MARK: - Initialization
|
|
@@ -118,22 +74,10 @@ public final class RNABCarPlayController: NSObject {
|
|
|
118
74
|
self?.listItemFactory?.createListItem(for: track, handler: handler)
|
|
119
75
|
?? CPListItem(text: track.title, detailText: nil)
|
|
120
76
|
}
|
|
121
|
-
nowPlayingManager.navigateToUrl = { [weak self] url, title in
|
|
122
|
-
Task { @MainActor in await self?.navigateToUrl(url, title: title) }
|
|
123
|
-
}
|
|
124
77
|
}
|
|
125
78
|
|
|
126
79
|
// MARK: - Lifecycle
|
|
127
80
|
|
|
128
|
-
/// Reports CarPlay scene connection to the library (drives the JS-side
|
|
129
|
-
/// `isCarPlayConnected` / `onCarPlayConnectedChanged`). Called by
|
|
130
|
-
/// RNABCarPlaySceneDelegate on scene connect/disconnect — deliberately not
|
|
131
|
-
/// from start()/stop(), which also cycle on a JS runtime reload.
|
|
132
|
-
@objc
|
|
133
|
-
public static func setConnected(_ connected: Bool) {
|
|
134
|
-
HybridAudioBrowser.setCarPlayConnected(connected)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
81
|
@objc
|
|
138
82
|
public func start() {
|
|
139
83
|
guard !isStarted else { return }
|
|
@@ -146,18 +90,6 @@ public final class RNABCarPlayController: NSObject {
|
|
|
146
90
|
interfaceDelegate = delegate
|
|
147
91
|
interfaceController.delegate = delegate
|
|
148
92
|
|
|
149
|
-
// Restart when a new HybridAudioBrowser replaces the shared instance
|
|
150
|
-
// (JS runtime reload) — our subscriptions point at the old instance's
|
|
151
|
-
// emitters and would otherwise go silent.
|
|
152
|
-
let instanceToken = HybridAudioBrowser.instanceChangedEmitter.addListener { [weak self] _ in
|
|
153
|
-
Task { @MainActor in
|
|
154
|
-
self?.restart()
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
listenerRemovals.append {
|
|
158
|
-
HybridAudioBrowser.instanceChangedEmitter.removeListener(instanceToken)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
93
|
// Show loading template while waiting
|
|
162
94
|
showLoadingTemplate()
|
|
163
95
|
|
|
@@ -167,21 +99,20 @@ public final class RNABCarPlayController: NSObject {
|
|
|
167
99
|
guard self.isStarted else { return }
|
|
168
100
|
self.logger.debug("AudioBrowser and player ready, setting up CarPlay")
|
|
169
101
|
self.audioBrowser = browser
|
|
170
|
-
self.isGated = browser.isGateActive
|
|
171
102
|
self.trackSelector = TrackSelector(browserManager: browser.browserManager)
|
|
172
103
|
|
|
173
104
|
// Create image loader with CarPlay display traits
|
|
174
105
|
self.imageLoader = CarPlayImageLoader(
|
|
175
106
|
carTraitCollection: self.interfaceController.carTraitCollection,
|
|
176
|
-
browserManager: browser.browserManager
|
|
107
|
+
browserManager: browser.browserManager
|
|
177
108
|
)
|
|
178
109
|
|
|
179
110
|
// Create list item factory
|
|
180
111
|
let factory = CarPlayListItemFactory(
|
|
181
|
-
isActiveTrack: { [weak self]
|
|
112
|
+
isActiveTrack: { [weak self] src in self?.isActiveTrack(src: src) ?? false },
|
|
182
113
|
onItemSelected: { [weak self] track, completion in
|
|
183
114
|
self?.handleItemSelection(track: track, completion: completion)
|
|
184
|
-
}
|
|
115
|
+
}
|
|
185
116
|
)
|
|
186
117
|
factory.imageLoader = self.imageLoader
|
|
187
118
|
self.listItemFactory = factory
|
|
@@ -203,33 +134,18 @@ public final class RNABCarPlayController: NSObject {
|
|
|
203
134
|
logger.info("Stopping CarPlay controller")
|
|
204
135
|
|
|
205
136
|
// Remove all emitter listeners
|
|
206
|
-
for removal in listenerRemovals {
|
|
207
|
-
removal()
|
|
208
|
-
}
|
|
137
|
+
for removal in listenerRemovals { removal() }
|
|
209
138
|
listenerRemovals.removeAll()
|
|
210
139
|
|
|
211
140
|
// Clear config callback
|
|
212
141
|
audioBrowser?.browserManager.onConfigChanged = nil
|
|
213
|
-
audioBrowser?.browserManager.onFavoritesChanged = nil
|
|
214
142
|
|
|
215
143
|
nowPlayingManager.teardown()
|
|
216
144
|
listItemFactory = nil
|
|
217
145
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/// Full stop/start cycle. Used when the JS runtime reloads: the new
|
|
224
|
-
/// HybridAudioBrowser instance replaces the emitters this controller is
|
|
225
|
-
/// subscribed to and resets the readiness gate, so everything must be
|
|
226
|
-
/// re-subscribed against the live instance (start() waits for it).
|
|
227
|
-
@MainActor
|
|
228
|
-
private func restart() {
|
|
229
|
-
guard isStarted else { return }
|
|
230
|
-
logger.info("AudioBrowser instance changed — restarting CarPlay controller")
|
|
231
|
-
stop()
|
|
232
|
-
start()
|
|
146
|
+
navigationStack.removeAll()
|
|
147
|
+
pendingSelectionKey = nil
|
|
148
|
+
pendingNavigationUrls.removeAll()
|
|
233
149
|
}
|
|
234
150
|
|
|
235
151
|
// MARK: - Content Subscriptions
|
|
@@ -260,35 +176,10 @@ public final class RNABCarPlayController: NSObject {
|
|
|
260
176
|
audioBrowser?.contentChangedEmitter.removeListener(contentToken)
|
|
261
177
|
}
|
|
262
178
|
|
|
263
|
-
// Subscribe to config changes (for Now Playing buttons
|
|
264
|
-
// button state — e.g. resolveAlbumUrl appearing/disappearing)
|
|
179
|
+
// Subscribe to config changes (for Now Playing buttons)
|
|
265
180
|
audioBrowser.browserManager.onConfigChanged = { [weak self] _ in
|
|
266
181
|
Task { @MainActor in
|
|
267
182
|
self?.nowPlayingManager.setupNowPlayingButtons()
|
|
268
|
-
self?.nowPlayingManager.updateNowPlayingButtonStates()
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Now-playing buttons + Up Next are runtime-updatable via updateOptions();
|
|
273
|
-
// refresh them when player options change (configureBrowser is no longer
|
|
274
|
-
// the only path that can change them).
|
|
275
|
-
let optionsToken = audioBrowser.playerOptionsChangedEmitter.addListener { [weak self] in
|
|
276
|
-
Task { @MainActor in
|
|
277
|
-
self?.nowPlayingManager.setupNowPlayingButtons()
|
|
278
|
-
self?.nowPlayingManager.updateNowPlayingButtonStates()
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
listenerRemovals.append { [weak audioBrowser] in
|
|
282
|
-
audioBrowser?.playerOptionsChangedEmitter.removeListener(optionsToken)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Refresh the Now Playing heart when favorites change externally (app /
|
|
286
|
-
// webview). The favorite/active-track emitters only fire for the player's
|
|
287
|
-
// own toggles, so without this an in-app favorite leaves the CarPlay heart
|
|
288
|
-
// stale.
|
|
289
|
-
audioBrowser.browserManager.onFavoritesChanged = { [weak self] in
|
|
290
|
-
Task { @MainActor in
|
|
291
|
-
self?.nowPlayingManager.updateFavoriteButtonState()
|
|
292
183
|
}
|
|
293
184
|
}
|
|
294
185
|
|
|
@@ -302,54 +193,14 @@ public final class RNABCarPlayController: NSObject {
|
|
|
302
193
|
audioBrowser?.favoriteChangedEmitter.removeListener(favoriteToken)
|
|
303
194
|
}
|
|
304
195
|
|
|
305
|
-
//
|
|
306
|
-
// `isSelected` (a rebuild, skipped when unchanged) so CarPlay re-renders
|
|
307
|
-
// draw the correct selected background on the first frame — see
|
|
308
|
-
// CarPlayNowPlayingManager.BuiltButtonRow.
|
|
309
|
-
let repeatToken = audioBrowser.repeatModeChangedEmitter.addListener { [weak self] _ in
|
|
310
|
-
Task { @MainActor in
|
|
311
|
-
self?.nowPlayingManager.setupNowPlayingButtons()
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
listenerRemovals.append { [weak audioBrowser] in
|
|
315
|
-
audioBrowser?.repeatModeChangedEmitter.removeListener(repeatToken)
|
|
316
|
-
}
|
|
317
|
-
let shuffleToken = audioBrowser.shuffleChangedEmitter.addListener { [weak self] _ in
|
|
318
|
-
Task { @MainActor in
|
|
319
|
-
self?.nowPlayingManager.setupNowPlayingButtons()
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
listenerRemovals.append { [weak audioBrowser] in
|
|
323
|
-
audioBrowser?.shuffleChangedEmitter.removeListener(shuffleToken)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Subscribe to external content changes (notifyContentChanged /
|
|
327
|
-
// invalidateAllContent).
|
|
196
|
+
// Subscribe to external content changes (from notifyContentChanged)
|
|
328
197
|
let externalContentToken = audioBrowser.externalContentChangedEmitter.addListener { [weak self] path in
|
|
329
|
-
|
|
330
|
-
// listeners synchronously on the emitting (JS) thread.
|
|
331
|
-
Task { @MainActor in
|
|
332
|
-
if path == HybridAudioBrowser.invalidateAllSentinel {
|
|
333
|
-
self?.invalidateAllContent()
|
|
334
|
-
} else {
|
|
335
|
-
self?.notifyContentChanged(path: path)
|
|
336
|
-
}
|
|
337
|
-
}
|
|
198
|
+
self?.notifyContentChanged(path: path)
|
|
338
199
|
}
|
|
339
200
|
listenerRemovals.append { [weak audioBrowser] in
|
|
340
201
|
audioBrowser?.externalContentChangedEmitter.removeListener(externalContentToken)
|
|
341
202
|
}
|
|
342
203
|
|
|
343
|
-
// Subscribe to gate changes (set / in-place update / clear)
|
|
344
|
-
let gateToken = audioBrowser.gateChangedEmitter.addListener { [weak self] active in
|
|
345
|
-
Task { @MainActor in
|
|
346
|
-
self?.handleGateChanged(active)
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
listenerRemovals.append { [weak audioBrowser] in
|
|
350
|
-
audioBrowser?.gateChangedEmitter.removeListener(gateToken)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
204
|
// Subscribe to active track changes (for playing indicator in lists)
|
|
354
205
|
let activeTrackToken = audioBrowser.activeTrackChangedEmitter.addListener { [weak self] event in
|
|
355
206
|
Task { @MainActor in
|
|
@@ -370,17 +221,6 @@ public final class RNABCarPlayController: NSObject {
|
|
|
370
221
|
audioBrowser?.queueChangedEmitter.removeListener(queueToken)
|
|
371
222
|
}
|
|
372
223
|
|
|
373
|
-
// A voice media intent started playback — surface Now Playing so the user
|
|
374
|
-
// lands on the playing station (Up Next holds the rest of the results).
|
|
375
|
-
let showNowPlayingToken = audioBrowser.showNowPlayingRequestedEmitter.addListener { [weak self] _ in
|
|
376
|
-
Task { @MainActor in
|
|
377
|
-
self?.nowPlayingManager.showNowPlaying()
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
listenerRemovals.append { [weak audioBrowser] in
|
|
381
|
-
audioBrowser?.showNowPlayingRequestedEmitter.removeListener(showNowPlayingToken)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
224
|
// Subscribe to navigation errors (from browser layer)
|
|
385
225
|
let navErrorToken = audioBrowser.navigationErrorEmitter.addListener { [weak self] event in
|
|
386
226
|
Task { @MainActor in
|
|
@@ -397,9 +237,8 @@ public final class RNABCarPlayController: NSObject {
|
|
|
397
237
|
private func handleNavigationError(_ event: NavigationErrorEvent) {
|
|
398
238
|
guard let error = event.error else { return }
|
|
399
239
|
logger.warning("Navigation error: \(error.code.stringValue) - \(error.message)")
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
let path = interfaceController.topTemplate.flatMap { getPath(from: $0) } ?? "/"
|
|
240
|
+
// Use current path from navigation stack, or "/" as fallback
|
|
241
|
+
let path = navigationStack.last ?? "/"
|
|
403
242
|
showNavigationError(error, path: path)
|
|
404
243
|
}
|
|
405
244
|
|
|
@@ -409,9 +248,7 @@ public final class RNABCarPlayController: NSObject {
|
|
|
409
248
|
private func buildInitialInterface() async {
|
|
410
249
|
guard let audioBrowser else {
|
|
411
250
|
logger.error("AudioBrowser not available")
|
|
412
|
-
|
|
413
|
-
NavigationError(code: .unknownError, message: "", statusCode: nil, statusCodeSuccess: nil),
|
|
414
|
-
)
|
|
251
|
+
showErrorTemplate(message: "Audio browser not initialized")
|
|
415
252
|
return
|
|
416
253
|
}
|
|
417
254
|
|
|
@@ -423,65 +260,33 @@ public final class RNABCarPlayController: NSObject {
|
|
|
423
260
|
} else {
|
|
424
261
|
// No tabs yet - query them
|
|
425
262
|
logger.info("No tabs available, querying...")
|
|
426
|
-
// Config exists by now (start() waited for it), so re-show the loading
|
|
427
|
-
// root to pick up the app's localized loading title — the first loading
|
|
428
|
-
// template was created before config was available.
|
|
429
|
-
if config.carPlayLoadingTitle != nil {
|
|
430
|
-
showLoadingTemplate()
|
|
431
|
-
}
|
|
432
263
|
do {
|
|
433
264
|
let queriedTabs = try await audioBrowser.browserManager.queryTabs()
|
|
434
265
|
if !queriedTabs.isEmpty {
|
|
435
266
|
await showTabBar(tabs: queriedTabs)
|
|
436
267
|
} else {
|
|
437
|
-
|
|
438
|
-
// the app's formatNavigationError like every other failure (ADR 0001).
|
|
439
|
-
await showRootNavigationError(
|
|
440
|
-
NavigationError(code: .emptyContent, message: "", statusCode: nil, statusCodeSuccess: nil),
|
|
441
|
-
)
|
|
268
|
+
showErrorTemplate(message: "No content available")
|
|
442
269
|
}
|
|
443
270
|
} catch {
|
|
444
271
|
logger.error("Failed to query tabs: \(error.localizedDescription)")
|
|
445
|
-
|
|
272
|
+
showErrorTemplate(message: "Failed to load content")
|
|
446
273
|
}
|
|
447
274
|
}
|
|
448
275
|
}
|
|
449
276
|
|
|
450
|
-
/// Shows a loading template
|
|
451
|
-
/// the browser is configured this can only show the spinner (iOS 18.4+) — the
|
|
452
|
-
/// app's `carPlayLoadingTitle` isn't known yet; `buildInitialInterface`
|
|
453
|
-
/// re-shows it once config is available.
|
|
277
|
+
/// Shows a loading template while waiting for initialization
|
|
454
278
|
private func showLoadingTemplate() {
|
|
455
|
-
let template =
|
|
456
|
-
|
|
279
|
+
let template = CPListTemplate(
|
|
280
|
+
title: nil,
|
|
281
|
+
sections: [],
|
|
282
|
+
)
|
|
283
|
+
interfaceController.setRootTemplate(template, animated: false, completion: nil)
|
|
457
284
|
}
|
|
458
285
|
|
|
459
286
|
// MARK: - Tab Bar
|
|
460
287
|
|
|
461
288
|
@MainActor
|
|
462
289
|
private func showTabBar(tabs: [Track]) async {
|
|
463
|
-
// Serialize concurrent tab-bar builds across BOTH the gated and non-gated
|
|
464
|
-
// paths with one shared generation token: bump at entry, capture, and bail
|
|
465
|
-
// before any template mutation once a newer build supersedes us. Both paths
|
|
466
|
-
// suspend the main actor at `await` points (per-tab `gateDecision` when
|
|
467
|
-
// gated, first-tab `loadContent` when not), so a gate change can interleave
|
|
468
|
-
// a build of the opposite kind — a `clearGate` kicking the non-gated build
|
|
469
|
-
// while a gated build is mid-await, or vice versa. A single token covers the
|
|
470
|
-
// clear-vs-gated interleave too, not just gated-vs-gated: the superseding
|
|
471
|
-
// build bumps it, and the in-flight build bails before painting (preventing
|
|
472
|
-
// a stale gate page over just-cleared content, or a clobbered root template).
|
|
473
|
-
gateBuildGeneration &+= 1
|
|
474
|
-
let generation = gateBuildGeneration
|
|
475
|
-
|
|
476
|
-
// While gated, each tab path is resolved per-request: a gated tab renders
|
|
477
|
-
// the gate page with that request's chrome; an allowed tab shows real
|
|
478
|
-
// content. This generalizes the old "all tabs show the gate" — a path the
|
|
479
|
-
// resolver allows now shows real content.
|
|
480
|
-
if isGated {
|
|
481
|
-
await showGatedTabBar(tabs: tabs, generation: generation)
|
|
482
|
-
return
|
|
483
|
-
}
|
|
484
|
-
|
|
485
290
|
logger.info("Building tab bar with \(tabs.count) tabs")
|
|
486
291
|
|
|
487
292
|
let maxTabs = CPTabBarTemplate.maximumTabCount
|
|
@@ -491,14 +296,10 @@ public final class RNABCarPlayController: NSObject {
|
|
|
491
296
|
createTabTemplate(for: tab)
|
|
492
297
|
}
|
|
493
298
|
|
|
494
|
-
// A newer build (e.g. a gate raised mid-clear) superseded us — don't clobber
|
|
495
|
-
// its root template.
|
|
496
|
-
guard gateBuildGeneration == generation else { return }
|
|
497
|
-
|
|
498
299
|
// Set the tab bar immediately so UI appears fast
|
|
499
300
|
logger.info("Setting tab bar root template with \(tabTemplates.count) templates")
|
|
500
301
|
let tabBar = CPTabBarTemplate(templates: tabTemplates)
|
|
501
|
-
interfaceController.
|
|
302
|
+
interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
|
|
502
303
|
|
|
503
304
|
// Load content for the first tab only - others load lazily when selected
|
|
504
305
|
if let firstTemplate = tabTemplates.first, let firstTab = tabs.first, let url = firstTab.url {
|
|
@@ -506,93 +307,27 @@ public final class RNABCarPlayController: NSObject {
|
|
|
506
307
|
}
|
|
507
308
|
}
|
|
508
309
|
|
|
509
|
-
///
|
|
510
|
-
/// the single gate choke point (`gateDecision(for:)`): a gated tab becomes a
|
|
511
|
-
/// gate page carrying that decision's chrome (and fires `onGate`); an allowed
|
|
512
|
-
/// tab becomes a normal content shell that lazy-loads. With a static gate (no
|
|
513
|
-
/// resolver) every tab is gated with the default chrome — today's behaviour.
|
|
514
|
-
///
|
|
515
|
-
/// `generation` is the shared `gateBuildGeneration` captured by the calling
|
|
516
|
-
/// `showTabBar`; we bail before any template mutation once a newer build (of
|
|
517
|
-
/// either kind) bumps it past `generation`.
|
|
518
|
-
@MainActor
|
|
519
|
-
private func showGatedTabBar(tabs: [Track], generation: UInt) async {
|
|
520
|
-
guard let audioBrowser else { return }
|
|
521
|
-
|
|
522
|
-
guard !tabs.isEmpty else {
|
|
523
|
-
// Tabs unknown (config not loaded yet, or none) — resolve the root path
|
|
524
|
-
// and show a single gate page (or, if allowed, fall back to a normal
|
|
525
|
-
// build once tabs arrive).
|
|
526
|
-
let outcome = await audioBrowser.gateDecision(
|
|
527
|
-
for: NativeGateRequest(reason: .browse, path: nil, search: nil),
|
|
528
|
-
)
|
|
529
|
-
guard gateBuildGeneration == generation else { return } // superseded by a newer build
|
|
530
|
-
if outcome.gated {
|
|
531
|
-
audioBrowser.onGate(GateEvent(reason: .browse))
|
|
532
|
-
interfaceController.safeSetRoot(
|
|
533
|
-
makeGateTemplate(gate: outcome.chrome, tab: nil), animated: true,
|
|
534
|
-
)
|
|
535
|
-
}
|
|
536
|
-
return
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
let limitedTabs = Array(tabs.prefix(CPTabBarTemplate.maximumTabCount))
|
|
540
|
-
var templates: [CPListTemplate] = []
|
|
541
|
-
for tab in limitedTabs {
|
|
542
|
-
let outcome = await audioBrowser.gateDecision(
|
|
543
|
-
for: NativeGateRequest(reason: .browse, path: tab.url, search: nil),
|
|
544
|
-
)
|
|
545
|
-
guard gateBuildGeneration == generation else { return } // superseded by a newer build
|
|
546
|
-
if outcome.gated {
|
|
547
|
-
audioBrowser.onGate(GateEvent(reason: .browse))
|
|
548
|
-
templates.append(makeGateTemplate(gate: outcome.chrome, tab: tab))
|
|
549
|
-
} else {
|
|
550
|
-
// This path is allowed even though a gate is active — show real content.
|
|
551
|
-
templates.append(createTabTemplate(for: tab))
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate,
|
|
556
|
-
tabBar.templates.count == templates.count
|
|
557
|
-
{
|
|
558
|
-
// Equal-count in-place swap keeps the selected tab index.
|
|
559
|
-
tabBar.updateTemplates(templates)
|
|
560
|
-
} else {
|
|
561
|
-
interfaceController.safeSetRoot(
|
|
562
|
-
CPTabBarTemplate(templates: templates), animated: true,
|
|
563
|
-
)
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Eagerly fill any allowed (content) tabs: templateDidAppear isn't
|
|
567
|
-
// guaranteed to re-fire for templates swapped in via updateTemplates.
|
|
568
|
-
for (tab, template) in zip(limitedTabs, templates) {
|
|
569
|
-
guard getPath(from: template) != nil, let url = tab.url else { continue }
|
|
570
|
-
await loadContent(for: url, into: template)
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/// Creates a tab template shell without loading content (synchronous).
|
|
575
|
-
/// The shell carries the loading state (spinner / `carPlayLoadingTitle`)
|
|
576
|
-
/// until its content lazy-loads on first appearance.
|
|
310
|
+
/// Creates a tab template shell without loading content (synchronous)
|
|
577
311
|
private func createTabTemplate(for track: Track) -> CPListTemplate {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
312
|
+
let template = CPListTemplate(
|
|
313
|
+
title: track.title,
|
|
314
|
+
sections: [],
|
|
315
|
+
)
|
|
583
316
|
|
|
584
|
-
/// Stamps a template's tab-bar entry: title and image (default, SF Symbol,
|
|
585
|
-
/// or loaded artwork). Shared by content tabs and gate-page tabs so a gate
|
|
586
|
-
/// keeps the familiar tab bar.
|
|
587
|
-
private func applyTabBarEntry(to template: CPTemplate, for track: Track) {
|
|
588
317
|
// Set tab title explicitly (required for tab bar display)
|
|
589
318
|
template.tabTitle = track.title
|
|
590
319
|
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
320
|
+
// Store path for lazy loading and refresh
|
|
321
|
+
if let url = track.url {
|
|
322
|
+
template.userInfo = ["path": url] as [String: Any]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Set tab image - CarPlay requires an image for proper tab display
|
|
326
|
+
// Tab bar icons are 24pt x 24pt per CarPlay Developer Guide
|
|
327
|
+
// https://developer.apple.com/download/files/CarPlay-Developer-Guide.pdf
|
|
328
|
+
template.tabImage = imageLoader?.defaultTabImage()
|
|
329
|
+
|
|
330
|
+
if let artwork = track.artwork, SFSymbolRenderer.isSFSymbol(artwork) {
|
|
596
331
|
let (symbolName, _, _) = SFSymbolRenderer.parseArtwork(artwork)
|
|
597
332
|
if let image = imageLoader?.sfSymbolImage(symbolName) {
|
|
598
333
|
template.tabImage = image
|
|
@@ -607,15 +342,28 @@ public final class RNABCarPlayController: NSObject {
|
|
|
607
342
|
}
|
|
608
343
|
}
|
|
609
344
|
}
|
|
610
|
-
} else {
|
|
611
|
-
// No icon supplied: clear any prior image so a restamp (handleTabsChanged)
|
|
612
|
-
// of an existing template doesn't leave a stale icon on a now title-only tab.
|
|
613
|
-
template.tabImage = nil
|
|
614
345
|
}
|
|
346
|
+
|
|
347
|
+
return template
|
|
615
348
|
}
|
|
616
349
|
|
|
617
350
|
// MARK: - List Templates
|
|
618
351
|
|
|
352
|
+
@MainActor
|
|
353
|
+
private func createListTemplate(
|
|
354
|
+
for resolvedTrack: ResolvedTrack,
|
|
355
|
+
path: String,
|
|
356
|
+
) -> CPListTemplate {
|
|
357
|
+
let template = CPListTemplate(
|
|
358
|
+
title: resolvedTrack.title,
|
|
359
|
+
sections: [],
|
|
360
|
+
)
|
|
361
|
+
updateTemplate(template, with: resolvedTrack)
|
|
362
|
+
template.userInfo = ["path": path] as [String: Any]
|
|
363
|
+
|
|
364
|
+
return template
|
|
365
|
+
}
|
|
366
|
+
|
|
619
367
|
/// Finds the path associated with a template, if any
|
|
620
368
|
private func getPath(from template: CPTemplate) -> String? {
|
|
621
369
|
(template.userInfo as? [String: Any])?["path"] as? String
|
|
@@ -631,6 +379,7 @@ public final class RNABCarPlayController: NSObject {
|
|
|
631
379
|
/// Configures the assistant cell ("Ask Siri to Play Audio") on a template
|
|
632
380
|
/// based on the `carPlaySiriListButton` property of the resolved content.
|
|
633
381
|
private func configureAssistantCell(on template: CPListTemplate, from resolvedTrack: ResolvedTrack) {
|
|
382
|
+
guard #available(iOS 15.4, *) else { return }
|
|
634
383
|
guard let position = resolvedTrack.carPlaySiriListButton else {
|
|
635
384
|
template.assistantCellConfiguration = nil
|
|
636
385
|
return
|
|
@@ -645,96 +394,15 @@ public final class RNABCarPlayController: NSObject {
|
|
|
645
394
|
|
|
646
395
|
// MARK: - Content Loading
|
|
647
396
|
|
|
648
|
-
/// Resolves `path` and fills an empty (loading) template. Driven by
|
|
649
|
-
/// `templateDidAppear` → `loadContentIfNeeded`, so it runs once the template is
|
|
650
|
-
/// on screen — the timing where CarPlay actually applies updates (updates made
|
|
651
|
-
/// earlier, e.g. straight after push, are dropped). Shows a centered empty
|
|
652
|
-
/// state on empty/error/timeout.
|
|
653
397
|
@MainActor
|
|
654
398
|
private func loadContent(for path: String, into template: CPListTemplate) async {
|
|
655
399
|
guard let audioBrowser else { return }
|
|
656
400
|
|
|
657
|
-
// Single-flight: don't start a second load if one is already in flight for
|
|
658
|
-
// this template (e.g. the user backed out and into a still-loading screen).
|
|
659
|
-
let templateId = ObjectIdentifier(template)
|
|
660
|
-
guard !loadingTemplates.contains(templateId) else { return }
|
|
661
|
-
loadingTemplates.insert(templateId)
|
|
662
|
-
defer { loadingTemplates.remove(templateId) }
|
|
663
|
-
|
|
664
|
-
// Watchdog: if still loading (no rows) after the timeout, show an error.
|
|
665
|
-
let timeout = resolveTimeout
|
|
666
|
-
let watchdog = Task { @MainActor [weak self] in
|
|
667
|
-
do { try await Task.sleep(for: timeout) } catch { return } // cancelled → abort
|
|
668
|
-
guard let self, template.sections.isEmpty else { return }
|
|
669
|
-
self.logger.error("loadContent: resolve timed out for \(path)")
|
|
670
|
-
// A timeout is a navigation error (code .timeout) too, so it formats through
|
|
671
|
-
// the app's formatNavigationError like every other browse failure.
|
|
672
|
-
let timedOut = NavigationError(
|
|
673
|
-
code: .timeout, message: "", statusCode: nil, statusCodeSuccess: nil,
|
|
674
|
-
)
|
|
675
|
-
await self.showNavigationErrorView(timedOut, path: path, on: template)
|
|
676
|
-
}
|
|
677
|
-
|
|
678
401
|
do {
|
|
679
402
|
let resolved = try await audioBrowser.browserManager.resolve(path, useCache: true)
|
|
680
|
-
|
|
681
|
-
// A page carrying an assistant cell (carPlaySiriListButton) is meaningful
|
|
682
|
-
// even with no rows — the "Ask Siri to Play" cell IS its content — so it
|
|
683
|
-
// renders rather than falling through to the empty-content state.
|
|
684
|
-
let hasAssistantCell = resolved.carPlaySiriListButton != nil
|
|
685
|
-
if resolved.children?.isEmpty ?? true, !hasAssistantCell {
|
|
686
|
-
// Empty is modeled as a navigation error (code .emptyContent) so it goes
|
|
687
|
-
// through the same path-aware formatter as failures — letting an app give
|
|
688
|
-
// an empty Favorites tab different copy than an empty search. ADR 0001.
|
|
689
|
-
let empty = NavigationError(
|
|
690
|
-
code: .emptyContent, message: "", statusCode: nil, statusCodeSuccess: nil,
|
|
691
|
-
)
|
|
692
|
-
await showNavigationErrorView(empty, path: path, on: template)
|
|
693
|
-
} else {
|
|
694
|
-
updateTemplate(template, with: resolved)
|
|
695
|
-
}
|
|
403
|
+
updateTemplate(template, with: resolved)
|
|
696
404
|
} catch {
|
|
697
|
-
watchdog.cancel()
|
|
698
405
|
logger.error("Failed to load content for \(path): \(error.localizedDescription)")
|
|
699
|
-
await showNavigationErrorView(NavigationError.from(error), path: path, on: template)
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
/// Renders a navigation error as the template's centered empty/error view,
|
|
704
|
-
/// formatted via the app's `formatNavigationError` (or the built-in default):
|
|
705
|
-
/// `title → view title`, `message → subtitle`. Used for both real failures and
|
|
706
|
-
/// the empty-content case (ADR 0001).
|
|
707
|
-
@MainActor
|
|
708
|
-
private func showNavigationErrorView(
|
|
709
|
-
_ navError: NavigationError,
|
|
710
|
-
path: String,
|
|
711
|
-
on template: CPListTemplate,
|
|
712
|
-
) async {
|
|
713
|
-
let formatted = await formattedNavigationError(navError, path: path)
|
|
714
|
-
showMessage(
|
|
715
|
-
on: template,
|
|
716
|
-
title: formatted.title,
|
|
717
|
-
subtitle: formatted.message.flatMap { $0.isEmpty ? nil : $0 },
|
|
718
|
-
)
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/// Resolves a navigation error to its display form via the app's
|
|
722
|
-
/// `formatNavigationError` callback, falling back to the default if unset/failed.
|
|
723
|
-
@MainActor
|
|
724
|
-
private func formattedNavigationError(
|
|
725
|
-
_ navError: NavigationError,
|
|
726
|
-
path: String,
|
|
727
|
-
) async -> FormattedNavigationError {
|
|
728
|
-
let fallback = navError.defaultFormatted()
|
|
729
|
-
guard let formatter = config.formatNavigationError else { return fallback }
|
|
730
|
-
let params = FormatNavigationErrorParams(
|
|
731
|
-
error: navError, defaultFormatted: fallback, path: path,
|
|
732
|
-
)
|
|
733
|
-
do {
|
|
734
|
-
let custom = try await formatter(params).await()
|
|
735
|
-
return custom ?? fallback
|
|
736
|
-
} catch {
|
|
737
|
-
return fallback
|
|
738
406
|
}
|
|
739
407
|
}
|
|
740
408
|
|
|
@@ -748,8 +416,17 @@ public final class RNABCarPlayController: NSObject {
|
|
|
748
416
|
return
|
|
749
417
|
}
|
|
750
418
|
|
|
751
|
-
//
|
|
752
|
-
|
|
419
|
+
// Deduplicate: ignore if the same item is already being loaded
|
|
420
|
+
let key = track.src ?? track.url
|
|
421
|
+
if let key, key == pendingSelectionKey {
|
|
422
|
+
completion()
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
pendingSelectionKey = key
|
|
426
|
+
|
|
427
|
+
// If this track is already loaded, resume playback and show Now Playing
|
|
428
|
+
if let src = track.src, isActiveTrack(src: src) {
|
|
429
|
+
pendingSelectionKey = nil
|
|
753
430
|
try? audioBrowser.play()
|
|
754
431
|
nowPlayingManager.showNowPlaying()
|
|
755
432
|
completion()
|
|
@@ -757,171 +434,78 @@ public final class RNABCarPlayController: NSObject {
|
|
|
757
434
|
}
|
|
758
435
|
|
|
759
436
|
guard let player = audioBrowser.getPlayer(), let trackSelector else {
|
|
437
|
+
pendingSelectionKey = nil
|
|
760
438
|
completion()
|
|
761
439
|
return
|
|
762
440
|
}
|
|
763
441
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
// the tapped track's metadata, instead of after selection resolves: queue
|
|
767
|
-
// expansion and the media URL resolve can both hit the network, and waiting
|
|
768
|
-
// animates in a blank Now Playing screen. The load pipeline re-publishes
|
|
769
|
-
// the same fields and dedupes.
|
|
770
|
-
if track.src != nil {
|
|
771
|
-
player.loadNowPlayingMetadata(for: track)
|
|
772
|
-
// Derive the album line's destination from the tapped track before the
|
|
773
|
-
// push — CarPlay reads isAlbumArtistButtonEnabled at display time.
|
|
774
|
-
nowPlayingManager.prepareAlbumArtistButton(for: track)
|
|
775
|
-
nowPlayingManager.showNowPlaying()
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// Release CarPlay immediately so the list never locks up. Apple's handler
|
|
779
|
-
// guidance is to finish processing the tap promptly; for a browse we "finish"
|
|
780
|
-
// by pushing the destination and filling it in (see navigateToUrl), and for
|
|
781
|
-
// playback the Now Playing surface owns its own loading state.
|
|
782
|
-
completion()
|
|
783
|
-
|
|
784
|
-
Task { [weak self] in
|
|
442
|
+
Task {
|
|
443
|
+
defer { self.pendingSelectionKey = nil }
|
|
785
444
|
let result = await trackSelector.select(track: track, player: player)
|
|
786
|
-
guard let self else { return }
|
|
787
445
|
switch result {
|
|
788
|
-
case
|
|
446
|
+
case .play(let intent):
|
|
789
447
|
self.executePlayback(intent, player: player)
|
|
790
|
-
self.nowPlayingManager.showNowPlaying(
|
|
448
|
+
self.nowPlayingManager.showNowPlaying()
|
|
791
449
|
case .intercepted:
|
|
792
|
-
self.nowPlayingManager.showNowPlaying(
|
|
793
|
-
case
|
|
794
|
-
|
|
450
|
+
self.nowPlayingManager.showNowPlaying()
|
|
451
|
+
case .browse(let url):
|
|
452
|
+
self.navigateToUrl(url, completion: completion)
|
|
453
|
+
return // navigateToUrl handles its own completion
|
|
795
454
|
case .none:
|
|
796
455
|
break
|
|
797
456
|
}
|
|
457
|
+
completion()
|
|
798
458
|
}
|
|
799
459
|
}
|
|
800
460
|
|
|
801
461
|
private func executePlayback(_ intent: TrackSelector.PlaybackIntent, player: TrackPlayer) {
|
|
802
462
|
switch intent {
|
|
803
|
-
case
|
|
463
|
+
case .skipTo(let index):
|
|
804
464
|
try? player.skipTo(index, playWhenReady: true)
|
|
805
|
-
case
|
|
465
|
+
case .setQueue(let tracks, let startIndex, let sourcePath):
|
|
806
466
|
player.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: sourcePath)
|
|
807
|
-
case
|
|
467
|
+
case .loadTrack(let track):
|
|
808
468
|
player.load(track, playWhenReady: true)
|
|
809
469
|
}
|
|
810
470
|
}
|
|
811
471
|
|
|
812
|
-
///
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
/// right after a push are dropped). Backing out and re-tapping retries.
|
|
817
|
-
@MainActor
|
|
818
|
-
private func navigateToUrl(_ url: String, title: String) async {
|
|
819
|
-
// The gate is resolved per-request: a gated path pushes the gate page
|
|
820
|
-
// (with this request's chrome) instead of browsing into content; this also
|
|
821
|
-
// blocks indirect entries like the Now Playing album line. An allowed path
|
|
822
|
-
// browses normally even while a gate is active.
|
|
823
|
-
if isGated, let audioBrowser {
|
|
824
|
-
let outcome = await audioBrowser.gateDecision(
|
|
825
|
-
for: NativeGateRequest(reason: .browse, path: url, search: nil),
|
|
826
|
-
)
|
|
827
|
-
if outcome.gated {
|
|
828
|
-
audioBrowser.onGate(GateEvent(reason: .browse))
|
|
829
|
-
if let top = interfaceController.topTemplate, getPath(from: top) == url { return }
|
|
830
|
-
interfaceController.safePush(
|
|
831
|
-
makeGateTemplate(gate: outcome.chrome, tab: nil), animated: true,
|
|
832
|
-
)
|
|
833
|
-
return
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
// Avoid pushing a duplicate if the top template already shows this path.
|
|
837
|
-
if let top = interfaceController.topTemplate, getPath(from: top) == url {
|
|
472
|
+
/// Navigates to a browsable URL path, showing error action sheet on failure with retry option.
|
|
473
|
+
private func navigateToUrl(_ url: String, completion: @escaping () -> Void) {
|
|
474
|
+
guard let audioBrowser else {
|
|
475
|
+
completion()
|
|
838
476
|
return
|
|
839
477
|
}
|
|
840
|
-
// …and guard a rapid double-tap whose first push hasn't appeared yet (so the
|
|
841
|
-
// check above can't see it). Cleared when the pushed template appears.
|
|
842
|
-
guard !navigatingPaths.contains(url) else { return }
|
|
843
|
-
navigatingPaths.insert(url)
|
|
844
|
-
|
|
845
|
-
let template = makeLoadingTemplate(title: title, path: url)
|
|
846
|
-
interfaceController.safePush(template, animated: true) { [weak self] pushed, error in
|
|
847
|
-
guard !pushed else { return }
|
|
848
|
-
// The push can fail (e.g. CarPlay's template stack depth limit). The
|
|
849
|
-
// appear callback that normally clears the guard will never fire, so
|
|
850
|
-
// clear it here — otherwise the destination stays unreachable until
|
|
851
|
-
// CarPlay reconnects.
|
|
852
|
-
self?.navigatingPaths.remove(url)
|
|
853
|
-
if let error {
|
|
854
|
-
self?.logger.error("pushTemplate failed for \(url): \(error.localizedDescription)")
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
/// Builds an empty list template for content that is still resolving.
|
|
860
|
-
/// On iOS 18.4+ it shows the system spinner (`showsSpinnerWhileEmpty`). On older
|
|
861
|
-
/// iOS it shows the app-localized `carPlayLoadingTitle` as the centered empty
|
|
862
|
-
/// state when configured; otherwise it stays blank rather than ship a
|
|
863
|
-
/// hardcoded, un-localized "Loading…". The empty view is set at creation —
|
|
864
|
-
/// the timing CarPlay renders reliably (see `replaceWithMessage`).
|
|
865
|
-
private func makeLoadingTemplate(title: String?, path: String?) -> CPListTemplate {
|
|
866
|
-
let template = CPListTemplate(title: title, sections: [])
|
|
867
|
-
if let path {
|
|
868
|
-
template.userInfo = ["path": path] as [String: Any]
|
|
869
|
-
}
|
|
870
|
-
if let loadingTitle = config.carPlayLoadingTitle {
|
|
871
|
-
// Subtitle slot on purpose: the title slot renders at CarPlay's large
|
|
872
|
-
// empty-state size, which is too heavy for a transient loading hint.
|
|
873
|
-
template.emptyViewSubtitleVariants = [loadingTitle]
|
|
874
|
-
}
|
|
875
|
-
if #available(iOS 18.4, *) {
|
|
876
|
-
template.showsSpinnerWhileEmpty = true
|
|
877
|
-
}
|
|
878
|
-
return template
|
|
879
|
-
}
|
|
880
478
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
private func showMessage(on template: CPListTemplate, title: String, subtitle: String?) {
|
|
886
|
-
if isTabRoot(template) {
|
|
887
|
-
if #available(iOS 18.4, *) {
|
|
888
|
-
template.showsSpinnerWhileEmpty = false
|
|
889
|
-
}
|
|
890
|
-
template.emptyViewTitleVariants = [title.isEmpty ? "Couldn't load" : title]
|
|
891
|
-
template.emptyViewSubtitleVariants = subtitle.map { [$0] } ?? []
|
|
892
|
-
template.updateSections([])
|
|
893
|
-
} else {
|
|
894
|
-
replaceWithMessage(template, title: title, subtitle: subtitle)
|
|
479
|
+
// Deduplicate: ignore if already navigating to this URL
|
|
480
|
+
guard !pendingNavigationUrls.contains(url) else {
|
|
481
|
+
completion()
|
|
482
|
+
return
|
|
895
483
|
}
|
|
896
|
-
|
|
484
|
+
pendingNavigationUrls.insert(url)
|
|
897
485
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
486
|
+
Task {
|
|
487
|
+
defer { self.pendingNavigationUrls.remove(url) }
|
|
488
|
+
do {
|
|
489
|
+
let resolved = try await audioBrowser.browserManager.resolve(url, useCache: true)
|
|
490
|
+
|
|
491
|
+
// Guard against duplicate push if top template already shows this path
|
|
492
|
+
if let top = self.interfaceController.topTemplate,
|
|
493
|
+
self.getPath(from: top) == url
|
|
494
|
+
{
|
|
495
|
+
completion()
|
|
496
|
+
return
|
|
497
|
+
}
|
|
904
498
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
// Only replace if the loading template is still the visible top template
|
|
916
|
-
// (the user may have backed out while the resolve was in flight).
|
|
917
|
-
guard interfaceController.topTemplate === loadingTemplate else { return }
|
|
918
|
-
|
|
919
|
-
let message = CPListTemplate(title: loadingTemplate.title, sections: [])
|
|
920
|
-
message.emptyViewTitleVariants = [title.isEmpty ? "Couldn't load" : title]
|
|
921
|
-
message.emptyViewSubtitleVariants = subtitle.map { [$0] } ?? []
|
|
922
|
-
|
|
923
|
-
interfaceController.safePop(animated: false) { [weak self] _, _ in
|
|
924
|
-
self?.interfaceController.safePush(message, animated: false)
|
|
499
|
+
let listTemplate = self.createListTemplate(for: resolved, path: url)
|
|
500
|
+
self.navigationStack.append(url)
|
|
501
|
+
self.interfaceController.pushTemplate(listTemplate, animated: true, completion: nil)
|
|
502
|
+
completion()
|
|
503
|
+
} catch {
|
|
504
|
+
logger.error("Failed to navigate to \(url): \(error.localizedDescription)")
|
|
505
|
+
let navError = NavigationError.from(error)
|
|
506
|
+
self.showNavigationError(navError, path: url)
|
|
507
|
+
completion()
|
|
508
|
+
}
|
|
925
509
|
}
|
|
926
510
|
}
|
|
927
511
|
|
|
@@ -930,49 +514,12 @@ public final class RNABCarPlayController: NSObject {
|
|
|
930
514
|
@MainActor
|
|
931
515
|
private func handleTabsChanged(_ tabs: [Track]) {
|
|
932
516
|
logger.debug("Tabs changed: \(tabs.count) tabs")
|
|
933
|
-
//
|
|
934
|
-
// (e.g. titles after a locale switch): restamp them in place. No rebuild,
|
|
935
|
-
// so the selected tab and any pushed navigation stack survive. (Gate
|
|
936
|
-
// templates carry no path, so a gated tab bar never matches here.)
|
|
937
|
-
if !isGated,
|
|
938
|
-
let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate,
|
|
939
|
-
tabBar.templates.count == min(tabs.count, CPTabBarTemplate.maximumTabCount),
|
|
940
|
-
zip(tabBar.templates, tabs).allSatisfy({ getPath(from: $0) == $1.url })
|
|
941
|
-
{
|
|
942
|
-
for (template, tab) in zip(tabBar.templates, tabs) {
|
|
943
|
-
applyTabBarEntry(to: template, for: tab)
|
|
944
|
-
}
|
|
945
|
-
pendingTabs = nil
|
|
946
|
-
return
|
|
947
|
-
}
|
|
948
|
-
// Rebuilding replaces the root template, tearing down any pushed
|
|
949
|
-
// navigation stack. If the user is browsing, defer until they're back at
|
|
950
|
-
// the tab bar (applied from templateDidAppear).
|
|
951
|
-
guard interfaceController.templates.count <= 1 else {
|
|
952
|
-
pendingTabs = tabs
|
|
953
|
-
return
|
|
954
|
-
}
|
|
955
|
-
pendingTabs = nil
|
|
517
|
+
// Rebuild tab bar if we're at the root
|
|
956
518
|
Task {
|
|
957
519
|
await showTabBar(tabs: tabs)
|
|
958
520
|
}
|
|
959
521
|
}
|
|
960
522
|
|
|
961
|
-
/// Applies a deferred tab change once the user has popped back to the tab
|
|
962
|
-
/// bar. Returns true if a rebuild was kicked off (the appeared template is
|
|
963
|
-
/// about to be replaced, so callers should skip further work on it).
|
|
964
|
-
@MainActor
|
|
965
|
-
fileprivate func applyPendingTabsIfAtRoot() -> Bool {
|
|
966
|
-
guard let tabs = pendingTabs, interfaceController.templates.count <= 1 else {
|
|
967
|
-
return false
|
|
968
|
-
}
|
|
969
|
-
pendingTabs = nil
|
|
970
|
-
Task {
|
|
971
|
-
await showTabBar(tabs: tabs)
|
|
972
|
-
}
|
|
973
|
-
return true
|
|
974
|
-
}
|
|
975
|
-
|
|
976
523
|
@MainActor
|
|
977
524
|
private func handleContentChanged(_ content: ResolvedTrack?) {
|
|
978
525
|
// This callback fires when the main browser's content changes.
|
|
@@ -985,9 +532,8 @@ public final class RNABCarPlayController: NSObject {
|
|
|
985
532
|
private func handleActiveTrackChanged(_ event: PlaybackActiveTrackChangedEvent) {
|
|
986
533
|
logger.debug("handleActiveTrackChanged: \(event.lastTrack?.src ?? "nil") → \(event.track?.src ?? "nil")")
|
|
987
534
|
updatePlayingIndicators()
|
|
988
|
-
//
|
|
989
|
-
|
|
990
|
-
nowPlayingManager.handleActiveTrackChanged()
|
|
535
|
+
// Update favorite button to reflect the new track's favorite state
|
|
536
|
+
nowPlayingManager.updateFavoriteButtonState()
|
|
991
537
|
}
|
|
992
538
|
|
|
993
539
|
/// Updates the isPlaying state on all list items based on the current active track.
|
|
@@ -1010,31 +556,19 @@ public final class RNABCarPlayController: NSObject {
|
|
|
1010
556
|
}
|
|
1011
557
|
|
|
1012
558
|
for template in templates {
|
|
1013
|
-
var templateChanged = false
|
|
1014
559
|
for section in template.sections {
|
|
1015
560
|
for item in section.items {
|
|
1016
561
|
guard let listItem = item as? CPListItem,
|
|
1017
|
-
let
|
|
1018
|
-
info.src != nil
|
|
562
|
+
let itemSrc = listItem.carPlayItemInfo?.src
|
|
1019
563
|
else { continue }
|
|
1020
564
|
|
|
1021
|
-
let isPlaying = isActiveTrack(
|
|
565
|
+
let isPlaying = isActiveTrack(src: itemSrc)
|
|
1022
566
|
if listItem.isPlaying != isPlaying {
|
|
1023
|
-
logger.debug(
|
|
1024
|
-
"Updating isPlaying for \(info.id ?? info.src ?? ""): \(listItem.isPlaying) → \(isPlaying)")
|
|
567
|
+
logger.debug("Updating isPlaying for \(itemSrc): \(listItem.isPlaying) → \(isPlaying)")
|
|
1025
568
|
listItem.isPlaying = isPlaying
|
|
1026
|
-
templateChanged = true
|
|
1027
569
|
}
|
|
1028
570
|
}
|
|
1029
571
|
}
|
|
1030
|
-
// Setting isPlaying on an already-displayed item does not reliably reach
|
|
1031
|
-
// the CarPlay screen (rdar-known; the flag only renders when the item is
|
|
1032
|
-
// pushed with it already set). Re-pushing the same sections re-serializes
|
|
1033
|
-
// every item with its current flags — done only when something actually
|
|
1034
|
-
// changed, so routine appear-time passes don't churn the display.
|
|
1035
|
-
if templateChanged {
|
|
1036
|
-
template.updateSections(template.sections)
|
|
1037
|
-
}
|
|
1038
572
|
}
|
|
1039
573
|
}
|
|
1040
574
|
|
|
@@ -1098,108 +632,6 @@ public final class RNABCarPlayController: NSObject {
|
|
|
1098
632
|
}
|
|
1099
633
|
}
|
|
1100
634
|
|
|
1101
|
-
/// Refreshes every currently-displayed template (tabs + navigation stack) by
|
|
1102
|
-
/// re-fetching each path with the cache bypassed. Called when all content was
|
|
1103
|
-
/// invalidated app-wide (e.g. a locale change).
|
|
1104
|
-
@MainActor
|
|
1105
|
-
public func invalidateAllContent() {
|
|
1106
|
-
guard isStarted else { return }
|
|
1107
|
-
Task { @MainActor in await refreshAllDisplayedTemplates() }
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
@MainActor
|
|
1111
|
-
private func refreshAllDisplayedTemplates() async {
|
|
1112
|
-
guard let audioBrowser else { return }
|
|
1113
|
-
|
|
1114
|
-
var templates: [CPListTemplate] = []
|
|
1115
|
-
if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate {
|
|
1116
|
-
for template in tabBar.templates {
|
|
1117
|
-
if let listTemplate = template as? CPListTemplate {
|
|
1118
|
-
templates.append(listTemplate)
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
for template in interfaceController.templates {
|
|
1123
|
-
if let listTemplate = template as? CPListTemplate,
|
|
1124
|
-
!templates.contains(where: { $0 === listTemplate })
|
|
1125
|
-
{
|
|
1126
|
-
templates.append(listTemplate)
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
for template in templates {
|
|
1131
|
-
guard let path = getPath(from: template) else { continue }
|
|
1132
|
-
do {
|
|
1133
|
-
let resolved = try await audioBrowser.browserManager.resolve(path, useCache: false)
|
|
1134
|
-
updateTemplate(template, with: resolved)
|
|
1135
|
-
} catch {
|
|
1136
|
-
logger.error("invalidateAllContent: failed to refresh \(path): \(error.localizedDescription)")
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
// MARK: - Gate
|
|
1142
|
-
|
|
1143
|
-
/// Applies a gate state change: set or clear. The per-request chrome is
|
|
1144
|
-
/// obtained at render time via `gateDecision(for:)`, so this just tears down
|
|
1145
|
-
/// any pushed navigation and rebuilds the tab bar — `showTabBar` routes to the
|
|
1146
|
-
/// gated build while a gate is active and to the normal build once cleared.
|
|
1147
|
-
/// Custom Now Playing buttons (e.g. favorite) hide while gated and return on
|
|
1148
|
-
/// clear.
|
|
1149
|
-
@MainActor
|
|
1150
|
-
private func handleGateChanged(_ active: Bool) {
|
|
1151
|
-
isGated = active
|
|
1152
|
-
let tabs = audioBrowser?.browserManager.getTabs() ?? []
|
|
1153
|
-
if tabs.isEmpty, !active {
|
|
1154
|
-
// Never had tabs (gate was up since before config) — full initial build.
|
|
1155
|
-
Task { await buildInitialInterface() }
|
|
1156
|
-
} else {
|
|
1157
|
-
// Tear down any pushed navigation — gated content must not stay reachable
|
|
1158
|
-
// behind the gate, and on clear we return to the root. (No-op at root.)
|
|
1159
|
-
interfaceController.safePopToRoot(animated: false)
|
|
1160
|
-
Task { await showTabBar(tabs: tabs) }
|
|
1161
|
-
}
|
|
1162
|
-
nowPlayingManager.setupNowPlayingButtons()
|
|
1163
|
-
nowPlayingManager.updateNowPlayingButtonStates()
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
/// Builds the gate page for one tab-bar slot, carrying the tab's entry so
|
|
1167
|
-
/// the tab bar itself stays familiar.
|
|
1168
|
-
///
|
|
1169
|
-
/// A CPListTemplate, NOT a CPInformationTemplate: CarPlay enforces
|
|
1170
|
-
/// per-entitlement template allowances at runtime, and the audio
|
|
1171
|
-
/// entitlement does not include the information template — handing one to
|
|
1172
|
-
/// CPTabBarTemplate throws an unhandled ObjC exception (crash at init).
|
|
1173
|
-
///
|
|
1174
|
-
/// Within a list, tab children never show navigation-bar buttons (the tab
|
|
1175
|
-
/// bar owns that chrome), and the centered empty view renders only when the
|
|
1176
|
-
/// list has NO rows. The gate is a plain centered message — `title` as the
|
|
1177
|
-
/// empty-view title and `message` as its subtitle, where newlines collapse
|
|
1178
|
-
/// to spaces (the "variants" are width alternatives, not lines). No action
|
|
1179
|
-
/// button: a gate withholds content; the consumer surfaces any "subscribe"
|
|
1180
|
-
/// affordance in its own UI, not on the car surface.
|
|
1181
|
-
private func makeGateTemplate(gate gateChrome: Gate?, tab: Track?) -> CPListTemplate {
|
|
1182
|
-
// A gated decision with neither an override nor a stored default chrome
|
|
1183
|
-
// (resolver-only `true`) falls back to the built-in minimal gate.
|
|
1184
|
-
let gate = gateChrome ?? HybridAudioBrowser.builtInGate
|
|
1185
|
-
let template = CPListTemplate(title: gate.title, sections: [])
|
|
1186
|
-
// Set at creation — the timing CarPlay renders the empty view reliably
|
|
1187
|
-
// (see replaceWithMessage).
|
|
1188
|
-
template.emptyViewTitleVariants = [gate.title]
|
|
1189
|
-
if let message = gate.message, !message.isEmpty {
|
|
1190
|
-
template.emptyViewSubtitleVariants = [
|
|
1191
|
-
message.replacingOccurrences(of: "\n", with: " "),
|
|
1192
|
-
]
|
|
1193
|
-
}
|
|
1194
|
-
// Marks the page as a gate (vs. a content tab, which carries a `path`),
|
|
1195
|
-
// so the lazy-loader and refresh paths never try to fill it.
|
|
1196
|
-
template.userInfo = ["gate": true] as [String: Any]
|
|
1197
|
-
if let tab {
|
|
1198
|
-
applyTabBarEntry(to: template, for: tab)
|
|
1199
|
-
}
|
|
1200
|
-
return template
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
635
|
// MARK: - Error Handling
|
|
1204
636
|
|
|
1205
637
|
/// Shows a navigation error using CPActionSheetTemplate.
|
|
@@ -1235,7 +667,7 @@ public final class RNABCarPlayController: NSObject {
|
|
|
1235
667
|
private func presentErrorActionSheet(customDisplay: FormattedNavigationError) {
|
|
1236
668
|
// If another template is already presented, dismiss it first
|
|
1237
669
|
if interfaceController.presentedTemplate != nil {
|
|
1238
|
-
interfaceController.
|
|
670
|
+
interfaceController.dismissTemplate(animated: false) { [weak self] _, _ in
|
|
1239
671
|
self?.showErrorActionSheet(customDisplay: customDisplay)
|
|
1240
672
|
}
|
|
1241
673
|
} else {
|
|
@@ -1248,7 +680,7 @@ public final class RNABCarPlayController: NSObject {
|
|
|
1248
680
|
// OK action - dismiss the action sheet (use system-localized "OK")
|
|
1249
681
|
let okTitle = Bundle(for: UIAlertController.self).localizedString(forKey: "OK", value: "OK", table: nil)
|
|
1250
682
|
let ok = CPAlertAction(title: okTitle, style: .cancel) { [weak self] _ in
|
|
1251
|
-
self?.interfaceController.
|
|
683
|
+
self?.interfaceController.dismissTemplate(animated: true, completion: nil)
|
|
1252
684
|
}
|
|
1253
685
|
|
|
1254
686
|
let actionSheet = CPActionSheetTemplate(
|
|
@@ -1257,20 +689,19 @@ public final class RNABCarPlayController: NSObject {
|
|
|
1257
689
|
actions: [ok],
|
|
1258
690
|
)
|
|
1259
691
|
|
|
1260
|
-
interfaceController.
|
|
692
|
+
interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)
|
|
1261
693
|
}
|
|
1262
694
|
|
|
1263
|
-
/// Shows
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
template
|
|
1273
|
-
interfaceController.safeSetRoot(template, animated: true)
|
|
695
|
+
/// Shows a simple error template as root (for initialization errors when no other template exists)
|
|
696
|
+
private func showErrorTemplate(message: String) {
|
|
697
|
+
// CPAlertTemplate cannot be set as root - use a list template instead
|
|
698
|
+
let errorItem = CPListItem(text: message, detailText: nil)
|
|
699
|
+
errorItem.isEnabled = false
|
|
700
|
+
let template = CPListTemplate(
|
|
701
|
+
title: "Error",
|
|
702
|
+
sections: [CPListSection(items: [errorItem])],
|
|
703
|
+
)
|
|
704
|
+
interfaceController.setRootTemplate(template, animated: true, completion: nil)
|
|
1274
705
|
}
|
|
1275
706
|
}
|
|
1276
707
|
|
|
@@ -1286,30 +717,13 @@ private final class InterfaceControllerDelegate: NSObject, CPInterfaceController
|
|
|
1286
717
|
}
|
|
1287
718
|
|
|
1288
719
|
func templateDidAppear(_ aTemplate: CPTemplate, animated _: Bool) {
|
|
1289
|
-
|
|
1290
|
-
// guard must let both through — the playing indicator was toggled while
|
|
1291
|
-
// the list was covered, and this appearance is the moment to repaint it.
|
|
1292
|
-
guard aTemplate is CPListTemplate || aTemplate is CPTabBarTemplate else { return }
|
|
1293
|
-
|
|
1294
|
-
// A tab change deferred while the user was browsing replaces the whole
|
|
1295
|
-
// tab bar — no point updating a template that's about to be discarded.
|
|
1296
|
-
if controller?.applyPendingTabsIfAtRoot() == true { return }
|
|
720
|
+
guard let listTemplate = aTemplate as? CPListTemplate else { return }
|
|
1297
721
|
|
|
1298
722
|
// Update playing indicators when navigating back to a list template
|
|
1299
723
|
controller?.updatePlayingIndicators()
|
|
1300
724
|
|
|
1301
725
|
// Lazy load content for tabs that haven't been loaded yet
|
|
1302
|
-
|
|
1303
|
-
controller?.loadContentIfNeeded(for: listTemplate)
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
func templateDidDisappear(_ aTemplate: CPTemplate, animated _: Bool) {
|
|
1308
|
-
// The complement of the appear hook: whether popping Now Playing reports
|
|
1309
|
-
// "tab bar appeared" or only "now playing disappeared" is a platform
|
|
1310
|
-
// detail — cover both so the revealed list always repaints its indicator.
|
|
1311
|
-
guard aTemplate is CPNowPlayingTemplate else { return }
|
|
1312
|
-
controller?.updatePlayingIndicators()
|
|
726
|
+
controller?.loadContentIfNeeded(for: listTemplate)
|
|
1313
727
|
}
|
|
1314
728
|
}
|
|
1315
729
|
|
|
@@ -1318,16 +732,12 @@ private final class InterfaceControllerDelegate: NSObject, CPInterfaceController
|
|
|
1318
732
|
private extension RNABCarPlayController {
|
|
1319
733
|
/// Loads content for a template if it hasn't been loaded yet (lazy loading for tabs)
|
|
1320
734
|
func loadContentIfNeeded(for template: CPListTemplate) {
|
|
735
|
+
// Skip if already has content
|
|
736
|
+
guard template.sections.isEmpty else { return }
|
|
737
|
+
|
|
1321
738
|
// Get path from userInfo
|
|
1322
739
|
guard let path = getPath(from: template) else { return }
|
|
1323
740
|
|
|
1324
|
-
// The template is now on screen, so a queued duplicate navigation to it is
|
|
1325
|
-
// no longer a concern (the top-template check will catch any further taps).
|
|
1326
|
-
navigatingPaths.remove(path)
|
|
1327
|
-
|
|
1328
|
-
// Skip if already has content (single-flight in loadContent guards re-entry).
|
|
1329
|
-
guard template.sections.isEmpty else { return }
|
|
1330
|
-
|
|
1331
741
|
logger.debug("Lazy loading content for tab: \(path)")
|
|
1332
742
|
|
|
1333
743
|
Task {
|