diff --git a/.gitignore b/.gitignore index b971f9042..6bb96d430 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,9 @@ rust/target/ strawApp/src/main/jniLibs/ # UniFFI-generated Kotlin bindings (regen'd from .so on every build) strawApp/src/main/java/uniffi/ + +# Rust build artifacts +rust/target/ +strawApp/src/main/jniLibs/ +# UniFFI-generated Kotlin bindings (regen'd from .so on every build) +strawApp/src/main/java/uniffi/ diff --git a/rust/strawcore/Cargo.toml b/rust/strawcore/Cargo.toml index 590a12e12..1d82915cc 100644 --- a/rust/strawcore/Cargo.toml +++ b/rust/strawcore/Cargo.toml @@ -19,10 +19,27 @@ crate-type = ["cdylib", "staticlib"] uniffi = { version = "0.28", features = ["cli", "tokio"] } # Tokio multi-thread runtime — rustypipe is async-first. tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync"] } -# rustypipe — the actual YouTube Innertube client. Phase U-2 wires search. +# rustypipe — the actual YouTube Innertube client. Phase U-2 wires search, +# U-3 wires streamInfo, U-4 wires channels. +# +# Points at the Sulkta-Coop fork (kayos/m1-sig-port branch, tag v0.11.5-sulkta.2) +# because upstream 0.11.4 hard-failed at init when YT rotated the +# player.js to a shape its sig-regex doesn't recognise (player c2f7551f, May 2026). +# The fork: +# - skips player.js deobf entirely for the iOS/Android client paths +# (pre-signed URLs, no &s= cipher, no &n= throttle param) +# - soft-fails sig_fn/nsig_fn extraction with a switchable error class +# so the player_from_clients chain falls through to iOS instead of +# killing the call +# - defaults to iOS-first client order +# - emits Level::WRN reporter event on partial extraction +# # Force rustls + webpki-roots so we don't pull openssl-sys (cross-compiling # system OpenSSL to four Android ABIs is a tarpit; rustls is pure-Rust). -rustypipe = { version = "0.11", default-features = false, features = ["rustls-tls-webpki-roots"] } +# +# When YT rotates back to a sig shape both upstream and our fork recognise, +# we can flip back to crates.io. Until then, the fork is the only working dep. +rustypipe = { git = "http://192.168.0.5:3001/Sulkta-Coop/rustypipe.git", tag = "v0.11.5-sulkta.2", default-features = false, features = ["rustls-tls-webpki-roots"] } # rquickjs-sys (transitive dep of rustypipe for YT signature decryption JS) # doesn't ship prebuilt Android bindings. Direct-depend with `bindgen` feature # so it generates them at build time. Crafting-table has libclang preinstalled. diff --git a/strawApp/build.gradle.kts b/strawApp/build.gradle.kts index 13ef39dde..d1c4b0dd2 100644 --- a/strawApp/build.gradle.kts +++ b/strawApp/build.gradle.kts @@ -110,4 +110,90 @@ dependencies { implementation("androidx.media3:media3-session:1.4.1") // Guava ListenableFuture support for awaiting MediaController connect. implementation("androidx.concurrent:concurrent-futures-ktx:1.2.0") + + // strawcore — Rust YouTube extractor via UniFFI/JNA. Built by the + // cargoBuild + uniffiBindgen tasks below; phase U-2+ exposes search / + // streamInfo / channelInfo to replace NewPipeExtractor. + implementation("net.java.dev.jna:jna:5.14.0@aar") } + +// ============================================================================= +// Phase U-1 / Path-C-2 — Rust core build glue. +// +// Two tasks chain into the Android build: +// cargoBuild — cross-compiles rust/strawcore for the four Android ABIs +// via cargo-ndk and drops the .so files in strawApp/src/main/jniLibs/. +// uniffiBindgen — generates the Kotlin bindings from the freshly-built lib +// into strawApp/src/main/java/uniffi/strawcore/. +// +// Both depend on: +// - cargo + rustup with the four Android targets installed +// - cargo-ndk on PATH +// - ANDROID_NDK_HOME pointing at an NDK with the right toolchains +// All of that lives in the crafting-table container. +// ============================================================================= + +val rustRoot = file("../rust").absolutePath +val jniLibsDir = file("src/main/jniLibs").absolutePath +val bindingsDir = file("src/main/java").absolutePath + +val cargoHome: String = System.getenv("CARGO_HOME") ?: "/caches/cargo" +val cargoBin: String = "$cargoHome/bin/cargo" +val ndkHome: String = System.getenv("ANDROID_NDK_HOME") + ?: System.getenv("ANDROID_NDK_ROOT") + ?: "/caches/android-sdk/ndk/27.2.12479018" + +val cargoBuild by tasks.registering(Exec::class) { + group = "rust" + description = "Cross-compile strawcore for all Android ABIs via cargo-ndk." + workingDir = file(rustRoot) + environment("ANDROID_NDK_HOME", ndkHome) + environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}") + commandLine = listOf( + cargoBin, "ndk", + "-t", "arm64-v8a", + "-t", "armeabi-v7a", + "-t", "x86", + "-t", "x86_64", + "-o", jniLibsDir, + "build", "--release", "-p", "strawcore", + ) + standardOutput = System.out + errorOutput = System.err +} + +val cargoBuildHost by tasks.registering(Exec::class) { + group = "rust" + description = "Build host-arch debug strawcore so bindgen can read its UniFFI metadata." + workingDir = file(rustRoot) + environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}") + commandLine = listOf(cargoBin, "build", "-p", "strawcore") + standardOutput = System.out + errorOutput = System.err +} + +val uniffiBindgen by tasks.registering(Exec::class) { + group = "rust" + description = "Generate Kotlin bindings for strawcore via uniffi-bindgen." + dependsOn(cargoBuildHost) + workingDir = file(rustRoot) + environment("PATH", "$cargoHome/bin:${System.getenv("PATH") ?: ""}") + commandLine = listOf( + cargoBin, "run", "--quiet", "--bin", "uniffi-bindgen", "--", + "generate", + "--library", "target/debug/libstrawcore.so", + "--crate", "strawcore", + "--language", "kotlin", + "--no-format", + "--out-dir", bindingsDir, + ) + standardOutput = System.out + errorOutput = System.err +} + +// Make sure Android's JNI-libs merge picks up the freshly built .so files, +// and Kotlin compilation can resolve the generated bindings. +tasks.matching { it.name.startsWith("merge") && it.name.endsWith("JniLibFolders") } + .configureEach { dependsOn(cargoBuild) } +tasks.matching { it.name.startsWith("compile") && it.name.endsWith("Kotlin") } + .configureEach { dependsOn(uniffiBindgen) }