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,258 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
|
|
3
|
+
extension TrackPlayer {
|
|
4
|
+
/// Returns the active item's runtime audio format, or the null variant
|
|
5
|
+
/// when nothing is currently playable / the format hasn't loaded yet.
|
|
6
|
+
/// `Variant_NullType_NowPlayingFormat` is Nitrogen's bridge for
|
|
7
|
+
/// `NowPlayingFormat | null` (`.first(.null)` = absent, `.second(format)` =
|
|
8
|
+
/// present). Cached by the async resolver; never blocks.
|
|
9
|
+
///
|
|
10
|
+
/// An empty / out-of-range queue short-circuits to `.null` regardless
|
|
11
|
+
/// of the cache — the canonical-state branch below.
|
|
12
|
+
func getNowPlayingFormat() throws -> Variant_NullType_NowPlayingFormat {
|
|
13
|
+
return onPlayerQueueSync {
|
|
14
|
+
// Lib canonical state takes precedence over the cached format.
|
|
15
|
+
// After `clearQueue` / queue-end the engine's `currentMediaItem`
|
|
16
|
+
// can briefly retain the last item; without this guard the read
|
|
17
|
+
// returns the prior track's stale codec / bitrate / duration
|
|
18
|
+
// even though `currentTrackIndex == -1` and the JS-facing state
|
|
19
|
+
// shows track == null.
|
|
20
|
+
if self.tracks.isEmpty
|
|
21
|
+
|| self.currentTrackIndex < 0
|
|
22
|
+
|| self.currentTrackIndex >= self.tracks.count {
|
|
23
|
+
return .first(.null)
|
|
24
|
+
}
|
|
25
|
+
// RG fields are read live from `AudioTapProvider`'s per-item
|
|
26
|
+
// cache so they surface as soon as the item's own metadata read
|
|
27
|
+
// completes — independently of the slow-path AVFoundation
|
|
28
|
+
// format-description loads that populate `lastEmittedFormat`'s
|
|
29
|
+
// codec/bitrate/duration fields.
|
|
30
|
+
let activeItem = self.activeMediaItem
|
|
31
|
+
let rgData = activeItem.flatMap { AudioTapProvider.shared.replayGainDataForItem($0) }
|
|
32
|
+
let rgActive = activeItem.map { AudioTapProvider.shared.replayGainActiveForItem($0) } ?? false
|
|
33
|
+
if let cached = self.lastEmittedFormat {
|
|
34
|
+
return .second(mergeRG(cached, rgData: rgData, rgActive: rgActive))
|
|
35
|
+
}
|
|
36
|
+
// No slow-path snapshot yet. Surface RG fields alone so
|
|
37
|
+
// consumers waiting on RG (e.g. visualisation, normalisation
|
|
38
|
+
// UI) don't have to wait for the AVAsset chain to complete.
|
|
39
|
+
// Non-RG fields fall through to their `.null` sentinels.
|
|
40
|
+
if rgData != nil {
|
|
41
|
+
return .second(NowPlayingFormat(
|
|
42
|
+
codec: .unknown,
|
|
43
|
+
container: .unknown,
|
|
44
|
+
mimeType: .first(.null),
|
|
45
|
+
sampleRate: .first(.null),
|
|
46
|
+
channelCount: .first(.null),
|
|
47
|
+
bitrate: .first(.null),
|
|
48
|
+
durationSec: .first(.null),
|
|
49
|
+
replayGainTrackDb: rgData?.trackGainDb.map { .second($0) } ?? .first(.null),
|
|
50
|
+
replayGainTrackPeak: rgData?.trackPeak.map { .second($0) } ?? .first(.null),
|
|
51
|
+
replayGainAlbumDb: rgData?.albumGainDb.map { .second($0) } ?? .first(.null),
|
|
52
|
+
replayGainAlbumPeak: rgData?.albumPeak.map { .second($0) } ?? .first(.null),
|
|
53
|
+
replayGainActive: rgActive,
|
|
54
|
+
ios: NowPlayingFormatIosExtras(bitsPerChannel: .first(.null)),
|
|
55
|
+
android: nil
|
|
56
|
+
))
|
|
57
|
+
}
|
|
58
|
+
return .first(.null)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Replace the four RG fields of `cached` with live values from
|
|
63
|
+
/// the AudioTap. Non-RG fields are preserved as-is.
|
|
64
|
+
private func mergeRG(
|
|
65
|
+
_ cached: NowPlayingFormat,
|
|
66
|
+
rgData: ReplayGainData?,
|
|
67
|
+
rgActive: Bool
|
|
68
|
+
) -> NowPlayingFormat {
|
|
69
|
+
return NowPlayingFormat(
|
|
70
|
+
codec: cached.codec,
|
|
71
|
+
container: cached.container,
|
|
72
|
+
mimeType: cached.mimeType,
|
|
73
|
+
sampleRate: cached.sampleRate,
|
|
74
|
+
channelCount: cached.channelCount,
|
|
75
|
+
bitrate: cached.bitrate,
|
|
76
|
+
durationSec: cached.durationSec,
|
|
77
|
+
replayGainTrackDb: rgData?.trackGainDb.map { .second($0) } ?? .first(.null),
|
|
78
|
+
replayGainTrackPeak: rgData?.trackPeak.map { .second($0) } ?? .first(.null),
|
|
79
|
+
replayGainAlbumDb: rgData?.albumGainDb.map { .second($0) } ?? .first(.null),
|
|
80
|
+
replayGainAlbumPeak: rgData?.albumPeak.map { .second($0) } ?? .first(.null),
|
|
81
|
+
replayGainActive: rgActive,
|
|
82
|
+
ios: cached.ios,
|
|
83
|
+
android: cached.android
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Emit the current now-playing format to subscribers. Used by
|
|
88
|
+
/// the AudioTap RG-data-populated callback to push the updated
|
|
89
|
+
/// format without re-running `NowPlayingFormatExtractor`.
|
|
90
|
+
internal func emitCurrentNowPlayingFormat(for item: AVPlayerItem) {
|
|
91
|
+
guard let active = self.activeMediaItem,
|
|
92
|
+
ObjectIdentifier(active) == ObjectIdentifier(item) else { return }
|
|
93
|
+
let rgData = AudioTapProvider.shared.replayGainDataForItem(item)
|
|
94
|
+
let rgActive = AudioTapProvider.shared.replayGainActiveForItem(item)
|
|
95
|
+
if let cached = self.lastEmittedFormat {
|
|
96
|
+
let merged = mergeRG(cached, rgData: rgData, rgActive: rgActive)
|
|
97
|
+
self.lastEmittedFormat = merged
|
|
98
|
+
self.nowPlayingFormatListeners.forEach { $0(.second(merged)) }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func onNowPlayingFormatChange(
|
|
103
|
+
callback: @escaping (_ format: Variant_NullType_NowPlayingFormat?) -> Void
|
|
104
|
+
) throws -> () -> Void {
|
|
105
|
+
let id = nowPlayingFormatListeners.add(callback)
|
|
106
|
+
return { [weak self] in self?.nowPlayingFormatListeners.remove(id: id) }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Engine-agnostic active-item accessor. Reads via the active
|
|
110
|
+
/// engine's `currentMediaItem` getter (works for GaplessEngine
|
|
111
|
+
/// AND CrossfadeEngine — `self.player` is nil during crossfade-
|
|
112
|
+
/// mode playback because that engine routes through dual private
|
|
113
|
+
/// AVPlayers). Falls back to `self.player?.currentItem` for paths
|
|
114
|
+
/// that pre-date the engine surface.
|
|
115
|
+
internal var activeMediaItem: AVPlayerItem? {
|
|
116
|
+
return self.engine?.currentMediaItem ?? self.player?.currentItem
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Bump the request generation, drop a stale cache (emitting null so
|
|
120
|
+
/// the JS consumer sees a loading state), and kick off the async
|
|
121
|
+
/// extractor for the current AVPlayerItem. When there is no current
|
|
122
|
+
/// item, clears + emits null and returns without launching a Task.
|
|
123
|
+
internal func refreshNowPlayingFormatForActiveItem() {
|
|
124
|
+
self.nowPlayingFormatRequestGen &+= 1
|
|
125
|
+
let gen = self.nowPlayingFormatRequestGen
|
|
126
|
+
|
|
127
|
+
guard let item = self.activeMediaItem else {
|
|
128
|
+
if self.lastEmittedFormat != nil || self.lastEmittedFormatItemId != nil {
|
|
129
|
+
self.lastEmittedFormat = nil
|
|
130
|
+
self.lastEmittedFormatItemId = nil
|
|
131
|
+
nowPlayingFormatListeners.forEach { $0(.first(.null)) }
|
|
132
|
+
}
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
let itemId = ObjectIdentifier(item)
|
|
136
|
+
|
|
137
|
+
// Same item already resolved — no work, no emit. The two same-item
|
|
138
|
+
// re-emits do not pass through this check: `emitCurrentNowPlayingFormat(for:)`
|
|
139
|
+
// merges fresh replay-gain fields into the cached snapshot without
|
|
140
|
+
// running the extractor; `reresolveNowPlayingFormatForBitrate` re-runs
|
|
141
|
+
// the extractor and emits only when the bitrate changed.
|
|
142
|
+
if self.lastEmittedFormatItemId == itemId, self.lastEmittedFormat != nil {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// New (or different) item — drop stale cache + emit null so the
|
|
147
|
+
// JS consumer can show a loading state before the new format
|
|
148
|
+
// lands. Its bitrate re-resolve budget starts fresh here as well as on
|
|
149
|
+
// the identity check below, since an item allocated at a released
|
|
150
|
+
// item's address would otherwise inherit a spent one.
|
|
151
|
+
self.bitrateReresolveItemId = itemId
|
|
152
|
+
self.bitrateReresolveAttempts = 0
|
|
153
|
+
if self.lastEmittedFormat != nil {
|
|
154
|
+
self.lastEmittedFormat = nil
|
|
155
|
+
self.lastEmittedFormatItemId = nil
|
|
156
|
+
nowPlayingFormatListeners.forEach { $0(.first(.null)) }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Task { [weak self] in
|
|
160
|
+
let resolved = await NowPlayingFormatExtractor.extract(item: item)
|
|
161
|
+
// The generation token, the active item and the last-emitted format are
|
|
162
|
+
// all player state, so the completion settles on the queue that owns
|
|
163
|
+
// them — otherwise the stale-resolve guard compares a value written
|
|
164
|
+
// from another queue.
|
|
165
|
+
await self?.onPlayerQueue { [weak self] in
|
|
166
|
+
guard let self else { return }
|
|
167
|
+
guard self.nowPlayingFormatRequestGen == gen else { return }
|
|
168
|
+
// Drops fires from items the active engine has already
|
|
169
|
+
// advanced past during the await.
|
|
170
|
+
guard let current = self.activeMediaItem,
|
|
171
|
+
ObjectIdentifier(current) == itemId else { return }
|
|
172
|
+
guard let format = resolved else { return }
|
|
173
|
+
self.lastEmittedFormat = format
|
|
174
|
+
self.lastEmittedFormatItemId = itemId
|
|
175
|
+
self.nowPlayingFormatListeners.forEach { $0(.second(format)) }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Compare two `Variant_NullType_Double?` values for "is the bitrate
|
|
181
|
+
/// effectively the same". Treats `nil` and `.first(.null)` as
|
|
182
|
+
/// equivalent (both mean "no bitrate"); `.second(Double)` matches
|
|
183
|
+
/// only on IEEE-754 equality (Swift's `Double ==`). Value equality is
|
|
184
|
+
/// intentional — the extractor produces values from a small set of
|
|
185
|
+
/// integer-source feeds (Core Audio bitrate is `UInt32`, access-log
|
|
186
|
+
/// bitrate is a server-declared kbps figure) so equality holds across
|
|
187
|
+
/// repeated reads of the same underlying signal.
|
|
188
|
+
private func sameBitrateVariant(
|
|
189
|
+
_ a: Variant_NullType_Double?, _ b: Variant_NullType_Double?
|
|
190
|
+
) -> Bool {
|
|
191
|
+
let aValue: Double? = {
|
|
192
|
+
guard let a = a, case .second(let v) = a else { return nil }
|
|
193
|
+
return v
|
|
194
|
+
}()
|
|
195
|
+
let bValue: Double? = {
|
|
196
|
+
guard let b = b, case .second(let v) = b else { return nil }
|
|
197
|
+
return v
|
|
198
|
+
}()
|
|
199
|
+
return aValue == bValue
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Re-resolve the active item's now-playing format to fill a missing
|
|
203
|
+
/// bitrate. The base `refreshNowPlayingFormatForActiveItem` returns early
|
|
204
|
+
/// for an item it has already emitted a format for; this entry point runs
|
|
205
|
+
/// the extractor again and emits only when the resolved bitrate differs
|
|
206
|
+
/// from the emitted one. Called from the access-log notification handler
|
|
207
|
+
/// while the emitted format has no bitrate, whatever the codec.
|
|
208
|
+
///
|
|
209
|
+
/// `bitrateReresolveInFlight` early-returns when a previous call is
|
|
210
|
+
/// still resolving — access-log notifications fire many times per
|
|
211
|
+
/// second so without this gate every fire while the first extract is
|
|
212
|
+
/// running would spawn another detached Task — and each item gets at most
|
|
213
|
+
/// `maxBitrateReresolvesPerItem` runs, since a stream whose server never
|
|
214
|
+
/// declares a bitrate would otherwise be re-read for the life of the
|
|
215
|
+
/// track. The active-item gate + generation token + `lastEmittedFormatItemId`
|
|
216
|
+
/// check still apply on the completion side.
|
|
217
|
+
internal func reresolveNowPlayingFormatForBitrate() {
|
|
218
|
+
if self.bitrateReresolveInFlight { return }
|
|
219
|
+
guard let item = self.activeMediaItem else { return }
|
|
220
|
+
let itemId = ObjectIdentifier(item)
|
|
221
|
+
if self.bitrateReresolveItemId != itemId {
|
|
222
|
+
self.bitrateReresolveItemId = itemId
|
|
223
|
+
self.bitrateReresolveAttempts = 0
|
|
224
|
+
}
|
|
225
|
+
guard self.bitrateReresolveAttempts < Self.maxBitrateReresolvesPerItem else { return }
|
|
226
|
+
self.bitrateReresolveAttempts += 1
|
|
227
|
+
self.nowPlayingFormatRequestGen &+= 1
|
|
228
|
+
let gen = self.nowPlayingFormatRequestGen
|
|
229
|
+
self.bitrateReresolveInFlight = true
|
|
230
|
+
|
|
231
|
+
Task { [weak self] in
|
|
232
|
+
let resolved = await NowPlayingFormatExtractor.extract(item: item)
|
|
233
|
+
// The generation token, the active item and the last-emitted format are
|
|
234
|
+
// all player state, so the completion settles on the queue that owns
|
|
235
|
+
// them — otherwise the stale-resolve guard compares a value written
|
|
236
|
+
// from another queue.
|
|
237
|
+
await self?.onPlayerQueue { [weak self] in
|
|
238
|
+
guard let self else { return }
|
|
239
|
+
defer { self.bitrateReresolveInFlight = false }
|
|
240
|
+
guard self.nowPlayingFormatRequestGen == gen else { return }
|
|
241
|
+
guard let current = self.activeMediaItem,
|
|
242
|
+
ObjectIdentifier(current) == itemId else { return }
|
|
243
|
+
guard self.lastEmittedFormatItemId == itemId else { return }
|
|
244
|
+
guard let format = resolved else { return }
|
|
245
|
+
// Emit only on a meaningful change. Bitrate is the field this
|
|
246
|
+
// path exists to fill; comparing the variant tag handles both
|
|
247
|
+
// null→value transitions and value→value updates without
|
|
248
|
+
// pulling the wrapped Double into Equatable territory.
|
|
249
|
+
let prev = self.lastEmittedFormat
|
|
250
|
+
let bitrateChanged = !sameBitrateVariant(prev?.bitrate, format.bitrate)
|
|
251
|
+
if !bitrateChanged { return }
|
|
252
|
+
self.lastEmittedFormat = format
|
|
253
|
+
self.lastEmittedFormatItemId = itemId
|
|
254
|
+
self.nowPlayingFormatListeners.forEach { $0(.second(format)) }
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|