react-native-queue-player 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/QueuePlayer.podspec +14 -9
  2. package/README.md +3 -3
  3. package/android/consumer-rules.pro +1 -1
  4. package/android/src/main/cpp/airplay2_control.c +4 -4
  5. package/android/src/main/cpp/airplay2_control.h +4 -4
  6. package/android/src/main/cpp/airplay2_jni.cpp +3 -19
  7. package/android/src/main/cpp/airplay2_pair.c +7 -7
  8. package/android/src/main/cpp/airplay2_pair.h +2 -2
  9. package/android/src/main/cpp/airplay2_rtsp.c +8 -8
  10. package/android/src/main/cpp/airplay2_session.c +2 -2
  11. package/android/src/main/cpp/airplay_jni.cpp +8 -69
  12. package/android/src/main/java/com/margelo/nitro/queueplayer/AirPlayEngine.kt +7 -16
  13. package/android/src/main/java/com/margelo/nitro/queueplayer/CacheMimeTypes.kt +1 -1
  14. package/android/src/main/java/com/margelo/nitro/queueplayer/CastManager.kt +2 -14
  15. package/android/src/main/java/com/margelo/nitro/queueplayer/CellularTransportMonitor.kt +77 -0
  16. package/android/src/main/java/com/margelo/nitro/queueplayer/CrossfadeEngine.kt +187 -52
  17. package/android/src/main/java/com/margelo/nitro/queueplayer/Equalizer.kt +27 -7
  18. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerEngine.kt +33 -12
  19. package/android/src/main/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngine.kt +31 -8
  20. package/android/src/main/java/com/margelo/nitro/queueplayer/FFTProcessorTee.kt +45 -30
  21. package/android/src/main/java/com/margelo/nitro/queueplayer/FifoCacheEvictor.kt +111 -15
  22. package/android/src/main/java/com/margelo/nitro/queueplayer/GaplessEngine.kt +60 -33
  23. package/android/src/main/java/com/margelo/nitro/queueplayer/HeadlessJsMediaService.kt +1 -1
  24. package/android/src/main/java/com/margelo/nitro/queueplayer/IEqualizerEngine.kt +3 -1
  25. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCache.kt +103 -29
  26. package/android/src/main/java/com/margelo/nitro/queueplayer/LookaheadCacheWriter.kt +77 -29
  27. package/android/src/main/java/com/margelo/nitro/queueplayer/MediaItemBuilder.kt +6 -6
  28. package/android/src/main/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractor.kt +48 -1
  29. package/android/src/main/java/com/margelo/nitro/queueplayer/PitchCorrection.kt +3 -2
  30. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackEngine.kt +71 -76
  31. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackErrorMapping.kt +3 -3
  32. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackModeStateMachine.kt +2 -2
  33. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackService.kt +68 -46
  34. package/android/src/main/java/com/margelo/nitro/queueplayer/PlaybackServiceCallback.kt +6 -5
  35. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueMutationArithmetic.kt +5 -6
  36. package/android/src/main/java/com/margelo/nitro/queueplayer/QueueSkipArithmetic.kt +11 -8
  37. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessor.kt +26 -6
  38. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainData.kt +22 -3
  39. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainExtractor.kt +38 -12
  40. package/android/src/main/java/com/margelo/nitro/queueplayer/ReplayGainGain.kt +17 -13
  41. package/android/src/main/java/com/margelo/nitro/queueplayer/SuppliedReplayGain.kt +66 -0
  42. package/android/src/main/java/com/margelo/nitro/queueplayer/TrackPlayer.kt +839 -271
  43. package/android/src/main/java/com/margelo/nitro/queueplayer/Visualizer.kt +4 -8
  44. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastBackend.kt +4 -3
  45. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/CastSession.kt +5 -4
  46. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2JNI.kt +0 -2
  47. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlay2Session.kt +4 -2
  48. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayBackend.kt +16 -9
  49. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayMetadataSync.kt +1 -1
  50. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirPlayRenderersFactory.kt +6 -14
  51. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/AirplayJNI.kt +1 -13
  52. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/airplay/MulticastLockHolder.kt +1 -1
  53. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/chromecast/ChromecastBackend.kt +3 -3
  54. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServer.kt +11 -35
  55. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaServerHandle.kt +0 -5
  56. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/MediaTokenRegistry.kt +0 -6
  57. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitor.kt +32 -33
  58. package/android/src/test/java/androidx/media3/session/{MediaSessionControllerRequestTestSeam.kt → MediaSessionControllerRequestTestSupport.kt} +1 -1
  59. package/android/src/test/java/com/margelo/nitro/queueplayer/AvrcpMetadataTest.kt +12 -0
  60. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineFocusLossTest.kt +115 -0
  61. package/android/src/test/java/com/margelo/nitro/queueplayer/CrossfadeEngineLifecycleTest.kt +45 -0
  62. package/android/src/test/java/com/margelo/nitro/queueplayer/EngineEndSignalOrderTest.kt +70 -0
  63. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerEngineTest.kt +17 -0
  64. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerKtTest.kt +0 -6
  65. package/android/src/test/java/com/margelo/nitro/queueplayer/EqualizerLegacyEngineTest.kt +17 -0
  66. package/android/src/test/java/com/margelo/nitro/queueplayer/FFTProcessorTeeTest.kt +15 -0
  67. package/android/src/test/java/com/margelo/nitro/queueplayer/FifoCacheEvictorTest.kt +135 -4
  68. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineLifecycleTest.kt +1 -0
  69. package/android/src/test/java/com/margelo/nitro/queueplayer/GaplessEngineReplayGainTest.kt +36 -2
  70. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheTest.kt +24 -18
  71. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterCancelTest.kt +85 -0
  72. package/android/src/test/java/com/margelo/nitro/queueplayer/LookaheadCacheWriterTest.kt +112 -1
  73. package/android/src/test/java/com/margelo/nitro/queueplayer/MediaItemBuilderTest.kt +5 -3
  74. package/android/src/test/java/com/margelo/nitro/queueplayer/NowPlayingFormatExtractorTest.kt +45 -0
  75. package/android/src/test/java/com/margelo/nitro/queueplayer/PitchAwareAudioProcessorChainTest.kt +1 -1
  76. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackSearchTest.kt +47 -0
  77. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceCallbackTest.kt +53 -29
  78. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackServiceLifecycleTest.kt +8 -8
  79. package/android/src/test/java/com/margelo/nitro/queueplayer/QueueSkipArithmeticTest.kt +33 -1
  80. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainAudioProcessorTest.kt +128 -5
  81. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainExtractorTest.kt +209 -14
  82. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainGainTest.kt +150 -10
  83. package/android/src/test/java/com/margelo/nitro/queueplayer/ReplayGainMergeTest.kt +258 -0
  84. package/android/src/test/java/com/margelo/nitro/queueplayer/RobolectricServiceBindHelper.kt +2 -2
  85. package/android/src/test/java/com/margelo/nitro/queueplayer/SessionCommandForwardingPlayerTest.kt +2 -2
  86. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowDynamicsProcessingRejectingEnable.kt +19 -0
  87. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizer.kt +1 -1
  88. package/android/src/test/java/com/margelo/nitro/queueplayer/ShadowEqualizerRejectingBandWrites.kt +27 -0
  89. package/android/src/test/java/com/margelo/nitro/queueplayer/SuppliedReplayGainTest.kt +61 -0
  90. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAirPlayMetadataWiringTest.kt +4 -4
  91. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerAudioFocusTest.kt +1 -1
  92. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastCommandRoutingTest.kt +2 -1
  93. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerCastStateTest.kt +2 -1
  94. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerEventsTest.kt +88 -38
  95. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLifecycleTest.kt +205 -27
  96. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerLookaheadConfigTest.kt +277 -38
  97. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerMutationTest.kt +5 -3
  98. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerProgressThrottleTest.kt +2 -1
  99. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueChangeTest.kt +85 -3
  100. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerQueueTest.kt +11 -7
  101. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerReadersTest.kt +2 -1
  102. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipCapabilityTest.kt +90 -4
  103. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerSkipTest.kt +8 -26
  104. package/android/src/test/java/com/margelo/nitro/queueplayer/TrackPlayerTransportTest.kt +3 -3
  105. package/android/src/test/java/com/margelo/nitro/queueplayer/VisualizerKtTest.kt +0 -4
  106. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/FakeRemotePlayer.kt +2 -2
  107. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/LocalAddressMonitorTest.kt +173 -0
  108. package/android/src/test/java/com/margelo/nitro/queueplayer/cast/net/ShadowConnectivityManagerRecordingRequests.kt +27 -0
  109. package/app.plugin.js +17 -1
  110. package/ios/AVPlayerItemQueueItemId.swift +26 -12
  111. package/ios/AVQueueBuilder.swift +120 -85
  112. package/ios/ArtworkLoader.swift +0 -7
  113. package/ios/ArtworkResolver.swift +22 -4
  114. package/ios/AssetReplayGainReader.swift +118 -0
  115. package/ios/AudioSession.swift +67 -16
  116. package/ios/AudioTapProvider.swift +284 -149
  117. package/ios/CarPlayBridge.swift +0 -12
  118. package/ios/CarPlayCoordinator.swift +18 -2
  119. package/ios/CarPlaySceneDelegate.swift +2 -2
  120. package/ios/Cast/AirPlayRouteState.swift +9 -8
  121. package/ios/Cast/Core/CastEventBridge.swift +66 -26
  122. package/ios/Cast/Core/CastNowPlayingController.swift +38 -16
  123. package/ios/Cast/Core/CastSession.swift +2 -2
  124. package/ios/Cast/Core/CastTransportRouter.swift +21 -9
  125. package/ios/Cast/Core/LocalMediaServer.swift +41 -52
  126. package/ios/Cast/Core/LocalNetworkPermissionProbe.swift +32 -11
  127. package/ios/Cast/Core/MediaServerHandle.swift +0 -4
  128. package/ios/Cast/Core/MediaTokenRegistry.swift +0 -6
  129. package/ios/CastManager.swift +1 -1
  130. package/ios/CrossfadeEngine.swift +660 -226
  131. package/ios/EQTap.swift +20 -17
  132. package/ios/Equalizer.swift +1 -1
  133. package/ios/FLACStreamInfo.swift +65 -0
  134. package/ios/GaplessEngine.swift +57 -30
  135. package/ios/InputGuards.swift +35 -5
  136. package/ios/LookaheadCache.swift +202 -58
  137. package/ios/LookaheadCachePrefetcher.swift +48 -36
  138. package/ios/MediaServer/MediaHTTPConnection.swift +691 -0
  139. package/ios/NetworkRecoveryPolicy.swift +32 -0
  140. package/ios/NowPlayingFormatExtractor.swift +25 -18
  141. package/ios/NowPlayingInfo.swift +84 -48
  142. package/ios/OutputRouteMonitor.swift +16 -3
  143. package/ios/PendingBrowseRequests.swift +1 -1
  144. package/ios/PlaceholderArtwork.swift +9 -5
  145. package/ios/PlaybackEngine.swift +165 -76
  146. package/ios/PlaybackErrorMapping.swift +3 -3
  147. package/ios/PlaybackModeStateMachine.swift +2 -2
  148. package/ios/PlaybackNetworkMonitor.swift +73 -0
  149. package/ios/PlayerStateDerivation.swift +6 -6
  150. package/ios/QueueMutationArithmetic.swift +25 -0
  151. package/ios/QueueSkipArithmetic.swift +5 -3
  152. package/ios/QueueWindowArithmetic.swift +152 -0
  153. package/ios/ReadThroughServer.swift +283 -0
  154. package/ios/RemoteCommands.swift +44 -4
  155. package/ios/ReplayGainData.swift +22 -3
  156. package/ios/ReplayGainExtractor.swift +70 -27
  157. package/ios/Siri/VoiceDonation.swift +14 -9
  158. package/ios/StreamingBitrateProbe.swift +22 -68
  159. package/ios/SuppliedReplayGain.swift +83 -0
  160. package/ios/Tests/AVQueueBuilderTests.swift +158 -221
  161. package/ios/Tests/ActiveItemEngineStub.swift +44 -0
  162. package/ios/Tests/AssetReplayGainReaderTests.swift +228 -0
  163. package/ios/Tests/AudioTapProviderDispatchTargetTests.swift +158 -0
  164. package/ios/Tests/AudioTapProviderReplayGainTests.swift +122 -10
  165. package/ios/Tests/BitrateReresolveBudgetTests.swift +71 -0
  166. package/ios/Tests/CastMediaItemTranslationTests.swift +72 -0
  167. package/ios/Tests/CastNowPlayingControllerTests.swift +1 -1
  168. package/ios/Tests/CrossfadeAdvanceWithoutFadeTests.swift +120 -0
  169. package/ios/Tests/CrossfadeEngineIncomingMixTests.swift +122 -0
  170. package/ios/Tests/CrossfadeEngineTests.swift +164 -0
  171. package/ios/Tests/CrossfadeMixReapplyTests.swift +66 -0
  172. package/ios/Tests/CrossfadePlaybackIntentTests.swift +81 -0
  173. package/ios/Tests/EngineSwapTests.swift +91 -0
  174. package/ios/Tests/EqualizerAudioMixProviderTests.swift +46 -0
  175. package/ios/Tests/EqualizerHybridTests.swift +5 -2
  176. package/ios/Tests/FLACStreamInfoTests.swift +88 -0
  177. package/ios/Tests/GaplessEngineLifecycleTests.swift +4 -4
  178. package/ios/Tests/InputGuardsTests.swift +41 -0
  179. package/ios/Tests/InterruptionIntentTests.swift +77 -0
  180. package/ios/Tests/LookaheadCacheCellularAccessTests.swift +185 -0
  181. package/ios/Tests/LookaheadCachePrefetcherTests.swift +53 -30
  182. package/ios/Tests/LookaheadCacheRuntimeConfigTests.swift +160 -1
  183. package/ios/Tests/LookaheadCacheTests.swift +44 -18
  184. package/ios/Tests/MediaHTTPConnectionTests.swift +79 -0
  185. package/ios/Tests/MutationDeferralTests.swift +220 -0
  186. package/ios/Tests/NowPlayingInfoTests.swift +86 -99
  187. package/ios/Tests/NowPlayingSnapshotTests.swift +127 -0
  188. package/ios/Tests/OriginRestartStitcherTests.swift +60 -0
  189. package/ios/Tests/PlaybackNetworkMonitorTests.swift +95 -0
  190. package/ios/Tests/PlayerFixtures.swift +48 -0
  191. package/ios/Tests/PlayerStateDerivationTests.swift +10 -10
  192. package/ios/Tests/QueueMutationGenerationTests.swift +141 -0
  193. package/ios/Tests/QueueRebuildPrefixTests.swift +317 -0
  194. package/ios/Tests/QueueStateTests.swift +3 -1
  195. package/ios/Tests/QueueWindowArithmeticTests.swift +56 -0
  196. package/ios/Tests/QueueWindowSliceTests.swift +162 -0
  197. package/ios/Tests/ReadThroughRoutingLifecycleTests.swift +63 -0
  198. package/ios/Tests/ReadThroughServerTests.swift +345 -0
  199. package/ios/Tests/RemoteCommandsTests.swift +28 -0
  200. package/ios/Tests/ReplayGainExtractorTests.swift +216 -6
  201. package/ios/Tests/ReplayGainMergeTests.swift +230 -0
  202. package/ios/Tests/RetryRecoveryTests.swift +303 -0
  203. package/ios/Tests/SkipCapabilityTests.swift +233 -8
  204. package/ios/Tests/SkipIndexTests.swift +32 -2
  205. package/ios/Tests/SleepTimerPauseIntentTests.swift +43 -0
  206. package/ios/Tests/StallRecoveryTests.swift +97 -0
  207. package/ios/Tests/StreamingBitrateProbeTests.swift +17 -20
  208. package/ios/Tests/TopUpWindowGateTests.swift +394 -0
  209. package/ios/Tests/TrackPlayer+TestHops.swift +14 -0
  210. package/ios/Tests/TrackPlayerCallOrderTests.swift +93 -0
  211. package/ios/Tests/TrackPlayerConfigureTeardownTests.swift +86 -0
  212. package/ios/Tests/TrackPlayerEndVerdictTests.swift +165 -0
  213. package/ios/Tests/TrackPlayerSeekTests.swift +137 -0
  214. package/ios/Tests/TrackPlayerThreadingTests.swift +127 -0
  215. package/ios/Tests/TrackSourceClassifierTests.swift +33 -7
  216. package/ios/Tests/VoiceVocabularyOptInTests.swift +38 -0
  217. package/ios/TrackPlayer+Automotive.swift +101 -0
  218. package/ios/TrackPlayer+Cache.swift +232 -0
  219. package/ios/TrackPlayer+Config.swift +291 -0
  220. package/ios/TrackPlayer+EngineDelegate.swift +209 -0
  221. package/ios/TrackPlayer+EventsAPI.swift +111 -0
  222. package/ios/TrackPlayer+EventsDispatch.swift +968 -0
  223. package/ios/TrackPlayer+EventsWiring.swift +173 -0
  224. package/ios/TrackPlayer+Lifecycle.swift +930 -0
  225. package/ios/TrackPlayer+NowPlayingFormat.swift +258 -0
  226. package/ios/TrackPlayer+Queue.swift +940 -0
  227. package/ios/TrackPlayer+Recovery.swift +196 -0
  228. package/ios/TrackPlayer+Skip.swift +455 -0
  229. package/ios/TrackPlayer+SleepTimer.swift +175 -0
  230. package/ios/TrackPlayer+State.swift +108 -0
  231. package/ios/TrackPlayer+Threading.swift +135 -0
  232. package/ios/TrackPlayer+Transport.swift +267 -0
  233. package/ios/TrackPlayer+Window.swift +195 -0
  234. package/ios/TrackPlayer.swift +305 -4619
  235. package/ios/Visualizer.swift +6 -5
  236. package/ios/tests-harness/Podfile +1 -1
  237. package/ios/tests-harness/TestHost.xcodeproj/project.pbxproj +19 -11
  238. package/ios/tests-harness/scripts/seed-xcodeproj.rb +2 -2
  239. package/lib/module/hooks/useActiveTrack.js +29 -22
  240. package/lib/module/hooks/useActiveTrack.js.map +1 -1
  241. package/lib/module/hooks/useCast.js +8 -26
  242. package/lib/module/hooks/useCast.js.map +1 -1
  243. package/lib/module/hooks/useEqualizer.js +19 -12
  244. package/lib/module/hooks/useEqualizer.js.map +1 -1
  245. package/lib/module/hooks/useLookaheadCache.js +3 -7
  246. package/lib/module/hooks/useLookaheadCache.js.map +1 -1
  247. package/lib/module/hooks/useQueue.js +66 -19
  248. package/lib/module/hooks/useQueue.js.map +1 -1
  249. package/lib/module/index.js +6 -3
  250. package/lib/module/index.js.map +1 -1
  251. package/lib/module/queueDelta.js +41 -0
  252. package/lib/module/queueDelta.js.map +1 -0
  253. package/lib/module/types.js +28 -5
  254. package/lib/module/types.js.map +1 -1
  255. package/lib/typescript/TrackPlayer.nitro.d.ts +53 -28
  256. package/lib/typescript/TrackPlayer.nitro.d.ts.map +1 -1
  257. package/lib/typescript/hooks/useActiveTrack.d.ts +5 -7
  258. package/lib/typescript/hooks/useActiveTrack.d.ts.map +1 -1
  259. package/lib/typescript/hooks/useCast.d.ts +6 -1
  260. package/lib/typescript/hooks/useCast.d.ts.map +1 -1
  261. package/lib/typescript/hooks/useEqualizer.d.ts +2 -1
  262. package/lib/typescript/hooks/useEqualizer.d.ts.map +1 -1
  263. package/lib/typescript/hooks/useLookaheadCache.d.ts.map +1 -1
  264. package/lib/typescript/hooks/useQueue.d.ts +4 -4
  265. package/lib/typescript/hooks/useQueue.d.ts.map +1 -1
  266. package/lib/typescript/index.d.ts +2 -1
  267. package/lib/typescript/index.d.ts.map +1 -1
  268. package/lib/typescript/queueDelta.d.ts +10 -0
  269. package/lib/typescript/queueDelta.d.ts.map +1 -0
  270. package/lib/typescript/types.d.ts +168 -12
  271. package/lib/typescript/types.d.ts.map +1 -1
  272. package/nitrogen/generated/android/c++/JFunc_void_QueueChangeDelta_double_QueueChangeReason.hpp +85 -0
  273. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.cpp +45 -19
  274. package/nitrogen/generated/android/c++/JHybridTrackPlayerSpec.hpp +3 -3
  275. package/nitrogen/generated/android/c++/JLookaheadCacheConfig.hpp +8 -4
  276. package/nitrogen/generated/android/c++/JPlayerConfig.hpp +5 -1
  277. package/nitrogen/generated/android/c++/JQueueChangeDelta.hpp +128 -0
  278. package/nitrogen/generated/android/c++/JTrackItem.hpp +19 -3
  279. 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
  280. 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
  281. 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
  282. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void.kt +0 -2
  283. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_AudioRoute.kt +0 -2
  284. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_BufferState.kt +0 -2
  285. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CacheStatus.kt +0 -2
  286. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastDiscoveryState.kt +0 -2
  287. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastLocalNetworkPermissionEvent.kt +0 -2
  288. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastRoute.kt +0 -2
  289. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_CastSessionDiedEvent.kt +0 -2
  290. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlaybackError.kt +0 -2
  291. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerProgress.kt +0 -2
  292. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_PlayerState_StateChangeReason.kt +0 -2
  293. 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
  294. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_ServiceReadyReason.kt +0 -2
  295. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SkipCapability.kt +0 -2
  296. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_SleepTimerState.kt +0 -2
  297. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerErrorReason.kt +0 -2
  298. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_VisualizerFrame.kt +0 -2
  299. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_bool.kt +0 -2
  300. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_double_double.kt +0 -2
  301. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_TrackItem__double_TrackChangeReason_std__optional_double_.kt +0 -2
  302. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__optional_std__variant_nitro__NullType__NowPlayingFormat__.kt +0 -2
  303. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_CastReceiver_.kt +0 -2
  304. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/Func_void_std__vector_EqualizerBand_.kt +0 -2
  305. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridCastManagerSpec.kt +2 -0
  306. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridEqualizerSpec.kt +2 -0
  307. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridTrackPlayerSpec.kt +6 -4
  308. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/HybridVisualizerSpec.kt +2 -0
  309. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/LookaheadCacheConfig.kt +9 -4
  310. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/PlayerConfig.kt +7 -2
  311. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/QueueChangeDelta.kt +86 -0
  312. package/nitrogen/generated/android/kotlin/com/margelo/nitro/queueplayer/TrackItem.kt +24 -4
  313. package/nitrogen/generated/android/queueplayerOnLoad.cpp +2 -2
  314. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.cpp +16 -16
  315. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Bridge.hpp +80 -65
  316. package/nitrogen/generated/ios/QueuePlayer-Swift-Cxx-Umbrella.hpp +3 -0
  317. package/nitrogen/generated/ios/c++/HybridTrackPlayerSpecSwift.hpp +6 -3
  318. package/nitrogen/generated/ios/swift/Func_void_CacheStatus.swift +5 -5
  319. package/nitrogen/generated/ios/swift/Func_void_QueueChangeDelta_double_QueueChangeReason.swift +46 -0
  320. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec.swift +3 -3
  321. package/nitrogen/generated/ios/swift/HybridTrackPlayerSpec_cxx.swift +32 -24
  322. package/nitrogen/generated/ios/swift/LookaheadCacheConfig.swift +20 -2
  323. package/nitrogen/generated/ios/swift/PlayerConfig.swift +19 -1
  324. package/nitrogen/generated/ios/swift/QueueChangeDelta.swift +82 -0
  325. package/nitrogen/generated/ios/swift/TrackItem.swift +73 -1
  326. package/nitrogen/generated/shared/c++/HybridTrackPlayerSpec.hpp +6 -3
  327. package/nitrogen/generated/shared/c++/LookaheadCacheConfig.hpp +7 -3
  328. package/nitrogen/generated/shared/c++/PlayerConfig.hpp +5 -1
  329. package/nitrogen/generated/shared/c++/QueueChangeDelta.hpp +113 -0
  330. package/nitrogen/generated/shared/c++/TrackItem.hpp +18 -2
  331. package/package.json +8 -8
  332. package/src/TrackPlayer.nitro.ts +53 -27
  333. package/src/hooks/useActiveTrack.ts +29 -22
  334. package/src/hooks/useCast.ts +14 -15
  335. package/src/hooks/useEqualizer.ts +19 -12
  336. package/src/hooks/useLookaheadCache.ts +3 -7
  337. package/src/hooks/useQueue.ts +66 -17
  338. package/src/index.ts +13 -3
  339. package/src/queueDelta.ts +41 -0
  340. package/src/types.ts +169 -12
  341. package/android/src/main/java/com/margelo/nitro/queueplayer/PendingIntentBuffer.kt +0 -82
  342. package/android/src/main/java/com/margelo/nitro/queueplayer/cast/http/LocalMediaServerLifecycle.kt +0 -15
  343. package/android/src/test/java/com/margelo/nitro/queueplayer/PendingIntentBufferTest.kt +0 -192
  344. package/android/src/test/java/com/margelo/nitro/queueplayer/PlaybackEngineInterfaceTest.kt +0 -135
  345. package/android/src/test/java/com/margelo/nitro/queueplayer/SmokeTest.kt +0 -27
  346. package/ios/Cast/Core/LocalAddressMonitor.swift +0 -110
  347. package/ios/Cast/Core/MediaHTTPConnection.swift +0 -349
  348. package/ios/MetadataReader.swift +0 -555
  349. package/ios/Tests/CrossfadeEngineStubTests.swift +0 -43
  350. package/ios/Tests/MetadataReaderTests.swift +0 -603
  351. package/ios/Tests/PlaybackEngineProtocolTests.swift +0 -92
  352. package/ios/Tests/VoiceDonationTests.swift +0 -26
  353. package/nitrogen/generated/android/c++/JFunc_void_std__vector_TrackItem__double_QueueChangeReason.hpp +0 -101
  354. package/nitrogen/generated/ios/swift/Func_void_std__vector_TrackItem__double_QueueChangeReason.swift +0 -46
  355. /package/ios/{Cast/Core → MediaServer}/MimeTypes.swift +0 -0
@@ -4,11 +4,12 @@ package com.margelo.nitro.queueplayer
4
4
  * Pure-Kotlin skip-index arithmetic. Mirrors the iOS sibling
5
5
  * `ios/QueueSkipArithmetic.swift` 1-for-1.
6
6
  *
7
- * Extracted from [TrackPlayer] so the logic stands alone without
8
- * instantiating an ExoPlayer. [TrackPlayer.computeNextIndex] /
9
- * [TrackPlayer.computePreviousIndex] are thin adapters that translate
10
- * the Nitrogen-generated [RepeatMode] enum into [QueueSkipRepeatMode]
11
- * and delegate.
7
+ * Kept separate from [TrackPlayer] so the logic runs without an
8
+ * ExoPlayer. [TrackPlayer.computeNextIndex] is the thin adapter that
9
+ * translates the Nitrogen-generated [RepeatMode] enum into
10
+ * [QueueSkipRepeatMode] and delegates; the skip-previous path goes
11
+ * through [QueueSkipArithmetic.resolveSkipToPrevious], which wraps
12
+ * [QueueSkipArithmetic.computePrevious].
12
13
  *
13
14
  * Value order (0/1/2) is held fixed across the iOS enum and this one
14
15
  * so a test that asserts the raw integer catches any accidental
