perf(extract): borrow instead of clone + parallelize channel browse

Four allocation/latency wins in the hot extraction paths. Behavior
preserved — all 120 in-crate tests green, clippy clean.

- stream_extractor: borrow streamingData out of the owned android + ios
  player responses instead of deep-cloning the largest subtree of each
  response. A shared Value::Null static backs the absent case.
- stream_extractor: merge_formats returns borrowed &Value format objects
  rather than cloning each one (~20-40 per video) — they are only read
  (.get()) downstream.
- channel: fetch the Home and Videos tabs concurrently via thread::scope
  so a channel open costs one round-trip of latency, not two. Home stays
  mandatory; the Videos tab stays best-effort (now also resilient to a
  panicked worker thread).
- downloader: build the response body with String::from_utf8 (in-place on
  the valid-UTF-8 common case) instead of from_utf8_lossy, which always
  copies; the lossy fallback for invalid bytes is preserved.
This commit is contained in:
Sulkta 2026-06-21 05:40:11 -07:00
parent 820daec026
commit 5e0ec08928
3 changed files with 95 additions and 43 deletions

View file

@ -117,7 +117,14 @@ impl Downloader for ReqwestDownloader {
"response body exceeded cap {MAX_BODY_BYTES}"
)));
}
let body = String::from_utf8_lossy(&buf).into_owned();
// Reuse the body buffer on the valid-UTF-8 path (the overwhelming
// common case): String::from_utf8 reinterprets the Vec in place,
// whereas from_utf8_lossy always allocates + copies. Fall back to
// lossy only on genuinely invalid bytes, preserving U+FFFD behavior.
let body = match String::from_utf8(buf) {
Ok(s) => s,
Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
};
Ok(Response::new(code, message, headers, body, url_after_redirects))
}