From 78aad68ba068284355aef8bfb296367bc07805be Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sun, 30 Aug 2026 11:38:58 +0200 Subject: [PATCH 1/2] fix: fill progressive SABR seek windows --- .../routes/SabrPlaybackWindowBuilder.kt | 6 +-- .../routes/SabrProgressivePlaybackWindow.kt | 9 ++++- .../SabrProgressivePlaybackWindowTest.kt | 40 +++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilder.kt b/src/main/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilder.kt index 58f28b67..56dd5c8d 100644 --- a/src/main/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilder.kt +++ b/src/main/kotlin/dev/typetype/server/routes/SabrPlaybackWindowBuilder.kt @@ -15,6 +15,8 @@ import dev.typetype.server.services.resolvePlaybackStartMs import org.schabi.newpipe.extractor.services.youtube.sabr.SabrSegmentRequest import org.schabi.newpipe.extractor.services.youtube.sabr.YoutubeSabrFormat +private const val MAX_SEGMENTS_PER_TRACK = 12 + internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessionStore) { suspend fun build( holder: SabrSessionHolder, @@ -140,6 +142,7 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi seq = progressive.nextSequence coveredEndMs = progressive.coveredEndMs if (coveredEndMs >= goalEndMs) break + if (progressive.hasReadableMedia) continue blockedRequest = SabrSegmentRequest.media(format, seq) blockedBy = "${format.trackName()}:${format.itag}:$seq pending" break @@ -214,7 +217,4 @@ internal class SabrPlaybackWindowBuilder(private val sabrSessionStore: SabrSessi ) { fun covers(requiredEndMs: Long): Boolean = (track.segments.isNotEmpty() || atEnd) && coveredEndMs >= requiredEndMs } - private companion object { - const val MAX_SEGMENTS_PER_TRACK = 12 - } } diff --git a/src/main/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindow.kt b/src/main/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindow.kt index cc14f9e0..35b977e6 100644 --- a/src/main/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindow.kt +++ b/src/main/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindow.kt @@ -11,9 +11,14 @@ import org.schabi.newpipe.extractor.services.youtube.sabr.YoutubeSabrFormat internal data class SabrProgressiveWindowSegment( val sequence: Int, val response: SabrPlaybackWindowSegment, + val hasReadableMedia: Boolean, ) -internal data class SabrProgressiveWindowAppend(val nextSequence: Int, val coveredEndMs: Long) +internal data class SabrProgressiveWindowAppend( + val nextSequence: Int, + val coveredEndMs: Long, + val hasReadableMedia: Boolean, +) internal fun MutableList.appendProgressiveWindowSegment( holder: SabrSessionHolder, @@ -27,6 +32,7 @@ internal fun MutableList.appendProgressiveWindowSegme return SabrProgressiveWindowAppend( segment.sequence + 1, segment.response.startMs + segment.response.durationMs, + segment.hasReadableMedia, ) } @@ -52,6 +58,7 @@ internal fun SabrSessionHolder.progressiveWindowSegment( startMs = startMs, durationMs = durationMs, ), + actual != null, ) } diff --git a/src/test/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindowTest.kt b/src/test/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindowTest.kt index 031d11ce..6cc81ad7 100644 --- a/src/test/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindowTest.kt +++ b/src/test/kotlin/dev/typetype/server/routes/SabrProgressivePlaybackWindowTest.kt @@ -81,6 +81,46 @@ class SabrProgressivePlaybackWindowTest { assertEquals(9_985L, result.response.audio.segments.single().durationMs) } + @Test + fun `vod window includes enough progressive segments to satisfy the buffer goal`() = runTest { + val audio = format(140, isAudio = true) + val video = format(299, isAudio = false) + val state = mockk(relaxed = true) + every { state.getSegmentNumberAtOrAfterTimeMs(any(), 5_731_077L) } returns 1 + val session = mockk(relaxed = true) + every { session.streamState } returns state + every { session.getCachedSegment(any()) } returns null + every { session.getReadableSegment(any()) } answers { + val request = firstArg() + val durationMs = if (request.format.isAudio) 10_000L else 6_000L + readableSegment( + request.format, + sequence = request.sequenceNumber, + startMs = 5_731_000L + (request.sequenceNumber - 1L) * durationMs, + durationMs = durationMs, + ) + } + val holder = holder(session, audio, video) + val store = mockk() + coEvery { store.cachedSegment(holder, any()) } returns null + + val result = SabrPlaybackWindowBuilder(store).build( + holder, + SabrPlaybackWindowRequest( + generation = 0L, + playerTimeMs = 5_731_077L, + videoItag = video.itag, + audioItag = audio.itag, + bufferGoalMs = 30_000L, + ), + ) + + assertTrue(result.isReady) + assertTrue(result.blockedRequests.isEmpty()) + assertEquals(6, requireNotNull(result.response.video).segments.size) + assertEquals(4, result.response.audio.segments.size) + } + private fun readableSegment( format: YoutubeSabrFormat, sequence: Int = 1, From 87c619e013be3dfd198d4cab1da38b80b639a1cc Mon Sep 17 00:00:00 2001 From: Priveetee Date: Sun, 30 Aug 2026 11:50:40 +0200 Subject: [PATCH 2/2] chore: prepare server 1.7.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 64006a63..2de0665a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC kotlin.code.style=official -appVersion=1.7.0 +appVersion=1.7.1 systemProp.sun.net.client.defaultReadTimeout=180000 systemProp.sun.net.client.defaultConnectTimeout=60000