vc=58: parallel SponsorBlock + RYD fetch on video open

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.
This commit is contained in:
Kayos 2026-05-26 11:27:34 -07:00
parent 8dec2f2621
commit 7fff36c5e3
2 changed files with 21 additions and 8 deletions

View file

@ -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"

View file

@ -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 ->