@@ -102,7 +103,9 @@ internal object QueueSkipArithmetic {
102
103
  * When [restartEnabled] is true (`PlayerConfig.skipToPreviousBehavior
103
104
  * == RESTART_OR_PREVIOUS`), Previous is enabled whenever a track is
104
105
  * loaded — even at index 0, where past the threshold it restarts the
105
- * current track — so the control never greys out.
106
+ * current track — so the control never greys out while something is
107
+ * playing. With no loaded track (`currentIndex < 0`) there is nothing
108
+ * to restart, matching what resolveSkipToPrevious does for that state.
106
109
  */
107
110
  fun canSkipPrevious(
108
111
  trackCount: Int,
@@ -110,7 +113,7 @@ internal object QueueSkipArithmetic {
110
113
  repeatMode: QueueSkipRepeatMode,
111
114
  restartEnabled: Boolean = false
112
115
  ): Boolean =
113
- if (restartEnabled) trackCount > 0
116
+ if (restartEnabled) trackCount > 0 && currentIndex >= 0
114
117
  else computePrevious(trackCount, currentIndex, repeatMode) != null
115
118
 
116
119
  /**
@@ -126,7 +129,7 @@ internal object QueueSkipArithmetic {
126
129
  data class Previous(val index: Int) : SkipToPreviousAction
127
130
  /** Restart the current track (seek to 0) — not a track change. */
128
131
  object RestartCurrent : SkipToPreviousAction
129
- /** Nothing to do (empty queue). */
132
+ /** Nothing to do (empty queue, or no loaded track). */
130
133
  object NoOp : SkipToPreviousAction
131
134
  }
132
135
 
@@ -12,8 +12,20 @@ import kotlin.math.roundToInt
12
12
  * gain to a PCM stream. No knowledge of the engine, the EQ stage, or the
13
13
  * visualizer pipeline — purely "given a PCM buffer, multiply by
14
14
  * [linearGain]". The owning engine pushes a new gain via [setLinearGain]
15
- * from its `Player.Listener` callbacks; `onMediaItemTransition` resets to
16
- * unity until the next track's tags arrive.
15
+ * from its `Player.Listener` callbacks and from the ReplayGain-mode
16
+ * setter; `onMediaItemTransition` resets to unity until the next track's
17
+ * tags arrive.
18
+ *
19
+ * It is **always active** on a supported PCM encoding once configured, NOT
20
+ * gated on the current gain. Media3's
21
+ * [androidx.media3.common.audio.AudioProcessingPipeline] fixes the
22
+ * active-processor set in `configure()` / `flush()` and never re-evaluates
23
+ * it while a stream plays, so gating `isActive` on the (later-pushed) gain
24
+ * would exclude this processor from the pipeline for any queue loaded at
25
+ * unity and no gain could ever reach the audio afterwards. Staying active
26
+ * and passing the block straight through at unity keeps it present and
27
+ * ready, at the cost of one bulk buffer copy per block when no gain is
28
+ * applied.
17
29
  *
18
30
  * Handles both 16-bit and float PCM. The sink runs at 16-bit (see
19
31
  * [AudioPipelineRenderersFactory] — float output would drop the whole
@@ -30,7 +42,7 @@ internal class ReplayGainAudioProcessor : BaseAudioProcessor() {
30
42
  * it on every [queueInput] while the engine's main thread writes it
31
43
  * from the metadata listener. The value is `pow(10, gainDb / 20)`
32
44
  * already clip-capped against the track peak (caller's job); a
33
- * value of `1.0f` means RG is inactive for this track.
45
+ * value of `1.0f` passes the stream through untouched.
34
46
  */
35
47
  @Volatile
36
48
  private var linearGain: Float = 1.0f
@@ -43,8 +55,8 @@ internal class ReplayGainAudioProcessor : BaseAudioProcessor() {
43
55
  private var encoding: Int = C.ENCODING_INVALID
44
56
 
45
57
  /**
46
- * Push a new linear gain. `1.0f` disables the processor's per-sample
47
- * multiply via [isActive].
58
+ * Push a new linear gain, observed from the next [queueInput] onwards.
59
+ * `1.0f` skips the per-sample multiply and passes the block through.
48
60
  */
49
61
  fun setLinearGain(value: Float) {
50
62
  linearGain = value
@@ -60,7 +72,7 @@ internal class ReplayGainAudioProcessor : BaseAudioProcessor() {
60
72
  }
61
73
 
62
74
  override fun isActive(): Boolean =
63
- super.isActive() && linearGain != 1.0f &&
75
+ super.isActive() &&
64
76
  (encoding == C.ENCODING_PCM_FLOAT || encoding == C.ENCODING_PCM_16BIT)
65
77
 
66
78
  override fun queueInput(input: ByteBuffer) {
@@ -68,6 +80,14 @@ internal class ReplayGainAudioProcessor : BaseAudioProcessor() {
68
80
  val byteCount = input.remaining()
69
81
  if (byteCount == 0) return
70
82
  val out = replaceOutputBuffer(byteCount)
83
+ if (gain == 1.0f) {
84
+ // Unity — present in the pipeline but applying nothing. A bulk copy
85
+ // is ~25x cheaper per block than walking the samples for a multiply
86
+ // by one, and both consume the input and produce identical bytes.
87
+ out.put(input)
88
+ out.flip()
89
+ return
90
+ }
71
91
  if (encoding == C.ENCODING_PCM_FLOAT) {
72
92
  val inF = input.asFloatBuffer()
73
93
  val outF = out.asFloatBuffer()
@@ -31,9 +31,10 @@ data class ReplayGainData(
31
31
  * both may be null when the album tag is absent. No fallback
32
32
  * to track gain.
33
33
  *
34
- * Caller computes `linearGain = 10^(gainDb / 20)` and clip-caps via
35
- * peak when both are non-null. When `gainDb` is null the caller
36
- * treats RG as inactive for this track (`linearGain = 1.0f`,
34
+ * Caller computes `linearGain = 10^(gainDb / 20)` and clip-caps it
35
+ * at `1 / peak` when the peak is usable, at unity when it is not
36
+ * see [computeLinearGain]. When `gainDb` is null the caller treats
37
+ * RG as inactive for this track (`linearGain = 1.0f`,
37
38
  * `replayGainActive = false`).
38
39
  */
39
40
  fun selectGain(mode: ReplayGainMode): Pair<Float?, Float?> = when (mode) {
@@ -41,4 +42,22 @@ data class ReplayGainData(
41
42
  ReplayGainMode.TRACK -> trackGainDb to trackPeak
42
43
  ReplayGainMode.ALBUM -> albumGainDb to albumPeak
43
44
  }
45
+
46
+ companion object {
47
+ /**
48
+ * Resolve the ReplayGain a track plays with, all-or-nothing per
49
+ * item. [suppliedClaimed] is `true` when the consumer put any of
50
+ * the four fields on the `TrackItem`, computed before any value
51
+ * is validated: a claim discards detection outright, including
52
+ * for the mode the consumer never mentioned.
53
+ * `suppliedClaimed && supplied == null` is a real state — the
54
+ * consumer owns the track and gave nothing usable, so no gain is
55
+ * applied and the file's own tags stay unused.
56
+ */
57
+ internal fun merge(
58
+ suppliedClaimed: Boolean,
59
+ supplied: ReplayGainData?,
60
+ detected: ReplayGainData?,
61
+ ): ReplayGainData? = if (suppliedClaimed) supplied else detected
62
+ }
44
63
  }
@@ -159,7 +159,9 @@ internal object ReplayGainExtractor {
159
159
  /**
160
160
  * Parse a `REPLAYGAIN_*_GAIN` string (e.g. `"-6.54 dB"`,
161
161
  * `"+3.21"`, `"−1.0 dB"` with U+2212 minus) into a Float dB.
162
- * Returns `null` for unparseable / NaN / Inf inputs.
162
+ * Returns `null` for unparseable input; the parsed number is then
163
+ * put through [acceptGainDb], so text handling is all this adds
164
+ * over the numeric entry point.
163
165
  */
164
166
  internal fun parseGainDb(rawInput: String): Float? {
165
167
  var trimmed = rawInput.trim()
@@ -184,25 +186,49 @@ internal object ReplayGainExtractor {
184
186
  // Reject comma decimals (RG2 spec — period only).
185
187
  if (trimmed.contains(',')) return null
186
188
 
187
- val value = trimmed.toFloatOrNull() ?: return null
189
+ val value = trimmed.toDoubleOrNull() ?: return null
190
+ return acceptGainDb(value)
191
+ }
192
+
193
+ /**
194
+ * Shared numeric acceptance for a peak value, whatever entry point
195
+ * it arrived through — a tag string or a consumer-supplied number.
196
+ * Accepts `0.001 ... 2.0` inclusive (`1.0` is digital full scale and
197
+ * a legitimate measurement); rejects anything outside that band and
198
+ * anything non-finite. The band is checked on the `Double`, before the
199
+ * narrowing to `Float`, so a value just past it cannot round in.
200
+ */
201
+ internal fun acceptPeak(value: Double): Float? {
202
+ if (value.isNaN() || value.isInfinite()) return null
203
+ // Band-check the Double: 2.0000001 narrows to 2.0f, so a check
204
+ // applied after the narrowing would let it through.
205
+ if (value < 0.001 || value > 2.0) return null
206
+ return value.toFloat()
207
+ }
208
+
209
+ /**
210
+ * Shared numeric acceptance for a gain in dB, whatever entry point
211
+ * it arrived through. There is no plausibility band on gain — only
212
+ * non-finite values are rejected, including a magnitude that
213
+ * narrows to an infinite [Float].
214
+ */
215
+ internal fun acceptGainDb(value: Double): Float? {
188
216
  if (value.isNaN() || value.isInfinite()) return null
189
- return value
217
+ val narrowed = value.toFloat()
218
+ if (narrowed.isInfinite()) return null
219
+ return narrowed
190
220
  }
191
221
 
192
222
  /**
193
223
  * Parse a `REPLAYGAIN_*_PEAK` string (linear, period decimal).
194
- * Returns `null` for unparseable / NaN / Inf / out-of-range
195
- * values, or for the encoder-default sentinels (peak `< 0.001`
196
- * or peak exactly `1.0`, both signal the analyser didn't run).
224
+ * Returns `null` for unparseable input; the parsed number is then
225
+ * put through [acceptPeak], which holds the `0.001 ... 2.0`
226
+ * acceptance band a full-scale `1.0` included.
197
227
  */
198
228
  internal fun parsePeak(rawInput: String): Float? {
199
229
  val trimmed = rawInput.trim()
200
- val value = trimmed.toFloatOrNull() ?: return null
201
- if (value.isNaN() || value.isInfinite()) return null
202
- if (value < 0f || value > 2.0f) return null
203
- if (value < 0.001f) return null
204
- if (value == 1.0f) return null
205
- return value
230
+ val value = trimmed.toDoubleOrNull() ?: return null
231
+ return acceptPeak(value)
206
232
  }
207
233
 
208
234
  // endregion
@@ -5,30 +5,34 @@ import kotlin.math.pow
5
5
 
6
6
  /**
7
7
  * Pure gain math per NOTES.md §25. Selects the `(gainDb, peak)` pair
8
- * from [data] under [mode] (strict, no fallback), then:
8
+ * from [data] under [mode] (strict, no fallback), then caps the result
9
+ * for clip safety:
9
10
  *
10
11
  * ```
11
- * linearGain = pow(10, gainDb / 20)
12
- * if peak != null and peak > 0.001 and peak != 1.0:
13
- * linearGain = min(linearGain, 1.0 / peak)
12
+ * cap = usable peak ? 1.0 / peak : 1.0
13
+ * linearGain = min(pow(10, gainDb / 20), cap)
14
14
  * ```
15
15
  *
16
+ * A peak is usable inside the `0.001 .. 2.0` band that
17
+ * [ReplayGainExtractor.acceptPeak] holds — full scale (`1.0`) and true
18
+ * peaks above it included. With no usable peak the cap sits at unity,
19
+ * so a boost is suppressed rather than applied unprotected: a peak
20
+ * cannot be derived from a gain. Attenuation is untouched either way,
21
+ * since a negative gain can never clip. The band is re-checked here so
22
+ * the math stays correct for any caller that bypasses the extractor.
23
+ *
16
24
  * Returns `1.0f` (unity) whenever the selected mode's gain tag is
17
25
  * absent — RG is inactive for that track. Mirrors the iOS
18
26
  * `AudioTapProvider.computeLinearGain(data:mode:)` contract so a tag
19
27
  * fixture parses to the same linear gain on both platforms.
20
- *
21
- * Encoder-default sentinels (`peak == 1.0` and `peak < 0.001`) bypass
22
- * the peak-based clip cap. The extractor returns `null` for both
23
- * already, but defending here keeps the math correct against any
24
- * future caller that bypasses the extractor.
25
28
  */
26
29
  internal fun computeLinearGain(data: ReplayGainData?, mode: ReplayGainMode): Float {
27
30
  val (gainDb, peak) = data?.selectGain(mode) ?: (null to null)
28
31
  if (gainDb == null) return 1.0f
29
- var raw = 10.0.pow(gainDb.toDouble() / 20.0).toFloat()
30
- if (peak != null && peak > 0.001f && peak != 1.0f) {
31
- raw = min(raw, 1.0f / peak)
32
+ val raw = 10.0.pow(gainDb.toDouble() / 20.0).toFloat()
33
+ var cap = 1.0f
34
+ if (peak != null && peak >= 0.001f && peak <= 2.0f) {
35
+ cap = 1.0f / peak
32
36
  }
33
- return raw
37
+ return min(raw, cap)
34
38
  }
@@ -0,0 +1,66 @@
1
+ package com.margelo.nitro.queueplayer
2
+
3
+ import androidx.media3.common.util.UnstableApi
4
+
5
+ /**
6
+ * The consumer's ReplayGain claim over one queue entry, resolved from
7
+ * the `TrackItem` the engine is playing.
8
+ *
9
+ * The claim and the values it survived with are two separate facts, and
10
+ * keeping them separate is the whole point of this type. A `TrackItem`
11
+ * that supplies only an out-of-band peak has claimed its track just as
12
+ * firmly as one that supplies all four values, so collapsing the claim
13
+ * into "did any value survive acceptance" would hand that track back to
14
+ * the file's own tags. Presence claims the track; validity only decides
15
+ * what there is to apply.
16
+ *
17
+ * An instance therefore exists whenever any of the four fields was
18
+ * present, and [data] is `null` when none of those values survived
19
+ * [ReplayGainExtractor]'s acceptance rules — a track that owns its
20
+ * ReplayGain and plays at unity.
21
+ *
22
+ * Mirrors the iOS `SuppliedReplayGain`, which rides on the
23
+ * `AVPlayerItem` rather than being resolved per read; the shapes match
24
+ * so one contract covers both platforms.
25
+ */
26
+ @UnstableApi
27
+ internal class SuppliedReplayGain private constructor(val data: ReplayGainData?) {
28
+ companion object {
29
+ /**
30
+ * Build the claim for [track], or `null` when the consumer supplied
31
+ * none of the four ReplayGain fields and the track is left on
32
+ * whatever its file carries.
33
+ *
34
+ * Each present value goes through the same
35
+ * [ReplayGainExtractor.acceptGainDb] / [ReplayGainExtractor.acceptPeak]
36
+ * pair the tag strings land on, so a supplied number and a tagged
37
+ * number are held to one rule.
38
+ */
39
+ fun from(track: TrackItem): SuppliedReplayGain? {
40
+ if (track.replayGainTrackGain == null &&
41
+ track.replayGainTrackPeak == null &&
42
+ track.replayGainAlbumGain == null &&
43
+ track.replayGainAlbumPeak == null
44
+ ) {
45
+ return null
46
+ }
47
+ val trackGain = track.replayGainTrackGain?.let { ReplayGainExtractor.acceptGainDb(it) }
48
+ val trackPeak = track.replayGainTrackPeak?.let { ReplayGainExtractor.acceptPeak(it) }
49
+ val albumGain = track.replayGainAlbumGain?.let { ReplayGainExtractor.acceptGainDb(it) }
50
+ val albumPeak = track.replayGainAlbumPeak?.let { ReplayGainExtractor.acceptPeak(it) }
51
+ if (trackGain == null && trackPeak == null &&
52
+ albumGain == null && albumPeak == null
53
+ ) {
54
+ return SuppliedReplayGain(null)
55
+ }
56
+ return SuppliedReplayGain(
57
+ ReplayGainData(
58
+ trackGainDb = trackGain,
59
+ trackPeak = trackPeak,
60
+ albumGainDb = albumGain,
61
+ albumPeak = albumPeak,
62
+ )
63
+ )
64
+ }
65
+ }
66
+ }