From 7fff36c5e35125004ab7c56f25961d8a0c8982b0 Mon Sep 17 00:00:00 2001 From: Kayos Date: Tue, 26 May 2026 11:27:34 -0700 Subject: [PATCH] vc=58: parallel SponsorBlock + RYD fetch on video open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sequential withContext blocks left the slower of the two requests fully serialized behind the faster one — 200-500ms wasted per video open. async{}+await in a coroutineScope runs both on Dispatchers.IO concurrently. Saves the slower-task latency on every detail-screen load. Rust port was overscoped — the dominant cost is network latency, not parse, so a UniFFI hop wouldn't help and the parallelization fix is a 5-line Kotlin change. Updated the Rust port plan memo accordingly. --- buildSrc/src/main/kotlin/ProjectConfig.kt | 4 +-- .../feature/detail/VideoDetailViewModel.kt | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index e4f73c6c8..b65da1030 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via // strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no // NewPipeExtractor in the runtime path. -const val STRAW_VERSION_CODE = 57 -const val STRAW_VERSION_NAME = "0.1.0-BQ" +const val STRAW_VERSION_CODE = 58 +const val STRAW_VERSION_NAME = "0.1.0-BR" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailViewModel.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailViewModel.kt index 45336a6bc..f20864efa 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailViewModel.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/detail/VideoDetailViewModel.kt @@ -29,6 +29,8 @@ import com.sulkta.straw.util.runCatchingCancellable import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -160,13 +162,24 @@ class VideoDetailViewModel : ViewModel() { } } - val ryd = withContext(Dispatchers.IO) { - runCatchingCancellable { RydClient.fetch(videoId) }.getOrNull() - } + // RYD + SponsorBlock in parallel — both are independent + // network round-trips that block the detail UI. Running + // them sequentially via two withContext blocks left the + // slower one fully serialized behind the faster one + // (~200-500ms wasted per video open). async{}.await() + // on Dispatchers.IO closes that gap. val sbCats = Settings.get().sbCategories.value.map { it.key } - val segments = if (sbCats.isEmpty()) emptyList() else withContext(Dispatchers.IO) { - runCatchingCancellable { SponsorBlockClient.fetch(videoId, sbCats) } - .getOrDefault(emptyList()) + val (ryd, segments) = coroutineScope { + val rydDeferred = async(Dispatchers.IO) { + runCatchingCancellable { RydClient.fetch(videoId) }.getOrNull() + } + val sbDeferred = async(Dispatchers.IO) { + if (sbCats.isEmpty()) emptyList() + else runCatchingCancellable { + SponsorBlockClient.fetch(videoId, sbCats) + }.getOrDefault(emptyList()) + } + rydDeferred.await() to sbDeferred.await() } val related = info.related.map { r ->