DASH HD playback — WIP behind TORTTUBE_DASH=1 + upstream notes

Sidecar resolve_dash op shipped — returns rustypipe's full video_only_streams
+ audio_streams (16+ representations for NGGYU, from 360p H.264 through 4K
AV1). Addon _build_dash_mpd assembles a valid on-demand MPEG-DASH manifest
filtered to H.264 ≤1080p + best AAC audio.

Two unblocked-by-WIP issues surfaced during integration:
- inputstream.adaptive's libcurl can't open file:// URLs (logged in
  docs/upstream.md as an enhancement-target).
- Rapid Player.Open retries can trigger Kodi's 'two concurrent
  busydialogs' fatal exit; need lifecycle hardening before re-enabling.

Pivoted to localhost HTTP-server serving (ThreadingHTTPServer on a
port-0 socket, MPD bytes captured in a per-instance handler subclass).
Lifecycle: server.shutdown() runs in a finally block after the
SponsorBlockMonitor watcher exits. Works in isolation but Kodi crashed
under rapid retry conditions — needs more testing.

For v0.0.5: DASH path is gated behind TORTTUBE_DASH=1 env var; default
falls through to the stable yt-dlp progressive 360p path that's been
verified live. M7 milestone added to track the remaining work; PRs
to inputstream.adaptive + Kodi candidates logged in docs/upstream.md.
This commit is contained in:
Kayos 2026-05-23 11:14:56 -07:00
parent f610965fcf
commit 45e1306bf3
6 changed files with 329 additions and 14 deletions

View file

@ -32,6 +32,11 @@ enum Request {
/// no inputstream.adaptive needed. Slower than `resolve` but always
/// gives a working stream.
ResolvePlay { id: String },
/// DASH-ready resolve: returns rustypipe's full `video_only_streams`
/// + `audio_streams` arrays so the addon can build a DASH MPD with
/// all quality rungs and feed it to inputstream.adaptive. Unlocks
/// 1080p+ via H.264 hardware decode on RPi.
ResolveDash { id: String },
Rip { id: String, dest_dir: String },
Sponsorblock {
id: String,
@ -131,6 +136,10 @@ async fn handle_line(line: &str) -> Response {
Ok(v) => Response::ok(v),
Err(e) => e.into(),
},
Request::ResolveDash { id } => match resolve::resolve_dash(&id).await {
Ok(v) => Response::ok(v),
Err(e) => e.into(),
},
Request::Rip { id, dest_dir } => match rip::rip(&id, &dest_dir).await {
Ok(v) => Response::ok(v),
Err(e) => e.into(),

View file

@ -5,6 +5,38 @@ use serde_json::Value;
use crate::{run_yt_dlp, HandlerError};
/// DASH-ready resolve: returns rustypipe's full `video_only_streams` +
/// `audio_streams` arrays + `details`. The Python addon builds an MPD
/// from these and hands it to inputstream.adaptive — unlocks 1080p+ via
/// H.264 hardware decode on the RPi (vs the 360p ceiling on progressive).
pub(crate) async fn resolve_dash(id: &str) -> Result<Value, HandlerError> {
use rustypipe::client::RustyPipe;
let rp = RustyPipe::new();
let player = rp
.query()
.player(id)
.await
.map_err(|e| classify_rustypipe_error(&e))?;
let details_json = serde_json::to_value(&player.details)
.map_err(|e| HandlerError::Internal(format!("serialize details: {e}")))?;
let video_streams = serde_json::to_value(&player.video_only_streams)
.map_err(|e| HandlerError::Internal(format!("serialize video_only_streams: {e}")))?;
let audio_streams = serde_json::to_value(&player.audio_streams)
.map_err(|e| HandlerError::Internal(format!("serialize audio_streams: {e}")))?;
tracing::info!(id, "resolve_dash ok via rustypipe");
Ok(serde_json::json!({
"source": "rustypipe",
"details": details_json,
"video_only_streams": video_streams,
"audio_streams": audio_streams,
"expires_in_seconds": player.expires_in_seconds,
}))
}
/// Playback-ready single-URL resolve. Asks yt-dlp for `best[ext=mp4]/best` —
/// a combined audio+video format that Kodi can play as a plain HTTP URL.
/// Slower than `resolve()` (~3-5s) but guarantees a working stream.