react-native-queue-player 1.1.2 → 2.0.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/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
package/ios/PlaybackEngine.swift
CHANGED
|
@@ -15,41 +15,40 @@ import Foundation
|
|
|
15
15
|
/// track-boundary scheduling. The lib's `TrackPlayer` holds the canonical
|
|
16
16
|
/// queue model + listener registries + system-side surfaces (NowPlaying,
|
|
17
17
|
/// RemoteCommands, AudioSession, ArtworkResolver) and routes transport /
|
|
18
|
-
/// queue calls through whichever engine is active.
|
|
19
|
-
/// crossfade) is
|
|
18
|
+
/// queue calls through whichever engine is active. An engine swap (gapless ↔
|
|
19
|
+
/// crossfade) is `TrackPlayer.applyEngineSwapForMode`: it builds the new
|
|
20
|
+
/// engine, seeds it through `setItems`, then releases the old one.
|
|
20
21
|
///
|
|
21
|
-
/// Threading:
|
|
22
|
-
///
|
|
23
|
-
///
|
|
24
|
-
///
|
|
25
|
-
///
|
|
26
|
-
///
|
|
22
|
+
/// Threading: `TrackPlayer` calls every method on its `playerQueue`, a
|
|
23
|
+
/// private serial queue, and every delegate fire lands on that queue.
|
|
24
|
+
/// The crossfade engine is constructed confined to it and hops each
|
|
25
|
+
/// AVFoundation observer, notification and timer callback onto it before
|
|
26
|
+
/// firing (a few fires are also synchronous inside a `setItems` / `seek`
|
|
27
|
+
/// / `remove` call). The gapless engine does not use the delegate at all:
|
|
28
|
+
/// `TrackPlayer` observes its player directly and does its own hop. State
|
|
29
|
+
/// queries (positions, indices, isPlaying) read the underlying player from
|
|
30
|
+
/// that queue. An engine is responsible for any internal off-queue work
|
|
31
|
+
/// (the crossfade engine's standby preroll) and for returning to the queue
|
|
32
|
+
/// before touching shared state.
|
|
27
33
|
protocol PlaybackEngine: AnyObject {
|
|
28
34
|
|
|
29
|
-
/// Build the underlying player(s), apply [items] starting at
|
|
30
|
-
/// [startIndex] / [startPositionSeconds], and wait for the
|
|
31
|
-
/// underlying player(s) to reach a playable state.
|
|
32
|
-
///
|
|
33
|
-
/// Empty [items] is allowed; the engine prepares an empty queue
|
|
34
|
-
/// and `currentMediaItemIndex` returns `-1` until items arrive
|
|
35
|
-
/// via `setItems` / `insertItems`.
|
|
36
|
-
func prepare(
|
|
37
|
-
items: [AVPlayerItem],
|
|
38
|
-
startIndex: Int,
|
|
39
|
-
startPositionSeconds: TimeInterval
|
|
40
|
-
) async
|
|
41
|
-
|
|
42
35
|
/// Release the underlying player(s) and stop all background work.
|
|
43
36
|
/// After `release`, no further methods are valid.
|
|
44
37
|
func release()
|
|
45
38
|
|
|
46
39
|
// MARK: - Queue mutation
|
|
47
40
|
|
|
48
|
-
/// Drain the queue and
|
|
49
|
-
/// [startIndex]
|
|
50
|
-
///
|
|
51
|
-
///
|
|
52
|
-
///
|
|
41
|
+
/// Drain the queue and install [items], beginning playback at
|
|
42
|
+
/// [startIndex]. Callers pass a run that may start behind the playhead, so
|
|
43
|
+
/// [startIndex] names where in that run to begin rather than where to cut
|
|
44
|
+
/// it: an engine holds every item it is given unless it cannot.
|
|
45
|
+
///
|
|
46
|
+
/// `GaplessEngine` cannot: `AVQueuePlayer` is forward-only and consumes
|
|
47
|
+
/// finished items, so it drops everything before [startIndex] and
|
|
48
|
+
/// `allMediaItems.count == max(0, items.count - startIndex)` there.
|
|
49
|
+
/// `CrossfadeEngine` holds the whole run, which is what lets it keep a
|
|
50
|
+
/// trail behind the playhead for an immediate backward skip.
|
|
51
|
+
///
|
|
53
52
|
/// Active index resolves to `items[startIndex]` at
|
|
54
53
|
/// [startPositionSeconds].
|
|
55
54
|
///
|
|
@@ -67,8 +66,8 @@ protocol PlaybackEngine: AnyObject {
|
|
|
67
66
|
///
|
|
68
67
|
/// Caller (`TrackPlayer`) constructs `[AVPlayerItem]` via
|
|
69
68
|
/// `AVQueueBuilder` so consumer config (HTTP headers, user-agent,
|
|
70
|
-
///
|
|
71
|
-
///
|
|
69
|
+
/// read-through server routing) is applied before items reach
|
|
70
|
+
/// the engine.
|
|
72
71
|
func setItems(
|
|
73
72
|
_ items: [AVPlayerItem],
|
|
74
73
|
startIndex: Int,
|
|
@@ -96,13 +95,16 @@ protocol PlaybackEngine: AnyObject {
|
|
|
96
95
|
/// Remove [item] from the engine's queue. Removing the current
|
|
97
96
|
/// item on `GaplessEngine` triggers `AVQueuePlayer` auto-advance
|
|
98
97
|
/// to the next queued item; on `CrossfadeEngine` removing the
|
|
99
|
-
///
|
|
100
|
-
///
|
|
98
|
+
/// current item cancels any pending fade and replaces the leading
|
|
99
|
+
/// leg's item with the one now at that index (the previous item when
|
|
100
|
+
/// the removed one was last), or drains when the queue is empty.
|
|
101
|
+
/// No-op when [item] is not present.
|
|
101
102
|
func remove(_ item: AVPlayerItem)
|
|
102
103
|
|
|
103
104
|
/// Remove every item from the queue. After this call,
|
|
104
105
|
/// `currentMediaItem == nil` and `allMediaItems` is empty. Used
|
|
105
|
-
/// by `TrackPlayer.fullRebuildPlayerQueue`
|
|
106
|
+
/// by `TrackPlayer.fullRebuildPlayerQueue` when it installs a whole slice,
|
|
107
|
+
/// before re-inserting
|
|
106
108
|
/// the post-mutation queue snapshot.
|
|
107
109
|
func removeAllItems()
|
|
108
110
|
|
|
@@ -118,6 +120,12 @@ protocol PlaybackEngine: AnyObject {
|
|
|
118
120
|
func play()
|
|
119
121
|
func pause()
|
|
120
122
|
func stop()
|
|
123
|
+
/// Seek to [index] within the engine's own queue, at [position].
|
|
124
|
+
///
|
|
125
|
+
/// [index] is an index into the slice this engine was handed, **not** a
|
|
126
|
+
/// queue position — the two differ whenever the caller installed anything
|
|
127
|
+
/// other than the whole track list. Callers translate by `queueItemId`
|
|
128
|
+
/// identity before calling.
|
|
121
129
|
func seek(toIndex index: Int, position: TimeInterval)
|
|
122
130
|
func skipToNext()
|
|
123
131
|
func skipToPrevious()
|
|
@@ -141,10 +149,14 @@ protocol PlaybackEngine: AnyObject {
|
|
|
141
149
|
|
|
142
150
|
// MARK: - State
|
|
143
151
|
|
|
144
|
-
/// Current item index within the engine's queue. Returns `-1`
|
|
152
|
+
/// Current item index within the engine's own queue. Returns `-1`
|
|
145
153
|
/// when the queue is empty (matches the canonical sentinel in
|
|
146
|
-
/// `TrackPlayer`).
|
|
147
|
-
///
|
|
154
|
+
/// `TrackPlayer`).
|
|
155
|
+
///
|
|
156
|
+
/// This is **not** a queue position — it counts within the slice the
|
|
157
|
+
/// engine was handed. Resolve the active track by item identity
|
|
158
|
+
/// (`currentMediaItem.queueItemId`) rather than reading this as an index
|
|
159
|
+
/// into the consumer's track list.
|
|
148
160
|
var currentMediaItemIndex: Int { get }
|
|
149
161
|
var currentPositionSeconds: TimeInterval { get }
|
|
150
162
|
var currentDurationSeconds: TimeInterval { get }
|
|
@@ -163,6 +175,40 @@ protocol PlaybackEngine: AnyObject {
|
|
|
163
175
|
/// duplicate it for hot-path reads.
|
|
164
176
|
var allMediaItems: [AVPlayerItem] { get }
|
|
165
177
|
|
|
178
|
+
/// Identity of the item actually being heard.
|
|
179
|
+
///
|
|
180
|
+
/// `currentMediaItem` reports the incoming leg the moment a crossfade is in
|
|
181
|
+
/// flight, which is right for "what is the queue moving to" and wrong for
|
|
182
|
+
/// "where is the run anchored". Callers that reason about the installed run
|
|
183
|
+
/// — where it starts, what may be trimmed behind it — read this instead, and
|
|
184
|
+
/// it stays on the outgoing leg until the fade completes.
|
|
185
|
+
var leadingQueueItemId: String? { get }
|
|
186
|
+
|
|
187
|
+
/// Identities of the items attached to the engine's players right now.
|
|
188
|
+
///
|
|
189
|
+
/// Not the whole installed run — only what is loaded and about to be, or
|
|
190
|
+
/// already, heard. A queue mutation that touches one of these has to be
|
|
191
|
+
/// applied to the engine at once; one that does not can wait for a fade to
|
|
192
|
+
/// finish rather than cancelling it.
|
|
193
|
+
///
|
|
194
|
+
/// Mid-fade a crossfading engine reports both legs, which is the only
|
|
195
|
+
/// truthful answer while two items are audible.
|
|
196
|
+
///
|
|
197
|
+
/// Required rather than defaulted, for the same reason as
|
|
198
|
+
/// `isFadePendingOrActive` and more so: an engine that under-reports has a
|
|
199
|
+
/// mutation to an audible item deferred, and the fade then runs into a track
|
|
200
|
+
/// the queue no longer has.
|
|
201
|
+
var attachedQueueItemIds: [String] { get }
|
|
202
|
+
|
|
203
|
+
/// Whether a crossfade is scheduled or running.
|
|
204
|
+
///
|
|
205
|
+
/// Every branch that changes the installed items cancels an in-flight fade,
|
|
206
|
+
/// so work that can wait — extending the run, trimming behind it — asks
|
|
207
|
+
/// first rather than cutting the fade short. Required rather than defaulted:
|
|
208
|
+
/// an engine that answers wrongly has its fades cut short silently, so each
|
|
209
|
+
/// one states its own answer.
|
|
210
|
+
var isFadePendingOrActive: Bool { get }
|
|
211
|
+
|
|
166
212
|
// MARK: - Effect chain
|
|
167
213
|
|
|
168
214
|
/// Set the single active `AudioMixProvider`. The engine consults
|
|
@@ -182,33 +228,15 @@ protocol PlaybackEngine: AnyObject {
|
|
|
182
228
|
/// No-op when no provider is set.
|
|
183
229
|
func refreshAllItemMixes()
|
|
184
230
|
|
|
185
|
-
// MARK: - Handoff
|
|
186
|
-
|
|
187
|
-
/// Hand playback off to [nextEngine] when the user changes
|
|
188
|
-
/// `PlaybackMode`.
|
|
189
|
-
///
|
|
190
|
-
/// Contract:
|
|
191
|
-
/// - The receiver inherits the donor's queue, current index, and
|
|
192
|
-
/// current position; the caller does NOT pre-call `prepare` on
|
|
193
|
-
/// [nextEngine].
|
|
194
|
-
/// - When [atTrackBoundary] is `true`, the donor plays the
|
|
195
|
-
/// active track to its natural end and the receiver starts
|
|
196
|
-
/// playing the NEXT queue position fresh.
|
|
197
|
-
/// - When [atTrackBoundary] is `false` (idle player or empty
|
|
198
|
-
/// queue), the swap is immediate; receiver picks up at the
|
|
199
|
-
/// inherited position.
|
|
200
|
-
/// - The donor's `release` is the caller's responsibility AFTER
|
|
201
|
-
/// the boundary fires; the engine itself does not self-release.
|
|
202
|
-
func handoff(to nextEngine: PlaybackEngine, atTrackBoundary: Bool) async
|
|
203
|
-
|
|
204
231
|
// MARK: - Delegate
|
|
205
232
|
|
|
206
|
-
/// Single delegate receiving state / track /
|
|
207
|
-
/// queue-end events. `TrackPlayer` is the canonical delegate;
|
|
208
|
-
///
|
|
233
|
+
/// Single delegate receiving state / track / error /
|
|
234
|
+
/// queue-end events. `TrackPlayer` is the canonical delegate; the
|
|
235
|
+
/// crossfade engine fires it on `playerQueue`, the gapless engine
|
|
236
|
+
/// never fires it (`TrackPlayer` leaves its delegate nil).
|
|
209
237
|
///
|
|
210
|
-
/// Threading:
|
|
211
|
-
/// a
|
|
238
|
+
/// Threading: set on `playerQueue`, the queue the fires run on, so a
|
|
239
|
+
/// write cannot race a fire during an engine swap.
|
|
212
240
|
var delegate: PlaybackEngineDelegate? { get set }
|
|
213
241
|
}
|
|
214
242
|
|
|
@@ -240,11 +268,6 @@ protocol PlaybackEngineDelegate: AnyObject {
|
|
|
240
268
|
reason: StateChangeReason?
|
|
241
269
|
)
|
|
242
270
|
|
|
243
|
-
/// Fires when the underlying player's `timeControlStatus`
|
|
244
|
-
/// transitions to / from `.playing`. Drives diagnostics +
|
|
245
|
-
/// progress-tick wake-up.
|
|
246
|
-
func engineIsPlayingChanged(_ engine: PlaybackEngine, isPlaying: Bool)
|
|
247
|
-
|
|
248
271
|
/// Fires on `AVPlayer.currentItem` KVO when the active item
|
|
249
272
|
/// changes (auto-advance, user skip, queue replace). The
|
|
250
273
|
/// delegate translates against its own state machine (e.g.
|
|
@@ -257,10 +280,16 @@ protocol PlaybackEngineDelegate: AnyObject {
|
|
|
257
280
|
/// counterpart fires on `Player.Listener.onTracksChanged`.
|
|
258
281
|
func engineActiveItemFormatChanged(_ engine: PlaybackEngine)
|
|
259
282
|
|
|
260
|
-
///
|
|
261
|
-
///
|
|
262
|
-
///
|
|
263
|
-
|
|
283
|
+
/// A playback failure, with the item it happened on when the engine can name
|
|
284
|
+
/// it.
|
|
285
|
+
///
|
|
286
|
+
/// The item is what makes the automatic retry reachable: the retry is keyed
|
|
287
|
+
/// by `queueItemId`, so a failure reported without one is surfaced to the
|
|
288
|
+
/// consumer and never retried. Engines that know which item failed must say
|
|
289
|
+
/// so — both of `CrossfadeEngine`'s failure paths do, since each is already
|
|
290
|
+
/// filtered to the leading leg's current item.
|
|
291
|
+
func engineDidFailWithError(
|
|
292
|
+
_ engine: PlaybackEngine, error: Error, item: AVPlayerItem?)
|
|
264
293
|
|
|
265
294
|
/// Fires on every `AVPlayerItemDidPlayToEndTime` — i.e. each
|
|
266
295
|
/// item's natural end, including non-terminal items in a queue.
|
|
@@ -286,10 +315,12 @@ protocol PlaybackEngineDelegate: AnyObject {
|
|
|
286
315
|
/// subsequent `engineDidTransitionTrack` for the same incoming
|
|
287
316
|
/// index is a structural KVO echo and must be deduplicated.
|
|
288
317
|
///
|
|
289
|
-
/// Crossfade-only — `GaplessEngine` never invokes this.
|
|
290
|
-
///
|
|
291
|
-
///
|
|
292
|
-
|
|
318
|
+
/// Crossfade-only — `GaplessEngine` never invokes this. The
|
|
319
|
+
/// incoming track is read from `engine.currentMediaItem`, which
|
|
320
|
+
/// already resolves to the standby leg once the fade is in flight;
|
|
321
|
+
/// the delegate maps that item to a queue position by identity
|
|
322
|
+
/// rather than trusting an engine-relative index.
|
|
323
|
+
func engineCrossfadeDidBegin(_ engine: PlaybackEngine)
|
|
293
324
|
|
|
294
325
|
/// Fires when an in-flight crossfade is cancelled before
|
|
295
326
|
/// `swapRoles` runs (pause / stop / audio-focus loss / queue
|
|
@@ -300,20 +331,78 @@ protocol PlaybackEngineDelegate: AnyObject {
|
|
|
300
331
|
/// `engineCrossfadeDidBegin`: only fires when a `DidBegin` was
|
|
301
332
|
/// fired earlier and the fade did not complete.
|
|
302
333
|
///
|
|
303
|
-
/// Crossfade-only.
|
|
304
|
-
///
|
|
305
|
-
|
|
334
|
+
/// Crossfade-only. The reverted track is read from
|
|
335
|
+
/// `engine.currentMediaItem`, which resolves back to the leading leg
|
|
336
|
+
/// once `fadeInFlight` clears.
|
|
337
|
+
func engineCrossfadeDidCancel(_ engine: PlaybackEngine)
|
|
338
|
+
|
|
339
|
+
/// A fade is no longer scheduled or running, however it ended.
|
|
340
|
+
///
|
|
341
|
+
/// Distinct from `engineCrossfadeDidCancel`, which reports that an
|
|
342
|
+
/// *in-flight* fade was reverted and carries a track change with it. This
|
|
343
|
+
/// fires for every end of every fade — completed, cancelled, abandoned
|
|
344
|
+
/// during the arm window, timed out — because work that waits on a fade has
|
|
345
|
+
/// to be released by all of them, not only the ones a listener cares about.
|
|
346
|
+
func engineFadeDidSettle(_ engine: PlaybackEngine)
|
|
347
|
+
|
|
348
|
+
/// The active leg's buffer flags changed (`isPlaybackBufferEmpty` /
|
|
349
|
+
/// `isPlaybackLikelyToKeepUp` / `isPlaybackBufferFull`). Prompts a
|
|
350
|
+
/// buffer-state recompute so the discrete `onBufferStateChange` surface
|
|
351
|
+
/// tracks the crossfade engine's leg the same way it tracks the gapless
|
|
352
|
+
/// `AVQueuePlayer`'s item KVO. Buffer state only — no direct player-state
|
|
353
|
+
/// emission.
|
|
354
|
+
func engineBufferStateMaybeChanged(_ engine: PlaybackEngine)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
extension PlaybackEngineDelegate {
|
|
358
|
+
/// Nothing to release by default.
|
|
359
|
+
func engineFadeDidSettle(_ engine: PlaybackEngine) {}
|
|
360
|
+
/// The gapless engine drives buffer recompute through its own item KVO, not
|
|
361
|
+
/// this delegate; conformers that do not surface buffer state ignore it.
|
|
362
|
+
func engineBufferStateMaybeChanged(_ engine: PlaybackEngine) {}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
extension PlaybackEngine {
|
|
366
|
+
/// An engine with one leg is always leading with the item it reports as
|
|
367
|
+
/// current; only a crossfading engine has two ends that can disagree.
|
|
368
|
+
var leadingQueueItemId: String? { currentMediaItem?.queueItemId }
|
|
369
|
+
|
|
306
370
|
}
|
|
307
371
|
|
|
308
372
|
/// Single-slot audio-mix provider. The equalizer + visualizer share
|
|
309
373
|
/// a single provider that builds an `MTAudioProcessingTap`-backed
|
|
310
374
|
/// `AVAudioMix` per `AVPlayerItem` the engine constructs.
|
|
311
375
|
///
|
|
312
|
-
/// `audioMix(for:)` is called on the
|
|
313
|
-
///
|
|
376
|
+
/// `audioMix(for:)` is called on the mix dispatch target — `TrackPlayer`
|
|
377
|
+
/// points it at `playerQueue`; implementations may allocate freshly or
|
|
378
|
+
/// cache + reuse. Returning `nil` skips
|
|
314
379
|
/// attachment for that item — used to bypass mix on items that
|
|
315
380
|
/// don't support a tap (HLS sources).
|
|
316
381
|
protocol AudioMixProvider: AnyObject {
|
|
317
382
|
/// Build the audio mix for [item], or return `nil` to skip.
|
|
318
383
|
func audioMix(for item: AVPlayerItem) -> AVAudioMix?
|
|
384
|
+
|
|
385
|
+
/// Declare which asset is active, without building a mix. `audioMix(for:)`
|
|
386
|
+
/// announces whatever item it is handed, so a caller that refreshes several
|
|
387
|
+
/// items uses this to name the one actually playing.
|
|
388
|
+
func noteActiveAsset(_ asset: AVAsset?)
|
|
389
|
+
|
|
390
|
+
/// Whether any registered consumer wants the tap installed right now (the
|
|
391
|
+
/// equaliser is enabled, or a visualizer has subscribers). The crossfade
|
|
392
|
+
/// engine holds a freshly-seated leading leg at rate 0 until its
|
|
393
|
+
/// `.readyToPlay` mix install lands only when this is true — a mix set once
|
|
394
|
+
/// the leg is already rendering is dropped, silencing the tap — so the
|
|
395
|
+
/// common no-tap path is left to start immediately.
|
|
396
|
+
func anyConsumerWantsTap() -> Bool
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
extension AudioMixProvider {
|
|
400
|
+
/// A provider with no consumer model (a test double) never holds a leg for a
|
|
401
|
+
/// tap; only `AudioTapProvider` overrides this.
|
|
402
|
+
func anyConsumerWantsTap() -> Bool { false }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
extension AudioMixProvider {
|
|
406
|
+
/// Providers that track no active asset need no behaviour here.
|
|
407
|
+
func noteActiveAsset(_ asset: AVAsset?) {}
|
|
319
408
|
}
|
|
@@ -53,9 +53,9 @@ public enum PlaybackErrorMapping {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/// Strip query strings from a native error message so logged
|
|
56
|
-
/// `nativeMessage` doesn't carry auth tokens
|
|
57
|
-
///
|
|
58
|
-
/// `localizedDescription` strings
|
|
56
|
+
/// `nativeMessage` doesn't carry auth tokens: stream URLs with
|
|
57
|
+
/// `?token=…`-style query strings end up inside
|
|
58
|
+
/// `localizedDescription` strings, and every `?…` run is removed.
|
|
59
59
|
///
|
|
60
60
|
/// Walks the entire message, removing every `?…(whitespace|end)`
|
|
61
61
|
/// run so a multi-URL error message ("redirect from
|
|
@@ -66,8 +66,8 @@ final class PlaybackModeStateMachine {
|
|
|
66
66
|
/// the Nitro Promise<Void> bridge as a JS-thrown error.
|
|
67
67
|
///
|
|
68
68
|
/// The caller is responsible for managing the brief audio
|
|
69
|
-
/// interruption
|
|
70
|
-
///
|
|
69
|
+
/// interruption — `applyEngineSwapForMode` carries the position
|
|
70
|
+
/// and re-installs the queue.
|
|
71
71
|
func setRequested(
|
|
72
72
|
state: State,
|
|
73
73
|
requested: PlaybackMode
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Network
|
|
3
|
+
|
|
4
|
+
/// Reports the moment the network comes back after an outage.
|
|
5
|
+
///
|
|
6
|
+
/// A player that failed its retries while the device was offline has nothing
|
|
7
|
+
/// to react to when connectivity returns: the item stays failed and playback
|
|
8
|
+
/// stays stopped until the user notices and presses play. This is the signal
|
|
9
|
+
/// that lets recovery run at the point it can actually succeed.
|
|
10
|
+
///
|
|
11
|
+
/// Only the **down → up** edge is reported. A satisfied → satisfied update —
|
|
12
|
+
/// a Wi-Fi to cellular handoff, an interface change — is not an outage
|
|
13
|
+
/// recovery, and treating it as one would retry against a connection that
|
|
14
|
+
/// never dropped.
|
|
15
|
+
///
|
|
16
|
+
/// One-shot: `start()` arms the monitor once, and `stop()` after it is
|
|
17
|
+
/// terminal — a later `start()` does nothing. `NWPathMonitor` does not define
|
|
18
|
+
/// `start` after `cancel`, and the player never needs it: `configure()`
|
|
19
|
+
/// creates a fresh monitor and destroy stops it.
|
|
20
|
+
///
|
|
21
|
+
/// `NWPathMonitor` delivers the current path once on `start` and then on
|
|
22
|
+
/// every change, so a monitor started while online reports that first
|
|
23
|
+
/// delivery as a restore (the seed below is unsatisfied). Downstream that is
|
|
24
|
+
/// harmless — recovery only runs for an item that has already failed.
|
|
25
|
+
final class PlaybackNetworkMonitor {
|
|
26
|
+
|
|
27
|
+
/// Fired on the down → up transition, on `queue`.
|
|
28
|
+
var onNetworkRestored: (() -> Void)?
|
|
29
|
+
|
|
30
|
+
private let monitor = NWPathMonitor()
|
|
31
|
+
private let queue: DispatchQueue
|
|
32
|
+
/// Seeded unsatisfied so a monitor that starts while offline reports the
|
|
33
|
+
/// first success as a recovery rather than swallowing it as "no change".
|
|
34
|
+
private var lastStatus: NWPath.Status = .unsatisfied
|
|
35
|
+
private var started = false
|
|
36
|
+
/// Set by `stop()` after a `start()`; nothing clears it.
|
|
37
|
+
private var stopped = false
|
|
38
|
+
|
|
39
|
+
/// - Parameter queue: the queue callbacks are delivered on. Callers pass the
|
|
40
|
+
/// queue that serialises player state, so the handler needs no hop.
|
|
41
|
+
init(queue: DispatchQueue) {
|
|
42
|
+
self.queue = queue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func start() {
|
|
46
|
+
guard !started, !stopped else { return }
|
|
47
|
+
started = true
|
|
48
|
+
monitor.pathUpdateHandler = { [weak self] path in
|
|
49
|
+
guard let self else { return }
|
|
50
|
+
let previous = self.lastStatus
|
|
51
|
+
self.lastStatus = path.status
|
|
52
|
+
guard previous != .satisfied, path.status == .satisfied else { return }
|
|
53
|
+
self.onNetworkRestored?()
|
|
54
|
+
}
|
|
55
|
+
monitor.start(queue: queue)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Terminal once the monitor has started; a `stop()` before any `start()`
|
|
59
|
+
/// changes nothing.
|
|
60
|
+
func stop() {
|
|
61
|
+
guard started else { return }
|
|
62
|
+
started = false
|
|
63
|
+
stopped = true
|
|
64
|
+
monitor.pathUpdateHandler = nil
|
|
65
|
+
monitor.cancel()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// A monitor dropped without `stop()` — `configure()` replaced it, or the
|
|
69
|
+
/// player was released — must not keep its handler queue alive.
|
|
70
|
+
deinit {
|
|
71
|
+
monitor.cancel()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -7,14 +7,14 @@ import AVFoundation
|
|
|
7
7
|
/// Android's `TransportStateTranslator`; pure + deterministic.
|
|
8
8
|
///
|
|
9
9
|
/// iOS has no native "ended" player state (unlike Android's `STATE_ENDED`), so
|
|
10
|
-
/// `
|
|
11
|
-
///
|
|
10
|
+
/// `TrackPlayer` sets `reachedQueueEnd` when the queue drains with repeat off
|
|
11
|
+
/// and clears it when playback resumes / a new track loads.
|
|
12
12
|
enum PlayerStateDerivation {
|
|
13
13
|
/// - Parameters:
|
|
14
14
|
/// - status: the active player's `timeControlStatus`.
|
|
15
15
|
/// - currentIndex: the lib's canonical current track index (`-1` = empty).
|
|
16
16
|
/// - hasCurrentItem: whether the active player has a current item.
|
|
17
|
-
/// - reachedQueueEnd: the
|
|
17
|
+
/// - reachedQueueEnd: whether the queue has drained with repeat off (see type doc).
|
|
18
18
|
static func translate(
|
|
19
19
|
status: AVPlayer.TimeControlStatus,
|
|
20
20
|
currentIndex: Int,
|
|
@@ -22,8 +22,8 @@ enum PlayerStateDerivation {
|
|
|
22
22
|
reachedQueueEnd: Bool
|
|
23
23
|
) -> PlayerState {
|
|
24
24
|
// A drained queue (repeat off) that isn't actively playing is terminal.
|
|
25
|
-
// `.playing` takes precedence so a resume reads correctly even before
|
|
26
|
-
//
|
|
25
|
+
// `.playing` takes precedence so a resume reads correctly even before
|
|
26
|
+
// `reachedQueueEnd` is cleared.
|
|
27
27
|
if reachedQueueEnd, currentIndex >= 0, status != .playing {
|
|
28
28
|
return .ended
|
|
29
29
|
}
|
|
@@ -33,7 +33,7 @@ enum PlayerStateDerivation {
|
|
|
33
33
|
case .waitingToPlayAtSpecifiedRate:
|
|
34
34
|
// Waiting WITH a current item = initial load / rebuffer → `.buffering`.
|
|
35
35
|
// Waiting with NO item = the AVQueuePlayer advanced past the last item
|
|
36
|
-
// (end of queue) before
|
|
36
|
+
// (end of queue) before `reachedQueueEnd` was set → terminal `.ended`.
|
|
37
37
|
return hasCurrentItem ? .buffering : (currentIndex < 0 ? .none : .ended)
|
|
38
38
|
case .playing:
|
|
39
39
|
return .playing
|
|
@@ -19,6 +19,31 @@ import Foundation
|
|
|
19
19
|
/// test bundle.
|
|
20
20
|
public enum QueueMutationArithmetic {
|
|
21
21
|
|
|
22
|
+
/// How many leading positions the engine already holds correctly.
|
|
23
|
+
///
|
|
24
|
+
/// A rebuild that installs the whole slice throws away the item the engine
|
|
25
|
+
/// has already buffered, which is an audible cut when it happens at a
|
|
26
|
+
/// boundary. Comparing what the engine holds against what it should hold
|
|
27
|
+
/// says how much of the front can stay, so only the divergent tail is
|
|
28
|
+
/// replaced.
|
|
29
|
+
///
|
|
30
|
+
/// Both sides are `queueItemId` — the library's own per-position identity.
|
|
31
|
+
/// Consumer fields cannot be used here: two tracks may be the same URL, and
|
|
32
|
+
/// `track.id` is optional consumer data, so neither distinguishes positions.
|
|
33
|
+
/// A `nil` id belongs to an item the library did not stamp and never
|
|
34
|
+
/// matches, so the prefix stops there.
|
|
35
|
+
@_spi(QueuePlayerTests)
|
|
36
|
+
public static func commonPrefixLength(
|
|
37
|
+
current: [String?], desired: [String]
|
|
38
|
+
) -> Int {
|
|
39
|
+
var length = 0
|
|
40
|
+
while length < current.count, length < desired.count {
|
|
41
|
+
guard let id = current[length], id == desired[length] else { break }
|
|
42
|
+
length += 1
|
|
43
|
+
}
|
|
44
|
+
return length
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
/// Clamp an `insertBefore` argument to a valid insertion point in
|
|
23
48
|
/// a track list of size `trackCount`. `nil` → append (return
|
|
24
49
|
/// `trackCount`). Out-of-range values clamp into `0...trackCount`.
|
|
@@ -91,14 +91,16 @@ public enum QueueSkipArithmetic {
|
|
|
91
91
|
/// When `restartEnabled` is true (`PlayerConfig.skipToPreviousBehavior
|
|
92
92
|
/// == .restartOrPrevious`), Previous is enabled whenever a track is
|
|
93
93
|
/// loaded — even at index 0, where past the threshold it restarts the
|
|
94
|
-
/// current track — so the control never greys out
|
|
94
|
+
/// current track — so the control never greys out while something is
|
|
95
|
+
/// playing. With no loaded track (`currentIndex < 0`) there is nothing
|
|
96
|
+
/// to restart, matching what resolveSkipToPrevious does for that state.
|
|
95
97
|
public static func canSkipPrevious(
|
|
96
98
|
trackCount: Int,
|
|
97
99
|
currentIndex: Int,
|
|
98
100
|
repeatMode: QueueSkipRepeatMode,
|
|
99
101
|
restartEnabled: Bool = false
|
|
100
102
|
) -> Bool {
|
|
101
|
-
if restartEnabled { return trackCount > 0 }
|
|
103
|
+
if restartEnabled { return trackCount > 0 && currentIndex >= 0 }
|
|
102
104
|
return computePrevious(
|
|
103
105
|
trackCount: trackCount,
|
|
104
106
|
currentIndex: currentIndex,
|
|
@@ -117,7 +119,7 @@ public enum QueueSkipArithmetic {
|
|
|
117
119
|
case previous(Int)
|
|
118
120
|
/// Restart the current track (seek to 0) — not a track change.
|
|
119
121
|
case restartCurrent
|
|
120
|
-
/// Nothing to do (empty queue).
|
|
122
|
+
/// Nothing to do (empty queue, or no loaded track).
|
|
121
123
|
case noOp
|
|
122
124
|
}
|
|
123
125
|
|