react-native-queue-player 1.2.0 → 2.0.1
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 +111 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +78 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaResponseCheck.kt +156 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaValidatingDataSource.kt +165 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/MimeCapturingDataSource.kt +18 -9
- 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 +13 -5
- 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 +985 -276
- 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 +84 -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/MediaResponseCheckTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaValidatingDataSourceTest.kt +189 -0
- 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 +122 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +291 -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 +672 -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 +229 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaResponseCheck.swift +122 -0
- 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/MediaResponseCheckTests.swift +128 -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 +418 -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 +441 -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 +206 -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 +1042 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +982 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +951 -0
- package/ios/TrackPlayer+Recovery.swift +213 -0
- package/ios/TrackPlayer+Skip.swift +465 -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 +272 -0
- package/ios/TrackPlayer+Window.swift +227 -0
- package/ios/TrackPlayer.swift +331 -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,175 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
|
|
4
|
+
extension TrackPlayer {
|
|
5
|
+
func setSleepTimer(seconds: Double) throws -> Promise<Void> {
|
|
6
|
+
return enqueue { self.setSleepTimerInternal(seconds: seconds) }
|
|
7
|
+
}
|
|
8
|
+
func setSleepTimerToTrackEnd() throws -> Promise<Void> {
|
|
9
|
+
return enqueue { self.armSleepTimerToTrackEndInternal() }
|
|
10
|
+
}
|
|
11
|
+
func clearSleepTimer() throws -> Promise<Void> {
|
|
12
|
+
return enqueue { self.clearSleepTimerInternal() }
|
|
13
|
+
}
|
|
14
|
+
func getSleepTimer() throws -> SleepTimerState {
|
|
15
|
+
return onPlayerQueueSync { self.sleepTimerSnapshot() }
|
|
16
|
+
}
|
|
17
|
+
func onSleepTimerChange(
|
|
18
|
+
callback: @escaping (_ state: SleepTimerState) -> Void
|
|
19
|
+
) throws -> () -> Void {
|
|
20
|
+
let id = sleepTimerListeners.add(callback)
|
|
21
|
+
playerQueue.async { [weak self] in
|
|
22
|
+
guard let self else { return }
|
|
23
|
+
guard self.sleepTimerListeners.contains(id: id) else { return }
|
|
24
|
+
callback(self.sleepTimerSnapshot())
|
|
25
|
+
}
|
|
26
|
+
return { [weak self] in _ = self?.sleepTimerListeners.remove(id: id) }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// MARK: - Sleep timer — internal
|
|
30
|
+
|
|
31
|
+
/// Arm a wall-clock duration timer; a non-positive value clears any timer.
|
|
32
|
+
private func setSleepTimerInternal(seconds: Double) {
|
|
33
|
+
guard seconds.isFinite, seconds > 0 else {
|
|
34
|
+
clearSleepTimerInternal()
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
restoreSleepTimerVolume()
|
|
38
|
+
sleepTimerCore.armDuration(seconds: seconds, nowMs: Self.nowEpochMs())
|
|
39
|
+
startSleepTimerTick()
|
|
40
|
+
emitSleepTimerChanged()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func armSleepTimerToTrackEndInternal() {
|
|
44
|
+
restoreSleepTimerVolume()
|
|
45
|
+
sleepTimerCore.armEndOfTrack()
|
|
46
|
+
startSleepTimerTick()
|
|
47
|
+
emitSleepTimerChanged()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func clearSleepTimerInternal() {
|
|
51
|
+
let wasActive = sleepTimerCore.isActive
|
|
52
|
+
restoreSleepTimerVolume()
|
|
53
|
+
sleepTimerCore.clear()
|
|
54
|
+
stopSleepTimerTick()
|
|
55
|
+
if wasActive { emitSleepTimerChanged() }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private func sleepTimerSnapshot() -> SleepTimerState {
|
|
59
|
+
let now = Self.nowEpochMs()
|
|
60
|
+
return SleepTimerState(
|
|
61
|
+
active: sleepTimerCore.isActive,
|
|
62
|
+
endsAtEpochMs: sleepTimerCore.deadlineEpochMs.map { Double($0) },
|
|
63
|
+
remainingSeconds: sleepTimerCore.remainingSeconds(nowMs: now).map { Double($0) },
|
|
64
|
+
endOfTrack: sleepTimerCore.isEndOfTrack)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func emitSleepTimerChanged() {
|
|
68
|
+
let snapshot = sleepTimerSnapshot()
|
|
69
|
+
sleepTimerListeners.forEach { $0(snapshot) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private func startSleepTimerTick() {
|
|
73
|
+
guard sleepTimerTimer == nil else { return }
|
|
74
|
+
let timer = DispatchSource.makeTimerSource(queue: playerQueue)
|
|
75
|
+
timer.schedule(
|
|
76
|
+
deadline: .now() + Self.sleepTimerTickInterval,
|
|
77
|
+
repeating: Self.sleepTimerTickInterval)
|
|
78
|
+
timer.setEventHandler { [weak self] in
|
|
79
|
+
self?.onSleepTimerTick()
|
|
80
|
+
}
|
|
81
|
+
sleepTimerTimer = timer
|
|
82
|
+
// The source is resumed exactly once and never suspended — a suspended
|
|
83
|
+
// source traps libdispatch on release.
|
|
84
|
+
timer.resume()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Caller must be on `playerQueue` — cancelling from the source's own
|
|
88
|
+
/// queue is what guarantees no further tick.
|
|
89
|
+
internal func stopSleepTimerTick() {
|
|
90
|
+
sleepTimerTimer?.cancel()
|
|
91
|
+
sleepTimerTimer = nil
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func onSleepTimerTick() {
|
|
95
|
+
// While casting, the local engine is intentionally frozen (its position
|
|
96
|
+
// never advances), so feed the state machine the receiver's position /
|
|
97
|
+
// duration instead — otherwise the end-of-track tail rule never trips and
|
|
98
|
+
// `.endOfTrack` stays idle forever.
|
|
99
|
+
let durationSec: Double
|
|
100
|
+
let positionSec: Double
|
|
101
|
+
if let remote = PlaybackStateRouter.shared.activeRemote() {
|
|
102
|
+
let d = Double(remote.lastDurationMs) / 1000.0
|
|
103
|
+
let p = Double(remote.lastPositionMs) / 1000.0
|
|
104
|
+
durationSec = (d.isFinite && d > 0) ? d : 0
|
|
105
|
+
positionSec = (p.isFinite && p > 0) ? p : 0
|
|
106
|
+
} else {
|
|
107
|
+
let duration = self.engine?.currentDurationSeconds ?? 0
|
|
108
|
+
let position = self.engine?.currentPositionSeconds ?? 0
|
|
109
|
+
durationSec = (duration.isFinite && duration > 0) ? duration : 0
|
|
110
|
+
positionSec = (position.isFinite && position > 0) ? position : 0
|
|
111
|
+
}
|
|
112
|
+
let result = sleepTimerCore.tick(
|
|
113
|
+
nowMs: Self.nowEpochMs(),
|
|
114
|
+
trackDurationSec: durationSec,
|
|
115
|
+
trackPositionSec: positionSec)
|
|
116
|
+
applySleepTimerResult(result)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Apply a `SleepTimerCore.Tick` decision to the engine — fade, state emit,
|
|
120
|
+
/// and the boundary pause. Shared by the polling tick and the real track-end
|
|
121
|
+
/// signal (`handlePlayerItemDidPlayToEndTime`).
|
|
122
|
+
internal func applySleepTimerResult(_ result: SleepTimerCore.Tick) {
|
|
123
|
+
applySleepTimerFade(result.fadeFraction)
|
|
124
|
+
if result.stateChanged { emitSleepTimerChanged() }
|
|
125
|
+
if result.pauseNow {
|
|
126
|
+
// Route the boundary pause to the receiver when casting (the receiver
|
|
127
|
+
// emits its own paused state); only the local-engine pause needs the
|
|
128
|
+
// sleep-timer reason tag.
|
|
129
|
+
if !CastTransportRouter.routePause() {
|
|
130
|
+
self.pendingStateChangeReason = .sleepTimer
|
|
131
|
+
// The timer's pause is a transport intent like the user's: without
|
|
132
|
+
// clearing these, a drain that follows (the timer firing at a track
|
|
133
|
+
// end on the last installed item) or a buffer refill would restart
|
|
134
|
+
// playback the timer just stopped.
|
|
135
|
+
self.wantsToPlay = false
|
|
136
|
+
self.isRecoveringFromStall = false
|
|
137
|
+
self.engine?.pause()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if !sleepTimerCore.isActive { stopSleepTimerTick() }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Fade multiplies off `volumeState` without mutating it, so `getVolume`
|
|
144
|
+
/// stays truthful and the pre-fade level is restored on clear/fire.
|
|
145
|
+
private func applySleepTimerFade(_ fraction: Double) {
|
|
146
|
+
// Fade is local-only: the receiver's volume is user/system-owned and
|
|
147
|
+
// `volumeState` tracks the local level, so ramping it on the receiver would
|
|
148
|
+
// clobber the cast volume. When casting the boundary pause still routes to
|
|
149
|
+
// the receiver; the pre-pause fade is skipped. Undo any in-progress local
|
|
150
|
+
// fade first so a cast session appearing mid-fade can't strand the local
|
|
151
|
+
// engine at a ramped-down volume for a later cast→local return.
|
|
152
|
+
if PlaybackStateRouter.shared.activeRemote() != nil {
|
|
153
|
+
restoreSleepTimerVolume()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
if fraction < 1.0 {
|
|
157
|
+
self.engine?.setVolume(Float(Double(self.volumeState) * fraction))
|
|
158
|
+
sleepTimerFading = true
|
|
159
|
+
} else if sleepTimerFading {
|
|
160
|
+
self.engine?.setVolume(self.volumeState)
|
|
161
|
+
sleepTimerFading = false
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private func restoreSleepTimerVolume() {
|
|
166
|
+
if sleepTimerFading {
|
|
167
|
+
self.engine?.setVolume(self.volumeState)
|
|
168
|
+
sleepTimerFading = false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private static func nowEpochMs() -> Int64 {
|
|
173
|
+
Int64(Date().timeIntervalSince1970 * 1000)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
extension TrackPlayer {
|
|
4
|
+
// MARK: - State snapshots
|
|
5
|
+
//
|
|
6
|
+
// Nitro calls these getters on a JS worker thread. Player state lives
|
|
7
|
+
// on `playerQueue`, so every read hops there with `onPlayerQueueSync`,
|
|
8
|
+
// which runs the body inline when the caller is already on that queue
|
|
9
|
+
// (a `sync` from the queue onto itself would deadlock). The cached
|
|
10
|
+
// `canSkip*` Bools are written on `playerQueue` by
|
|
11
|
+
// `recomputeCapabilities()`, so the same hop reads them consistently.
|
|
12
|
+
func getState() throws -> PlayerState {
|
|
13
|
+
// The receiver owns playback during a cast session — the local engine is
|
|
14
|
+
// parked and paused for its duration, so its derived state describes
|
|
15
|
+
// nothing the user can hear. Until the receiver has reported (session
|
|
16
|
+
// still activating, holding its initial `.idle`) the local read is the
|
|
17
|
+
// better answer; after that the receiver is authoritative, including a
|
|
18
|
+
// later `.idle`, which means it genuinely has nothing loaded.
|
|
19
|
+
if let remote = PlaybackStateRouter.shared.activeRemote(), remote.hasReportedState {
|
|
20
|
+
return remote.lastState.asPlayerState
|
|
21
|
+
}
|
|
22
|
+
return onPlayerQueueSync {
|
|
23
|
+
// Both engines derive PlayerState from the same inputs via
|
|
24
|
+
// `engine.timeControlStatus` (gapless returns its AVQueuePlayer's, which
|
|
25
|
+
// is what `self.player` also points at; crossfade returns the active
|
|
26
|
+
// leg's), so gapless + crossfade report identical states. `.none` when no
|
|
27
|
+
// engine is configured.
|
|
28
|
+
guard let engine = self.engine else { return .none }
|
|
29
|
+
return PlayerStateDerivation.translate(
|
|
30
|
+
status: engine.timeControlStatus,
|
|
31
|
+
currentIndex: self.currentTrackIndex,
|
|
32
|
+
hasCurrentItem: engine.currentMediaItem != nil,
|
|
33
|
+
reachedQueueEnd: self.reachedQueueEnd
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
func getCurrentTrackIndex() throws -> Double {
|
|
38
|
+
// Nitrogen spec types this return as Double; bridge the internal Int.
|
|
39
|
+
return onPlayerQueueSync { Double(self.currentTrackIndex) }
|
|
40
|
+
}
|
|
41
|
+
func getCurrentTrackSource() throws -> TrackSource? {
|
|
42
|
+
return onPlayerQueueSync { self.currentTrackSource }
|
|
43
|
+
}
|
|
44
|
+
func getCanSkipNext() throws -> Bool {
|
|
45
|
+
return onPlayerQueueSync { self.cachedSkipCapability.canSkipNext }
|
|
46
|
+
}
|
|
47
|
+
func getSkipCapability() throws -> SkipCapability {
|
|
48
|
+
return onPlayerQueueSync { self.cachedSkipCapability }
|
|
49
|
+
}
|
|
50
|
+
func getCanSkipPrevious() throws -> Bool {
|
|
51
|
+
return onPlayerQueueSync { self.cachedSkipCapability.canSkipPrevious }
|
|
52
|
+
}
|
|
53
|
+
func getPlaybackSpeed() throws -> Double {
|
|
54
|
+
return onPlayerQueueSync { Double(self.playbackSpeedState) }
|
|
55
|
+
}
|
|
56
|
+
func getVolume() throws -> Double {
|
|
57
|
+
return onPlayerQueueSync { Double(self.volumeState) }
|
|
58
|
+
}
|
|
59
|
+
func getRemoteControls() throws -> RemoteControlOptions {
|
|
60
|
+
return onPlayerQueueSync {
|
|
61
|
+
let options: RemoteControlOptions = .init(
|
|
62
|
+
skipMode: self.remoteSkipMode,
|
|
63
|
+
forwardJumpInterval: self.remoteForwardInterval,
|
|
64
|
+
backwardJumpInterval: self.remoteBackwardInterval)
|
|
65
|
+
return options
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func getBufferState() throws -> BufferState {
|
|
70
|
+
return onPlayerQueueSync { self.currentBufferState }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func getIsSeekable() throws -> Bool {
|
|
74
|
+
return onPlayerQueueSync {
|
|
75
|
+
guard let item = self.engine?.currentMediaItem else { return true }
|
|
76
|
+
return !item.seekableTimeRanges.isEmpty
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func isFullyBuffered() throws -> Bool {
|
|
81
|
+
return onPlayerQueueSync { self.currentFullyBuffered }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func onBufferStateChange(
|
|
85
|
+
callback: @escaping (_ state: BufferState) -> Void
|
|
86
|
+
) throws -> () -> Void {
|
|
87
|
+
let id = bufferStateListeners.add(callback)
|
|
88
|
+
// Fire once on subscribe with the current state so a UI renders immediately.
|
|
89
|
+
playerQueue.async { [weak self] in
|
|
90
|
+
guard let self else { return }
|
|
91
|
+
guard self.bufferStateListeners.contains(id: id) else { return }
|
|
92
|
+
callback(self.currentBufferState)
|
|
93
|
+
}
|
|
94
|
+
return { [weak self] in self?.bufferStateListeners.remove(id: id) }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func onFullyBufferedChange(
|
|
98
|
+
callback: @escaping (_ fullyBuffered: Bool) -> Void
|
|
99
|
+
) throws -> () -> Void {
|
|
100
|
+
let id = fullyBufferedListeners.add(callback)
|
|
101
|
+
playerQueue.async { [weak self] in
|
|
102
|
+
guard let self else { return }
|
|
103
|
+
guard self.fullyBufferedListeners.contains(id: id) else { return }
|
|
104
|
+
callback(self.currentFullyBuffered)
|
|
105
|
+
}
|
|
106
|
+
return { [weak self] in self?.fullyBufferedListeners.remove(id: id) }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
|
|
4
|
+
extension TrackPlayer {
|
|
5
|
+
/// True when the caller is already executing on `playerQueue`.
|
|
6
|
+
///
|
|
7
|
+
/// `DispatchQueue.sync` runs its body on the calling thread, so a thread
|
|
8
|
+
/// identity test answers the wrong question — it reports "main" for work
|
|
9
|
+
/// serialised on the queue and would let a nested `sync` deadlock. The
|
|
10
|
+
/// queue's own specific travels with the execution context instead.
|
|
11
|
+
internal var isOnPlayerQueue: Bool {
|
|
12
|
+
DispatchQueue.getSpecific(key: playerQueueKey) == ObjectIdentifier(self)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Claim the audio session, then start the engine.
|
|
16
|
+
///
|
|
17
|
+
/// Every path that starts playback goes through here so activation cannot be
|
|
18
|
+
/// forgotten at a new one. `activateIfNeeded` is idempotent, so the cost
|
|
19
|
+
/// after the first play is a bool check.
|
|
20
|
+
///
|
|
21
|
+
/// Activation is deliberately not done at `configure()`: that would stop
|
|
22
|
+
/// whatever else the user is listening to the moment a consumer app
|
|
23
|
+
/// launches, before they have pressed anything.
|
|
24
|
+
internal func beginPlayback() {
|
|
25
|
+
audioSession.activateIfNeeded()
|
|
26
|
+
engine?.play()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Enqueue `body` on `playerQueue` **at call time**, on the calling thread,
|
|
30
|
+
/// and resolve the returned promise when it completes.
|
|
31
|
+
///
|
|
32
|
+
/// This is what makes the queue's FIFO order equal the JS call order.
|
|
33
|
+
/// `Promise.async` starts an unstructured `Task`, and two Tasks created back
|
|
34
|
+
/// to back have no ordering relationship with each other — so a body that
|
|
35
|
+
/// hops onto the queue *inside* the Task reaches the player at an arbitrary
|
|
36
|
+
/// later point. Two calls issued in order from JS could then arrive
|
|
37
|
+
/// reversed: `play()` then `pause()` landing as pause-then-play, leaving
|
|
38
|
+
/// audio running after the user paused it.
|
|
39
|
+
///
|
|
40
|
+
/// A serial queue gives mutual exclusion, not ordering. Ordering comes from
|
|
41
|
+
/// when the work is enqueued, which is why this runs before returning.
|
|
42
|
+
internal func enqueue(_ body: @escaping () -> Void) -> Promise<Void> {
|
|
43
|
+
let promise = Promise<Void>()
|
|
44
|
+
playerQueue.async {
|
|
45
|
+
body()
|
|
46
|
+
promise.resolve()
|
|
47
|
+
}
|
|
48
|
+
return promise
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Enqueue a mutation at call time and hand its result to a receiver mirror
|
|
52
|
+
/// that runs off the queue.
|
|
53
|
+
///
|
|
54
|
+
/// The enqueue is what fixes ordering against other player work — a later
|
|
55
|
+
/// call cannot overtake this one. Doing it with `async` rather than `sync`
|
|
56
|
+
/// matters just as much: the mutation can rebuild the whole player queue,
|
|
57
|
+
/// and `sync` would run that on the JS thread.
|
|
58
|
+
///
|
|
59
|
+
/// `mirror` awaits the receiver afterwards, holding neither the queue nor
|
|
60
|
+
/// the calling thread, and is chained behind the previous mutation's mirror
|
|
61
|
+
/// so the receiver sees mutations in the order they were made. The promise
|
|
62
|
+
/// resolves once the mirror returns, so a consumer awaiting the call knows
|
|
63
|
+
/// local and remote are both settled.
|
|
64
|
+
internal func enqueueThenMirror<T>(
|
|
65
|
+
_ body: @escaping () -> T,
|
|
66
|
+
mirror: @escaping (T, _ generation: UInt64) async -> Void
|
|
67
|
+
) -> Promise<Void> {
|
|
68
|
+
let promise = Promise<Void>()
|
|
69
|
+
playerQueue.async {
|
|
70
|
+
self.queueMutationGeneration &+= 1
|
|
71
|
+
let generation = self.queueMutationGeneration
|
|
72
|
+
let value = body()
|
|
73
|
+
let previous = self.castMirrorChain
|
|
74
|
+
self.castMirrorChain = Task {
|
|
75
|
+
// Deltas are only correct against a receiver that has applied
|
|
76
|
+
// everything before them, so this waits rather than racing. A failed
|
|
77
|
+
// mirror still completes its Task, so one bad round-trip cannot wedge
|
|
78
|
+
// the chain.
|
|
79
|
+
await previous?.value
|
|
80
|
+
await mirror(value, generation)
|
|
81
|
+
promise.resolve()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return promise
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Value-returning variant of `enqueue`.
|
|
88
|
+
internal func enqueueValue<T>(_ body: @escaping () -> T) -> Promise<T> {
|
|
89
|
+
let promise = Promise<T>()
|
|
90
|
+
playerQueue.async {
|
|
91
|
+
promise.resolve(withResult: body())
|
|
92
|
+
}
|
|
93
|
+
return promise
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Throwing variant of `enqueue` — a thrown error rejects the JS promise
|
|
97
|
+
/// with its message, as `Promise.async` does for a throwing body.
|
|
98
|
+
internal func enqueueThrowing(_ body: @escaping () throws -> Void) -> Promise<Void> {
|
|
99
|
+
let promise = Promise<Void>()
|
|
100
|
+
playerQueue.async {
|
|
101
|
+
do {
|
|
102
|
+
try body()
|
|
103
|
+
promise.resolve()
|
|
104
|
+
} catch {
|
|
105
|
+
promise.reject(withError: error)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return promise
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Serialise a mutation body on `playerQueue` and return a
|
|
112
|
+
/// Promise-compatible awaitable. Centralising here keeps every access to
|
|
113
|
+
/// player state on one queue, which is what makes the state safe to read
|
|
114
|
+
/// without locks inside these bodies.
|
|
115
|
+
internal func onPlayerQueue(_ body: @escaping () -> Void) async {
|
|
116
|
+
await withCheckedContinuation { (cont: CheckedContinuation<Void, Never>) in
|
|
117
|
+
playerQueue.async {
|
|
118
|
+
body()
|
|
119
|
+
cont.resume()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Run `body` on `playerQueue` synchronously and return its value. Used by
|
|
125
|
+
/// the Nitro getters and the listener register / dispose paths, which are
|
|
126
|
+
/// not Promise-wrapped and run on whatever thread the JS bridge uses.
|
|
127
|
+
///
|
|
128
|
+
/// Running inline when already on the queue is what makes re-entrant calls
|
|
129
|
+
/// safe: `playerQueue.sync` from a block already on `playerQueue` is an
|
|
130
|
+
/// immediate deadlock.
|
|
131
|
+
internal func onPlayerQueueSync<T>(_ body: () -> T) -> T {
|
|
132
|
+
if isOnPlayerQueue { return body() }
|
|
133
|
+
return playerQueue.sync(execute: body)
|
|
134
|
+
}
|
|
135
|
+
}
|