@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,1590 @@
|
|
|
1
|
+
@preconcurrency import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
import Kingfisher
|
|
4
|
+
import MediaPlayer
|
|
5
|
+
import NitroModules
|
|
6
|
+
import os.log
|
|
7
|
+
|
|
8
|
+
/// Result of media URL resolution
|
|
9
|
+
struct MediaResolvedUrl {
|
|
10
|
+
let url: String
|
|
11
|
+
let headers: [String: String]?
|
|
12
|
+
let userAgent: String?
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@MainActor
|
|
16
|
+
class TrackPlayer {
|
|
17
|
+
private let logger = Logger(subsystem: "com.audiobrowser", category: "TrackPlayer")
|
|
18
|
+
|
|
19
|
+
let nowPlayingInfoController: NowPlayingInfoController
|
|
20
|
+
let remoteCommandController: RemoteCommandController
|
|
21
|
+
let sleepTimerManager = SleepTimerManager()
|
|
22
|
+
private let retryManager = RetryManager()
|
|
23
|
+
private var pendingRetryTask: Task<Void, Never>?
|
|
24
|
+
private weak var callbacks: TrackPlayerCallbacks?
|
|
25
|
+
private var lastIndex: Int = -1
|
|
26
|
+
private var lastTrack: Track?
|
|
27
|
+
|
|
28
|
+
/// Retry configuration for load errors (network failures, timeouts, etc.)
|
|
29
|
+
var retryConfig: Variant_Bool_RetryConfig? {
|
|
30
|
+
didSet {
|
|
31
|
+
retryManager.updatePolicy(from: retryConfig)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Network monitor for accelerating retries when connectivity is restored.
|
|
36
|
+
/// When set, retries will trigger immediately when network comes back online
|
|
37
|
+
/// instead of waiting for the full exponential backoff delay.
|
|
38
|
+
weak var networkMonitor: NetworkMonitor? {
|
|
39
|
+
didSet {
|
|
40
|
+
retryManager.networkMonitor = networkMonitor
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Callback to resolve media URLs with optional transform
|
|
45
|
+
var mediaUrlResolver: ((String) async -> MediaResolvedUrl)?
|
|
46
|
+
|
|
47
|
+
/// Callback to resolve artwork URLs with size context for Now Playing.
|
|
48
|
+
/// Returns an ImageSource with potentially size-optimized URL.
|
|
49
|
+
var artworkUrlResolver: ((Track, ImageContext?) async -> ImageSource?)?
|
|
50
|
+
|
|
51
|
+
/// The repeat mode for the queue player.
|
|
52
|
+
var repeatMode: RepeatMode = .off {
|
|
53
|
+
didSet {
|
|
54
|
+
guard oldValue != repeatMode else { return }
|
|
55
|
+
|
|
56
|
+
// Sync state with MPRemoteCommandCenter for CarPlay/lock screen button display
|
|
57
|
+
remoteCommandController.updateRepeatMode(repeatMode)
|
|
58
|
+
|
|
59
|
+
callbacks?.playerDidChangeRepeatMode(
|
|
60
|
+
RepeatModeChangedEvent(repeatMode: repeatMode),
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Whether shuffle mode is enabled.
|
|
66
|
+
/// When enabled, next/previous traverse the shuffle order instead of sequential order.
|
|
67
|
+
/// Like Media3, the shuffle order is pre-generated when tracks are added to the queue,
|
|
68
|
+
/// so toggling shuffle just changes how navigation works without reshuffling.
|
|
69
|
+
var shuffleEnabled: Bool = false {
|
|
70
|
+
didSet {
|
|
71
|
+
guard oldValue != shuffleEnabled else { return }
|
|
72
|
+
|
|
73
|
+
// Sync state with MPRemoteCommandCenter for CarPlay/lock screen button display
|
|
74
|
+
remoteCommandController.updateShuffleMode(shuffleEnabled)
|
|
75
|
+
|
|
76
|
+
logger.debug("Shuffle \(self.shuffleEnabled ? "enabled" : "disabled"), order: \(self.shuffleOrder.shuffled)")
|
|
77
|
+
callbacks?.playerDidChangeShuffleEnabled(shuffleEnabled)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// The shuffle order for randomized playback
|
|
82
|
+
private var shuffleOrder = ShuffleOrder()
|
|
83
|
+
|
|
84
|
+
// MARK: - Queue Properties
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
The index of the current track. `-1` when there is no current track
|
|
88
|
+
*/
|
|
89
|
+
private(set) var currentIndex: Int = -1
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
The source path from which the current queue was expanded (e.g., from a contextual URL).
|
|
93
|
+
Used to avoid re-expanding the queue when selecting tracks from the same source.
|
|
94
|
+
*/
|
|
95
|
+
private(set) var queueSourcePath: String?
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
All tracks held by the queue.
|
|
99
|
+
*/
|
|
100
|
+
private(set) var tracks: [Track] = [] {
|
|
101
|
+
didSet {
|
|
102
|
+
callbacks?.playerDidChangeQueue(tracks)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var currentTrack: Track? {
|
|
107
|
+
guard currentIndex >= 0, currentIndex < tracks.count else { return nil }
|
|
108
|
+
return tracks[currentIndex]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
The upcoming tracks in playback order.
|
|
113
|
+
When shuffle is enabled, returns tracks in shuffled order.
|
|
114
|
+
*/
|
|
115
|
+
var nextTracks: [Track] {
|
|
116
|
+
guard currentIndex >= 0, currentIndex < tracks.count else { return [] }
|
|
117
|
+
|
|
118
|
+
if shuffleEnabled {
|
|
119
|
+
// Return tracks in shuffled order after current position
|
|
120
|
+
var result: [Track] = []
|
|
121
|
+
var index = currentIndex
|
|
122
|
+
while let nextIndex = shuffleOrder.getNextIndex(after: index) {
|
|
123
|
+
result.append(tracks[nextIndex])
|
|
124
|
+
index = nextIndex
|
|
125
|
+
}
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
guard currentIndex < tracks.count - 1 else { return [] }
|
|
130
|
+
return Array(tracks[currentIndex + 1 ..< tracks.count])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
The previous tracks in playback order.
|
|
135
|
+
When shuffle is enabled, returns tracks in shuffled order.
|
|
136
|
+
*/
|
|
137
|
+
var previousTracks: [Track] {
|
|
138
|
+
guard currentIndex >= 0, currentIndex < tracks.count else { return [] }
|
|
139
|
+
|
|
140
|
+
if shuffleEnabled {
|
|
141
|
+
// Return tracks in shuffled order before current position
|
|
142
|
+
var result: [Track] = []
|
|
143
|
+
var index = currentIndex
|
|
144
|
+
while let prevIndex = shuffleOrder.getPreviousIndex(before: index) {
|
|
145
|
+
result.insert(tracks[prevIndex], at: 0)
|
|
146
|
+
index = prevIndex
|
|
147
|
+
}
|
|
148
|
+
return result
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
guard currentIndex > 0 else { return [] }
|
|
152
|
+
return Array(tracks[0 ..< currentIndex])
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
Whether the current track is the last track in playback order.
|
|
157
|
+
When shuffle is enabled, this checks the shuffle order; otherwise, queue order.
|
|
158
|
+
*/
|
|
159
|
+
private var isLastInPlaybackOrder: Bool {
|
|
160
|
+
if shuffleEnabled {
|
|
161
|
+
return shuffleOrder.isLast(currentIndex)
|
|
162
|
+
}
|
|
163
|
+
return currentIndex == tracks.count - 1
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// MARK: - AVPlayer Properties (from AVPlayerWrapper)
|
|
167
|
+
|
|
168
|
+
/// Represents a seek operation that's pending while a track loads
|
|
169
|
+
private struct PendingSeek: Sendable {
|
|
170
|
+
let time: TimeInterval
|
|
171
|
+
let completion: (@Sendable (Bool) -> Void)?
|
|
172
|
+
|
|
173
|
+
func execute(on player: AVPlayer, delegate: TrackPlayer?) {
|
|
174
|
+
let cmTime = CMTime(seconds: time, preferredTimescale: 1000)
|
|
175
|
+
let seekTime = time
|
|
176
|
+
player
|
|
177
|
+
.seek(to: cmTime, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { finished in
|
|
178
|
+
Task { @MainActor in
|
|
179
|
+
delegate?.handleSeekCompleted(to: Double(seekTime), didFinish: finished)
|
|
180
|
+
}
|
|
181
|
+
completion?(finished)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
func cancel() {
|
|
186
|
+
completion?(false)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private var avPlayer = AVPlayer()
|
|
191
|
+
|
|
192
|
+
private lazy var playerObserver: PlayerStateObserver = .init(
|
|
193
|
+
onStatusChange: { [weak self] status in
|
|
194
|
+
Task { @MainActor in self?.avPlayerStatusDidChange(status) }
|
|
195
|
+
},
|
|
196
|
+
onTimeControlStatusChange: { [weak self] status in
|
|
197
|
+
Task { @MainActor in self?.avPlayerDidChangeTimeControlStatus(status) }
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
private lazy var playerTimeObserver: PlayerTimeObserver = .init(
|
|
202
|
+
periodicObserverTimeInterval: CMTime(seconds: 1, preferredTimescale: 1000),
|
|
203
|
+
onAudioDidStart: { [weak self] in
|
|
204
|
+
Task { @MainActor in self?.audioDidStart() }
|
|
205
|
+
},
|
|
206
|
+
onSecondElapsed: { [weak self] seconds in
|
|
207
|
+
Task { @MainActor in self?.setNowPlayingCurrentTime(seconds: seconds) }
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
private lazy var playerItemNotificationObserver: PlayerItemNotificationObserver = .init(
|
|
212
|
+
onDidPlayToEndTime: { [weak self] in
|
|
213
|
+
Task { @MainActor in self?.handleTrackDidPlayToEndTime() }
|
|
214
|
+
},
|
|
215
|
+
onFailedToPlayToEndTime: { [weak self] error in
|
|
216
|
+
Task { @MainActor in self?.handleItemFailedToPlayToEndTime(error: error) }
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
private lazy var playerItemObserver: PlayerItemPropertyObserver = .init(
|
|
221
|
+
onDurationUpdate: { [weak self] duration in
|
|
222
|
+
Task { @MainActor in self?.callbacks?.playerDidUpdateDuration(duration) }
|
|
223
|
+
},
|
|
224
|
+
onPlaybackLikelyToKeepUpUpdate: { [weak self] isLikely in
|
|
225
|
+
Task { @MainActor in self?.avItemDidUpdatePlaybackLikelyToKeepUp(isLikely) }
|
|
226
|
+
},
|
|
227
|
+
onStatusChange: { [weak self] status, error in
|
|
228
|
+
Task { @MainActor in self?.avItemStatusDidChange(status, error: error) }
|
|
229
|
+
},
|
|
230
|
+
onTimedMetadataReceived: { [weak self] groups in
|
|
231
|
+
self?.callbacks?.playerDidReceiveTimedMetadata(groups)
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
private lazy var progressUpdateManager: PlaybackProgressUpdateManager =
|
|
236
|
+
PlaybackProgressUpdateManager { [weak self] in
|
|
237
|
+
guard let self, currentIndex >= 0 else { return }
|
|
238
|
+
let progressEvent = PlaybackProgressUpdatedEvent(
|
|
239
|
+
track: Double(currentIndex),
|
|
240
|
+
position: currentTime,
|
|
241
|
+
duration: duration,
|
|
242
|
+
buffered: bufferedPosition,
|
|
243
|
+
)
|
|
244
|
+
callbacks?.playerDidUpdateProgress(progressEvent)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
lazy var playingStateManager: PlayingStateManager = PlayingStateManager { [weak self] state in
|
|
248
|
+
self?.callbacks?.playerDidChangePlayingState(state)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private var pendingSeek: PendingSeek?
|
|
252
|
+
private var metadataLoadTask: Task<Void, Never>?
|
|
253
|
+
private var playableLoadTask: Task<Void, Never>?
|
|
254
|
+
private var asset: AVAsset?
|
|
255
|
+
private var url: URL?
|
|
256
|
+
private var urlOptions: [String: Any]?
|
|
257
|
+
private(set) var playbackError: TrackPlayerError.PlaybackError? {
|
|
258
|
+
didSet {
|
|
259
|
+
guard oldValue != playbackError else { return }
|
|
260
|
+
|
|
261
|
+
// Setting an error should set state to .error
|
|
262
|
+
if let _ = playbackError, state != .error {
|
|
263
|
+
state = .error
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
callbacks?.playerDidError(PlaybackErrorEvent(error: playbackError?.toNitroError()))
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private(set) var lastPlayerTimeControlStatus: AVPlayer.TimeControlStatus = .paused
|
|
271
|
+
|
|
272
|
+
func getPlayback() -> Playback {
|
|
273
|
+
Playback(state: state, error: playbackError?.toNitroError())
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
func getRepeatMode() -> RepeatMode {
|
|
277
|
+
repeatMode
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
func setRepeatMode(_ mode: RepeatMode) {
|
|
281
|
+
repeatMode = mode
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
Controls the time pitch algorithm applied to each track loaded into the player.
|
|
286
|
+
If the loaded `AudioItem` conforms to `TimePitcher`-protocol this will be overriden.
|
|
287
|
+
*/
|
|
288
|
+
var audioTimePitchAlgorithm: AVAudioTimePitchAlgorithm = .timeDomain
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
Default remote commands to use for each playing track
|
|
292
|
+
*/
|
|
293
|
+
var remoteCommands: [RemoteCommand] = [] {
|
|
294
|
+
didSet {
|
|
295
|
+
enableRemoteCommands(remoteCommands)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
Handles the `playWhenReady` setting while executing a given action.
|
|
301
|
+
|
|
302
|
+
This method takes an optional `Bool` value and a closure representing an action to execute.
|
|
303
|
+
If the `Bool` value is not `nil`, `self.playWhenReady` is set accordingly either before or
|
|
304
|
+
after executing the action.
|
|
305
|
+
|
|
306
|
+
- Parameters:
|
|
307
|
+
- playWhenReady: Optional `Bool` to set `self.playWhenReady`.
|
|
308
|
+
- If `true`, `self.playWhenReady` will be set after executing the action.
|
|
309
|
+
- If `false`, `self.playWhenReady` will be set before executing the action.
|
|
310
|
+
- If `nil`, `self.playWhenReady` will not be changed.
|
|
311
|
+
- action: A closure representing the action to execute. This closure can throw an error.
|
|
312
|
+
|
|
313
|
+
- Throws: This function will propagate any errors thrown by the `action` closure.
|
|
314
|
+
*/
|
|
315
|
+
func handlePlayWhenReady(_ playWhenReady: Bool?, action: () throws -> Void) rethrows {
|
|
316
|
+
if playWhenReady == false {
|
|
317
|
+
self.playWhenReady = false
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
try action()
|
|
321
|
+
|
|
322
|
+
if playWhenReady == true {
|
|
323
|
+
self.playWhenReady = true
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// MARK: - AVPlayer State and Computed Properties
|
|
328
|
+
|
|
329
|
+
private(set) var state: PlaybackState = .none {
|
|
330
|
+
didSet {
|
|
331
|
+
guard oldValue != state else { return }
|
|
332
|
+
|
|
333
|
+
// Clear error when transitioning away from error state
|
|
334
|
+
if oldValue == .error, state != .error {
|
|
335
|
+
playbackError = nil
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
switch state {
|
|
339
|
+
case .ready:
|
|
340
|
+
setTimePitchingAlgorithmForCurrentItem()
|
|
341
|
+
// When item becomes ready and playWhenReady is true, start playback
|
|
342
|
+
if playWhenReady {
|
|
343
|
+
startPlayback()
|
|
344
|
+
}
|
|
345
|
+
case .loading:
|
|
346
|
+
setTimePitchingAlgorithmForCurrentItem()
|
|
347
|
+
default: break
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
switch state {
|
|
351
|
+
case .ready, .loading, .playing, .paused:
|
|
352
|
+
updateNowPlayingPlaybackValues()
|
|
353
|
+
// Update playback state for CarPlay Now Playing
|
|
354
|
+
updateNowPlayingPlaybackState()
|
|
355
|
+
default: break
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let playback = Playback(state: state, error: playbackError?.toNitroError())
|
|
359
|
+
callbacks?.playerDidChangePlayback(playback)
|
|
360
|
+
|
|
361
|
+
// Emit queue ended event when playback ends on the last track
|
|
362
|
+
// This matches Android's behavior (TrackPlayer.kt:642-646)
|
|
363
|
+
if state == .ended, isLastInPlaybackOrder {
|
|
364
|
+
let event = PlaybackQueueEndedEvent(
|
|
365
|
+
track: Double(currentIndex),
|
|
366
|
+
position: currentTime,
|
|
367
|
+
)
|
|
368
|
+
callbacks?.playerDidEndQueue(event)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
progressUpdateManager.onPlaybackStateChanged(state)
|
|
372
|
+
playingStateManager.update(playWhenReady: playWhenReady, state: state)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
var playbackActive: Bool {
|
|
377
|
+
switch state {
|
|
378
|
+
case .none, .stopped, .ended, .error:
|
|
379
|
+
false
|
|
380
|
+
default: true
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
var reasonForWaitingToPlay: AVPlayer.WaitingReason? {
|
|
385
|
+
avPlayer.reasonForWaitingToPlay
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// MARK: - Getters from AVPlayerWrapper
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
The elapsed playback time of the current track.
|
|
392
|
+
*/
|
|
393
|
+
var currentTime: Double {
|
|
394
|
+
let seconds = avPlayer.currentTime().seconds
|
|
395
|
+
return seconds.isNaN ? 0 : seconds
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
The duration of the current track.
|
|
400
|
+
*/
|
|
401
|
+
var duration: Double {
|
|
402
|
+
guard let item = avPlayer.currentItem else { return 0.0 }
|
|
403
|
+
|
|
404
|
+
if !item.asset.duration.seconds.isNaN {
|
|
405
|
+
return item.asset.duration.seconds
|
|
406
|
+
}
|
|
407
|
+
if !item.duration.seconds.isNaN {
|
|
408
|
+
return item.duration.seconds
|
|
409
|
+
}
|
|
410
|
+
if let seekable = item.seekableTimeRanges.last?.timeRangeValue.duration.seconds,
|
|
411
|
+
!seekable.isNaN
|
|
412
|
+
{
|
|
413
|
+
return seekable
|
|
414
|
+
}
|
|
415
|
+
return 0.0
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
The bufferedPosition of the active track
|
|
420
|
+
*/
|
|
421
|
+
var bufferedPosition: Double {
|
|
422
|
+
avPlayer.currentItem?.loadedTimeRanges.last?.timeRangeValue.end.seconds ?? 0
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
The current state of the underlying `TrackPlayer`.
|
|
427
|
+
*/
|
|
428
|
+
var playerState: PlaybackState {
|
|
429
|
+
state
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// MARK: - Setters for AVPlayerWrapper
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
Whether the player should start playing automatically when the track is ready.
|
|
436
|
+
*/
|
|
437
|
+
var playWhenReady: Bool = false {
|
|
438
|
+
didSet {
|
|
439
|
+
if playWhenReady == true, state == .error || state == .stopped {
|
|
440
|
+
reload(startFromCurrentTime: state == .error)
|
|
441
|
+
}
|
|
442
|
+
if playWhenReady {
|
|
443
|
+
startPlayback()
|
|
444
|
+
} else {
|
|
445
|
+
pausePlayback()
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if oldValue != playWhenReady {
|
|
449
|
+
callbacks?.playerDidChangePlayWhenReady(playWhenReady)
|
|
450
|
+
playingStateManager.update(playWhenReady: playWhenReady, state: state)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
The amount of milliseconds to be buffered by the player. Default value is 0, this means the AVPlayer will choose an appropriate level of buffering. Setting `bufferDuration` to larger than zero automatically disables `automaticallyWaitsToMinimizeStalling`. Setting it back to zero automatically enables `automaticallyWaitsToMinimizeStalling`.
|
|
457
|
+
|
|
458
|
+
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration)
|
|
459
|
+
*/
|
|
460
|
+
var bufferDuration: Double = 0 {
|
|
461
|
+
didSet {
|
|
462
|
+
avPlayer.automaticallyWaitsToMinimizeStalling = bufferDuration == 0
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
Indicates whether the player should automatically delay playback in order to minimize stalling. Setting this to true will also set `bufferDuration` back to `0`.
|
|
468
|
+
|
|
469
|
+
[Read more from Apple Documentation](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal)
|
|
470
|
+
*/
|
|
471
|
+
var automaticallyWaitsToMinimizeStalling: Bool {
|
|
472
|
+
get { avPlayer.automaticallyWaitsToMinimizeStalling }
|
|
473
|
+
set {
|
|
474
|
+
if newValue {
|
|
475
|
+
bufferDuration = 0
|
|
476
|
+
}
|
|
477
|
+
avPlayer.automaticallyWaitsToMinimizeStalling = newValue
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var volume: Float {
|
|
482
|
+
get { avPlayer.volume }
|
|
483
|
+
set { avPlayer.volume = newValue }
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
var isMuted: Bool {
|
|
487
|
+
get { avPlayer.isMuted }
|
|
488
|
+
set { avPlayer.isMuted = newValue }
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
var rate: Float = 1.0 {
|
|
492
|
+
didSet {
|
|
493
|
+
avPlayer.rate = rate
|
|
494
|
+
updateNowPlayingPlaybackValues()
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// MARK: - Init
|
|
499
|
+
|
|
500
|
+
init(
|
|
501
|
+
nowPlayingInfoController: NowPlayingInfoController = NowPlayingInfoController(),
|
|
502
|
+
callbacks: TrackPlayerCallbacks? = nil,
|
|
503
|
+
) {
|
|
504
|
+
self.nowPlayingInfoController = nowPlayingInfoController
|
|
505
|
+
remoteCommandController = RemoteCommandController(callbacks: callbacks)
|
|
506
|
+
self.callbacks = callbacks
|
|
507
|
+
|
|
508
|
+
// Configure sleep timer
|
|
509
|
+
sleepTimerManager.onComplete = { [weak self] in
|
|
510
|
+
self?.pause()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Configure retry manager
|
|
514
|
+
retryManager.shouldRetry = { [weak self] in
|
|
515
|
+
self?.playWhenReady ?? false
|
|
516
|
+
}
|
|
517
|
+
retryManager.onRetry = { [weak self] startFromCurrentTime in
|
|
518
|
+
self?.reload(startFromCurrentTime: startFromCurrentTime)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Handle command center changes when MPNowPlayingSession is created/destroyed (iOS 16+)
|
|
522
|
+
// This callback is guaranteed to be called on the main thread by NowPlayingInfoController
|
|
523
|
+
nowPlayingInfoController.onRemoteCommandCenterChanged = { [weak self] newCommandCenter in
|
|
524
|
+
MainActor.assumeIsolated {
|
|
525
|
+
self?.remoteCommandController.switchCommandCenter(newCommandCenter)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
setupAVPlayer()
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// MARK: - Player Actions
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
Will replace the current track with a new one and load it into the player.
|
|
536
|
+
|
|
537
|
+
- parameter track: The Track to replace the current track.
|
|
538
|
+
- parameter playWhenReady: Optional, whether to start playback when the track is ready.
|
|
539
|
+
*/
|
|
540
|
+
func load(_ track: Track, playWhenReady: Bool? = nil) {
|
|
541
|
+
handlePlayWhenReady(playWhenReady) {
|
|
542
|
+
if currentIndex == -1 {
|
|
543
|
+
tracks.append(track)
|
|
544
|
+
currentIndex = 0
|
|
545
|
+
} else {
|
|
546
|
+
setTrack(at: currentIndex, track)
|
|
547
|
+
}
|
|
548
|
+
handleCurrentTrackChanged()
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
Toggle playback status.
|
|
554
|
+
*/
|
|
555
|
+
func togglePlaying() {
|
|
556
|
+
switch avPlayer.timeControlStatus {
|
|
557
|
+
case .playing, .waitingToPlayAtSpecifiedRate:
|
|
558
|
+
pause()
|
|
559
|
+
case .paused:
|
|
560
|
+
play()
|
|
561
|
+
@unknown default:
|
|
562
|
+
fatalError("Unknown AVPlayer.timeControlStatus")
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
Start playback
|
|
568
|
+
*/
|
|
569
|
+
func play() {
|
|
570
|
+
playWhenReady = true
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
Pause playback
|
|
575
|
+
*/
|
|
576
|
+
func pause() {
|
|
577
|
+
playWhenReady = false
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
Toggle playback between play and pause
|
|
582
|
+
*/
|
|
583
|
+
func togglePlayback() {
|
|
584
|
+
playWhenReady = !playWhenReady
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
Stop playback
|
|
589
|
+
*/
|
|
590
|
+
func stop() {
|
|
591
|
+
state = .stopped
|
|
592
|
+
if currentTrack?.live != true {
|
|
593
|
+
seekTo(0)
|
|
594
|
+
}
|
|
595
|
+
playWhenReady = false
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
Reload the current track.
|
|
600
|
+
*/
|
|
601
|
+
func reload(startFromCurrentTime: Bool) {
|
|
602
|
+
var time: Double? = nil
|
|
603
|
+
if startFromCurrentTime {
|
|
604
|
+
if let currentItem = avPlayer.currentItem {
|
|
605
|
+
if !currentItem.duration.isIndefinite {
|
|
606
|
+
time = currentItem.currentTime().seconds
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
loadAVPlayer()
|
|
611
|
+
if let time {
|
|
612
|
+
seekTo(time)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
Seek to a specific time in the track.
|
|
618
|
+
*/
|
|
619
|
+
func seekTo(_ seconds: TimeInterval) {
|
|
620
|
+
seekTo(seconds, completion: { _ in })
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
Seek to a specific time in the track with a completion handler.
|
|
625
|
+
|
|
626
|
+
- parameter seconds: The time to seek to.
|
|
627
|
+
- parameter completion: Called when the seek operation completes. The Bool parameter indicates whether the seek finished successfully (true) or was interrupted/deferred (false).
|
|
628
|
+
*/
|
|
629
|
+
func seekTo(_ seconds: TimeInterval, completion: @escaping @Sendable (Bool) -> Void) {
|
|
630
|
+
// If an track is currently being loaded asynchronously, defer the seek until it's ready.
|
|
631
|
+
if state == .loading {
|
|
632
|
+
// Cancel any previous pending seek before creating a new one
|
|
633
|
+
pendingSeek?.cancel()
|
|
634
|
+
pendingSeek = PendingSeek(time: seconds, completion: completion)
|
|
635
|
+
} else if avPlayer.currentItem != nil {
|
|
636
|
+
let time = CMTime(seconds: seconds, preferredTimescale: 1000)
|
|
637
|
+
let seekSeconds = seconds
|
|
638
|
+
avPlayer
|
|
639
|
+
.seek(to: time, toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self] finished in
|
|
640
|
+
Task { @MainActor in
|
|
641
|
+
self?.handleSeekCompleted(to: Double(seekSeconds), didFinish: finished)
|
|
642
|
+
}
|
|
643
|
+
completion(finished)
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
// No track loaded and not loading - seek fails immediately
|
|
647
|
+
completion(false)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
Seek by relative a time offset in the track.
|
|
653
|
+
*/
|
|
654
|
+
func seekBy(_ offset: TimeInterval) {
|
|
655
|
+
// Calculate the target time based on current state
|
|
656
|
+
let targetTime: TimeInterval
|
|
657
|
+
if state == .loading {
|
|
658
|
+
// If loading, offset from pending seek (or 0 if no pending seek)
|
|
659
|
+
targetTime = (pendingSeek?.time ?? 0) + offset
|
|
660
|
+
} else if let currentItem = avPlayer.currentItem {
|
|
661
|
+
// If playing, offset from current position
|
|
662
|
+
targetTime = currentItem.currentTime().seconds + offset
|
|
663
|
+
} else {
|
|
664
|
+
// No track and not loading - nothing to seek in
|
|
665
|
+
return
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Delegate to absolute seek
|
|
669
|
+
seekTo(targetTime)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// MARK: - Remote Command Center
|
|
673
|
+
|
|
674
|
+
func enableRemoteCommands(_ commands: [RemoteCommand]) {
|
|
675
|
+
remoteCommandController.enable(commands: commands)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// MARK: - NowPlayingInfo
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
Loads NowPlayingInfo-meta values with the values found in the current track. Use this if a change to the track is made and you want to update the `NowPlayingInfoController`s values.
|
|
682
|
+
|
|
683
|
+
Reloads:
|
|
684
|
+
- Artist
|
|
685
|
+
- Title
|
|
686
|
+
- Album title
|
|
687
|
+
- Album artwork
|
|
688
|
+
*/
|
|
689
|
+
func loadNowPlayingMetaValues() {
|
|
690
|
+
guard let track = currentTrack else { return }
|
|
691
|
+
|
|
692
|
+
nowPlayingInfoController.set(keyValues: [
|
|
693
|
+
MediaItemProperty.artist(track.artist),
|
|
694
|
+
MediaItemProperty.title(track.title),
|
|
695
|
+
MediaItemProperty.albumTitle(track.album),
|
|
696
|
+
NowPlayingInfoProperty.playbackRate(Double(rate)),
|
|
697
|
+
NowPlayingInfoProperty.isLiveStream(track.live),
|
|
698
|
+
])
|
|
699
|
+
loadArtworkForTrack(track)
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
Resyncs the playbackvalues of the currently playing track.
|
|
704
|
+
|
|
705
|
+
Will resync:
|
|
706
|
+
- Current time
|
|
707
|
+
- Duration
|
|
708
|
+
- Playback rate
|
|
709
|
+
*/
|
|
710
|
+
func updateNowPlayingPlaybackValues() {
|
|
711
|
+
logger.debug("updateNowPlayingPlaybackValues: duration=\(self.duration), rate=\(self.rate), currentTime=\(self.currentTime), playWhenReady=\(self.playWhenReady)")
|
|
712
|
+
nowPlayingInfoController.set(keyValues: [
|
|
713
|
+
MediaItemProperty.duration(duration),
|
|
714
|
+
NowPlayingInfoProperty.playbackRate(Double(rate)),
|
|
715
|
+
NowPlayingInfoProperty.elapsedPlaybackTime(currentTime),
|
|
716
|
+
])
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/// Updates the Now Playing playback state for CarPlay.
|
|
720
|
+
/// This is separate from playbackRate and required for CarPlay to show correct play/pause button.
|
|
721
|
+
private func updateNowPlayingPlaybackState() {
|
|
722
|
+
let state: MPNowPlayingPlaybackState = playWhenReady ? .playing : .paused
|
|
723
|
+
logger.debug("updateNowPlayingPlaybackState: \(state.rawValue) (playWhenReady=\(self.playWhenReady))")
|
|
724
|
+
nowPlayingInfoController.setPlaybackState(state)
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
func clear() {
|
|
728
|
+
clearTracks()
|
|
729
|
+
unloadAVPlayer()
|
|
730
|
+
nowPlayingInfoController.unlinkPlayer()
|
|
731
|
+
nowPlayingInfoController.clear()
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// MARK: - Private
|
|
735
|
+
|
|
736
|
+
private func setNowPlayingCurrentTime(seconds: Double) {
|
|
737
|
+
nowPlayingInfoController.set(
|
|
738
|
+
keyValue: NowPlayingInfoProperty.elapsedPlaybackTime(seconds),
|
|
739
|
+
)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private func loadArtworkForTrack(_ track: Track) {
|
|
743
|
+
let artworkUrl = track.artworkSource?.uri ?? track.artwork
|
|
744
|
+
logger.debug("loadArtworkForTrack: \(track.title), artworkUrl: \(artworkUrl ?? "nil")")
|
|
745
|
+
|
|
746
|
+
// Now Playing artwork: use screen width in pixels, capped at 1200px
|
|
747
|
+
let screenScale = UIScreen.main.scale
|
|
748
|
+
let screenWidth = UIScreen.main.bounds.width * screenScale
|
|
749
|
+
let artworkSize = min(screenWidth, 1200)
|
|
750
|
+
let nowPlayingSize = ImageContext(width: artworkSize, height: artworkSize)
|
|
751
|
+
|
|
752
|
+
Task {
|
|
753
|
+
let image: UIImage?
|
|
754
|
+
|
|
755
|
+
// Try to resolve artwork URL with size context for CDN optimization
|
|
756
|
+
if let resolver = artworkUrlResolver,
|
|
757
|
+
let imageSource = await resolver(track, nowPlayingSize)
|
|
758
|
+
{
|
|
759
|
+
logger.debug("loadArtworkForTrack: using resolved URL: \(imageSource.uri)")
|
|
760
|
+
image = await loadImage(from: imageSource)
|
|
761
|
+
} else {
|
|
762
|
+
// Fall back to loading from track's existing artwork URL
|
|
763
|
+
image = await track.loadArtwork()
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Verify we're still on the same track after async load
|
|
767
|
+
guard currentTrack?.src == track.src else { return }
|
|
768
|
+
|
|
769
|
+
if let image {
|
|
770
|
+
logger.debug("loadArtworkForTrack: loaded image \(image.size.width)x\(image.size.height)")
|
|
771
|
+
// Note: The requestHandler closure is called from MediaPlayer's background queue,
|
|
772
|
+
// so we must mark it @Sendable to break @MainActor isolation inheritance.
|
|
773
|
+
let artwork = MPMediaItemArtwork(boundsSize: image.size) { @Sendable requestedSize in
|
|
774
|
+
return image
|
|
775
|
+
}
|
|
776
|
+
nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(artwork))
|
|
777
|
+
} else {
|
|
778
|
+
logger.debug("loadArtworkForTrack: no image loaded")
|
|
779
|
+
nowPlayingInfoController.set(keyValue: MediaItemProperty.artwork(nil))
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/// Loads an image from an ImageSource using Kingfisher.
|
|
785
|
+
private func loadImage(from imageSource: ImageSource) async -> UIImage? {
|
|
786
|
+
guard let url = URL(string: imageSource.uri) else { return nil }
|
|
787
|
+
|
|
788
|
+
if url.isFileURL {
|
|
789
|
+
return UIImage(contentsOfFile: url.path)
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Build Kingfisher options with headers if provided
|
|
793
|
+
var options: KingfisherOptionsInfo = []
|
|
794
|
+
if let headers = imageSource.headers, !headers.isEmpty {
|
|
795
|
+
let modifier = AnyModifier { request in
|
|
796
|
+
var mutableRequest = request
|
|
797
|
+
for (key, value) in headers {
|
|
798
|
+
mutableRequest.setValue(value, forHTTPHeaderField: key)
|
|
799
|
+
}
|
|
800
|
+
return mutableRequest
|
|
801
|
+
}
|
|
802
|
+
options.append(.requestModifier(modifier))
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
do {
|
|
806
|
+
let result = try await KingfisherManager.shared.retrieveImage(with: url, options: options)
|
|
807
|
+
return result.image
|
|
808
|
+
} catch {
|
|
809
|
+
logger.error("Failed to load artwork from \(imageSource.uri): \(error.localizedDescription)")
|
|
810
|
+
return nil
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private func setTimePitchingAlgorithmForCurrentItem() {
|
|
815
|
+
// Use player's default pitch algorithm (per-track pitch control not in Nitro API)
|
|
816
|
+
avPlayer.currentItem?.audioTimePitchAlgorithm = audioTimePitchAlgorithm
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// MARK: - AVPlayer Management Methods (from AVPlayerWrapper)
|
|
820
|
+
|
|
821
|
+
/// Starts playback at the configured rate
|
|
822
|
+
private func startPlayback() {
|
|
823
|
+
avPlayer.play()
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/// Pauses playback
|
|
827
|
+
private func pausePlayback() {
|
|
828
|
+
avPlayer.pause()
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
private func clearCurrentAVItem() {
|
|
832
|
+
guard let currentAsset = asset else {
|
|
833
|
+
avPlayer.replaceCurrentItem(with: nil)
|
|
834
|
+
return
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
stopObservingAVPlayerItem()
|
|
838
|
+
|
|
839
|
+
// Don't call currentAsset.cancelLoading() - it blocks the main thread for 500ms+
|
|
840
|
+
// Instead, cancel on a background queue
|
|
841
|
+
DispatchQueue.global(qos: .utility).async {
|
|
842
|
+
currentAsset.cancelLoading()
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
asset = nil
|
|
846
|
+
pendingSeek?.cancel()
|
|
847
|
+
pendingSeek = nil
|
|
848
|
+
|
|
849
|
+
avPlayer.replaceCurrentItem(with: nil)
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private func startObservingAVPlayerItem(_ avItem: AVPlayerItem) {
|
|
853
|
+
playerItemObserver.startObserving(item: avItem)
|
|
854
|
+
playerItemNotificationObserver.startObserving(item: avItem)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private func stopObservingAVPlayerItem() {
|
|
858
|
+
playerItemObserver.stopObservingCurrentItem()
|
|
859
|
+
playerItemNotificationObserver.stopObservingCurrentItem()
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private func recreateAVPlayer() {
|
|
863
|
+
playbackError = nil
|
|
864
|
+
playerTimeObserver.unregisterForBoundaryTimeEvents()
|
|
865
|
+
playerTimeObserver.unregisterForPeriodicEvents()
|
|
866
|
+
playerObserver.stopObserving()
|
|
867
|
+
stopObservingAVPlayerItem()
|
|
868
|
+
clearCurrentAVItem()
|
|
869
|
+
|
|
870
|
+
// Unlink old player before creating new one
|
|
871
|
+
nowPlayingInfoController.unlinkPlayer()
|
|
872
|
+
|
|
873
|
+
avPlayer = AVPlayer()
|
|
874
|
+
setupAVPlayer()
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private func setupAVPlayer() {
|
|
878
|
+
// disabled since we're not making use of video playback
|
|
879
|
+
avPlayer.allowsExternalPlayback = false
|
|
880
|
+
|
|
881
|
+
playerObserver.avPlayer = avPlayer
|
|
882
|
+
playerObserver.startObserving()
|
|
883
|
+
|
|
884
|
+
playerTimeObserver.avPlayer = avPlayer
|
|
885
|
+
playerTimeObserver.registerForBoundaryTimeEvents()
|
|
886
|
+
playerTimeObserver.registerForPeriodicTimeEvents()
|
|
887
|
+
|
|
888
|
+
// Link AVPlayer to NowPlayingInfoController for automatic publishing on iOS 16+
|
|
889
|
+
nowPlayingInfoController.linkPlayer(avPlayer)
|
|
890
|
+
|
|
891
|
+
// Apply initial playback state
|
|
892
|
+
if playWhenReady {
|
|
893
|
+
startPlayback()
|
|
894
|
+
} else {
|
|
895
|
+
// Ensure defaultRate is set for when playback starts later
|
|
896
|
+
if #available(iOS 16.0, *) {
|
|
897
|
+
avPlayer.defaultRate = rate
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
func loadAVPlayer() {
|
|
903
|
+
if state == .error {
|
|
904
|
+
recreateAVPlayer()
|
|
905
|
+
} else if let oldAsset = asset {
|
|
906
|
+
// Prepare for new item: cancel in-flight tasks, stop observing, cancel asset loading
|
|
907
|
+
metadataLoadTask?.cancel()
|
|
908
|
+
playableLoadTask?.cancel()
|
|
909
|
+
stopObservingAVPlayerItem()
|
|
910
|
+
pausePlayback()
|
|
911
|
+
DispatchQueue.global(qos: .utility).async {
|
|
912
|
+
oldAsset.cancelLoading()
|
|
913
|
+
}
|
|
914
|
+
asset = nil
|
|
915
|
+
pendingSeek?.cancel()
|
|
916
|
+
pendingSeek = nil
|
|
917
|
+
}
|
|
918
|
+
if let url {
|
|
919
|
+
let pendingAsset = AVURLAsset(url: url, options: urlOptions)
|
|
920
|
+
asset = pendingAsset
|
|
921
|
+
state = .loading
|
|
922
|
+
|
|
923
|
+
// Load metadata asynchronously, separate from playable to allow playback to start faster
|
|
924
|
+
metadataLoadTask = Task { [weak self] in
|
|
925
|
+
guard let self else { return }
|
|
926
|
+
|
|
927
|
+
guard let (commonMetadata, chapterLocales, metadataFormats) = try? await pendingAsset.load(
|
|
928
|
+
.commonMetadata,
|
|
929
|
+
.availableChapterLocales,
|
|
930
|
+
.availableMetadataFormats,
|
|
931
|
+
) else { return }
|
|
932
|
+
|
|
933
|
+
guard !Task.isCancelled, pendingAsset == asset else { return }
|
|
934
|
+
|
|
935
|
+
if !commonMetadata.isEmpty {
|
|
936
|
+
callbacks?.playerDidReceiveCommonMetadata(commonMetadata)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if !chapterLocales.isEmpty {
|
|
940
|
+
for locale in chapterLocales {
|
|
941
|
+
guard !Task.isCancelled else { return }
|
|
942
|
+
if let chapters = try? await pendingAsset.loadChapterMetadataGroups(
|
|
943
|
+
withTitleLocale: locale,
|
|
944
|
+
containingItemsWithCommonKeys: [],
|
|
945
|
+
) {
|
|
946
|
+
callbacks?.playerDidReceiveChapterMetadata(chapters)
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
} else {
|
|
950
|
+
let duration = await (try? pendingAsset.load(.duration)) ?? .zero
|
|
951
|
+
for format in metadataFormats {
|
|
952
|
+
guard !Task.isCancelled else { return }
|
|
953
|
+
if let metadata = try? await pendingAsset.loadMetadata(for: format) {
|
|
954
|
+
let timeRange = CMTimeRange(
|
|
955
|
+
start: CMTime(seconds: 0, preferredTimescale: 1000),
|
|
956
|
+
end: duration,
|
|
957
|
+
)
|
|
958
|
+
let group = AVTimedMetadataGroup(items: metadata, timeRange: timeRange)
|
|
959
|
+
callbacks?.playerDidReceiveTimedMetadata([group])
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// Load playable and start playback when ready
|
|
966
|
+
playableLoadTask = Task { [weak self] in
|
|
967
|
+
guard let self else { return }
|
|
968
|
+
|
|
969
|
+
do {
|
|
970
|
+
let isPlayable = try await pendingAsset.load(.isPlayable)
|
|
971
|
+
|
|
972
|
+
guard !Task.isCancelled else { return }
|
|
973
|
+
|
|
974
|
+
await MainActor.run {
|
|
975
|
+
guard pendingAsset == self.asset else { return }
|
|
976
|
+
|
|
977
|
+
if !isPlayable {
|
|
978
|
+
self.playbackError = TrackPlayerError.PlaybackError.trackWasUnplayable
|
|
979
|
+
return
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
let avItem = AVPlayerItem(asset: pendingAsset)
|
|
983
|
+
avItem.preferredForwardBufferDuration = self.bufferDuration / 1000.0
|
|
984
|
+
// Set metadata on item before it becomes current, so there's no gap
|
|
985
|
+
self.nowPlayingInfoController.prepareItem(avItem)
|
|
986
|
+
self.logger.debug("AVPlayerItem created, calling replaceCurrentItem")
|
|
987
|
+
self.avPlayer.replaceCurrentItem(with: avItem)
|
|
988
|
+
self.startObservingAVPlayerItem(avItem)
|
|
989
|
+
self.logger.debug("AVPlayerItem loaded, currentItem: \(String(describing: self.avPlayer.currentItem)), playWhenReady: \(self.playWhenReady), avPlayer.status=\(self.avPlayer.status.rawValue)")
|
|
990
|
+
if self.playWhenReady {
|
|
991
|
+
self.startPlayback()
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Execute any pending seek operation
|
|
995
|
+
if let pending = self.pendingSeek {
|
|
996
|
+
self.pendingSeek = nil
|
|
997
|
+
pending.execute(on: self.avPlayer, delegate: self)
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} catch {
|
|
1001
|
+
guard !Task.isCancelled else { return }
|
|
1002
|
+
|
|
1003
|
+
// Try retry before setting error
|
|
1004
|
+
if self.retryManager.isRetryable(error) {
|
|
1005
|
+
let retried = await self.retryManager.attemptRetry(startFromCurrentTime: false)
|
|
1006
|
+
if retried {
|
|
1007
|
+
return // Retry was triggered, don't set error
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
await MainActor.run {
|
|
1012
|
+
guard pendingAsset == self.asset else { return }
|
|
1013
|
+
self.playbackError = TrackPlayerError.PlaybackError.failedToLoadKeyValue
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
func unloadAVPlayer() {
|
|
1021
|
+
clearCurrentAVItem()
|
|
1022
|
+
state = .none
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// MARK: - Internal Event Handlers
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
Sets the progress update interval.
|
|
1029
|
+
- Parameter interval: The interval in seconds, or nil to disable progress updates
|
|
1030
|
+
*/
|
|
1031
|
+
func setProgressUpdateInterval(_ interval: TimeInterval?) {
|
|
1032
|
+
progressUpdateManager.setUpdateInterval(interval)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private func handleSeekCompleted(to seconds: Double, didFinish: Bool) {
|
|
1036
|
+
setNowPlayingCurrentTime(seconds: seconds)
|
|
1037
|
+
callbacks?.playerDidCompleteSeek(position: seconds, didFinish: didFinish)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
func handleTrackDidPlayToEndTime() {
|
|
1041
|
+
// Check if sleep timer should trigger on track end
|
|
1042
|
+
sleepTimerManager.onTrackPlayedToEnd()
|
|
1043
|
+
|
|
1044
|
+
if repeatMode == .track {
|
|
1045
|
+
replay()
|
|
1046
|
+
} else if repeatMode == .queue || !isLastInPlaybackOrder {
|
|
1047
|
+
next()
|
|
1048
|
+
} else {
|
|
1049
|
+
state = .ended
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// MARK: - Observer Callbacks
|
|
1054
|
+
|
|
1055
|
+
func avPlayerDidChangeTimeControlStatus(_ status: AVPlayer.TimeControlStatus) {
|
|
1056
|
+
switch status {
|
|
1057
|
+
case .paused:
|
|
1058
|
+
let currentState = state
|
|
1059
|
+
let currentTime = currentTime
|
|
1060
|
+
let duration = duration
|
|
1061
|
+
// Ignore pauses when near track end - let handleTrackDidPlayToEndTime handle track
|
|
1062
|
+
// completion
|
|
1063
|
+
let nearTrackEnd = currentTime >= duration - 0.5 && duration > 0
|
|
1064
|
+
|
|
1065
|
+
// Completely ignore pause events when near track end to avoid race with
|
|
1066
|
+
// handleTrackDidPlayToEndTime
|
|
1067
|
+
if nearTrackEnd {
|
|
1068
|
+
// Ignore - track completion will be handled by handleTrackDidPlayToEndTime
|
|
1069
|
+
} else if asset == nil, currentState != .stopped {
|
|
1070
|
+
state = .none
|
|
1071
|
+
} else if currentState != .error, currentState != .stopped {
|
|
1072
|
+
// Only update state, never modify playWhenReady
|
|
1073
|
+
// playWhenReady represents user intent and should only change via explicit user actions
|
|
1074
|
+
if !playWhenReady {
|
|
1075
|
+
state = .paused
|
|
1076
|
+
}
|
|
1077
|
+
// If playWhenReady is true, this is likely buffering/seeking - don't change state
|
|
1078
|
+
}
|
|
1079
|
+
case .waitingToPlayAtSpecifiedRate:
|
|
1080
|
+
if asset != nil {
|
|
1081
|
+
state = .buffering
|
|
1082
|
+
}
|
|
1083
|
+
case .playing:
|
|
1084
|
+
state = .playing
|
|
1085
|
+
@unknown default:
|
|
1086
|
+
break
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
func avPlayerStatusDidChange(_ status: AVPlayer.Status) {
|
|
1091
|
+
if status == .failed {
|
|
1092
|
+
handlePlaybackFailure(error: avPlayer.currentItem?.error)
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/// Handles AVPlayerItem status changes - this catches errors that don't show up in AVPlayer.status
|
|
1097
|
+
func avItemStatusDidChange(_ status: AVPlayerItem.Status, error: Error?) {
|
|
1098
|
+
if status == .failed {
|
|
1099
|
+
let effectiveError = error ?? avPlayer.currentItem?.error
|
|
1100
|
+
handlePlaybackFailure(error: effectiveError)
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/// Common handler for playback failures from either AVPlayer or AVPlayerItem
|
|
1105
|
+
private func handlePlaybackFailure(error: Error?) {
|
|
1106
|
+
if let error {
|
|
1107
|
+
let nsError = error as NSError
|
|
1108
|
+
logger.error("Playback failure: domain=\(nsError.domain), code=\(nsError.code), localizedDescription=\(error.localizedDescription)")
|
|
1109
|
+
} else {
|
|
1110
|
+
logger.error("Playback failure with nil error")
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Try retry before setting error state
|
|
1114
|
+
if retryManager.isRetryable(error) {
|
|
1115
|
+
pendingRetryTask?.cancel()
|
|
1116
|
+
pendingRetryTask = Task {
|
|
1117
|
+
let retried = await retryManager.attemptRetry(startFromCurrentTime: true)
|
|
1118
|
+
if !retried {
|
|
1119
|
+
// Max retries exceeded or retry cancelled, surface error
|
|
1120
|
+
self.setPlaybackError(from: error)
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
logger.warning("Error not retryable, surfacing playback_failed")
|
|
1127
|
+
setPlaybackError(from: error)
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/// Sets the playback error from an Error, with appropriate classification
|
|
1131
|
+
private func setPlaybackError(from error: Error?) {
|
|
1132
|
+
let nsError = error as NSError?
|
|
1133
|
+
playbackError = nsError?.code == URLError.notConnectedToInternet.rawValue
|
|
1134
|
+
? TrackPlayerError.PlaybackError.notConnectedToInternet
|
|
1135
|
+
: TrackPlayerError.PlaybackError.playbackFailed
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/// Handles AVPlayerItemFailedToPlayToEndTime notification with retry logic
|
|
1139
|
+
private func handleItemFailedToPlayToEndTime(error: Error?) {
|
|
1140
|
+
let effectiveError = error ?? avPlayer.currentItem?.error
|
|
1141
|
+
handlePlaybackFailure(error: effectiveError)
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
func audioDidStart() {
|
|
1145
|
+
state = .playing
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
func avItemDidUpdatePlaybackLikelyToKeepUp(_ playbackLikelyToKeepUp: Bool) {
|
|
1149
|
+
logger.debug("avItemDidUpdatePlaybackLikelyToKeepUp: \(playbackLikelyToKeepUp), state=\(self.state.rawValue)")
|
|
1150
|
+
if playbackLikelyToKeepUp, state != .playing {
|
|
1151
|
+
logger.debug("setting state = .ready")
|
|
1152
|
+
state = .ready
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// MARK: - Queue Validation
|
|
1157
|
+
|
|
1158
|
+
private func throwIfQueueEmpty() throws {
|
|
1159
|
+
if tracks.isEmpty {
|
|
1160
|
+
throw TrackPlayerError.QueueError.empty
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
private func throwIfIndexInvalid(
|
|
1165
|
+
index: Int,
|
|
1166
|
+
name: String = "index",
|
|
1167
|
+
min: Int? = nil,
|
|
1168
|
+
max: Int? = nil,
|
|
1169
|
+
) throws {
|
|
1170
|
+
guard index >= (min ?? 0), (max ?? tracks.count) > index else {
|
|
1171
|
+
throw TrackPlayerError.QueueError.invalidIndex(
|
|
1172
|
+
index: index,
|
|
1173
|
+
message: "\(name) must be non-negative and less than \(tracks.count)",
|
|
1174
|
+
)
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// MARK: - Queue Methods
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
Set the track at a specific index.
|
|
1182
|
+
|
|
1183
|
+
- parameter index: The index of the track to set.
|
|
1184
|
+
- parameter track: The track to set.
|
|
1185
|
+
*/
|
|
1186
|
+
func setTrack(at index: Int, _ track: Track) {
|
|
1187
|
+
guard index >= 0, index < tracks.count else { return }
|
|
1188
|
+
tracks[index] = track
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
Replace the entire queue with new tracks.
|
|
1193
|
+
|
|
1194
|
+
- parameter tracks: The tracks to set as the new queue.
|
|
1195
|
+
- parameter initialIndex: The index to start at. Defaults to 0.
|
|
1196
|
+
- parameter playWhenReady: Optional, whether to start playback when the track is ready.
|
|
1197
|
+
- parameter sourcePath: Optional path from which this queue was expanded (for contextual URL optimization).
|
|
1198
|
+
*/
|
|
1199
|
+
func setQueue(_ newTracks: [Track], initialIndex: Int = 0, playWhenReady: Bool? = nil, sourcePath: String? = nil) {
|
|
1200
|
+
guard !newTracks.isEmpty else {
|
|
1201
|
+
clear()
|
|
1202
|
+
return
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Replace tracks and index atomically to avoid intermediate nil state
|
|
1206
|
+
let clampedIndex = max(0, min(initialIndex, newTracks.count - 1))
|
|
1207
|
+
tracks = newTracks
|
|
1208
|
+
currentIndex = clampedIndex
|
|
1209
|
+
queueSourcePath = sourcePath
|
|
1210
|
+
|
|
1211
|
+
// Generate new shuffle order for the new queue
|
|
1212
|
+
// Like Media3, this is a fresh randomized order - not reshuffled based on current track
|
|
1213
|
+
shuffleOrder = ShuffleOrder(length: newTracks.count)
|
|
1214
|
+
|
|
1215
|
+
// Handle playWhenReady and load the new track
|
|
1216
|
+
handlePlayWhenReady(playWhenReady) {
|
|
1217
|
+
handleCurrentTrackChanged()
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
Add tracks to the queue.
|
|
1223
|
+
|
|
1224
|
+
- parameter tracks: The tracks to add to the queue.
|
|
1225
|
+
- parameter initialIndex: Optional, the index to start at when queue was empty. Defaults to 0.
|
|
1226
|
+
- parameter playWhenReady: Optional, whether to start playback when the track is ready.
|
|
1227
|
+
*/
|
|
1228
|
+
func add(_ tracks: [Track], initialIndex: Int? = nil, playWhenReady: Bool? = nil) {
|
|
1229
|
+
handlePlayWhenReady(playWhenReady) {
|
|
1230
|
+
add(tracks, initialIndex: initialIndex ?? 0)
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private func add(_ newTracks: [Track], initialIndex: Int) {
|
|
1235
|
+
guard !newTracks.isEmpty else { return }
|
|
1236
|
+
let wasEmpty = tracks.isEmpty
|
|
1237
|
+
let insertIndex = tracks.count
|
|
1238
|
+
tracks.append(contentsOf: newTracks)
|
|
1239
|
+
|
|
1240
|
+
// Update shuffle order - inserts new items at random positions (like Media3)
|
|
1241
|
+
shuffleOrder.insert(at: insertIndex, count: newTracks.count)
|
|
1242
|
+
|
|
1243
|
+
if wasEmpty {
|
|
1244
|
+
// Use initialIndex, clamped to valid range
|
|
1245
|
+
currentIndex = max(0, min(initialIndex, tracks.count - 1))
|
|
1246
|
+
handleCurrentTrackChanged()
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
func add(_ tracks: [Track], at index: Int) throws {
|
|
1251
|
+
guard !tracks.isEmpty else { return }
|
|
1252
|
+
guard index >= 0, self.tracks.count >= index else {
|
|
1253
|
+
throw TrackPlayerError.QueueError.invalidIndex(
|
|
1254
|
+
index: index,
|
|
1255
|
+
message: "Index to insert at has to be non-negative and equal to or smaller than the number of tracks: (\(self.tracks.count))",
|
|
1256
|
+
)
|
|
1257
|
+
}
|
|
1258
|
+
let wasEmpty = self.tracks.isEmpty
|
|
1259
|
+
// Correct index when tracks were inserted in front of it:
|
|
1260
|
+
if self.tracks.count > 1, currentIndex >= index {
|
|
1261
|
+
currentIndex += tracks.count
|
|
1262
|
+
}
|
|
1263
|
+
self.tracks.insert(contentsOf: tracks, at: index)
|
|
1264
|
+
|
|
1265
|
+
// Update shuffle order - inserts new items at random positions (like Media3)
|
|
1266
|
+
shuffleOrder.insert(at: index, count: tracks.count)
|
|
1267
|
+
|
|
1268
|
+
if wasEmpty {
|
|
1269
|
+
currentIndex = 0
|
|
1270
|
+
handleCurrentTrackChanged()
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
Step to the next track in the queue.
|
|
1276
|
+
*/
|
|
1277
|
+
func next() {
|
|
1278
|
+
guard currentTrack != nil, !tracks.isEmpty else { return }
|
|
1279
|
+
|
|
1280
|
+
let wrap = repeatMode == .queue
|
|
1281
|
+
|
|
1282
|
+
if tracks.count == 1 {
|
|
1283
|
+
if wrap, playWhenReady {
|
|
1284
|
+
replay()
|
|
1285
|
+
}
|
|
1286
|
+
return
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
var newIndex: Int?
|
|
1290
|
+
if shuffleEnabled {
|
|
1291
|
+
// Use shuffle order for navigation
|
|
1292
|
+
newIndex = shuffleOrder.getNextIndex(after: currentIndex)
|
|
1293
|
+
if newIndex == nil {
|
|
1294
|
+
// Wrap to start of shuffle order (same order, like Media3)
|
|
1295
|
+
newIndex = shuffleOrder.firstIndex
|
|
1296
|
+
}
|
|
1297
|
+
} else {
|
|
1298
|
+
// Sequential navigation
|
|
1299
|
+
let nextIndex = currentIndex + 1
|
|
1300
|
+
if nextIndex < tracks.count {
|
|
1301
|
+
newIndex = nextIndex
|
|
1302
|
+
} else if wrap {
|
|
1303
|
+
newIndex = 0
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if let newIndex, newIndex != currentIndex {
|
|
1308
|
+
currentIndex = newIndex
|
|
1309
|
+
handleCurrentTrackChanged()
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
Step to the previous track in the queue.
|
|
1315
|
+
*/
|
|
1316
|
+
func previous() {
|
|
1317
|
+
guard currentTrack != nil, !tracks.isEmpty else { return }
|
|
1318
|
+
|
|
1319
|
+
let wrap = repeatMode == .queue
|
|
1320
|
+
|
|
1321
|
+
if tracks.count == 1 {
|
|
1322
|
+
if wrap, playWhenReady {
|
|
1323
|
+
replay()
|
|
1324
|
+
}
|
|
1325
|
+
return
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
var newIndex: Int?
|
|
1329
|
+
if shuffleEnabled {
|
|
1330
|
+
// Use shuffle order for navigation
|
|
1331
|
+
newIndex = shuffleOrder.getPreviousIndex(before: currentIndex)
|
|
1332
|
+
if newIndex == nil {
|
|
1333
|
+
// Wrap to end of shuffle order (same order, like Media3)
|
|
1334
|
+
newIndex = shuffleOrder.lastIndex
|
|
1335
|
+
}
|
|
1336
|
+
} else {
|
|
1337
|
+
// Sequential navigation
|
|
1338
|
+
let prevIndex = currentIndex - 1
|
|
1339
|
+
if prevIndex >= 0 {
|
|
1340
|
+
newIndex = prevIndex
|
|
1341
|
+
} else if wrap {
|
|
1342
|
+
newIndex = tracks.count - 1
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
if let newIndex, newIndex != currentIndex {
|
|
1347
|
+
currentIndex = newIndex
|
|
1348
|
+
handleCurrentTrackChanged()
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
Remove a track from the queue.
|
|
1354
|
+
|
|
1355
|
+
- parameter index: The index of the track to remove.
|
|
1356
|
+
- throws: `TrackPlayerError.QueueError`
|
|
1357
|
+
*/
|
|
1358
|
+
func remove(_ index: Int) throws {
|
|
1359
|
+
try throwIfQueueEmpty()
|
|
1360
|
+
try throwIfIndexInvalid(index: index)
|
|
1361
|
+
tracks.remove(at: index)
|
|
1362
|
+
|
|
1363
|
+
// Update shuffle order
|
|
1364
|
+
shuffleOrder.remove(from: index, to: index + 1)
|
|
1365
|
+
|
|
1366
|
+
if index == currentIndex {
|
|
1367
|
+
currentIndex = tracks.count > 0 ? currentIndex % tracks.count : -1
|
|
1368
|
+
handleCurrentTrackChanged()
|
|
1369
|
+
} else if index < currentIndex {
|
|
1370
|
+
currentIndex -= 1
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
Skip to a certain track in the queue.
|
|
1376
|
+
|
|
1377
|
+
- parameter index: The index of the track to skip to.
|
|
1378
|
+
- parameter playWhenReady: Optional, whether to start playback when the track is ready.
|
|
1379
|
+
- throws: `TrackPlayerError`
|
|
1380
|
+
*/
|
|
1381
|
+
func skipTo(_ index: Int, playWhenReady: Bool? = nil) throws {
|
|
1382
|
+
try handlePlayWhenReady(playWhenReady) {
|
|
1383
|
+
if index == currentIndex {
|
|
1384
|
+
seekTo(0)
|
|
1385
|
+
} else {
|
|
1386
|
+
try skipTo(index)
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
private func skipTo(_ index: Int) throws {
|
|
1392
|
+
try throwIfQueueEmpty()
|
|
1393
|
+
try throwIfIndexInvalid(index: index)
|
|
1394
|
+
|
|
1395
|
+
if index == currentIndex {
|
|
1396
|
+
if playWhenReady {
|
|
1397
|
+
replay()
|
|
1398
|
+
}
|
|
1399
|
+
} else {
|
|
1400
|
+
currentIndex = index
|
|
1401
|
+
handleCurrentTrackChanged()
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
/**
|
|
1406
|
+
Move a track in the queue from one position to another.
|
|
1407
|
+
|
|
1408
|
+
- parameter fromIndex: The index of the track to move.
|
|
1409
|
+
- parameter toIndex: The index to move the track to.
|
|
1410
|
+
- throws: `TrackPlayerError.QueueError`
|
|
1411
|
+
*/
|
|
1412
|
+
func move(fromIndex: Int, toIndex: Int) throws {
|
|
1413
|
+
try throwIfQueueEmpty()
|
|
1414
|
+
try throwIfIndexInvalid(index: fromIndex, name: "fromIndex")
|
|
1415
|
+
try throwIfIndexInvalid(index: toIndex, name: "toIndex", max: Int.max)
|
|
1416
|
+
|
|
1417
|
+
// Mutate a copy and assign once to trigger didSet only once
|
|
1418
|
+
var newTracks = tracks
|
|
1419
|
+
let track = newTracks.remove(at: fromIndex)
|
|
1420
|
+
newTracks.insert(track, at: min(newTracks.count, toIndex))
|
|
1421
|
+
tracks = newTracks
|
|
1422
|
+
|
|
1423
|
+
if fromIndex == currentIndex {
|
|
1424
|
+
currentIndex = toIndex
|
|
1425
|
+
handleCurrentTrackChanged()
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
Remove all upcoming tracks, those returned by `next()`
|
|
1431
|
+
*/
|
|
1432
|
+
func removeUpcomingTracks() {
|
|
1433
|
+
guard !tracks.isEmpty else { return }
|
|
1434
|
+
let nextIndex = currentIndex + 1
|
|
1435
|
+
guard nextIndex < tracks.count else { return }
|
|
1436
|
+
tracks.removeSubrange(nextIndex ..< tracks.count)
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
Removes all tracks from queue
|
|
1441
|
+
*/
|
|
1442
|
+
private func clearTracks() {
|
|
1443
|
+
guard currentIndex != -1 else { return }
|
|
1444
|
+
currentIndex = -1
|
|
1445
|
+
tracks.removeAll()
|
|
1446
|
+
queueSourcePath = nil
|
|
1447
|
+
handleCurrentTrackChanged()
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
func replay() {
|
|
1451
|
+
seekTo(0) { [weak self] succeeded in
|
|
1452
|
+
if succeeded {
|
|
1453
|
+
Task { @MainActor in
|
|
1454
|
+
self?.play()
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
func handleCurrentTrackChanged() {
|
|
1461
|
+
// Reset end-of-track sleep timer when track changes
|
|
1462
|
+
sleepTimerManager.onTrackChanged()
|
|
1463
|
+
|
|
1464
|
+
// Cancel any pending retry and reset count when track changes
|
|
1465
|
+
pendingRetryTask?.cancel()
|
|
1466
|
+
pendingRetryTask = nil
|
|
1467
|
+
retryManager.reset()
|
|
1468
|
+
|
|
1469
|
+
// Clear any previous playback error when switching tracks
|
|
1470
|
+
if playbackError != nil {
|
|
1471
|
+
playbackError = nil
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
let lastPosition = currentTime
|
|
1475
|
+
let shouldContinuePlayback = playWhenReady
|
|
1476
|
+
if let currentTrack {
|
|
1477
|
+
// Ensure playWhenReady is set before loading to preserve playback state
|
|
1478
|
+
playWhenReady = shouldContinuePlayback
|
|
1479
|
+
|
|
1480
|
+
// Reset playback values without updating, because that will happen in
|
|
1481
|
+
// the loadNowPlayingMetaValues call straight after:
|
|
1482
|
+
nowPlayingInfoController.setWithoutUpdate(keyValues: [
|
|
1483
|
+
MediaItemProperty.duration(nil),
|
|
1484
|
+
NowPlayingInfoProperty.playbackRate(nil),
|
|
1485
|
+
NowPlayingInfoProperty.elapsedPlaybackTime(nil),
|
|
1486
|
+
])
|
|
1487
|
+
loadNowPlayingMetaValues()
|
|
1488
|
+
|
|
1489
|
+
// Load the track - resolve media URL first if resolver is configured
|
|
1490
|
+
guard let src = currentTrack.src else {
|
|
1491
|
+
logger.error("Failed to load track - no src")
|
|
1492
|
+
logger.error(" track.title: \(currentTrack.title)")
|
|
1493
|
+
logger.error(" track.url: \(currentTrack.url ?? "nil")")
|
|
1494
|
+
clearCurrentAVItem()
|
|
1495
|
+
playbackError = TrackPlayerError.PlaybackError.invalidSourceUrl("nil")
|
|
1496
|
+
return
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
logger.debug("Loading track: \(currentTrack.title)")
|
|
1500
|
+
logger.debug(" track.url: \(currentTrack.url ?? "nil")")
|
|
1501
|
+
logger.debug(" track.src: \(src)")
|
|
1502
|
+
|
|
1503
|
+
// Check if it's already a full URL (http/https) or local file
|
|
1504
|
+
if src.hasPrefix("http://") || src.hasPrefix("https://") || src.hasPrefix("file://") {
|
|
1505
|
+
// Already a full URL, use directly (may still need transform for headers)
|
|
1506
|
+
resolveAndLoadMedia(src: src, track: currentTrack)
|
|
1507
|
+
} else {
|
|
1508
|
+
// Relative path - needs media URL resolution
|
|
1509
|
+
resolveAndLoadMedia(src: src, track: currentTrack)
|
|
1510
|
+
}
|
|
1511
|
+
} else {
|
|
1512
|
+
unloadAVPlayer()
|
|
1513
|
+
nowPlayingInfoController.clear()
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
let eventData = PlaybackActiveTrackChangedEvent(
|
|
1517
|
+
lastIndex: lastIndex == -1 ? nil : Double(lastIndex),
|
|
1518
|
+
lastTrack: lastTrack,
|
|
1519
|
+
lastPosition: lastPosition,
|
|
1520
|
+
index: currentIndex == -1 ? nil : Double(currentIndex),
|
|
1521
|
+
track: currentTrack,
|
|
1522
|
+
)
|
|
1523
|
+
callbacks?.playerDidChangeActiveTrack(eventData)
|
|
1524
|
+
lastTrack = currentTrack
|
|
1525
|
+
lastIndex = currentIndex
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/// Resolves the media URL (applying transform if configured) and loads the player
|
|
1529
|
+
private func resolveAndLoadMedia(src: String, track: Track) {
|
|
1530
|
+
// If we have a resolver, use it asynchronously
|
|
1531
|
+
if let resolver = mediaUrlResolver {
|
|
1532
|
+
// Don't use @MainActor for the entire Task - this can cause deadlocks
|
|
1533
|
+
// when the JS callback needs to schedule work back on the main thread.
|
|
1534
|
+
// Instead, only hop to main thread when we need to access/modify state.
|
|
1535
|
+
Task {
|
|
1536
|
+
self.logger.debug("resolveAndLoadMedia: starting resolution for \(src)")
|
|
1537
|
+
|
|
1538
|
+
// Check on main thread that this track is still the current one
|
|
1539
|
+
let isCurrentTrack = await MainActor.run { self.currentTrack?.src == track.src }
|
|
1540
|
+
guard isCurrentTrack else {
|
|
1541
|
+
self.logger.debug("Track changed during media resolution, skipping load")
|
|
1542
|
+
return
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
self.logger.debug("resolveAndLoadMedia: calling resolver...")
|
|
1546
|
+
let resolved = await resolver(src)
|
|
1547
|
+
self.logger.debug("resolveAndLoadMedia: resolver returned, resolved URL: \(resolved.url)")
|
|
1548
|
+
if let headers = resolved.headers {
|
|
1549
|
+
self.logger.debug(" headers: \(headers)")
|
|
1550
|
+
}
|
|
1551
|
+
if let userAgent = resolved.userAgent {
|
|
1552
|
+
self.logger.debug(" userAgent: \(userAgent)")
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// Load on main thread
|
|
1556
|
+
await MainActor.run {
|
|
1557
|
+
self.loadMediaWithResolvedUrl(resolved, track: track)
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
} else {
|
|
1561
|
+
// No resolver, use src directly
|
|
1562
|
+
let resolved = MediaResolvedUrl(url: src, headers: nil, userAgent: nil)
|
|
1563
|
+
loadMediaWithResolvedUrl(resolved, track: track)
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/// Loads the AVPlayer with a resolved media URL
|
|
1568
|
+
private func loadMediaWithResolvedUrl(_ resolved: MediaResolvedUrl, track _: Track) {
|
|
1569
|
+
guard let mediaUrl = URL(string: resolved.url) else {
|
|
1570
|
+
logger.error("Invalid media URL: \(resolved.url)")
|
|
1571
|
+
playbackError = TrackPlayerError.PlaybackError.invalidSourceUrl(resolved.url)
|
|
1572
|
+
return
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// Build URL options with headers if provided
|
|
1576
|
+
var options: [String: Any] = [:]
|
|
1577
|
+
if let headers = resolved.headers, !headers.isEmpty {
|
|
1578
|
+
options["AVURLAssetHTTPHeaderFieldsKey"] = headers
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let isLocalFile = mediaUrl.isFileURL
|
|
1582
|
+
url = isLocalFile ? URL(fileURLWithPath: mediaUrl.path) : mediaUrl
|
|
1583
|
+
urlOptions = options.isEmpty ? nil : options
|
|
1584
|
+
|
|
1585
|
+
logger.debug(" final playbackUrl: \(mediaUrl.absoluteString)")
|
|
1586
|
+
logger.debug(" isLocalFile: \(isLocalFile)")
|
|
1587
|
+
|
|
1588
|
+
loadAVPlayer()
|
|
1589
|
+
}
|
|
1590
|
+
}
|