Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.anilbeesetti.nextplayer.feature.player.extensions

import androidx.media3.common.text.Cue

fun List<Cue>.stackUnpositionedCues(): List<Cue> {
if (size <= 1) return this

var nextLine = -1
return map { cue ->
val text = cue.text
if (cue.line != Cue.DIMEN_UNSET || text == null) {
cue
} else {
cue.buildUpon()
.setLine(nextLine.toFloat(), Cue.LINE_TYPE_NUMBER)
.build()
.also { nextLine -= text.count { it == '\n' } + 1 }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.media3.common.Player
import androidx.media3.common.listen
import androidx.media3.common.text.Cue
import androidx.media3.common.util.UnstableApi
import dev.anilbeesetti.nextplayer.feature.player.extensions.stackUnpositionedCues

@UnstableApi
@Composable
Expand All @@ -26,10 +27,10 @@ class CuesState(
private set

suspend fun observe() {
cues = player.currentCues.cues
cues = player.currentCues.cues.stackUnpositionedCues()
player.listen { events ->
if (events.contains(Player.EVENT_CUES)) {
cues = player.currentCues.cues
cues = player.currentCues.cues.stackUnpositionedCues()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package dev.anilbeesetti.nextplayer.feature.player.extensions

import androidx.media3.common.text.Cue
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Test

class CueExtensionsTest {

@Test
fun `empty cue list remains empty`() {
assertEquals(emptyList<Cue>(), emptyList<Cue>().stackUnpositionedCues())
}

@Test
fun `single unpositioned cue remains unchanged`() {
val cue = textCue("only cue")

val result = listOf(cue).stackUnpositionedCues()

assertSame(cue, result.single())
assertEquals(Cue.DIMEN_UNSET, result.single().line)
}

@Test
fun `two unpositioned cues are stacked from the bottom`() {
val result = listOf(textCue("first"), textCue("second")).stackUnpositionedCues()

assertEquals(-1f, result[0].line)
assertEquals(Cue.LINE_TYPE_NUMBER, result[0].lineType)
assertEquals(-2f, result[1].line)
assertEquals(Cue.LINE_TYPE_NUMBER, result[1].lineType)
}

@Test
fun `multiline lower cue offsets the cue above by its rendered line count`() {
val result = listOf(textCue("a\nb"), textCue("above")).stackUnpositionedCues()

assertEquals(-1f, result[0].line)
assertEquals(-3f, result[1].line)
}

@Test
fun `explicitly positioned cue remains unchanged while siblings are stacked`() {
val first = textCue("first")
val positioned = Cue.Builder()
.setText("positioned")
.setLine(0.25f, Cue.LINE_TYPE_FRACTION)
.build()
val second = textCue("second")

val result = listOf(first, positioned, second).stackUnpositionedCues()

assertEquals(-1f, result[0].line)
assertSame(positioned, result[1])
assertEquals(0.25f, result[1].line)
assertEquals(Cue.LINE_TYPE_FRACTION, result[1].lineType)
assertEquals(-2f, result[2].line)
}

@Test
fun `stacking preserves cue text identity`() {
val cue = textCue("styled")

val result = listOf(cue, textCue("second")).stackUnpositionedCues()

assertSame(cue.text, result[0].text)
}

private fun textCue(text: CharSequence): Cue = Cue.Builder().setText(text).build()
}
Loading