Rust port of NewPipeExtractor (YT-only). Plugs into Straw via UniFFI.
Find a file
Kayos e6fbbb79b4 channel: second-browse to Videos tab + parse lockupViewModel
Found via emulator smoke that channelInfo was returning empty
recent_videos list, breaking the subscriptions feed.

Two root causes:
1. First browse of a channel by browseId lands on the HOME tab in
   2026 YT, not Videos. Home uses sectionListRenderer, not the
   richGridRenderer my parser expected. The Videos tab in the
   response carries an empty content block (you need a SECOND
   browse with the params token to populate it).
2. Channel video items on the Videos tab migrated from
   videoRenderer to lockupViewModel (YT made the switch ~2024).
   My old parser only handled videoRenderer.

Fix:
* fetch_channel_browse now does TWO browses — first for Home
  (header + metadata), second with params='EgZ2aWRlb3PyBgQKAjoA'
  for the Videos tab. Same magic constant NPE uses (audit Track
  A §2.4).
* parse_videos_tab handles BOTH videoRenderer (legacy/fallback)
  AND lockupViewModel (current). lockupViewModel parse extracts:
    - contentId → video ID
    - metadata.lockupMetadataViewModel.title.content → title
    - metadataRows[].metadataParts[].text.content → view-count
      ('1.1m views') + relative-age ('2 years ago') + uploader
    - contentImage.thumbnailViewModel.overlays[]
      .thumbnailBottomOverlayViewModel.badges[]
      .thumbnailBadgeViewModel.text → duration ('3:14:08')
    - contentImage.thumbnailViewModel.image.sources[] → thumbnails
* parse_videos_continuation pulls the continuation token from the
  Videos tab grid for pagination.

Second browse is best-effort: if it fails, recent_videos stays
empty and the channel header still populates from the first.

Verified the YT response shape by probing live channel
UCwwtUfy0-CqN50HfaFDzL0w (NCS Spektrem) — got 30+ lockup-style
video items with the expected fields.
2026-05-24 20:06:43 -07:00
src channel: second-browse to Videos tab + parse lockupViewModel 2026-05-24 20:06:43 -07:00
tests Phase 4 (complete) — stream_extractor orchestrator 2026-05-24 17:08:04 -07:00
.gitignore Initial commit 2026-05-24 16:26:57 -07:00
Cargo.lock Rename package to strawcore-core 2026-05-24 17:28:38 -07:00
Cargo.toml Drop cdylib + staticlib from strawcore-core crate-type 2026-05-24 17:40:37 -07:00
LICENSE Initial commit 2026-05-24 16:26:57 -07:00
README.md Phase 1 — Foundation 2026-05-24 16:32:36 -07:00

strawcore

Rust port of NewPipeExtractor (v0.26.2), YouTube-only. Plugs into Straw via UniFFI.

Why this exists

rustypipe regex-parses YouTube's player.js and reimplements the signature deobfuscator in Rust. Every YT player rotation breaks it. NPE embeds Mozilla Rhino and executes the JS function live — resilient by design, and that's the architecture we're mirroring.

The rustypipe-backed Straw build (vc=15..17) also routed playback through iOS-progressive URLs, which hit a server-side ~917 KiB end-byte cap. NPE uses the Android client + po_token → DASH manifest path, which doesn't see the cap. Same fix, different layer.

See memory/npe-audit-2026-05-24/SPEC.md in the workspace repo for the full plan.

Status

Phase Subsystem Status
1 Foundation (downloader + service spine) in progress
2 JS engine (rquickjs + ress) pending
3 InnerTube + itag table pending
4 Stream extractor + DASH pending
5 PoTokenProvider trait + Android JNI bridge pending
6 Search + Channel + Playlist + Kiosks pending
7 UniFFI surface swap pending
8 Delete rustypipe everywhere pending

Build + test

cargo build
cargo test --lib                          # offline unit tests
cargo test --features online-tests        # full smoke incl. live httpbin.org

License

GPL-3.0-or-later. NPE is GPL-3.0; this port inherits.