react-native-queue-player 1.2.0 → 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/consumer-rules.pro +1 -1
- 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 +3 -19
- 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 +8 -69
- 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 +839 -271
- 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/CastSession.kt +5 -4
- 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 +4 -2
- 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 +1 -1
- 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/AirplayJNI.kt +1 -13
- 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/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 +17 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
- 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 +2 -2
- 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 +1 -1
- 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 +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
- 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 +2 -2
- 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/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +2 -2
- 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 +1 -1
- 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/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/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 -4619
- 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/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
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
// MARK: - PlaybackEngineDelegate
|
|
5
|
+
|
|
6
|
+
/// `CrossfadeEngine` fires these callbacks; `GaplessEngine`'s
|
|
7
|
+
/// equivalent surface continues to flow through `self.player`'s
|
|
8
|
+
/// KVO chain installed by `installGaplessObservers` /
|
|
9
|
+
/// `installEventObservers`. Each delegate method therefore runs
|
|
10
|
+
/// only when the active engine is `CrossfadeEngine` (per the
|
|
11
|
+
/// `delegate = self` wiring in `applyEngineSwapForMode`).
|
|
12
|
+
extension TrackPlayer: PlaybackEngineDelegate {
|
|
13
|
+
func engineStateMaybeChanged(
|
|
14
|
+
_ engine: PlaybackEngine, reason: StateChangeReason?
|
|
15
|
+
) {
|
|
16
|
+
if let reason {
|
|
17
|
+
// Engine-supplied classified reason takes precedence over
|
|
18
|
+
// any prior staked reason — match the gapless KVO path
|
|
19
|
+
// where pendingStateChangeReason is overwritten by the
|
|
20
|
+
// newer staked reason.
|
|
21
|
+
self.pendingStateChangeReason = reason
|
|
22
|
+
}
|
|
23
|
+
// The leading leg's `timeControlStatus` drives `.buffering` (load /
|
|
24
|
+
// rebuffer) and `reachedQueueEnd` drives `.ended`.
|
|
25
|
+
let computed = PlayerStateDerivation.translate(
|
|
26
|
+
status: engine.timeControlStatus,
|
|
27
|
+
currentIndex: self.currentTrackIndex,
|
|
28
|
+
hasCurrentItem: engine.currentMediaItem != nil,
|
|
29
|
+
reachedQueueEnd: self.reachedQueueEnd
|
|
30
|
+
)
|
|
31
|
+
emitState(computed, reason: reasonForComputedState(computed))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func engineDidTransitionTrack(_ engine: PlaybackEngine) {
|
|
35
|
+
// Bring TrackPlayer's currentTrackIndex into sync with the
|
|
36
|
+
// engine's index — CrossfadeEngine.completeCrossfade
|
|
37
|
+
// increments engine.currentMediaItemIndex; on setItems / seek
|
|
38
|
+
// the engine's index is already aligned with the caller's
|
|
39
|
+
// resumeIndex.
|
|
40
|
+
//
|
|
41
|
+
// Crossfade post-swap echo: fade-start already fired
|
|
42
|
+
// `engineCrossfadeDidBegin` which dispatched the JS-facing
|
|
43
|
+
// track-change for the same incoming index. The
|
|
44
|
+
// `lastEmittedQueueItemId` dedup inside `dispatchTrackChange`
|
|
45
|
+
// turns the second call into a no-op. Capability + lookahead
|
|
46
|
+
// refresh still run unconditionally — both are idempotent and
|
|
47
|
+
// the second pass picks up any state that landed during the
|
|
48
|
+
// fade window.
|
|
49
|
+
// Resolve the engine's active item to a queue position by identity.
|
|
50
|
+
// An engine's own index counts within whatever slice it was handed, so
|
|
51
|
+
// it is not a queue position; `queueItemId` is, and it survives the
|
|
52
|
+
// item-replacement paths (cross-attach, cached-URL upgrade).
|
|
53
|
+
//
|
|
54
|
+
// Still skipped during a queue mutation: the mutation already computed
|
|
55
|
+
// the authoritative `currentTrackIndex` via QueueMutationArithmetic, and
|
|
56
|
+
// the engine is mid-rebuild. (CrossfadeEngine.setItems fires this
|
|
57
|
+
// delegate synchronously; GaplessEngine does not.)
|
|
58
|
+
if !self.isMutatingPlayerQueue,
|
|
59
|
+
let resolved = self.matchTrackIndex(forCurrentItem: engine.currentMediaItem) {
|
|
60
|
+
self.currentTrackIndex = resolved
|
|
61
|
+
}
|
|
62
|
+
let reason = self.pendingTrackChangeReason ?? .autoAdvance
|
|
63
|
+
self.pendingTrackChangeReason = nil
|
|
64
|
+
dispatchTrackChange(reason: reason)
|
|
65
|
+
self.recomputeCapabilities()
|
|
66
|
+
self.rescheduleLookahead()
|
|
67
|
+
// Extend the installed run as the playhead advances into it. Driven from
|
|
68
|
+
// the transition rather than from arming, which would force the build into
|
|
69
|
+
// the moment immediately before the ramp is scheduled.
|
|
70
|
+
//
|
|
71
|
+
// Not while a mutation is in flight: this delegate is fired synchronously
|
|
72
|
+
// from `CrossfadeEngine.setItems`, so topping up there would re-enter
|
|
73
|
+
// `setItems` from inside itself — and the mutation that raised the flag
|
|
74
|
+
// installs the run it wants anyway.
|
|
75
|
+
if !self.isMutatingPlayerQueue {
|
|
76
|
+
self.topUpWindow()
|
|
77
|
+
}
|
|
78
|
+
// Refresh the now-playing format for the new active item. The crossfade
|
|
79
|
+
// engine's `\.tracks` KVO is `.new`-only and a forward skip rebuilds the
|
|
80
|
+
// incoming leg as a fresh AVPlayerItem whose tracks key isn't auto-loaded
|
|
81
|
+
// (only playable/duration are), so engineActiveItemFormatChanged may never
|
|
82
|
+
// fire for it — without this, getNowPlayingFormat() stays nil after a
|
|
83
|
+
// skip to a cached track. The extractor loads the format asynchronously
|
|
84
|
+
// and the lastEmittedFormatItemId dedup makes a redundant call a no-op.
|
|
85
|
+
self.refreshNowPlayingFormatForActiveItem()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func engineCrossfadeDidBegin(_ engine: PlaybackEngine) {
|
|
89
|
+
// Fade-start: the standby leg is now audibly playing (volume 0
|
|
90
|
+
// ramping up). Flip JS-facing active track + lock-screen / Now
|
|
91
|
+
// Playing surface to the incoming track immediately so the
|
|
92
|
+
// user sees the new track's title/artwork the moment they
|
|
93
|
+
// start hearing it. The leading leg continues fading out for
|
|
94
|
+
// the configured `crossfadeDurationMs`; engine accessors
|
|
95
|
+
// already prefer the standby leg while `fadeInFlight` is true.
|
|
96
|
+
//
|
|
97
|
+
// Reason is hardcoded `.autoAdvance` — fade-start is always an
|
|
98
|
+
// auto-advance event; any user-driven transition (skipToNext /
|
|
99
|
+
// skipToIndex / setMediaItems) would have called
|
|
100
|
+
// `cancelFadeInternal` first, so a stashed
|
|
101
|
+
// `pendingTrackChangeReason` here is stale and should not
|
|
102
|
+
// re-attribute the fade.
|
|
103
|
+
// A queue mutation already computed the authoritative index; the
|
|
104
|
+
// rebuild's own fade-cancel must not walk it back to the outgoing leg.
|
|
105
|
+
guard !self.isMutatingPlayerQueue,
|
|
106
|
+
let incoming = self.matchTrackIndex(forCurrentItem: engine.currentMediaItem)
|
|
107
|
+
else { return }
|
|
108
|
+
self.currentTrackIndex = incoming
|
|
109
|
+
self.pendingTrackChangeReason = nil
|
|
110
|
+
dispatchTrackChange(reason: .autoAdvance)
|
|
111
|
+
self.recomputeCapabilities()
|
|
112
|
+
self.rescheduleLookahead()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func engineCrossfadeDidCancel(_ engine: PlaybackEngine) {
|
|
116
|
+
// Fade-cancelled mid-flight (pause / stop / focus loss /
|
|
117
|
+
// queue mutation). JS already received `onTrackChange(incoming,
|
|
118
|
+
// reason: .autoAdvance)` from the prior fade-start fire; the
|
|
119
|
+
// incoming track is no longer the active one. Revert
|
|
120
|
+
// `currentTrackIndex` to the leading-leg index and emit
|
|
121
|
+
// `onTrackChange(leading, reason: .crossfadeCancelled)` so
|
|
122
|
+
// consumer UI can rewind to the leading track.
|
|
123
|
+
// See `engineCrossfadeDidBegin` — a rebuild issued by a queue mutation
|
|
124
|
+
// cancels the in-flight fade, and that cancel must not clobber the
|
|
125
|
+
// index the mutation just computed.
|
|
126
|
+
guard !self.isMutatingPlayerQueue,
|
|
127
|
+
let reverted = self.matchTrackIndex(forCurrentItem: engine.currentMediaItem)
|
|
128
|
+
else { return }
|
|
129
|
+
self.currentTrackIndex = reverted
|
|
130
|
+
self.pendingTrackChangeReason = nil
|
|
131
|
+
dispatchTrackChange(reason: .crossfadeCancelled)
|
|
132
|
+
self.recomputeCapabilities()
|
|
133
|
+
self.rescheduleLookahead()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Apply whatever was held back while the fade ran.
|
|
137
|
+
///
|
|
138
|
+
/// Fired for every end of a fade, which is the point: a cancel taken during
|
|
139
|
+
/// the arm window reverts nothing and so reports nothing else, and work
|
|
140
|
+
/// waiting on that fade would otherwise wait for one that is already over —
|
|
141
|
+
/// with the engine still holding a run the queue has retired.
|
|
142
|
+
func engineFadeDidSettle(_ engine: PlaybackEngine) {
|
|
143
|
+
_ = engine
|
|
144
|
+
self.flushDeferredEngineWork()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func engineBufferStateMaybeChanged(_ engine: PlaybackEngine) {
|
|
148
|
+
_ = engine
|
|
149
|
+
// Buffer surface only — no direct player-state emission. The crossfade
|
|
150
|
+
// leading leg's buffer-flag KVO lands here; recompute the discrete
|
|
151
|
+
// buffer-state and fully-buffered surfaces, exactly as the gapless item KVO
|
|
152
|
+
// does through `installBufferObservers` (the stall-recovery restart inside
|
|
153
|
+
// `recomputeBufferState` still applies here, as on gapless). Already on
|
|
154
|
+
// `playerQueue` (the engine's ownerQueue), so recompute synchronously — a
|
|
155
|
+
// second async hop would reorder against the recompute's change dedup.
|
|
156
|
+
self.recomputeBufferState()
|
|
157
|
+
self.recomputeFullyBuffered()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
func engineActiveItemFormatChanged(_ engine: PlaybackEngine) {
|
|
161
|
+
_ = engine
|
|
162
|
+
self.refreshNowPlayingFormatForActiveItem()
|
|
163
|
+
// Streaming-item audioMix catch-up for the crossfade engine
|
|
164
|
+
// (which fires this delegate hook on its own KVO chain). The
|
|
165
|
+
// gapless engine has a parallel catch-up path through
|
|
166
|
+
// `dispatchItemStatus(.readyToPlay)`; both call into
|
|
167
|
+
// `refreshActiveItemMixes`, which is idempotent on items that
|
|
168
|
+
// already carry a tap, so a double-fire is harmless.
|
|
169
|
+
AudioTapProvider.shared.refreshActiveItemMixes()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func engineDidFailWithError(
|
|
173
|
+
_ engine: PlaybackEngine, error: Error, item: AVPlayerItem?
|
|
174
|
+
) {
|
|
175
|
+
_ = engine
|
|
176
|
+
let underlying = error as NSError
|
|
177
|
+
// The item is forwarded, not dropped: without it `dispatchErrorOrRetry`
|
|
178
|
+
// synthesises a key that cannot appear in `queueItemIds`, and the
|
|
179
|
+
// automatic-retry branch requires a real `queueItemId`.
|
|
180
|
+
dispatchErrorOrRetry(
|
|
181
|
+
item: item,
|
|
182
|
+
underlying: underlying,
|
|
183
|
+
nativeDomainOverride: nil
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func engineDidPlayItemToEnd(_ engine: PlaybackEngine) {
|
|
188
|
+
// CrossfadeEngine fires this on natural end-of-leading; the
|
|
189
|
+
// boundary signal that drives JS-facing track-change is
|
|
190
|
+
// engineDidTransitionTrack which fires immediately after.
|
|
191
|
+
//
|
|
192
|
+
// Under repeat-one the same track replays with no track-change (the
|
|
193
|
+
// queueItemId is unchanged, so dispatchTrackChange's dedup skips its
|
|
194
|
+
// reset). Each loop is a fresh playthrough, so re-arm the milestone
|
|
195
|
+
// tracker here for that case only.
|
|
196
|
+
if self.repeatModeState == .track {
|
|
197
|
+
self.milestoneTracker.reset()
|
|
198
|
+
}
|
|
199
|
+
_ = engine
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func enginePlaybackEnded(_ engine: PlaybackEngine) {
|
|
203
|
+
_ = engine
|
|
204
|
+
// The engine reports that it drained; what that means for the queue —
|
|
205
|
+
// wrap, refill, or over — is decided above it, the same way for both
|
|
206
|
+
// engines.
|
|
207
|
+
applyEndVerdict()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
extension TrackPlayer {
|
|
4
|
+
// MARK: - Events — public registration surface
|
|
5
|
+
//
|
|
6
|
+
// Each event has a multi-listener `ListenerRegistry`. Each `onXxx`
|
|
7
|
+
// call appends a new entry; the returned closure removes that entry
|
|
8
|
+
// by stable id. ListenerRegistry's internal queue.sync makes
|
|
9
|
+
// add/remove safe against concurrent fire rounds — fire iterates a
|
|
10
|
+
// snapshot taken under the lock.
|
|
11
|
+
|
|
12
|
+
func onStateChange(
|
|
13
|
+
callback: @escaping (_ state: PlayerState, _ reason: StateChangeReason) -> Void
|
|
14
|
+
) throws -> () -> Void {
|
|
15
|
+
let id = stateChangeListeners.add(callback)
|
|
16
|
+
return { [weak self] in self?.stateChangeListeners.remove(id: id) }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func onTrackChange(
|
|
20
|
+
callback: @escaping (_ track: TrackItem?, _ index: Double, _ reason: TrackChangeReason, _ transitionGapMs: Double?) -> Void
|
|
21
|
+
) throws -> () -> Void {
|
|
22
|
+
let id = trackChangeListeners.add(callback)
|
|
23
|
+
return { [weak self] in self?.trackChangeListeners.remove(id: id) }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func onQueueChange(
|
|
27
|
+
callback: @escaping (_ delta: QueueChangeDelta, _ currentIndex: Double, _ reason: QueueChangeReason) -> Void
|
|
28
|
+
) throws -> () -> Void {
|
|
29
|
+
let id = queueChangeListeners.add(callback)
|
|
30
|
+
return { [weak self] in self?.queueChangeListeners.remove(id: id) }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Emit a queue change as a description of what happened rather than a copy
|
|
34
|
+
/// of the queue. Callers pass only the fields their mutation defines; the
|
|
35
|
+
/// rest stay empty, and `revision` lets a subscriber notice a dropped event.
|
|
36
|
+
internal func emitQueueChange(
|
|
37
|
+
_ reason: QueueChangeReason,
|
|
38
|
+
inserted: [TrackItem] = [],
|
|
39
|
+
insertedAt: Int = -1,
|
|
40
|
+
removed: [Int] = [],
|
|
41
|
+
movedFrom: Int = -1,
|
|
42
|
+
movedTo: Int = -1,
|
|
43
|
+
order: [Int] = []
|
|
44
|
+
) {
|
|
45
|
+
self.queueRevision += 1
|
|
46
|
+
let delta = QueueChangeDelta(
|
|
47
|
+
revision: Double(self.queueRevision),
|
|
48
|
+
length: Double(self.tracks.count),
|
|
49
|
+
inserted: inserted,
|
|
50
|
+
insertedAt: Double(insertedAt),
|
|
51
|
+
removed: removed.map(Double.init),
|
|
52
|
+
movedFrom: Double(movedFrom),
|
|
53
|
+
movedTo: Double(movedTo),
|
|
54
|
+
order: order.map(Double.init)
|
|
55
|
+
)
|
|
56
|
+
let index = Double(self.currentTrackIndex)
|
|
57
|
+
queueChangeListeners.forEach { $0(delta, index, reason) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func onProgress(
|
|
61
|
+
callback: @escaping (_ progress: PlayerProgress) -> Void
|
|
62
|
+
) throws -> () -> Void {
|
|
63
|
+
let id = progressListeners.add(callback)
|
|
64
|
+
return { [weak self] in self?.progressListeners.remove(id: id) }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func onPlaybackMilestone(
|
|
68
|
+
callback: @escaping (_ milestone: Double, _ trackIndex: Double) -> Void
|
|
69
|
+
) throws -> () -> Void {
|
|
70
|
+
let id = milestoneListeners.add(callback)
|
|
71
|
+
return { [weak self] in self?.milestoneListeners.remove(id: id) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func onError(callback: @escaping (_ error: PlaybackError) -> Void) throws -> () -> Void {
|
|
75
|
+
let id = errorListeners.add(callback)
|
|
76
|
+
return { [weak self] in self?.errorListeners.remove(id: id) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func onQueueEnd(callback: @escaping () -> Void) throws -> () -> Void {
|
|
80
|
+
let id = queueEndListeners.add(callback)
|
|
81
|
+
return { [weak self] in self?.queueEndListeners.remove(id: id) }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Subscribe to skip-capability changes. Auto-fires the callback
|
|
85
|
+
/// once on registration with the current cached values (mirrors
|
|
86
|
+
/// Media3 `Player.Listener.onAvailableCommandsChanged`) so new
|
|
87
|
+
/// subscribers don't have to seed via the getters first. The
|
|
88
|
+
/// auto-fire hops to `playerQueue` so the cache read is on the
|
|
89
|
+
/// queue that writes it. Subsequent fires happen synchronously
|
|
90
|
+
/// from `recomputeCapabilities()`, also on `playerQueue`.
|
|
91
|
+
///
|
|
92
|
+
/// **Dispose race guard:** the deferred fire checks the
|
|
93
|
+
/// subscriber is still registered (`contains(id:)`) before
|
|
94
|
+
/// invoking the callback. Strict-mode subscribe → dispose →
|
|
95
|
+
/// subscribe sequences fire the cleanup before the deferred fire
|
|
96
|
+
/// runs; without the guard, the auto-fire would call into an
|
|
97
|
+
/// already-unmounted React component's `setSnapshot`.
|
|
98
|
+
func onSkipCapabilityChange(
|
|
99
|
+
callback: @escaping (_ capability: SkipCapability) -> Void
|
|
100
|
+
) throws -> () -> Void {
|
|
101
|
+
let id = skipCapabilityListeners.add(callback)
|
|
102
|
+
playerQueue.async { [weak self] in
|
|
103
|
+
guard let self else { return }
|
|
104
|
+
guard self.skipCapabilityListeners.contains(id: id) else { return }
|
|
105
|
+
callback(SkipCapability(
|
|
106
|
+
canSkipPrevious: self.cachedSkipCapability.canSkipPrevious,
|
|
107
|
+
canSkipNext: self.cachedSkipCapability.canSkipNext))
|
|
108
|
+
}
|
|
109
|
+
return { [weak self] in self?.skipCapabilityListeners.remove(id: id) }
|
|
110
|
+
}
|
|
111
|
+
}
|