playback: apply the item's gain in one tested step

Review follow-ups.

- `ReplayGainController.applyItem` owns reading the gain off a
  `MediaItem`, so the transition, the controller and the extras read are
  exercised together rather than only in isolation.
- drop the leftover `setTrack(null)` on a missing active source: now that
  the transition sets the gain from the item, that line only raced it and
  could reset a playing item to unity.
- tests for a zero gain surviving the extras round trip (zero is a real
  gain, not an absent one), for the latest item's gain being the one left
  applied, and for a missing item applying unity.
This commit is contained in:
austinried
2026-10-16 13:07:07 +00:00
parent 5f5ad85dff
commit 7ce754f95a
4 changed files with 68 additions and 6 deletions
@@ -266,6 +266,10 @@ private fun Bundle.putGain(
private fun Bundle.gain(key: String): Float? = if (containsKey(key)) getFloat(key) else null
internal fun ReplayGainController.applyItem(mediaItem: MediaItem?) {
setTrack(mediaItem.replayGain())
}
internal fun queueMediaItem(
item: QueueItem,
local: String?,
@@ -140,7 +140,7 @@ class PlaybackService : MediaSessionService() {
reason: Int,
) {
observeCurrentSong(mediaItem?.mediaId)
replayGain.setTrack(mediaItem?.replayGain())
replayGain.applyItem(mediaItem)
}
},
)
@@ -187,11 +187,7 @@ class PlaybackService : MediaSessionService() {
session?.setMediaButtonPreferences(listOf(starButton(false)))
songJob =
scope.launch {
val sourceId = sourceRepository?.activeSourceIdOnce()
if (sourceId == null) {
replayGain.setTrack(null)
return@launch
}
val sourceId = sourceRepository?.activeSourceIdOnce() ?: return@launch
library.song(sourceId, songId).collect { song ->
if (currentSongId != songId) return@collect
starred = song?.starred != null
@@ -76,4 +76,17 @@ class MediaItemTest {
assertEquals(ReplayGain(), item.replayGain())
}
@Test
fun aZeroGainIsNotMistakenForNoGain() {
val item =
queueMediaItem(
song.copy(replayGain = ReplayGain(trackGain = 0f, trackPeak = 0.5f)),
local = null,
stream = "http://server/stream?s=s1",
transcode = false,
)
assertEquals(ReplayGain(trackGain = 0f, trackPeak = 0.5f), item.replayGain())
}
}
@@ -1,9 +1,12 @@
package com.subtracks.playback
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.prefs.ReplayGainMode
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ReplayGainControllerTest {
private val applied = mutableListOf<Float>()
private val controller = ReplayGainController { applied += it }
@@ -43,6 +46,52 @@ class ReplayGainControllerTest {
assertEquals(1f, applied.last(), TOLERANCE)
}
@Test
fun theLatestItemLeavesItsGainApplied() {
controller.setSettings(ReplayGainSettings(ReplayGainMode.Track, 0f))
controller.applyItem(item("a", trackGain = 6.0206f))
controller.applyItem(item("b", trackGain = -6.0206f))
assertEquals(0.5f, applied.last(), TOLERANCE)
}
@Test
fun anItemWithoutGainPlaysAtUnity() {
controller.setSettings(ReplayGainSettings(ReplayGainMode.Track, 0f))
controller.applyItem(item("a", trackGain = null))
assertEquals(1f, applied.last(), TOLERANCE)
}
@Test
fun noItemPlaysAtUnity() {
controller.setSettings(ReplayGainSettings(ReplayGainMode.Track, 0f))
controller.applyItem(item("a", trackGain = 6.0206f))
controller.applyItem(null)
assertEquals(1f, applied.last(), TOLERANCE)
}
private fun item(
id: String,
trackGain: Float?,
) = queueMediaItem(
QueueItem(
id = id,
title = id,
artist = null,
album = null,
coverArtId = null,
replayGain = ReplayGain(trackGain = trackGain),
),
local = null,
stream = "http://server/$id",
transcode = false,
)
private companion object {
const val TOLERANCE = 0.001f
}