Skip to content

Commit 09db8fd

Browse files
committed
fix: align VOD audio windows to the playhead
1 parent 8ff8fee commit 09db8fd

3 files changed

Lines changed: 39 additions & 6 deletions

File tree

‎src/main/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilder.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi
2828
if (effectiveRequest.audioOnly) return buildAudioOnly(holder, effectiveRequest, live?.toResponse())
2929
val video = buildTrack(holder, holder.videoFormat, effectiveRequest, effectiveRequest.playerTimeMs, live?.active == true)
3030
val decodeStartMs = video.track.segments.firstOrNull()?.startMs ?: effectiveRequest.playerTimeMs
31+
val audioStartMs = if (live?.active == true) minOf(effectiveRequest.playerTimeMs, decodeStartMs) else effectiveRequest.playerTimeMs
3132
val audio = buildTrack(
3233
holder,
3334
holder.audioFormat,
3435
effectiveRequest,
35-
minOf(effectiveRequest.playerTimeMs, decodeStartMs),
36+
audioStartMs,
3637
live?.active == true,
3738
)
3839
val playbackStartMs = resolvedPlaybackStartMs(
@@ -191,7 +192,6 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi
191192
atEnd = atEnd,
192193
)
193194
}
194-
195195
private fun CachedSabrSegment.toWindowSegment(
196196
holder: SabrSessionHolder,
197197
format: YoutubeSabrFormat,

‎src/test/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilderTest.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class SabrPlaybackWindowBuilderTest {
3131
val streamState = mockk<YoutubeSabrStreamState>(relaxed = true)
3232
every { session.streamState } returns streamState
3333
every { streamState.getSegmentNumberAtOrAfterTimeMs(video, 491_203L) } returns 98
34-
every { streamState.getSegmentNumberAtOrAfterTimeMs(audio, 488_200L) } returns 49
34+
every { streamState.getSegmentNumberAtOrAfterTimeMs(audio, 491_203L) } returns 49
3535
every { session.getCachedSegment(any()) } answers {
3636
firstArg<SabrSegmentRequest>().takeIf { it.format.itag == 299 && it.sequenceNumber == 101 }
3737
?.let { mediaSegment(sequence = 101, startMs = 488_200L, durationMs = 6_500L) }

‎src/test/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindowTest.kt‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@ import org.schabi.newpipe.extractor.services.youtube.sabr.YoutubeSabrStreamState
2121
import java.time.Instant
2222

2323
class SabrProgressivePlaybackWindowTest {
24+
@Test
25+
fun `vod audio starts at the playhead rather than the preceding video keyframe`() = runTest {
26+
val audio = format(140, isAudio = true)
27+
val video = format(137, isAudio = false)
28+
val state = mockk<YoutubeSabrStreamState>(relaxed = true)
29+
every { state.getSegmentNumberAtOrAfterTimeMs(video, 100_000L) } returns 16
30+
every { state.getSegmentNumberAtOrAfterTimeMs(audio, 100_000L) } returns 11
31+
val session = mockk<YoutubeSabrSession>(relaxed = true)
32+
every { session.streamState } returns state
33+
every { session.getReadableSegment(match { it.format == video && it.sequenceNumber == 16 }) } returns
34+
readableSegment(video, sequence = 16, startMs = 95_000L, durationMs = 7_000L)
35+
every { session.getReadableSegment(match { it.format == audio && it.sequenceNumber == 11 }) } returns
36+
readableSegment(audio, sequence = 11, startMs = 99_845L, durationMs = 9_985L)
37+
every { session.getCachedSegment(any()) } returns null
38+
val holder = holder(session, audio, video)
39+
val store = mockk<SabrSessionStore>()
40+
coEvery { store.cachedSegment(holder, any()) } returns null
41+
42+
val result = SabrPlaybackWindowBuilder(store).build(
43+
holder,
44+
SabrPlaybackWindowRequest(0L, 100_000L, video.itag, audio.itag, bufferGoalMs = 1_000L),
45+
)
46+
47+
assertTrue(result.isReady)
48+
assertEquals(95_000L, requireNotNull(result.response.video).segments.single().startMs)
49+
assertEquals(99_845L, result.response.audio.segments.single().startMs)
50+
}
51+
2452
@Test
2553
fun `vod window exposes timeline segments before their payload completes`() = runTest {
2654
val audio = format(140, isAudio = true)
@@ -53,12 +81,17 @@ class SabrProgressivePlaybackWindowTest {
5381
assertEquals(9_985L, result.response.audio.segments.single().durationMs)
5482
}
5583

56-
private fun readableSegment(format: YoutubeSabrFormat, durationMs: Long): SabrMediaSegment {
84+
private fun readableSegment(
85+
format: YoutubeSabrFormat,
86+
sequence: Int = 1,
87+
startMs: Long = 0L,
88+
durationMs: Long,
89+
): SabrMediaSegment {
5790
val header = mockk<SabrMediaHeader>()
5891
every { header.itag } returns format.itag
5992
every { header.isInitSegment } returns false
60-
every { header.sequenceNumber } returns 1
61-
every { header.startMs } returns 0L
93+
every { header.sequenceNumber } returns sequence
94+
every { header.startMs } returns startMs
6295
every { header.durationMs } returns durationMs
6396
return mockk<SabrMediaSegment>().also {
6497
every { it.header } returns header

0 commit comments

Comments
 (0)