react-native-queue-player 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/QueuePlayer.podspec +14 -9
- package/README.md +3 -3
- package/android/build.gradle +7 -0
- package/android/consumer-rules.pro +72 -3
- package/android/src/main/cpp/airplay2_control.c +4 -4
- package/android/src/main/cpp/airplay2_control.h +4 -4
- package/android/src/main/cpp/airplay2_jni.cpp +26 -22
- package/android/src/main/cpp/airplay2_pair.c +7 -7
- package/android/src/main/cpp/airplay2_pair.h +2 -2
- package/android/src/main/cpp/airplay2_rtsp.c +8 -8
- package/android/src/main/cpp/airplay2_session.c +2 -2
- package/android/src/main/cpp/airplay_jni.cpp +20 -72
- package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
- package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
- package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
- package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
- package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
- package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
- package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
- package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +870 -278
- package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastEventBridge.kt +1 -10
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastTransportRouter.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/RemotePlaybackStateMapping.kt +18 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +23 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +58 -21
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlaySession.kt +26 -14
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MetadataSyncTarget.kt +20 -0
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastSession.kt +73 -13
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
- package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +23 -12
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
- package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +43 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
- package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
- package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +8 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +68 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +163 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +123 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
- package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
- package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +27 -11
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSyncTest.kt +303 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
- package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
- package/app.plugin.js +17 -1
- package/ios/AVPlayerItemQueueItemId.swift +26 -12
- package/ios/AVQueueBuilder.swift +120 -85
- package/ios/ArtworkLoader.swift +0 -7
- package/ios/ArtworkResolver.swift +22 -4
- package/ios/AssetReplayGainReader.swift +118 -0
- package/ios/AudioSession.swift +67 -16
- package/ios/AudioTapProvider.swift +284 -149
- package/ios/CarPlayBridge.swift +0 -12
- package/ios/CarPlayCoordinator.swift +18 -2
- package/ios/CarPlaySceneDelegate.swift +2 -2
- package/ios/Cast/AirPlayRouteState.swift +9 -8
- package/ios/Cast/Chromecast/ChromecastSession.swift +90 -11
- package/ios/Cast/Core/CastEventBridge.swift +66 -26
- package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
- package/ios/Cast/Core/CastSession.swift +11 -0
- package/ios/Cast/Core/CastTransportRouter.swift +21 -9
- package/ios/Cast/Core/LocalMediaServer.swift +41 -52
- package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
- package/ios/Cast/Core/MediaServerHandle.swift +0 -4
- package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
- package/ios/CastManager.swift +1 -1
- package/ios/CrossfadeEngine.swift +660 -226
- package/ios/EQTap.swift +20 -17
- package/ios/Equalizer.swift +1 -1
- package/ios/FLACStreamInfo.swift +65 -0
- package/ios/GaplessEngine.swift +57 -30
- package/ios/InputGuards.swift +35 -5
- package/ios/LookaheadCache.swift +202 -58
- package/ios/LookaheadCachePrefetcher.swift +48 -36
- package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
- package/ios/NetworkRecoveryPolicy.swift +32 -0
- package/ios/NowPlayingFormatExtractor.swift +25 -18
- package/ios/NowPlayingInfo.swift +84 -48
- package/ios/OutputRouteMonitor.swift +16 -3
- package/ios/PendingBrowseRequests.swift +1 -1
- package/ios/PlaceholderArtwork.swift +9 -5
- package/ios/PlaybackEngine.swift +165 -76
- package/ios/PlaybackErrorMapping.swift +3 -3
- package/ios/PlaybackModeStateMachine.swift +2 -2
- package/ios/PlaybackNetworkMonitor.swift +73 -0
- package/ios/PlayerStateDerivation.swift +6 -6
- package/ios/QueueMutationArithmetic.swift +25 -0
- package/ios/QueueSkipArithmetic.swift +5 -3
- package/ios/QueueWindowArithmetic.swift +152 -0
- package/ios/ReadThroughServer.swift +283 -0
- package/ios/RemoteCommands.swift +44 -4
- package/ios/ReplayGainData.swift +22 -3
- package/ios/ReplayGainExtractor.swift +70 -27
- package/ios/Siri/VoiceDonation.swift +14 -9
- package/ios/StreamingBitrateProbe.swift +22 -68
- package/ios/SuppliedReplayGain.swift +83 -0
- package/ios/Tests/AVQueueBuilderTests.swift +158 -221
- package/ios/Tests/ActiveItemEngineStub.swift +44 -0
- package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
- package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
- package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
- package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
- package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
- package/ios/Tests/CastNowPlayingControllerTests.swift +5 -0
- package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
- package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
- package/ios/Tests/CrossfadeEngineTests.swift +164 -0
- package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
- package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
- package/ios/Tests/EngineSwapTests.swift +91 -0
- package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
- package/ios/Tests/EqualizerHybridTests.swift +5 -2
- package/ios/Tests/FLACStreamInfoTests.swift +88 -0
- package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
- package/ios/Tests/InputGuardsTests.swift +41 -0
- package/ios/Tests/InterruptionIntentTests.swift +77 -0
- package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
- package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
- package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
- package/ios/Tests/LookaheadCacheTests.swift +44 -18
- package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
- package/ios/Tests/MutationDeferralTests.swift +220 -0
- package/ios/Tests/NowPlayingInfoTests.swift +86 -99
- package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
- package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
- package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
- package/ios/Tests/PlaybackStateRouterTests.swift +1 -0
- package/ios/Tests/PlayerFixtures.swift +48 -0
- package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
- package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
- package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
- package/ios/Tests/QueueStateTests.swift +3 -1
- package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
- package/ios/Tests/QueueWindowSliceTests.swift +162 -0
- package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
- package/ios/Tests/ReadThroughServerTests.swift +345 -0
- package/ios/Tests/RemoteCommandsTests.swift +28 -0
- package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
- package/ios/Tests/ReplayGainMergeTests.swift +230 -0
- package/ios/Tests/RetryRecoveryTests.swift +303 -0
- package/ios/Tests/SkipCapabilityTests.swift +233 -8
- package/ios/Tests/SkipIndexTests.swift +32 -2
- package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
- package/ios/Tests/StallRecoveryTests.swift +97 -0
- package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
- package/ios/Tests/TopUpWindowGateTests.swift +394 -0
- package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
- package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
- package/ios/Tests/TrackPlayerCastStateTests.swift +78 -0
- package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
- package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
- package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
- package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
- package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
- package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
- package/ios/TrackPlayer+Automotive.swift +101 -0
- package/ios/TrackPlayer+Cache.swift +232 -0
- package/ios/TrackPlayer+Config.swift +291 -0
- package/ios/TrackPlayer+EngineDelegate.swift +209 -0
- package/ios/TrackPlayer+EventsAPI.swift +111 -0
- package/ios/TrackPlayer+EventsDispatch.swift +968 -0
- package/ios/TrackPlayer+EventsWiring.swift +173 -0
- package/ios/TrackPlayer+Lifecycle.swift +930 -0
- package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
- package/ios/TrackPlayer+Queue.swift +940 -0
- package/ios/TrackPlayer+Recovery.swift +196 -0
- package/ios/TrackPlayer+Skip.swift +455 -0
- package/ios/TrackPlayer+SleepTimer.swift +175 -0
- package/ios/TrackPlayer+State.swift +108 -0
- package/ios/TrackPlayer+Threading.swift +135 -0
- package/ios/TrackPlayer+Transport.swift +267 -0
- package/ios/TrackPlayer+Window.swift +195 -0
- package/ios/TrackPlayer.swift +305 -4610
- package/ios/Visualizer.swift +6 -5
- package/ios/tests-harness/Podfile +1 -1
- package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
- package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
- package/lib/module/hooks/useActiveTrack.js +29 -22
- package/lib/module/hooks/useActiveTrack.js.map +1 -1
- package/lib/module/hooks/useCast.js +8 -26
- package/lib/module/hooks/useCast.js.map +1 -1
- package/lib/module/hooks/useEqualizer.js +19 -12
- package/lib/module/hooks/useEqualizer.js.map +1 -1
- package/lib/module/hooks/useLookaheadCache.js +3 -7
- package/lib/module/hooks/useLookaheadCache.js.map +1 -1
- package/lib/module/hooks/useQueue.js +66 -19
- package/lib/module/hooks/useQueue.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/queueDelta.js +41 -0
- package/lib/module/queueDelta.js.map +1 -0
- package/lib/module/types.js +28 -5
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
- package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
- package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
- package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
- package/lib/typescript/hooks/useCast.d.ts +6 -1
- package/lib/typescript/hooks/useCast.d.ts.map +1 -1
- package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
- package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
- package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
- package/lib/typescript/hooks/useQueue.d.ts +4 -4
- package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/queueDelta.d.ts +10 -0
- package/lib/typescript/queueDelta.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +168 -12
- package/lib/typescript/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
- package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
- package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
- package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
- package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
- package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_BrowseItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______MediaSearchRequest.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_TrackItem______std__string.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/{Func_void_std__vector_TrackItem__double_QueueChangeReason.kt → Func_void_QueueChangeDelta_double_QueueChangeReason.kt} +14 -16
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
- package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
- package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
- package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
- package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
- package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
- package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
- package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
- package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
- package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
- package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
- package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
- package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
- package/package.json +8 -8
- package/src/TrackPlayer.nitro.ts +53 -27
- package/src/hooks/useActiveTrack.ts +29 -22
- package/src/hooks/useCast.ts +14 -15
- package/src/hooks/useEqualizer.ts +19 -12
- package/src/hooks/useLookaheadCache.ts +3 -7
- package/src/hooks/useQueue.ts +66 -17
- package/src/index.ts +13 -3
- package/src/queueDelta.ts +41 -0
- package/src/types.ts +169 -12
- package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2MetadataSync.kt +0 -189
- package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
- package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
- package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
- package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
- package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
- package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
- package/ios/MetadataReader.swift +0 -555
- package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
- package/ios/Tests/MetadataReaderTests.swift +0 -603
- package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
- package/ios/Tests/VoiceDonationTests.swift +0 -26
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
- package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
- /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
|
-
///
|
|
4
|
-
///
|
|
5
|
-
/// `ReplayGainData
|
|
3
|
+
/// The ReplayGain value layer: the four tag names, the numeric
|
|
4
|
+
/// acceptance rules, the string parsers that feed them, and the
|
|
5
|
+
/// assembly of a `ReplayGainData` from a name/value dictionary.
|
|
6
6
|
///
|
|
7
|
-
///
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
11
|
-
///
|
|
12
|
-
///
|
|
7
|
+
/// Deliberately source-agnostic. Names arrive from
|
|
8
|
+
/// `AssetReplayGainReader`, which derives them from whatever shape
|
|
9
|
+
/// the container gave AVFoundation (ID3 `TXXX` info fields, Vorbis
|
|
10
|
+
/// comment keys, M4A freeform reverse-DNS keys); values arrive as the
|
|
11
|
+
/// raw strings those tags carry. Every entry point lands on the same
|
|
12
|
+
/// `acceptGainDb` / `acceptPeak` pair so one rule governs all of them.
|
|
13
13
|
///
|
|
14
14
|
/// Parser tolerance follows NOTES.md §25 — `" dB"` suffix optional,
|
|
15
15
|
/// `+/-` prefix optional, U+2212 minus sign normalised, period
|
|
16
|
-
/// decimal, whitespace tolerant, NaN/Inf rejected.
|
|
17
|
-
///
|
|
16
|
+
/// decimal, whitespace tolerant, NaN/Inf rejected. Name matching is
|
|
17
|
+
/// case-insensitive.
|
|
18
18
|
enum ReplayGainExtractor {
|
|
19
19
|
|
|
20
20
|
// Tag descriptions matched (case-insensitive).
|
|
@@ -23,18 +23,66 @@ enum ReplayGainExtractor {
|
|
|
23
23
|
static let albumGainKey = "REPLAYGAIN_ALBUM_GAIN"
|
|
24
24
|
static let albumPeakKey = "REPLAYGAIN_ALBUM_PEAK"
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// MARK: - Tag assembly
|
|
27
|
+
|
|
28
|
+
/// Build a `ReplayGainData` from a `[name: rawValue]` dictionary.
|
|
29
|
+
/// Names are matched case-insensitively against the four constants
|
|
30
|
+
/// above; each value goes through the parser for its kind, so an
|
|
31
|
+
/// unparseable or out-of-band value lands as `nil` rather than
|
|
32
|
+
/// rejecting its siblings.
|
|
33
|
+
///
|
|
34
|
+
/// Returns `nil` when none of the four names produced a usable
|
|
35
|
+
/// value — "this source carries no ReplayGain".
|
|
36
|
+
static func rgDataFromTags(_ tags: [String: String]) -> ReplayGainData? {
|
|
37
|
+
let upper = tags.reduce(into: [String: String]()) { d, kv in
|
|
38
|
+
d[kv.key.uppercased()] = kv.value
|
|
39
|
+
}
|
|
40
|
+
let trackGain = upper[trackGainKey].flatMap { parseGainDb($0) }
|
|
41
|
+
let trackPeak = upper[trackPeakKey].flatMap { parsePeak($0) }
|
|
42
|
+
let albumGain = upper[albumGainKey].flatMap { parseGainDb($0) }
|
|
43
|
+
let albumPeak = upper[albumPeakKey].flatMap { parsePeak($0) }
|
|
44
|
+
if trackGain == nil && trackPeak == nil
|
|
45
|
+
&& albumGain == nil && albumPeak == nil {
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
return ReplayGainData(
|
|
49
|
+
trackGainDb: trackGain, trackPeak: trackPeak,
|
|
50
|
+
albumGainDb: albumGain, albumPeak: albumPeak
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// MARK: - Numeric acceptance
|
|
55
|
+
|
|
56
|
+
/// Shared numeric acceptance for a gain in dB, whatever entry point
|
|
57
|
+
/// it arrived through — a tag string via `parseGainDb` or a number
|
|
58
|
+
/// straight from the consumer. Rejects only non-finite values: a
|
|
59
|
+
/// gain carries no plausible range guard, unlike a peak, and clip
|
|
60
|
+
/// safety is the gain clamp's job rather than the acceptor's.
|
|
61
|
+
static func acceptGainDb(_ value: Double) -> Double? {
|
|
62
|
+
guard value.isFinite else { return nil }
|
|
63
|
+
return value
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Shared numeric acceptance for a linear peak, whatever entry point
|
|
67
|
+
/// it arrived through — a tag string via `parsePeak` or a number
|
|
68
|
+
/// straight from the consumer. Accepts `0.001 ... 2.0` inclusive:
|
|
69
|
+
/// `1.0` is the spec's encoding of digital full scale and is a real
|
|
70
|
+
/// measurement, and values above it are ordinary true peaks. Rejects
|
|
71
|
+
/// non-finite values, anything below the encoder-zero floor and
|
|
72
|
+
/// anything above an implausible true peak.
|
|
73
|
+
static func acceptPeak(_ value: Double) -> Double? {
|
|
74
|
+
guard value.isFinite else { return nil }
|
|
75
|
+
guard value >= 0.001, value <= 2.0 else { return nil }
|
|
76
|
+
return value
|
|
31
77
|
}
|
|
32
78
|
|
|
33
79
|
// MARK: - Gain parsing
|
|
34
80
|
|
|
35
81
|
/// Parse a `REPLAYGAIN_*_GAIN` string (e.g. `"-6.54 dB"`,
|
|
36
82
|
/// `"+3.21"`, `"−1.0 dB"` with U+2212 minus) into a Double dB.
|
|
37
|
-
/// Returns `nil` for unparseable
|
|
83
|
+
/// Returns `nil` for unparseable input; the parsed number is then
|
|
84
|
+
/// put through `acceptGainDb`, so text handling is all this adds
|
|
85
|
+
/// over the numeric entry point.
|
|
38
86
|
static func parseGainDb(_ rawInput: String) -> Double? {
|
|
39
87
|
var trimmed = rawInput.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
40
88
|
guard !trimmed.isEmpty else { return nil }
|
|
@@ -62,21 +110,16 @@ enum ReplayGainExtractor {
|
|
|
62
110
|
if trimmed.contains(",") { return nil }
|
|
63
111
|
|
|
64
112
|
guard let value = Double(trimmed) else { return nil }
|
|
65
|
-
|
|
66
|
-
return value
|
|
113
|
+
return acceptGainDb(value)
|
|
67
114
|
}
|
|
68
115
|
|
|
69
116
|
/// Parse a `REPLAYGAIN_*_PEAK` string (linear, period decimal).
|
|
70
|
-
/// Returns `nil` for unparseable
|
|
71
|
-
///
|
|
72
|
-
///
|
|
117
|
+
/// Returns `nil` for unparseable input; the parsed number is then
|
|
118
|
+
/// put through `acceptPeak`, which holds the `0.001 ... 2.0`
|
|
119
|
+
/// acceptance band — a full-scale `1.0` included.
|
|
73
120
|
static func parsePeak(_ rawInput: String) -> Double? {
|
|
74
121
|
let trimmed = rawInput.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
75
122
|
guard let value = Double(trimmed) else { return nil }
|
|
76
|
-
|
|
77
|
-
if value > 2.0 { return nil }
|
|
78
|
-
if value < 0.001 { return nil }
|
|
79
|
-
if value == 1.0 { return nil }
|
|
80
|
-
return value
|
|
123
|
+
return acceptPeak(value)
|
|
81
124
|
}
|
|
82
125
|
}
|
|
@@ -15,19 +15,24 @@ import OSLog
|
|
|
15
15
|
enum VoiceDonation {
|
|
16
16
|
private static let logger = Logger(subsystem: "com.queueplayer.lib", category: "SiriDonation")
|
|
17
17
|
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
///
|
|
22
|
-
///
|
|
23
|
-
/// `
|
|
24
|
-
/// `com.apple.developer.siri` entitlement is
|
|
25
|
-
|
|
18
|
+
/// Whether the host app has declared Siri support: the lib's config plugin
|
|
19
|
+
/// (`react-native-queue-player/app.plugin.js`) sets `NSSiriUsageDescription`
|
|
20
|
+
/// in Info.plist when the consumer opts in with `{ siri: true }`.
|
|
21
|
+
///
|
|
22
|
+
/// The second of two gates, and the weaker one. `PlayerConfig`'s
|
|
23
|
+
/// `voiceVocabularyDonationEnabled` is the first: it is the consumer
|
|
24
|
+
/// asserting the `com.apple.developer.siri` entitlement is on the target,
|
|
25
|
+
/// which is the requirement `INVocabulary` actually checks — it throws
|
|
26
|
+
/// `NSInternalInconsistencyException` without it, and Swift cannot catch
|
|
27
|
+
/// that. iOS exposes no API for a process to read its own entitlements, so
|
|
28
|
+
/// no gate here can verify the assertion. This one catches the narrower
|
|
29
|
+
/// misconfiguration of opting in without the usage description.
|
|
30
|
+
private static var hostDeclaresSiri: Bool {
|
|
26
31
|
return Bundle.main.object(forInfoDictionaryKey: "NSSiriUsageDescription") != nil
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
static func donateVoiceVocabulary(_ vocabulary: VoiceVocabulary) {
|
|
30
|
-
guard
|
|
35
|
+
guard hostDeclaresSiri else {
|
|
31
36
|
logger.info("Skipping vocabulary donation — Siri not configured (Info.plist missing NSSiriUsageDescription)")
|
|
32
37
|
return
|
|
33
38
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import AudioToolbox
|
|
2
2
|
import Foundation
|
|
3
3
|
|
|
4
|
-
/// One-shot
|
|
4
|
+
/// One-shot local-file bitrate probe via Core Audio's
|
|
5
5
|
/// `AudioFileStream`.
|
|
6
6
|
///
|
|
7
|
-
/// **Why this exists.**
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
/// (
|
|
11
|
-
/// HLS-oriented and routinely returns NaN/-1 for non-HLS sources.
|
|
7
|
+
/// **Why this exists.** `AVAssetTrack.estimatedDataRate` returns 0
|
|
8
|
+
/// for some local sources, and `AudioFile`'s
|
|
9
|
+
/// `kAudioFilePropertyBitRate` returns 0 for some transcoded MP3s
|
|
10
|
+
/// (some server-transcoded MP3s exhibit this).
|
|
12
11
|
/// Core Audio's `AudioFileStreamOpen` parses the bitstream from
|
|
13
12
|
/// raw bytes and surfaces format/packet info as soon as the first
|
|
14
13
|
/// frame headers land — same approach SwiftAudioPlayer,
|
|
@@ -33,8 +32,8 @@ import Foundation
|
|
|
33
32
|
/// mFramesPerPacket)`. For 192 kbps CBR this converges within
|
|
34
33
|
/// ~1% after ~100 packets — well under our probe window.
|
|
35
34
|
///
|
|
36
|
-
/// **Cost.** One `
|
|
37
|
-
///
|
|
35
|
+
/// **Cost.** One 256 KB `FileHandle` read per distinct local URL,
|
|
36
|
+
/// and no network request at all. The 256 KB window tolerates
|
|
38
37
|
/// large ID3v2 tags (sometimes 32 KB+ for embedded album art)
|
|
39
38
|
/// without starving the parser of audio frames. Parse runs
|
|
40
39
|
/// synchronously on a detached utility-priority Task. In-memory
|
|
@@ -54,24 +53,23 @@ enum StreamingBitrateProbe {
|
|
|
54
53
|
return c
|
|
55
54
|
}()
|
|
56
55
|
|
|
57
|
-
/// 256 KB
|
|
56
|
+
/// 256 KB prefix: enough to skip past large ID3v2 tags + still
|
|
58
57
|
/// gather enough MP3 frames to compute a stable average.
|
|
59
58
|
private static let probeByteCount = 262_144
|
|
60
59
|
|
|
61
|
-
/// Resolve the encoded bitrate (bits/sec) of the audio at
|
|
62
|
-
/// `url
|
|
63
|
-
///
|
|
64
|
-
///
|
|
65
|
-
///
|
|
66
|
-
///
|
|
67
|
-
///
|
|
68
|
-
///
|
|
60
|
+
/// Resolve the encoded bitrate (bits/sec) of the audio at a
|
|
61
|
+
/// `file://` `url` by reading its prefix with `FileHandle` and
|
|
62
|
+
/// running an AudioFileStream parse over it. Necessary because
|
|
63
|
+
/// Core Audio's `AudioFile` API's `kAudioFilePropertyBitRate`
|
|
64
|
+
/// returns 0 for some transcoded MP3s (e.g. server-transcoded MP3s
|
|
65
|
+
/// the lookahead cache has downloaded). The format extractor uses it as
|
|
66
|
+
/// the third-tier fallback for `file://` sources whose codec has no
|
|
67
|
+
/// dedicated reader.
|
|
69
68
|
///
|
|
70
|
-
/// Returns nil
|
|
71
|
-
///
|
|
72
|
-
///
|
|
73
|
-
|
|
74
|
-
static func bitrate(forUrl url: URL, urlSession: URLSession = .shared) async -> Double? {
|
|
69
|
+
/// Returns nil for a non-`file://` URL, an empty or unreadable
|
|
70
|
+
/// file, a format the parser can't identify, or a probe window
|
|
71
|
+
/// that yielded no packets. Results are cached per URL string.
|
|
72
|
+
static func bitrate(forUrl url: URL) async -> Double? {
|
|
75
73
|
let key = url.absoluteString
|
|
76
74
|
if let cached = readCache(key) {
|
|
77
75
|
#if DEBUG
|
|
@@ -79,15 +77,8 @@ enum StreamingBitrateProbe {
|
|
|
79
77
|
#endif
|
|
80
78
|
return cached
|
|
81
79
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
bps = await fetchAndParseLocal(url: url)
|
|
85
|
-
} else if let scheme = url.scheme?.lowercased(),
|
|
86
|
-
scheme == "http" || scheme == "https" {
|
|
87
|
-
bps = await fetchAndParseRemote(url: url, urlSession: urlSession)
|
|
88
|
-
} else {
|
|
89
|
-
bps = nil
|
|
90
|
-
}
|
|
80
|
+
guard url.isFileURL else { return nil }
|
|
81
|
+
let bps = await fetchAndParseLocal(url: url)
|
|
91
82
|
if let bps = bps {
|
|
92
83
|
writeCache(key, bps)
|
|
93
84
|
}
|
|
@@ -120,43 +111,6 @@ enum StreamingBitrateProbe {
|
|
|
120
111
|
}
|
|
121
112
|
}
|
|
122
113
|
|
|
123
|
-
/// Trade-off: this issues a parallel HTTP request to the same
|
|
124
|
-
/// URL AVPlayer is streaming from. On servers with low per-IP
|
|
125
|
-
/// connection caps (some self-hosted Subsonic / Navidrome) a
|
|
126
|
-
/// concurrent Range fetch can briefly compete for a connection
|
|
127
|
-
/// slot. In practice the probe completes in <100 ms and the
|
|
128
|
-
/// cache returns immediately for repeat hits, so the impact is
|
|
129
|
-
/// bounded. Worth knowing if the player ever stalls right after
|
|
130
|
-
/// a track change against a server with strict connection
|
|
131
|
-
/// limits — try a dedicated `URLSession` with
|
|
132
|
-
/// `httpMaximumConnectionsPerHost = 1` if it bites.
|
|
133
|
-
private static func fetchAndParseRemote(
|
|
134
|
-
url: URL, urlSession: URLSession = .shared
|
|
135
|
-
) async -> Double? {
|
|
136
|
-
var req = URLRequest(url: url, timeoutInterval: 10)
|
|
137
|
-
req.setValue("bytes=0-\(probeByteCount - 1)", forHTTPHeaderField: "Range")
|
|
138
|
-
let data: Data
|
|
139
|
-
do {
|
|
140
|
-
let (responseData, response) = try await urlSession.data(for: req)
|
|
141
|
-
if let http = response as? HTTPURLResponse,
|
|
142
|
-
!((200...299).contains(http.statusCode)) {
|
|
143
|
-
#if DEBUG
|
|
144
|
-
NSLog("[RNQP-BITRATE-PROBE] non-2xx status=%d url=%@",
|
|
145
|
-
http.statusCode, url.absoluteString)
|
|
146
|
-
#endif
|
|
147
|
-
return nil
|
|
148
|
-
}
|
|
149
|
-
data = responseData
|
|
150
|
-
} catch {
|
|
151
|
-
#if DEBUG
|
|
152
|
-
NSLog("[RNQP-BITRATE-PROBE] fetch-failed err=%@ url=%@",
|
|
153
|
-
String(describing: error), url.absoluteString)
|
|
154
|
-
#endif
|
|
155
|
-
return nil
|
|
156
|
-
}
|
|
157
|
-
return await runParse(data: data, url: url, source: "remote")
|
|
158
|
-
}
|
|
159
|
-
|
|
160
114
|
/// Read the first `probeByteCount` bytes of a local file via
|
|
161
115
|
/// FileHandle and run the same AudioFileStream parse.
|
|
162
116
|
/// FileHandle is preferred over `Data(contentsOf:)` because
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
|
|
3
|
+
/// The consumer's ReplayGain claim over one queue entry, resolved once
|
|
4
|
+
/// at queue-build time and carried on the `AVPlayerItem` it was built
|
|
5
|
+
/// for.
|
|
6
|
+
///
|
|
7
|
+
/// The claim and the values it survived with are two separate facts, and
|
|
8
|
+
/// keeping them separate is the whole point of this type. A `TrackItem`
|
|
9
|
+
/// that supplies only an out-of-band peak has claimed its track just as
|
|
10
|
+
/// firmly as one that supplies all four values, so collapsing the claim
|
|
11
|
+
/// into "did any value survive acceptance" would hand that track back to
|
|
12
|
+
/// the file's own tags. Presence claims the track; validity only decides
|
|
13
|
+
/// what there is to apply.
|
|
14
|
+
///
|
|
15
|
+
/// The instance therefore exists whenever any of the four fields was
|
|
16
|
+
/// present, and `data` is `nil` when none of those values survived
|
|
17
|
+
/// `ReplayGainExtractor`'s acceptance rules — a track that owns its
|
|
18
|
+
/// ReplayGain and plays at unity.
|
|
19
|
+
final class SuppliedReplayGain {
|
|
20
|
+
/// The supplied values that passed acceptance. `nil` means the
|
|
21
|
+
/// consumer claimed this track and gave nothing usable.
|
|
22
|
+
let data: ReplayGainData?
|
|
23
|
+
|
|
24
|
+
private init(data: ReplayGainData?) {
|
|
25
|
+
self.data = data
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Build the claim for [track], or `nil` when the consumer supplied
|
|
29
|
+
/// none of the four ReplayGain fields and the track is left on
|
|
30
|
+
/// whatever its file carries.
|
|
31
|
+
///
|
|
32
|
+
/// Each present value goes through the same `acceptGainDb` /
|
|
33
|
+
/// `acceptPeak` pair the tag strings land on, so a supplied number and
|
|
34
|
+
/// a tagged number are held to one rule.
|
|
35
|
+
static func from(track: TrackItem) -> SuppliedReplayGain? {
|
|
36
|
+
guard track.replayGainTrackGain != nil
|
|
37
|
+
|| track.replayGainTrackPeak != nil
|
|
38
|
+
|| track.replayGainAlbumGain != nil
|
|
39
|
+
|| track.replayGainAlbumPeak != nil
|
|
40
|
+
else {
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
let trackGain = track.replayGainTrackGain.flatMap { ReplayGainExtractor.acceptGainDb($0) }
|
|
44
|
+
let trackPeak = track.replayGainTrackPeak.flatMap { ReplayGainExtractor.acceptPeak($0) }
|
|
45
|
+
let albumGain = track.replayGainAlbumGain.flatMap { ReplayGainExtractor.acceptGainDb($0) }
|
|
46
|
+
let albumPeak = track.replayGainAlbumPeak.flatMap { ReplayGainExtractor.acceptPeak($0) }
|
|
47
|
+
if trackGain == nil && trackPeak == nil
|
|
48
|
+
&& albumGain == nil && albumPeak == nil {
|
|
49
|
+
return SuppliedReplayGain(data: nil)
|
|
50
|
+
}
|
|
51
|
+
return SuppliedReplayGain(
|
|
52
|
+
data: ReplayGainData(
|
|
53
|
+
trackGainDb: trackGain, trackPeak: trackPeak,
|
|
54
|
+
albumGainDb: albumGain, albumPeak: albumPeak
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private var suppliedReplayGainKey: UInt8 = 0
|
|
61
|
+
|
|
62
|
+
extension AVPlayerItem {
|
|
63
|
+
/// The consumer's ReplayGain claim over the `TrackItem` this item was
|
|
64
|
+
/// built from. Stamped by `AVQueueBuilder.makeItem` at construction
|
|
65
|
+
/// and carried across by the crossfade engine's item rebuilds, the
|
|
66
|
+
/// same way `queueItemId` and `sourceURL` are.
|
|
67
|
+
///
|
|
68
|
+
/// Stamped rather than looked up: the queue-mutating surface is
|
|
69
|
+
/// `setQueue` / `addToQueue` / `removeFromQueue` / `moveInQueue` /
|
|
70
|
+
/// `clearQueue`, and every one of them rebuilds or moves items, so
|
|
71
|
+
/// there is no in-place metadata edit that could leave a stamped value
|
|
72
|
+
/// describing a different track than the one playing.
|
|
73
|
+
///
|
|
74
|
+
/// `nil` means the consumer supplied nothing and the item's ReplayGain
|
|
75
|
+
/// comes from whatever `AssetReplayGainReader` finds in the file.
|
|
76
|
+
var suppliedReplayGain: SuppliedReplayGain? {
|
|
77
|
+
get { objc_getAssociatedObject(self, &suppliedReplayGainKey) as? SuppliedReplayGain }
|
|
78
|
+
set {
|
|
79
|
+
objc_setAssociatedObject(
|
|
80
|
+
self, &suppliedReplayGainKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|