Skip to content

Commit 235df60

Browse files
committed
perf: reuse SABR bootstrap preparation
1 parent 09db8fd commit 235df60

6 files changed

Lines changed: 43 additions & 32 deletions

File tree

src/main/kotlin/dev/typetype/server/ExtractionServiceRegistry.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,8 @@ internal class ExtractionServiceRegistry(
134134
),
135135
YouTubeSubtitleCache(cache),
136136
)
137-
val youtubeSabrBootstrapStreamService = CachedStreamService(
138-
YoutubeScopedStreamService(SabrBootstrapStreamService(sabrSessionStore, tokenYoutubeSessionClient)),
139-
cache,
140-
"stream-youtube-sabr-bootstrap:v1",
137+
val youtubeSabrBootstrapStreamService = YoutubeScopedStreamService(
138+
SabrBootstrapStreamService(sabrSessionStore, tokenYoutubeSessionClient),
141139
)
142140
val nicoNicoStreamService = CachedStreamService(directPipePipeStreamService, cache, "stream-niconico:v1")
143141
val bilibiliStreamService = CachedStreamService(directPipePipeStreamService, cache, "stream-bilibili:v1")

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,21 @@ package dev.typetype.server.services
22

33
import dev.typetype.server.models.ExtractionResult
44
import dev.typetype.server.models.StreamResponse
5-
import kotlinx.coroutines.async
6-
import kotlinx.coroutines.coroutineScope
75

86
internal class SabrBootstrapStreamService(
97
private val sessionStore: SabrSessionStore,
108
private val tokenSessionClient: TypetypeTokenYoutubeSessionClient,
119
) : StreamService {
12-
override suspend fun getStreamInfo(url: String): ExtractionResult<StreamResponse> = coroutineScope {
10+
override suspend fun getStreamInfo(url: String): ExtractionResult<StreamResponse> {
1311
val videoId = youtubeVideoId(url)
14-
?: return@coroutineScope ExtractionResult.BadRequest("Invalid YouTube URL")
15-
val prepared = async { sessionStore.fetchInfo(videoId, cachedFirst = true) }
16-
val session = async { tokenSessionClient.fetchPlaybackSession(videoId) }
17-
val metadata = session.await()
18-
?: return@coroutineScope ExtractionResult.Failure("SABR bootstrap metadata unavailable")
19-
if (prepared.await() == null) {
20-
return@coroutineScope ExtractionResult.Failure("SABR playback formats unavailable")
21-
}
22-
ExtractionResult.Success(metadata.toFallbackStreamResponse(videoId))
12+
?: return ExtractionResult.BadRequest("Invalid YouTube URL")
13+
val metadata = tokenSessionClient.fetchPlaybackSession(videoId)
14+
?: return ExtractionResult.Failure("SABR bootstrap metadata unavailable")
15+
val prepared = metadata.preparedSabrInfo()
16+
?: sessionStore.fetchInfo(videoId, cachedFirst = true)
17+
?: return ExtractionResult.Failure("SABR playback formats unavailable")
18+
sessionStore.rememberPreparedInfo(videoId, prepared)
19+
return ExtractionResult.Success(metadata.toFallbackStreamResponse(videoId))
2320
}
2421
}
2522

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,12 @@ internal class SabrInfoFetcher(
5959

6060
suspend fun rememberExtractedInfo(videoId: String, info: YoutubeSabrInfo): Unit {
6161
repository.rememberInitialization(videoId, info)
62-
fetchInfoOnce(videoId, startTimeMs = 0L)
63-
?.takeIf { it.hasAudioAndVideoFormats() }
64-
?.let { repository.putPrepared(videoId, startTimeMs = 0L, it) }
62+
fetchInfo(videoId, startTimeMs = 0L, cachedFirst = true)
63+
}
64+
65+
suspend fun rememberPreparedInfo(videoId: String, prepared: SabrPreparedInfo): Unit {
66+
repository.rememberInitialization(videoId, prepared.info)
67+
repository.putPrepared(videoId, startTimeMs = 0L, prepared)
6568
}
6669

6770
suspend fun invalidatePlayback(videoId: String): Unit = repository.invalidatePlayback(videoId)
@@ -87,13 +90,7 @@ internal class SabrInfoFetcher(
8790
): SabrPreparedInfo? =
8891
withTimeoutOrNull(SabrSessionStoreDefaults.INFO_TIMEOUT_MS) {
8992
val tokenSession = sessionClient?.fetchPlaybackSession(videoId, isolatedPlayback)
90-
tokenSession?.token
91-
?.takeIf { it.visitorData == tokenSession.info.visitorData }
92-
?.let { sessionToken ->
93-
SabrPreparedInfo(tokenSession.info, sessionToken, tokenSession.isLive, tokenSession.isLiveContent)
94-
.takeIf { it.hasAudioAndVideoFormats() }
95-
?.let { return@withTimeoutOrNull it }
96-
}
93+
tokenSession?.preparedSabrInfo()?.let { return@withTimeoutOrNull it }
9794
val token = tokenClient.fetch(videoId)
9895
?: return@withTimeoutOrNull null.also {
9996
logger.warn(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ internal class SabrSessionStore(
151151

152152
internal suspend fun rememberExtractedInfo(videoId: String, info: YoutubeSabrInfo): Unit =
153153
infoFetcher.rememberExtractedInfo(videoId, info)
154-
154+
internal suspend fun rememberPreparedInfo(videoId: String, prepared: SabrPreparedInfo): Unit = infoFetcher.rememberPreparedInfo(videoId, prepared)
155155
internal suspend fun invalidatePlaybackInfo(videoId: String): Unit = infoFetcher.invalidatePlayback(videoId)
156156

157157
internal suspend fun recoverProtectedPlaybackInfo(holder: SabrSessionHolder): Unit =

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ internal data class TokenYoutubeSession(
1818
val isLiveContent: Boolean,
1919
val hlsUrl: String = "",
2020
)
21+
22+
internal fun TokenYoutubeSession.preparedSabrInfo(): SabrPreparedInfo? {
23+
val boundToken = token?.takeIf { it.visitorData == info.visitorData } ?: return null
24+
return SabrPreparedInfo(info, boundToken, isLive, isLiveContent)
25+
.takeIf(SabrPreparedInfo::hasAudioAndVideoFormats)
26+
}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ class SabrBootstrapStreamServiceTest {
1717
val sessionStore = mockk<SabrSessionStore>()
1818
val tokenClient = mockk<TypetypeTokenYoutubeSessionClient>()
1919
val prepared = preparedInfo()
20-
coEvery { sessionStore.fetchInfo(VIDEO_ID, cachedFirst = true) } returns prepared
21-
coEvery { tokenClient.fetchPlaybackSession(VIDEO_ID) } returns tokenSession(prepared.info)
20+
val session = tokenSession(prepared.info, tokenBundle())
21+
coEvery { sessionStore.rememberPreparedInfo(VIDEO_ID, any()) } returns Unit
22+
coEvery { tokenClient.fetchPlaybackSession(VIDEO_ID) } returns session
2223
val service = SabrBootstrapStreamService(sessionStore, tokenClient)
2324

2425
val result = service.getStreamInfo(YOUTUBE_URL)
@@ -27,7 +28,8 @@ class SabrBootstrapStreamServiceTest {
2728
assertEquals("Bootstrap title", response.title)
2829
assertEquals(listOf(137), response.videoOnlyStreams.map { it.itag })
2930
assertEquals(listOf(140), response.audioStreams.map { it.itag })
30-
coVerify(exactly = 1) { sessionStore.fetchInfo(VIDEO_ID, cachedFirst = true) }
31+
coVerify(exactly = 0) { sessionStore.fetchInfo(any(), any()) }
32+
coVerify(exactly = 1) { sessionStore.rememberPreparedInfo(VIDEO_ID, any()) }
3133
coVerify(exactly = 1) { tokenClient.fetchPlaybackSession(VIDEO_ID) }
3234
}
3335

@@ -58,12 +60,22 @@ class SabrBootstrapStreamServiceTest {
5860
every { audio.audioTrackId } returns "en.4"
5961
val info = mockk<YoutubeSabrInfo>()
6062
every { info.formats } returns listOf(video, audio)
63+
every { info.visitorData } returns VISITOR_DATA
6164
return SabrPreparedInfo(info, null)
6265
}
6366

64-
private fun tokenSession(info: YoutubeSabrInfo): TokenYoutubeSession = TokenYoutubeSession(
67+
private fun tokenBundle() = SabrTokenBundle(
68+
videoId = VIDEO_ID,
69+
visitorBoundPoToken = "player-token",
70+
visitorBoundPoTokenBytes = byteArrayOf(1),
71+
visitorData = VISITOR_DATA,
72+
videoBoundPoToken = "media-token",
73+
videoBoundPoTokenBytes = byteArrayOf(2),
74+
)
75+
76+
private fun tokenSession(info: YoutubeSabrInfo, token: SabrTokenBundle? = null): TokenYoutubeSession = TokenYoutubeSession(
6577
info = info,
66-
token = null,
78+
token = token,
6779
title = "Bootstrap title",
6880
author = "Bootstrap channel",
6981
channelId = "channel-id",
@@ -80,5 +92,6 @@ class SabrBootstrapStreamServiceTest {
8092
private companion object {
8193
const val VIDEO_ID = "f6f3PhauXyg"
8294
const val YOUTUBE_URL = "https://www.youtube.com/watch?v=$VIDEO_ID"
95+
const val VISITOR_DATA = "visitor-data"
8396
}
8497
}

0 commit comments

Comments
 (0)