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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.margelo.nitro.queueplayer
|
|
2
2
|
|
|
3
3
|
import androidx.media3.common.C
|
|
4
|
+
import androidx.media3.common.audio.AudioProcessingPipeline
|
|
4
5
|
import androidx.media3.common.audio.AudioProcessor
|
|
5
6
|
import androidx.media3.common.util.UnstableApi
|
|
7
|
+
import com.google.common.collect.ImmutableList
|
|
6
8
|
import org.junit.Assert.assertEquals
|
|
7
9
|
import org.junit.Assert.assertFalse
|
|
8
10
|
import org.junit.Assert.assertTrue
|
|
@@ -17,8 +19,9 @@ import java.nio.ByteOrder
|
|
|
17
19
|
* Unit coverage for [ReplayGainAudioProcessor] — the per-sample gain
|
|
18
20
|
* stage Media3 wires into the AudioSink chain. Exercises the
|
|
19
21
|
* [AudioProcessor] contract: configure, isActive gating, in-place multiply
|
|
20
|
-
* on both 16-bit and float PCM, the 16-bit full-scale clamp,
|
|
21
|
-
* encoding gate
|
|
22
|
+
* on both 16-bit and float PCM, the 16-bit full-scale clamp, the
|
|
23
|
+
* encoding gate, and the unity pass-through that keeps the processor in
|
|
24
|
+
* the [AudioProcessingPipeline] while it applies nothing.
|
|
22
25
|
*/
|
|
23
26
|
@RunWith(RobolectricTestRunner::class)
|
|
24
27
|
@Config(sdk = [34])
|
|
@@ -26,13 +29,21 @@ import java.nio.ByteOrder
|
|
|
26
29
|
class ReplayGainAudioProcessorTest {
|
|
27
30
|
|
|
28
31
|
@Test
|
|
29
|
-
fun `isActive is
|
|
32
|
+
fun `isActive is true at unity gain on a Float32 input`() {
|
|
30
33
|
val proc = ReplayGainAudioProcessor()
|
|
31
34
|
proc.configure(floatFormat(48000, 2))
|
|
32
|
-
|
|
35
|
+
assertTrue(proc.isActive)
|
|
33
36
|
|
|
34
37
|
proc.setLinearGain(1.0f)
|
|
35
|
-
|
|
38
|
+
assertTrue(proc.isActive)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Test
|
|
42
|
+
fun `isActive is true at unity gain on a 16-bit input`() {
|
|
43
|
+
val proc = ReplayGainAudioProcessor()
|
|
44
|
+
proc.configure(int16Format(48000, 2))
|
|
45
|
+
proc.setLinearGain(1.0f)
|
|
46
|
+
assertTrue(proc.isActive)
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
@Test
|
|
@@ -51,6 +62,13 @@ class ReplayGainAudioProcessorTest {
|
|
|
51
62
|
assertTrue(proc.isActive)
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
@Test
|
|
66
|
+
fun `isActive is false before configure`() {
|
|
67
|
+
val proc = ReplayGainAudioProcessor()
|
|
68
|
+
proc.setLinearGain(0.5f)
|
|
69
|
+
assertFalse(proc.isActive)
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
@Test
|
|
55
73
|
fun `isActive is false on an unsupported encoding`() {
|
|
56
74
|
val proc = ReplayGainAudioProcessor()
|
|
@@ -59,6 +77,88 @@ class ReplayGainAudioProcessorTest {
|
|
|
59
77
|
assertFalse(proc.isActive)
|
|
60
78
|
}
|
|
61
79
|
|
|
80
|
+
@Test
|
|
81
|
+
fun `queueInput at unity gain passes float samples through unchanged`() {
|
|
82
|
+
val proc = ReplayGainAudioProcessor()
|
|
83
|
+
proc.configure(floatFormat(48000, 1))
|
|
84
|
+
proc.setLinearGain(1.0f)
|
|
85
|
+
|
|
86
|
+
val input = floatBuffer(floatArrayOf(1.0f, 0.5f, -0.25f, 0.0f, -1.0f))
|
|
87
|
+
proc.queueInput(input)
|
|
88
|
+
|
|
89
|
+
val output = readFloats(proc.output, 5)
|
|
90
|
+
assertEquals(1.0f, output[0], 1e-6f)
|
|
91
|
+
assertEquals(0.5f, output[1], 1e-6f)
|
|
92
|
+
assertEquals(-0.25f, output[2], 1e-6f)
|
|
93
|
+
assertEquals(0.0f, output[3], 1e-6f)
|
|
94
|
+
assertEquals(-1.0f, output[4], 1e-6f)
|
|
95
|
+
assertEquals(0, input.remaining())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Test
|
|
99
|
+
fun `queueInput at unity gain passes 16-bit samples through unchanged`() {
|
|
100
|
+
val proc = ReplayGainAudioProcessor()
|
|
101
|
+
proc.configure(int16Format(48000, 1))
|
|
102
|
+
proc.setLinearGain(1.0f)
|
|
103
|
+
|
|
104
|
+
val input = shortBuffer(shortArrayOf(32767, -32768, 10000, 0, -3))
|
|
105
|
+
proc.queueInput(input)
|
|
106
|
+
|
|
107
|
+
val output = readShorts(proc.output, 5)
|
|
108
|
+
assertEquals(32767, output[0].toInt())
|
|
109
|
+
assertEquals(-32768, output[1].toInt())
|
|
110
|
+
assertEquals(10000, output[2].toInt())
|
|
111
|
+
assertEquals(0, output[3].toInt())
|
|
112
|
+
assertEquals(-3, output[4].toInt())
|
|
113
|
+
assertEquals(0, input.remaining())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Test
|
|
117
|
+
fun `a pipeline configured at unity gain stays operational`() {
|
|
118
|
+
val proc = ReplayGainAudioProcessor()
|
|
119
|
+
val pipeline = newPipeline(proc, int16Format(48000, 1))
|
|
120
|
+
|
|
121
|
+
assertTrue(
|
|
122
|
+
"the gain stage must stay in the active set so a later gain can reach the audio",
|
|
123
|
+
pipeline.isOperational
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@Test
|
|
128
|
+
fun `a gain pushed after the pipeline is configured reaches the audio`() {
|
|
129
|
+
val proc = ReplayGainAudioProcessor()
|
|
130
|
+
// configure() + flush() is where Media3 fixes the active-processor set,
|
|
131
|
+
// and it runs at unity for a queue loaded while ReplayGain is off.
|
|
132
|
+
val pipeline = newPipeline(proc, int16Format(48000, 1))
|
|
133
|
+
|
|
134
|
+
// The gain arrives afterwards, as it does when the mode is selected
|
|
135
|
+
// mid-playback. Nothing reconfigures or flushes the sink.
|
|
136
|
+
proc.setLinearGain(0.5f)
|
|
137
|
+
|
|
138
|
+
val input = shortBuffer(shortArrayOf(10000, -8000, 4))
|
|
139
|
+
pipeline.queueInput(input)
|
|
140
|
+
|
|
141
|
+
assertTrue(pipeline.isOperational)
|
|
142
|
+
val output = drainShorts(pipeline, 3)
|
|
143
|
+
assertEquals(5000, output[0].toInt())
|
|
144
|
+
assertEquals(-4000, output[1].toInt())
|
|
145
|
+
assertEquals(2, output[2].toInt())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun `a gain pushed then returned to unity stops reaching the audio`() {
|
|
150
|
+
val proc = ReplayGainAudioProcessor()
|
|
151
|
+
val pipeline = newPipeline(proc, int16Format(48000, 1))
|
|
152
|
+
|
|
153
|
+
proc.setLinearGain(0.5f)
|
|
154
|
+
pipeline.queueInput(shortBuffer(shortArrayOf(10000)))
|
|
155
|
+
assertEquals(5000, drainShorts(pipeline, 1)[0].toInt())
|
|
156
|
+
|
|
157
|
+
proc.setLinearGain(1.0f)
|
|
158
|
+
pipeline.queueInput(shortBuffer(shortArrayOf(10000)))
|
|
159
|
+
assertEquals(10000, drainShorts(pipeline, 1)[0].toInt())
|
|
160
|
+
}
|
|
161
|
+
|
|
62
162
|
@Test
|
|
63
163
|
fun `queueInput multiplies every sample by the linear gain in place`() {
|
|
64
164
|
val proc = ReplayGainAudioProcessor()
|
|
@@ -156,6 +256,29 @@ class ReplayGainAudioProcessorTest {
|
|
|
156
256
|
|
|
157
257
|
// region Helpers
|
|
158
258
|
|
|
259
|
+
/** A single-stage pipeline taken through Media3's configure-then-flush setup. */
|
|
260
|
+
private fun newPipeline(
|
|
261
|
+
proc: ReplayGainAudioProcessor,
|
|
262
|
+
format: AudioProcessor.AudioFormat,
|
|
263
|
+
): AudioProcessingPipeline {
|
|
264
|
+
val pipeline = AudioProcessingPipeline(ImmutableList.of<AudioProcessor>(proc))
|
|
265
|
+
pipeline.configure(format)
|
|
266
|
+
pipeline.flush(AudioProcessor.StreamMetadata.DEFAULT)
|
|
267
|
+
return pipeline
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Read [count] shorts of pipeline output and consume them the way
|
|
272
|
+
* `DefaultAudioSink` does — an undrained output buffer blocks the next
|
|
273
|
+
* [AudioProcessingPipeline.queueInput] from reaching the processors.
|
|
274
|
+
*/
|
|
275
|
+
private fun drainShorts(pipeline: AudioProcessingPipeline, count: Int): ShortArray {
|
|
276
|
+
val buffer = pipeline.output
|
|
277
|
+
val values = readShorts(buffer, count)
|
|
278
|
+
buffer.position(buffer.position() + count * Short.SIZE_BYTES)
|
|
279
|
+
return values
|
|
280
|
+
}
|
|
281
|
+
|
|
159
282
|
private fun floatFormat(sampleRate: Int, channelCount: Int) =
|
|
160
283
|
AudioProcessor.AudioFormat(sampleRate, channelCount, C.ENCODING_PCM_FLOAT)
|
|
161
284
|
|
|
@@ -103,7 +103,7 @@ class ReplayGainExtractorTest {
|
|
|
103
103
|
|
|
104
104
|
// endregion
|
|
105
105
|
|
|
106
|
-
// region parsePeak
|
|
106
|
+
// region parsePeak acceptance band
|
|
107
107
|
|
|
108
108
|
@Test
|
|
109
109
|
fun `parsePeak reads the standard linear format`() {
|
|
@@ -113,25 +113,40 @@ class ReplayGainExtractorTest {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
@Test
|
|
116
|
-
fun `parsePeak
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
fun `parsePeak accepts a peak of exactly 1_0`() {
|
|
117
|
+
// 1.0 is the RG2 encoding of digital full scale — a real and
|
|
118
|
+
// common measurement for a modern master, and the value most in
|
|
119
|
+
// need of the 1/peak clip cap.
|
|
120
|
+
assertPeak("1.000000", 1.0f)
|
|
121
|
+
assertPeak("1", 1.0f)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@Test
|
|
125
|
+
fun `parsePeak accepts real measurements either side of unity`() {
|
|
126
|
+
assertPeak("0.923366", 0.923366f)
|
|
127
|
+
assertPeak("1.160082", 1.160082f)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
fun `parsePeak accepts the 0_001 and 2_0 boundaries and rejects just outside them`() {
|
|
132
|
+
assertPeak("0.001", 0.001f)
|
|
133
|
+
assertPeak("2.0", 2.0f)
|
|
125
134
|
assertNull(
|
|
126
|
-
"
|
|
127
|
-
ReplayGainExtractor.parsePeak("0.
|
|
135
|
+
"0.000999 is below the encoder-zero floor",
|
|
136
|
+
ReplayGainExtractor.parsePeak("0.000999"),
|
|
128
137
|
)
|
|
129
138
|
assertNull(
|
|
130
|
-
"
|
|
131
|
-
ReplayGainExtractor.parsePeak("
|
|
139
|
+
"2.000001 is an implausible true peak",
|
|
140
|
+
ReplayGainExtractor.parsePeak("2.000001"),
|
|
132
141
|
)
|
|
133
142
|
}
|
|
134
143
|
|
|
144
|
+
@Test
|
|
145
|
+
fun `parsePeak rejects peaks below 0_001`() {
|
|
146
|
+
assertNull(ReplayGainExtractor.parsePeak("0.0001"))
|
|
147
|
+
assertNull(ReplayGainExtractor.parsePeak("0"))
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
@Test
|
|
136
151
|
fun `parsePeak rejects values outside the 0-to-2 range`() {
|
|
137
152
|
assertNull(ReplayGainExtractor.parsePeak("-0.5"))
|
|
@@ -148,6 +163,138 @@ class ReplayGainExtractorTest {
|
|
|
148
163
|
|
|
149
164
|
// endregion
|
|
150
165
|
|
|
166
|
+
// region acceptPeak — the numeric entry point
|
|
167
|
+
|
|
168
|
+
@Test
|
|
169
|
+
fun `acceptPeak accepts full scale and the real-measurement band`() {
|
|
170
|
+
assertAcceptedPeak(1.0, 1.0f)
|
|
171
|
+
assertAcceptedPeak(0.923366, 0.923366f)
|
|
172
|
+
assertAcceptedPeak(1.160082, 1.160082f)
|
|
173
|
+
assertAcceptedPeak(0.5, 0.5f)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun `acceptPeak accepts the 0_001 and 2_0 boundaries and rejects just outside them`() {
|
|
178
|
+
assertAcceptedPeak(0.001, 0.001f)
|
|
179
|
+
assertAcceptedPeak(2.0, 2.0f)
|
|
180
|
+
assertNull("0.000999 is below the encoder-zero floor", ReplayGainExtractor.acceptPeak(0.000999))
|
|
181
|
+
assertNull("2.000001 is an implausible true peak", ReplayGainExtractor.acceptPeak(2.000001))
|
|
182
|
+
assertNull(ReplayGainExtractor.acceptPeak(0.0))
|
|
183
|
+
assertNull(ReplayGainExtractor.acceptPeak(-0.5))
|
|
184
|
+
assertNull(ReplayGainExtractor.acceptPeak(3.0))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@Test
|
|
188
|
+
fun `acceptPeak rejects a value above 2_0 that narrows to 2_0 as a Float`() {
|
|
189
|
+
// 2.0000001 is inside half a Float ulp of 2.0, so a range check
|
|
190
|
+
// applied after the Double -> Float narrowing sees 2.0f and lets
|
|
191
|
+
// it through. The 2.0 case is the positive control: this test
|
|
192
|
+
// must not be satisfiable by rejecting everything.
|
|
193
|
+
assertAcceptedPeak(2.0, 2.0f)
|
|
194
|
+
assertNull(ReplayGainExtractor.acceptPeak(2.0000001))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Test
|
|
198
|
+
fun `acceptPeak rejects non-finite input`() {
|
|
199
|
+
assertAcceptedPeak(1.0, 1.0f)
|
|
200
|
+
assertNull(ReplayGainExtractor.acceptPeak(Double.NaN))
|
|
201
|
+
assertNull(ReplayGainExtractor.acceptPeak(Double.POSITIVE_INFINITY))
|
|
202
|
+
assertNull(ReplayGainExtractor.acceptPeak(Double.NEGATIVE_INFINITY))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// endregion
|
|
206
|
+
|
|
207
|
+
// region acceptGainDb — the numeric entry point
|
|
208
|
+
|
|
209
|
+
@Test
|
|
210
|
+
fun `acceptGainDb accepts every finite value with no plausibility band`() {
|
|
211
|
+
assertAcceptedGain(-20.0, -20.0f)
|
|
212
|
+
assertAcceptedGain(-6.54, -6.54f)
|
|
213
|
+
assertAcceptedGain(0.0, 0.0f)
|
|
214
|
+
assertAcceptedGain(6.0, 6.0f)
|
|
215
|
+
assertAcceptedGain(3.21, 3.21f)
|
|
216
|
+
// Physically absurd, still accepted — gain carries no range guard;
|
|
217
|
+
// clip safety is the clamp's job, not the acceptor's.
|
|
218
|
+
assertAcceptedGain(100.0, 100.0f)
|
|
219
|
+
assertAcceptedGain(-100.0, -100.0f)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Test
|
|
223
|
+
fun `acceptGainDb rejects non-finite input`() {
|
|
224
|
+
assertAcceptedGain(-20.0, -20.0f)
|
|
225
|
+
assertNull(ReplayGainExtractor.acceptGainDb(Double.NaN))
|
|
226
|
+
assertNull(ReplayGainExtractor.acceptGainDb(Double.POSITIVE_INFINITY))
|
|
227
|
+
assertNull(ReplayGainExtractor.acceptGainDb(Double.NEGATIVE_INFINITY))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@Test
|
|
231
|
+
fun `acceptGainDb rejects a magnitude that narrows to Float infinity`() {
|
|
232
|
+
// Finite as a Double, infinite as a Float — the returned value has
|
|
233
|
+
// to be finite, so the check belongs after the narrowing.
|
|
234
|
+
assertAcceptedGain(-20.0, -20.0f)
|
|
235
|
+
assertNull(ReplayGainExtractor.acceptGainDb(1e40))
|
|
236
|
+
assertNull(ReplayGainExtractor.acceptGainDb(-1e40))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// endregion
|
|
240
|
+
|
|
241
|
+
// region String and numeric entry points share one rule
|
|
242
|
+
|
|
243
|
+
@Test
|
|
244
|
+
fun `parsePeak and acceptPeak agree on every value in the peak acceptance table`() {
|
|
245
|
+
val table = listOf(
|
|
246
|
+
"1.000000" to 1.0,
|
|
247
|
+
"1" to 1.0,
|
|
248
|
+
"0.923366" to 0.923366,
|
|
249
|
+
"1.160082" to 1.160082,
|
|
250
|
+
"0.5" to 0.5,
|
|
251
|
+
"2.0" to 2.0,
|
|
252
|
+
"2.000001" to 2.000001,
|
|
253
|
+
"0.001" to 0.001,
|
|
254
|
+
"0.000999" to 0.000999,
|
|
255
|
+
"0" to 0.0,
|
|
256
|
+
"-0.5" to -0.5,
|
|
257
|
+
"3.0" to 3.0,
|
|
258
|
+
"nan" to Double.NaN,
|
|
259
|
+
"inf" to Double.POSITIVE_INFINITY,
|
|
260
|
+
"-inf" to Double.NEGATIVE_INFINITY,
|
|
261
|
+
)
|
|
262
|
+
for ((raw, numeric) in table) {
|
|
263
|
+
assertSamePeakVerdict(raw, numeric)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@Test
|
|
268
|
+
fun `parseGainDb and acceptGainDb agree on every value in the gain acceptance table`() {
|
|
269
|
+
val table = listOf(
|
|
270
|
+
"-20.00 dB" to -20.0,
|
|
271
|
+
"-6.54" to -6.54,
|
|
272
|
+
"+3.21 dB" to 3.21,
|
|
273
|
+
"0.00 dB" to 0.0,
|
|
274
|
+
"100.0" to 100.0,
|
|
275
|
+
"-100.0" to -100.0,
|
|
276
|
+
"nan dB" to Double.NaN,
|
|
277
|
+
"inf" to Double.POSITIVE_INFINITY,
|
|
278
|
+
"-inf" to Double.NEGATIVE_INFINITY,
|
|
279
|
+
)
|
|
280
|
+
for ((raw, numeric) in table) {
|
|
281
|
+
assertSameGainVerdict(raw, numeric)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@Test
|
|
286
|
+
fun `comma decimals reject on the string path while the numeric path accepts the value`() {
|
|
287
|
+
// The period-decimal rule is a property of RG2 text, not of the
|
|
288
|
+
// number — so it lives in the string parser and the numeric
|
|
289
|
+
// acceptor takes the same value happily.
|
|
290
|
+
assertNull(ReplayGainExtractor.parsePeak("0,5"))
|
|
291
|
+
assertAcceptedPeak(0.5, 0.5f)
|
|
292
|
+
assertNull(ReplayGainExtractor.parseGainDb("-6,54 dB"))
|
|
293
|
+
assertAcceptedGain(-6.54, -6.54f)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// endregion
|
|
297
|
+
|
|
151
298
|
// region canonicalKeyForName mapping
|
|
152
299
|
|
|
153
300
|
@Test
|
|
@@ -450,6 +597,54 @@ class ReplayGainExtractorTest {
|
|
|
450
597
|
assertEquals(expected, actual!!, accuracy)
|
|
451
598
|
}
|
|
452
599
|
|
|
600
|
+
private fun assertAcceptedPeak(value: Double, expected: Float, accuracy: Float = 0.0001f) {
|
|
601
|
+
val actual = ReplayGainExtractor.acceptPeak(value)
|
|
602
|
+
assertNotNull("expected acceptPeak($value) to succeed", actual)
|
|
603
|
+
assertEquals(expected, actual!!, accuracy)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private fun assertAcceptedGain(value: Double, expected: Float, accuracy: Float = 0.001f) {
|
|
607
|
+
val actual = ReplayGainExtractor.acceptGainDb(value)
|
|
608
|
+
assertNotNull("expected acceptGainDb($value) to succeed", actual)
|
|
609
|
+
assertEquals(expected, actual!!, accuracy)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private fun assertSamePeakVerdict(raw: String, numeric: Double) {
|
|
613
|
+
val fromString = ReplayGainExtractor.parsePeak(raw)
|
|
614
|
+
val fromNumeric = ReplayGainExtractor.acceptPeak(numeric)
|
|
615
|
+
assertEquals(
|
|
616
|
+
"parsePeak('$raw') and acceptPeak($numeric) must agree on acceptance",
|
|
617
|
+
fromString == null,
|
|
618
|
+
fromNumeric == null,
|
|
619
|
+
)
|
|
620
|
+
if (fromString != null && fromNumeric != null) {
|
|
621
|
+
assertEquals(
|
|
622
|
+
"parsePeak('$raw') and acceptPeak($numeric) must agree on value",
|
|
623
|
+
fromString,
|
|
624
|
+
fromNumeric,
|
|
625
|
+
0.0001f,
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private fun assertSameGainVerdict(raw: String, numeric: Double) {
|
|
631
|
+
val fromString = ReplayGainExtractor.parseGainDb(raw)
|
|
632
|
+
val fromNumeric = ReplayGainExtractor.acceptGainDb(numeric)
|
|
633
|
+
assertEquals(
|
|
634
|
+
"parseGainDb('$raw') and acceptGainDb($numeric) must agree on acceptance",
|
|
635
|
+
fromString == null,
|
|
636
|
+
fromNumeric == null,
|
|
637
|
+
)
|
|
638
|
+
if (fromString != null && fromNumeric != null) {
|
|
639
|
+
assertEquals(
|
|
640
|
+
"parseGainDb('$raw') and acceptGainDb($numeric) must agree on value",
|
|
641
|
+
fromString,
|
|
642
|
+
fromNumeric,
|
|
643
|
+
0.001f,
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
453
648
|
private fun txxx(description: String, value: String): TextInformationFrame =
|
|
454
649
|
TextInformationFrame(
|
|
455
650
|
/* id = */ "TXXX",
|
|
@@ -94,30 +94,170 @@ class ReplayGainGainTest {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
@Test
|
|
97
|
-
fun `
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// raw to 1.0 — but the contract is "peak == 1.0 means no useful
|
|
102
|
-
// peak data", so the cap is bypassed.
|
|
97
|
+
fun `a full-scale peak caps the boost at unity`() {
|
|
98
|
+
// 1.0 is digital full scale, so 1/peak == 1.0 and a +6 dB boost
|
|
99
|
+
// (raw ≈ 1.995) has nowhere to go: any boost on a full-scale
|
|
100
|
+
// master clips. The cap binds to exactly unity.
|
|
103
101
|
val data = ReplayGainData(
|
|
104
102
|
trackGainDb = 6.0f,
|
|
105
103
|
trackPeak = 1.0f,
|
|
106
104
|
albumGainDb = null,
|
|
107
105
|
albumPeak = null,
|
|
108
106
|
)
|
|
109
|
-
assertEquals(
|
|
107
|
+
assertEquals(1.0f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
@Test
|
|
113
|
-
fun `
|
|
111
|
+
fun `a full-scale peak leaves attenuation alone`() {
|
|
112
|
+
// -20 dB raw = 0.100, well under the 1.0 cap — the cap is present
|
|
113
|
+
// and simply does not bind. Attenuation can never clip.
|
|
114
|
+
val data = ReplayGainData(
|
|
115
|
+
trackGainDb = -20.0f,
|
|
116
|
+
trackPeak = 1.0f,
|
|
117
|
+
albumGainDb = null,
|
|
118
|
+
albumPeak = null,
|
|
119
|
+
)
|
|
120
|
+
assertEquals(lin(-20.0), computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Test
|
|
124
|
+
fun `an unusable peak clamps the boost to unity`() {
|
|
125
|
+
// A peak below 0.001 carries no usable measurement, and a peak
|
|
126
|
+
// cannot be derived from a gain — so boost is suppressed rather
|
|
127
|
+
// than run uncapped.
|
|
128
|
+
for (peak in listOf(0.0005f, 0.0001f)) {
|
|
129
|
+
val data = ReplayGainData(
|
|
130
|
+
trackGainDb = 6.0f,
|
|
131
|
+
trackPeak = peak,
|
|
132
|
+
albumGainDb = null,
|
|
133
|
+
albumPeak = null,
|
|
134
|
+
)
|
|
135
|
+
assertEquals(
|
|
136
|
+
"peak $peak carries no usable measurement",
|
|
137
|
+
1.0f,
|
|
138
|
+
computeLinearGain(data, ReplayGainMode.TRACK),
|
|
139
|
+
1e-6f,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@Test
|
|
145
|
+
fun `no peak at all clamps the boost to unity`() {
|
|
114
146
|
val data = ReplayGainData(
|
|
115
147
|
trackGainDb = 6.0f,
|
|
116
|
-
trackPeak =
|
|
148
|
+
trackPeak = null,
|
|
117
149
|
albumGainDb = null,
|
|
118
150
|
albumPeak = null,
|
|
119
151
|
)
|
|
120
|
-
assertEquals(
|
|
152
|
+
assertEquals(1.0f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
fun `no peak at all leaves attenuation untouched`() {
|
|
157
|
+
// The positive half of the clamp: the boost suppression must not
|
|
158
|
+
// become a blanket clamp that also flattens cuts.
|
|
159
|
+
val data = ReplayGainData(
|
|
160
|
+
trackGainDb = -20.0f,
|
|
161
|
+
trackPeak = null,
|
|
162
|
+
albumGainDb = null,
|
|
163
|
+
albumPeak = null,
|
|
164
|
+
)
|
|
165
|
+
assertEquals(lin(-20.0), computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@Test
|
|
169
|
+
fun `a zero gain with no peak stays at unity`() {
|
|
170
|
+
val data = ReplayGainData(
|
|
171
|
+
trackGainDb = 0.0f,
|
|
172
|
+
trackPeak = null,
|
|
173
|
+
albumGainDb = null,
|
|
174
|
+
albumPeak = null,
|
|
175
|
+
)
|
|
176
|
+
assertEquals(1.0f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Test
|
|
180
|
+
fun `a peak with headroom lets the full boost through`() {
|
|
181
|
+
// The positive control for the clamp rows above: peak 0.5 gives
|
|
182
|
+
// 1/peak = 2.0, so +6 dB (≈ 1.9953) fits underneath and is applied
|
|
183
|
+
// in full. Without this, "expect 1.000" everywhere is satisfied by
|
|
184
|
+
// an implementation that applies nothing at all.
|
|
185
|
+
val data = ReplayGainData(
|
|
186
|
+
trackGainDb = 6.0f,
|
|
187
|
+
trackPeak = 0.5f,
|
|
188
|
+
albumGainDb = null,
|
|
189
|
+
albumPeak = null,
|
|
190
|
+
)
|
|
191
|
+
assertEquals(1.9953f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-4f)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Test
|
|
195
|
+
fun `a boost beyond the headroom is capped at 1 over peak`() {
|
|
196
|
+
// +12 dB raw ≈ 3.981; peak 0.5 → 1/peak = 2.0 — cap binds.
|
|
197
|
+
val data = ReplayGainData(
|
|
198
|
+
trackGainDb = 12.0f,
|
|
199
|
+
trackPeak = 0.5f,
|
|
200
|
+
albumGainDb = null,
|
|
201
|
+
albumPeak = null,
|
|
202
|
+
)
|
|
203
|
+
assertEquals(2.0f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-6f)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Test
|
|
207
|
+
fun `a true peak above unity caps the boost below unity`() {
|
|
208
|
+
// Peaks above 1.0 are ordinary true-peak measurements: 1/1.16 =
|
|
209
|
+
// 0.862, so a +6 dB boost ends up as an attenuation.
|
|
210
|
+
val data = ReplayGainData(
|
|
211
|
+
trackGainDb = 6.0f,
|
|
212
|
+
trackPeak = 1.16f,
|
|
213
|
+
albumGainDb = null,
|
|
214
|
+
albumPeak = null,
|
|
215
|
+
)
|
|
216
|
+
assertEquals(1.0f / 1.16f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-4f)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@Test
|
|
220
|
+
fun `the clip cap applies in album mode as well as track mode`() {
|
|
221
|
+
// Wiring the cap on the track pair alone passes every row above.
|
|
222
|
+
val noPeak = ReplayGainData(
|
|
223
|
+
trackGainDb = null,
|
|
224
|
+
trackPeak = null,
|
|
225
|
+
albumGainDb = 6.0f,
|
|
226
|
+
albumPeak = null,
|
|
227
|
+
)
|
|
228
|
+
assertEquals(
|
|
229
|
+
"album mode with no album peak clamps the boost to unity",
|
|
230
|
+
1.0f,
|
|
231
|
+
computeLinearGain(noPeak, ReplayGainMode.ALBUM),
|
|
232
|
+
1e-6f,
|
|
233
|
+
)
|
|
234
|
+
val fullScale = ReplayGainData(
|
|
235
|
+
trackGainDb = null,
|
|
236
|
+
trackPeak = null,
|
|
237
|
+
albumGainDb = 6.0f,
|
|
238
|
+
albumPeak = 1.0f,
|
|
239
|
+
)
|
|
240
|
+
assertEquals(
|
|
241
|
+
"album mode with a full-scale album peak caps the boost at unity",
|
|
242
|
+
1.0f,
|
|
243
|
+
computeLinearGain(fullScale, ReplayGainMode.ALBUM),
|
|
244
|
+
1e-6f,
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@Test
|
|
249
|
+
fun `each mode caps against its own peak`() {
|
|
250
|
+
// Same gain in both modes, different peaks — the applied value has
|
|
251
|
+
// to differ, which shows the cap reads the selected pair's peak
|
|
252
|
+
// rather than whichever peak the data happens to carry.
|
|
253
|
+
val data = ReplayGainData(
|
|
254
|
+
trackGainDb = 6.0f,
|
|
255
|
+
trackPeak = 0.5f,
|
|
256
|
+
albumGainDb = 6.0f,
|
|
257
|
+
albumPeak = 1.16f,
|
|
258
|
+
)
|
|
259
|
+
assertEquals(1.9953f, computeLinearGain(data, ReplayGainMode.TRACK), 1e-4f)
|
|
260
|
+
assertEquals(1.0f / 1.16f, computeLinearGain(data, ReplayGainMode.ALBUM), 1e-4f)
|
|
121
261
|
}
|
|
122
262
|
|
|
123
263
|
@Test
|