react-native-audio-browser 0.2.0-next.9 → 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 +148 -433
- 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 +196 -804
- 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/ServiceBinding.kt +0 -46
- 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/ServiceBindingTest.kt +0 -68
- 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 -123
- 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
|
@@ -16,10 +16,6 @@ import os.log
|
|
|
16
16
|
/// Only safe when the caller blocks until the MainActor work completes (DispatchQueue.main.sync).
|
|
17
17
|
private struct UncheckedSendableBox<T>: @unchecked Sendable { let value: T }
|
|
18
18
|
|
|
19
|
-
/// `@unchecked Sendable` because it must be: the spec's requirements are all
|
|
20
|
-
/// nonisolated and Nitro calls them synchronously on the JS thread, so the class
|
|
21
|
-
/// can't be `@MainActor`. Isolation is therefore per-property, and `@unchecked`
|
|
22
|
-
/// means the compiler won't tell you when a new one needs it.
|
|
23
19
|
public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
24
20
|
private let logger = Logger(subsystem: "com.audiobrowser", category: "AudioBrowser")
|
|
25
21
|
|
|
@@ -28,52 +24,20 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
28
24
|
/// Shared instance for CarPlay access. Set when HybridAudioBrowser is created.
|
|
29
25
|
private(set) nonisolated(unsafe) weak static var shared: HybridAudioBrowser?
|
|
30
26
|
|
|
31
|
-
/// Fires when a new instance becomes `shared` (e.g. on a JS runtime reload),
|
|
32
|
-
/// so connected external controllers (CarPlay) can re-subscribe — their
|
|
33
|
-
/// listeners sit on the previous instance's emitters and would otherwise go
|
|
34
|
-
/// silent.
|
|
35
|
-
static let instanceChangedEmitter = Emitter<Void>()
|
|
36
|
-
|
|
37
27
|
// MARK: - Private Properties
|
|
38
28
|
|
|
39
|
-
/// Main-confined by convention, not annotation: `deinit` reads it as a
|
|
40
|
-
/// safety net and can't hop. Reach it from `onMainActor` / `@MainActor`.
|
|
41
29
|
private var player: TrackPlayer?
|
|
42
30
|
private let networkMonitor = NetworkMonitor()
|
|
43
|
-
private let playbackStateStore = PlaybackStateStore()
|
|
44
31
|
let browserManager = BrowserManager()
|
|
45
32
|
private let trackSelector: TrackSelector
|
|
46
33
|
private var volumeObservation: NSKeyValueObservation?
|
|
47
34
|
private var routeChangeObserver: NSObjectProtocol?
|
|
48
|
-
private var
|
|
49
|
-
private
|
|
50
|
-
/// Re-applies our resolved audio-session category. Captured at setup so a
|
|
51
|
-
/// media-services reset (which clears the category) can restore it.
|
|
52
|
-
@MainActor private var applyAudioSessionCategory: () -> Void = {}
|
|
53
|
-
@MainActor private var nowPlayingOverride: NowPlayingUpdate?
|
|
54
|
-
/// When false, the now-playing surface uses the raw track fields (override + formatter ignored).
|
|
55
|
-
@MainActor private var nowPlayingMetadataEnabled = true
|
|
56
|
-
/// Customizes the now-playing title/subtitle from the track + live timed metadata + playback state.
|
|
57
|
-
@MainActor private var nowPlayingMetadataFormatter: ((_ params: FormatNowPlayingParams) -> Promise<NowPlayingUpdate?>)?
|
|
58
|
-
/// Initial player state staged before the player exists — from setup options or the imperative
|
|
59
|
-
/// setters called pre-setup. Strict last-write-wins; consumed when the player comes up.
|
|
60
|
-
@MainActor private var pendingPlayWhenReady: Bool?
|
|
61
|
-
@MainActor private var pendingRepeatMode: RepeatMode?
|
|
62
|
-
/// Latest live timed (ICY/ID3) metadata, passed to the formatter. Cleared on track change.
|
|
63
|
-
@MainActor private var latestTimedMetadata: TimedMetadata?
|
|
64
|
-
@MainActor private let playerOptions = PlayerUpdateOptions()
|
|
35
|
+
private var nowPlayingOverride: NowPlayingUpdate?
|
|
36
|
+
private let playerOptions = PlayerUpdateOptions()
|
|
65
37
|
|
|
66
38
|
/// Configured playback rates for the playback-rate capability (for CarPlay rate cycling)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@MainActor var carPlayNowPlayingButtons: [CarPlayNowPlayingButton] {
|
|
70
|
-
playerOptions.carPlayNowPlayingButtons
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/// Written from the JS thread, the cooperative pool and MainActor, with a
|
|
74
|
-
/// `didSet` that calls back into Nitro. MainActor-isolated so the compiler
|
|
75
|
-
/// requires an `onMainActor` hop at every access.
|
|
76
|
-
@MainActor private var lastNavigationError: NavigationError? {
|
|
39
|
+
var playbackRates: [Double] { playerOptions.playbackRates }
|
|
40
|
+
private var lastNavigationError: NavigationError? {
|
|
77
41
|
didSet {
|
|
78
42
|
// Skip if both nil (no real change)
|
|
79
43
|
guard oldValue != nil || lastNavigationError != nil else { return }
|
|
@@ -81,8 +45,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
81
45
|
}
|
|
82
46
|
}
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
@MainActor private var lastFormattedNavigationError: FormattedNavigationError? {
|
|
48
|
+
private var lastFormattedNavigationError: FormattedNavigationError? {
|
|
86
49
|
didSet {
|
|
87
50
|
// Skip if both nil (no real change)
|
|
88
51
|
guard oldValue != nil || lastFormattedNavigationError != nil else { return }
|
|
@@ -100,21 +63,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
100
63
|
public let queueChangedEmitter = Emitter<[Track]>()
|
|
101
64
|
public let navigationErrorEmitter = Emitter<NavigationErrorEvent>()
|
|
102
65
|
public let repeatModeChangedEmitter = Emitter<RepeatModeChangedEvent>()
|
|
103
|
-
public let shuffleChangedEmitter = Emitter<Bool>()
|
|
104
66
|
public let externalContentChangedEmitter = Emitter<String>()
|
|
105
|
-
/// Signals that the gate's active state changed (set or cleared). Carries
|
|
106
|
-
/// the active flag; per-request chrome is obtained at each serve site via
|
|
107
|
-
/// `gateDecision(for:)`, so the emitter only tells surfaces to re-render.
|
|
108
|
-
public let gateChangedEmitter = Emitter<Bool>()
|
|
109
|
-
/// Fired when a voice media intent (`handlePlayMediaIntent`) successfully
|
|
110
|
-
/// starts playback. CarPlay surfaces the Now Playing template in response, so
|
|
111
|
-
/// the user lands on the playing station (with the rest of the results in Up
|
|
112
|
-
/// Next) rather than the screen they invoked Siri from. No-op where unobserved.
|
|
113
|
-
public let showNowPlayingRequestedEmitter = Emitter<Void>()
|
|
114
|
-
/// Fires after `updateOptions` applies a change, so external-surface managers
|
|
115
|
-
/// (CarPlay) can refresh UI driven by now-runtime-updatable options
|
|
116
|
-
/// (now-playing buttons, Up Next).
|
|
117
|
-
public let playerOptionsChangedEmitter = Emitter<Void>()
|
|
118
67
|
|
|
119
68
|
// MARK: - Thread Safety
|
|
120
69
|
|
|
@@ -122,9 +71,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
122
71
|
/// because DispatchQueue.main.sync blocks the caller — no concurrent access window.
|
|
123
72
|
private func onMainActor<T>(_ work: @MainActor () -> T) -> T {
|
|
124
73
|
if Thread.isMainThread {
|
|
125
|
-
MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
74
|
+
return MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
126
75
|
} else {
|
|
127
|
-
DispatchQueue.main.sync {
|
|
76
|
+
return DispatchQueue.main.sync {
|
|
128
77
|
MainActor.assumeIsolated { UncheckedSendableBox(value: work()) }.value
|
|
129
78
|
}
|
|
130
79
|
}
|
|
@@ -132,10 +81,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
132
81
|
|
|
133
82
|
private func onMainActor<T>(_ work: @MainActor () throws -> T) throws -> T {
|
|
134
83
|
if Thread.isMainThread {
|
|
135
|
-
try MainActor.assumeIsolated {
|
|
84
|
+
return try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
|
|
136
85
|
} else {
|
|
137
|
-
try DispatchQueue.main.sync {
|
|
138
|
-
try MainActor.assumeIsolated {
|
|
86
|
+
return try DispatchQueue.main.sync {
|
|
87
|
+
try MainActor.assumeIsolated { UncheckedSendableBox(value: try work()) }.value
|
|
139
88
|
}
|
|
140
89
|
}
|
|
141
90
|
}
|
|
@@ -162,15 +111,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
162
111
|
}
|
|
163
112
|
|
|
164
113
|
public var configuration: NativeBrowserConfiguration = .init(
|
|
165
|
-
path: nil, request: nil,
|
|
166
|
-
singleTrack: nil, handleTrackLoad: nil,
|
|
167
|
-
|
|
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,
|
|
168
117
|
) {
|
|
169
118
|
didSet {
|
|
170
|
-
// Copied here, on the setter's thread: the Task below outlives this call,
|
|
171
|
-
// and Nitro can reassign `configuration` — a C++ struct holding
|
|
172
|
-
// std::string/std::function — from the JS thread while it runs.
|
|
173
|
-
let configuredPath = configuration.path
|
|
174
119
|
onMainActor { browserManager.config = BrowserConfig(from: configuration) }
|
|
175
120
|
|
|
176
121
|
// Query tabs and navigate to initial path after config is set (matches Kotlin behavior)
|
|
@@ -181,9 +126,12 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
181
126
|
Task {
|
|
182
127
|
let tabs = try? await browserManager.queryTabs()
|
|
183
128
|
// Navigate to configured path, first tab, or "/"
|
|
184
|
-
let initialPath =
|
|
129
|
+
let initialPath = configuration.path ?? tabs?.first?.url ?? "/"
|
|
185
130
|
// Clear error before navigation (matches Kotlin clearNavigationError())
|
|
186
|
-
|
|
131
|
+
await MainActor.run {
|
|
132
|
+
lastNavigationError = nil
|
|
133
|
+
lastFormattedNavigationError = nil
|
|
134
|
+
}
|
|
187
135
|
do {
|
|
188
136
|
try await browserManager.navigate(initialPath)
|
|
189
137
|
} catch {
|
|
@@ -201,25 +149,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
201
149
|
public var onNavigationError: (NavigationErrorEvent) -> Void = { _ in }
|
|
202
150
|
public var onFormattedNavigationError: (FormattedNavigationError?) -> Void = { _ in }
|
|
203
151
|
|
|
204
|
-
/// Per-request gate decision, set by JS. Native calls it at a serve site to
|
|
205
|
-
/// learn whether a browse path / search interaction should be gated and with
|
|
206
|
-
/// what chrome. The double `Promise` mirrors every other native→JS
|
|
207
|
-
/// value-returning callback (e.g. `searchCallback`): the outer resolves to the
|
|
208
|
-
/// JS-side Promise, the inner to the decision.
|
|
209
|
-
///
|
|
210
|
-
/// Default fails CLOSED (`gated: true`). This default is only consulted in the
|
|
211
|
-
/// init window where `hasResolver` was already recorded true but JS hasn't
|
|
212
|
-
/// (re)bound the real resolver yet (JS reload / instance churn re-seeding gate
|
|
213
|
-
/// state, e.g. CarPlay re-seeding `isGated` at scene connect). Serving content
|
|
214
|
-
/// in that window would be a fail-open leak — same direction as the
|
|
215
|
-
/// resolver-error path in `gateDecision`. Matches Android's default.
|
|
216
|
-
public var resolveGate: (NativeGateRequest) -> Promise<Promise<GateDecision>> = { _ in
|
|
217
|
-
Promise.resolved(withResult: Promise.resolved(withResult: GateDecision(gated: true, gate: nil)))
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/// Fired when a request is gated (served the gate). Set by JS.
|
|
221
|
-
public var onGate: (GateEvent) -> Void = { _ in }
|
|
222
|
-
|
|
223
152
|
// MARK: - Player Callbacks
|
|
224
153
|
|
|
225
154
|
public var onChapterMetadata: ([ChapterMetadata]) -> Void = { _ in }
|
|
@@ -230,7 +159,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
230
159
|
public var onPlaybackPlayWhenReadyChanged: (PlaybackPlayWhenReadyChangedEvent) -> Void = { _ in }
|
|
231
160
|
public var onPlaybackPlayingState: (PlayingState) -> Void = { _ in }
|
|
232
161
|
public var onPlaybackProgressUpdated: (PlaybackProgressUpdatedEvent) -> Void = { _ in }
|
|
233
|
-
public var onPlaybackInterval: () -> Void = {}
|
|
234
162
|
public var onPlaybackQueueEnded: (PlaybackQueueEndedEvent) -> Void = { _ in }
|
|
235
163
|
public var onPlaybackQueueChanged: ([Track]) -> Void = { _ in }
|
|
236
164
|
public var onPlaybackRepeatModeChanged: (RepeatModeChangedEvent) -> Void = { _ in }
|
|
@@ -240,8 +168,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
240
168
|
|
|
241
169
|
// MARK: - Remote Callbacks
|
|
242
170
|
|
|
171
|
+
public var onRemoteBookmark: () -> Void = {}
|
|
172
|
+
public var onRemoteDislike: () -> Void = {}
|
|
243
173
|
public var onRemoteJumpBackward: (RemoteJumpBackwardEvent) -> Void = { _ in }
|
|
244
174
|
public var onRemoteJumpForward: (RemoteJumpForwardEvent) -> Void = { _ in }
|
|
175
|
+
public var onRemoteLike: () -> Void = {}
|
|
245
176
|
public var onRemoteNext: () -> Void = {}
|
|
246
177
|
public var onRemotePause: () -> Void = {}
|
|
247
178
|
public var onRemotePlay: () -> Void = {}
|
|
@@ -249,13 +180,17 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
249
180
|
public var onRemotePlaySearch: (RemotePlaySearchEvent) -> Void = { _ in }
|
|
250
181
|
public var onRemotePrevious: () -> Void = {}
|
|
251
182
|
public var onRemoteSeek: (RemoteSeekEvent) -> Void = { _ in }
|
|
183
|
+
public var onRemoteSetRating: (RemoteSetRatingEvent) -> Void = { _ in }
|
|
252
184
|
public var onRemoteSkip: (RemoteSkipEvent) -> Void = { _ in }
|
|
253
185
|
public var onRemoteStop: () -> Void = {}
|
|
254
186
|
|
|
255
187
|
// MARK: - Remote Handlers (optional overrides from JS)
|
|
256
188
|
|
|
189
|
+
public var handleRemoteBookmark: (() -> Void)?
|
|
190
|
+
public var handleRemoteDislike: (() -> Void)?
|
|
257
191
|
public var handleRemoteJumpBackward: ((RemoteJumpBackwardEvent) -> Void)?
|
|
258
192
|
public var handleRemoteJumpForward: ((RemoteJumpForwardEvent) -> Void)?
|
|
193
|
+
public var handleRemoteLike: (() -> Void)?
|
|
259
194
|
public var handleRemoteNext: (() -> Void)?
|
|
260
195
|
public var handleRemotePause: (() -> Void)?
|
|
261
196
|
public var handleRemotePlay: (() -> Void)?
|
|
@@ -273,9 +208,10 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
273
208
|
public var onNowPlayingChanged: (NowPlayingMetadata) -> Void = { _ in }
|
|
274
209
|
public var onOnlineChanged: (Bool) -> Void = { _ in } {
|
|
275
210
|
didSet {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
211
|
+
networkMonitor.onChanged = { [weak self] isOnline in
|
|
212
|
+
self?.onOnlineChanged(isOnline)
|
|
213
|
+
}
|
|
214
|
+
// Immediately notify current state
|
|
279
215
|
onOnlineChanged(networkMonitor.isOnline)
|
|
280
216
|
}
|
|
281
217
|
}
|
|
@@ -289,7 +225,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
289
225
|
}
|
|
290
226
|
}
|
|
291
227
|
|
|
292
|
-
public var
|
|
228
|
+
public var onIosOutputChanged: (IosOutput) -> Void = { _ in } {
|
|
293
229
|
didSet {
|
|
294
230
|
setupRouteChangeObserver()
|
|
295
231
|
}
|
|
@@ -309,11 +245,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
309
245
|
HybridAudioBrowser.shared?.player?.destroy()
|
|
310
246
|
playerAndConfiguredBrowser.reset()
|
|
311
247
|
HybridAudioBrowser.shared = self
|
|
312
|
-
// Tell connected external controllers (CarPlay) to re-subscribe against
|
|
313
|
-
// this instance. Unconditional: `shared` is weak, so on a JS reload the
|
|
314
|
-
// old instance may already be gone — "shared was nil" does not mean no
|
|
315
|
-
// controller is subscribed to a dead instance's emitters.
|
|
316
|
-
HybridAudioBrowser.instanceChangedEmitter.emit(())
|
|
317
248
|
}
|
|
318
249
|
setupEmitterToNitroForwarding()
|
|
319
250
|
setupBrowserCallbacks()
|
|
@@ -382,50 +313,33 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
382
313
|
private func handleNavigationError(_ error: Error, path: String) {
|
|
383
314
|
let navError = NavigationError.from(error)
|
|
384
315
|
|
|
385
|
-
|
|
316
|
+
lastNavigationError = navError
|
|
386
317
|
|
|
387
318
|
// Format the error (async if using JS callback, sync for defaults)
|
|
388
319
|
let defaultFormatted = navError.defaultFormatted()
|
|
389
320
|
if let formatter = onMainActor({ browserManager.config.formatNavigationError }) {
|
|
390
321
|
let params = FormatNavigationErrorParams(error: navError, defaultFormatted: defaultFormatted, path: path)
|
|
391
|
-
// Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes
|
|
392
|
-
// The result callbacks are @Sendable: a plain closure would inherit MainActor
|
|
393
|
-
// isolation from this block, and Nitro resolves its promises synchronously on the
|
|
394
|
-
// JS thread — Swift 6.2's dynamic isolation check then traps on closure entry.
|
|
395
|
-
// They therefore hop back explicitly, like every other writer.
|
|
322
|
+
// Dispatch to main thread for the Nitro bridge call to avoid C++ noexcept crashes
|
|
396
323
|
DispatchQueue.main.async { [weak self] in
|
|
397
324
|
formatter(params)
|
|
398
|
-
.then {
|
|
399
|
-
self?.
|
|
325
|
+
.then { customDisplay in
|
|
326
|
+
self?.lastFormattedNavigationError = customDisplay ?? defaultFormatted
|
|
400
327
|
}
|
|
401
|
-
.catch {
|
|
402
|
-
self?.
|
|
328
|
+
.catch { _ in
|
|
329
|
+
self?.lastFormattedNavigationError = defaultFormatted
|
|
403
330
|
}
|
|
404
331
|
}
|
|
405
332
|
} else {
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/// Callable from any thread — the Nitro promise callbacks below resolve on
|
|
411
|
-
/// the JS thread.
|
|
412
|
-
private func setFormattedNavigationError(_ formatted: FormattedNavigationError?) {
|
|
413
|
-
onMainActor { lastFormattedNavigationError = formatted }
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/// Synchronous on purpose: the `didSet`s notify JS, which has to happen
|
|
417
|
-
/// before the navigation that may set them again.
|
|
418
|
-
private func clearNavigationError() {
|
|
419
|
-
onMainActor {
|
|
420
|
-
lastNavigationError = nil
|
|
421
|
-
lastFormattedNavigationError = nil
|
|
333
|
+
lastFormattedNavigationError = defaultFormatted
|
|
422
334
|
}
|
|
423
335
|
}
|
|
424
336
|
|
|
425
337
|
// MARK: - Browser Methods
|
|
426
338
|
|
|
427
339
|
public func navigatePath(path: String) throws {
|
|
428
|
-
|
|
340
|
+
// Clear error synchronously before starting navigation (didSet notifies JS)
|
|
341
|
+
lastNavigationError = nil
|
|
342
|
+
lastFormattedNavigationError = nil
|
|
429
343
|
Task {
|
|
430
344
|
do {
|
|
431
345
|
try await browserManager.navigate(path)
|
|
@@ -436,18 +350,15 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
436
350
|
}
|
|
437
351
|
|
|
438
352
|
public func navigateTrack(track: Track) throws {
|
|
439
|
-
|
|
440
|
-
// MainActor anyway — a bare Task read the reference off the cooperative
|
|
441
|
-
// pool, racing the assignment in `setupPlayer`.
|
|
442
|
-
Task { @MainActor in
|
|
353
|
+
Task {
|
|
443
354
|
guard let player else { return }
|
|
444
355
|
let result = await trackSelector.select(track: track, player: player)
|
|
445
356
|
switch result {
|
|
446
|
-
case
|
|
357
|
+
case .play(let intent):
|
|
447
358
|
executePlayback(intent)
|
|
448
359
|
case .intercepted:
|
|
449
360
|
break
|
|
450
|
-
case
|
|
361
|
+
case .browse(let url):
|
|
451
362
|
navigateToBrowsableUrl(url)
|
|
452
363
|
case .none:
|
|
453
364
|
break
|
|
@@ -458,22 +369,24 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
458
369
|
private func executePlayback(_ intent: TrackSelector.PlaybackIntent) {
|
|
459
370
|
onMainActor {
|
|
460
371
|
switch intent {
|
|
461
|
-
case
|
|
372
|
+
case .skipTo(let index):
|
|
462
373
|
do {
|
|
463
374
|
try player?.skipTo(index, playWhenReady: true)
|
|
464
375
|
} catch {
|
|
465
376
|
logger.error("Failed to skip to track at index \(index): \(error)")
|
|
466
377
|
}
|
|
467
|
-
case
|
|
378
|
+
case .setQueue(let tracks, let startIndex, let sourcePath):
|
|
468
379
|
player?.setQueue(tracks, initialIndex: startIndex, playWhenReady: true, sourcePath: sourcePath)
|
|
469
|
-
case
|
|
380
|
+
case .loadTrack(let track):
|
|
470
381
|
player?.load(track, playWhenReady: true)
|
|
471
382
|
}
|
|
472
383
|
}
|
|
473
384
|
}
|
|
474
385
|
|
|
475
386
|
private func navigateToBrowsableUrl(_ url: String) {
|
|
476
|
-
|
|
387
|
+
// Clear error synchronously before starting navigation (didSet notifies JS)
|
|
388
|
+
lastNavigationError = nil
|
|
389
|
+
lastFormattedNavigationError = nil
|
|
477
390
|
Task {
|
|
478
391
|
do {
|
|
479
392
|
try await browserManager.navigate(url)
|
|
@@ -496,19 +409,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
496
409
|
}
|
|
497
410
|
|
|
498
411
|
public func getNavigationError() throws -> NavigationError? {
|
|
499
|
-
|
|
412
|
+
lastNavigationError
|
|
500
413
|
}
|
|
501
414
|
|
|
502
415
|
public func getFormattedNavigationError() throws -> FormattedNavigationError? {
|
|
503
|
-
|
|
416
|
+
lastFormattedNavigationError
|
|
504
417
|
}
|
|
505
418
|
|
|
506
|
-
/// Internal signal sent on `externalContentChangedEmitter` to tell CarPlay to
|
|
507
|
-
/// refresh every displayed template. Not a real path (paths are always
|
|
508
|
-
/// `/…`), so it can't collide with one; kept internal — the public API is
|
|
509
|
-
/// `invalidateAllContent()`.
|
|
510
|
-
static let invalidateAllSentinel = "__rnab_invalidate_all__"
|
|
511
|
-
|
|
512
419
|
public func notifyContentChanged(path: String) throws {
|
|
513
420
|
onMainActor { browserManager.invalidateContentCache(path) }
|
|
514
421
|
|
|
@@ -527,168 +434,22 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
527
434
|
}
|
|
528
435
|
}
|
|
529
436
|
|
|
530
|
-
public func invalidateAllContent() throws {
|
|
531
|
-
onMainActor { browserManager.clearContentCache() }
|
|
532
|
-
|
|
533
|
-
// Tell external controllers (CarPlay) to refresh every displayed template.
|
|
534
|
-
externalContentChangedEmitter.emit(Self.invalidateAllSentinel)
|
|
535
|
-
|
|
536
|
-
// Re-query the tabs source too: a callback source can resolve to new
|
|
537
|
-
// titles after an invalidation (e.g. a locale switch). BrowserManager
|
|
538
|
-
// emits tabsChanged only when the result actually differs, and a failed
|
|
539
|
-
// re-query keeps the current tabs. (Android needs no equivalent — its
|
|
540
|
-
// root re-query already runs queryTabs(); the web stub re-queries tabs on
|
|
541
|
-
// the re-navigation.)
|
|
542
|
-
Task {
|
|
543
|
-
_ = try? await browserManager.queryTabs()
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Re-resolve the JS-facing current path with the cache cleared.
|
|
547
|
-
Task {
|
|
548
|
-
do {
|
|
549
|
-
try await browserManager.refresh()
|
|
550
|
-
} catch {
|
|
551
|
-
handleNavigationError(error, path: onMainActor { browserManager.getPath() })
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
437
|
public func setFavorites(favorites: [String]) throws {
|
|
557
438
|
onMainActor { browserManager.setFavorites(favorites) }
|
|
558
439
|
}
|
|
559
440
|
|
|
560
|
-
// MARK: - Gate
|
|
561
|
-
|
|
562
|
-
/// The gate's default chrome, if one was set. Served when the resolver
|
|
563
|
-
/// returns a gated decision with no per-request override, and for the static
|
|
564
|
-
/// gate (no resolver). May be nil for a resolver-only gate.
|
|
565
|
-
private(set) var defaultChrome: Gate?
|
|
566
|
-
|
|
567
|
-
/// Whether JS installed a per-request resolver. When false the gate is static
|
|
568
|
-
/// — every request is gated with `defaultChrome` and the JS hop is skipped.
|
|
569
|
-
private(set) var hasResolver = false
|
|
570
|
-
|
|
571
|
-
/// True once `setGate` runs, until `clearGate`. While active, external
|
|
572
|
-
/// surfaces (CarPlay) keep their tabs visible but consult `gateDecision(for:)`
|
|
573
|
-
/// per request, rendering the gate chrome where gated and refusing
|
|
574
|
-
/// external-surface search. Playback, the queue and Now Playing are
|
|
575
|
-
/// unaffected — a gate blocks finding content, never hearing it.
|
|
576
|
-
private(set) var isGateActive = false
|
|
577
|
-
|
|
578
|
-
/// The minimal built-in gate served when a request is gated but neither a
|
|
579
|
-
/// per-request override nor a stored default chrome exists (resolver-only
|
|
580
|
-
/// gate returning `true`).
|
|
581
|
-
static let builtInGate = Gate(title: "Unavailable", message: nil)
|
|
582
|
-
|
|
583
|
-
/// The outcome of a single gate decision: whether to gate, and (if so) the
|
|
584
|
-
/// chrome to render.
|
|
585
|
-
struct GateOutcome {
|
|
586
|
-
let gated: Bool
|
|
587
|
-
let chrome: Gate?
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
public func setGate(gate: Gate?, hasResolver: Bool) throws {
|
|
591
|
-
onMainActor {
|
|
592
|
-
defaultChrome = gate
|
|
593
|
-
self.hasResolver = hasResolver
|
|
594
|
-
isGateActive = true
|
|
595
|
-
gateChangedEmitter.emit(true)
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
public func clearGate() throws {
|
|
600
|
-
onMainActor {
|
|
601
|
-
guard isGateActive else { return }
|
|
602
|
-
defaultChrome = nil
|
|
603
|
-
hasResolver = false
|
|
604
|
-
isGateActive = false
|
|
605
|
-
gateChangedEmitter.emit(false)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/// The single choke point for every gate enforcement site. Asks whether
|
|
610
|
-
/// `request` should be gated and, if so, with what chrome (override → stored
|
|
611
|
-
/// default → built-in). When no resolver is installed the gate is static and
|
|
612
|
-
/// resolves without a JS hop.
|
|
613
|
-
///
|
|
614
|
-
/// Resolver errors **fail CLOSED by design**: a thrown/rejected resolver, or a
|
|
615
|
-
/// rejection at either `.await()` layer (bridge tear-down on JS reload, the JS
|
|
616
|
-
/// runtime mid-reload, a serialization error) cannot prove the request is
|
|
617
|
-
/// allowed — and a gate exists to withhold content, so serving on error would
|
|
618
|
-
/// be a content leak (a paywall bypass). On error we gate with the stored
|
|
619
|
-
/// default / built-in chrome so the user still sees a coherent gate page. A
|
|
620
|
-
/// *successful* resolver returning `gated: false` still allows the request.
|
|
621
|
-
@MainActor
|
|
622
|
-
func gateDecision(for request: NativeGateRequest) async -> GateOutcome {
|
|
623
|
-
guard isGateActive else { return GateOutcome(gated: false, chrome: nil) }
|
|
624
|
-
if !hasResolver { return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate) }
|
|
625
|
-
guard let decision = try? await resolveGate(request).await().await() else {
|
|
626
|
-
// Resolver error: fail CLOSED (see doc comment) — gate with the default /
|
|
627
|
-
// built-in so the gate page still renders rather than leaking content.
|
|
628
|
-
return GateOutcome(gated: true, chrome: defaultChrome ?? Self.builtInGate)
|
|
629
|
-
}
|
|
630
|
-
guard decision.gated else { return GateOutcome(gated: false, chrome: nil) } // explicit allow
|
|
631
|
-
return GateOutcome(gated: true, chrome: decision.gate ?? defaultChrome ?? Self.builtInGate)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// MARK: - Car Connection (CarPlay)
|
|
635
|
-
|
|
636
|
-
/// Static because the CarPlay scene can connect before the JS runtime
|
|
637
|
-
/// creates the shared instance (cold start in the car).
|
|
638
|
-
private nonisolated(unsafe) static var carPlayConnected = false
|
|
639
|
-
|
|
640
|
-
/// Called by the CarPlay scene delegate (via `RNABCarPlayController`) on
|
|
641
|
-
/// scene connect/disconnect — not from controller start/stop, which also
|
|
642
|
-
/// cycle on a JS reload and would emit spurious connection changes.
|
|
643
|
-
static func setCarPlayConnected(_ connected: Bool) {
|
|
644
|
-
guard carPlayConnected != connected else { return }
|
|
645
|
-
carPlayConnected = connected
|
|
646
|
-
shared?.onCarConnectedChanged(connected)
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
public func isCarConnected() throws -> Bool {
|
|
650
|
-
HybridAudioBrowser.carPlayConnected
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
public var onCarConnectedChanged: (Bool) -> Void = { _ in } {
|
|
654
|
-
didSet {
|
|
655
|
-
// Immediately notify current state (the scene may have connected before
|
|
656
|
-
// this JS runtime subscribed).
|
|
657
|
-
onCarConnectedChanged(HybridAudioBrowser.carPlayConnected)
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
441
|
// MARK: - Player Setup
|
|
662
442
|
|
|
663
|
-
public func setupPlayer(options:
|
|
443
|
+
public func setupPlayer(options: PartialSetupPlayerOptions) throws -> Promise<Void> {
|
|
664
444
|
Promise.async {
|
|
665
|
-
// Configure
|
|
666
|
-
// options like `allowBluetooth` / `allowAirPlay`), but do NOT activate it here. Activating a
|
|
667
|
-
// non-mixable `.playback` session interrupts other apps' audio — so activating at setup
|
|
668
|
-
// pauses whatever the user is listening to (Safari, Spotify, a podcast) the instant our app
|
|
669
|
-
// launches, before they play anything. Setting the category is silent; only activation
|
|
670
|
-
// interrupts. The session is activated lazily when playback actually starts producing output
|
|
671
|
-
// (see `playerDidChangePlayingState`) and re-activated on interruption-end. Best-effort: a
|
|
672
|
-
// category-config failure must never brick setup.
|
|
445
|
+
// Configure audio session
|
|
673
446
|
let session = AVAudioSession.sharedInstance()
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
mode: AVAudioSession.Mode,
|
|
677
|
-
policy: AVAudioSession.RouteSharingPolicy,
|
|
678
|
-
options: AVAudioSession.CategoryOptions,
|
|
679
|
-
) = options.ios?.resolveAudioSessionConfig()
|
|
680
|
-
?? (category: .playback, mode: .default, policy: .default, options: [])
|
|
681
|
-
// Capture category application once so a media-services reset (which
|
|
682
|
-
// clears the category) can restore the exact same config later.
|
|
683
|
-
let applyCategory: () -> Void = {
|
|
684
|
-
try? session.setCategory(cfg.category, mode: cfg.mode, policy: cfg.policy, options: cfg.options)
|
|
685
|
-
}
|
|
686
|
-
applyCategory()
|
|
447
|
+
try session.setCategory(.playback, mode: .default)
|
|
448
|
+
try session.setActive(true)
|
|
687
449
|
|
|
688
450
|
// Create player and configure on main actor
|
|
689
451
|
await MainActor.run { [weak self] in
|
|
690
452
|
guard let self else { return }
|
|
691
|
-
self.applyAudioSessionCategory = applyCategory
|
|
692
453
|
|
|
693
454
|
// Create player with self as callbacks delegate
|
|
694
455
|
player = TrackPlayer(callbacks: self)
|
|
@@ -706,52 +467,18 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
706
467
|
// Wire up network monitor for accelerated retries when connectivity is restored
|
|
707
468
|
player?.networkMonitor = networkMonitor
|
|
708
469
|
|
|
709
|
-
// Central fan-out for connectivity changes (wired once here, idempotent on re-setup, and
|
|
710
|
-
// independent of any JS subscription):
|
|
711
|
-
// - bridge to the JS `onOnlineChanged` event (no-op default until JS subscribes);
|
|
712
|
-
// - re-render now-playing so the formatter's offline/online label updates immediately on a
|
|
713
|
-
// connectivity change, not only on the next playback transition (fixes the stall-entry
|
|
714
|
-
// race where the device goes offline after the buffering line was already rendered);
|
|
715
|
-
// - reconnect a stalled stream when connectivity returns (see TrackPlayer.handleNetworkRestored).
|
|
716
|
-
// The monitor only mutates state on the main thread, so this fires main-isolated.
|
|
717
|
-
networkMonitor.onChanged = { [weak self] isOnline in
|
|
718
|
-
MainActor.assumeIsolated {
|
|
719
|
-
guard let self else { return }
|
|
720
|
-
self.onOnlineChanged(isOnline)
|
|
721
|
-
self.applyNowPlayingMetadata()
|
|
722
|
-
if isOnline { self.player?.handleNetworkRestored() }
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Observe audio-session interruptions so another app taking over audio
|
|
727
|
-
// (or a phone call) reflects as a real pause rather than a stuck state.
|
|
728
|
-
setupInterruptionObserver()
|
|
729
|
-
|
|
730
|
-
// Observe media-server resets so a long-running stream recovers instead
|
|
731
|
-
// of going permanently silent.
|
|
732
|
-
setupMediaServicesResetObserver()
|
|
733
|
-
|
|
734
470
|
// Configure media URL resolver
|
|
735
|
-
player?.mediaLoader.mediaUrlResolver = { [weak self] src
|
|
471
|
+
player?.mediaLoader.mediaUrlResolver = { [weak self] src in
|
|
736
472
|
guard let self else {
|
|
737
473
|
return MediaResolvedUrl(url: src, headers: nil, userAgent: nil)
|
|
738
474
|
}
|
|
739
|
-
return await browserManager.resolveMediaUrl(src
|
|
475
|
+
return await browserManager.resolveMediaUrl(src)
|
|
740
476
|
}
|
|
741
477
|
|
|
742
478
|
// Configure artwork URL resolver for Now Playing (with size context)
|
|
743
479
|
player?.nowPlayingUpdater.artworkUrlResolver = { [weak self] track, imageContext in
|
|
744
480
|
guard let self else { return nil }
|
|
745
|
-
|
|
746
|
-
return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: perRoute, imageContext: imageContext)
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Emit the JS `onNowPlayingChanged` event when the rendered text lines change. The updater
|
|
750
|
-
// owns the system now-playing write; this shapes the JS metadata (elapsed time / artwork).
|
|
751
|
-
player?.nowPlayingUpdater.onChanged = { [weak self] track, title, artist, album in
|
|
752
|
-
guard let self else { return }
|
|
753
|
-
self.onNowPlayingChanged(
|
|
754
|
-
self.makeNowPlayingMetadata(track: track, title: title, artist: artist, album: album))
|
|
481
|
+
return await browserManager.resolveArtworkUrl(track: track, perRouteConfig: nil, imageContext: imageContext)
|
|
755
482
|
}
|
|
756
483
|
|
|
757
484
|
// Configure sleep timer callback
|
|
@@ -759,30 +486,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
759
486
|
self?.onSleepTimerChanged(state)
|
|
760
487
|
}
|
|
761
488
|
|
|
762
|
-
//
|
|
763
|
-
|
|
764
|
-
nowPlayingMetadataEnabled = options.autoUpdateNowPlayingMetadata ?? true
|
|
765
|
-
nowPlayingMetadataFormatter = options.nowPlayingMetadataFormatter
|
|
766
|
-
// `options.keepSessionAliveOnError` is deliberately ignored here: iOS needs no masking.
|
|
767
|
-
// A terminal error resolves the playback state to *paused*
|
|
768
|
-
// (PlaybackCoordinator.applySideEffects), the now-playing info is retained (never cleared
|
|
769
|
-
// on error), and next/previous stay enabled (gated on queue position, not state). So the
|
|
770
|
-
// session stays controllable through errors by default — the behavior Android achieves via
|
|
771
|
-
// InterceptingPlayer is already the iOS norm.
|
|
772
|
-
|
|
773
|
-
// The bundled runtime options and initial state are part of the atomic launch
|
|
774
|
-
// description. Stage the state first so setup options and earlier pre-setup setter
|
|
775
|
-
// calls resolve by strict last-write-wins, then apply everything to the live player.
|
|
776
|
-
if let bundled = options.options {
|
|
777
|
-
updateOptions(options: bundled)
|
|
778
|
-
} else {
|
|
779
|
-
// updateOptions applies remote commands itself; without bundled options, apply the
|
|
780
|
-
// defaults (play, pause, next, previous, seekTo) here.
|
|
781
|
-
applyRemoteCommands()
|
|
782
|
-
}
|
|
783
|
-
if let mode = options.repeatMode { pendingRepeatMode = mode }
|
|
784
|
-
if let intent = options.playWhenReady { pendingPlayWhenReady = intent }
|
|
785
|
-
applyPendingPlayerState()
|
|
489
|
+
// Apply default remote commands (play, pause, next, previous, seekTo)
|
|
490
|
+
applyRemoteCommands()
|
|
786
491
|
|
|
787
492
|
// Notify listeners that player is ready (e.g., CarPlay)
|
|
788
493
|
playerAndConfiguredBrowser.check()
|
|
@@ -790,22 +495,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
790
495
|
}
|
|
791
496
|
}
|
|
792
497
|
|
|
793
|
-
/// Applies initial player state staged before the player existed — from setup options or the
|
|
794
|
-
/// imperative setters called pre-setup. Consumed on apply so a later re-setup doesn't replay
|
|
795
|
-
/// stale state.
|
|
796
|
-
@MainActor
|
|
797
|
-
private func applyPendingPlayerState() {
|
|
798
|
-
guard let player else { return }
|
|
799
|
-
if let mode = pendingRepeatMode {
|
|
800
|
-
player.repeatMode = mode
|
|
801
|
-
pendingRepeatMode = nil
|
|
802
|
-
}
|
|
803
|
-
if let intent = pendingPlayWhenReady {
|
|
804
|
-
player.playWhenReady = intent
|
|
805
|
-
pendingPlayWhenReady = nil
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
498
|
public func updateOptions(options: NativeUpdateOptions) {
|
|
810
499
|
onMainActor {
|
|
811
500
|
let previousInterval = playerOptions.progressUpdateEventInterval
|
|
@@ -813,10 +502,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
813
502
|
// Update stored options
|
|
814
503
|
playerOptions.update(from: options)
|
|
815
504
|
|
|
816
|
-
// Propagate the favorite match mode to the browser so it can hydrate
|
|
817
|
-
// row hearts (the `favorite` capability is the single favoriting switch).
|
|
818
|
-
browserManager.setFavoriteMatch(playerOptions.capabilities.favoriteMatch)
|
|
819
|
-
|
|
820
505
|
// Apply remote commands to player
|
|
821
506
|
applyRemoteCommands()
|
|
822
507
|
|
|
@@ -824,9 +509,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
824
509
|
if playerOptions.progressUpdateEventInterval != previousInterval {
|
|
825
510
|
player?.setProgressUpdateInterval(playerOptions.progressUpdateEventInterval)
|
|
826
511
|
}
|
|
827
|
-
|
|
828
|
-
onOptionsChanged(playerOptions.toOptions())
|
|
829
|
-
playerOptionsChangedEmitter.emit(())
|
|
830
512
|
}
|
|
831
513
|
}
|
|
832
514
|
|
|
@@ -844,12 +526,8 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
844
526
|
player.remoteCommands = remoteCommands
|
|
845
527
|
}
|
|
846
528
|
|
|
847
|
-
public func getOptions() throws ->
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
public func setPlaybackIntervalEnabled(enabled: Bool) {
|
|
852
|
-
onMainActor { player?.setPlaybackIntervalEnabled(enabled) }
|
|
529
|
+
public func getOptions() throws -> UpdateOptions {
|
|
530
|
+
playerOptions.toUpdateOptions()
|
|
853
531
|
}
|
|
854
532
|
|
|
855
533
|
// MARK: - Playback Control
|
|
@@ -884,15 +562,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
884
562
|
}
|
|
885
563
|
|
|
886
564
|
public func setPlayWhenReady(playWhenReady: Bool) throws {
|
|
887
|
-
onMainActor {
|
|
888
|
-
// Pre-setup this stages the intent for the player to come up with — never a no-op.
|
|
889
|
-
if let player { player.playWhenReady = playWhenReady }
|
|
890
|
-
else { pendingPlayWhenReady = playWhenReady }
|
|
891
|
-
}
|
|
565
|
+
onMainActor { player?.playWhenReady = playWhenReady }
|
|
892
566
|
}
|
|
893
567
|
|
|
894
568
|
public func getPlayWhenReady() throws -> Bool {
|
|
895
|
-
onMainActor { player?.playWhenReady ??
|
|
569
|
+
onMainActor { player?.playWhenReady ?? false }
|
|
896
570
|
}
|
|
897
571
|
|
|
898
572
|
public func seekTo(position: Double) throws {
|
|
@@ -903,10 +577,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
903
577
|
onMainActor { player?.seekBy(offset) }
|
|
904
578
|
}
|
|
905
579
|
|
|
906
|
-
public func seekToLiveEdge() throws {
|
|
907
|
-
onMainActor { player?.seekToLiveEdge() }
|
|
908
|
-
}
|
|
909
|
-
|
|
910
580
|
public func setVolume(level: Double) throws {
|
|
911
581
|
onMainActor { player?.volume = Float(level) }
|
|
912
582
|
}
|
|
@@ -944,15 +614,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
944
614
|
}
|
|
945
615
|
|
|
946
616
|
public func getRepeatMode() throws -> RepeatMode {
|
|
947
|
-
onMainActor { player?.repeatMode ??
|
|
617
|
+
onMainActor { player?.repeatMode ?? .off }
|
|
948
618
|
}
|
|
949
619
|
|
|
950
620
|
public func setRepeatMode(mode: RepeatMode) throws {
|
|
951
|
-
onMainActor {
|
|
952
|
-
// Pre-setup this stages the mode for the player to come up with — never a no-op.
|
|
953
|
-
if let player { player.repeatMode = mode }
|
|
954
|
-
else { pendingRepeatMode = mode }
|
|
955
|
-
}
|
|
621
|
+
onMainActor { player?.repeatMode = mode }
|
|
956
622
|
}
|
|
957
623
|
|
|
958
624
|
public func getShuffleEnabled() throws -> Bool {
|
|
@@ -964,13 +630,11 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
964
630
|
}
|
|
965
631
|
|
|
966
632
|
public func getPlaybackError() throws -> PlaybackError? {
|
|
967
|
-
onMainActor { player?.
|
|
633
|
+
onMainActor { player?.playbackError?.toNitroError() }
|
|
968
634
|
}
|
|
969
635
|
|
|
970
636
|
public func retry() throws {
|
|
971
|
-
|
|
972
|
-
// may have expired since the failure (matches the internal retry path).
|
|
973
|
-
onMainActor { player?.reloadResolving(startFromCurrentTime: true) }
|
|
637
|
+
onMainActor { player?.reload(startFromCurrentTime: true) }
|
|
974
638
|
}
|
|
975
639
|
|
|
976
640
|
// MARK: - Sleep Timer
|
|
@@ -984,9 +648,9 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
984
648
|
}
|
|
985
649
|
}
|
|
986
650
|
|
|
987
|
-
public func setSleepTimer(seconds: Double
|
|
651
|
+
public func setSleepTimer(seconds: Double) throws {
|
|
988
652
|
onMainActor {
|
|
989
|
-
player?.sleepTimerManager.set(seconds: seconds
|
|
653
|
+
player?.sleepTimerManager.set(seconds: seconds)
|
|
990
654
|
}
|
|
991
655
|
}
|
|
992
656
|
|
|
@@ -1066,13 +730,13 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1066
730
|
onMainActor {
|
|
1067
731
|
guard let track = player?.currentTrack, let src = track.src else { return }
|
|
1068
732
|
guard let index = player?.currentIndex, index >= 0 else { return }
|
|
1069
|
-
|
|
1070
|
-
//
|
|
1071
|
-
// reconciles to its canonical ids on the next setFavorites.
|
|
1072
|
-
browserManager.setFavorited(src: src, favorited: favorited)
|
|
733
|
+
browserManager.updateFavorite(id: src, favorited: favorited)
|
|
734
|
+
// Create updated track with new favorited state
|
|
1073
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)
|
|
1074
738
|
favoriteChangedEmitter.emit(FavoriteChangedEvent(track: updatedTrack, favorited: favorited))
|
|
1075
|
-
// Fire active track changed so
|
|
739
|
+
// Fire active track changed so useActiveTrack() hook updates UI
|
|
1076
740
|
let position = player?.currentTime ?? 0
|
|
1077
741
|
activeTrackChangedEmitter.emit(PlaybackActiveTrackChangedEvent(
|
|
1078
742
|
lastIndex: Double(index),
|
|
@@ -1084,24 +748,27 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1084
748
|
}
|
|
1085
749
|
}
|
|
1086
750
|
|
|
1087
|
-
/// Whether the active (now-playing) track is favorited, per the authoritative
|
|
1088
|
-
/// favorite set (via `getActiveTrack`'s hydration).
|
|
1089
|
-
func isActiveTrackFavorited() -> Bool {
|
|
1090
|
-
(try? getActiveTrack())?.favorited ?? false
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
751
|
public func toggleActiveTrackFavorited() throws {
|
|
1094
|
-
|
|
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
|
+
}
|
|
1095
759
|
}
|
|
1096
760
|
|
|
1097
|
-
public func setQueue(tracks: [Track], startIndex: Double?,
|
|
1098
|
-
onMainActor {
|
|
761
|
+
public func setQueue(tracks: [Track], startIndex: Double?, startPositionMs: Double?) throws {
|
|
762
|
+
try onMainActor {
|
|
1099
763
|
guard let player else { return }
|
|
1100
|
-
player.
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
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
|
+
}
|
|
1105
772
|
}
|
|
1106
773
|
}
|
|
1107
774
|
|
|
@@ -1126,11 +793,7 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1126
793
|
}
|
|
1127
794
|
|
|
1128
795
|
public func getActiveTrack() throws -> Track? {
|
|
1129
|
-
|
|
1130
|
-
// track is loaded outside the browse cache, so its own flag isn't otherwise
|
|
1131
|
-
// kept in sync as favorites change. This is the single source of truth for
|
|
1132
|
-
// "is the active track favorited" (now-playing heart, toggle direction, JS).
|
|
1133
|
-
onMainActor { player?.currentTrack.map(browserManager.hydrateFavorite) }
|
|
796
|
+
onMainActor { player?.currentTrack }
|
|
1134
797
|
}
|
|
1135
798
|
|
|
1136
799
|
// MARK: - Now Playing
|
|
@@ -1142,58 +805,21 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1142
805
|
}
|
|
1143
806
|
}
|
|
1144
807
|
|
|
1145
|
-
/// Transient now-playing fields (e.g. feedback for a refused remote
|
|
1146
|
-
/// command). Outranks the formatter and the override while active, so live
|
|
1147
|
-
/// metadata can't stomp it mid-flash. Reverted by a NATIVE timer — JS
|
|
1148
|
-
/// timers pause with a backgrounded host on Android, and the lock screen
|
|
1149
|
-
/// is exactly the backgrounded case — and cleared early on track change.
|
|
1150
|
-
@MainActor private var nowPlayingFlash: NowPlayingUpdate?
|
|
1151
|
-
@MainActor private var nowPlayingFlashRevert: DispatchWorkItem?
|
|
1152
|
-
|
|
1153
|
-
/// Pending debounced audio-session release (see `playerShouldReleaseSession`).
|
|
1154
|
-
@MainActor private var sessionReleaseWork: DispatchWorkItem?
|
|
1155
|
-
|
|
1156
|
-
public func flashNowPlaying(update: NowPlayingUpdate, durationMs: Double) throws {
|
|
1157
|
-
onMainActor {
|
|
1158
|
-
nowPlayingFlashRevert?.cancel()
|
|
1159
|
-
nowPlayingFlash = update
|
|
1160
|
-
let revert = DispatchWorkItem { [weak self] in
|
|
1161
|
-
guard let self else { return }
|
|
1162
|
-
self.nowPlayingFlash = nil
|
|
1163
|
-
self.nowPlayingFlashRevert = nil
|
|
1164
|
-
self.applyNowPlayingMetadata()
|
|
1165
|
-
}
|
|
1166
|
-
nowPlayingFlashRevert = revert
|
|
1167
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + durationMs / 1000, execute: revert)
|
|
1168
|
-
applyNowPlayingMetadata()
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
public func clearNowPlayingFlash() throws {
|
|
1173
|
-
onMainActor {
|
|
1174
|
-
guard nowPlayingFlash != nil else { return }
|
|
1175
|
-
cancelNowPlayingFlash()
|
|
1176
|
-
applyNowPlayingMetadata()
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
@MainActor
|
|
1181
|
-
private func cancelNowPlayingFlash() {
|
|
1182
|
-
nowPlayingFlashRevert?.cancel()
|
|
1183
|
-
nowPlayingFlashRevert = nil
|
|
1184
|
-
nowPlayingFlash = nil
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
808
|
public func getNowPlaying() throws -> NowPlayingMetadata? {
|
|
1188
809
|
onMainActor {
|
|
1189
810
|
guard let track = player?.currentTrack else { return nil }
|
|
1190
|
-
let flash = nowPlayingFlash
|
|
1191
811
|
let override = nowPlayingOverride
|
|
1192
|
-
return
|
|
1193
|
-
|
|
1194
|
-
title:
|
|
1195
|
-
|
|
1196
|
-
|
|
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,
|
|
1197
823
|
)
|
|
1198
824
|
}
|
|
1199
825
|
}
|
|
@@ -1203,47 +829,36 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1203
829
|
/// Falls back to the raw artwork string only if it's not an SF Symbol (which JS can't load).
|
|
1204
830
|
private func nowPlayingArtwork(for track: Track) -> String? {
|
|
1205
831
|
if let uri = track.artworkSource?.uri { return uri }
|
|
1206
|
-
guard let artwork = track.artwork
|
|
832
|
+
guard let artwork = track.artwork else { return nil }
|
|
1207
833
|
return SFSymbolRenderer.isSFSymbol(artwork) ? nil : artwork
|
|
1208
834
|
}
|
|
1209
835
|
|
|
1210
|
-
///
|
|
836
|
+
/// Applies the current now playing metadata (with override if set) to NowPlayingInfoCenter and notifies JS.
|
|
1211
837
|
@MainActor
|
|
1212
|
-
private func
|
|
1213
|
-
track
|
|
1214
|
-
|
|
1215
|
-
|
|
838
|
+
private func applyNowPlayingMetadata() {
|
|
839
|
+
guard let track = player?.currentTrack else { return }
|
|
840
|
+
let override = nowPlayingOverride
|
|
841
|
+
|
|
842
|
+
let nowPlaying = NowPlayingMetadata(
|
|
1216
843
|
elapsedTime: player?.currentTime,
|
|
1217
|
-
title: title,
|
|
1218
|
-
album: album,
|
|
1219
|
-
artist: artist,
|
|
844
|
+
title: override?.title ?? track.title,
|
|
845
|
+
album: track.album,
|
|
846
|
+
artist: override?.artist ?? track.artist,
|
|
1220
847
|
duration: track.duration,
|
|
1221
848
|
artwork: nowPlayingArtwork(for: track),
|
|
1222
849
|
description: track.description,
|
|
1223
850
|
mediaId: track.src ?? track.url,
|
|
1224
851
|
genre: track.genre,
|
|
852
|
+
rating: nil,
|
|
1225
853
|
)
|
|
1226
|
-
}
|
|
1227
854
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
let stalled: StallReason? =
|
|
1236
|
-
player.isStalled ? (networkMonitor.getOnline() ? .buffering : .offline) : nil
|
|
1237
|
-
player.nowPlayingUpdater.render(
|
|
1238
|
-
track: track,
|
|
1239
|
-
timedMetadata: latestTimedMetadata,
|
|
1240
|
-
playWhenReady: player.playWhenReady,
|
|
1241
|
-
stalled: stalled,
|
|
1242
|
-
error: player.coordinator.nitroPlaybackError(),
|
|
1243
|
-
flash: nowPlayingFlash,
|
|
1244
|
-
override: nowPlayingMetadataEnabled ? nowPlayingOverride : nil,
|
|
1245
|
-
formatter: nowPlayingMetadataEnabled ? nowPlayingMetadataFormatter : nil,
|
|
1246
|
-
)
|
|
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)
|
|
1247
862
|
}
|
|
1248
863
|
|
|
1249
864
|
// MARK: - Network
|
|
@@ -1278,63 +893,6 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1278
893
|
|
|
1279
894
|
// MARK: - External Audio Output
|
|
1280
895
|
|
|
1281
|
-
/// Observes audio-session interruptions (phone calls, or another app such as
|
|
1282
|
-
/// Music/Spotify starting playback). iOS pauses our AVPlayer underneath us but
|
|
1283
|
-
/// doesn't update our playback state, so without this the UI stays stuck on
|
|
1284
|
-
/// "playing". On `.began` we reflect a real pause; on `.ended` we resume if we
|
|
1285
|
-
/// were playing and the system says we should.
|
|
1286
|
-
private func setupInterruptionObserver() {
|
|
1287
|
-
if let observer = interruptionObserver {
|
|
1288
|
-
NotificationCenter.default.removeObserver(observer)
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
interruptionObserver = NotificationCenter.default.addObserver(
|
|
1292
|
-
forName: AVAudioSession.interruptionNotification,
|
|
1293
|
-
object: nil,
|
|
1294
|
-
queue: .main,
|
|
1295
|
-
) { [weak self] notification in
|
|
1296
|
-
self?.handleAudioSessionInterruption(notification)
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
private func handleAudioSessionInterruption(_ notification: Notification) {
|
|
1301
|
-
guard
|
|
1302
|
-
let info = notification.userInfo,
|
|
1303
|
-
let typeValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
1304
|
-
let type = AVAudioSession.InterruptionType(rawValue: typeValue)
|
|
1305
|
-
else { return }
|
|
1306
|
-
|
|
1307
|
-
switch type {
|
|
1308
|
-
case .began:
|
|
1309
|
-
// iOS 17+ fires a `.began` (with no matching `.ended`) when an output
|
|
1310
|
-
// route such as headphones disappears. Reflecting a pause here would
|
|
1311
|
-
// strand resume-intent state with nothing to clear it, and the
|
|
1312
|
-
// route-change observer already handles the output change — so ignore it.
|
|
1313
|
-
if #available(iOS 17.0, *),
|
|
1314
|
-
let reasonValue = info[AVAudioSessionInterruptionReasonKey] as? UInt,
|
|
1315
|
-
AVAudioSession.InterruptionReason(rawValue: reasonValue) == .routeDisconnected
|
|
1316
|
-
{
|
|
1317
|
-
return
|
|
1318
|
-
}
|
|
1319
|
-
onMainActor { player?.handleInterruptionBegan() }
|
|
1320
|
-
case .ended:
|
|
1321
|
-
let shouldResume = (info[AVAudioSessionInterruptionOptionKey] as? UInt)
|
|
1322
|
-
.map { AVAudioSession.InterruptionOptions(rawValue: $0).contains(.shouldResume) }
|
|
1323
|
-
?? false
|
|
1324
|
-
onMainActor {
|
|
1325
|
-
// Reactivate the possibly-deactivated session before resuming — but
|
|
1326
|
-
// only when a resume will happen: activating while staying paused
|
|
1327
|
-
// grabs a non-mixable session with nothing to release it.
|
|
1328
|
-
if shouldResume, player?.willResumeAfterInterruption == true {
|
|
1329
|
-
try? AVAudioSession.sharedInstance().setActive(true)
|
|
1330
|
-
}
|
|
1331
|
-
player?.handleInterruptionEnded(shouldResume: shouldResume)
|
|
1332
|
-
}
|
|
1333
|
-
@unknown default:
|
|
1334
|
-
break
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
896
|
/// Sets up observer for audio route changes
|
|
1339
897
|
private func setupRouteChangeObserver() {
|
|
1340
898
|
if let observer = routeChangeObserver {
|
|
@@ -1345,97 +903,58 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1345
903
|
forName: AVAudioSession.routeChangeNotification,
|
|
1346
904
|
object: nil,
|
|
1347
905
|
queue: .main,
|
|
1348
|
-
) { [weak self] notification in
|
|
1349
|
-
guard let self else { return }
|
|
1350
|
-
// The previous output disappeared (headphones unplugged, a Bluetooth
|
|
1351
|
-
// speaker powered off). Pause rather than abruptly resume out of the
|
|
1352
|
-
// built-in speaker — the platform convention every media app follows.
|
|
1353
|
-
// A deliberate pause (clears play intent), not an interruption: the user
|
|
1354
|
-
// restarts playback themselves once they want it.
|
|
1355
|
-
if let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
1356
|
-
AVAudioSession.RouteChangeReason(rawValue: reasonValue) == .oldDeviceUnavailable
|
|
1357
|
-
{
|
|
1358
|
-
// Also cancels a pending interruption auto-resume: unplugging during
|
|
1359
|
-
// a call must not blast the built-in speaker when the call ends.
|
|
1360
|
-
self.onMainActor { self.player?.handleRouteDisconnected() }
|
|
1361
|
-
}
|
|
1362
|
-
if let output = self.getCurrentOutput() {
|
|
1363
|
-
self.onOutputChanged(output)
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
/// Observes audio-server (`mediaserverd`) resets. When it fires, the session
|
|
1369
|
-
/// category is cleared and every AVPlayer/session handle is invalid — a
|
|
1370
|
-
/// long-running stream would otherwise go permanently silent until the app
|
|
1371
|
-
/// restarts.
|
|
1372
|
-
private func setupMediaServicesResetObserver() {
|
|
1373
|
-
if let observer = mediaServicesResetObserver {
|
|
1374
|
-
NotificationCenter.default.removeObserver(observer)
|
|
1375
|
-
}
|
|
1376
|
-
mediaServicesResetObserver = NotificationCenter.default.addObserver(
|
|
1377
|
-
forName: AVAudioSession.mediaServicesWereResetNotification,
|
|
1378
|
-
object: nil,
|
|
1379
|
-
queue: .main,
|
|
1380
906
|
) { [weak self] _ in
|
|
1381
|
-
guard let self else { return }
|
|
1382
|
-
|
|
1383
|
-
// recreate itself and reconnect the current stream.
|
|
1384
|
-
self.onMainActor {
|
|
1385
|
-
self.applyAudioSessionCategory()
|
|
1386
|
-
self.player?.handleMediaServicesReset()
|
|
1387
|
-
}
|
|
907
|
+
guard let self, let output = self.getCurrentOutput() else { return }
|
|
908
|
+
self.onIosOutputChanged(output)
|
|
1388
909
|
}
|
|
1389
910
|
}
|
|
1390
911
|
|
|
1391
912
|
/// Gets the current audio output info
|
|
1392
|
-
private func getCurrentOutput() ->
|
|
913
|
+
private func getCurrentOutput() -> IosOutput? {
|
|
1393
914
|
let session = AVAudioSession.sharedInstance()
|
|
1394
915
|
guard let output = session.currentRoute.outputs.first else { return nil }
|
|
1395
916
|
|
|
1396
|
-
let (outputType, external): (
|
|
917
|
+
let (outputType, external): (IosOutputType, Bool) = switch output.portType {
|
|
1397
918
|
case .builtInSpeaker:
|
|
1398
|
-
(.
|
|
919
|
+
(.builtInSpeaker, false)
|
|
1399
920
|
case .builtInReceiver:
|
|
1400
|
-
(.
|
|
921
|
+
(.builtInReceiver, false)
|
|
1401
922
|
case .airPlay:
|
|
1402
923
|
(.airplay, true)
|
|
1403
|
-
case .bluetoothA2DP
|
|
1404
|
-
(.
|
|
924
|
+
case .bluetoothA2DP:
|
|
925
|
+
(.bluetoothA2dp, true)
|
|
926
|
+
case .bluetoothHFP:
|
|
927
|
+
(.bluetoothHfp, true)
|
|
928
|
+
case .bluetoothLE:
|
|
929
|
+
(.bluetoothLe, true)
|
|
1405
930
|
case .headphones:
|
|
1406
931
|
(.headphones, true)
|
|
1407
932
|
case .carAudio:
|
|
1408
|
-
(.
|
|
933
|
+
(.carAudio, true)
|
|
1409
934
|
case .HDMI:
|
|
1410
935
|
(.hdmi, true)
|
|
1411
936
|
case .usbAudio:
|
|
1412
|
-
(.
|
|
937
|
+
(.usbAudio, true)
|
|
1413
938
|
default:
|
|
1414
939
|
(.other, true)
|
|
1415
940
|
}
|
|
1416
941
|
|
|
1417
|
-
return
|
|
942
|
+
return IosOutput(type: outputType, name: output.portName, external: external)
|
|
1418
943
|
}
|
|
1419
944
|
|
|
1420
|
-
public func
|
|
945
|
+
public func getIosOutput() throws -> IosOutput? {
|
|
1421
946
|
getCurrentOutput()
|
|
1422
947
|
}
|
|
1423
948
|
|
|
1424
|
-
public func
|
|
1425
|
-
// iOS can always present the system route picker (AVRoutePickerView).
|
|
1426
|
-
true
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
public func openOutputPicker() throws {
|
|
949
|
+
public func openIosOutputPicker() throws {
|
|
1430
950
|
DispatchQueue.main.async { [weak self] in
|
|
1431
|
-
guard let self else { return }
|
|
1432
951
|
guard let windowScene = UIApplication.shared.connectedScenes
|
|
1433
952
|
.compactMap({ $0 as? UIWindowScene })
|
|
1434
953
|
.first(where: { $0.activationState == .foregroundActive }),
|
|
1435
954
|
let window = windowScene.windows.first(where: { $0.isKeyWindow }),
|
|
1436
955
|
var topController = window.rootViewController
|
|
1437
956
|
else {
|
|
1438
|
-
self
|
|
957
|
+
self?.logger.debug("openIosOutputPicker: Could not find active window scene")
|
|
1439
958
|
return
|
|
1440
959
|
}
|
|
1441
960
|
|
|
@@ -1444,52 +963,25 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1444
963
|
topController = presented
|
|
1445
964
|
}
|
|
1446
965
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
// button. Two requirements that are easy to get wrong:
|
|
1450
|
-
// 1. The view must be *visible* to the system (a `isHidden` source view
|
|
1451
|
-
// suppresses the presentation), so we keep it transparent instead.
|
|
1452
|
-
// 2. The internal button can be nested below the top-level subviews on
|
|
1453
|
-
// modern iOS, so we search recursively rather than only direct children.
|
|
1454
|
-
let routePicker = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
|
1455
|
-
routePicker.alpha = 0.01
|
|
966
|
+
let routePicker = AVRoutePickerView(frame: .zero)
|
|
967
|
+
routePicker.isHidden = true
|
|
1456
968
|
topController.view.addSubview(routePicker)
|
|
1457
969
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
button.sendActions(for: .touchUpInside)
|
|
1465
|
-
} else {
|
|
1466
|
-
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
|
+
}
|
|
1467
976
|
}
|
|
1468
977
|
|
|
1469
|
-
//
|
|
1470
|
-
|
|
1471
|
-
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) {
|
|
1472
980
|
routePicker.removeFromSuperview()
|
|
1473
981
|
}
|
|
1474
982
|
}
|
|
1475
983
|
}
|
|
1476
984
|
|
|
1477
|
-
/// Depth-first search for the first `UIButton` inside a view tree.
|
|
1478
|
-
private static func firstButton(in view: UIView) -> UIButton? {
|
|
1479
|
-
if let button = view as? UIButton { return button }
|
|
1480
|
-
for subview in view.subviews {
|
|
1481
|
-
if let button = firstButton(in: subview) { return button }
|
|
1482
|
-
}
|
|
1483
|
-
return nil
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
/// Compact description of a view tree, e.g. `AVRoutePickerView[UIButton[UIImageView]]`.
|
|
1487
|
-
private static func describeHierarchy(_ view: UIView) -> String {
|
|
1488
|
-
let name = String(describing: type(of: view))
|
|
1489
|
-
let children = view.subviews.map(describeHierarchy).joined(separator: ", ")
|
|
1490
|
-
return children.isEmpty ? name : "\(name)[\(children)]"
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
985
|
// MARK: - Equalizer (unsupported on iOS)
|
|
1494
986
|
|
|
1495
987
|
public func getEqualizerSettings() throws -> EqualizerSettings? {
|
|
@@ -1534,28 +1026,12 @@ public class HybridAudioBrowser: HybridAudioBrowserSpec, @unchecked Sendable {
|
|
|
1534
1026
|
extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
1535
1027
|
public func playerDidChangePlayback(_ playback: Playback) {
|
|
1536
1028
|
onPlaybackChanged(playback)
|
|
1537
|
-
// Re-render so the formatter reflects the new state — notably the error line on a failure (the
|
|
1538
|
-
// coordinator sets playbackError before this fires). Redundant updates are deduped on publish.
|
|
1539
|
-
applyNowPlayingMetadata()
|
|
1540
1029
|
}
|
|
1541
1030
|
|
|
1542
1031
|
public func playerDidChangeActiveTrack(_ event: PlaybackActiveTrackChangedEvent) {
|
|
1543
|
-
// Clear now playing override
|
|
1544
|
-
// (matches Kotlin behavior; a flash must not carry over to a new track)
|
|
1032
|
+
// Clear now playing override when track changes (matches Kotlin behavior)
|
|
1545
1033
|
nowPlayingOverride = nil
|
|
1546
|
-
|
|
1547
|
-
latestTimedMetadata = nil
|
|
1548
|
-
// Hydrate the active track's `favorited` from the authoritative set so JS
|
|
1549
|
-
// consumers (useActiveTrack) match getActiveTrack()'s hydrated value — the
|
|
1550
|
-
// coordinator emits the raw queue track, whose flag isn't kept in sync.
|
|
1551
|
-
let hydrated = PlaybackActiveTrackChangedEvent(
|
|
1552
|
-
lastIndex: event.lastIndex,
|
|
1553
|
-
lastTrack: event.lastTrack,
|
|
1554
|
-
lastPosition: event.lastPosition,
|
|
1555
|
-
index: event.index,
|
|
1556
|
-
track: event.track.map(browserManager.hydrateFavorite),
|
|
1557
|
-
)
|
|
1558
|
-
activeTrackChangedEmitter.emit(hydrated)
|
|
1034
|
+
activeTrackChangedEmitter.emit(event)
|
|
1559
1035
|
// Also notify now playing changed when track changes
|
|
1560
1036
|
applyNowPlayingMetadata()
|
|
1561
1037
|
}
|
|
@@ -1564,61 +1040,12 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1564
1040
|
onPlaybackProgressUpdated(event)
|
|
1565
1041
|
}
|
|
1566
1042
|
|
|
1567
|
-
public func playerDidFirePlaybackInterval() {
|
|
1568
|
-
onPlaybackInterval()
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
1043
|
public func playerDidChangePlayWhenReady(_ playWhenReady: Bool) {
|
|
1572
|
-
// Intent to play again is the earliest cancel signal for a pending session release.
|
|
1573
|
-
if playWhenReady { cancelSessionRelease() }
|
|
1574
1044
|
onPlaybackPlayWhenReadyChanged(PlaybackPlayWhenReadyChangedEvent(playWhenReady: playWhenReady))
|
|
1575
1045
|
}
|
|
1576
1046
|
|
|
1577
1047
|
public func playerDidChangePlayingState(_ state: PlayingState) {
|
|
1578
1048
|
onPlaybackPlayingState(state)
|
|
1579
|
-
// Lazily activate the audio session the moment playback actually produces — or is buffering to
|
|
1580
|
-
// produce — output. This (not setup) is where we grab the session, so launching the app never
|
|
1581
|
-
// interrupts other apps' audio; the interruption only happens when the user truly starts
|
|
1582
|
-
// playback, which is correct. Keyed off play *state*, not `playWhenReady`, because the intent
|
|
1583
|
-
// can be set at setup (before any track) while output only begins here. Idempotent:
|
|
1584
|
-
// `setActive(true)` is a no-op when the session is already active.
|
|
1585
|
-
if state.playing || state.buffering {
|
|
1586
|
-
try? AVAudioSession.sharedInstance().setActive(true)
|
|
1587
|
-
// The now-playing session's activation request at link time predates this
|
|
1588
|
-
// (the audio session was still inactive, and the system may have declined);
|
|
1589
|
-
// re-request now so the surface can appear even if playback never produces
|
|
1590
|
-
// audio (first-ever load failing on a dead stream).
|
|
1591
|
-
player?.nowPlayingInfoController.reactivateSessionIfNeeded()
|
|
1592
|
-
// Output resumed — cancel any pending release armed by an earlier stop.
|
|
1593
|
-
cancelSessionRelease()
|
|
1594
|
-
}
|
|
1595
|
-
// Re-render so the formatter reacts to a stall starting or recovering (`stalled`) — a buffering
|
|
1596
|
-
// flag change that may not transition the coordinator state.
|
|
1597
|
-
applyNowPlayingMetadata()
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
public func playerShouldReleaseSession() {
|
|
1601
|
-
// Release the audio session so other apps' audio (Spotify, a podcast) can resume — debounced,
|
|
1602
|
-
// because iOS dislikes deactivating while our audio is still trailing off, and a quick
|
|
1603
|
-
// pause→resume shouldn't thrash the session. The coordinator only requests this on a genuine
|
|
1604
|
-
// stop (not an interruption, not mid-retry); a re-activation cancels it (see
|
|
1605
|
-
// `playerDidChangePlayWhenReady` and the activation above). Mirrors the now-playing flash timer.
|
|
1606
|
-
sessionReleaseWork?.cancel()
|
|
1607
|
-
let release = DispatchWorkItem { [weak self] in
|
|
1608
|
-
guard let self, self.player?.playWhenReady == false else { return }
|
|
1609
|
-
self.sessionReleaseWork = nil
|
|
1610
|
-
// `.notifyOthersOnDeactivation` signals other apps they may resume; without it the session
|
|
1611
|
-
// is released but nobody is told, so the user's audio would not come back.
|
|
1612
|
-
try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
|
1613
|
-
}
|
|
1614
|
-
sessionReleaseWork = release
|
|
1615
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: release)
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
@MainActor
|
|
1619
|
-
private func cancelSessionRelease() {
|
|
1620
|
-
sessionReleaseWork?.cancel()
|
|
1621
|
-
sessionReleaseWork = nil
|
|
1622
1049
|
}
|
|
1623
1050
|
|
|
1624
1051
|
public func playerDidEndQueue(_ event: PlaybackQueueEndedEvent) {
|
|
@@ -1634,7 +1061,6 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1634
1061
|
}
|
|
1635
1062
|
|
|
1636
1063
|
public func playerDidChangeShuffleEnabled(_ enabled: Bool) {
|
|
1637
|
-
shuffleChangedEmitter.emit(enabled)
|
|
1638
1064
|
onPlaybackShuffleModeChanged(enabled)
|
|
1639
1065
|
}
|
|
1640
1066
|
|
|
@@ -1650,10 +1076,7 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1650
1076
|
public func playerDidReceiveTimedMetadata(_ groups: [AVTimedMetadataGroup]) {
|
|
1651
1077
|
for group in groups {
|
|
1652
1078
|
if let metadata = TimedMetadata.from(items: group.items) {
|
|
1653
|
-
latestTimedMetadata = metadata
|
|
1654
1079
|
onTimedMetadata(metadata)
|
|
1655
|
-
// Re-render the now-playing so the formatter can reflect the live song.
|
|
1656
|
-
applyNowPlayingMetadata()
|
|
1657
1080
|
}
|
|
1658
1081
|
}
|
|
1659
1082
|
}
|
|
@@ -1770,6 +1193,10 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1770
1193
|
remoteSeek(position: position)
|
|
1771
1194
|
}
|
|
1772
1195
|
|
|
1196
|
+
public func remoteSetRating(rating _: Any) {
|
|
1197
|
+
// TODO: Convert rating to RemoteSetRatingEvent
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1773
1200
|
public func remotePlayId(id: String, index: Int?) {
|
|
1774
1201
|
let event = RemotePlayIdEvent(id: id, index: index.map { Double($0) })
|
|
1775
1202
|
if let handler = handleRemotePlayId {
|
|
@@ -1788,92 +1215,57 @@ extension HybridAudioBrowser: TrackPlayerCallbacks {
|
|
|
1788
1215
|
}
|
|
1789
1216
|
}
|
|
1790
1217
|
|
|
1791
|
-
///
|
|
1792
|
-
///
|
|
1793
|
-
///
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
}
|
|
1802
|
-
// No-criteria intent ("play «app»") → resume. The gate must NOT block
|
|
1803
|
-
// this: the gate blocks *finding* content, never *hearing* the active or
|
|
1804
|
-
// persisted track. The gate is checked below, for the search branch only.
|
|
1805
|
-
if criteria.isResume {
|
|
1806
|
-
if player.currentTrack != nil {
|
|
1807
|
-
browser.logger.info("resume: warm — playing current track")
|
|
1808
|
-
player.play()
|
|
1809
|
-
browser.showNowPlayingRequestedEmitter.emit(())
|
|
1810
|
-
completion(true)
|
|
1811
|
-
} else if let state = browser.playbackStateStore.load() {
|
|
1812
|
-
browser.logger.info("resume: cold — restoring persisted track")
|
|
1813
|
-
let track = state.track.toNitro()
|
|
1814
|
-
let startMs = (track.live == true) ? nil : state.positionMs
|
|
1815
|
-
// Match Android resume: re-expand the full queue from the track's contextual
|
|
1816
|
-
// URL (parent container → siblings + selected index). Fall back to the single
|
|
1817
|
-
// track when the url isn't contextual or expansion fails.
|
|
1818
|
-
if let url = state.track.url,
|
|
1819
|
-
let expanded = try? await browser.browserManager.expandQueueFromContextualUrl(url)
|
|
1820
|
-
{
|
|
1821
|
-
player.setQueue(expanded.tracks, initialIndex: expanded.selectedIndex, startPositionMs: startMs, playWhenReady: true)
|
|
1822
|
-
} else {
|
|
1823
|
-
player.setQueue([track], initialIndex: 0, startPositionMs: startMs, playWhenReady: true)
|
|
1824
|
-
}
|
|
1825
|
-
browser.showNowPlayingRequestedEmitter.emit(())
|
|
1826
|
-
completion(true)
|
|
1827
|
-
} else {
|
|
1828
|
-
browser.logger.error("resume: nothing playing and nothing persisted → no-op")
|
|
1829
|
-
completion(false) // nothing playing and nothing persisted
|
|
1830
|
-
}
|
|
1831
|
-
return
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
// Assemble the Nitro SearchParams here (MainActor) from the criteria's
|
|
1835
|
-
// Sendable fields, so the structured mode/genre/… reach the request like
|
|
1836
|
-
// Android. Today the API still text-searches `q`; the rest is forward-compat.
|
|
1837
|
-
let params = SearchParams(
|
|
1838
|
-
mode: criteria.searchMode.flatMap { SearchMode(fromString: $0) },
|
|
1839
|
-
query: criteria.query,
|
|
1840
|
-
genre: criteria.genre,
|
|
1841
|
-
artist: criteria.artist,
|
|
1842
|
-
album: criteria.album,
|
|
1843
|
-
title: criteria.title,
|
|
1844
|
-
playlist: criteria.playlist,
|
|
1845
|
-
// .currentlyPlaying can't reach here — isResume routed it to resume.
|
|
1846
|
-
reference: criteria.reference == .my ? .my : .unknown,
|
|
1847
|
-
)
|
|
1848
|
-
|
|
1849
|
-
// Search is *finding* new content — refused when the gate decides this
|
|
1850
|
-
// request is gated (resume above is unaffected: the gate never blocks
|
|
1851
|
-
// hearing, and the gate check stays after the resume check).
|
|
1852
|
-
let outcome = await browser.gateDecision(
|
|
1853
|
-
for: NativeGateRequest(reason: .search, path: nil, search: params),
|
|
1854
|
-
)
|
|
1855
|
-
guard !outcome.gated else {
|
|
1856
|
-
browser.logger.info("handlePlayMediaIntent: search refused — gated")
|
|
1857
|
-
browser.onGate(GateEvent(reason: .search))
|
|
1858
|
-
completion(false)
|
|
1859
|
-
return
|
|
1860
|
-
}
|
|
1861
|
-
|
|
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 {
|
|
1862
1228
|
do {
|
|
1863
|
-
|
|
1229
|
+
let resolved = try await self.browserManager.search(searchTerm)
|
|
1230
|
+
guard let tracks = resolved.children, !tracks.isEmpty else {
|
|
1864
1231
|
completion(false)
|
|
1865
1232
|
return
|
|
1866
1233
|
}
|
|
1867
|
-
|
|
1868
|
-
|
|
1234
|
+
await MainActor.run {
|
|
1235
|
+
self.getPlayer()?.setQueue(tracks, initialIndex: 0, playWhenReady: true)
|
|
1236
|
+
}
|
|
1869
1237
|
completion(true)
|
|
1870
1238
|
} catch {
|
|
1871
|
-
|
|
1239
|
+
self.logger.error("handlePlayMediaIntent failed: \(error.localizedDescription)")
|
|
1872
1240
|
completion(false)
|
|
1873
1241
|
}
|
|
1874
1242
|
}
|
|
1875
1243
|
}
|
|
1876
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
|
+
|
|
1877
1269
|
public func remoteChangeRepeatMode(mode: RepeatMode) {
|
|
1878
1270
|
// Apply the repeat mode change from CarPlay/lock screen
|
|
1879
1271
|
try? setRepeatMode(mode: mode)
|