diff --git a/buildSrc/src/main/kotlin/ProjectConfig.kt b/buildSrc/src/main/kotlin/ProjectConfig.kt index a63073c18..5f3a39428 100644 --- a/buildSrc/src/main/kotlin/ProjectConfig.kt +++ b/buildSrc/src/main/kotlin/ProjectConfig.kt @@ -15,6 +15,6 @@ const val NEWPIPE_APPLICATION_ID_OLD = "org.schabi.newpipe" const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app" // Sulkta fork — Straw -const val STRAW_VERSION_CODE = 10 -const val STRAW_VERSION_NAME = "0.1.0-W" +const val STRAW_VERSION_CODE = 11 +const val STRAW_VERSION_NAME = "0.1.0-W2" const val STRAW_APPLICATION_ID = "com.sulkta.straw" diff --git a/rust/strawcore/src/stream.rs b/rust/strawcore/src/stream.rs index d388c5235..ea35ac76b 100644 --- a/rust/strawcore/src/stream.rs +++ b/rust/strawcore/src/stream.rs @@ -17,7 +17,7 @@ use crate::error::StrawcoreError; use crate::search::SearchItem; -use rustypipe::client::RustyPipe; +use rustypipe::client::{ClientType, RustyPipe}; #[derive(Debug, Clone, uniffi::Record)] pub struct StreamInfo { @@ -129,7 +129,15 @@ pub async fn stream_info(url: String) -> Result { log::info!("strawcore::stream_info id={}", id); let rp = RustyPipe::new(); - let player = rp.query().player(&id).await?; + // rustypipe's default `player()` uses the Web client first. Those URLs + // come back signed against the Web fetch's session/UA — ExoPlayer can't + // replay them (404/403/black screen). Force the TV embedded + iOS + // clients, both of which return ungated direct-play URLs the way + // NewPipe's resolver does. + let player = rp + .query() + .player_from_clients(&id, &[ClientType::Tv, ClientType::Ios]) + .await?; let details = &player.details; // Progressive (combined audio+video) goes through video_streams; the diff --git a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlayerScreen.kt b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlayerScreen.kt index dfb0518e0..5279e8fdc 100644 --- a/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlayerScreen.kt +++ b/strawApp/src/main/kotlin/com/sulkta/straw/feature/player/PlayerScreen.kt @@ -121,6 +121,28 @@ fun PlayerScreen( } } + // Surface playback errors so a 403/404 from googlevideo doesn't show + // as a silent black screen. Captures everything ExoPlayer's renderer + // pipeline raises. + DisposableEffect(exoPlayer) { + val listener = object : Player.Listener { + override fun onPlayerError(error: androidx.media3.common.PlaybackException) { + val msg = buildString { + append("play err ") + append(error.errorCodeName) + append(": ") + append(error.message ?: error.cause?.message ?: "?") + } + com.sulkta.straw.util.strawLogW("StrawPlayer") { "$msg" } + runCatching { + Toast.makeText(context, msg.take(160), Toast.LENGTH_LONG).show() + } + } + } + exoPlayer.addListener(listener) + onDispose { exoPlayer.removeListener(listener) } + } + // PiP setup: on Android 12+ tell the OS this activity can auto-enter // PiP, so when the user presses Home or swipes away the video shrinks // into a floating window instead of pausing/exiting. Aspect ratio is