@5stones/react-native-audio-browser 0.1.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 +43 -0
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +210 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +36 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +1191 -0
- package/android/src/main/java/com/audiobrowser/AudioBrowserPackage.kt +21 -0
- package/android/src/main/java/com/audiobrowser/Callbacks.kt +92 -0
- package/android/src/main/java/com/audiobrowser/HeadlessTaskService.kt +59 -0
- package/android/src/main/java/com/audiobrowser/Service.kt +336 -0
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +1169 -0
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +103 -0
- package/android/src/main/java/com/audiobrowser/browser/RouteMatch.kt +9 -0
- package/android/src/main/java/com/audiobrowser/browser/SimpleRouter.kt +150 -0
- package/android/src/main/java/com/audiobrowser/extension/EnumExtensions.kt +5 -0
- package/android/src/main/java/com/audiobrowser/extension/NumberExt.kt +13 -0
- package/android/src/main/java/com/audiobrowser/http/HttpClient.kt +171 -0
- package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +251 -0
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +80 -0
- package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +101 -0
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +125 -0
- package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +256 -0
- package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +163 -0
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +177 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +580 -0
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +578 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +58 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +245 -0
- package/android/src/main/java/com/audiobrowser/player/Player.kt +1509 -0
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +230 -0
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +191 -0
- package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +80 -0
- package/android/src/main/java/com/audiobrowser/util/AndroidAudioContentTypeFactory.kt +17 -0
- package/android/src/main/java/com/audiobrowser/util/BatteryOptimizationHelper.kt +95 -0
- package/android/src/main/java/com/audiobrowser/util/BatteryWarningStore.kt +38 -0
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +155 -0
- package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +390 -0
- package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +197 -0
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +75 -0
- package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +97 -0
- package/android/src/main/java/com/audiobrowser/util/NetworkConnectivityMonitor.kt +121 -0
- package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +25 -0
- package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +72 -0
- package/android/src/main/java/com/audiobrowser/util/RepeatModeFactory.kt +22 -0
- package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +36 -0
- package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +130 -0
- package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +50 -0
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +125 -0
- package/android/src/main/res/values/strings.xml +6 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +273 -0
- package/android/src/test/java/com/audiobrowser/browser/SimpleBrowserTest.kt +33 -0
- package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +156 -0
- package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +240 -0
- package/ios/Bridge.h +8 -0
- package/ios/Browser/BrowserConfig.swift +85 -0
- package/ios/Browser/BrowserManager.swift +985 -0
- package/ios/Browser/BrowserPathHelper.swift +156 -0
- package/ios/Browser/JsonModels.swift +165 -0
- package/ios/Browser/SimpleRouter.swift +184 -0
- package/ios/CarPlay/CarPlayController.swift +1342 -0
- package/ios/CarPlay/RNABCarPlaySceneDelegate.h +15 -0
- package/ios/CarPlay/RNABCarPlaySceneDelegate.m +59 -0
- package/ios/CarPlay/Track+CarPlay.swift +49 -0
- package/ios/Extension/Capability+RemoteCommand.swift +76 -0
- package/ios/Extension/ChapterMetadata+AVFoundation.swift +37 -0
- package/ios/Extension/TimedMetadata+AVFoundation.swift +126 -0
- package/ios/Extension/Track+AVPlayer.swift +68 -0
- package/ios/Extension/TrackMetadata+AVFoundation.swift +128 -0
- package/ios/Http/HttpClient.swift +237 -0
- package/ios/HybridAudioBrowser.swift +1309 -0
- package/ios/Model/MediaURL.swift +66 -0
- package/ios/Model/PlayerUpdateOptions.swift +116 -0
- package/ios/Model/RemoteCommand.swift +71 -0
- package/ios/Model/SourceType.swift +6 -0
- package/ios/Model/TrackPlayerError.swift +69 -0
- package/ios/NowPlayingInfo/MediaItemProperty.swift +78 -0
- package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +9 -0
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +283 -0
- package/ios/NowPlayingInfo/NowPlayingInfoKeyValue.swift +6 -0
- package/ios/NowPlayingInfo/NowPlayingInfoProperty.swift +220 -0
- package/ios/Observer/PlayerItemNotificationObserver.swift +83 -0
- package/ios/Observer/PlayerItemPropertyObserver.swift +110 -0
- package/ios/Observer/PlayerStateObserver.swift +52 -0
- package/ios/Observer/PlayerTimeObserver.swift +109 -0
- package/ios/Option/PitchAlgorithms.swift +17 -0
- package/ios/Option/SessionCategories.swift +100 -0
- package/ios/Option/TimeEventFrequency.swift +18 -0
- package/ios/Player/PlaybackProgressUpdateManager.swift +60 -0
- package/ios/Player/PlayingStateManager.swift +29 -0
- package/ios/Player/RetryManager.swift +281 -0
- package/ios/Player/ShuffleOrder.swift +191 -0
- package/ios/Player/SleepTimerManager.swift +137 -0
- package/ios/RemoteCommand/RemoteCommandController.swift +400 -0
- package/ios/Support/RNTrackPlayer-Bridging-Header.h +7 -0
- package/ios/TrackPlayer.swift +1590 -0
- package/ios/TrackPlayerCallbacks.swift +121 -0
- package/ios/Util/Emitter.swift +46 -0
- package/ios/Util/LRUCache.swift +182 -0
- package/ios/Util/MetadataAdapter.swift +131 -0
- package/ios/Util/NetworkMonitor.swift +68 -0
- package/ios/Util/OnceValue.swift +70 -0
- package/ios/Util/SVGProcessor.swift +56 -0
- package/lib/commonjs/AudioBrowser.js +50 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -0
- package/lib/commonjs/features/battery.js +154 -0
- package/lib/commonjs/features/battery.js.map +1 -0
- package/lib/commonjs/features/browser.js +271 -0
- package/lib/commonjs/features/browser.js.map +1 -0
- package/lib/commonjs/features/equalizer.js +69 -0
- package/lib/commonjs/features/equalizer.js.map +1 -0
- package/lib/commonjs/features/errors.js +165 -0
- package/lib/commonjs/features/errors.js.map +1 -0
- package/lib/commonjs/features/favorites.js +101 -0
- package/lib/commonjs/features/favorites.js.map +1 -0
- package/lib/commonjs/features/index.js +160 -0
- package/lib/commonjs/features/index.js.map +1 -0
- package/lib/commonjs/features/metadata.js +54 -0
- package/lib/commonjs/features/metadata.js.map +1 -0
- package/lib/commonjs/features/network.js +40 -0
- package/lib/commonjs/features/network.js.map +1 -0
- package/lib/commonjs/features/nowPlaying.js +61 -0
- package/lib/commonjs/features/nowPlaying.js.map +1 -0
- package/lib/commonjs/features/output.js +54 -0
- package/lib/commonjs/features/output.js.map +1 -0
- package/lib/commonjs/features/playback/controls.js +79 -0
- package/lib/commonjs/features/playback/controls.js.map +1 -0
- package/lib/commonjs/features/playback/index.js +83 -0
- package/lib/commonjs/features/playback/index.js.map +1 -0
- package/lib/commonjs/features/playback/playWhenReady.js +55 -0
- package/lib/commonjs/features/playback/playWhenReady.js.map +1 -0
- package/lib/commonjs/features/playback/playing.js +41 -0
- package/lib/commonjs/features/playback/playing.js.map +1 -0
- package/lib/commonjs/features/playback/progress.js +104 -0
- package/lib/commonjs/features/playback/progress.js.map +1 -0
- package/lib/commonjs/features/playback/rate.js +29 -0
- package/lib/commonjs/features/playback/rate.js.map +1 -0
- package/lib/commonjs/features/playback/state.js +62 -0
- package/lib/commonjs/features/playback/state.js.map +1 -0
- package/lib/commonjs/features/playback/volume.js +68 -0
- package/lib/commonjs/features/playback/volume.js.map +1 -0
- package/lib/commonjs/features/player/index.js +28 -0
- package/lib/commonjs/features/player/index.js.map +1 -0
- package/lib/commonjs/features/player/options.js +182 -0
- package/lib/commonjs/features/player/options.js.map +1 -0
- package/lib/commonjs/features/player/setup.js +143 -0
- package/lib/commonjs/features/player/setup.js.map +1 -0
- package/lib/commonjs/features/queue/activeTrack.js +52 -0
- package/lib/commonjs/features/queue/activeTrack.js.map +1 -0
- package/lib/commonjs/features/queue/index.js +50 -0
- package/lib/commonjs/features/queue/index.js.map +1 -0
- package/lib/commonjs/features/queue/queue.js +177 -0
- package/lib/commonjs/features/queue/queue.js.map +1 -0
- package/lib/commonjs/features/queue/repeatMode.js +58 -0
- package/lib/commonjs/features/queue/repeatMode.js.map +1 -0
- package/lib/commonjs/features/queue/shuffle.js +57 -0
- package/lib/commonjs/features/queue/shuffle.js.map +1 -0
- package/lib/commonjs/features/rating.js +2 -0
- package/lib/commonjs/features/rating.js.map +1 -0
- package/lib/commonjs/features/remoteControls.js +286 -0
- package/lib/commonjs/features/remoteControls.js.map +1 -0
- package/lib/commonjs/features/sleepTimer.js +164 -0
- package/lib/commonjs/features/sleepTimer.js.map +1 -0
- package/lib/commonjs/index.js +22 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native.js +9 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/native.web.js +10 -0
- package/lib/commonjs/native.web.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/audio-browser.nitro.js +6 -0
- package/lib/commonjs/specs/audio-browser.nitro.js.map +1 -0
- package/lib/commonjs/types/browser-native.js +6 -0
- package/lib/commonjs/types/browser-native.js.map +1 -0
- package/lib/commonjs/types/browser-nodes.js +6 -0
- package/lib/commonjs/types/browser-nodes.js.map +1 -0
- package/lib/commonjs/types/browser.js +6 -0
- package/lib/commonjs/types/browser.js.map +1 -0
- package/lib/commonjs/types/index.js +28 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/types/player.js +2 -0
- package/lib/commonjs/types/player.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +58 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -0
- package/lib/commonjs/utils/NativeUpdatedValue.js +59 -0
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -0
- package/lib/commonjs/utils/resolveAssetSource.js +11 -0
- package/lib/commonjs/utils/resolveAssetSource.js.map +1 -0
- package/lib/commonjs/utils/useDebug.js +221 -0
- package/lib/commonjs/utils/useDebug.js.map +1 -0
- package/lib/commonjs/utils/useNativeUpdatedValue.js +44 -0
- package/lib/commonjs/utils/useNativeUpdatedValue.js.map +1 -0
- package/lib/commonjs/web/NativeAudioBrowser.js +668 -0
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -0
- package/lib/commonjs/web/SimpleRouter.js +144 -0
- package/lib/commonjs/web/SimpleRouter.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/Event.js +19 -0
- package/lib/commonjs/web/TrackPlayer/Event.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/Player.js +276 -0
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +284 -0
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js +12 -0
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/SetupNotCalledError.js +13 -0
- package/lib/commonjs/web/TrackPlayer/SetupNotCalledError.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js +75 -0
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/State.js +18 -0
- package/lib/commonjs/web/TrackPlayer/State.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/index.js +52 -0
- package/lib/commonjs/web/TrackPlayer/index.js.map +1 -0
- package/lib/commonjs/web/browser/BrowserManager.js +594 -0
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -0
- package/lib/commonjs/web/browser/FavoriteManager.js +80 -0
- package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -0
- package/lib/commonjs/web/browser/NavigationErrorManager.js +106 -0
- package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -0
- package/lib/commonjs/web/browser/SearchManager.js +80 -0
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -0
- package/lib/commonjs/web/http/HttpClient.js +81 -0
- package/lib/commonjs/web/http/HttpClient.js.map +1 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js +294 -0
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -0
- package/lib/commonjs/web/player/NowPlayingManager.js +70 -0
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -0
- package/lib/commonjs/web/player/OptionsManager.js +88 -0
- package/lib/commonjs/web/player/OptionsManager.js.map +1 -0
- package/lib/commonjs/web/util/BrowserPathHelper.js +138 -0
- package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -0
- package/lib/commonjs/web/util/shuffle.js +21 -0
- package/lib/commonjs/web/util/shuffle.js.map +1 -0
- package/lib/module/AudioBrowser.js +7 -0
- package/lib/module/AudioBrowser.js.map +1 -0
- package/lib/module/features/battery.js +144 -0
- package/lib/module/features/battery.js.map +1 -0
- package/lib/module/features/browser.js +257 -0
- package/lib/module/features/browser.js.map +1 -0
- package/lib/module/features/equalizer.js +60 -0
- package/lib/module/features/equalizer.js.map +1 -0
- package/lib/module/features/errors.js +156 -0
- package/lib/module/features/errors.js.map +1 -0
- package/lib/module/features/favorites.js +95 -0
- package/lib/module/features/favorites.js.map +1 -0
- package/lib/module/features/index.js +26 -0
- package/lib/module/features/index.js.map +1 -0
- package/lib/module/features/metadata.js +51 -0
- package/lib/module/features/metadata.js.map +1 -0
- package/lib/module/features/network.js +35 -0
- package/lib/module/features/network.js.map +1 -0
- package/lib/module/features/nowPlaying.js +54 -0
- package/lib/module/features/nowPlaying.js.map +1 -0
- package/lib/module/features/output.js +47 -0
- package/lib/module/features/output.js.map +1 -0
- package/lib/module/features/playback/controls.js +69 -0
- package/lib/module/features/playback/controls.js.map +1 -0
- package/lib/module/features/playback/index.js +10 -0
- package/lib/module/features/playback/index.js.map +1 -0
- package/lib/module/features/playback/playWhenReady.js +49 -0
- package/lib/module/features/playback/playWhenReady.js.map +1 -0
- package/lib/module/features/playback/playing.js +36 -0
- package/lib/module/features/playback/playing.js.map +1 -0
- package/lib/module/features/playback/progress.js +98 -0
- package/lib/module/features/playback/progress.js.map +1 -0
- package/lib/module/features/playback/rate.js +25 -0
- package/lib/module/features/playback/rate.js.map +1 -0
- package/lib/module/features/playback/state.js +57 -0
- package/lib/module/features/playback/state.js.map +1 -0
- package/lib/module/features/playback/volume.js +60 -0
- package/lib/module/features/playback/volume.js.map +1 -0
- package/lib/module/features/player/index.js +5 -0
- package/lib/module/features/player/index.js.map +1 -0
- package/lib/module/features/player/options.js +176 -0
- package/lib/module/features/player/options.js.map +1 -0
- package/lib/module/features/player/setup.js +140 -0
- package/lib/module/features/player/setup.js.map +1 -0
- package/lib/module/features/queue/activeTrack.js +46 -0
- package/lib/module/features/queue/activeTrack.js.map +1 -0
- package/lib/module/features/queue/index.js +7 -0
- package/lib/module/features/queue/index.js.map +1 -0
- package/lib/module/features/queue/queue.js +162 -0
- package/lib/module/features/queue/queue.js.map +1 -0
- package/lib/module/features/queue/repeatMode.js +52 -0
- package/lib/module/features/queue/repeatMode.js.map +1 -0
- package/lib/module/features/queue/shuffle.js +50 -0
- package/lib/module/features/queue/shuffle.js.map +1 -0
- package/lib/module/features/rating.js +2 -0
- package/lib/module/features/rating.js.map +1 -0
- package/lib/module/features/remoteControls.js +275 -0
- package/lib/module/features/remoteControls.js.map +1 -0
- package/lib/module/features/sleepTimer.js +155 -0
- package/lib/module/features/sleepTimer.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +5 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/native.web.js +7 -0
- package/lib/module/native.web.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/specs/audio-browser.nitro.js +4 -0
- package/lib/module/specs/audio-browser.nitro.js.map +1 -0
- package/lib/module/types/browser-native.js +4 -0
- package/lib/module/types/browser-native.js.map +1 -0
- package/lib/module/types/browser-nodes.js +4 -0
- package/lib/module/types/browser-nodes.js.map +1 -0
- package/lib/module/types/browser.js +4 -0
- package/lib/module/types/browser.js.map +1 -0
- package/lib/module/types/index.js +5 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/types/player.js +2 -0
- package/lib/module/types/player.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +53 -0
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -0
- package/lib/module/utils/NativeUpdatedValue.js +54 -0
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -0
- package/lib/module/utils/resolveAssetSource.js +7 -0
- package/lib/module/utils/resolveAssetSource.js.map +1 -0
- package/lib/module/utils/useDebug.js +218 -0
- package/lib/module/utils/useDebug.js.map +1 -0
- package/lib/module/utils/useNativeUpdatedValue.js +40 -0
- package/lib/module/utils/useNativeUpdatedValue.js.map +1 -0
- package/lib/module/web/NativeAudioBrowser.js +664 -0
- package/lib/module/web/NativeAudioBrowser.js.map +1 -0
- package/lib/module/web/SimpleRouter.js +139 -0
- package/lib/module/web/SimpleRouter.js.map +1 -0
- package/lib/module/web/TrackPlayer/Event.js +15 -0
- package/lib/module/web/TrackPlayer/Event.js.map +1 -0
- package/lib/module/web/TrackPlayer/Player.js +271 -0
- package/lib/module/web/TrackPlayer/Player.js.map +1 -0
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js +279 -0
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/module/web/TrackPlayer/RepeatMode.js +8 -0
- package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -0
- package/lib/module/web/TrackPlayer/SetupNotCalledError.js +8 -0
- package/lib/module/web/TrackPlayer/SetupNotCalledError.js.map +1 -0
- package/lib/module/web/TrackPlayer/SleepTimer.js +70 -0
- package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -0
- package/lib/module/web/TrackPlayer/State.js +14 -0
- package/lib/module/web/TrackPlayer/State.js.map +1 -0
- package/lib/module/web/TrackPlayer/index.js +7 -0
- package/lib/module/web/TrackPlayer/index.js.map +1 -0
- package/lib/module/web/browser/BrowserManager.js +590 -0
- package/lib/module/web/browser/BrowserManager.js.map +1 -0
- package/lib/module/web/browser/FavoriteManager.js +75 -0
- package/lib/module/web/browser/FavoriteManager.js.map +1 -0
- package/lib/module/web/browser/NavigationErrorManager.js +101 -0
- package/lib/module/web/browser/NavigationErrorManager.js.map +1 -0
- package/lib/module/web/browser/SearchManager.js +76 -0
- package/lib/module/web/browser/SearchManager.js.map +1 -0
- package/lib/module/web/http/HttpClient.js +76 -0
- package/lib/module/web/http/HttpClient.js.map +1 -0
- package/lib/module/web/http/RequestConfigBuilder.js +291 -0
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -0
- package/lib/module/web/player/NowPlayingManager.js +65 -0
- package/lib/module/web/player/NowPlayingManager.js.map +1 -0
- package/lib/module/web/player/OptionsManager.js +83 -0
- package/lib/module/web/player/OptionsManager.js.map +1 -0
- package/lib/module/web/util/BrowserPathHelper.js +134 -0
- package/lib/module/web/util/BrowserPathHelper.js.map +1 -0
- package/lib/module/web/util/shuffle.js +17 -0
- package/lib/module/web/util/shuffle.js.map +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts +5 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -0
- package/lib/typescript/src/features/battery.d.ts +112 -0
- package/lib/typescript/src/features/battery.d.ts.map +1 -0
- package/lib/typescript/src/features/browser.d.ts +68 -0
- package/lib/typescript/src/features/browser.d.ts.map +1 -0
- package/lib/typescript/src/features/equalizer.d.ts +35 -0
- package/lib/typescript/src/features/equalizer.d.ts.map +1 -0
- package/lib/typescript/src/features/errors.d.ts +136 -0
- package/lib/typescript/src/features/errors.d.ts.map +1 -0
- package/lib/typescript/src/features/favorites.d.ts +84 -0
- package/lib/typescript/src/features/favorites.d.ts.map +1 -0
- package/lib/typescript/src/features/index.d.ts +23 -0
- package/lib/typescript/src/features/index.d.ts.map +1 -0
- package/lib/typescript/src/features/metadata.d.ts +103 -0
- package/lib/typescript/src/features/metadata.d.ts.map +1 -0
- package/lib/typescript/src/features/network.d.ts +18 -0
- package/lib/typescript/src/features/network.d.ts.map +1 -0
- package/lib/typescript/src/features/nowPlaying.d.ts +36 -0
- package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -0
- package/lib/typescript/src/features/output.d.ts +28 -0
- package/lib/typescript/src/features/output.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/controls.d.ts +40 -0
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/index.d.ts +8 -0
- package/lib/typescript/src/features/playback/index.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +30 -0
- package/lib/typescript/src/features/playback/playWhenReady.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/playing.d.ts +21 -0
- package/lib/typescript/src/features/playback/playing.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/progress.d.ts +51 -0
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/rate.d.ts +12 -0
- package/lib/typescript/src/features/playback/rate.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/state.d.ts +43 -0
- package/lib/typescript/src/features/playback/state.d.ts.map +1 -0
- package/lib/typescript/src/features/playback/volume.d.ts +32 -0
- package/lib/typescript/src/features/playback/volume.d.ts.map +1 -0
- package/lib/typescript/src/features/player/index.d.ts +3 -0
- package/lib/typescript/src/features/player/index.d.ts.map +1 -0
- package/lib/typescript/src/features/player/options.d.ts +413 -0
- package/lib/typescript/src/features/player/options.d.ts.map +1 -0
- package/lib/typescript/src/features/player/setup.d.ts +471 -0
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts +38 -0
- package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/index.d.ts +5 -0
- package/lib/typescript/src/features/queue/index.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/queue.d.ts +111 -0
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/repeatMode.d.ts +34 -0
- package/lib/typescript/src/features/queue/repeatMode.d.ts.map +1 -0
- package/lib/typescript/src/features/queue/shuffle.d.ts +27 -0
- package/lib/typescript/src/features/queue/shuffle.d.ts.map +1 -0
- 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 +164 -0
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -0
- package/lib/typescript/src/features/sleepTimer.d.ts +80 -0
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/native.d.ts +3 -0
- package/lib/typescript/src/native.d.ts.map +1 -0
- 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 +242 -0
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-native.d.ts +39 -0
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -0
- package/lib/typescript/src/types/browser-nodes.d.ts +153 -0
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -0
- package/lib/typescript/src/types/browser.d.ts +710 -0
- package/lib/typescript/src/types/browser.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +3 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- 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 +35 -0
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -0
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +39 -0
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -0
- package/lib/typescript/src/utils/resolveAssetSource.d.ts +4 -0
- package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -0
- package/lib/typescript/src/utils/useDebug.d.ts +65 -0
- package/lib/typescript/src/utils/useDebug.d.ts.map +1 -0
- package/lib/typescript/src/utils/useNativeUpdatedValue.d.ts +14 -0
- package/lib/typescript/src/utils/useNativeUpdatedValue.d.ts.map +1 -0
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts +176 -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/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 +71 -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/nitro.json +24 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +88 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.gradle +27 -0
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +124 -0
- package/nitrogen/generated/android/AudioBrowserOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +68 -0
- package/nitrogen/generated/android/c++/JAndroidAudioOffloadSettings.hpp +61 -0
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +86 -0
- package/nitrogen/generated/android/c++/JAndroidPlayerWakeMode.hpp +62 -0
- package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +86 -0
- package/nitrogen/generated/android/c++/JAppKilledPlaybackBehavior.hpp +62 -0
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +163 -0
- package/nitrogen/generated/android/c++/JBatteryOptimizationStatus.hpp +62 -0
- package/nitrogen/generated/android/c++/JBatteryOptimizationStatusChangedEvent.hpp +58 -0
- package/nitrogen/generated/android/c++/JBatteryWarningPendingChangedEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JBrowseError.hpp +57 -0
- package/nitrogen/generated/android/c++/JBrowseResult.cpp +26 -0
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +83 -0
- package/nitrogen/generated/android/c++/JBrowserSourceCallbackParam.hpp +76 -0
- package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +65 -0
- package/nitrogen/generated/android/c++/JChapterMetadata.hpp +70 -0
- package/nitrogen/generated/android/c++/JEqualizerSettings.hpp +125 -0
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +71 -0
- package/nitrogen/generated/android/c++/JFormatNavigationErrorParams.hpp +72 -0
- package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +61 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.hpp +111 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____MediaTransformParams.hpp +134 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +143 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +136 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +143 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +157 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_BatteryOptimizationStatusChangedEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_BatteryWarningPendingChangedEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_EqualizerSettings.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +88 -0
- package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_NavigationErrorEvent.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +89 -0
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +98 -0
- package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +83 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +88 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +81 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackPlayWhenReadyChangedEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackProgressUpdatedEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackQueueEndedEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_PlayingState.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemoteJumpBackwardEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemoteJumpForwardEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemotePlayIdEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemotePlaySearchEvent.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSeekEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +87 -0
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSkipEvent.hpp +77 -0
- package/nitrogen/generated/android/c++/JFunc_void_RepeatModeChangedEvent.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_TimedMetadata.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_TrackMetadata.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_FormattedNavigationError_.hpp +79 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +89 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__SleepTimerTime__SleepTimerEndOfTrack__.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_ChapterMetadata_.hpp +98 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +105 -0
- package/nitrogen/generated/android/c++/JHeartRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JHttpMethod.hpp +74 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +1811 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +250 -0
- package/nitrogen/generated/android/c++/JIOSCategory.hpp +71 -0
- package/nitrogen/generated/android/c++/JIOSCategoryMode.hpp +80 -0
- package/nitrogen/generated/android/c++/JIOSCategoryOptions.hpp +74 -0
- package/nitrogen/generated/android/c++/JIOSCategoryPolicy.hpp +62 -0
- package/nitrogen/generated/android/c++/JImageContext.hpp +61 -0
- package/nitrogen/generated/android/c++/JImageQueryParams.hpp +62 -0
- package/nitrogen/generated/android/c++/JImageSource.hpp +86 -0
- package/nitrogen/generated/android/c++/JIosOutput.hpp +67 -0
- package/nitrogen/generated/android/c++/JIosOutputType.hpp +86 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +153 -0
- package/nitrogen/generated/android/c++/JMediaTransformParams.hpp +69 -0
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +196 -0
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +147 -0
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JNavigationError.hpp +72 -0
- package/nitrogen/generated/android/c++/JNavigationErrorEvent.hpp +62 -0
- package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +68 -0
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +86 -0
- package/nitrogen/generated/android/c++/JNotificationButton.hpp +68 -0
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +94 -0
- package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +104 -0
- package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +62 -0
- package/nitrogen/generated/android/c++/JOptions.hpp +97 -0
- package/nitrogen/generated/android/c++/JPartialAndroidSetupPlayerOptions.hpp +104 -0
- package/nitrogen/generated/android/c++/JPartialIOSSetupPlayerOptions.hpp +100 -0
- 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 +66 -0
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +83 -0
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +61 -0
- package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +60 -0
- package/nitrogen/generated/android/c++/JPlaybackPlayWhenReadyChangedEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JPlaybackProgressUpdatedEvent.hpp +69 -0
- package/nitrogen/generated/android/c++/JPlaybackQueueEndedEvent.hpp +61 -0
- package/nitrogen/generated/android/c++/JPlaybackState.hpp +80 -0
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +101 -0
- package/nitrogen/generated/android/c++/JPlayingState.hpp +61 -0
- package/nitrogen/generated/android/c++/JProgress.hpp +65 -0
- package/nitrogen/generated/android/c++/JRatingType.hpp +74 -0
- package/nitrogen/generated/android/c++/JRemoteJumpBackwardEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JRemoteJumpForwardEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JRemotePlayIdEvent.hpp +62 -0
- package/nitrogen/generated/android/c++/JRemotePlaySearchEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JRemoteSeekEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +66 -0
- package/nitrogen/generated/android/c++/JRemoteSkipEvent.hpp +57 -0
- package/nitrogen/generated/android/c++/JRepeatMode.hpp +62 -0
- package/nitrogen/generated/android/c++/JRepeatModeChangedEvent.hpp +58 -0
- package/nitrogen/generated/android/c++/JRequestConfig.hpp +115 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +154 -0
- package/nitrogen/generated/android/c++/JRetryConfig.hpp +61 -0
- package/nitrogen/generated/android/c++/JSearchMode.hpp +71 -0
- package/nitrogen/generated/android/c++/JSearchParams.hpp +84 -0
- package/nitrogen/generated/android/c++/JSleepTimer.cpp +30 -0
- package/nitrogen/generated/android/c++/JSleepTimer.hpp +88 -0
- package/nitrogen/generated/android/c++/JSleepTimerEndOfTrack.hpp +57 -0
- package/nitrogen/generated/android/c++/JSleepTimerTime.hpp +57 -0
- package/nitrogen/generated/android/c++/JStarRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JThumbsRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JTimedMetadata.hpp +74 -0
- package/nitrogen/generated/android/c++/JTrack.hpp +129 -0
- package/nitrogen/generated/android/c++/JTrackMetadata.hpp +118 -0
- package/nitrogen/generated/android/c++/JTrackStyle.hpp +59 -0
- package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +134 -0
- package/nitrogen/generated/android/c++/JUpdateOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_AndroidAudioOffloadSettings.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_AndroidAudioOffloadSettings.hpp +70 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_RetryConfig.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_RetryConfig.hpp +70 -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_Double.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_Double.hpp +69 -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 +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioOffloadSettings.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidPlayerWakeMode.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AppKilledPlaybackBehavior.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AudioBrowserOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BatteryOptimizationStatus.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BatteryOptimizationStatusChangedEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BatteryWarningPendingChangedEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowseError.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowseResult.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/BrowserSourceCallbackParam.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +23 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ChapterMetadata.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/EqualizerSettings.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FavoriteChangedEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNavigationErrorParams.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____MediaTransformParams.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_BatteryOptimizationStatusChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_BatteryWarningPendingChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_EqualizerSettings.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_FavoriteChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_IosOutput.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_NavigationErrorEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_NowPlayingMetadata.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_Options.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_Playback.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackActiveTrackChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackErrorEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackPlayWhenReadyChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackProgressUpdatedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlaybackQueueEndedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_PlayingState.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteJumpBackwardEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteJumpForwardEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemotePlayIdEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemotePlaySearchEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteSeekEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteSetRatingEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RemoteSkipEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_RepeatModeChangedEvent.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_TimedMetadata.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_TrackMetadata.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_FormattedNavigationError_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_ResolvedTrack_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__optional_std__variant_nitro__NullType__SleepTimerTime__SleepTimerEndOfTrack__.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__vector_ChapterMetadata_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_std__vector_Track_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HeartRating.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HttpMethod.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +1137 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSCategory.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSCategoryMode.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSCategoryOptions.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSCategoryPolicy.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageContext.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageQueryParams.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageSource.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutput.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +30 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaTransformParams.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +69 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +63 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationError.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButton.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialAndroidSetupPlayerOptions.kt +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialIOSSetupPlayerOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PercentageRating.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Playback.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackActiveTrackChangedEvent.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackPlayWhenReadyChangedEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackProgressUpdatedEvent.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackQueueEndedEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayingState.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Progress.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RatingType.kt +26 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteJumpBackwardEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteJumpForwardEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemotePlayIdEvent.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemotePlaySearchEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteSeekEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteSetRatingEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteSkipEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RepeatMode.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RepeatModeChangedEvent.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RequestConfig.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +89 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimer.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimerEndOfTrack.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SleepTimerTime.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StarRating.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ThumbsRating.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TimedMetadata.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackMetadata.kt +83 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +21 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_AndroidAudioOffloadSettings.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_RetryConfig.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +85 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_NullType_Double.kt +59 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_NullType_NotificationButtonLayout.kt +59 -0
- package/nitrogen/generated/ios/AudioBrowser+autolinking.rb +60 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +407 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +2822 -0
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +300 -0
- package/nitrogen/generated/ios/AudioBrowserAutolinking.mm +33 -0
- package/nitrogen/generated/ios/AudioBrowserAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +1190 -0
- package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +52 -0
- package/nitrogen/generated/ios/swift/AndroidAudioOffloadSettings.swift +85 -0
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +125 -0
- package/nitrogen/generated/ios/swift/AndroidPlayerWakeMode.swift +44 -0
- package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +187 -0
- package/nitrogen/generated/ios/swift/AppKilledPlaybackBehavior.swift +44 -0
- package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +445 -0
- package/nitrogen/generated/ios/swift/BatteryOptimizationStatus.swift +44 -0
- package/nitrogen/generated/ios/swift/BatteryOptimizationStatusChangedEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/BatteryWarningPendingChangedEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/BrowseError.swift +36 -0
- package/nitrogen/generated/ios/swift/BrowseResult.swift +18 -0
- package/nitrogen/generated/ios/swift/BrowserSourceCallbackParam.swift +86 -0
- package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +48 -0
- package/nitrogen/generated/ios/swift/ChapterMetadata.swift +107 -0
- package/nitrogen/generated/ios/swift/EqualizerSettings.swift +168 -0
- package/nitrogen/generated/ios/swift/FavoriteChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/FormatNavigationErrorParams.swift +58 -0
- package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_FormattedNavigationError____FormatNavigationErrorParams.swift +61 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____MediaTransformParams.swift +62 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +77 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.swift +62 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +69 -0
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.swift +68 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_BatteryOptimizationStatusChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_BatteryWarningPendingChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_EqualizerSettings.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_FavoriteChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_IosOutput.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NavigationErrorEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NowPlayingMetadata.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_Options.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_Playback.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackActiveTrackChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackErrorEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackPlayWhenReadyChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackProgressUpdatedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlaybackQueueEndedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_PlayingState.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemoteJumpBackwardEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemoteJumpForwardEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemotePlayIdEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemotePlaySearchEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemoteSeekEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemoteSetRatingEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RemoteSkipEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RepeatModeChangedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_RequestConfig.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_TimedMetadata.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_TrackMetadata.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_FormattedNavigationError_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_ResolvedTrack_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__variant_nitro__NullType__SleepTimerTime__SleepTimerEndOfTrack__.swift +69 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_RequestConfig__.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_std__vector_Track___.swift +67 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError_.swift +59 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_ChapterMetadata_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_Track_.swift +47 -0
- package/nitrogen/generated/ios/swift/HeartRating.swift +36 -0
- package/nitrogen/generated/ios/swift/HttpMethod.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +181 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +2352 -0
- package/nitrogen/generated/ios/swift/IOSCategory.swift +56 -0
- package/nitrogen/generated/ios/swift/IOSCategoryMode.swift +68 -0
- package/nitrogen/generated/ios/swift/IOSCategoryOptions.swift +60 -0
- package/nitrogen/generated/ios/swift/IOSCategoryPolicy.swift +44 -0
- package/nitrogen/generated/ios/swift/ImageContext.swift +71 -0
- package/nitrogen/generated/ios/swift/ImageQueryParams.swift +85 -0
- package/nitrogen/generated/ios/swift/ImageSource.swift +139 -0
- package/nitrogen/generated/ios/swift/IosOutput.swift +58 -0
- package/nitrogen/generated/ios/swift/IosOutputType.swift +76 -0
- package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +434 -0
- package/nitrogen/generated/ios/swift/MediaTransformParams.swift +59 -0
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +360 -0
- package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +275 -0
- package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +215 -0
- package/nitrogen/generated/ios/swift/NavigationError.swift +100 -0
- package/nitrogen/generated/ios/swift/NavigationErrorEvent.swift +48 -0
- package/nitrogen/generated/ios/swift/NavigationErrorType.swift +52 -0
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +187 -0
- package/nitrogen/generated/ios/swift/NotificationButton.swift +52 -0
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +159 -0
- package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +351 -0
- package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +85 -0
- package/nitrogen/generated/ios/swift/Options.swift +148 -0
- package/nitrogen/generated/ios/swift/PartialAndroidSetupPlayerOptions.swift +328 -0
- package/nitrogen/generated/ios/swift/PartialIOSSetupPlayerOptions.swift +159 -0
- package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +157 -0
- package/nitrogen/generated/ios/swift/PercentageRating.swift +36 -0
- package/nitrogen/generated/ios/swift/Playback.swift +59 -0
- package/nitrogen/generated/ios/swift/PlaybackActiveTrackChangedEvent.swift +128 -0
- package/nitrogen/generated/ios/swift/PlaybackError.swift +47 -0
- package/nitrogen/generated/ios/swift/PlaybackErrorEvent.swift +48 -0
- package/nitrogen/generated/ios/swift/PlaybackPlayWhenReadyChangedEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/PlaybackProgressUpdatedEvent.swift +69 -0
- package/nitrogen/generated/ios/swift/PlaybackQueueEndedEvent.swift +47 -0
- package/nitrogen/generated/ios/swift/PlaybackState.swift +68 -0
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +385 -0
- package/nitrogen/generated/ios/swift/PlayingState.swift +47 -0
- package/nitrogen/generated/ios/swift/Progress.swift +58 -0
- package/nitrogen/generated/ios/swift/RatingType.swift +60 -0
- package/nitrogen/generated/ios/swift/RemoteJumpBackwardEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RemoteJumpForwardEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RemotePlayIdEvent.swift +59 -0
- package/nitrogen/generated/ios/swift/RemotePlaySearchEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RemoteSeekEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +76 -0
- package/nitrogen/generated/ios/swift/RemoteSkipEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RepeatMode.swift +44 -0
- package/nitrogen/generated/ios/swift/RepeatModeChangedEvent.swift +36 -0
- package/nitrogen/generated/ios/swift/RequestConfig.swift +298 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +511 -0
- package/nitrogen/generated/ios/swift/RetryConfig.swift +59 -0
- package/nitrogen/generated/ios/swift/SearchMode.swift +56 -0
- package/nitrogen/generated/ios/swift/SearchParams.swift +209 -0
- package/nitrogen/generated/ios/swift/SleepTimer.swift +19 -0
- package/nitrogen/generated/ios/swift/SleepTimerEndOfTrack.swift +36 -0
- package/nitrogen/generated/ios/swift/SleepTimerTime.swift +36 -0
- package/nitrogen/generated/ios/swift/StarRating.swift +36 -0
- package/nitrogen/generated/ios/swift/ThumbsRating.swift +36 -0
- package/nitrogen/generated/ios/swift/TimedMetadata.swift +175 -0
- package/nitrogen/generated/ios/swift/Track.swift +488 -0
- package/nitrogen/generated/ios/swift/TrackMetadata.swift +505 -0
- package/nitrogen/generated/ios/swift/TrackStyle.swift +40 -0
- package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +372 -0
- package/nitrogen/generated/ios/swift/UpdateOptions.swift +215 -0
- package/nitrogen/generated/ios/swift/Variant_Bool_AndroidAudioOffloadSettings.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_Bool_RetryConfig.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +20 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_Double.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NullType_NotificationButtonLayout.swift +18 -0
- package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +88 -0
- package/nitrogen/generated/shared/c++/AndroidAudioOffloadSettings.hpp +79 -0
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +101 -0
- package/nitrogen/generated/shared/c++/AndroidPlayerWakeMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +101 -0
- package/nitrogen/generated/shared/c++/AppKilledPlaybackBehavior.hpp +80 -0
- package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +133 -0
- package/nitrogen/generated/shared/c++/BatteryOptimizationStatus.hpp +80 -0
- package/nitrogen/generated/shared/c++/BatteryOptimizationStatusChangedEvent.hpp +76 -0
- package/nitrogen/generated/shared/c++/BatteryWarningPendingChangedEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/BrowseError.hpp +75 -0
- package/nitrogen/generated/shared/c++/BrowserSourceCallbackParam.hpp +81 -0
- package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +84 -0
- package/nitrogen/generated/shared/c++/ChapterMetadata.hpp +88 -0
- package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +105 -0
- package/nitrogen/generated/shared/c++/FavoriteChangedEvent.hpp +80 -0
- package/nitrogen/generated/shared/c++/FormatNavigationErrorParams.hpp +88 -0
- package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +79 -0
- package/nitrogen/generated/shared/c++/HeartRating.hpp +75 -0
- package/nitrogen/generated/shared/c++/HttpMethod.hpp +96 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +207 -0
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +375 -0
- package/nitrogen/generated/shared/c++/IOSCategory.hpp +92 -0
- package/nitrogen/generated/shared/c++/IOSCategoryMode.hpp +104 -0
- package/nitrogen/generated/shared/c++/IOSCategoryOptions.hpp +96 -0
- package/nitrogen/generated/shared/c++/IOSCategoryPolicy.hpp +80 -0
- package/nitrogen/generated/shared/c++/ImageContext.hpp +79 -0
- package/nitrogen/generated/shared/c++/ImageQueryParams.hpp +80 -0
- package/nitrogen/generated/shared/c++/ImageSource.hpp +91 -0
- package/nitrogen/generated/shared/c++/IosOutput.hpp +85 -0
- package/nitrogen/generated/shared/c++/IosOutputType.hpp +112 -0
- package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +123 -0
- package/nitrogen/generated/shared/c++/MediaTransformParams.hpp +84 -0
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +135 -0
- package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +131 -0
- package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +103 -0
- package/nitrogen/generated/shared/c++/NavigationError.hpp +90 -0
- package/nitrogen/generated/shared/c++/NavigationErrorEvent.hpp +77 -0
- package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +88 -0
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +101 -0
- package/nitrogen/generated/shared/c++/NotificationButton.hpp +88 -0
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +94 -0
- package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +124 -0
- package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +80 -0
- package/nitrogen/generated/shared/c++/Options.hpp +105 -0
- package/nitrogen/generated/shared/c++/PartialAndroidSetupPlayerOptions.hpp +121 -0
- package/nitrogen/generated/shared/c++/PartialIOSSetupPlayerOptions.hpp +103 -0
- package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +96 -0
- package/nitrogen/generated/shared/c++/PercentageRating.hpp +75 -0
- package/nitrogen/generated/shared/c++/Playback.hpp +84 -0
- package/nitrogen/generated/shared/c++/PlaybackActiveTrackChangedEvent.hpp +93 -0
- package/nitrogen/generated/shared/c++/PlaybackError.hpp +79 -0
- package/nitrogen/generated/shared/c++/PlaybackErrorEvent.hpp +77 -0
- package/nitrogen/generated/shared/c++/PlaybackPlayWhenReadyChangedEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/PlaybackProgressUpdatedEvent.hpp +87 -0
- package/nitrogen/generated/shared/c++/PlaybackQueueEndedEvent.hpp +79 -0
- package/nitrogen/generated/shared/c++/PlaybackState.hpp +104 -0
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +119 -0
- package/nitrogen/generated/shared/c++/PlayingState.hpp +79 -0
- package/nitrogen/generated/shared/c++/Progress.hpp +83 -0
- package/nitrogen/generated/shared/c++/RatingType.hpp +96 -0
- package/nitrogen/generated/shared/c++/RemoteJumpBackwardEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/RemoteJumpForwardEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/RemotePlayIdEvent.hpp +80 -0
- package/nitrogen/generated/shared/c++/RemotePlaySearchEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/RemoteSeekEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +86 -0
- package/nitrogen/generated/shared/c++/RemoteSkipEvent.hpp +75 -0
- package/nitrogen/generated/shared/c++/RepeatMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/RepeatModeChangedEvent.hpp +76 -0
- package/nitrogen/generated/shared/c++/RequestConfig.hpp +107 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +153 -0
- package/nitrogen/generated/shared/c++/RetryConfig.hpp +79 -0
- package/nitrogen/generated/shared/c++/SearchMode.hpp +92 -0
- package/nitrogen/generated/shared/c++/SearchParams.hpp +102 -0
- package/nitrogen/generated/shared/c++/SleepTimerEndOfTrack.hpp +75 -0
- package/nitrogen/generated/shared/c++/SleepTimerTime.hpp +75 -0
- package/nitrogen/generated/shared/c++/StarRating.hpp +75 -0
- package/nitrogen/generated/shared/c++/ThumbsRating.hpp +75 -0
- package/nitrogen/generated/shared/c++/TimedMetadata.hpp +92 -0
- package/nitrogen/generated/shared/c++/Track.hpp +145 -0
- package/nitrogen/generated/shared/c++/TrackMetadata.hpp +136 -0
- package/nitrogen/generated/shared/c++/TrackStyle.hpp +76 -0
- package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +116 -0
- package/nitrogen/generated/shared/c++/UpdateOptions.hpp +103 -0
- package/package.json +120 -0
- package/src/AudioBrowser.ts +4 -0
- package/src/features/battery.ts +176 -0
- package/src/features/browser.ts +315 -0
- package/src/features/equalizer.ts +64 -0
- package/src/features/errors.ts +196 -0
- package/src/features/favorites.ts +102 -0
- package/src/features/index.ts +23 -0
- package/src/features/metadata.ts +130 -0
- package/src/features/network.ts +34 -0
- package/src/features/nowPlaying.ts +56 -0
- package/src/features/output.ts +50 -0
- package/src/features/playback/controls.ts +66 -0
- package/src/features/playback/index.ts +7 -0
- package/src/features/playback/playWhenReady.ts +57 -0
- package/src/features/playback/playing.ts +40 -0
- package/src/features/playback/progress.ts +122 -0
- package/src/features/playback/rate.ts +22 -0
- package/src/features/playback/state.ts +72 -0
- package/src/features/playback/volume.ts +59 -0
- package/src/features/player/index.ts +2 -0
- package/src/features/player/options.ts +479 -0
- package/src/features/player/setup.ts +551 -0
- package/src/features/queue/activeTrack.ts +59 -0
- package/src/features/queue/index.ts +4 -0
- package/src/features/queue/queue.ts +173 -0
- package/src/features/queue/repeatMode.ts +58 -0
- package/src/features/queue/shuffle.ts +49 -0
- package/src/features/rating.ts +17 -0
- package/src/features/remoteControls.ts +341 -0
- package/src/features/sleepTimer.ts +186 -0
- package/src/index.ts +5 -0
- package/src/native.ts +5 -0
- package/src/native.web.ts +4 -0
- package/src/specs/audio-browser.nitro.ts +326 -0
- package/src/types/browser-native.ts +66 -0
- package/src/types/browser-nodes.ts +174 -0
- package/src/types/browser.ts +792 -0
- package/src/types/index.ts +2 -0
- package/src/types/player.ts +69 -0
- package/src/utils/LazyNativeEmitter.ts +58 -0
- package/src/utils/NativeUpdatedValue.ts +56 -0
- package/src/utils/resolveAssetSource.ts +4 -0
- package/src/utils/useDebug.ts +283 -0
- package/src/utils/useNativeUpdatedValue.ts +41 -0
- package/src/web/NativeAudioBrowser.ts +781 -0
- package/src/web/SimpleRouter.ts +177 -0
- package/src/web/TrackPlayer/Event.ts +11 -0
- package/src/web/TrackPlayer/Player.ts +323 -0
- package/src/web/TrackPlayer/PlaylistPlayer.ts +337 -0
- package/src/web/TrackPlayer/RepeatMode.ts +7 -0
- package/src/web/TrackPlayer/SetupNotCalledError.ts +5 -0
- package/src/web/TrackPlayer/SleepTimer.ts +71 -0
- package/src/web/TrackPlayer/State.ts +15 -0
- package/src/web/TrackPlayer/index.ts +4 -0
- package/src/web/browser/BrowserManager.ts +642 -0
- package/src/web/browser/FavoriteManager.ts +77 -0
- package/src/web/browser/NavigationErrorManager.ts +112 -0
- package/src/web/browser/SearchManager.ts +82 -0
- package/src/web/http/HttpClient.ts +107 -0
- package/src/web/http/RequestConfigBuilder.ts +347 -0
- package/src/web/player/NowPlayingManager.ts +83 -0
- package/src/web/player/OptionsManager.ts +101 -0
- package/src/web/util/BrowserPathHelper.ts +147 -0
- package/src/web/util/shuffle.ts +14 -0
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
import CarPlay
|
|
2
|
+
import Foundation
|
|
3
|
+
import Kingfisher
|
|
4
|
+
import NitroModules
|
|
5
|
+
import os.log
|
|
6
|
+
|
|
7
|
+
/// Controller managing CarPlay templates and navigation.
|
|
8
|
+
///
|
|
9
|
+
/// Responsibilities:
|
|
10
|
+
/// - Creates and manages CPTabBarTemplate from browser tabs
|
|
11
|
+
/// - Converts browser content to CPListTemplate for navigation
|
|
12
|
+
/// - Handles item selection for playback and navigation
|
|
13
|
+
/// - Loads artwork for list items
|
|
14
|
+
/// - Integrates with CPNowPlayingTemplate
|
|
15
|
+
///
|
|
16
|
+
/// This class is exposed to Objective-C for use by RNABCarPlaySceneDelegate.
|
|
17
|
+
@MainActor
|
|
18
|
+
@objc(RNABCarPlayController)
|
|
19
|
+
public final class RNABCarPlayController: NSObject {
|
|
20
|
+
private let logger = Logger(subsystem: "com.audiobrowser", category: "CarPlayController")
|
|
21
|
+
|
|
22
|
+
private let interfaceController: CPInterfaceController
|
|
23
|
+
private weak var audioBrowser: HybridAudioBrowser?
|
|
24
|
+
|
|
25
|
+
/// Track content subscriptions
|
|
26
|
+
private var isStarted = false
|
|
27
|
+
|
|
28
|
+
/// Current navigation stack paths (for back navigation context)
|
|
29
|
+
private var navigationStack: [String] = []
|
|
30
|
+
|
|
31
|
+
/// Helper object for CPNowPlayingTemplateObserver conformance
|
|
32
|
+
/// (kept separate to avoid exposing CarPlay protocols to Obj-C header)
|
|
33
|
+
private var nowPlayingObserver: NowPlayingObserver?
|
|
34
|
+
|
|
35
|
+
/// Helper object for CPInterfaceControllerDelegate conformance
|
|
36
|
+
private var interfaceDelegate: InterfaceControllerDelegate?
|
|
37
|
+
|
|
38
|
+
/// Reference to the Up Next template for updating when queue changes
|
|
39
|
+
private weak var upNextTemplate: CPListTemplate?
|
|
40
|
+
|
|
41
|
+
/// Convenience accessor for browser config
|
|
42
|
+
private var config: BrowserConfig {
|
|
43
|
+
audioBrowser?.browserManager.config ?? BrowserConfig()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Gets the current active track's favorited state
|
|
47
|
+
private var isActiveTrackFavorited: Bool {
|
|
48
|
+
(try? audioBrowser?.getActiveTrack())?.favorited ?? false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Checks if the given src matches the currently active (loaded) track
|
|
52
|
+
private func isActiveTrack(src: String) -> Bool {
|
|
53
|
+
audioBrowser?.getPlayer()?.currentTrack?.src == src
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MARK: - Initialization
|
|
57
|
+
|
|
58
|
+
@objc
|
|
59
|
+
public init(interfaceController: CPInterfaceController) {
|
|
60
|
+
self.interfaceController = interfaceController
|
|
61
|
+
audioBrowser = HybridAudioBrowser.shared
|
|
62
|
+
super.init()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: - Lifecycle
|
|
66
|
+
|
|
67
|
+
@objc
|
|
68
|
+
public func start() {
|
|
69
|
+
guard !isStarted else { return }
|
|
70
|
+
isStarted = true
|
|
71
|
+
|
|
72
|
+
logger.info("Starting CarPlay controller")
|
|
73
|
+
|
|
74
|
+
// Set up interface controller delegate for template lifecycle events
|
|
75
|
+
let delegate = InterfaceControllerDelegate(controller: self)
|
|
76
|
+
interfaceDelegate = delegate
|
|
77
|
+
interfaceController.delegate = delegate
|
|
78
|
+
|
|
79
|
+
// Show loading template while waiting
|
|
80
|
+
showLoadingTemplate()
|
|
81
|
+
|
|
82
|
+
// Wait for both browser and player to be ready
|
|
83
|
+
Task { @MainActor in
|
|
84
|
+
let (browser, _) = await playerAndConfiguredBrowser.wait()
|
|
85
|
+
guard self.isStarted else { return }
|
|
86
|
+
self.logger.debug("AudioBrowser and player ready, setting up CarPlay")
|
|
87
|
+
self.audioBrowser = browser
|
|
88
|
+
self.setupContentSubscriptions()
|
|
89
|
+
self.setupNowPlayingTemplate()
|
|
90
|
+
await self.buildInitialInterface()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@objc
|
|
95
|
+
public func stop() {
|
|
96
|
+
guard isStarted else { return }
|
|
97
|
+
isStarted = false
|
|
98
|
+
|
|
99
|
+
logger.info("Stopping CarPlay controller")
|
|
100
|
+
|
|
101
|
+
// Remove Now Playing observer
|
|
102
|
+
if let observer = nowPlayingObserver {
|
|
103
|
+
CPNowPlayingTemplate.shared.remove(observer)
|
|
104
|
+
nowPlayingObserver = nil
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
navigationStack.removeAll()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// MARK: - Content Subscriptions
|
|
111
|
+
|
|
112
|
+
private func setupContentSubscriptions() {
|
|
113
|
+
guard let audioBrowser else {
|
|
114
|
+
logger.warning("AudioBrowser not available for CarPlay")
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Subscribe to tab changes
|
|
119
|
+
audioBrowser.tabsChangedEmitter.addListener { [weak self] tabs in
|
|
120
|
+
Task { @MainActor in
|
|
121
|
+
self?.handleTabsChanged(tabs)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Subscribe to content changes
|
|
126
|
+
audioBrowser.contentChangedEmitter.addListener { [weak self] content in
|
|
127
|
+
Task { @MainActor in
|
|
128
|
+
self?.handleContentChanged(content)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Subscribe to config changes (for Now Playing buttons)
|
|
133
|
+
audioBrowser.browserManager.onConfigChanged = { [weak self] _ in
|
|
134
|
+
Task { @MainActor in
|
|
135
|
+
self?.setupNowPlayingButtons()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Subscribe to favorite changes (for Now Playing button)
|
|
140
|
+
audioBrowser.favoriteChangedEmitter.addListener { [weak self] _ in
|
|
141
|
+
Task { @MainActor in
|
|
142
|
+
self?.updateFavoriteButtonState()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Subscribe to external content changes (from notifyContentChanged)
|
|
147
|
+
audioBrowser.externalContentChangedEmitter.addListener { [weak self] path in
|
|
148
|
+
self?.notifyContentChanged(path: path)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Subscribe to active track changes (for playing indicator in lists)
|
|
152
|
+
audioBrowser.activeTrackChangedEmitter.addListener { [weak self] event in
|
|
153
|
+
Task { @MainActor in
|
|
154
|
+
self?.handleActiveTrackChanged(event)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Subscribe to queue changes (for Up Next list updates)
|
|
159
|
+
audioBrowser.queueChangedEmitter.addListener { [weak self] tracks in
|
|
160
|
+
Task { @MainActor in
|
|
161
|
+
self?.handleQueueChanged(tracks)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Subscribe to navigation errors (from browser layer)
|
|
166
|
+
audioBrowser.navigationErrorEmitter.addListener { [weak self] event in
|
|
167
|
+
Task { @MainActor in
|
|
168
|
+
self?.handleNavigationError(event)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Handles navigation errors from the browser layer, displaying them in CarPlay
|
|
174
|
+
@MainActor
|
|
175
|
+
private func handleNavigationError(_ event: NavigationErrorEvent) {
|
|
176
|
+
guard let error = event.error else { return }
|
|
177
|
+
logger.warning("Navigation error: \(error.code.stringValue) - \(error.message)")
|
|
178
|
+
// Use current path from navigation stack, or "/" as fallback
|
|
179
|
+
let path = navigationStack.last ?? "/"
|
|
180
|
+
showNavigationError(error, path: path)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// MARK: - Initial Interface
|
|
184
|
+
|
|
185
|
+
@MainActor
|
|
186
|
+
private func buildInitialInterface() async {
|
|
187
|
+
guard let audioBrowser else {
|
|
188
|
+
logger.error("AudioBrowser not available")
|
|
189
|
+
showErrorTemplate(message: "Audio browser not initialized")
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Get tabs from browser manager
|
|
194
|
+
let tabs = audioBrowser.browserManager.getTabs()
|
|
195
|
+
|
|
196
|
+
if let tabs, !tabs.isEmpty {
|
|
197
|
+
await showTabBar(tabs: tabs)
|
|
198
|
+
} else {
|
|
199
|
+
// No tabs yet - query them
|
|
200
|
+
logger.info("No tabs available, querying...")
|
|
201
|
+
do {
|
|
202
|
+
let queriedTabs = try await audioBrowser.browserManager.queryTabs()
|
|
203
|
+
if !queriedTabs.isEmpty {
|
|
204
|
+
await showTabBar(tabs: queriedTabs)
|
|
205
|
+
} else {
|
|
206
|
+
showErrorTemplate(message: "No content available")
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
logger.error("Failed to query tabs: \(error.localizedDescription)")
|
|
210
|
+
showErrorTemplate(message: "Failed to load content")
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// Shows a loading template while waiting for initialization
|
|
216
|
+
private func showLoadingTemplate() {
|
|
217
|
+
let template = CPListTemplate(
|
|
218
|
+
title: nil,
|
|
219
|
+
sections: [],
|
|
220
|
+
)
|
|
221
|
+
interfaceController.setRootTemplate(template, animated: false, completion: nil)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// MARK: - Tab Bar
|
|
225
|
+
|
|
226
|
+
@MainActor
|
|
227
|
+
private func showTabBar(tabs: [Track]) async {
|
|
228
|
+
logger.info("Building tab bar with \(tabs.count) tabs")
|
|
229
|
+
|
|
230
|
+
let maxTabs = CPTabBarTemplate.maximumTabCount
|
|
231
|
+
|
|
232
|
+
// Reserve one slot for search if configured
|
|
233
|
+
let hasSearch = config.hasSearch
|
|
234
|
+
let maxContentTabs = hasSearch ? maxTabs - 1 : maxTabs
|
|
235
|
+
|
|
236
|
+
// Create tab templates synchronously (empty shells) - don't block on content loading
|
|
237
|
+
let tabTemplates: [CPListTemplate] = tabs.prefix(maxContentTabs).map { tab in
|
|
238
|
+
createTabTemplate(for: tab)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Set the tab bar immediately so UI appears fast
|
|
242
|
+
logger.info("Setting tab bar root template with \(tabTemplates.count) templates")
|
|
243
|
+
let tabBar = CPTabBarTemplate(templates: tabTemplates)
|
|
244
|
+
interfaceController.setRootTemplate(tabBar, animated: true, completion: nil)
|
|
245
|
+
|
|
246
|
+
// Load content for the first tab only - others load lazily when selected
|
|
247
|
+
if let firstTemplate = tabTemplates.first, let firstTab = tabs.first, let url = firstTab.url {
|
|
248
|
+
await loadContent(for: url, into: firstTemplate)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Creates a tab template shell without loading content (synchronous)
|
|
253
|
+
private func createTabTemplate(for track: Track) -> CPListTemplate {
|
|
254
|
+
let template = CPListTemplate(
|
|
255
|
+
title: track.title,
|
|
256
|
+
sections: [],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
// Set tab title explicitly (required for tab bar display)
|
|
260
|
+
template.tabTitle = track.title
|
|
261
|
+
|
|
262
|
+
// Store path for lazy loading and refresh
|
|
263
|
+
if let url = track.url {
|
|
264
|
+
template.userInfo = ["path": url] as [String: Any]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Set tab image - CarPlay requires an image for proper tab display
|
|
268
|
+
// Tab bar icons are 24pt x 24pt per CarPlay Developer Guide
|
|
269
|
+
// https://developer.apple.com/download/files/CarPlay-Developer-Guide.pdf
|
|
270
|
+
template.tabImage = defaultTabImage()
|
|
271
|
+
|
|
272
|
+
// Support SF Symbols via "sf:" prefix (e.g., "sf:heart.fill")
|
|
273
|
+
if let artwork = track.artwork, artwork.hasPrefix("sf:") {
|
|
274
|
+
let symbolName = String(artwork.dropFirst(3))
|
|
275
|
+
if let image = sfSymbolImage(symbolName) {
|
|
276
|
+
template.tabImage = image
|
|
277
|
+
}
|
|
278
|
+
} else if track.artwork != nil || track.artworkSource != nil {
|
|
279
|
+
// loadArtwork handles both artwork and artworkSource
|
|
280
|
+
let tabImageSize = CGSize(width: 24, height: 24)
|
|
281
|
+
loadArtwork(for: track, size: tabImageSize) { [weak template] image in
|
|
282
|
+
Task { @MainActor in
|
|
283
|
+
if let image {
|
|
284
|
+
template?.tabImage = image
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return template
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// MARK: - List Templates
|
|
294
|
+
|
|
295
|
+
@MainActor
|
|
296
|
+
private func createListTemplate(
|
|
297
|
+
for resolvedTrack: ResolvedTrack,
|
|
298
|
+
path: String,
|
|
299
|
+
) -> CPListTemplate {
|
|
300
|
+
let template = CPListTemplate(
|
|
301
|
+
title: resolvedTrack.title,
|
|
302
|
+
sections: createSections(from: resolvedTrack),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
template.userInfo = ["path": path] as [String: Any]
|
|
306
|
+
|
|
307
|
+
return template
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// Finds the path associated with a template, if any
|
|
311
|
+
private func getPath(from template: CPTemplate) -> String? {
|
|
312
|
+
(template.userInfo as? [String: Any])?["path"] as? String
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private func createSections(from resolvedTrack: ResolvedTrack) -> [CPListSection] {
|
|
316
|
+
guard let children = resolvedTrack.children else {
|
|
317
|
+
return []
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let maxSections = CPListTemplate.maximumSectionCount
|
|
321
|
+
let maxTotalItems = CPListTemplate.maximumItemCount
|
|
322
|
+
|
|
323
|
+
// Group by groupTitle if present
|
|
324
|
+
var groups: [String?: [Track]] = [:]
|
|
325
|
+
for track in children {
|
|
326
|
+
let groupKey = track.groupTitle
|
|
327
|
+
groups[groupKey, default: []].append(track)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Create sections (respecting both section and total item limits)
|
|
331
|
+
var sections: [CPListSection] = []
|
|
332
|
+
var totalItemCount = 0
|
|
333
|
+
|
|
334
|
+
// Ungrouped items first
|
|
335
|
+
if let ungrouped = groups[nil], !ungrouped.isEmpty {
|
|
336
|
+
let availableSlots = maxTotalItems - totalItemCount
|
|
337
|
+
let items = ungrouped.prefix(availableSlots).map { createListItem(for: $0) }
|
|
338
|
+
if !items.isEmpty {
|
|
339
|
+
sections.append(CPListSection(items: items))
|
|
340
|
+
totalItemCount += items.count
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Then grouped items (respecting section and item limits)
|
|
345
|
+
for (groupTitle, tracks) in groups.sorted(by: { ($0.key ?? "") < ($1.key ?? "") }) {
|
|
346
|
+
guard sections.count < maxSections else { break }
|
|
347
|
+
guard totalItemCount < maxTotalItems else { break }
|
|
348
|
+
guard groupTitle != nil else { continue }
|
|
349
|
+
|
|
350
|
+
let availableSlots = maxTotalItems - totalItemCount
|
|
351
|
+
let items = tracks.prefix(availableSlots).map { createListItem(for: $0) }
|
|
352
|
+
if !items.isEmpty {
|
|
353
|
+
sections.append(CPListSection(items: items, header: groupTitle, sectionIndexTitle: nil))
|
|
354
|
+
totalItemCount += items.count
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return sections
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// Creates a CPListItem for a track with common setup (userInfo, artwork, isPlaying).
|
|
362
|
+
/// - Parameters:
|
|
363
|
+
/// - track: The track to create the item for
|
|
364
|
+
/// - handler: Optional custom handler. If nil, uses default browse/play handling.
|
|
365
|
+
private func createListItem(
|
|
366
|
+
for track: Track,
|
|
367
|
+
handler: ((CPSelectableListItem, @escaping () -> Void) -> Void)? = nil,
|
|
368
|
+
) -> CPListItem {
|
|
369
|
+
let item = CPListItem(
|
|
370
|
+
text: track.title,
|
|
371
|
+
detailText: track.subtitle ?? track.artist,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
// Store track info for selection handling and updatePlayingIndicators()
|
|
375
|
+
item.userInfo = [
|
|
376
|
+
"url": track.url as Any,
|
|
377
|
+
"src": track.src as Any,
|
|
378
|
+
"hasSrc": track.src != nil,
|
|
379
|
+
"hasUrl": track.url != nil,
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
// Set accessory type based on whether track is browsable or playable
|
|
383
|
+
if let src = track.src {
|
|
384
|
+
// Playable track - check if it's currently playing
|
|
385
|
+
item.accessoryType = .none
|
|
386
|
+
item.isPlaying = isActiveTrack(src: src)
|
|
387
|
+
if item.isPlaying {
|
|
388
|
+
logger.debug("Setting isPlaying=true for: \(track.title) (src: \(src))")
|
|
389
|
+
}
|
|
390
|
+
} else if track.url != nil {
|
|
391
|
+
// Browsable only - show disclosure indicator
|
|
392
|
+
item.accessoryType = .disclosureIndicator
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Load artwork with size context for proper CDN optimization
|
|
396
|
+
// Support SF Symbols via "sf:" prefix (e.g., "sf:heart.fill")
|
|
397
|
+
if let artwork = track.artwork, artwork.hasPrefix("sf:") {
|
|
398
|
+
let symbolName = String(artwork.dropFirst(3))
|
|
399
|
+
if let image = sfSymbolImageForListItem(symbolName) {
|
|
400
|
+
item.setImage(image)
|
|
401
|
+
}
|
|
402
|
+
} else if track.artwork != nil || track.artworkSource != nil {
|
|
403
|
+
// Set empty placeholder to reserve space while loading
|
|
404
|
+
item.setImage(placeholderImage)
|
|
405
|
+
loadArtwork(for: track, size: CPListItem.maximumImageSize) { [weak item] image in
|
|
406
|
+
Task { @MainActor in
|
|
407
|
+
item?.setImage(image)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Set selection handler
|
|
413
|
+
if let handler {
|
|
414
|
+
item.handler = handler
|
|
415
|
+
} else {
|
|
416
|
+
item.handler = { [weak self] _, completion in
|
|
417
|
+
self?.handleItemSelection(track: track, completion: completion)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return item
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// MARK: - Content Loading
|
|
425
|
+
|
|
426
|
+
@MainActor
|
|
427
|
+
private func loadContent(for path: String, into template: CPListTemplate) async {
|
|
428
|
+
guard let audioBrowser else { return }
|
|
429
|
+
|
|
430
|
+
do {
|
|
431
|
+
let resolved = try await audioBrowser.browserManager.resolve(path, useCache: true)
|
|
432
|
+
let sections = createSections(from: resolved)
|
|
433
|
+
template.updateSections(sections)
|
|
434
|
+
} catch {
|
|
435
|
+
logger.error("Failed to load content for \(path): \(error.localizedDescription)")
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// MARK: - Selection Handling
|
|
440
|
+
|
|
441
|
+
private func handleItemSelection(track: Track, completion: @escaping () -> Void) {
|
|
442
|
+
logger.info("Selected track: \(track.title)")
|
|
443
|
+
|
|
444
|
+
guard let audioBrowser else {
|
|
445
|
+
completion()
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// If this track is already loaded, resume playback and show Now Playing
|
|
450
|
+
if let src = track.src, isActiveTrack(src: src) {
|
|
451
|
+
try? audioBrowser.play()
|
|
452
|
+
showNowPlaying()
|
|
453
|
+
completion()
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check if this is a contextual URL (playable-only track with queue context)
|
|
458
|
+
if let url = track.url, BrowserPathHelper.isContextual(url) {
|
|
459
|
+
let parentPath = BrowserPathHelper.stripTrackId(url)
|
|
460
|
+
let trackId = BrowserPathHelper.extractTrackId(url)
|
|
461
|
+
let player = audioBrowser.getPlayer()
|
|
462
|
+
|
|
463
|
+
// Check if queue already came from this parent path - just skip to the track
|
|
464
|
+
if let trackId,
|
|
465
|
+
parentPath == player?.queueSourcePath,
|
|
466
|
+
let index = player?.tracks.firstIndex(where: { $0.src == trackId })
|
|
467
|
+
{
|
|
468
|
+
logger.debug("Queue already from \(parentPath), skipping to index \(index)")
|
|
469
|
+
try? player?.skipTo(index, playWhenReady: true)
|
|
470
|
+
showNowPlaying()
|
|
471
|
+
completion()
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
Task {
|
|
476
|
+
do {
|
|
477
|
+
// Expand the queue from the contextual URL
|
|
478
|
+
if let expanded = try await audioBrowser.browserManager.expandQueueFromContextualUrl(url) {
|
|
479
|
+
let (tracks, startIndex) = expanded
|
|
480
|
+
|
|
481
|
+
await MainActor.run {
|
|
482
|
+
// Replace queue and start at the selected track
|
|
483
|
+
audioBrowser.getPlayer()?.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: parentPath)
|
|
484
|
+
|
|
485
|
+
// Show now playing template
|
|
486
|
+
self.showNowPlaying()
|
|
487
|
+
completion()
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
// Fallback: just load the single track
|
|
491
|
+
await MainActor.run {
|
|
492
|
+
try? audioBrowser.load(track: track)
|
|
493
|
+
try? audioBrowser.play()
|
|
494
|
+
self.showNowPlaying()
|
|
495
|
+
completion()
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} catch {
|
|
499
|
+
logger.error("Error expanding queue: \(error.localizedDescription)")
|
|
500
|
+
await MainActor.run {
|
|
501
|
+
try? audioBrowser.load(track: track)
|
|
502
|
+
try? audioBrowser.play()
|
|
503
|
+
self.showNowPlaying()
|
|
504
|
+
completion()
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// If track has src, it's playable - load it
|
|
510
|
+
else if track.src != nil {
|
|
511
|
+
Task { @MainActor in
|
|
512
|
+
try? audioBrowser.load(track: track)
|
|
513
|
+
try? audioBrowser.play()
|
|
514
|
+
showNowPlaying()
|
|
515
|
+
completion()
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// If track has url, it's browsable - navigate to it
|
|
519
|
+
else if let url = track.url {
|
|
520
|
+
navigateToUrl(url, completion: completion)
|
|
521
|
+
} else {
|
|
522
|
+
completion()
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/// Navigates to a browsable URL path, showing error action sheet on failure with retry option.
|
|
527
|
+
private func navigateToUrl(_ url: String, completion: @escaping () -> Void) {
|
|
528
|
+
guard let audioBrowser else {
|
|
529
|
+
completion()
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
Task {
|
|
534
|
+
do {
|
|
535
|
+
let resolved = try await audioBrowser.browserManager.resolve(url, useCache: true)
|
|
536
|
+
|
|
537
|
+
await MainActor.run {
|
|
538
|
+
let listTemplate = self.createListTemplate(for: resolved, path: url)
|
|
539
|
+
self.navigationStack.append(url)
|
|
540
|
+
self.interfaceController.pushTemplate(listTemplate, animated: true, completion: nil)
|
|
541
|
+
completion()
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
544
|
+
logger.error("Failed to navigate to \(url): \(error.localizedDescription)")
|
|
545
|
+
await MainActor.run {
|
|
546
|
+
let navError = self.toNavigationError(error)
|
|
547
|
+
self.showNavigationError(navError, path: url)
|
|
548
|
+
completion()
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// MARK: - Now Playing
|
|
555
|
+
|
|
556
|
+
private func setupNowPlayingTemplate() {
|
|
557
|
+
let template = CPNowPlayingTemplate.shared
|
|
558
|
+
|
|
559
|
+
// Create and register observer for Up Next button
|
|
560
|
+
let observer = NowPlayingObserver(controller: self)
|
|
561
|
+
nowPlayingObserver = observer
|
|
562
|
+
template.add(observer)
|
|
563
|
+
|
|
564
|
+
// Setup custom Now Playing buttons from config
|
|
565
|
+
setupNowPlayingButtons()
|
|
566
|
+
|
|
567
|
+
// Update button states based on config
|
|
568
|
+
updateNowPlayingButtonStates()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/// Sets up custom Now Playing buttons based on configuration
|
|
572
|
+
private func setupNowPlayingButtons() {
|
|
573
|
+
let buttons = config.carPlayNowPlayingButtons
|
|
574
|
+
logger.info("Setting up Now Playing buttons: \(buttons.map(\.stringValue))")
|
|
575
|
+
|
|
576
|
+
guard !buttons.isEmpty else {
|
|
577
|
+
CPNowPlayingTemplate.shared.updateNowPlayingButtons([])
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
var nowPlayingButtons: [CPNowPlayingButton] = []
|
|
582
|
+
|
|
583
|
+
for buttonType in buttons {
|
|
584
|
+
switch buttonType {
|
|
585
|
+
case .shuffle:
|
|
586
|
+
let shuffleButton = CPNowPlayingShuffleButton { [weak self] _ in
|
|
587
|
+
self?.handleShuffleButtonTapped()
|
|
588
|
+
}
|
|
589
|
+
nowPlayingButtons.append(shuffleButton)
|
|
590
|
+
|
|
591
|
+
case .repeat:
|
|
592
|
+
let repeatButton = CPNowPlayingRepeatButton { [weak self] _ in
|
|
593
|
+
self?.handleRepeatButtonTapped()
|
|
594
|
+
}
|
|
595
|
+
nowPlayingButtons.append(repeatButton)
|
|
596
|
+
|
|
597
|
+
case .favorite:
|
|
598
|
+
let favoriteButton = CPNowPlayingImageButton(
|
|
599
|
+
image: favoriteButtonImage(isFavorited: isActiveTrackFavorited),
|
|
600
|
+
) { [weak self] _ in
|
|
601
|
+
self?.handleFavoriteButtonTapped()
|
|
602
|
+
}
|
|
603
|
+
nowPlayingButtons.append(favoriteButton)
|
|
604
|
+
|
|
605
|
+
case .playbackRate:
|
|
606
|
+
let rateButton = CPNowPlayingPlaybackRateButton { [weak self] _ in
|
|
607
|
+
self?.handlePlaybackRateButtonTapped()
|
|
608
|
+
}
|
|
609
|
+
nowPlayingButtons.append(rateButton)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
CPNowPlayingTemplate.shared.updateNowPlayingButtons(nowPlayingButtons)
|
|
614
|
+
logger.info("Updated Now Playing with \(nowPlayingButtons.count) custom button(s)")
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/// Returns the appropriate image for the favorite button based on state
|
|
618
|
+
/// Sized to CPNowPlayingButtonMaximumImageSize per Apple docs
|
|
619
|
+
private func favoriteButtonImage(isFavorited: Bool) -> UIImage {
|
|
620
|
+
let symbolName = isFavorited ? "heart.fill" : "heart"
|
|
621
|
+
guard let image = UIImage(systemName: symbolName)?.resized(to: CPNowPlayingButtonMaximumImageSize) else {
|
|
622
|
+
return UIImage()
|
|
623
|
+
}
|
|
624
|
+
return image
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/// Handles shuffle button tap - toggles shuffle mode
|
|
628
|
+
private func handleShuffleButtonTapped() {
|
|
629
|
+
guard let player = audioBrowser?.getPlayer() else { return }
|
|
630
|
+
|
|
631
|
+
let newEnabled = !player.shuffleEnabled
|
|
632
|
+
player.shuffleEnabled = newEnabled
|
|
633
|
+
logger.info("CarPlay shuffle mode changed: \(newEnabled)")
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/// Handles repeat button tap - cycles through repeat modes
|
|
637
|
+
private func handleRepeatButtonTapped() {
|
|
638
|
+
guard let player = audioBrowser?.getPlayer() else { return }
|
|
639
|
+
|
|
640
|
+
let currentMode = player.getRepeatMode()
|
|
641
|
+
let newMode: RepeatMode = switch currentMode {
|
|
642
|
+
case .off:
|
|
643
|
+
.track
|
|
644
|
+
case .track:
|
|
645
|
+
.queue
|
|
646
|
+
case .queue:
|
|
647
|
+
.off
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
player.setRepeatMode(newMode)
|
|
651
|
+
logger.info("CarPlay repeat mode changed: \(currentMode.stringValue) → \(newMode.stringValue)")
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/// Handles favorite button tap - toggles favorite state of current track
|
|
655
|
+
private func handleFavoriteButtonTapped() {
|
|
656
|
+
try? audioBrowser?.toggleActiveTrackFavorited()
|
|
657
|
+
logger.info("CarPlay favorite toggled")
|
|
658
|
+
// Button appearance is updated via onFavoriteChanged subscription
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/// Handles playback rate button tap - cycles through available rates
|
|
662
|
+
private func handlePlaybackRateButtonTapped() {
|
|
663
|
+
guard let audioBrowser, let player = audioBrowser.getPlayer() else { return }
|
|
664
|
+
|
|
665
|
+
let rates = audioBrowser.playbackRates
|
|
666
|
+
guard !rates.isEmpty else { return }
|
|
667
|
+
|
|
668
|
+
let currentRate = Double(player.rate)
|
|
669
|
+
let nextRate: Double
|
|
670
|
+
|
|
671
|
+
// Find current rate index and cycle to next
|
|
672
|
+
if let currentIndex = rates.firstIndex(where: { (currentRate - $0).magnitude < 0.01 }) {
|
|
673
|
+
// Current rate is in the list - cycle to next
|
|
674
|
+
let nextIndex = (currentIndex + 1) % rates.count
|
|
675
|
+
nextRate = rates[nextIndex]
|
|
676
|
+
} else {
|
|
677
|
+
// Current rate not in list - find first rate greater than current, or wrap to first
|
|
678
|
+
nextRate = rates.first { $0 > currentRate } ?? rates[0]
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
player.rate = Float(nextRate)
|
|
682
|
+
logger.info("CarPlay playback rate changed: \(currentRate) → \(nextRate)")
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/// Updates the favorite button appearance based on current track's favorite state
|
|
686
|
+
private func updateFavoriteButtonState() {
|
|
687
|
+
guard config.carPlayNowPlayingButtons.contains(.favorite) else { return }
|
|
688
|
+
let favorited = isActiveTrackFavorited
|
|
689
|
+
let buttons = CPNowPlayingTemplate.shared.nowPlayingButtons
|
|
690
|
+
|
|
691
|
+
// Find and update the favorite button (it's a CPNowPlayingImageButton)
|
|
692
|
+
for (index, button) in buttons.enumerated() {
|
|
693
|
+
if button is CPNowPlayingImageButton {
|
|
694
|
+
// Recreate the button with updated image
|
|
695
|
+
let newFavoriteButton = CPNowPlayingImageButton(
|
|
696
|
+
image: favoriteButtonImage(isFavorited: favorited),
|
|
697
|
+
) { [weak self] _ in
|
|
698
|
+
self?.handleFavoriteButtonTapped()
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
var updatedButtons = buttons
|
|
702
|
+
updatedButtons[index] = newFavoriteButton
|
|
703
|
+
CPNowPlayingTemplate.shared.updateNowPlayingButtons(updatedButtons)
|
|
704
|
+
break
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/// Updates Now Playing button states based on config and current queue
|
|
710
|
+
private func updateNowPlayingButtonStates() {
|
|
711
|
+
updateNowPlayingUpNextButton()
|
|
712
|
+
updateFavoriteButtonState()
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/// Updates the Up Next button enabled state based on config and queue size
|
|
716
|
+
private func updateNowPlayingUpNextButton() {
|
|
717
|
+
let template = CPNowPlayingTemplate.shared
|
|
718
|
+
template.isUpNextButtonEnabled = config.carPlayUpNextButton && (audioBrowser?.getPlayer()?.tracks.count ?? 0) > 1
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private func showNowPlaying() {
|
|
722
|
+
updateNowPlayingButtonStates()
|
|
723
|
+
let nowPlayingTemplate = CPNowPlayingTemplate.shared
|
|
724
|
+
interfaceController.pushTemplate(nowPlayingTemplate, animated: true, completion: nil)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// MARK: - Content Change Handlers
|
|
728
|
+
|
|
729
|
+
@MainActor
|
|
730
|
+
private func handleTabsChanged(_ tabs: [Track]) {
|
|
731
|
+
logger.debug("Tabs changed: \(tabs.count) tabs")
|
|
732
|
+
// Rebuild tab bar if we're at the root
|
|
733
|
+
Task {
|
|
734
|
+
await showTabBar(tabs: tabs)
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
@MainActor
|
|
739
|
+
private func handleContentChanged(_ content: ResolvedTrack?) {
|
|
740
|
+
// This callback fires when the main browser's content changes.
|
|
741
|
+
// For CarPlay-specific refreshes (e.g., favorites), use notifyContentChanged instead.
|
|
742
|
+
guard let content else { return }
|
|
743
|
+
refreshTemplatesForPath(content.url, with: content)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
@MainActor
|
|
747
|
+
private func handleActiveTrackChanged(_ event: PlaybackActiveTrackChangedEvent) {
|
|
748
|
+
logger.debug("handleActiveTrackChanged: \(event.lastTrack?.src ?? "nil") → \(event.track?.src ?? "nil")")
|
|
749
|
+
updatePlayingIndicators()
|
|
750
|
+
// Update favorite button to reflect the new track's favorite state
|
|
751
|
+
updateFavoriteButtonState()
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/// Updates the isPlaying state on all list items based on the current active track.
|
|
755
|
+
@MainActor
|
|
756
|
+
fileprivate func updatePlayingIndicators() {
|
|
757
|
+
var templates: [CPListTemplate] = []
|
|
758
|
+
|
|
759
|
+
if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate {
|
|
760
|
+
for template in tabBar.templates {
|
|
761
|
+
if let listTemplate = template as? CPListTemplate {
|
|
762
|
+
templates.append(listTemplate)
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if let topTemplate = interfaceController.topTemplate as? CPListTemplate,
|
|
768
|
+
!templates.contains(where: { $0 === topTemplate })
|
|
769
|
+
{
|
|
770
|
+
templates.append(topTemplate)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
for template in templates {
|
|
774
|
+
for section in template.sections {
|
|
775
|
+
for item in section.items {
|
|
776
|
+
guard let listItem = item as? CPListItem,
|
|
777
|
+
let userInfo = listItem.userInfo as? [String: Any],
|
|
778
|
+
let itemSrc = userInfo["src"] as? String
|
|
779
|
+
else { continue }
|
|
780
|
+
|
|
781
|
+
let isPlaying = isActiveTrack(src: itemSrc)
|
|
782
|
+
if listItem.isPlaying != isPlaying {
|
|
783
|
+
logger.debug("Updating isPlaying for \(itemSrc): \(listItem.isPlaying) → \(isPlaying)")
|
|
784
|
+
listItem.isPlaying = isPlaying
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// MARK: - Public Content Notification
|
|
792
|
+
|
|
793
|
+
/// Notifies CarPlay that content at the given path has changed and should be refreshed.
|
|
794
|
+
/// Called from HybridAudioBrowser.notifyContentChanged() to update CarPlay lists.
|
|
795
|
+
///
|
|
796
|
+
/// - Parameter path: The path where content has changed (e.g., "/favorites")
|
|
797
|
+
@objc
|
|
798
|
+
public func notifyContentChanged(path: String) {
|
|
799
|
+
guard isStarted else { return }
|
|
800
|
+
|
|
801
|
+
Task { @MainActor in
|
|
802
|
+
await refreshContentForPath(path)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/// Fetches fresh content for a path and updates any matching CarPlay templates.
|
|
807
|
+
@MainActor
|
|
808
|
+
private func refreshContentForPath(_ path: String) async {
|
|
809
|
+
guard let audioBrowser else { return }
|
|
810
|
+
|
|
811
|
+
logger.debug("Refreshing CarPlay content for path: \(path)")
|
|
812
|
+
|
|
813
|
+
// Fetch fresh content (bypassing cache since content changed)
|
|
814
|
+
do {
|
|
815
|
+
let resolved = try await audioBrowser.browserManager.resolve(path, useCache: false)
|
|
816
|
+
refreshTemplatesForPath(path, with: resolved)
|
|
817
|
+
} catch {
|
|
818
|
+
logger.error("Failed to refresh content for \(path): \(error.localizedDescription)")
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/// Updates all CarPlay templates that are displaying the given path.
|
|
823
|
+
@MainActor
|
|
824
|
+
private func refreshTemplatesForPath(_ path: String, with content: ResolvedTrack) {
|
|
825
|
+
logger.debug("Content changed for path: \(path)")
|
|
826
|
+
|
|
827
|
+
// Check if the root template is a tab bar and refresh matching tabs
|
|
828
|
+
if let tabBar = interfaceController.rootTemplate as? CPTabBarTemplate {
|
|
829
|
+
for template in tabBar.templates {
|
|
830
|
+
guard let listTemplate = template as? CPListTemplate,
|
|
831
|
+
let templatePath = getPath(from: listTemplate),
|
|
832
|
+
templatePath == path
|
|
833
|
+
else { continue }
|
|
834
|
+
|
|
835
|
+
logger.info("Refreshing tab template for path: \(path)")
|
|
836
|
+
let sections = createSections(from: content)
|
|
837
|
+
listTemplate.updateSections(sections)
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Check if any template in the navigation stack matches the changed path
|
|
842
|
+
// The top template is the currently visible one
|
|
843
|
+
if let topTemplate = interfaceController.topTemplate as? CPListTemplate,
|
|
844
|
+
let templatePath = getPath(from: topTemplate),
|
|
845
|
+
templatePath == path
|
|
846
|
+
{
|
|
847
|
+
logger.info("Refreshing top template for path: \(path)")
|
|
848
|
+
let sections = createSections(from: content)
|
|
849
|
+
topTemplate.updateSections(sections)
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// MARK: - Error Handling
|
|
854
|
+
|
|
855
|
+
/// Shows a navigation error using CPActionSheetTemplate.
|
|
856
|
+
/// - Parameters:
|
|
857
|
+
/// - error: The NavigationError to display
|
|
858
|
+
/// - path: The path that was being navigated to when the error occurred
|
|
859
|
+
private func showNavigationError(_ error: NavigationError, path: String) {
|
|
860
|
+
let defaultFormatted = defaultFormattedError(error)
|
|
861
|
+
|
|
862
|
+
// Check if custom formatter is configured
|
|
863
|
+
logger.debug("showNavigationError: formatNavigationError is \(self.config.formatNavigationError != nil ? "set" : "nil")")
|
|
864
|
+
if let formatter = config.formatNavigationError {
|
|
865
|
+
logger.debug("Calling formatNavigationError callback...")
|
|
866
|
+
// Call the JS callback and handle result
|
|
867
|
+
let params = FormatNavigationErrorParams(error: error, defaultFormatted: defaultFormatted, path: path)
|
|
868
|
+
formatter(params)
|
|
869
|
+
.then { [weak self] customDisplay in
|
|
870
|
+
self?.logger.debug("formatNavigationError returned: \(String(describing: customDisplay))")
|
|
871
|
+
self?.presentErrorActionSheet(customDisplay: customDisplay ?? defaultFormatted)
|
|
872
|
+
}
|
|
873
|
+
.catch { [weak self] callbackError in
|
|
874
|
+
self?.logger.error("formatNavigationError failed: \(callbackError)")
|
|
875
|
+
// On error, fall back to defaults
|
|
876
|
+
self?.presentErrorActionSheet(customDisplay: defaultFormatted)
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
presentErrorActionSheet(customDisplay: defaultFormatted)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/// Returns the default formatted error for the given navigation error
|
|
884
|
+
private func defaultFormattedError(_ error: NavigationError) -> FormattedNavigationError {
|
|
885
|
+
let title = switch error.code {
|
|
886
|
+
case .contentNotFound:
|
|
887
|
+
"Content Not Found"
|
|
888
|
+
case .networkError:
|
|
889
|
+
"Network Error"
|
|
890
|
+
case .httpError:
|
|
891
|
+
httpErrorTitle(statusCode: error.statusCode.map { Int($0) })
|
|
892
|
+
case .callbackError:
|
|
893
|
+
"Error"
|
|
894
|
+
case .unknownError:
|
|
895
|
+
"Error"
|
|
896
|
+
}
|
|
897
|
+
return FormattedNavigationError(title: title, message: error.message)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/// Presents the error action sheet with the given display info.
|
|
901
|
+
/// - Parameter customDisplay: The formatted error to display
|
|
902
|
+
private func presentErrorActionSheet(customDisplay: FormattedNavigationError) {
|
|
903
|
+
// If another template is already presented, dismiss it first
|
|
904
|
+
if interfaceController.presentedTemplate != nil {
|
|
905
|
+
interfaceController.dismissTemplate(animated: false) { [weak self] _, _ in
|
|
906
|
+
self?.showErrorActionSheet(customDisplay: customDisplay)
|
|
907
|
+
}
|
|
908
|
+
} else {
|
|
909
|
+
showErrorActionSheet(customDisplay: customDisplay)
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/// Actually shows the error action sheet (called after safety checks)
|
|
914
|
+
private func showErrorActionSheet(customDisplay: FormattedNavigationError) {
|
|
915
|
+
// OK action - dismiss the action sheet (use system-localized "OK")
|
|
916
|
+
let okTitle = Bundle(for: UIAlertController.self).localizedString(forKey: "OK", value: "OK", table: nil)
|
|
917
|
+
let ok = CPAlertAction(title: okTitle, style: .cancel) { [weak self] _ in
|
|
918
|
+
self?.interfaceController.dismissTemplate(animated: true, completion: nil)
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
let actionSheet = CPActionSheetTemplate(
|
|
922
|
+
title: customDisplay.title,
|
|
923
|
+
message: customDisplay.message,
|
|
924
|
+
actions: [ok],
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
interfaceController.presentTemplate(actionSheet, animated: true, completion: nil)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/// Returns a localized title for HTTP errors based on status code
|
|
931
|
+
private func httpErrorTitle(statusCode: Int?) -> String {
|
|
932
|
+
guard let code = statusCode else { return "Server Error" }
|
|
933
|
+
return HTTPURLResponse.localizedString(forStatusCode: code).capitalized
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/// Shows a simple error template as root (for initialization errors when no other template exists)
|
|
937
|
+
private func showErrorTemplate(message: String) {
|
|
938
|
+
// CPAlertTemplate cannot be set as root - use a list template instead
|
|
939
|
+
let errorItem = CPListItem(text: message, detailText: nil)
|
|
940
|
+
errorItem.isEnabled = false
|
|
941
|
+
let template = CPListTemplate(
|
|
942
|
+
title: "Error",
|
|
943
|
+
sections: [CPListSection(items: [errorItem])],
|
|
944
|
+
)
|
|
945
|
+
interfaceController.setRootTemplate(template, animated: true, completion: nil)
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/// Converts a generic Error (typically BrowserError) to a NavigationError
|
|
949
|
+
private func toNavigationError(_ error: Error) -> NavigationError {
|
|
950
|
+
if let browserError = error as? BrowserError {
|
|
951
|
+
switch browserError {
|
|
952
|
+
case .contentNotFound:
|
|
953
|
+
NavigationError(code: .contentNotFound, message: browserError.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
954
|
+
case let .httpError(code, _):
|
|
955
|
+
NavigationError(code: .httpError, message: browserError.localizedDescription, statusCode: Double(code), statusCodeSuccess: (200 ... 299).contains(code))
|
|
956
|
+
case .networkError:
|
|
957
|
+
NavigationError(code: .networkError, message: browserError.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
958
|
+
case .invalidConfiguration:
|
|
959
|
+
NavigationError(code: .unknownError, message: browserError.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
960
|
+
case .callbackError:
|
|
961
|
+
NavigationError(code: .callbackError, message: browserError.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
962
|
+
}
|
|
963
|
+
} else if let httpError = error as? HttpClient.HttpException {
|
|
964
|
+
// HTTP error from HttpClient (non-2xx response)
|
|
965
|
+
NavigationError(code: .httpError, message: httpError.localizedDescription, statusCode: Double(httpError.code), statusCodeSuccess: (200 ... 299).contains(httpError.code))
|
|
966
|
+
} else if error is URLError {
|
|
967
|
+
// Network error (connection failed, timeout, no internet, etc.)
|
|
968
|
+
NavigationError(code: .networkError, message: error.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
969
|
+
} else {
|
|
970
|
+
NavigationError(code: .unknownError, message: error.localizedDescription, statusCode: nil, statusCodeSuccess: nil)
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// MARK: - Image Loading
|
|
975
|
+
|
|
976
|
+
/// Creates an SF Symbol image for tabs - plain systemName, CarPlay handles tinting
|
|
977
|
+
private func sfSymbolImage(_ symbolName: String) -> UIImage? {
|
|
978
|
+
UIImage(systemName: symbolName)
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/// Creates an SF Symbol image for list items with light/dark mode support
|
|
982
|
+
private func sfSymbolImageForListItem(_ symbolName: String) -> UIImage? {
|
|
983
|
+
guard let symbol = UIImage(systemName: symbolName) else { return nil }
|
|
984
|
+
|
|
985
|
+
let size = symbol.size
|
|
986
|
+
let scale = symbol.scale
|
|
987
|
+
|
|
988
|
+
// Create both light and dark bitmap variants
|
|
989
|
+
let lightImage = renderSymbolToBitmap(symbol, tintColor: .black, size: size, scale: scale)
|
|
990
|
+
let darkImage = renderSymbolToBitmap(symbol, tintColor: .white, size: size, scale: scale)
|
|
991
|
+
|
|
992
|
+
// Combine with UIImageAsset for automatic light/dark switching
|
|
993
|
+
let asset = UIImageAsset()
|
|
994
|
+
asset.register(lightImage, with: UITraitCollection(userInterfaceStyle: .light))
|
|
995
|
+
asset.register(darkImage, with: UITraitCollection(userInterfaceStyle: .dark))
|
|
996
|
+
|
|
997
|
+
return asset.image(with: interfaceController.carTraitCollection)
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/// Renders an SF Symbol to a bitmap with the specified tint color
|
|
1001
|
+
private nonisolated func renderSymbolToBitmap(_ symbol: UIImage, tintColor: UIColor, size: CGSize, scale: CGFloat) -> UIImage {
|
|
1002
|
+
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|
1003
|
+
defer { UIGraphicsEndImageContext() }
|
|
1004
|
+
|
|
1005
|
+
tintColor.set()
|
|
1006
|
+
symbol.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: size))
|
|
1007
|
+
|
|
1008
|
+
guard let rendered = UIGraphicsGetImageFromCurrentImageContext() else {
|
|
1009
|
+
return symbol // Fallback to original if rendering fails
|
|
1010
|
+
}
|
|
1011
|
+
return rendered.withRenderingMode(.alwaysOriginal)
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private func defaultTabImage() -> UIImage? {
|
|
1015
|
+
sfSymbolImage("music.note.list")
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/// Cached empty placeholder image to reserve space while artwork loads
|
|
1019
|
+
private lazy var placeholderImage: UIImage? = {
|
|
1020
|
+
let size = CPListItem.maximumImageSize
|
|
1021
|
+
let scale = interfaceController.carTraitCollection.displayScale
|
|
1022
|
+
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|
1023
|
+
defer { UIGraphicsEndImageContext() }
|
|
1024
|
+
return UIGraphicsGetImageFromCurrentImageContext()
|
|
1025
|
+
}()
|
|
1026
|
+
|
|
1027
|
+
/// Loads artwork for a track with size context, using the artwork transform if configured.
|
|
1028
|
+
/// - Parameters:
|
|
1029
|
+
/// - track: The track to load artwork for
|
|
1030
|
+
/// - size: The target size in points (will be multiplied by CarPlay display scale)
|
|
1031
|
+
/// - completion: Called with the loaded image, or nil on failure
|
|
1032
|
+
private func loadArtwork(for track: Track, size: CGSize, completion: @escaping @Sendable (UIImage?) -> Void) {
|
|
1033
|
+
guard let browserManager = audioBrowser?.browserManager else {
|
|
1034
|
+
// Fall back to direct URL loading
|
|
1035
|
+
loadArtworkDirect(track: track, completion: completion)
|
|
1036
|
+
return
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Convert points to pixels using CarPlay display scale (not iPhone screen scale)
|
|
1040
|
+
let carTraits = interfaceController.carTraitCollection
|
|
1041
|
+
let scale = carTraits.displayScale
|
|
1042
|
+
let imageContext = ImageContext(width: size.width * scale, height: size.height * scale)
|
|
1043
|
+
|
|
1044
|
+
Task {
|
|
1045
|
+
// Resolve artwork URL with size context
|
|
1046
|
+
let imageSource = await browserManager.resolveArtworkUrl(
|
|
1047
|
+
track: track,
|
|
1048
|
+
perRouteConfig: nil,
|
|
1049
|
+
imageContext: imageContext,
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
await MainActor.run {
|
|
1053
|
+
if let imageSource {
|
|
1054
|
+
// Check for SF Symbol URI (e.g., "sf:heart.fill")
|
|
1055
|
+
if imageSource.uri.hasPrefix("sf:") {
|
|
1056
|
+
let symbolName = String(imageSource.uri.dropFirst(3))
|
|
1057
|
+
completion(self.sfSymbolImageForListItem(symbolName))
|
|
1058
|
+
return
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Parse URL - skip if invalid
|
|
1062
|
+
guard let url = URL(string: imageSource.uri) else {
|
|
1063
|
+
self.loadArtworkDirect(track: track, completion: completion)
|
|
1064
|
+
return
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// Capture trait collection before async call
|
|
1068
|
+
let carTraitCollection = self.interfaceController.carTraitCollection
|
|
1069
|
+
|
|
1070
|
+
// Load from resolved URL with any custom headers
|
|
1071
|
+
var options: KingfisherOptionsInfo = []
|
|
1072
|
+
if let headers = imageSource.headers, !headers.isEmpty {
|
|
1073
|
+
let modifier = AnyModifier { request in
|
|
1074
|
+
var request = request
|
|
1075
|
+
for (key, value) in headers {
|
|
1076
|
+
request.setValue(value, forHTTPHeaderField: key)
|
|
1077
|
+
}
|
|
1078
|
+
return request
|
|
1079
|
+
}
|
|
1080
|
+
options.append(.requestModifier(modifier))
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Add SVG processor if URL is an SVG
|
|
1084
|
+
if url.pathExtension.lowercased() == "svg" {
|
|
1085
|
+
options.append(.processor(SVGProcessor(size: nil, scale: carTraitCollection.displayScale)))
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Capture tinting preference before async call
|
|
1089
|
+
let shouldTint = track.artworkCarPlayTinted ?? false
|
|
1090
|
+
|
|
1091
|
+
KingfisherManager.shared.retrieveImage(with: url, options: options) { result in
|
|
1092
|
+
if case let .success(imageResult) = result {
|
|
1093
|
+
let image = imageResult.image
|
|
1094
|
+
|
|
1095
|
+
if shouldTint {
|
|
1096
|
+
// Apply light/dark tinting for monochrome icons
|
|
1097
|
+
completion(self.createAdaptiveImage(image, carTraitCollection: carTraitCollection))
|
|
1098
|
+
} else {
|
|
1099
|
+
// Regular images (photos, album art) - show as-is
|
|
1100
|
+
completion(image)
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
completion(nil)
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
} else {
|
|
1107
|
+
// No resolved URL - try direct loading as fallback
|
|
1108
|
+
self.loadArtworkDirect(track: track, completion: completion)
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/// Loads artwork directly from track's artwork URL without transform.
|
|
1115
|
+
private func loadArtworkDirect(track: Track, completion: @escaping @Sendable (UIImage?) -> Void) {
|
|
1116
|
+
guard let artworkUrl = track.artwork ?? track.artworkSource?.uri else {
|
|
1117
|
+
completion(nil)
|
|
1118
|
+
return
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Check for SF Symbol URI (e.g., "sf:heart.fill")
|
|
1122
|
+
if artworkUrl.hasPrefix("sf:") {
|
|
1123
|
+
let symbolName = String(artworkUrl.dropFirst(3))
|
|
1124
|
+
completion(sfSymbolImageForListItem(symbolName))
|
|
1125
|
+
return
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
guard let url = URL(string: artworkUrl) else {
|
|
1129
|
+
completion(nil)
|
|
1130
|
+
return
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Capture trait collection before async call
|
|
1134
|
+
let carTraitCollection = interfaceController.carTraitCollection
|
|
1135
|
+
let isSvg = url.pathExtension.lowercased() == "svg"
|
|
1136
|
+
let shouldTint = track.artworkCarPlayTinted ?? false
|
|
1137
|
+
|
|
1138
|
+
// Add SVG processor if URL is an SVG
|
|
1139
|
+
var options: KingfisherOptionsInfo = []
|
|
1140
|
+
if isSvg {
|
|
1141
|
+
options.append(.processor(SVGProcessor(size: nil, scale: carTraitCollection.displayScale)))
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
KingfisherManager.shared.retrieveImage(with: url, options: options) { result in
|
|
1145
|
+
if case let .success(imageResult) = result {
|
|
1146
|
+
let image = imageResult.image
|
|
1147
|
+
|
|
1148
|
+
if shouldTint {
|
|
1149
|
+
// Apply light/dark tinting for monochrome icons
|
|
1150
|
+
completion(self.createAdaptiveImage(image, carTraitCollection: carTraitCollection))
|
|
1151
|
+
} else {
|
|
1152
|
+
// Regular images (photos, album art) - show as-is
|
|
1153
|
+
completion(image)
|
|
1154
|
+
}
|
|
1155
|
+
} else {
|
|
1156
|
+
completion(nil)
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/// Renders an image to a bitmap with the specified tint color (for monochrome icons).
|
|
1162
|
+
/// Thread-safe: UIGraphicsBeginImageContextWithOptions is safe to call from any thread (iOS 4+).
|
|
1163
|
+
private nonisolated func renderImageToBitmap(_ image: UIImage, tintColor: UIColor) -> UIImage {
|
|
1164
|
+
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
|
|
1165
|
+
defer { UIGraphicsEndImageContext() }
|
|
1166
|
+
|
|
1167
|
+
tintColor.set()
|
|
1168
|
+
image.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: image.size))
|
|
1169
|
+
|
|
1170
|
+
guard let rendered = UIGraphicsGetImageFromCurrentImageContext() else {
|
|
1171
|
+
return image // Fallback to original if rendering fails
|
|
1172
|
+
}
|
|
1173
|
+
return rendered.withRenderingMode(.alwaysOriginal)
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/// Creates light/dark tinted variants of an image and returns the appropriate one for current appearance
|
|
1177
|
+
private nonisolated func createAdaptiveImage(_ image: UIImage, carTraitCollection: UITraitCollection) -> UIImage {
|
|
1178
|
+
let lightImage = renderImageToBitmap(image, tintColor: .black)
|
|
1179
|
+
let darkImage = renderImageToBitmap(image, tintColor: .white)
|
|
1180
|
+
|
|
1181
|
+
let asset = UIImageAsset()
|
|
1182
|
+
asset.register(lightImage, with: UITraitCollection(userInterfaceStyle: .light))
|
|
1183
|
+
asset.register(darkImage, with: UITraitCollection(userInterfaceStyle: .dark))
|
|
1184
|
+
|
|
1185
|
+
return asset.image(with: carTraitCollection)
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// MARK: - Now Playing Observer
|
|
1190
|
+
|
|
1191
|
+
/// Private helper class for CPNowPlayingTemplateObserver conformance.
|
|
1192
|
+
/// Kept separate from RNABCarPlayController to avoid exposing CarPlay protocols to Obj-C header.
|
|
1193
|
+
private final class NowPlayingObserver: NSObject, CPNowPlayingTemplateObserver, @unchecked Sendable {
|
|
1194
|
+
private let logger = Logger(subsystem: "com.audiobrowser", category: "NowPlayingObserver")
|
|
1195
|
+
private weak var controller: RNABCarPlayController?
|
|
1196
|
+
|
|
1197
|
+
@MainActor
|
|
1198
|
+
init(controller: RNABCarPlayController) {
|
|
1199
|
+
self.controller = controller
|
|
1200
|
+
super.init()
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
func nowPlayingTemplateUpNextButtonTapped(_: CPNowPlayingTemplate) {
|
|
1204
|
+
Task { @MainActor in
|
|
1205
|
+
controller?.handleUpNextButtonTapped()
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
func nowPlayingTemplateAlbumArtistButtonTapped(_: CPNowPlayingTemplate) {
|
|
1210
|
+
// Album/Artist button functionality - can be implemented later
|
|
1211
|
+
logger.debug("Album/Artist button tapped (not implemented)")
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// MARK: - Up Next Handler
|
|
1216
|
+
|
|
1217
|
+
private extension RNABCarPlayController {
|
|
1218
|
+
/// Handles the Up Next button tap from Now Playing screen
|
|
1219
|
+
func handleUpNextButtonTapped() {
|
|
1220
|
+
guard let player = audioBrowser?.getPlayer() else {
|
|
1221
|
+
logger.warning("Player not available for Up Next")
|
|
1222
|
+
return
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
let tracks = player.tracks
|
|
1226
|
+
|
|
1227
|
+
guard !tracks.isEmpty else {
|
|
1228
|
+
logger.debug("No tracks in queue for Up Next")
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
logger.info("Showing Up Next queue with \(tracks.count) tracks")
|
|
1233
|
+
|
|
1234
|
+
let template = CPListTemplate(
|
|
1235
|
+
title: "Up Next",
|
|
1236
|
+
sections: [createUpNextSections(tracks: tracks, player: player)],
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
// Store reference for queue change updates
|
|
1240
|
+
upNextTemplate = template
|
|
1241
|
+
|
|
1242
|
+
interfaceController.pushTemplate(template, animated: true, completion: nil)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/// Creates list items for the Up Next queue
|
|
1246
|
+
func createUpNextSections(tracks: [Track], player: TrackPlayer) -> CPListSection {
|
|
1247
|
+
let items = tracks.enumerated().map { index, track -> CPListItem in
|
|
1248
|
+
createListItem(for: track) { [weak self] _, completion in
|
|
1249
|
+
self?.logger.info("Skipping to track at index \(index): \(track.title)")
|
|
1250
|
+
do {
|
|
1251
|
+
try player.skipTo(index, playWhenReady: true)
|
|
1252
|
+
} catch {
|
|
1253
|
+
self?.logger.error("Failed to skip to track: \(error.localizedDescription)")
|
|
1254
|
+
}
|
|
1255
|
+
completion()
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return CPListSection(items: items)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/// Handles queue changes - updates Up Next list if visible
|
|
1262
|
+
@MainActor
|
|
1263
|
+
func handleQueueChanged(_ tracks: [Track]) {
|
|
1264
|
+
// Update the Up Next button enabled state
|
|
1265
|
+
updateNowPlayingUpNextButton()
|
|
1266
|
+
|
|
1267
|
+
// Update the Up Next template if it's currently visible
|
|
1268
|
+
guard let template = upNextTemplate,
|
|
1269
|
+
let player = audioBrowser?.getPlayer()
|
|
1270
|
+
else {
|
|
1271
|
+
return
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
logger.debug("Queue changed, updating Up Next list with \(tracks.count) tracks")
|
|
1275
|
+
template.updateSections([createUpNextSections(tracks: tracks, player: player)])
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// MARK: - CPInterfaceControllerDelegate
|
|
1280
|
+
|
|
1281
|
+
/// Separate delegate class to avoid exposing CPInterfaceControllerDelegate to Obj-C header
|
|
1282
|
+
private final class InterfaceControllerDelegate: NSObject, CPInterfaceControllerDelegate {
|
|
1283
|
+
private weak var controller: RNABCarPlayController?
|
|
1284
|
+
|
|
1285
|
+
init(controller: RNABCarPlayController) {
|
|
1286
|
+
self.controller = controller
|
|
1287
|
+
super.init()
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
func templateDidAppear(_ aTemplate: CPTemplate, animated _: Bool) {
|
|
1291
|
+
guard let listTemplate = aTemplate as? CPListTemplate else { return }
|
|
1292
|
+
|
|
1293
|
+
// Update playing indicators when navigating back to a list template
|
|
1294
|
+
controller?.updatePlayingIndicators()
|
|
1295
|
+
|
|
1296
|
+
// Lazy load content for tabs that haven't been loaded yet
|
|
1297
|
+
controller?.loadContentIfNeeded(for: listTemplate)
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// MARK: - Lazy Loading
|
|
1302
|
+
|
|
1303
|
+
private extension RNABCarPlayController {
|
|
1304
|
+
/// Loads content for a template if it hasn't been loaded yet (lazy loading for tabs)
|
|
1305
|
+
func loadContentIfNeeded(for template: CPListTemplate) {
|
|
1306
|
+
// Skip if already has content
|
|
1307
|
+
guard template.sections.isEmpty else { return }
|
|
1308
|
+
|
|
1309
|
+
// Get path from userInfo
|
|
1310
|
+
guard let path = getPath(from: template) else { return }
|
|
1311
|
+
|
|
1312
|
+
logger.debug("Lazy loading content for tab: \(path)")
|
|
1313
|
+
|
|
1314
|
+
Task {
|
|
1315
|
+
await loadContent(for: path, into: template)
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// MARK: - UIImage Resize
|
|
1321
|
+
|
|
1322
|
+
private extension UIImage {
|
|
1323
|
+
/// Draws the image centered within the target size, maintaining aspect ratio
|
|
1324
|
+
func resized(to targetSize: CGSize) -> UIImage? {
|
|
1325
|
+
UIGraphicsBeginImageContextWithOptions(targetSize, false, 0.0)
|
|
1326
|
+
defer { UIGraphicsEndImageContext() }
|
|
1327
|
+
|
|
1328
|
+
// Scale to fit while maintaining aspect ratio
|
|
1329
|
+
let widthRatio = targetSize.width / size.width
|
|
1330
|
+
let heightRatio = targetSize.height / size.height
|
|
1331
|
+
let scale = min(widthRatio, heightRatio)
|
|
1332
|
+
|
|
1333
|
+
let scaledSize = CGSize(width: size.width * scale, height: size.height * scale)
|
|
1334
|
+
let origin = CGPoint(
|
|
1335
|
+
x: (targetSize.width - scaledSize.width) / 2,
|
|
1336
|
+
y: (targetSize.height - scaledSize.height) / 2,
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
draw(in: CGRect(origin: origin, size: scaledSize))
|
|
1340
|
+
return UIGraphicsGetImageFromCurrentImageContext()?.withRenderingMode(.alwaysTemplate)
|
|
1341
|
+
}
|
|
1342
|
+
}
|