vc=66: hybrid feed backfill — RSS-fast + streamInfo-complete
Cobb asked for views + durations back in the subs feed without
giving up the 5-10× RSS speedup vc=56 bought. Hybrid path:
1. Rust wrapper — new enrich_feed_item(video_url) ->
EnrichedFeedMetadata { view_count, duration_seconds }. Thin
wrapper around stream_info that discards the heavy play-URL
payload. Future opt: parse watch-page HTML JSON state directly
to skip JS deobf entirely. ~150 lines of pluck logic, punted.
2. EnrichmentStore — new SharedPreferences-lite store keyed by
videoId, value Enrichment(viewCount, durationSeconds,
fetchedAt). Bound to Settings.cacheTtl for staleness. Hard cap
5000 entries with oldest-eviction.
3. SubscriptionFeedViewModel — after the RSS refresh paints,
enrichVisibleItems() fans out enrichFeedItem for the first 30
items (skipping any already enriched fresh). Bounded at 8 wide
so we don't hammer YT; each call ~500ms full streamInfo so
30 items in ~2s. Runs on StrawApp.globalScope so a
refresh-cancel doesn't kill the in-flight enrichment.
mergeFromCache overlays the enrichment via .withEnrichment()
so RSS rows pick up viewCount + durationSeconds the moment
they land. The Enrichment store's StateFlow.value is read on
every merge call; the enrichment-complete handler triggers a
_ui.update that re-merges.
Net behavior: feed paints instantly from RSS (no view/duration),
~2s later the visible top-N populate with full metadata. Cached
forever (or until TTL/cap). Subsequent opens read straight from
EnrichmentStore.
StrawApp.onCreate inits the new store alongside the existing
SP-backed ones.
This commit is contained in:
parent
7156208c3c
commit
dd151e322d
5 changed files with 236 additions and 3 deletions
|
|
@ -25,6 +25,36 @@ const RSS_BASE: &str = "https://www.youtube.com/feeds/videos.xml?channel_id=";
|
|||
const MAX_CONCURRENT: usize = 50;
|
||||
const PER_CHANNEL_TIMEOUT_S: u64 = 8;
|
||||
|
||||
/// Hybrid-backfill metadata: just the two fields RSS doesn't return
|
||||
/// (view count + duration). Kotlin calls this lazily for visible feed
|
||||
/// items after the RSS-fed paint to fill in the gaps that
|
||||
/// channel_feed_rss leaves empty.
|
||||
///
|
||||
/// vc=66 — built specifically so the subs feed can show 'N views ·
|
||||
/// X duration' the way YT does, without paying the full channel_info
|
||||
/// page-scrape cost on initial paint. The underlying stream_info IS
|
||||
/// heavier than we'd like (~500ms each, runs JS deobf for play URLs
|
||||
/// we'll discard) — future opt would be to parse the watch-page HTML
|
||||
/// JSON state directly for just these two fields. ~100ms savings per
|
||||
/// call but ~150 lines of HTML/JSON pluck logic. Punted until needed.
|
||||
#[derive(Debug, Clone, uniffi::Record)]
|
||||
pub struct EnrichedFeedMetadata {
|
||||
pub view_count: i64,
|
||||
pub duration_seconds: i64,
|
||||
}
|
||||
|
||||
#[uniffi::export(async_runtime = "tokio")]
|
||||
pub async fn enrich_feed_item(
|
||||
video_url: String,
|
||||
) -> Result<EnrichedFeedMetadata, StrawcoreError> {
|
||||
crate::runtime::ensure_initialized();
|
||||
let info = crate::stream::stream_info(video_url).await?;
|
||||
Ok(EnrichedFeedMetadata {
|
||||
view_count: info.view_count,
|
||||
duration_seconds: info.duration_seconds,
|
||||
})
|
||||
}
|
||||
|
||||
/// Single-channel RSS — Kotlin keeps its per-channel cache + fan-out
|
||||
/// (parallelism cranked to 50 in the wrapper). Each call is ~50-150ms
|
||||
/// instead of the ~500ms channelInfo page-scrape, so a 50-sub refresh
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue