react-native-audio-browser 0.2.0-next.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AudioBrowser.podspec +1 -1
- package/README.md +2 -180
- package/android/build.gradle +1 -17
- package/android/gradle.properties +1 -1
- package/android/src/main/AndroidManifest.xml +0 -15
- package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +141 -401
- package/android/src/main/java/com/audiobrowser/Callbacks.kt +6 -4
- package/android/src/main/java/com/audiobrowser/Service.kt +18 -67
- package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +178 -442
- package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +4 -41
- package/android/src/main/java/com/audiobrowser/browser/TrackLoadHandler.kt +4 -4
- package/android/src/main/java/com/audiobrowser/http/RequestConfigBuilder.kt +97 -109
- package/android/src/main/java/com/audiobrowser/model/PlaybackMetadata.kt +10 -6
- package/android/src/main/java/com/audiobrowser/model/PlayerSetupOptions.kt +8 -40
- package/android/src/main/java/com/audiobrowser/model/PlayerUpdateOptions.kt +46 -40
- package/android/src/main/java/com/audiobrowser/player/AutomaticBufferManager.kt +1 -4
- package/android/src/main/java/com/audiobrowser/player/DynamicLoadControl.kt +1 -18
- package/android/src/main/java/com/audiobrowser/player/MediaFactory.kt +3 -15
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +134 -422
- package/android/src/main/java/com/audiobrowser/player/MediaSessionCommandManager.kt +404 -248
- package/android/src/main/java/com/audiobrowser/player/PlaybackProgressUpdateManager.kt +59 -0
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +5 -25
- package/android/src/main/java/com/audiobrowser/player/Player.kt +582 -469
- package/android/src/main/java/com/audiobrowser/player/PlayerListener.kt +67 -190
- package/android/src/main/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicy.kt +53 -118
- package/android/src/main/java/com/audiobrowser/player/SleepTimer.kt +3 -37
- package/android/src/main/java/com/audiobrowser/player/TransformingDataSource.kt +40 -85
- package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +0 -24
- package/android/src/main/java/com/audiobrowser/util/CoilBitmapLoader.kt +300 -23
- package/android/src/main/java/com/audiobrowser/util/EqualizerManager.kt +23 -13
- package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +2 -2
- package/android/src/main/java/com/audiobrowser/util/MetadataAdapter.kt +3 -4
- package/android/src/main/java/com/audiobrowser/util/PlayingStateFactory.kt +1 -2
- package/android/src/main/java/com/audiobrowser/util/RatingFactory.kt +72 -0
- package/android/src/main/java/com/audiobrowser/util/ResolvedTrackFactory.kt +36 -0
- package/android/src/main/java/com/audiobrowser/util/SvgArtworkRenderer.kt +111 -6
- package/android/src/main/java/com/audiobrowser/util/SystemVolumeMonitor.kt +8 -22
- package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +80 -112
- package/android/src/main/res/values/strings.xml +0 -4
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +0 -4
- package/android/src/test/java/com/audiobrowser/browser/SimpleRouterTest.kt +2 -1
- package/android/src/test/java/com/audiobrowser/http/RequestConfigBuilderTest.kt +0 -72
- package/android/src/test/java/com/audiobrowser/player/PlaybackProgressUpdateManagerTest.kt +146 -0
- package/ios/Browser/BrowserConfig.swift +11 -41
- package/ios/Browser/BrowserManager+URLResolution.swift +93 -213
- package/ios/Browser/BrowserManager.swift +110 -303
- package/ios/Browser/BrowserPathHelper.swift +3 -32
- package/ios/Browser/JsonModels.swift +66 -287
- package/ios/CarPlay/CarPlayArtworkResolver.swift +6 -6
- package/ios/CarPlay/CarPlayController.swift +149 -739
- package/ios/CarPlay/CarPlayImageLoader.swift +8 -57
- package/ios/CarPlay/CarPlayListItemFactory.swift +35 -44
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +36 -180
- package/ios/CarPlay/RNABAudioBrowser.h +10 -15
- package/ios/CarPlay/RNABAudioBrowser.m +14 -15
- package/ios/CarPlay/RNABCarPlaySceneDelegate.m +0 -2
- package/ios/CarPlay/RNABMediaIntentHandler.swift +43 -53
- package/ios/CarPlay/Track+CarPlay.swift +63 -0
- package/ios/Extension/Capability+RemoteCommand.swift +9 -7
- package/ios/Extension/ChapterMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/NavigationError+Conversion.swift +1 -8
- package/ios/Extension/ResolvedTrack+Copying.swift +4 -10
- package/ios/Extension/TimedMetadata+AVFoundation.swift +1 -1
- package/ios/Extension/Track+AVPlayer.swift +4 -4
- package/ios/Extension/Track+Copying.swift +3 -9
- package/ios/Extension/TrackMetadata+AVFoundation.swift +1 -1
- package/ios/HybridAudioBrowser.swift +173 -751
- package/ios/Model/NitroTypeStubs.swift +224 -299
- package/ios/Model/PlayerUpdateOptions.swift +23 -23
- package/ios/Model/RemoteCommand.swift +6 -24
- package/ios/Model/TrackPlayerError.swift +18 -82
- package/ios/NowPlayingInfo/NowPlayingInfoCenter.swift +9 -0
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +121 -99
- package/ios/Observer/PlayerItemNotificationObserver.swift +5 -45
- package/ios/Observer/PlayerItemPropertyObserver.swift +4 -27
- package/ios/Observer/PlayerTimeObserver.swift +53 -3
- package/ios/Option/SessionCategories.swift +11 -34
- package/ios/Player/LoadSeekCoordinator.swift +1 -16
- package/ios/Player/MediaLoader.swift +11 -31
- package/ios/Player/NowPlayingUpdater.swift +26 -119
- package/ios/Player/PlaybackCoordinator.swift +51 -285
- package/ios/Player/PlaybackCoordinatorCallbacks.swift +1 -6
- package/ios/Player/PlaybackEffectHandler.swift +9 -19
- package/ios/Player/PlaybackErrorHandler.swift +13 -97
- package/ios/Player/PlaybackProgressUpdateManager.swift +68 -0
- package/ios/Player/PlaybackStateMachine.swift +13 -18
- package/ios/Player/PlayingStateManager.swift +2 -3
- package/ios/Player/QueueManager.swift +61 -67
- package/ios/Player/RetryManager.swift +21 -98
- package/ios/Player/SleepTimerHandling.swift +0 -8
- package/ios/Player/SleepTimerManager.swift +11 -72
- package/ios/RemoteCommand/RemoteCommandController.swift +21 -40
- package/ios/Tests/BrowserPathHelperTests.swift +0 -28
- package/ios/Tests/CarPlayArtworkResolverTests.swift +39 -39
- package/ios/Tests/ChapterMetadataAVFoundationTests.swift +2 -2
- package/ios/Tests/LoadSeekCoordinatorTests.swift +7 -40
- package/ios/Tests/MediaLoaderTests.swift +20 -32
- package/ios/Tests/PlaybackCoordinatorTests.swift +13 -787
- package/ios/Tests/PlaybackErrorHandlerTests.swift +46 -380
- package/ios/Tests/PlaybackProgressUpdateManagerTests.swift +166 -0
- package/ios/Tests/PlaybackStateMachineTests.swift +8 -33
- package/ios/Tests/QueueManagerTests.swift +18 -149
- package/ios/Tests/ResolvedTrackCopyingTests.swift +8 -15
- package/ios/Tests/Support/TestTypes.swift +20 -61
- package/ios/Tests/TrackSelectorTests.swift +15 -15
- package/ios/TrackPlayer.swift +58 -380
- package/ios/TrackPlayerCallbacks.swift +44 -5
- package/ios/TrackSelector.swift +5 -5
- package/ios/Util/ArtworkImageFetcher.swift +2 -4
- package/ios/Util/NetworkMonitor.swift +3 -7
- package/ios/Util/OnceValue.swift +0 -34
- package/lib/commonjs/AudioBrowser.js +11 -0
- package/lib/commonjs/AudioBrowser.js.map +1 -1
- package/lib/commonjs/features/browser.js +158 -59
- package/lib/commonjs/features/browser.js.map +1 -1
- package/lib/commonjs/features/equalizer.js +1 -1
- package/lib/commonjs/features/errors.js +5 -37
- package/lib/commonjs/features/errors.js.map +1 -1
- package/lib/commonjs/features/favorites.js +1 -1
- package/lib/commonjs/features/index.js +0 -22
- package/lib/commonjs/features/index.js.map +1 -1
- package/lib/commonjs/features/metadata.js +3 -3
- package/lib/commonjs/features/metadata.js.map +1 -1
- package/lib/commonjs/features/network.js +1 -1
- package/lib/commonjs/features/nowPlaying.js +1 -36
- package/lib/commonjs/features/nowPlaying.js.map +1 -1
- package/lib/commonjs/features/output.js +23 -37
- package/lib/commonjs/features/output.js.map +1 -1
- package/lib/commonjs/features/playback/controls.js +0 -9
- package/lib/commonjs/features/playback/controls.js.map +1 -1
- package/lib/commonjs/features/playback/index.js +0 -11
- package/lib/commonjs/features/playback/index.js.map +1 -1
- package/lib/commonjs/features/playback/playWhenReady.js +1 -1
- package/lib/commonjs/features/playback/playing.js +1 -1
- package/lib/commonjs/features/playback/progress.js +13 -29
- package/lib/commonjs/features/playback/progress.js.map +1 -1
- package/lib/commonjs/features/playback/state.js +1 -1
- package/lib/commonjs/features/playback/volume.js +2 -2
- package/lib/commonjs/features/player/options.js +29 -79
- package/lib/commonjs/features/player/options.js.map +1 -1
- package/lib/commonjs/features/player/setup.js +3 -256
- package/lib/commonjs/features/player/setup.js.map +1 -1
- package/lib/commonjs/features/queue/activeTrack.js +1 -1
- package/lib/commonjs/features/queue/queue.js +5 -12
- package/lib/commonjs/features/queue/queue.js.map +1 -1
- package/lib/commonjs/features/queue/repeatMode.js +1 -1
- package/lib/commonjs/features/queue/shuffle.js +1 -1
- package/lib/commonjs/features/rating.js +2 -0
- package/lib/commonjs/features/rating.js.map +1 -0
- package/lib/commonjs/features/remoteControls.js +104 -14
- package/lib/commonjs/features/remoteControls.js.map +1 -1
- package/lib/commonjs/features/sleepTimer.js +5 -10
- package/lib/commonjs/features/sleepTimer.js.map +1 -1
- package/lib/commonjs/native.js +0 -5
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/native.web.js +1 -4
- package/lib/commonjs/native.web.js.map +1 -1
- package/lib/commonjs/types/player.js +2 -0
- package/lib/commonjs/types/player.js.map +1 -0
- package/lib/commonjs/utils/LazyNativeEmitter.js +8 -8
- package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/commonjs/utils/NativeUpdatedValue.js +3 -0
- package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/commonjs/web/NativeAudioBrowser.js +124 -301
- package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
- package/lib/commonjs/web/SimpleRouter.test.js +174 -0
- package/lib/commonjs/web/SimpleRouter.test.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/Player.js +26 -90
- package/lib/commonjs/web/TrackPlayer/Player.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js +293 -0
- package/lib/commonjs/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/commonjs/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js +3 -34
- package/lib/commonjs/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/commonjs/web/TrackPlayer/index.js +5 -13
- package/lib/commonjs/web/TrackPlayer/index.js.map +1 -1
- package/lib/commonjs/web/browser/BrowserManager.js +50 -176
- package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
- package/lib/commonjs/web/browser/NavigationErrorManager.js +1 -3
- package/lib/commonjs/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/commonjs/web/browser/SearchManager.js +9 -11
- package/lib/commonjs/web/browser/SearchManager.js.map +1 -1
- package/lib/commonjs/web/http/HttpClient.js +23 -4
- package/lib/commonjs/web/http/HttpClient.js.map +1 -1
- package/lib/commonjs/web/http/RequestConfigBuilder.js +58 -107
- package/lib/commonjs/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/commonjs/web/player/NowPlayingManager.js +5 -6
- package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
- package/lib/commonjs/web/player/OptionsManager.js +17 -9
- package/lib/commonjs/web/player/OptionsManager.js.map +1 -1
- package/lib/module/AudioBrowser.js +1 -4
- package/lib/module/AudioBrowser.js.map +1 -1
- package/lib/module/features/browser.js +156 -53
- package/lib/module/features/browser.js.map +1 -1
- package/lib/module/features/equalizer.js +1 -1
- package/lib/module/features/errors.js +5 -37
- package/lib/module/features/errors.js.map +1 -1
- package/lib/module/features/favorites.js +1 -1
- package/lib/module/features/index.js +0 -2
- package/lib/module/features/index.js.map +1 -1
- package/lib/module/features/metadata.js +3 -3
- package/lib/module/features/metadata.js.map +1 -1
- package/lib/module/features/network.js +1 -1
- package/lib/module/features/nowPlaying.js +1 -34
- package/lib/module/features/nowPlaying.js.map +1 -1
- package/lib/module/features/output.js +19 -32
- package/lib/module/features/output.js.map +1 -1
- package/lib/module/features/playback/controls.js +0 -8
- package/lib/module/features/playback/controls.js.map +1 -1
- package/lib/module/features/playback/index.js +0 -1
- package/lib/module/features/playback/index.js.map +1 -1
- package/lib/module/features/playback/playWhenReady.js +1 -1
- package/lib/module/features/playback/playing.js +1 -1
- package/lib/module/features/playback/progress.js +13 -29
- package/lib/module/features/playback/progress.js.map +1 -1
- package/lib/module/features/playback/state.js +1 -1
- package/lib/module/features/playback/volume.js +2 -2
- package/lib/module/features/player/options.js +29 -79
- package/lib/module/features/player/options.js.map +1 -1
- package/lib/module/features/player/setup.js +3 -256
- package/lib/module/features/player/setup.js.map +1 -1
- package/lib/module/features/queue/activeTrack.js +1 -1
- package/lib/module/features/queue/queue.js +5 -12
- package/lib/module/features/queue/queue.js.map +1 -1
- package/lib/module/features/queue/repeatMode.js +1 -1
- package/lib/module/features/queue/shuffle.js +1 -1
- package/lib/module/features/rating.js +2 -0
- package/lib/module/features/rating.js.map +1 -0
- package/lib/module/features/remoteControls.js +103 -13
- package/lib/module/features/remoteControls.js.map +1 -1
- package/lib/module/features/sleepTimer.js +5 -10
- package/lib/module/features/sleepTimer.js.map +1 -1
- package/lib/module/native.js +0 -5
- package/lib/module/native.js.map +1 -1
- package/lib/module/native.web.js +1 -4
- package/lib/module/native.web.js.map +1 -1
- package/lib/module/types/player.js +2 -0
- package/lib/module/types/player.js.map +1 -0
- package/lib/module/utils/LazyNativeEmitter.js +8 -8
- package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
- package/lib/module/utils/NativeUpdatedValue.js +3 -0
- package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
- package/lib/module/web/NativeAudioBrowser.js +125 -302
- package/lib/module/web/NativeAudioBrowser.js.map +1 -1
- package/lib/module/web/SimpleRouter.test.js +174 -0
- package/lib/module/web/SimpleRouter.test.js.map +1 -0
- package/lib/module/web/TrackPlayer/Player.js +26 -90
- package/lib/module/web/TrackPlayer/Player.js.map +1 -1
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js +288 -0
- package/lib/module/web/TrackPlayer/PlaylistPlayer.js.map +1 -0
- package/lib/module/web/TrackPlayer/RepeatMode.js +1 -1
- package/lib/module/web/TrackPlayer/RepeatMode.js.map +1 -1
- package/lib/module/web/TrackPlayer/SleepTimer.js +3 -34
- package/lib/module/web/TrackPlayer/SleepTimer.js.map +1 -1
- package/lib/module/web/TrackPlayer/index.js +1 -2
- package/lib/module/web/TrackPlayer/index.js.map +1 -1
- package/lib/module/web/browser/BrowserManager.js +50 -176
- package/lib/module/web/browser/BrowserManager.js.map +1 -1
- package/lib/module/web/browser/NavigationErrorManager.js +1 -3
- package/lib/module/web/browser/NavigationErrorManager.js.map +1 -1
- package/lib/module/web/browser/SearchManager.js +9 -11
- package/lib/module/web/browser/SearchManager.js.map +1 -1
- package/lib/module/web/http/HttpClient.js +23 -4
- package/lib/module/web/http/HttpClient.js.map +1 -1
- package/lib/module/web/http/RequestConfigBuilder.js +58 -107
- package/lib/module/web/http/RequestConfigBuilder.js.map +1 -1
- package/lib/module/web/player/NowPlayingManager.js +5 -7
- package/lib/module/web/player/NowPlayingManager.js.map +1 -1
- package/lib/module/web/player/OptionsManager.js +17 -9
- package/lib/module/web/player/OptionsManager.js.map +1 -1
- package/lib/typescript/src/AudioBrowser.d.ts +1 -0
- package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
- package/lib/typescript/src/features/browser.d.ts +1 -40
- package/lib/typescript/src/features/browser.d.ts.map +1 -1
- package/lib/typescript/src/features/equalizer.d.ts +1 -1
- package/lib/typescript/src/features/errors.d.ts +6 -61
- package/lib/typescript/src/features/errors.d.ts.map +1 -1
- package/lib/typescript/src/features/favorites.d.ts +1 -1
- package/lib/typescript/src/features/index.d.ts +0 -2
- package/lib/typescript/src/features/index.d.ts.map +1 -1
- package/lib/typescript/src/features/metadata.d.ts +7 -9
- package/lib/typescript/src/features/metadata.d.ts.map +1 -1
- package/lib/typescript/src/features/network.d.ts +1 -1
- package/lib/typescript/src/features/nowPlaying.d.ts +1 -28
- package/lib/typescript/src/features/nowPlaying.d.ts.map +1 -1
- package/lib/typescript/src/features/output.d.ts +18 -28
- package/lib/typescript/src/features/output.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/controls.d.ts +0 -5
- package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/index.d.ts +0 -1
- package/lib/typescript/src/features/playback/index.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/playWhenReady.d.ts +1 -1
- package/lib/typescript/src/features/playback/playing.d.ts +1 -1
- package/lib/typescript/src/features/playback/progress.d.ts +1 -4
- package/lib/typescript/src/features/playback/progress.d.ts.map +1 -1
- package/lib/typescript/src/features/playback/state.d.ts +1 -1
- package/lib/typescript/src/features/playback/volume.d.ts +2 -2
- package/lib/typescript/src/features/player/options.d.ts +127 -176
- package/lib/typescript/src/features/player/options.d.ts.map +1 -1
- package/lib/typescript/src/features/player/setup.d.ts +167 -213
- package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/activeTrack.d.ts +1 -1
- package/lib/typescript/src/features/queue/queue.d.ts +4 -11
- package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
- package/lib/typescript/src/features/queue/repeatMode.d.ts +1 -1
- package/lib/typescript/src/features/queue/shuffle.d.ts +1 -1
- package/lib/typescript/src/features/rating.d.ts +14 -0
- package/lib/typescript/src/features/rating.d.ts.map +1 -0
- package/lib/typescript/src/features/remoteControls.d.ts +35 -23
- package/lib/typescript/src/features/remoteControls.d.ts.map +1 -1
- package/lib/typescript/src/features/sleepTimer.d.ts +4 -19
- package/lib/typescript/src/features/sleepTimer.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native.d.ts +2 -1
- package/lib/typescript/src/native.d.ts.map +1 -1
- package/lib/typescript/src/native.web.d.ts +3 -0
- package/lib/typescript/src/native.web.d.ts.map +1 -0
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts +225 -17
- package/lib/typescript/src/specs/audio-browser.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-native.d.ts +3 -9
- package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
- package/lib/typescript/src/types/browser-nodes.d.ts +13 -145
- package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
- package/lib/typescript/src/types/browser.d.ts +85 -349
- package/lib/typescript/src/types/browser.d.ts.map +1 -1
- package/lib/typescript/src/types/player.d.ts +64 -0
- package/lib/typescript/src/types/player.d.ts.map +1 -0
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +6 -8
- package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +2 -0
- package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
- package/lib/typescript/src/utils/resolveAssetSource.d.ts.map +1 -1
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts +188 -0
- package/lib/typescript/src/web/NativeAudioBrowser.d.ts.map +1 -0
- package/lib/typescript/src/web/SimpleRouter.d.ts +40 -0
- package/lib/typescript/src/web/SimpleRouter.d.ts.map +1 -0
- package/lib/typescript/src/web/SimpleRouter.test.d.ts +2 -0
- package/lib/typescript/src/web/SimpleRouter.test.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts +11 -0
- package/lib/typescript/src/web/TrackPlayer/Event.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts +59 -0
- package/lib/typescript/src/web/TrackPlayer/Player.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts +39 -0
- package/lib/typescript/src/web/TrackPlayer/PlaylistPlayer.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts +6 -0
- package/lib/typescript/src/web/TrackPlayer/RepeatMode.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts +4 -0
- package/lib/typescript/src/web/TrackPlayer/SetupNotCalledError.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts +34 -0
- package/lib/typescript/src/web/TrackPlayer/SleepTimer.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/State.d.ts +13 -0
- package/lib/typescript/src/web/TrackPlayer/State.d.ts.map +1 -0
- package/lib/typescript/src/web/TrackPlayer/index.d.ts +5 -0
- package/lib/typescript/src/web/TrackPlayer/index.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/BrowserManager.d.ts +154 -0
- package/lib/typescript/src/web/browser/BrowserManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts +42 -0
- package/lib/typescript/src/web/browser/FavoriteManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts +40 -0
- package/lib/typescript/src/web/browser/NavigationErrorManager.d.ts.map +1 -0
- package/lib/typescript/src/web/browser/SearchManager.d.ts +22 -0
- package/lib/typescript/src/web/browser/SearchManager.d.ts.map +1 -0
- package/lib/typescript/src/web/http/HttpClient.d.ts +25 -0
- package/lib/typescript/src/web/http/HttpClient.d.ts.map +1 -0
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts +98 -0
- package/lib/typescript/src/web/http/RequestConfigBuilder.d.ts.map +1 -0
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts +34 -0
- package/lib/typescript/src/web/player/NowPlayingManager.d.ts.map +1 -0
- package/lib/typescript/src/web/player/OptionsManager.d.ts +32 -0
- package/lib/typescript/src/web/player/OptionsManager.d.ts.map +1 -0
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts +69 -0
- package/lib/typescript/src/web/util/BrowserPathHelper.d.ts.map +1 -0
- package/lib/typescript/src/web/util/shuffle.d.ts +8 -0
- package/lib/typescript/src/web/util/shuffle.d.ts.map +1 -0
- package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +2 -4
- package/nitrogen/generated/android/AudioBrowserOnLoad.cpp +10 -24
- package/nitrogen/generated/android/c++/JAndroidAudioContentType.hpp +6 -6
- package/nitrogen/generated/android/c++/JAndroidOptions.hpp +20 -10
- package/nitrogen/generated/android/c++/JAndroidUpdateOptions.hpp +86 -0
- package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +1 -33
- package/nitrogen/generated/android/c++/JBrowseResult.hpp +0 -5
- package/nitrogen/generated/android/c++/JCarPlayNowPlayingButton.hpp +3 -3
- package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFormattedNavigationError.hpp +2 -3
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.hpp +147 -0
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +0 -8
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_IosOutput.hpp +80 -0
- package/nitrogen/generated/android/c++/JFunc_void_NowPlayingMetadata.hpp +10 -0
- package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +9 -14
- package/nitrogen/generated/android/c++/JFunc_void_Playback.hpp +0 -2
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_PlaybackErrorEvent.hpp +0 -2
- package/nitrogen/generated/android/c++/JFunc_void_RemoteSetRatingEvent.hpp +87 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +10 -10
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +0 -6
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +0 -6
- package/nitrogen/generated/android/c++/JHeartRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +221 -245
- package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.hpp +22 -25
- package/nitrogen/generated/android/c++/JImageRowItem.hpp +3 -33
- package/nitrogen/generated/android/c++/{JOutput.hpp → JIosOutput.hpp} +15 -15
- package/nitrogen/generated/android/c++/JIosOutputType.hpp +85 -0
- package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +1 -33
- package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +28 -61
- package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +9 -20
- package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +24 -21
- package/nitrogen/generated/android/c++/JNavigationErrorType.hpp +0 -6
- package/nitrogen/generated/android/c++/JNitroAndroidUpdateOptions.hpp +20 -10
- package/nitrogen/generated/android/c++/{JRemoteButton.hpp → JNotificationButton.hpp} +20 -20
- package/nitrogen/generated/android/c++/JNotificationButtonLayout.hpp +94 -0
- package/nitrogen/generated/android/c++/JNowPlayingMetadata.hpp +17 -3
- package/nitrogen/generated/android/c++/JNowPlayingUpdate.hpp +3 -7
- package/nitrogen/generated/android/c++/JOptions.hpp +14 -19
- package/nitrogen/generated/android/c++/{JNativeAndroidSetupOptions.hpp → JPartialAndroidSetupPlayerOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/{JNativeIOSSetupOptions.hpp → JPartialIOSSetupPlayerOptions.hpp} +10 -10
- package/nitrogen/generated/android/c++/JPartialSetupPlayerOptions.hpp +96 -0
- package/nitrogen/generated/android/c++/JPercentageRating.hpp +57 -0
- package/nitrogen/generated/android/c++/JPlayback.hpp +0 -2
- package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JPlaybackError.hpp +3 -18
- package/nitrogen/generated/android/c++/JPlaybackErrorEvent.hpp +0 -2
- package/nitrogen/generated/android/c++/JPlaybackState.hpp +3 -3
- package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
- package/nitrogen/generated/android/c++/JRatingType.hpp +73 -0
- package/nitrogen/generated/android/c++/JRemoteSetRatingEvent.hpp +66 -0
- package/nitrogen/generated/android/c++/JResolvedTrack.hpp +5 -23
- package/nitrogen/generated/android/c++/JRetryConfig.hpp +7 -11
- package/nitrogen/generated/android/c++/JSearchMode.hpp +9 -27
- package/nitrogen/generated/android/c++/JSearchParams.hpp +3 -9
- package/nitrogen/generated/android/c++/JStarRating.hpp +57 -0
- package/nitrogen/generated/android/c++/{JGateEvent.hpp → JThumbsRating.hpp} +15 -16
- package/nitrogen/generated/android/c++/JTrack.hpp +5 -23
- package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +0 -6
- package/nitrogen/generated/android/c++/JTransformableRequestConfig.hpp +1 -14
- package/nitrogen/generated/android/c++/JUpdateOptions.hpp +105 -0
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.cpp +34 -0
- package/nitrogen/generated/android/c++/JVariant_HeartRating_ThumbsRating_StarRating_PercentageRating.hpp +105 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_NotificationButtonLayout.hpp +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidAudioContentType.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidOptions.kt +9 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/AndroidUpdateOptions.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ArtworkRequestConfig.kt +4 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayNowPlayingButton.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormattedNavigationError.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.kt → Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.kt} +12 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_Output.kt → Func_void_IosOutput.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Func_void_GateEvent.kt → Func_void_RemoteSetRatingEvent.kt} +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_void_bool.kt +9 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Gate.kt → HeartRating.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/HybridAudioBrowserSpec.kt +109 -103
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +3 -24
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Output.kt → IosOutput.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IosOutputType.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaRequestConfig.kt +4 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +6 -18
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeRouteEntry.kt +4 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeUpdateOptions.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NavigationErrorType.kt +1 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroAndroidUpdateOptions.kt +9 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → NotificationButton.kt} +8 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NotificationButtonLayout.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingMetadata.kt +6 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NowPlayingUpdate.kt +3 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Options.kt +3 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeAndroidSetupOptions.kt → PartialAndroidSetupPlayerOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{NativeIOSSetupOptions.kt → PartialIOSSetupPlayerOptions.kt} +5 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PartialSetupPlayerOptions.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateDecision.kt → PercentageRating.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackError.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackState.kt +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateReason.kt → RatingType.kt} +10 -5
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{ArtworkVariants.kt → RemoteSetRatingEvent.kt} +6 -9
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RetryConfig.kt +4 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchMode.kt +6 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/SearchParams.kt +3 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{GateEvent.kt → StarRating.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → ThumbsRating.kt} +6 -6
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +3 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TransformableRequestConfig.kt +4 -7
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/UpdateOptions.kt +53 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.kt +77 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{Variant_NullType_RemoteButtonLayout.kt → Variant_NullType_NotificationButtonLayout.kt} +9 -9
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.cpp +28 -139
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +489 -1125
- package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +45 -66
- package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +118 -176
- package/nitrogen/generated/ios/swift/AndroidAudioContentType.swift +8 -8
- package/nitrogen/generated/ios/swift/AndroidOptions.swift +22 -12
- package/nitrogen/generated/ios/swift/AndroidUpdateOptions.swift +119 -0
- package/nitrogen/generated/ios/swift/ArtworkRequestConfig.swift +1 -95
- package/nitrogen/generated/ios/swift/CarPlayNowPlayingButton.swift +4 -4
- package/nitrogen/generated/ios/swift/FormattedNavigationError.swift +4 -17
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError______BrowserSourceCallbackParam.swift +68 -0
- package/nitrogen/generated/ios/swift/{Func_void_Output.swift → Func_void_IosOutput.swift} +10 -10
- package/nitrogen/generated/ios/swift/{Func_void_GateEvent.swift → Func_void_RemoteSetRatingEvent.swift} +10 -10
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +5 -5
- package/nitrogen/generated/ios/swift/{Func_void_std__shared_ptr_Promise_GateDecision__.swift → Func_void_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError___.swift} +17 -17
- package/nitrogen/generated/ios/swift/HeartRating.swift +29 -0
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec.swift +14 -20
- package/nitrogen/generated/ios/swift/HybridAudioBrowserSpec_cxx.swift +197 -230
- package/nitrogen/generated/ios/swift/ImageRowItem.swift +2 -121
- package/nitrogen/generated/ios/swift/{Output.swift → IosOutput.swift} +7 -7
- package/nitrogen/generated/ios/swift/IosOutputType.swift +76 -0
- package/nitrogen/generated/ios/swift/MediaRequestConfig.swift +1 -107
- package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +22 -161
- package/nitrogen/generated/ios/swift/NativeRouteEntry.swift +19 -19
- package/nitrogen/generated/ios/swift/NativeUpdateOptions.swift +25 -12
- package/nitrogen/generated/ios/swift/NavigationErrorType.swift +0 -8
- package/nitrogen/generated/ios/swift/NitroAndroidUpdateOptions.swift +41 -12
- package/nitrogen/generated/ios/swift/{RemoteButton.swift → NotificationButton.swift} +10 -10
- package/nitrogen/generated/ios/swift/NotificationButtonLayout.swift +92 -0
- package/nitrogen/generated/ios/swift/NowPlayingMetadata.swift +48 -1
- package/nitrogen/generated/ios/swift/NowPlayingUpdate.swift +1 -19
- package/nitrogen/generated/ios/swift/Options.swift +4 -10
- package/nitrogen/generated/ios/swift/{NativeAndroidSetupOptions.swift → PartialAndroidSetupPlayerOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/{NativeIOSSetupOptions.swift → PartialIOSSetupPlayerOptions.swift} +5 -5
- package/nitrogen/generated/ios/swift/PartialSetupPlayerOptions.swift +101 -0
- package/nitrogen/generated/ios/swift/PercentageRating.swift +29 -0
- package/nitrogen/generated/ios/swift/PlaybackError.swift +2 -43
- package/nitrogen/generated/ios/swift/PlaybackState.swift +4 -4
- package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
- package/nitrogen/generated/ios/swift/RatingType.swift +60 -0
- package/nitrogen/generated/ios/swift/RemoteSetRatingEvent.swift +58 -0
- package/nitrogen/generated/ios/swift/ResolvedTrack.swift +8 -74
- package/nitrogen/generated/ios/swift/RetryConfig.swift +4 -35
- package/nitrogen/generated/ios/swift/SearchMode.swift +12 -36
- package/nitrogen/generated/ios/swift/SearchParams.swift +2 -7
- package/nitrogen/generated/ios/swift/StarRating.swift +29 -0
- package/nitrogen/generated/ios/swift/ThumbsRating.swift +29 -0
- package/nitrogen/generated/ios/swift/Track.swift +8 -74
- package/nitrogen/generated/ios/swift/TransformableRequestConfig.swift +1 -60
- package/nitrogen/generated/ios/swift/UpdateOptions.swift +143 -0
- package/nitrogen/generated/ios/swift/Variant_HeartRating_ThumbsRating_StarRating_PercentageRating.swift +20 -0
- package/nitrogen/generated/ios/swift/{Variant_NullType_RemoteButtonLayout.swift → Variant_NullType_NotificationButtonLayout.swift} +3 -3
- package/nitrogen/generated/shared/c++/AndroidAudioContentType.hpp +10 -10
- package/nitrogen/generated/shared/c++/AndroidOptions.hpp +19 -8
- package/nitrogen/generated/shared/c++/AndroidUpdateOptions.hpp +109 -0
- package/nitrogen/generated/shared/c++/ArtworkRequestConfig.hpp +2 -10
- package/nitrogen/generated/shared/c++/CarPlayNowPlayingButton.hpp +6 -6
- package/nitrogen/generated/shared/c++/EqualizerSettings.hpp +2 -0
- package/nitrogen/generated/shared/c++/FormattedNavigationError.hpp +5 -6
- package/nitrogen/generated/shared/c++/{GateEvent.hpp → HeartRating.hpp} +17 -18
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.cpp +18 -21
- package/nitrogen/generated/shared/c++/HybridAudioBrowserSpec.hpp +35 -44
- package/nitrogen/generated/shared/c++/ImageRowItem.hpp +2 -33
- package/nitrogen/generated/shared/c++/{Output.hpp → IosOutput.hpp} +18 -18
- package/nitrogen/generated/shared/c++/IosOutputType.hpp +112 -0
- package/nitrogen/generated/shared/c++/MediaRequestConfig.hpp +2 -10
- package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +15 -31
- package/nitrogen/generated/shared/c++/NativeRouteEntry.hpp +5 -5
- package/nitrogen/generated/shared/c++/NativeUpdateOptions.hpp +7 -9
- package/nitrogen/generated/shared/c++/NavigationErrorType.hpp +0 -8
- package/nitrogen/generated/shared/c++/NitroAndroidUpdateOptions.hpp +19 -8
- package/nitrogen/generated/shared/c++/{RemoteButton.hpp → NotificationButton.hpp} +25 -25
- package/nitrogen/generated/shared/c++/NotificationButtonLayout.hpp +102 -0
- package/nitrogen/generated/shared/c++/NowPlayingMetadata.hpp +19 -3
- package/nitrogen/generated/shared/c++/NowPlayingUpdate.hpp +2 -6
- package/nitrogen/generated/shared/c++/Options.hpp +8 -8
- package/nitrogen/generated/shared/c++/{NativeAndroidSetupOptions.hpp → PartialAndroidSetupPlayerOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/{NativeIOSSetupOptions.hpp → PartialIOSSetupPlayerOptions.hpp} +11 -11
- package/nitrogen/generated/shared/c++/PartialSetupPlayerOptions.hpp +104 -0
- package/nitrogen/generated/shared/c++/{FavoriteConfig.hpp → PercentageRating.hpp} +17 -18
- package/nitrogen/generated/shared/c++/PlaybackError.hpp +3 -18
- package/nitrogen/generated/shared/c++/PlaybackState.hpp +9 -9
- package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
- package/nitrogen/generated/shared/c++/RatingType.hpp +96 -0
- package/nitrogen/generated/shared/c++/RemoteSetRatingEvent.hpp +94 -0
- package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +5 -24
- package/nitrogen/generated/shared/c++/RetryConfig.hpp +6 -10
- package/nitrogen/generated/shared/c++/SearchMode.hpp +15 -39
- package/nitrogen/generated/shared/c++/SearchParams.hpp +2 -9
- package/nitrogen/generated/shared/c++/{Gate.hpp → StarRating.hpp} +16 -21
- package/nitrogen/generated/shared/c++/{GateDecision.hpp → ThumbsRating.hpp} +17 -23
- package/nitrogen/generated/shared/c++/Track.hpp +5 -24
- package/nitrogen/generated/shared/c++/TransformableRequestConfig.hpp +2 -6
- package/nitrogen/generated/shared/c++/UpdateOptions.hpp +111 -0
- package/package.json +5 -12
- package/src/AudioBrowser.ts +1 -4
- package/src/features/browser.ts +201 -58
- package/src/features/equalizer.ts +1 -1
- package/src/features/errors.ts +5 -71
- package/src/features/favorites.ts +1 -1
- package/src/features/index.ts +0 -2
- package/src/features/metadata.ts +15 -9
- package/src/features/network.ts +1 -1
- package/src/features/nowPlaying.ts +1 -37
- package/src/features/output.ts +22 -35
- package/src/features/playback/controls.ts +0 -8
- package/src/features/playback/index.ts +0 -1
- package/src/features/playback/playWhenReady.ts +1 -1
- package/src/features/playback/playing.ts +1 -1
- package/src/features/playback/progress.ts +13 -32
- package/src/features/playback/state.ts +1 -1
- package/src/features/playback/volume.ts +2 -2
- package/src/features/player/options.ts +110 -217
- package/src/features/player/setup.ts +175 -397
- package/src/features/queue/activeTrack.ts +1 -1
- package/src/features/queue/queue.ts +6 -17
- package/src/features/queue/repeatMode.ts +1 -1
- package/src/features/queue/shuffle.ts +1 -1
- package/src/features/rating.ts +17 -0
- package/src/features/remoteControls.ts +115 -13
- package/src/features/sleepTimer.ts +5 -24
- package/src/native.ts +0 -5
- package/src/native.web.ts +1 -4
- package/src/specs/audio-browser.nitro.ts +41 -131
- package/src/types/browser-native.ts +6 -19
- package/src/types/browser-nodes.ts +13 -160
- package/src/types/browser.ts +89 -377
- package/src/types/player.ts +69 -0
- package/src/utils/LazyNativeEmitter.ts +8 -9
- package/src/utils/NativeUpdatedValue.ts +2 -0
- package/src/web/NativeAudioBrowser.ts +158 -365
- package/src/web/TrackPlayer/Player.ts +39 -113
- package/src/web/TrackPlayer/PlaylistPlayer.ts +346 -0
- package/src/web/TrackPlayer/RepeatMode.ts +1 -1
- package/src/web/TrackPlayer/SleepTimer.ts +3 -38
- package/src/web/TrackPlayer/index.ts +1 -2
- package/src/web/browser/BrowserManager.ts +62 -231
- package/src/web/browser/NavigationErrorManager.ts +1 -3
- package/src/web/browser/SearchManager.ts +15 -11
- package/src/web/http/HttpClient.ts +35 -5
- package/src/web/http/RequestConfigBuilder.ts +63 -120
- package/src/web/player/NowPlayingManager.ts +5 -6
- package/src/web/player/OptionsManager.ts +18 -12
- package/android/src/main/java/com/audiobrowser/browser/ArtworkResolutionRegistry.kt +0 -37
- package/android/src/main/java/com/audiobrowser/browser/BrowseArtworkRegistry.kt +0 -34
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +0 -299
- package/android/src/main/java/com/audiobrowser/browser/JsonArtwork.kt +0 -79
- package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +0 -42
- package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +0 -35
- package/android/src/main/java/com/audiobrowser/player/CapabilityControls.kt +0 -172
- package/android/src/main/java/com/audiobrowser/player/EqualizerController.kt +0 -153
- package/android/src/main/java/com/audiobrowser/player/InterceptingPlayer.kt +0 -141
- package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +0 -347
- package/android/src/main/java/com/audiobrowser/player/PlaybackErrorClassifier.kt +0 -129
- package/android/src/main/java/com/audiobrowser/player/PlaybackStateMachine.kt +0 -85
- package/android/src/main/java/com/audiobrowser/player/PlaybackTimer.kt +0 -56
- package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +0 -20
- package/android/src/main/java/com/audiobrowser/player/PlayerEngineFactory.kt +0 -130
- package/android/src/main/java/com/audiobrowser/player/SleepTimerManager.kt +0 -80
- package/android/src/main/java/com/audiobrowser/player/StuckRecoveryPolicy.kt +0 -50
- package/android/src/main/java/com/audiobrowser/player/VolumeFader.kt +0 -81
- package/android/src/main/java/com/audiobrowser/tls/AiaCertChaser.kt +0 -141
- package/android/src/main/java/com/audiobrowser/tls/AiaChasingTrustManager.kt +0 -37
- package/android/src/main/java/com/audiobrowser/tls/AiaTls.kt +0 -41
- package/android/src/main/java/com/audiobrowser/tls/CachingCertificateFetcher.kt +0 -209
- package/android/src/main/java/com/audiobrowser/util/ArtworkContentProvider.kt +0 -161
- package/android/src/main/java/com/audiobrowser/util/ArtworkUris.kt +0 -28
- package/android/src/main/java/com/audiobrowser/util/ArtworkVariant.kt +0 -24
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoader.kt +0 -49
- package/android/src/main/java/com/audiobrowser/util/CoilArtworkLoaderHolder.kt +0 -36
- package/android/src/main/java/com/audiobrowser/util/OutputMonitor.kt +0 -211
- package/android/src/main/java/com/audiobrowser/util/OutputSwitcher.kt +0 -58
- package/android/src/main/java/com/audiobrowser/util/RatingFavorites.kt +0 -14
- package/android/src/test/java/com/audiobrowser/TestFixtures.kt +0 -165
- package/android/src/test/java/com/audiobrowser/browser/ArtworkResolutionRegistryTest.kt +0 -46
- package/android/src/test/java/com/audiobrowser/browser/BrowseArtworkRegistryTest.kt +0 -39
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerBuildApiRequestTest.kt +0 -114
- package/android/src/test/java/com/audiobrowser/browser/BrowserManagerLayerResolutionTest.kt +0 -141
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +0 -177
- package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +0 -55
- package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +0 -94
- package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +0 -203
- package/android/src/test/java/com/audiobrowser/player/DynamicLoadControlTest.kt +0 -56
- package/android/src/test/java/com/audiobrowser/player/EqualizerControllerTest.kt +0 -216
- package/android/src/test/java/com/audiobrowser/player/MediaSessionSlotMappingTest.kt +0 -45
- package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +0 -442
- package/android/src/test/java/com/audiobrowser/player/PlaybackErrorClassifierTest.kt +0 -190
- package/android/src/test/java/com/audiobrowser/player/PlaybackStateMachineTest.kt +0 -198
- package/android/src/test/java/com/audiobrowser/player/PlaybackTimerTest.kt +0 -137
- package/android/src/test/java/com/audiobrowser/player/PlayerEngineFactoryTest.kt +0 -36
- package/android/src/test/java/com/audiobrowser/player/RetryLoadErrorHandlingPolicyTest.kt +0 -268
- package/android/src/test/java/com/audiobrowser/player/SessionErrorMaskTest.kt +0 -68
- package/android/src/test/java/com/audiobrowser/player/StuckRecoveryPolicyTest.kt +0 -40
- package/android/src/test/java/com/audiobrowser/player/TransformingDataSourceTest.kt +0 -193
- package/android/src/test/java/com/audiobrowser/tls/AiaCertChaserTest.kt +0 -107
- package/android/src/test/java/com/audiobrowser/tls/AiaChasingTrustManagerTest.kt +0 -73
- package/android/src/test/java/com/audiobrowser/tls/CachingCertificateFetcherTest.kt +0 -75
- package/android/src/test/java/com/audiobrowser/tls/CertFixtures.kt +0 -18
- package/android/src/test/java/com/audiobrowser/util/ArtworkContentProviderTest.kt +0 -163
- package/android/src/test/java/com/audiobrowser/util/ArtworkUrisTest.kt +0 -34
- package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +0 -28
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderHolderTest.kt +0 -53
- package/android/src/test/java/com/audiobrowser/util/CoilArtworkLoaderTest.kt +0 -58
- package/android/src/test/java/com/audiobrowser/util/FakeImageLoader.kt +0 -57
- package/android/src/test/java/com/audiobrowser/util/PlayingStateFactoryTest.kt +0 -40
- package/android/src/test/java/com/audiobrowser/util/RatingFavoritesTest.kt +0 -34
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryBrowseTest.kt +0 -50
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +0 -63
- package/android/src/test/java/com/audiobrowser/util/TrackFactoryRatingTest.kt +0 -35
- package/android/src/test/resources/certs/isrg-root-x1.pem +0 -31
- package/android/src/test/resources/certs/letsencrypt-r13.der +0 -0
- package/android/src/test/resources/certs/letsencrypt-r13.pem +0 -29
- package/android/src/test/resources/certs/stationplaylist-leaf.pem +0 -34
- package/ios/Browser/MediaResolveComposer.swift +0 -114
- package/ios/Browser/SearchDrillIn.swift +0 -44
- package/ios/Browser/SectionScope.swift +0 -35
- package/ios/Browser/TabBarEntries.swift +0 -15
- package/ios/CarPlay/CPInterfaceController+Safe.swift +0 -126
- package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
- package/ios/CarPlay/RNABMediaAddHandler.swift +0 -75
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +0 -69
- package/ios/Extension/PlayerCapabilities+Favorite.swift +0 -20
- package/ios/Extension/RequestConfig+Copying.swift +0 -31
- package/ios/Extension/Variant+Artwork.swift +0 -49
- package/ios/NowPlayingInfo/SilentAudioPrimer.swift +0 -73
- package/ios/Player/AirPlayStallJudgement.swift +0 -25
- package/ios/Player/EndOfTrackJudgement.swift +0 -32
- package/ios/Player/MediaIntentCriteria.swift +0 -139
- package/ios/Player/PersistedPlaybackState.swift +0 -11
- package/ios/Player/PlaybackStateStore.swift +0 -27
- package/ios/Player/PlaybackTimer.swift +0 -61
- package/ios/Player/VolumeFader.swift +0 -77
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +0 -47
- package/ios/Tests/AirPlayStallJudgementTests.swift +0 -52
- package/ios/Tests/EndOfTrackJudgementTests.swift +0 -35
- package/ios/Tests/JsonModelsTests.swift +0 -58
- package/ios/Tests/MediaIntentCriteriaTests.swift +0 -170
- package/ios/Tests/MediaLoaderAssetOptionsTests.swift +0 -46
- package/ios/Tests/MediaResolveTests.swift +0 -126
- package/ios/Tests/OnceValueTests.swift +0 -79
- package/ios/Tests/PlaybackStateStoreTests.swift +0 -48
- package/ios/Tests/PlaybackTimerTests.swift +0 -55
- package/ios/Tests/PlayingStateManagerTests.swift +0 -57
- package/ios/Tests/RemoteCommandControllerTests.swift +0 -156
- package/ios/Tests/RemoteCommandTests.swift +0 -67
- package/ios/Tests/RetryManagerTests.swift +0 -89
- package/ios/Tests/SearchDrillInTests.swift +0 -63
- package/ios/Tests/SectionScopeTests.swift +0 -82
- package/ios/Tests/SleepTimerManagerTests.swift +0 -107
- package/ios/Tests/Support/ManualSleepTimerScheduler.swift +0 -47
- package/ios/Tests/TabBarEntriesTests.swift +0 -71
- package/lib/commonjs/features/browser-config.js +0 -227
- package/lib/commonjs/features/browser-config.js.map +0 -1
- package/lib/commonjs/features/carConnection.js +0 -42
- package/lib/commonjs/features/carConnection.js.map +0 -1
- package/lib/commonjs/features/gate.js +0 -149
- package/lib/commonjs/features/gate.js.map +0 -1
- package/lib/commonjs/features/playback/trackPlaybackTime.js +0 -86
- package/lib/commonjs/features/playback/trackPlaybackTime.js.map +0 -1
- package/lib/commonjs/features/player/validateOptions.js +0 -24
- package/lib/commonjs/features/player/validateOptions.js.map +0 -1
- package/lib/commonjs/utils/artwork.js +0 -31
- package/lib/commonjs/utils/artwork.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js +0 -71
- package/lib/commonjs/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js +0 -34
- package/lib/commonjs/web/TrackPlayer/PlaybackTimer.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +0 -21
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/QueueManager.js +0 -273
- package/lib/commonjs/web/TrackPlayer/QueueManager.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js +0 -158
- package/lib/commonjs/web/TrackPlayer/QueuePlayer.js.map +0 -1
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js +0 -75
- package/lib/commonjs/web/TrackPlayer/VolumeFader.js.map +0 -1
- package/lib/commonjs/web/browser/assertBrowsePageShape.js +0 -22
- package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +0 -1
- package/lib/commonjs/web/browser/classifyTrackNavigation.js +0 -41
- package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +0 -1
- package/lib/commonjs/web/browser/parseSearchResponse.js +0 -20
- package/lib/commonjs/web/browser/parseSearchResponse.js.map +0 -1
- package/lib/commonjs/web/playbackErrorKind.js +0 -57
- package/lib/commonjs/web/playbackErrorKind.js.map +0 -1
- package/lib/commonjs/web/player/MediaSessionManager.js +0 -121
- package/lib/commonjs/web/player/MediaSessionManager.js.map +0 -1
- package/lib/commonjs/web/player/RemoteCommandController.js +0 -96
- package/lib/commonjs/web/player/RemoteCommandController.js.map +0 -1
- package/lib/module/features/browser-config.js +0 -221
- package/lib/module/features/browser-config.js.map +0 -1
- package/lib/module/features/carConnection.js +0 -37
- package/lib/module/features/carConnection.js.map +0 -1
- package/lib/module/features/gate.js +0 -144
- package/lib/module/features/gate.js.map +0 -1
- package/lib/module/features/playback/trackPlaybackTime.js +0 -83
- package/lib/module/features/playback/trackPlaybackTime.js.map +0 -1
- package/lib/module/features/player/validateOptions.js +0 -20
- package/lib/module/features/player/validateOptions.js.map +0 -1
- package/lib/module/utils/artwork.js +0 -26
- package/lib/module/utils/artwork.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js +0 -67
- package/lib/module/web/TrackPlayer/PlaybackStateMachine.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlaybackTimer.js +0 -29
- package/lib/module/web/TrackPlayer/PlaybackTimer.js.map +0 -1
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js +0 -17
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js.map +0 -1
- package/lib/module/web/TrackPlayer/QueueManager.js +0 -269
- package/lib/module/web/TrackPlayer/QueueManager.js.map +0 -1
- package/lib/module/web/TrackPlayer/QueuePlayer.js +0 -154
- package/lib/module/web/TrackPlayer/QueuePlayer.js.map +0 -1
- package/lib/module/web/TrackPlayer/VolumeFader.js +0 -70
- package/lib/module/web/TrackPlayer/VolumeFader.js.map +0 -1
- package/lib/module/web/browser/assertBrowsePageShape.js +0 -18
- package/lib/module/web/browser/assertBrowsePageShape.js.map +0 -1
- package/lib/module/web/browser/classifyTrackNavigation.js +0 -38
- package/lib/module/web/browser/classifyTrackNavigation.js.map +0 -1
- package/lib/module/web/browser/parseSearchResponse.js +0 -16
- package/lib/module/web/browser/parseSearchResponse.js.map +0 -1
- package/lib/module/web/playbackErrorKind.js +0 -52
- package/lib/module/web/playbackErrorKind.js.map +0 -1
- package/lib/module/web/player/MediaSessionManager.js +0 -116
- package/lib/module/web/player/MediaSessionManager.js.map +0 -1
- package/lib/module/web/player/RemoteCommandController.js +0 -92
- package/lib/module/web/player/RemoteCommandController.js.map +0 -1
- package/lib/typescript/src/features/browser-config.d.ts +0 -12
- package/lib/typescript/src/features/browser-config.d.ts.map +0 -1
- package/lib/typescript/src/features/carConnection.d.ts +0 -20
- package/lib/typescript/src/features/carConnection.d.ts.map +0 -1
- package/lib/typescript/src/features/gate.d.ts +0 -78
- package/lib/typescript/src/features/gate.d.ts.map +0 -1
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +0 -34
- package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +0 -1
- package/lib/typescript/src/features/player/validateOptions.d.ts +0 -2
- package/lib/typescript/src/features/player/validateOptions.d.ts.map +0 -1
- package/lib/typescript/src/utils/artwork.d.ts +0 -19
- package/lib/typescript/src/utils/artwork.d.ts.map +0 -1
- package/nitrogen/generated/android/c++/JArtworkVariants.hpp +0 -61
- package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
- package/nitrogen/generated/android/c++/JFavoritesMatchMode.hpp +0 -58
- package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +0 -100
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.hpp +0 -112
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.hpp +0 -121
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +0 -123
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +0 -133
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +0 -121
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.hpp +0 -139
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.hpp +0 -134
- package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +0 -131
- package/nitrogen/generated/android/c++/JFunc_void_GateEvent.hpp +0 -79
- package/nitrogen/generated/android/c++/JFunc_void_Output.hpp +0 -80
- package/nitrogen/generated/android/c++/JGate.hpp +0 -62
- package/nitrogen/generated/android/c++/JGateDecision.hpp +0 -64
- package/nitrogen/generated/android/c++/JGateReason.hpp +0 -58
- package/nitrogen/generated/android/c++/JIOSOptions.hpp +0 -95
- package/nitrogen/generated/android/c++/JMediaReference.hpp +0 -58
- package/nitrogen/generated/android/c++/JNativeGateRequest.hpp +0 -74
- package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +0 -182
- package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +0 -96
- package/nitrogen/generated/android/c++/JOutputType.hpp +0 -82
- package/nitrogen/generated/android/c++/JPlaybackErrorKind.hpp +0 -76
- package/nitrogen/generated/android/c++/JRemoteButtonLayout.hpp +0 -86
- package/nitrogen/generated/android/c++/JStallReason.hpp +0 -58
- package/nitrogen/generated/android/c++/JTrackRequest.hpp +0 -93
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_NullType_RemoteButtonLayout.hpp +0 -75
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.cpp +0 -41
- package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +0 -121
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.cpp +0 -26
- package/nitrogen/generated/android/c++/JVariant_String_ArtworkVariants.hpp +0 -70
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/FormatNowPlayingParams.kt +0 -50
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_RequestConfig___Track.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__optional_std__string____Track.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.kt +0 -80
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/MediaReference.kt +0 -23
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeGateRequest.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeSetupPlayerOptions.kt +0 -66
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/OutputType.kt +0 -31
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlaybackErrorKind.kt +0 -29
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButton.kt +0 -26
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/RemoteButtonLayout.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StallReason.kt +0 -23
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackRequest.kt +0 -44
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.kt +0 -65
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_String_ArtworkVariants.kt +0 -53
- package/nitrogen/generated/ios/swift/ArtworkVariants.swift +0 -34
- package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
- package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
- package/nitrogen/generated/ios/swift/FormatNowPlayingParams.swift +0 -67
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___MediaTransformParams.swift +0 -54
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___RequestConfig_std__optional_std__unordered_map_std__string__std__string__.swift +0 -69
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_RequestConfig___Track.swift +0 -54
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.swift +0 -60
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__optional_std__string____Track.swift +0 -60
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_GateDecision_____NativeGateRequest.swift +0 -61
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_TransformableRequestConfig____.swift +0 -61
- package/nitrogen/generated/ios/swift/Func_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.swift +0 -77
- package/nitrogen/generated/ios/swift/Func_void_GateDecision.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_TransformableRequestConfig.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__optional_NowPlayingUpdate_.swift +0 -46
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +0 -53
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_Promise_TransformableRequestConfig__.swift +0 -66
- package/nitrogen/generated/ios/swift/Func_void_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError____.swift +0 -81
- package/nitrogen/generated/ios/swift/Gate.swift +0 -47
- package/nitrogen/generated/ios/swift/GateDecision.swift +0 -40
- package/nitrogen/generated/ios/swift/GateEvent.swift +0 -29
- package/nitrogen/generated/ios/swift/GateReason.swift +0 -40
- package/nitrogen/generated/ios/swift/IOSOptions.swift +0 -51
- package/nitrogen/generated/ios/swift/MediaReference.swift +0 -40
- package/nitrogen/generated/ios/swift/NativeGateRequest.swift +0 -58
- package/nitrogen/generated/ios/swift/NativeSetupPlayerOptions.swift +0 -206
- package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +0 -90
- package/nitrogen/generated/ios/swift/OutputType.swift +0 -72
- package/nitrogen/generated/ios/swift/PlaybackErrorKind.swift +0 -64
- package/nitrogen/generated/ios/swift/RemoteButtonLayout.swift +0 -57
- package/nitrogen/generated/ios/swift/StallReason.swift +0 -40
- package/nitrogen/generated/ios/swift/TrackRequest.swift +0 -106
- package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
- package/nitrogen/generated/ios/swift/Variant_ResolvedTrack_BrowseError_Promise_BrowseResult_.swift +0 -19
- package/nitrogen/generated/ios/swift/Variant_String_ArtworkVariants.swift +0 -18
- package/nitrogen/generated/shared/c++/ArtworkVariants.hpp +0 -87
- package/nitrogen/generated/shared/c++/FavoritesMatchMode.hpp +0 -76
- package/nitrogen/generated/shared/c++/FormatNowPlayingParams.hpp +0 -110
- package/nitrogen/generated/shared/c++/GateReason.hpp +0 -76
- package/nitrogen/generated/shared/c++/IOSOptions.hpp +0 -91
- package/nitrogen/generated/shared/c++/MediaReference.hpp +0 -76
- package/nitrogen/generated/shared/c++/NativeGateRequest.hpp +0 -97
- package/nitrogen/generated/shared/c++/NativeSetupPlayerOptions.hpp +0 -138
- package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +0 -92
- package/nitrogen/generated/shared/c++/OutputType.hpp +0 -108
- package/nitrogen/generated/shared/c++/PlaybackErrorKind.hpp +0 -100
- package/nitrogen/generated/shared/c++/RemoteButtonLayout.hpp +0 -94
- package/nitrogen/generated/shared/c++/StallReason.hpp +0 -76
- package/nitrogen/generated/shared/c++/TrackRequest.hpp +0 -93
- package/src/codegen-no-config-variant.test.ts +0 -70
- package/src/features/browser-config.test.ts +0 -110
- package/src/features/browser-config.ts +0 -293
- package/src/features/browser.test.ts +0 -19
- package/src/features/carConnection.ts +0 -36
- package/src/features/gate.test.ts +0 -72
- package/src/features/gate.ts +0 -155
- package/src/features/playback/trackPlaybackTime.test.ts +0 -80
- package/src/features/playback/trackPlaybackTime.ts +0 -101
- package/src/features/player/options.test.ts +0 -38
- package/src/features/player/setup.test.ts +0 -125
- package/src/features/player/validateOptions.ts +0 -22
- package/src/features/queue/queue.test.ts +0 -36
- package/src/public-surface.test.ts +0 -116
- package/src/utils/artwork.ts +0 -30
- package/src/web/NativeAudioBrowser.playWhenReady.test.ts +0 -301
- package/src/web/TrackPlayer/PlaybackStateMachine.test.ts +0 -139
- package/src/web/TrackPlayer/PlaybackStateMachine.ts +0 -82
- package/src/web/TrackPlayer/PlaybackTimer.test.ts +0 -67
- package/src/web/TrackPlayer/PlaybackTimer.ts +0 -33
- package/src/web/TrackPlayer/Player.dispatch.test.ts +0 -229
- package/src/web/TrackPlayer/Player.load.test.ts +0 -86
- package/src/web/TrackPlayer/PlayingStateFactory.test.ts +0 -51
- package/src/web/TrackPlayer/PlayingStateFactory.ts +0 -24
- package/src/web/TrackPlayer/QueueManager.test.ts +0 -289
- package/src/web/TrackPlayer/QueueManager.ts +0 -302
- package/src/web/TrackPlayer/QueuePlayer.ts +0 -188
- package/src/web/TrackPlayer/VolumeFader.ts +0 -74
- package/src/web/browser/__tests__/layer-resolver.test.ts +0 -69
- package/src/web/browser/assertBrowsePageShape.test.ts +0 -27
- package/src/web/browser/assertBrowsePageShape.ts +0 -27
- package/src/web/browser/classifyTrackNavigation.test.ts +0 -41
- package/src/web/browser/classifyTrackNavigation.ts +0 -35
- package/src/web/browser/parseSearchResponse.test.ts +0 -25
- package/src/web/browser/parseSearchResponse.ts +0 -15
- package/src/web/http/RequestConfigBuilder.test.ts +0 -59
- package/src/web/playbackErrorKind.test.ts +0 -55
- package/src/web/playbackErrorKind.ts +0 -56
- package/src/web/player/MediaSessionManager.test.ts +0 -220
- package/src/web/player/MediaSessionManager.ts +0 -151
- package/src/web/player/NowPlayingManager.test.ts +0 -46
- package/src/web/player/RemoteCommandController.test.ts +0 -171
- package/src/web/player/RemoteCommandController.ts +0 -166
|
@@ -24,45 +24,19 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
24
24
|
/// Shared instance for CarPlay access. Set when HybridAudioBrowser is created.
|
|
25
25
|
private(set) nonisolated(unsafe) weak static var shared: HybridAudioBrowser?
|
|
26
26
|
|
|
27
|
-
/// Fires when a new instance becomes `shared` (e.g. on a JS runtime reload),
|
|
28
|
-
/// so connected external controllers (CarPlay) can re-subscribe — their
|
|
29
|
-
/// listeners sit on the previous instance's emitters and would otherwise go
|
|
30
|
-
/// silent.
|
|
31
|
-
static let instanceChangedEmitter = Emitter<Void>()
|
|
32
|
-
|
|
33
27
|
// MARK: - Private Properties
|
|
34
28
|
|
|
35
29
|
private var player: TrackPlayer?
|
|
36
30
|
private let networkMonitor = NetworkMonitor()
|
|
37
|
-
private let playbackStateStore = PlaybackStateStore()
|
|
38
31
|
let browserManager = BrowserManager()
|
|
39
32
|
private let trackSelector: TrackSelector
|
|
40
33
|
private var volumeObservation: NSKeyValueObservation?
|
|
41
34
|
private var routeChangeObserver: NSObjectProtocol?
|
|
42
|
-
private var interruptionObserver: NSObjectProtocol?
|
|
43
|
-
private var mediaServicesResetObserver: NSObjectProtocol?
|
|
44
|
-
/// Re-applies our resolved audio-session category. Captured at setup so a
|
|
45
|
-
/// media-services reset (which clears the category) can restore it.
|
|
46
|
-
private var applyAudioSessionCategory: () -> Void = {}
|
|
47
35
|
private var nowPlayingOverride: NowPlayingUpdate?
|
|
48
|
-
/// When false, the now-playing surface uses the raw track fields (override + formatter ignored).
|
|
49
|
-
private var nowPlayingMetadataEnabled = true
|
|
50
|
-
/// Customizes the now-playing title/subtitle from the track + live timed metadata + playback state.
|
|
51
|
-
private var nowPlayingMetadataFormatter: ((_ params: FormatNowPlayingParams) -> Promise<NowPlayingUpdate?>)?
|
|
52
|
-
/// Keep the media session controllable through a terminal error (see `keepSessionAliveOnError`).
|
|
53
|
-
private var keepSessionAliveOnError = true
|
|
54
|
-
/// Initial player state staged before the player exists — from setup options or the imperative
|
|
55
|
-
/// setters called pre-setup. Strict last-write-wins; consumed when the player comes up.
|
|
56
|
-
private var pendingPlayWhenReady: Bool?
|
|
57
|
-
private var pendingRepeatMode: RepeatMode?
|
|
58
|
-
/// Latest live timed (ICY/ID3) metadata, passed to the formatter. Cleared on track change.
|
|
59
|
-
private var latestTimedMetadata: TimedMetadata?
|
|
60
36
|
private let playerOptions = PlayerUpdateOptions()
|
|
61
37
|
|
|
62
38
|
/// Configured playback rates for the playback-rate capability (for CarPlay rate cycling)
|
|
63
39
|
var playbackRates: [Double] { playerOptions.playbackRates }
|
|
64
|
-
var carPlayUpNextButton: Bool { playerOptions.carPlayUpNextButton }
|
|
65
|
-
var carPlayNowPlayingButtons: [CarPlayNowPlayingButton] { playerOptions.carPlayNowPlayingButtons }
|
|
66
40
|
private var lastNavigationError: NavigationError? {
|
|
67
41
|
didSet {
|
|
68
42
|
// Skip if both nil (no real change)
|
|
@@ -89,21 +63,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
89
63
|
public let queueChangedEmitter = Emitter<[Track]>()
|
|
90
64
|
public let navigationErrorEmitter = Emitter<NavigationErrorEvent>()
|
|
91
65
|
public let repeatModeChangedEmitter = Emitter<RepeatModeChangedEvent>()
|
|
92
|
-
public let shuffleChangedEmitter = Emitter<Bool>()
|
|
93
66
|
public let externalContentChangedEmitter = Emitter<String>()
|
|
94
|
-
/// Signals that the gate's active state changed (set or cleared). Carries
|
|
95
|
-
/// the active flag; per-request chrome is obtained at each serve site via
|
|
96
|
-
/// `gateDecision(for:)`, so the emitter only tells surfaces to re-render.
|
|
97
|
-
public let gateChangedEmitter = Emitter<Bool>()
|
|
98
|
-
/// Fired when a voice media intent (`handlePlayMediaIntent`) successfully
|
|
99
|
-
/// starts playback. CarPlay surfaces the Now Playing template in response, so
|
|
100
|
-
/// the user lands on the playing station (with the rest of the results in Up
|
|
101
|
-
/// Next) rather than the screen they invoked Siri from. No-op where unobserved.
|
|
102
|
-
public let showNowPlayingRequestedEmitter = Emitter<Void>()
|
|
103
|
-
/// Fires after `updateOptions` applies a change, so external-surface managers
|
|
104
|
-
/// (CarPlay) can refresh UI driven by now-runtime-updatable options
|
|
105
|
-
/// (now-playing buttons, Up Next).
|
|
106
|
-
public let playerOptionsChangedEmitter = Emitter<Void>()
|
|
107
67
|
|
|
108
68
|
// MARK: - Thread Safety
|
|
109
69
|
|
|
@@ -111,9 +71,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
111
71
|
/// because DispatchQueue.main.sync blocks the caller — no concurrent access window.
|
|
112
72
|
private func onMainActor<T>(_ work: @MainActor () -> T) -> T {
|
|
113
73
|
if Thread.isMainThread {
|
|
114
|
-
MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
74
|
+
return MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
115
75
|
} else {
|
|
116
|
-
DispatchQueue.main.sync {
|
|
76
|
+
return DispatchQueue.main.sync {
|
|
117
77
|
MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
118
78
|
}
|
|
119
79
|
}
|
|
@@ -121,10 +81,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
121
81
|
|
|
122
82
|
private func onMainActor<T>(_ work: @MainActor () throws -> T) throws -> T {
|
|
123
83
|
if Thread.isMainThread {
|
|
124
|
-
try MainActor.assumeIsolated {
|
|
84
|
+
return try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
|
|
125
85
|
} else {
|
|
126
|
-
try DispatchQueue.main.sync {
|
|
127
|
-
try MainActor.assumeIsolated {
|
|
86
|
+
return try DispatchQueue.main.sync {
|
|
87
|
+
try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
|
|
128
88
|
}
|
|
129
89
|
}
|
|
130
90
|
}
|
|
@@ -151,9 +111,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
151
111
|
}
|
|
152
112
|
|
|
153
113
|
public var configuration: NativeBrowserConfiguration = .init(
|
|
154
|
-
path: nil, request: nil,
|
|
155
|
-
singleTrack: nil, handleTrackLoad: nil,
|
|
156
|
-
|
|
114
|
+
path: nil, request: nil, media: nil, artwork: nil, routes: nil,
|
|
115
|
+
singleTrack: nil, handleTrackLoad: nil, androidControllerOfflineError: nil,
|
|
116
|
+
carPlayUpNextButton: nil, carPlayNowPlayingButtons: nil, formatNavigationError: nil,
|
|
157
117
|
) {
|
|
158
118
|
didSet {
|
|
159
119
|
onMainActor { browserManager.config = BrowserConfig(from: configuration) }
|
|
@@ -189,25 +149,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
189
149
|
public var onNavigationError: (NavigationErrorEvent) -> Void = { _ in }
|
|
190
150
|
public var onFormattedNavigationError: (FormattedNavigationError?) -> Void = { _ in }
|
|
191
151
|
|
|
192
|
-
/// Per-request gate decision, set by JS. Native calls it at a serve site to
|
|
193
|
-
/// learn whether a browse path / search interaction should be gated and with
|
|
194
|
-
/// what chrome. The double `Promise` mirrors every other native→JS
|
|
195
|
-
/// value-returning callback (e.g. `searchCallback`): the outer resolves to the
|
|
196
|
-
/// JS-side Promise, the inner to the decision.
|
|
197
|
-
///
|
|
198
|
-
/// Default fails CLOSED (`gated: true`). This default is only consulted in the
|
|
199
|
-
/// init window where `hasResolver` was already recorded true but JS hasn't
|
|
200
|
-
/// (re)bound the real resolver yet (JS reload / instance churn re-seeding gate
|
|
201
|
-
/// state, e.g. CarPlay re-seeding `isGated` at scene connect). Serving content
|
|
202
|
-
/// in that window would be a fail-open leak — same direction as the
|
|
203
|
-
/// resolver-error path in `gateDecision`. Matches Android's default.
|
|
204
|
-
public var resolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> = { _ in
|
|
205
|
-
Promise.resolved(withResult: Promise.resolved(withResult: GateDecision(gated: true, gate: nil)))
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/// Fired when a request is gated (served the gate). Set by JS.
|
|
209
|
-
public var onGate: (GateEvent) -> Void = { _ in }
|
|
210
|
-
|
|
211
152
|
// MARK: - Player Callbacks
|
|
212
153
|
|
|
213
154
|
public var onChapterMetadata: ([ChapterMetadata]) -> Void = { _ in }
|
|
@@ -218,7 +159,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
218
159
|
public var onPlaybackPlayWhenReadyChanged: (PlaybackPlayWhenReadyChangedEvent) -> Void = { _ in }
|
|
219
160
|
public var onPlaybackPlayingState: (PlayingState) -> Void = { _ in }
|
|
220
161
|
public var onPlaybackProgressUpdated: (PlaybackProgressUpdatedEvent) -> Void = { _ in }
|
|
221
|
-
public var onPlaybackInterval: () -> Void = {}
|
|
222
162
|
public var onPlaybackQueueEnded: (PlaybackQueueEndedEvent) -> Void = { _ in }
|
|
223
163
|
public var onPlaybackQueueChanged: ([Track]) -> Void = { _ in }
|
|
224
164
|
public var onPlaybackRepeatModeChanged: (RepeatModeChangedEvent) -> Void = { _ in }
|
|
@@ -228,8 +168,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
228
168
|
|
|
229
169
|
// MARK: - Remote Callbacks
|
|
230
170
|
|
|
171
|
+
public var onRemoteBookmark: () -> Void = {}
|
|
172
|
+
public var onRemoteDislike: () -> Void = {}
|
|
231
173
|
public var onRemoteJumpBackward: (RemoteJumpBackwardEvent) -> Void = { _ in }
|
|
232
174
|
public var onRemoteJumpForward: (RemoteJumpForwardEvent) -> Void = { _ in }
|
|
175
|
+
public var onRemoteLike: () -> Void = {}
|
|
233
176
|
public var onRemoteNext: () -> Void = {}
|
|
234
177
|
public var onRemotePause: () -> Void = {}
|
|
235
178
|
public var onRemotePlay: () -> Void = {}
|
|
@@ -237,13 +180,17 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
237
180
|
public var onRemotePlaySearch: (RemotePlaySearchEvent) -> Void = { _ in }
|
|
238
181
|
public var onRemotePrevious: () -> Void = {}
|
|
239
182
|
public var onRemoteSeek: (RemoteSeekEvent) -> Void = { _ in }
|
|
183
|
+
public var onRemoteSetRating: (RemoteSetRatingEvent) -> Void = { _ in }
|
|
240
184
|
public var onRemoteSkip: (RemoteSkipEvent) -> Void = { _ in }
|
|
241
185
|
public var onRemoteStop: () -> Void = {}
|
|
242
186
|
|
|
243
187
|
// MARK: - Remote Handlers (optional overrides from JS)
|
|
244
188
|
|
|
189
|
+
public var handleRemoteBookmark: (() -> Void)?
|
|
190
|
+
public var handleRemoteDislike: (() -> Void)?
|
|
245
191
|
public var handleRemoteJumpBackward: ((RemoteJumpBackwardEvent) -> Void)?
|
|
246
192
|
public var handleRemoteJumpForward: ((RemoteJumpForwardEvent) -> Void)?
|
|
193
|
+
public var handleRemoteLike: (() -> Void)?
|
|
247
194
|
public var handleRemoteNext: (() -> Void)?
|
|
248
195
|
public var handleRemotePause: (() -> Void)?
|
|
249
196
|
public var handleRemotePlay: (() -> Void)?
|
|
@@ -261,9 +208,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
261
208
|
public var onNowPlayingChanged: (NowPlayingMetadata) -> Void = { _ in }
|
|
262
209
|
public var onOnlineChanged: (Bool) -> Void = { _ in } {
|
|
263
210
|
didSet {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
211
|
+
networkMonitor.onChanged = { [weak self] isOnline in
|
|
212
|
+
self?.onOnlineChanged(isOnline)
|
|
213
|
+
}
|
|
214
|
+
// Immediately notify current state
|
|
267
215
|
onOnlineChanged(networkMonitor.isOnline)
|
|
268
216
|
}
|
|
269
217
|
}
|
|
@@ -277,7 +225,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
277
225
|
}
|
|
278
226
|
}
|
|
279
227
|
|
|
280
|
-
public var
|
|
228
|
+
public var onIosOutputChanged: (IosOutput) -> Void = { _ in } {
|
|
281
229
|
didSet {
|
|
282
230
|
setupRouteChangeObserver()
|
|
283
231
|
}
|
|
@@ -297,11 +245,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
297
245
|
HybridAudioBrowser.shared?.player?.destroy()
|
|
298
246
|
playerAndConfiguredBrowser.reset()
|
|
299
247
|
HybridAudioBrowser.shared = self
|
|
300
|
-
// Tell connected external controllers (CarPlay) to re-subscribe against
|
|
301
|
-
// this instance. Unconditional: `shared` is weak, so on a JS reload the
|
|
302
|
-
// old instance may already be gone — "shared was nil" does not mean no
|
|
303
|
-
// controller is subscribed to a dead instance's emitters.
|
|
304
|
-
HybridAudioBrowser.instanceChangedEmitter.emit(())
|
|
305
248
|
}
|
|
306
249
|
setupEmitterToNitroForwarding()
|
|
307
250
|
setupBrowserCallbacks()
|
|
@@ -376,17 +319,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
376
319
|
let defaultFormatted = navError.defaultFormatted()
|
|
377
320
|
if let formatter = onMainActor({ browserManager.config.formatNavigationError }) {
|
|
378
321
|
let params = FormatNavigationErrorParams(error: navError, defaultFormatted: defaultFormatted, path: path)
|
|
379
|
-
// Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes
|
|
380
|
-
// The result callbacks are @Sendable: a plain closure would inherit MainActor
|
|
381
|
-
// isolation from this block, and Nitro resolves its promises synchronously on the
|
|
382
|
-
// JS thread — Swift 6.2's dynamic isolation check then traps on closure entry.
|
|
383
|
-
// The property is JS-thread-touched elsewhere too, so no hop back is needed.
|
|
322
|
+
// Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes
|
|
384
323
|
DispatchQueue.main.async { [weak self] in
|
|
385
324
|
formatter(params)
|
|
386
|
-
.then {
|
|
325
|
+
.then { customDisplay in
|
|
387
326
|
self?.lastFormattedNavigationError = customDisplay ?? defaultFormatted
|
|
388
327
|
}
|
|
389
|
-
.catch {
|
|
328
|
+
.catch { _ in
|
|
390
329
|
self?.lastFormattedNavigationError = defaultFormatted
|
|
391
330
|
}
|
|
392
331
|
}
|
|
@@ -415,11 +354,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
415
354
|
guard let player else { return }
|
|
416
355
|
let result = await trackSelector.select(track: track, player: player)
|
|
417
356
|
switch result {
|
|
418
|
-
case
|
|
357
|
+
case .play(let intent):
|
|
419
358
|
executePlayback(intent)
|
|
420
359
|
case .intercepted:
|
|
421
360
|
break
|
|
422
|
-
case
|
|
361
|
+
case .browse(let url):
|
|
423
362
|
navigateToBrowsableUrl(url)
|
|
424
363
|
case .none:
|
|
425
364
|
break
|
|
@@ -430,15 +369,15 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
430
369
|
private func executePlayback(_ intent: TrackSelector.PlaybackIntent) {
|
|
431
370
|
onMainActor {
|
|
432
371
|
switch intent {
|
|
433
|
-
case
|
|
372
|
+
case .skipTo(let index):
|
|
434
373
|
do {
|
|
435
374
|
try player?.skipTo(index, playWhenReady: true)
|
|
436
375
|
} catch {
|
|
437
376
|
logger.error("Failed to skip to track at index \(index): \(error)")
|
|
438
377
|
}
|
|
439
|
-
case
|
|
378
|
+
case .setQueue(let tracks, let startIndex, let sourcePath):
|
|
440
379
|
player?.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: sourcePath)
|
|
441
|
-
case
|
|
380
|
+
case .loadTrack(let track):
|
|
442
381
|
player?.load(track, playWhenReady: true)
|
|
443
382
|
}
|
|
444
383
|
}
|
|
@@ -477,12 +416,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
477
416
|
lastFormattedNavigationError
|
|
478
417
|
}
|
|
479
418
|
|
|
480
|
-
/// Internal signal sent on `externalContentChangedEmitter` to tell CarPlay to
|
|
481
|
-
/// refresh every displayed template. Not a real path (paths are always
|
|
482
|
-
/// `/…`), so it can't collide with one; kept internal — the public API is
|
|
483
|
-
/// `invalidateAllContent()`.
|
|
484
|
-
static let invalidateAllSentinel = "__rnab_invalidate_all__"
|
|
485
|
-
|
|
486
419
|
public func notifyContentChanged(path: String) throws {
|
|
487
420
|
onMainActor { browserManager.invalidateContentCache(path) }
|
|
488
421
|
|
|
@@ -501,168 +434,22 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
501
434
|
}
|
|
502
435
|
}
|
|
503
436
|
|
|
504
|
-
public func invalidateAllContent() throws {
|
|
505
|
-
onMainActor { browserManager.clearContentCache() }
|
|
506
|
-
|
|
507
|
-
// Tell external controllers (CarPlay) to refresh every displayed template.
|
|
508
|
-
externalContentChangedEmitter.emit(Self.invalidateAllSentinel)
|
|
509
|
-
|
|
510
|
-
// Re-query the tabs source too: a callback source can resolve to new
|
|
511
|
-
// titles after an invalidation (e.g. a locale switch). BrowserManager
|
|
512
|
-
// emits tabsChanged only when the result actually differs, and a failed
|
|
513
|
-
// re-query keeps the current tabs. (Android needs no equivalent — its
|
|
514
|
-
// root re-query already runs queryTabs(); the web stub re-queries tabs on
|
|
515
|
-
// the re-navigation.)
|
|
516
|
-
Task {
|
|
517
|
-
_ = try? await browserManager.queryTabs()
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Re-resolve the JS-facing current path with the cache cleared.
|
|
521
|
-
Task {
|
|
522
|
-
do {
|
|
523
|
-
try await browserManager.refresh()
|
|
524
|
-
} catch {
|
|
525
|
-
handleNavigationError(error, path: onMainActor { browserManager.getPath() })
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
437
|
public func setFavorites(favorites: [String]) throws {
|
|
531
438
|
onMainActor { browserManager.setFavorites(favorites) }
|
|
532
439
|
}
|
|
533
440
|
|
|
534
|
-
// MARK: - Gate
|
|
535
|
-
|
|
536
|
-
/// The gate's default chrome, if one was set. Served when the resolver
|
|
537
|
-
/// returns a gated decision with no per-request override, and for the static
|
|
538
|
-
/// gate (no resolver). May be nil for a resolver-only gate.
|
|
539
|
-
private(set) var defaultChrome: Gate?
|
|
540
|
-
|
|
541
|
-
/// Whether JS installed a per-request resolver. When false the gate is static
|
|
542
|
-
/// — every request is gated with `defaultChrome` and the JS hop is skipped.
|
|
543
|
-
private(set) var hasResolver = false
|
|
544
|
-
|
|
545
|
-
/// True once `setGate` runs, until `clearGate`. While active, external
|
|
546
|
-
/// surfaces (CarPlay) keep their tabs visible but consult `gateDecision(for:)`
|
|
547
|
-
/// per request, rendering the gate chrome where gated and refusing
|
|
548
|
-
/// external-surface search. Playback, the queue and Now Playing are
|
|
549
|
-
/// unaffected — a gate blocks finding content, never hearing it.
|
|
550
|
-
private(set) var isGateActive = false
|
|
551
|
-
|
|
552
|
-
/// The minimal built-in gate served when a request is gated but neither a
|
|
553
|
-
/// per-request override nor a stored default chrome exists (resolver-only
|
|
554
|
-
/// gate returning `true`).
|
|
555
|
-
static let builtInGate = Gate(title: "Unavailable", message: nil)
|
|
556
|
-
|
|
557
|
-
/// The outcome of a single gate decision: whether to gate, and (if so) the
|
|
558
|
-
/// chrome to render.
|
|
559
|
-
struct GateOutcome {
|
|
560
|
-
let gated: Bool
|
|
561
|
-
let chrome: Gate?
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
public func setGate(gate: Gate?, hasResolver: Bool) throws {
|
|
565
|
-
onMainActor {
|
|
566
|
-
defaultChrome = gate
|
|
567
|
-
self.hasResolver = hasResolver
|
|
568
|
-
isGateActive = true
|
|
569
|
-
gateChangedEmitter.emit(true)
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
public func clearGate() throws {
|
|
574
|
-
onMainActor {
|
|
575
|
-
guard isGateActive else { return }
|
|
576
|
-
defaultChrome = nil
|
|
577
|
-
hasResolver = false
|
|
578
|
-
isGateActive = false
|
|
579
|
-
gateChangedEmitter.emit(false)
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/// The single choke point for every gate enforcement site. Asks whether
|
|
584
|
-
/// `request` should be gated and, if so, with what chrome (override → stored
|
|
585
|
-
/// default → built-in). When no resolver is installed the gate is static and
|
|
586
|
-
/// resolves without a JS hop.
|
|
587
|
-
///
|
|
588
|
-
/// Resolver errors **fail CLOSED by design**: a thrown/rejected resolver, or a
|
|
589
|
-
/// rejection at either `.await()` layer (bridge tear-down on JS reload, the JS
|
|
590
|
-
/// runtime mid-reload, a serialization error) cannot prove the request is
|
|
591
|
-
/// allowed — and a gate exists to withhold content, so serving on error would
|
|
592
|
-
/// be a content leak (a paywall bypass). On error we gate with the stored
|
|
593
|
-
/// default / built-in chrome so the user still sees a coherent gate page. A
|
|
594
|
-
/// *successful* resolver returning `gated: false` still allows the request.
|
|
595
|
-
@MainActor
|
|
596
|
-
func gateDecision(for request: NativeGateRequest) async -> GateOutcome {
|
|
597
|
-
guard isGateActive else { return GateOutcome(gated: false, chrome: nil) }
|
|
598
|
-
if !hasResolver { return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate) }
|
|
599
|
-
guard let decision = try? await resolveGate(request).await().await() else {
|
|
600
|
-
// Resolver error: fail CLOSED (see doc comment) — gate with the default /
|
|
601
|
-
// built-in so the gate page still renders rather than leaking content.
|
|
602
|
-
return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate)
|
|
603
|
-
}
|
|
604
|
-
guard decision.gated else { return GateOutcome(gated: false, chrome: nil) } // explicit allow
|
|
605
|
-
return GateOutcome(gated: true, chrome: decision.gate ?? defaultChrome ?? Self.builtInGate)
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// MARK: - Car Connection (CarPlay)
|
|
609
|
-
|
|
610
|
-
/// Static because the CarPlay scene can connect before the JS runtime
|
|
611
|
-
/// creates the shared instance (cold start in the car).
|
|
612
|
-
private nonisolated(unsafe) static var carPlayConnected = false
|
|
613
|
-
|
|
614
|
-
/// Called by the CarPlay scene delegate (via `RNABCarPlayController`) on
|
|
615
|
-
/// scene connect/disconnect — not from controller start/stop, which also
|
|
616
|
-
/// cycle on a JS reload and would emit spurious connection changes.
|
|
617
|
-
static func setCarPlayConnected(_ connected: Bool) {
|
|
618
|
-
guard carPlayConnected != connected else { return }
|
|
619
|
-
carPlayConnected = connected
|
|
620
|
-
shared?.onCarConnectedChanged(connected)
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
public func isCarConnected() throws -> Bool {
|
|
624
|
-
HybridAudioBrowser.carPlayConnected
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
public var onCarConnectedChanged: (Bool) -> Void = { _ in } {
|
|
628
|
-
didSet {
|
|
629
|
-
// Immediately notify current state (the scene may have connected before
|
|
630
|
-
// this JS runtime subscribed).
|
|
631
|
-
onCarConnectedChanged(HybridAudioBrowser.carPlayConnected)
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
441
|
// MARK: - Player Setup
|
|
636
442
|
|
|
637
|
-
public func setupPlayer(options:
|
|
443
|
+
public func setupPlayer(options: PartialSetupPlayerOptions) throws -> Promise<Void> {
|
|
638
444
|
Promise.async {
|
|
639
|
-
// Configure
|
|
640
|
-
// options like `allowBluetooth` / `allowAirPlay`), but do NOT activate it here. Activating a
|
|
641
|
-
// non-mixable `.playback` session interrupts other apps' audio — so activating at setup
|
|
642
|
-
// pauses whatever the user is listening to (Safari, Spotify, a podcast) the instant our app
|
|
643
|
-
// launches, before they play anything. Setting the category is silent; only activation
|
|
644
|
-
// interrupts. The session is activated lazily when playback actually starts producing output
|
|
645
|
-
// (see `playerDidChangePlayingState`) and re-activated on interruption-end. Best-effort: a
|
|
646
|
-
// category-config failure must never brick setup.
|
|
445
|
+
// Configure audio session
|
|
647
446
|
let session = AVAudioSession.sharedInstance()
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
mode: AVAudioSession.Mode,
|
|
651
|
-
policy: AVAudioSession.RouteSharingPolicy,
|
|
652
|
-
options: AVAudioSession.CategoryOptions,
|
|
653
|
-
) = options.ios?.resolveAudioSessionConfig()
|
|
654
|
-
?? (category: .playback, mode: .default, policy: .default, options: [])
|
|
655
|
-
// Capture category application once so a media-services reset (which
|
|
656
|
-
// clears the category) can restore the exact same config later.
|
|
657
|
-
let applyCategory: () -> Void = {
|
|
658
|
-
try? session.setCategory(cfg.category, mode: cfg.mode, policy: cfg.policy, options: cfg.options)
|
|
659
|
-
}
|
|
660
|
-
applyCategory()
|
|
447
|
+
try session.setCategory(.playback, mode: .default)
|
|
448
|
+
try session.setActive(true)
|
|
661
449
|
|
|
662
450
|
// Create player and configure on main actor
|
|
663
451
|
await MainActor.run { [weak self] in
|
|
664
452
|
guard let self else { return }
|
|
665
|
-
self.applyAudioSessionCategory = applyCategory
|
|
666
453
|
|
|
667
454
|
// Create player with self as callbacks delegate
|
|
668
455
|
player = TrackPlayer(callbacks: self)
|
|
@@ -680,52 +467,18 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
680
467
|
// Wire up network monitor for accelerated retries when connectivity is restored
|
|
681
468
|
player?.networkMonitor = networkMonitor
|
|
682
469
|
|
|
683
|
-
// Central fan-out for connectivity changes (wired once here, idempotent on re-setup, and
|
|
684
|
-
// independent of any JS subscription):
|
|
685
|
-
// - bridge to the JS `onOnlineChanged` event (no-op default until JS subscribes);
|
|
686
|
-
// - re-render now-playing so the formatter's offline/online label updates immediately on a
|
|
687
|
-
// connectivity change, not only on the next playback transition (fixes the stall-entry
|
|
688
|
-
// race where the device goes offline after the buffering line was already rendered);
|
|
689
|
-
// - reconnect a stalled stream when connectivity returns (see TrackPlayer.handleNetworkRestored).
|
|
690
|
-
// The monitor only mutates state on the main thread, so this fires main-isolated.
|
|
691
|
-
networkMonitor.onChanged = { [weak self] isOnline in
|
|
692
|
-
MainActor.assumeIsolated {
|
|
693
|
-
guard let self else { return }
|
|
694
|
-
self.onOnlineChanged(isOnline)
|
|
695
|
-
self.applyNowPlayingMetadata()
|
|
696
|
-
if isOnline { self.player?.handleNetworkRestored() }
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Observe audio-session interruptions so another app taking over audio
|
|
701
|
-
// (or a phone call) reflects as a real pause rather than a stuck state.
|
|
702
|
-
setupInterruptionObserver()
|
|
703
|
-
|
|
704
|
-
// Observe media-server resets so a long-running stream recovers instead
|
|
705
|
-
// of going permanently silent.
|
|
706
|
-
setupMediaServicesResetObserver()
|
|
707
|
-
|
|
708
470
|
// Configure media URL resolver
|
|
709
|
-
player?.mediaLoader.mediaUrlResolver = { [weak self] src
|
|
471
|
+
player?.mediaLoader.mediaUrlResolver = { [weak self] src in
|
|
710
472
|
guard let self else {
|
|
711
473
|
return MediaResolvedUrl(url: src, headers: nil, userAgent: nil)
|
|
712
474
|
}
|
|
713
|
-
return await browserManager.resolveMediaUrl(src
|
|
475
|
+
return await browserManager.resolveMediaUrl(src)
|
|
714
476
|
}
|
|
715
477
|
|
|
716
478
|
// Configure artwork URL resolver for Now Playing (with size context)
|
|
717
479
|
player?.nowPlayingUpdater.artworkUrlResolver = { [weak self] track, imageContext in
|
|
718
480
|
guard let self else { return nil }
|
|
719
|
-
|
|
720
|
-
return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: perRoute, imageContext: imageContext)
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
// Emit the JS `onNowPlayingChanged` event when the rendered text lines change. The updater
|
|
724
|
-
// owns the system now-playing write; this shapes the JS metadata (elapsed time / artwork).
|
|
725
|
-
player?.nowPlayingUpdater.onChanged = { [weak self] track, title, artist, album in
|
|
726
|
-
guard let self else { return }
|
|
727
|
-
self.onNowPlayingChanged(
|
|
728
|
-
self.makeNowPlayingMetadata(track: track, title: title, artist: artist, album: album))
|
|
481
|
+
return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: nil, imageContext: imageContext)
|
|
729
482
|
}
|
|
730
483
|
|
|
731
484
|
// Configure sleep timer callback
|
|
@@ -733,30 +486,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
733
486
|
self?.onSleepTimerChanged(state)
|
|
734
487
|
}
|
|
735
488
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
nowPlayingMetadataEnabled = options.autoUpdateNowPlayingMetadata ?? true
|
|
739
|
-
nowPlayingMetadataFormatter = options.nowPlayingMetadataFormatter
|
|
740
|
-
// Stored for API symmetry, but iOS needs no masking: a terminal error resolves the playback
|
|
741
|
-
// state to *paused* (PlaybackCoordinator.applySideEffects), the now-playing info is retained
|
|
742
|
-
// (never cleared on error), and next/previous stay enabled (gated on queue position, not
|
|
743
|
-
// state). So the session stays controllable through errors by default — the behavior Android
|
|
744
|
-
// achieves via InterceptingPlayer is already the iOS norm.
|
|
745
|
-
keepSessionAliveOnError = options.keepSessionAliveOnError ?? true
|
|
746
|
-
|
|
747
|
-
// The bundled runtime options and initial state are part of the atomic launch
|
|
748
|
-
// description. Stage the state first so setup options and earlier pre-setup setter
|
|
749
|
-
// calls resolve by strict last-write-wins, then apply everything to the live player.
|
|
750
|
-
if let bundled = options.options {
|
|
751
|
-
updateOptions(options: bundled)
|
|
752
|
-
} else {
|
|
753
|
-
// updateOptions applies remote commands itself; without bundled options, apply the
|
|
754
|
-
// defaults (play, pause, next, previous, seekTo) here.
|
|
755
|
-
applyRemoteCommands()
|
|
756
|
-
}
|
|
757
|
-
if let mode = options.repeatMode { pendingRepeatMode = mode }
|
|
758
|
-
if let intent = options.playWhenReady { pendingPlayWhenReady = intent }
|
|
759
|
-
applyPendingPlayerState()
|
|
489
|
+
// Apply default remote commands (play, pause, next, previous, seekTo)
|
|
490
|
+
applyRemoteCommands()
|
|
760
491
|
|
|
761
492
|
// Notify listeners that player is ready (e.g., CarPlay)
|
|
762
493
|
playerAndConfiguredBrowser.check()
|
|
@@ -764,22 +495,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
764
495
|
}
|
|
765
496
|
}
|
|
766
497
|
|
|
767
|
-
/// Applies initial player state staged before the player existed — from setup options or the
|
|
768
|
-
/// imperative setters called pre-setup. Consumed on apply so a later re-setup doesn't replay
|
|
769
|
-
/// stale state.
|
|
770
|
-
@MainActor
|
|
771
|
-
private func applyPendingPlayerState() {
|
|
772
|
-
guard let player else { return }
|
|
773
|
-
if let mode = pendingRepeatMode {
|
|
774
|
-
player.repeatMode = mode
|
|
775
|
-
pendingRepeatMode = nil
|
|
776
|
-
}
|
|
777
|
-
if let intent = pendingPlayWhenReady {
|
|
778
|
-
player.playWhenReady = intent
|
|
779
|
-
pendingPlayWhenReady = nil
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
498
|
public func updateOptions(options: NativeUpdateOptions) {
|
|
784
499
|
onMainActor {
|
|
785
500
|
let previousInterval = playerOptions.progressUpdateEventInterval
|
|
@@ -787,10 +502,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
787
502
|
// Update stored options
|
|
788
503
|
playerOptions.update(from: options)
|
|
789
504
|
|
|
790
|
-
// Propagate the favorite match mode to the browser so it can hydrate
|
|
791
|
-
// row hearts (the `favorite` capability is the single favoriting switch).
|
|
792
|
-
browserManager.setFavoriteMatch(playerOptions.capabilities.favoriteMatch)
|
|
793
|
-
|
|
794
505
|
// Apply remote commands to player
|
|
795
506
|
applyRemoteCommands()
|
|
796
507
|
|
|
@@ -798,9 +509,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
798
509
|
if playerOptions.progressUpdateEventInterval != previousInterval {
|
|
799
510
|
player?.setProgressUpdateInterval(playerOptions.progressUpdateEventInterval)
|
|
800
511
|
}
|
|
801
|
-
|
|
802
|
-
onOptionsChanged(playerOptions.toOptions())
|
|
803
|
-
playerOptionsChangedEmitter.emit(())
|
|
804
512
|
}
|
|
805
513
|
}
|
|
806
514
|
|
|
@@ -818,12 +526,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
818
526
|
player.remoteCommands = remoteCommands
|
|
819
527
|
}
|
|
820
528
|
|
|
821
|
-
public func getOptions() throws ->
|
|
822
|
-
playerOptions.
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
public func setPlaybackIntervalEnabled(enabled: Bool) {
|
|
826
|
-
onMainActor { player?.setPlaybackIntervalEnabled(enabled) }
|
|
529
|
+
public func getOptions() throws -> UpdateOptions {
|
|
530
|
+
playerOptions.toUpdateOptions()
|
|
827
531
|
}
|
|
828
532
|
|
|
829
533
|
// MARK: - Playback Control
|
|
@@ -858,15 +562,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
858
562
|
}
|
|
859
563
|
|
|
860
564
|
public func setPlayWhenReady(playWhenReady: Bool) throws {
|
|
861
|
-
onMainActor {
|
|
862
|
-
// Pre-setup this stages the intent for the player to come up with — never a no-op.
|
|
863
|
-
if let player { player.playWhenReady = playWhenReady }
|
|
864
|
-
else { pendingPlayWhenReady = playWhenReady }
|
|
865
|
-
}
|
|
565
|
+
onMainActor { player?.playWhenReady = playWhenReady }
|
|
866
566
|
}
|
|
867
567
|
|
|
868
568
|
public func getPlayWhenReady() throws -> Bool {
|
|
869
|
-
onMainActor { player?.playWhenReady ??
|
|
569
|
+
onMainActor { player?.playWhenReady ?? false }
|
|
870
570
|
}
|
|
871
571
|
|
|
872
572
|
public func seekTo(position: Double) throws {
|
|
@@ -877,10 +577,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
877
577
|
onMainActor { player?.seekBy(offset) }
|
|
878
578
|
}
|
|
879
579
|
|
|
880
|
-
public func seekToLiveEdge() throws {
|
|
881
|
-
onMainActor { player?.seekToLiveEdge() }
|
|
882
|
-
}
|
|
883
|
-
|
|
884
580
|
public func setVolume(level: Double) throws {
|
|
885
581
|
onMainActor { player?.volume = Float(level) }
|
|
886
582
|
}
|
|
@@ -918,15 +614,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
918
614
|
}
|
|
919
615
|
|
|
920
616
|
public func getRepeatMode() throws -> RepeatMode {
|
|
921
|
-
onMainActor { player?.repeatMode ??
|
|
617
|
+
onMainActor { player?.repeatMode ?? .off }
|
|
922
618
|
}
|
|
923
619
|
|
|
924
620
|
public func setRepeatMode(mode: RepeatMode) throws {
|
|
925
|
-
onMainActor {
|
|
926
|
-
// Pre-setup this stages the mode for the player to come up with — never a no-op.
|
|
927
|
-
if let player { player.repeatMode = mode }
|
|
928
|
-
else { pendingRepeatMode = mode }
|
|
929
|
-
}
|
|
621
|
+
onMainActor { player?.repeatMode = mode }
|
|
930
622
|
}
|
|
931
623
|
|
|
932
624
|
public func getShuffleEnabled() throws -> Bool {
|
|
@@ -938,13 +630,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
938
630
|
}
|
|
939
631
|
|
|
940
632
|
public func getPlaybackError() throws -> PlaybackError? {
|
|
941
|
-
onMainActor { player?.
|
|
633
|
+
onMainActor { player?.playbackError?.toNitroError() }
|
|
942
634
|
}
|
|
943
635
|
|
|
944
636
|
public func retry() throws {
|
|
945
|
-
|
|
946
|
-
// may have expired since the failure (matches the internal retry path).
|
|
947
|
-
onMainActor { player?.reloadResolving(startFromCurrentTime: true) }
|
|
637
|
+
onMainActor { player?.reload(startFromCurrentTime: true) }
|
|
948
638
|
}
|
|
949
639
|
|
|
950
640
|
// MARK: - Sleep Timer
|
|
@@ -958,9 +648,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
958
648
|
}
|
|
959
649
|
}
|
|
960
650
|
|
|
961
|
-
public func setSleepTimer(seconds: Double
|
|
651
|
+
public func setSleepTimer(seconds: Double) throws {
|
|
962
652
|
onMainActor {
|
|
963
|
-
player?.sleepTimerManager.set(seconds: seconds
|
|
653
|
+
player?.sleepTimerManager.set(seconds: seconds)
|
|
964
654
|
}
|
|
965
655
|
}
|
|
966
656
|
|
|
@@ -1040,13 +730,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1040
730
|
onMainActor {
|
|
1041
731
|
guard let track = player?.currentTrack, let src = track.src else { return }
|
|
1042
732
|
guard let index = player?.currentIndex, index >= 0 else { return }
|
|
1043
|
-
|
|
1044
|
-
//
|
|
1045
|
-
// reconciles to its canonical ids on the next setFavorites.
|
|
1046
|
-
browserManager.setFavorited(src: src, favorited: favorited)
|
|
733
|
+
browserManager.updateFavorite(id: src, favorited: favorited)
|
|
734
|
+
// Create updated track with new favorited state
|
|
1047
735
|
let updatedTrack = track.copying(favorited: favorited)
|
|
736
|
+
// Update the track in the player's queue so getActiveTrack() returns correct state
|
|
737
|
+
player?.replace(index, updatedTrack)
|
|
1048
738
|
favoriteChangedEmitter.emit(FavoriteChangedEvent(track: updatedTrack, favorited: favorited))
|
|
1049
|
-
// Fire active track changed so
|
|
739
|
+
// Fire active track changed so useActiveTrack() hook updates UI
|
|
1050
740
|
let position = player?.currentTime ?? 0
|
|
1051
741
|
activeTrackChangedEmitter.emit(PlaybackActiveTrackChangedEvent(
|
|
1052
742
|
lastIndex: Double(index),
|
|
@@ -1058,24 +748,27 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1058
748
|
}
|
|
1059
749
|
}
|
|
1060
750
|
|
|
1061
|
-
/// Whether the active (now-playing) track is favorited, per the authoritative
|
|
1062
|
-
/// favorite set (via `getActiveTrack`'s hydration).
|
|
1063
|
-
func isActiveTrackFavorited() -> Bool {
|
|
1064
|
-
(try? getActiveTrack())?.favorited ?? false
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
751
|
public func toggleActiveTrackFavorited() throws {
|
|
1068
|
-
|
|
752
|
+
onMainActor {
|
|
753
|
+
guard let track = player?.currentTrack, let src = track.src else { return }
|
|
754
|
+
// Check current favorited state from cache
|
|
755
|
+
let currentTrack = browserManager.getCachedTrack(src)
|
|
756
|
+
let isFavorited = currentTrack?.favorited ?? track.favorited ?? false
|
|
757
|
+
try? setActiveTrackFavorited(favorited: !isFavorited)
|
|
758
|
+
}
|
|
1069
759
|
}
|
|
1070
760
|
|
|
1071
|
-
public func setQueue(tracks: [Track], startIndex: Double?,
|
|
1072
|
-
onMainActor {
|
|
761
|
+
public func setQueue(tracks: [Track], startIndex: Double?, startPositionMs: Double?) throws {
|
|
762
|
+
try onMainActor {
|
|
1073
763
|
guard let player else { return }
|
|
1074
|
-
player.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
764
|
+
player.clear()
|
|
765
|
+
player.add(tracks)
|
|
766
|
+
if let index = startIndex, index >= 0 {
|
|
767
|
+
try player.skipTo(Int(index))
|
|
768
|
+
}
|
|
769
|
+
if let position = startPositionMs {
|
|
770
|
+
player.seekTo(position / 1000.0)
|
|
771
|
+
}
|
|
1079
772
|
}
|
|
1080
773
|
}
|
|
1081
774
|
|
|
@@ -1100,11 +793,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1100
793
|
}
|
|
1101
794
|
|
|
1102
795
|
public func getActiveTrack() throws -> Track? {
|
|
1103
|
-
|
|
1104
|
-
// track is loaded outside the browse cache, so its own flag isn't otherwise
|
|
1105
|
-
// kept in sync as favorites change. This is the single source of truth for
|
|
1106
|
-
// "is the active track favorited" (now-playing heart, toggle direction, JS).
|
|
1107
|
-
onMainActor { player?.currentTrack.map(browserManager.hydrateFavorite) }
|
|
796
|
+
onMainActor { player?.currentTrack }
|
|
1108
797
|
}
|
|
1109
798
|
|
|
1110
799
|
// MARK: - Now Playing
|
|
@@ -1116,57 +805,21 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1116
805
|
}
|
|
1117
806
|
}
|
|
1118
807
|
|
|
1119
|
-
/// Transient now-playing fields (e.g. feedback for a refused remote
|
|
1120
|
-
/// command). Outranks the formatter and the override while active, so live
|
|
1121
|
-
/// metadata can't stomp it mid-flash. Reverted by a NATIVE timer — JS
|
|
1122
|
-
/// timers pause with a backgrounded host on Android, and the lock screen
|
|
1123
|
-
/// is exactly the backgrounded case — and cleared early on track change.
|
|
1124
|
-
private var nowPlayingFlash: NowPlayingUpdate?
|
|
1125
|
-
private var nowPlayingFlashRevert: DispatchWorkItem?
|
|
1126
|
-
|
|
1127
|
-
/// Pending debounced audio-session release (see `playerShouldReleaseSession`).
|
|
1128
|
-
private var sessionReleaseWork: DispatchWorkItem?
|
|
1129
|
-
|
|
1130
|
-
public func flashNowPlaying(update: NowPlayingUpdate, durationMs: Double) throws {
|
|
1131
|
-
onMainActor {
|
|
1132
|
-
nowPlayingFlashRevert?.cancel()
|
|
1133
|
-
nowPlayingFlash = update
|
|
1134
|
-
let revert = DispatchWorkItem { [weak self] in
|
|
1135
|
-
guard let self else { return }
|
|
1136
|
-
self.nowPlayingFlash = nil
|
|
1137
|
-
self.nowPlayingFlashRevert = nil
|
|
1138
|
-
self.applyNowPlayingMetadata()
|
|
1139
|
-
}
|
|
1140
|
-
nowPlayingFlashRevert = revert
|
|
1141
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + durationMs / 1000, execute: revert)
|
|
1142
|
-
applyNowPlayingMetadata()
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
public func clearNowPlayingFlash() throws {
|
|
1147
|
-
onMainActor {
|
|
1148
|
-
guard nowPlayingFlash != nil else { return }
|
|
1149
|
-
cancelNowPlayingFlash()
|
|
1150
|
-
applyNowPlayingMetadata()
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
private func cancelNowPlayingFlash() {
|
|
1155
|
-
nowPlayingFlashRevert?.cancel()
|
|
1156
|
-
nowPlayingFlashRevert = nil
|
|
1157
|
-
nowPlayingFlash = nil
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
808
|
public func getNowPlaying() throws -> NowPlayingMetadata? {
|
|
1161
809
|
onMainActor {
|
|
1162
810
|
guard let track = player?.currentTrack else { return nil }
|
|
1163
|
-
let flash = nowPlayingFlash
|
|
1164
811
|
let override = nowPlayingOverride
|
|
1165
|
-
return
|
|
1166
|
-
|
|
1167
|
-
title:
|
|
1168
|
-
|
|
1169
|
-
|
|
812
|
+
return NowPlayingMetadata(
|
|
813
|
+
elapsedTime: player?.currentTime,
|
|
814
|
+
title: override?.title ?? track.title,
|
|
815
|
+
album: track.album,
|
|
816
|
+
artist: override?.artist ?? track.artist,
|
|
817
|
+
duration: track.duration,
|
|
818
|
+
artwork: nowPlayingArtwork(for: track),
|
|
819
|
+
description: track.description,
|
|
820
|
+
mediaId: track.src ?? track.url,
|
|
821
|
+
genre: track.genre,
|
|
822
|
+
rating: nil,
|
|
1170
823
|
)
|
|
1171
824
|
}
|
|
1172
825
|
}
|
|
@@ -1176,47 +829,36 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1176
829
|
/// Falls back to the raw artwork string only if it's not an SF Symbol (which JS can't load).
|
|
1177
830
|
private func nowPlayingArtwork(for track: Track) -> String? {
|
|
1178
831
|
if let uri = track.artworkSource?.uri { return uri }
|
|
1179
|
-
guard let artwork = track.artwork
|
|
832
|
+
guard let artwork = track.artwork else { return nil }
|
|
1180
833
|
return SFSymbolRenderer.isSFSymbol(artwork) ? nil : artwork
|
|
1181
834
|
}
|
|
1182
835
|
|
|
1183
|
-
///
|
|
836
|
+
/// Applies the current now playing metadata (with override if set) to NowPlayingInfoCenter and notifies JS.
|
|
1184
837
|
@MainActor
|
|
1185
|
-
private func
|
|
1186
|
-
track
|
|
1187
|
-
|
|
1188
|
-
|
|
838
|
+
private func applyNowPlayingMetadata() {
|
|
839
|
+
guard let track = player?.currentTrack else { return }
|
|
840
|
+
let override = nowPlayingOverride
|
|
841
|
+
|
|
842
|
+
let nowPlaying = NowPlayingMetadata(
|
|
1189
843
|
elapsedTime: player?.currentTime,
|
|
1190
|
-
title: title,
|
|
1191
|
-
album: album,
|
|
1192
|
-
artist: artist,
|
|
844
|
+
title: override?.title ?? track.title,
|
|
845
|
+
album: track.album,
|
|
846
|
+
artist: override?.artist ?? track.artist,
|
|
1193
847
|
duration: track.duration,
|
|
1194
848
|
artwork: nowPlayingArtwork(for: track),
|
|
1195
849
|
description: track.description,
|
|
1196
850
|
mediaId: track.src ?? track.url,
|
|
1197
851
|
genre: track.genre,
|
|
852
|
+
rating: nil,
|
|
1198
853
|
)
|
|
1199
|
-
}
|
|
1200
854
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
let stalled: StallReason? =
|
|
1209
|
-
player.isStalled ? (networkMonitor.getOnline() ? .buffering : .offline) : nil
|
|
1210
|
-
player.nowPlayingUpdater.render(
|
|
1211
|
-
track: track,
|
|
1212
|
-
timedMetadata: latestTimedMetadata,
|
|
1213
|
-
playWhenReady: player.playWhenReady,
|
|
1214
|
-
stalled: stalled,
|
|
1215
|
-
error: player.coordinator.nitroPlaybackError(),
|
|
1216
|
-
flash: nowPlayingFlash,
|
|
1217
|
-
override: nowPlayingMetadataEnabled ? nowPlayingOverride : nil,
|
|
1218
|
-
formatter: nowPlayingMetadataEnabled ? nowPlayingMetadataFormatter : nil,
|
|
1219
|
-
)
|
|
855
|
+
// Update NowPlayingInfoCenter with override values
|
|
856
|
+
if let override {
|
|
857
|
+
player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.title(override.title ?? track.title))
|
|
858
|
+
player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.artist(override.artist ?? track.artist))
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
onNowPlayingChanged(nowPlaying)
|
|
1220
862
|
}
|
|
1221
863
|
|
|
1222
864
|
// MARK: - Network
|
|
@@ -1251,63 +893,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1251
893
|
|
|
1252
894
|
// MARK: - External Audio Output
|
|
1253
895
|
|
|
1254
|
-
/// Observes audio-session interruptions (phone calls, or another app such as
|
|
1255
|
-
/// Music/Spotify starting playback). iOS pauses our AVPlayer underneath us but
|
|
1256
|
-
/// doesn't update our playback state, so without this the UI stays stuck on
|
|
1257
|
-
/// "playing". On `.began` we reflect a real pause; on `.ended` we resume if we
|
|
1258
|
-
/// were playing and the system says we should.
|
|
1259
|
-
private func setupInterruptionObserver() {
|
|
1260
|
-
if let observer = interruptionObserver {
|
|
1261
|
-
NotificationCenter.default.removeObserver(observer)
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
interruptionObserver = NotificationCenter.default.addObserver(
|
|
1265
|
-
forName: AVAudioSession.interruptionNotification,
|
|
1266
|
-
object: nil,
|
|
1267
|
-
queue: .main,
|
|
1268
|
-
) { [weak self] notification in
|
|
1269
|
-
self?.handleAudioSessionInterruption(notification)
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
private func handleAudioSessionInterruption(_ notification: Notification) {
|
|
1274
|
-
guard
|
|
1275
|
-
let info = notification.userInfo,
|
|
1276
|
-
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
1277
|
-
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
|
|
1278
|
-
else { return }
|
|
1279
|
-
|
|
1280
|
-
switch type {
|
|
1281
|
-
case .began:
|
|
1282
|
-
// iOS 17+ fires a `.began` (with no matching `.ended`) when an output
|
|
1283
|
-
// route such as headphones disappears. Reflecting a pause here would
|
|
1284
|
-
// strand resume-intent state with nothing to clear it, and the
|
|
1285
|
-
// route-change observer already handles the output change — so ignore it.
|
|
1286
|
-
if #available(iOS 17.0, *),
|
|
1287
|
-
let reasonValue = info[AVAudioSessionInterruptionReasonKey] as? UInt,
|
|
1288
|
-
AVAudioSession.InterruptionReason(rawValue: reasonValue) == .routeDisconnected
|
|
1289
|
-
{
|
|
1290
|
-
return
|
|
1291
|
-
}
|
|
1292
|
-
onMainActor { player?.handleInterruptionBegan() }
|
|
1293
|
-
case .ended:
|
|
1294
|
-
let shouldResume = (info[AVAudioSessionInterruptionOptionKey] as? UInt)
|
|
1295
|
-
.map { AVAudioSession.InterruptionOptions(rawValue: $0).contains(.shouldResume) }
|
|
1296
|
-
?? false
|
|
1297
|
-
onMainActor {
|
|
1298
|
-
// Reactivate the possibly-deactivated session before resuming — but
|
|
1299
|
-
// only when a resume will happen: activating while staying paused
|
|
1300
|
-
// grabs a non-mixable session with nothing to release it.
|
|
1301
|
-
if shouldResume, player?.willResumeAfterInterruption == true {
|
|
1302
|
-
try? AVAudioSession.sharedInstance().setActive(true)
|
|
1303
|
-
}
|
|
1304
|
-
player?.handleInterruptionEnded(shouldResume: shouldResume)
|
|
1305
|
-
}
|
|
1306
|
-
@unknown default:
|
|
1307
|
-
break
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
896
|
/// Sets up observer for audio route changes
|
|
1312
897
|
private func setupRouteChangeObserver() {
|
|
1313
898
|
if let observer = routeChangeObserver {
|
|
@@ -1318,95 +903,58 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1318
903
|
forName: AVAudioSession.routeChangeNotification,
|
|
1319
904
|
object: nil,
|
|
1320
905
|
queue: .main,
|
|
1321
|
-
) { [weak self] notification in
|
|
1322
|
-
guard let self else { return }
|
|
1323
|
-
// The previous output disappeared (headphones unplugged, a Bluetooth
|
|
1324
|
-
// speaker powered off). Pause rather than abruptly resume out of the
|
|
1325
|
-
// built-in speaker — the platform convention every media app follows.
|
|
1326
|
-
// A deliberate pause (clears play intent), not an interruption: the user
|
|
1327
|
-
// restarts playback themselves once they want it.
|
|
1328
|
-
if let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
1329
|
-
AVAudioSession.RouteChangeReason(rawValue: reasonValue) == .oldDeviceUnavailable
|
|
1330
|
-
{
|
|
1331
|
-
// Also cancels a pending interruption auto-resume: unplugging during
|
|
1332
|
-
// a call must not blast the built-in speaker when the call ends.
|
|
1333
|
-
self.onMainActor { self.player?.handleRouteDisconnected() }
|
|
1334
|
-
}
|
|
1335
|
-
if let output = self.getCurrentOutput() {
|
|
1336
|
-
self.onOutputChanged(output)
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
/// Observes audio-server (`mediaserverd`) resets. When it fires, the session
|
|
1342
|
-
/// category is cleared and every AVPlayer/session handle is invalid — a
|
|
1343
|
-
/// long-running stream would otherwise go permanently silent until the app
|
|
1344
|
-
/// restarts.
|
|
1345
|
-
private func setupMediaServicesResetObserver() {
|
|
1346
|
-
if let observer = mediaServicesResetObserver {
|
|
1347
|
-
NotificationCenter.default.removeObserver(observer)
|
|
1348
|
-
}
|
|
1349
|
-
mediaServicesResetObserver = NotificationCenter.default.addObserver(
|
|
1350
|
-
forName: AVAudioSession.mediaServicesWereResetNotification,
|
|
1351
|
-
object: nil,
|
|
1352
|
-
queue: .main,
|
|
1353
906
|
) { [weak self] _ in
|
|
1354
|
-
guard let self else { return }
|
|
1355
|
-
|
|
1356
|
-
// recreate itself and reconnect the current stream.
|
|
1357
|
-
self.applyAudioSessionCategory()
|
|
1358
|
-
self.onMainActor { self.player?.handleMediaServicesReset() }
|
|
907
|
+
guard let self, let output = self.getCurrentOutput() else { return }
|
|
908
|
+
self.onIosOutputChanged(output)
|
|
1359
909
|
}
|
|
1360
910
|
}
|
|
1361
911
|
|
|
1362
912
|
/// Gets the current audio output info
|
|
1363
|
-
private func getCurrentOutput() ->
|
|
913
|
+
private func getCurrentOutput() -> IosOutput? {
|
|
1364
914
|
let session = AVAudioSession.sharedInstance()
|
|
1365
915
|
guard let output = session.currentRoute.outputs.first else { return nil }
|
|
1366
916
|
|
|
1367
|
-
let (outputType, external): (
|
|
917
|
+
let (outputType, external): (IosOutputType, Bool) = switch output.portType {
|
|
1368
918
|
case .builtInSpeaker:
|
|
1369
|
-
(.
|
|
919
|
+
(.builtInSpeaker, false)
|
|
1370
920
|
case .builtInReceiver:
|
|
1371
|
-
(.
|
|
921
|
+
(.builtInReceiver, false)
|
|
1372
922
|
case .airPlay:
|
|
1373
923
|
(.airplay, true)
|
|
1374
|
-
case .bluetoothA2DP
|
|
1375
|
-
(.
|
|
924
|
+
case .bluetoothA2DP:
|
|
925
|
+
(.bluetoothA2dp, true)
|
|
926
|
+
case .bluetoothHFP:
|
|
927
|
+
(.bluetoothHfp, true)
|
|
928
|
+
case .bluetoothLE:
|
|
929
|
+
(.bluetoothLe, true)
|
|
1376
930
|
case .headphones:
|
|
1377
931
|
(.headphones, true)
|
|
1378
932
|
case .carAudio:
|
|
1379
|
-
(.
|
|
933
|
+
(.carAudio, true)
|
|
1380
934
|
case .HDMI:
|
|
1381
935
|
(.hdmi, true)
|
|
1382
936
|
case .usbAudio:
|
|
1383
|
-
(.
|
|
937
|
+
(.usbAudio, true)
|
|
1384
938
|
default:
|
|
1385
939
|
(.other, true)
|
|
1386
940
|
}
|
|
1387
941
|
|
|
1388
|
-
return
|
|
942
|
+
return IosOutput(type: outputType, name: output.portName, external: external)
|
|
1389
943
|
}
|
|
1390
944
|
|
|
1391
|
-
public func
|
|
945
|
+
public func getIosOutput() throws -> IosOutput? {
|
|
1392
946
|
getCurrentOutput()
|
|
1393
947
|
}
|
|
1394
948
|
|
|
1395
|
-
public func
|
|
1396
|
-
// iOS can always present the system route picker (AVRoutePickerView).
|
|
1397
|
-
true
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
public func openOutputPicker() throws {
|
|
949
|
+
public func openIosOutputPicker() throws {
|
|
1401
950
|
DispatchQueue.main.async { [weak self] in
|
|
1402
|
-
guard let self else { return }
|
|
1403
951
|
guard let windowScene = UIApplication.shared.connectedScenes
|
|
1404
952
|
.compactMap({ $0 as? UIWindowScene })
|
|
1405
953
|
.first(where: { $0.activationState == .foregroundActive }),
|
|
1406
954
|
let window = windowScene.windows.first(where: { $0.isKeyWindow }),
|
|
1407
955
|
var topController = window.rootViewController
|
|
1408
956
|
else {
|
|
1409
|
-
self
|
|
957
|
+
self?.logger.debug("openIosOutputPicker: Could not find active window scene")
|
|
1410
958
|
return
|
|
1411
959
|
}
|
|
1412
960
|
|
|
@@ -1415,52 +963,25 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1415
963
|
topController = presented
|
|
1416
964
|
}
|
|
1417
965
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
// button. Two requirements that are easy to get wrong:
|
|
1421
|
-
// 1. The view must be *visible* to the system (a `isHidden` source view
|
|
1422
|
-
// suppresses the presentation), so we keep it transparent instead.
|
|
1423
|
-
// 2. The internal button can be nested below the top-level subviews on
|
|
1424
|
-
// modern iOS, so we search recursively rather than only direct children.
|
|
1425
|
-
let routePicker = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
|
1426
|
-
routePicker.alpha = 0.01
|
|
966
|
+
let routePicker = AVRoutePickerView(frame: .zero)
|
|
967
|
+
routePicker.isHidden = true
|
|
1427
968
|
topController.view.addSubview(routePicker)
|
|
1428
969
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
button.sendActions(for: .touchUpInside)
|
|
1436
|
-
} else {
|
|
1437
|
-
self.logger.error("openOutputPicker: no UIButton found inside AVRoutePickerView")
|
|
970
|
+
// Find the button inside AVRoutePickerView and trigger it
|
|
971
|
+
for subview in routePicker.subviews {
|
|
972
|
+
if let button = subview as? UIButton {
|
|
973
|
+
button.sendActions(for: .touchUpInside)
|
|
974
|
+
break
|
|
975
|
+
}
|
|
1438
976
|
}
|
|
1439
977
|
|
|
1440
|
-
//
|
|
1441
|
-
|
|
1442
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
|
978
|
+
// Remove after a delay to ensure picker has opened
|
|
979
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
1443
980
|
routePicker.removeFromSuperview()
|
|
1444
981
|
}
|
|
1445
982
|
}
|
|
1446
983
|
}
|
|
1447
984
|
|
|
1448
|
-
/// Depth-first search for the first `UIButton` inside a view tree.
|
|
1449
|
-
private static func firstButton(in view: UIView) -> UIButton? {
|
|
1450
|
-
if let button = view as? UIButton { return button }
|
|
1451
|
-
for subview in view.subviews {
|
|
1452
|
-
if let button = firstButton(in: subview) { return button }
|
|
1453
|
-
}
|
|
1454
|
-
return nil
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
/// Compact description of a view tree, e.g. `AVRoutePickerView[UIButton[UIImageView]]`.
|
|
1458
|
-
private static func describeHierarchy(_ view: UIView) -> String {
|
|
1459
|
-
let name = String(describing: type(of: view))
|
|
1460
|
-
let children = view.subviews.map(describeHierarchy).joined(separator: ", ")
|
|
1461
|
-
return children.isEmpty ? name : "\(name)[\(children)]"
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
985
|
// MARK: - Equalizer (unsupported on iOS)
|
|
1465
986
|
|
|
1466
987
|
public func getEqualizerSettings() throws -> EqualizerSettings? {
|
|
@@ -1505,28 +1026,12 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1505
1026
|
extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
1506
1027
|
public func playerDidChangePlayback(_ playback: Playback) {
|
|
1507
1028
|
onPlaybackChanged(playback)
|
|
1508
|
-
// Re-render so the formatter reflects the new state — notably the error line on a failure (the
|
|
1509
|
-
// coordinator sets playbackError before this fires). Redundant updates are deduped on publish.
|
|
1510
|
-
applyNowPlayingMetadata()
|
|
1511
1029
|
}
|
|
1512
1030
|
|
|
1513
1031
|
public func playerDidChangeActiveTrack(_ event: PlaybackActiveTrackChangedEvent) {
|
|
1514
|
-
// Clear now playing override
|
|
1515
|
-
// (matches Kotlin behavior; a flash must not carry over to a new track)
|
|
1032
|
+
// Clear now playing override when track changes (matches Kotlin behavior)
|
|
1516
1033
|
nowPlayingOverride = nil
|
|
1517
|
-
|
|
1518
|
-
latestTimedMetadata = nil
|
|
1519
|
-
// Hydrate the active track's `favorited` from the authoritative set so JS
|
|
1520
|
-
// consumers (useActiveTrack) match getActiveTrack()'s hydrated value — the
|
|
1521
|
-
// coordinator emits the raw queue track, whose flag isn't kept in sync.
|
|
1522
|
-
let hydrated = PlaybackActiveTrackChangedEvent(
|
|
1523
|
-
lastIndex: event.lastIndex,
|
|
1524
|
-
lastTrack: event.lastTrack,
|
|
1525
|
-
lastPosition: event.lastPosition,
|
|
1526
|
-
index: event.index,
|
|
1527
|
-
track: event.track.map(browserManager.hydrateFavorite),
|
|
1528
|
-
)
|
|
1529
|
-
activeTrackChangedEmitter.emit(hydrated)
|
|
1034
|
+
activeTrackChangedEmitter.emit(event)
|
|
1530
1035
|
// Also notify now playing changed when track changes
|
|
1531
1036
|
applyNowPlayingMetadata()
|
|
1532
1037
|
}
|
|
@@ -1535,60 +1040,12 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1535
1040
|
onPlaybackProgressUpdated(event)
|
|
1536
1041
|
}
|
|
1537
1042
|
|
|
1538
|
-
public func playerDidFirePlaybackInterval() {
|
|
1539
|
-
onPlaybackInterval()
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
1043
|
public func playerDidChangePlayWhenReady(_ playWhenReady: Bool) {
|
|
1543
|
-
// Intent to play again is the earliest cancel signal for a pending session release.
|
|
1544
|
-
if playWhenReady { cancelSessionRelease() }
|
|
1545
1044
|
onPlaybackPlayWhenReadyChanged(PlaybackPlayWhenReadyChangedEvent(playWhenReady: playWhenReady))
|
|
1546
1045
|
}
|
|
1547
1046
|
|
|
1548
1047
|
public func playerDidChangePlayingState(_ state: PlayingState) {
|
|
1549
1048
|
onPlaybackPlayingState(state)
|
|
1550
|
-
// Lazily activate the audio session the moment playback actually produces — or is buffering to
|
|
1551
|
-
// produce — output. This (not setup) is where we grab the session, so launching the app never
|
|
1552
|
-
// interrupts other apps' audio; the interruption only happens when the user truly starts
|
|
1553
|
-
// playback, which is correct. Keyed off play *state*, not `playWhenReady`, because the intent
|
|
1554
|
-
// can be set at setup (before any track) while output only begins here. Idempotent:
|
|
1555
|
-
// `setActive(true)` is a no-op when the session is already active.
|
|
1556
|
-
if state.playing || state.buffering {
|
|
1557
|
-
try? AVAudioSession.sharedInstance().setActive(true)
|
|
1558
|
-
// The now-playing session's activation request at link time predates this
|
|
1559
|
-
// (the audio session was still inactive, and the system may have declined);
|
|
1560
|
-
// re-request now so the surface can appear even if playback never produces
|
|
1561
|
-
// audio (first-ever load failing on a dead stream).
|
|
1562
|
-
player?.nowPlayingInfoController.reactivateSessionIfNeeded()
|
|
1563
|
-
// Output resumed — cancel any pending release armed by an earlier stop.
|
|
1564
|
-
cancelSessionRelease()
|
|
1565
|
-
}
|
|
1566
|
-
// Re-render so the formatter reacts to a stall starting or recovering (`stalled`) — a buffering
|
|
1567
|
-
// flag change that may not transition the coordinator state.
|
|
1568
|
-
applyNowPlayingMetadata()
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
public func playerShouldReleaseSession() {
|
|
1572
|
-
// Release the audio session so other apps' audio (Spotify, a podcast) can resume — debounced,
|
|
1573
|
-
// because iOS dislikes deactivating while our audio is still trailing off, and a quick
|
|
1574
|
-
// pause→resume shouldn't thrash the session. The coordinator only requests this on a genuine
|
|
1575
|
-
// stop (not an interruption, not mid-retry); a re-activation cancels it (see
|
|
1576
|
-
// `playerDidChangePlayWhenReady` and the activation above). Mirrors the now-playing flash timer.
|
|
1577
|
-
sessionReleaseWork?.cancel()
|
|
1578
|
-
let release = DispatchWorkItem { [weak self] in
|
|
1579
|
-
guard let self, self.player?.playWhenReady == false else { return }
|
|
1580
|
-
self.sessionReleaseWork = nil
|
|
1581
|
-
// `.notifyOthersOnDeactivation` signals other apps they may resume; without it the session
|
|
1582
|
-
// is released but nobody is told, so the user's audio would not come back.
|
|
1583
|
-
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
1584
|
-
}
|
|
1585
|
-
sessionReleaseWork = release
|
|
1586
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: release)
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
private func cancelSessionRelease() {
|
|
1590
|
-
sessionReleaseWork?.cancel()
|
|
1591
|
-
sessionReleaseWork = nil
|
|
1592
1049
|
}
|
|
1593
1050
|
|
|
1594
1051
|
public func playerDidEndQueue(_ event: PlaybackQueueEndedEvent) {
|
|
@@ -1604,7 +1061,6 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1604
1061
|
}
|
|
1605
1062
|
|
|
1606
1063
|
public func playerDidChangeShuffleEnabled(_ enabled: Bool) {
|
|
1607
|
-
shuffleChangedEmitter.emit(enabled)
|
|
1608
1064
|
onPlaybackShuffleModeChanged(enabled)
|
|
1609
1065
|
}
|
|
1610
1066
|
|
|
@@ -1620,10 +1076,7 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1620
1076
|
public func playerDidReceiveTimedMetadata(_ groups: [AVTimedMetadataGroup]) {
|
|
1621
1077
|
for group in groups {
|
|
1622
1078
|
if let metadata = TimedMetadata.from(items: group.items) {
|
|
1623
|
-
latestTimedMetadata = metadata
|
|
1624
1079
|
onTimedMetadata(metadata)
|
|
1625
|
-
// Re-render the now-playing so the formatter can reflect the live song.
|
|
1626
|
-
applyNowPlayingMetadata()
|
|
1627
1080
|
}
|
|
1628
1081
|
}
|
|
1629
1082
|
}
|
|
@@ -1740,6 +1193,10 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1740
1193
|
remoteSeek(position: position)
|
|
1741
1194
|
}
|
|
1742
1195
|
|
|
1196
|
+
public func remoteSetRating(rating _: Any) {
|
|
1197
|
+
// TODO: Convert rating to RemoteSetRatingEvent
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1743
1200
|
public func remotePlayId(id: String, index: Int?) {
|
|
1744
1201
|
let event = RemotePlayIdEvent(id: id, index: index.map { Double($0) })
|
|
1745
1202
|
if let handler = handleRemotePlayId {
|
|
@@ -1758,92 +1215,57 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1758
1215
|
}
|
|
1759
1216
|
}
|
|
1760
1217
|
|
|
1761
|
-
///
|
|
1762
|
-
///
|
|
1763
|
-
///
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1772
|
-
// No-criteria intent ("play «app»") → resume. The gate must NOT block
|
|
1773
|
-
// this: the gate blocks *finding* content, never *hearing* the active or
|
|
1774
|
-
// persisted track. The gate is checked below, for the search branch only.
|
|
1775
|
-
if criteria.isResume {
|
|
1776
|
-
if player.currentTrack != nil {
|
|
1777
|
-
browser.logger.info("resume: warm — playing current track")
|
|
1778
|
-
player.play()
|
|
1779
|
-
browser.showNowPlayingRequestedEmitter.emit(())
|
|
1780
|
-
completion(true)
|
|
1781
|
-
} else if let state = browser.playbackStateStore.load() {
|
|
1782
|
-
browser.logger.info("resume: cold — restoring persisted track")
|
|
1783
|
-
let track = state.track.toNitro()
|
|
1784
|
-
let startMs = (track.live == true) ? nil : state.positionMs
|
|
1785
|
-
// Match Android resume: re-expand the full queue from the track's contextual
|
|
1786
|
-
// URL (parent container → siblings + selected index). Fall back to the single
|
|
1787
|
-
// track when the url isn't contextual or expansion fails.
|
|
1788
|
-
if let url = state.track.url,
|
|
1789
|
-
let expanded = try? await browser.browserManager.expandQueueFromContextualUrl(url)
|
|
1790
|
-
{
|
|
1791
|
-
player.setQueue(expanded.tracks, initialIndex: expanded.selectedIndex, startPositionMs: startMs, playWhenReady: true)
|
|
1792
|
-
} else {
|
|
1793
|
-
player.setQueue([track], initialIndex: 0, startPositionMs: startMs, playWhenReady: true)
|
|
1794
|
-
}
|
|
1795
|
-
browser.showNowPlayingRequestedEmitter.emit(())
|
|
1796
|
-
completion(true)
|
|
1797
|
-
} else {
|
|
1798
|
-
browser.logger.error("resume: nothing playing and nothing persisted → no-op")
|
|
1799
|
-
completion(false) // nothing playing and nothing persisted
|
|
1800
|
-
}
|
|
1801
|
-
return
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
// Assemble the Nitro SearchParams here (MainActor) from the criteria's
|
|
1805
|
-
// Sendable fields, so the structured mode/genre/… reach the request like
|
|
1806
|
-
// Android. Today the API still text-searches `q`; the rest is forward-compat.
|
|
1807
|
-
let params = SearchParams(
|
|
1808
|
-
mode: criteria.searchMode.flatMap { SearchMode(fromString: $0) },
|
|
1809
|
-
query: criteria.query,
|
|
1810
|
-
genre: criteria.genre,
|
|
1811
|
-
artist: criteria.artist,
|
|
1812
|
-
album: criteria.album,
|
|
1813
|
-
title: criteria.title,
|
|
1814
|
-
playlist: criteria.playlist,
|
|
1815
|
-
// .currentlyPlaying can't reach here — isResume routed it to resume.
|
|
1816
|
-
reference: criteria.reference == .my ? .my : .unknown,
|
|
1817
|
-
)
|
|
1818
|
-
|
|
1819
|
-
// Search is *finding* new content — refused when the gate decides this
|
|
1820
|
-
// request is gated (resume above is unaffected: the gate never blocks
|
|
1821
|
-
// hearing, and the gate check stays after the resume check).
|
|
1822
|
-
let outcome = await browser.gateDecision(
|
|
1823
|
-
for: NativeGateRequest(reason: .search, path: nil, search: params),
|
|
1824
|
-
)
|
|
1825
|
-
guard !outcome.gated else {
|
|
1826
|
-
browser.logger.info("handlePlayMediaIntent: search refused — gated")
|
|
1827
|
-
browser.onGate(GateEvent(reason: .search))
|
|
1828
|
-
completion(false)
|
|
1829
|
-
return
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1218
|
+
/// Handles an INPlayMediaIntent by searching for the given term and starting playback.
|
|
1219
|
+
///
|
|
1220
|
+
/// Host apps should call this from their `INPlayMediaIntentHandling` implementation.
|
|
1221
|
+
/// Uses the browser's search route to find tracks, then queues and plays them.
|
|
1222
|
+
///
|
|
1223
|
+
/// - Parameters:
|
|
1224
|
+
/// - searchTerm: The search query from Siri.
|
|
1225
|
+
/// - completion: Called with `true` if tracks were found and queued, `false` otherwise.
|
|
1226
|
+
public func handlePlayMediaIntent(searchTerm: String, completion: @escaping @Sendable (Bool) -> Void) {
|
|
1227
|
+
Task {
|
|
1832
1228
|
do {
|
|
1833
|
-
|
|
1229
|
+
let resolved = try await self.browserManager.search(searchTerm)
|
|
1230
|
+
guard let tracks = resolved.children, !tracks.isEmpty else {
|
|
1834
1231
|
completion(false)
|
|
1835
1232
|
return
|
|
1836
1233
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1234
|
+
await MainActor.run {
|
|
1235
|
+
self.getPlayer()?.setQueue(tracks, initialIndex: 0, playWhenReady: true)
|
|
1236
|
+
}
|
|
1839
1237
|
completion(true)
|
|
1840
1238
|
} catch {
|
|
1841
|
-
|
|
1239
|
+
self.logger.error("handlePlayMediaIntent failed: \(error.localizedDescription)")
|
|
1842
1240
|
completion(false)
|
|
1843
1241
|
}
|
|
1844
1242
|
}
|
|
1845
1243
|
}
|
|
1846
1244
|
|
|
1245
|
+
public func remoteLike() {
|
|
1246
|
+
if let handler = handleRemoteLike {
|
|
1247
|
+
handler()
|
|
1248
|
+
} else {
|
|
1249
|
+
onRemoteLike()
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
public func remoteDislike() {
|
|
1254
|
+
if let handler = handleRemoteDislike {
|
|
1255
|
+
handler()
|
|
1256
|
+
} else {
|
|
1257
|
+
onRemoteDislike()
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
public func remoteBookmark() {
|
|
1262
|
+
if let handler = handleRemoteBookmark {
|
|
1263
|
+
handler()
|
|
1264
|
+
} else {
|
|
1265
|
+
onRemoteBookmark()
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1847
1269
|
public func remoteChangeRepeatMode(mode: RepeatMode) {
|
|
1848
1270
|
// Apply the repeat mode change from CarPlay/lock screen
|
|
1849
1271
|
try? setRepeatMode(mode: mode)
|