Skip to content

Commit 78aad68

Browse files
committed
fix: fill progressive SABR seek windows
1 parent 744d26d commit 78aad68

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import dev.typetype.server.services.resolvePlaybackStartMs
1515
import org.schabi.newpipe.extractor.services.youtube.sabr.SabrSegmentRequest
1616
import org.schabi.newpipe.extractor.services.youtube.sabr.YoutubeSabrFormat
1717

18+
private const val MAX_SEGMENTS_PER_TRACK = 12
19+
1820
internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessionStore) {
1921
suspend fun build(
2022
holder: SabrSessionHolder,
@@ -140,6 +142,7 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi
140142
seq = progressive.nextSequence
141143
coveredEndMs = progressive.coveredEndMs
142144
if (coveredEndMs >= goalEndMs) break
145+
if (progressive.hasReadableMedia) continue
143146
blockedRequest = SabrSegmentRequest.media(format, seq)
144147
blockedBy = "${format.trackName()}:${format.itag}:$seq pending"
145148
break
@@ -214,7 +217,4 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi
214217
) {
215218
fun covers(requiredEndMs: Long): Boolean = (track.segments.isNotEmpty() || atEnd) && coveredEndMs >= requiredEndMs
216219
}
217-
private companion object {
218-
const val MAX_SEGMENTS_PER_TRACK = 12
219-
}
220220
}

src/main/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindow.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ import org.schabi.newpipe.extractor.services.youtube.sabr.YoutubeSabrFormat
1111
internal data class SabrProgressiveWindowSegment(
1212
val sequence: Int,
1313
val response: SabrPlaybackWindowSegment,
14+
val hasReadableMedia: Boolean,
1415
)
1516

16-
internal data class SabrProgressiveWindowAppend(val nextSequence: Int, val coveredEndMs: Long)
17+
internal data class SabrProgressiveWindowAppend(
18+
val nextSequence: Int,
19+
val coveredEndMs: Long,
20+
val hasReadableMedia: Boolean,
21+
)
1722

1823
internal fun MutableList<SabrPlaybackWindowSegment>.appendProgressiveWindowSegment(
1924
holder: SabrSessionHolder,
@@ -27,6 +32,7 @@ internal fun MutableList<SabrPlaybackWindowSegment>.appendProgressiveWindowSegme
2732
return SabrProgressiveWindowAppend(
2833
segment.sequence + 1,
2934
segment.response.startMs + segment.response.durationMs,
35+
segment.hasReadableMedia,
3036
)
3137
}
3238

@@ -52,6 +58,7 @@ internal fun SabrSessionHolder.progressiveWindowSegment(
5258
startMs = startMs,
5359
durationMs = durationMs,
5460
),
61+
actual != null,
5562
)
5663
}
5764

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,46 @@ class SabrProgressivePlaybackWindowTest {
8181
assertEquals(9_985L, result.response.audio.segments.single().durationMs)
8282
}
8383

84+
@Test
85+
fun `vod window includes enough progressive segments to satisfy the buffer goal`() = runTest {
86+
val audio = format(140, isAudio = true)
87+
val video = format(299, isAudio = false)
88+
val state = mockk<YoutubeSabrStreamState>(relaxed = true)
89+
every { state.getSegmentNumberAtOrAfterTimeMs(any(), 5_731_077L) } returns 1
90+
val session = mockk<YoutubeSabrSession>(relaxed = true)
91+
every { session.streamState } returns state
92+
every { session.getCachedSegment(any()) } returns null
93+
every { session.getReadableSegment(any()) } answers {
94+
val request = firstArg<SabrSegmentRequest>()
95+
val durationMs = if (request.format.isAudio) 10_000L else 6_000L
96+
readableSegment(
97+
request.format,
98+
sequence = request.sequenceNumber,
99+
startMs = 5_731_000L + (request.sequenceNumber - 1L) * durationMs,
100+
durationMs = durationMs,
101+
)
102+
}
103+
val holder = holder(session, audio, video)
104+
val store = mockk<SabrSessionStore>()
105+
coEvery { store.cachedSegment(holder, any()) } returns null
106+
107+
val result = SabrPlaybackWindowBuilder(store).build(
108+
holder,
109+
SabrPlaybackWindowRequest(
110+
generation = 0L,
111+
playerTimeMs = 5_731_077L,
112+
videoItag = video.itag,
113+
audioItag = audio.itag,
114+
bufferGoalMs = 30_000L,
115+
),
116+
)
117+
118+
assertTrue(result.isReady)
119+
assertTrue(result.blockedRequests.isEmpty())
120+
assertEquals(6, requireNotNull(result.response.video).segments.size)
121+
assertEquals(4, result.response.audio.segments.size)
122+
}
123+
84124
private fun readableSegment(
85125
format: YoutubeSabrFormat,
86126
sequence: Int = 1,

0 commit comments

Comments
 (0)