Skip to content

Commit 0b4efe5

Browse files
committed
fix: bootstrap SABR before restoring playback position
1 parent de5ec9e commit 0b4efe5

3 files changed

Lines changed: 48 additions & 0 deletions

File tree

src/main/kotlin/dev/typetype/server/services/SabrSessionPumpLoop.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ internal class SabrSessionPumpLoop(
6969
runtime: SabrPumpRuntime,
7070
): Boolean {
7171
preparePumpEviction(holder)
72+
if (holder.prepareStartupBootstrapPump()) {
73+
holder.setPlaybackState(SabrPlaybackState.REQUESTING)
74+
pumpOnce(holder, localization, runtime)
75+
holder.setPlaybackState(SabrPlaybackState.IDLE)
76+
return true
77+
}
7278
holder.consumeRefetch()?.let { request ->
7379
if (holder.session.isBeyondEnd(request) && !holder.isFutureLiveRequest(request)) {
7480
holder.clearSegmentDemand(request)

src/main/kotlin/dev/typetype/server/services/SabrStartupBootstrap.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.typetype.server.services
22

33
internal fun SabrSessionHolder.prepareStartupBootstrapPump(): Boolean {
4+
if (expectsLive() || !hasPendingSeek()) return false
45
if (session.requestNumber == 0) {
56
session.streamState.setPlayerTimeMs(0L)
67
return true

src/test/kotlin/dev/typetype/server/services/SabrSeekRepositionPumpTest.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dev.typetype.server.services
33
import io.mockk.every
44
import io.mockk.mockk
55
import io.mockk.verify
6+
import io.mockk.verifyOrder
67
import kotlinx.coroutines.test.runTest
78
import org.junit.jupiter.api.Assertions.assertEquals
89
import org.junit.jupiter.api.Assertions.assertFalse
@@ -59,6 +60,7 @@ class SabrSeekRepositionPumpTest {
5960
val request = SabrSegmentRequest.media(video, 24)
6061
val session = mockk<YoutubeSabrSession>(relaxed = true)
6162
every { session.streamState } returns mockk(relaxed = true)
63+
every { session.requestNumber } returns 2
6264
every { session.getCachedSegment(any()) } returns null
6365
every { session.pumpOnceStreamingForDemand(any(), request) } returns mockk(relaxed = true)
6466
val holder = holder(session, audio, video)
@@ -78,6 +80,45 @@ class SabrSeekRepositionPumpTest {
7880
}
7981
}
8082

83+
@Test
84+
fun `cold playback bootstraps before applying saved position`() = runTest {
85+
SabrSegmentDemandTracker.clearAll()
86+
try {
87+
val audio = format(140, true)
88+
val video = format(137, false)
89+
val request = SabrSegmentRequest.media(video, 180)
90+
val session = mockk<YoutubeSabrSession>(relaxed = true)
91+
val state = mockk<YoutubeSabrStreamState>(relaxed = true)
92+
var requestNumber = 0
93+
every { session.streamState } returns state
94+
every { session.requestNumber } answers { requestNumber }
95+
every { session.getCachedSegment(any()) } returns null
96+
every { session.pumpOnceStreaming(any()) } answers {
97+
requestNumber = 1
98+
2
99+
}
100+
every { state.getMaxSegment(audio) } returns 1
101+
every { state.getMaxSegment(video) } returns 1
102+
every { session.pumpOnceStreamingForDemand(any(), request) } returns mockk(relaxed = true)
103+
val holder = holder(session, audio, video)
104+
holder.setRequestedSeekTimeMs(900_000L)
105+
holder.requestSegmentDemand(request)
106+
holder.requestForwardSeek(request)
107+
var rounds = 0
108+
109+
SabrSessionPumpLoop().run({ rounds++ < 2 }, holder, intervalMs = 0L)
110+
111+
verifyOrder {
112+
state.setPlayerTimeMs(0L)
113+
session.pumpOnceStreaming(any())
114+
session.prepareForForwardJump(request, 900_000L)
115+
session.pumpOnceStreamingForDemand(any(), request)
116+
}
117+
} finally {
118+
SabrSegmentDemandTracker.clearAll()
119+
}
120+
}
121+
81122
@Test
82123
fun `forward seek discovered beyond end is discarded`() = runTest {
83124
SabrSegmentDemandTracker.clearAll()

0 commit comments

Comments
 (0)