NewPipeExtractor (Java) → strawcore (Rust) migration begins. Phase U:
- U-1: Rust toolchain + UniFFI smoke test
- U-2: rustypipe search via uniffi suspend fun, SearchViewModel swapped
What landed:
- rust/strawcore — UniFFI-exported Rust crate using proc-macros.
Builds for arm64-v8a + armeabi-v7a + x86 + x86_64 via cargo-ndk.
Tokio multi-thread runtime singleton drives rustypipe's async API.
- strawApp/build.gradle.kts — cargoBuildHost + cargoBuild + uniffiBindgen
Gradle Exec tasks chained into the Android build. Generated Kotlin
bindings land in src/main/java/uniffi/strawcore/ (gitignored).
- SearchViewModel.kt — calls uniffi.strawcore.search(query) directly.
NewPipeExtractor still in deps for VideoDetail/Player/Channel paths;
those move to Rust in U-3 / U-4.
- Build chain quirks beat:
* cargo absolute path in Exec tasks (PATH wasn't propagating)
* uniffi-bindgen needs UNSTRIPPED host .so — separate cargoBuildHost
builds a debug-profile host lib to read metadata from
* rustypipe rustls-tls-webpki-roots avoids the openssl-sys
cross-compile tarpit
* rquickjs-sys 'bindgen' feature opted in (no prebuilt Android
bindings ship; crafting-table has libclang 14)
- crafting-table runtime install (until Dockerfile catches up):
rustup + 4 Android targets + cargo-ndk + NDK r27c. Persists in
/caches/cargo + /caches/android-sdk via the volume mount.
APK size: 22MB (U-1) → 37MB (U-2). libstrawcore.so 3-5MB per ABI carries
rustypipe + reqwest + tokio + rustls + rquickjs. NewPipeExtractor still
in for now (still drives detail + player + channel + feed), so the
Java half is doubled up. U-5 removes it.
65 lines
2.2 KiB
Rust
65 lines
2.2 KiB
Rust
// Phase U-2 — search via rustypipe, exposed to Kotlin as a suspend fun.
|
|
//
|
|
// `SearchItem` mirrors the existing Kotlin `StreamItem` field for field so
|
|
// `SearchViewModel` can swap NewPipeExtractor for this with a one-line
|
|
// change. We map only Video items from rustypipe's result (it also returns
|
|
// channels and playlists which we don't need for the search list yet).
|
|
|
|
use crate::error::StrawcoreError;
|
|
use rustypipe::client::RustyPipe;
|
|
use rustypipe::model::YouTubeItem;
|
|
|
|
#[derive(Debug, Clone, uniffi::Record)]
|
|
pub struct SearchItem {
|
|
pub url: String,
|
|
pub title: String,
|
|
pub uploader: String,
|
|
pub uploader_url: Option<String>,
|
|
pub thumbnail: Option<String>,
|
|
/// Duration in seconds. 0 = live/unknown.
|
|
pub duration_seconds: i64,
|
|
/// Reported view count. 0 = unknown.
|
|
pub view_count: i64,
|
|
}
|
|
|
|
fn yt_video_url(id: &str) -> String {
|
|
format!("https://www.youtube.com/watch?v={}", id)
|
|
}
|
|
|
|
fn yt_channel_url(id: &str) -> String {
|
|
format!("https://www.youtube.com/channel/{}", id)
|
|
}
|
|
|
|
#[uniffi::export(async_runtime = "tokio")]
|
|
pub async fn search(query: String) -> Result<Vec<SearchItem>, StrawcoreError> {
|
|
log::info!("strawcore::search query={}", query);
|
|
let rp = RustyPipe::new();
|
|
let results = rp.query().search(query).await?;
|
|
|
|
let items: Vec<SearchItem> = results
|
|
.items
|
|
.items
|
|
.into_iter()
|
|
.filter_map(|item| match item {
|
|
YouTubeItem::Video(v) => Some(SearchItem {
|
|
url: yt_video_url(&v.id),
|
|
title: v.name,
|
|
uploader: v
|
|
.channel
|
|
.as_ref()
|
|
.map(|c| c.name.clone())
|
|
.unwrap_or_default(),
|
|
uploader_url: v.channel.as_ref().map(|c| yt_channel_url(&c.id)),
|
|
thumbnail: v.thumbnail.first().map(|t| t.url.clone()),
|
|
duration_seconds: v.duration.unwrap_or(0) as i64,
|
|
view_count: v.view_count.unwrap_or(0) as i64,
|
|
}),
|
|
// Channels + playlists are dropped at U-2; future phases can
|
|
// surface them as a "channels you might like" row.
|
|
_ => None,
|
|
})
|
|
.collect();
|
|
|
|
log::info!("strawcore::search returned {} videos", items.len());
|
|
Ok(items)
|
|
}
|