Resume Functionality Restored and Transcoding Entirely Removed.

This commit is contained in:
2026-02-16 02:48:38 -06:00
parent dfcf3fcf2a
commit c030bbb155
2 changed files with 8 additions and 126 deletions

View File

@@ -7,8 +7,6 @@ pub struct Config {
pub base_url: String,
pub access_token: Option<String>,
pub user_id: Option<String>,
pub transcode_mbps: Option<u32>,
pub transcode_default_on: Option<bool>,
}
@@ -47,8 +45,6 @@ fn parse_config(s: &str) -> Result<Config, String> {
let mut base_url: Option<String> = None;
let mut access_token: Option<String> = None;
let mut user_id: Option<String> = None;
let mut transcode_mbps: Option<u32> = None;
let mut transcode_default_on: Option<bool> = None;
for (i, line) in s.lines().enumerate() {
let line = line.trim();
@@ -72,18 +68,6 @@ fn parse_config(s: &str) -> Result<Config, String> {
"base_url" => base_url = Some(val),
"access_token" => access_token = Some(val),
"user_id" => user_id = Some(val),
"transcode_mbps" => {
transcode_mbps = val.parse::<u32>().ok();
}
"transcode_default_on" => {
transcode_default_on = match val.as_str() {
"true" => Some(true),
"false" => Some(false),
_ => None,
};
}
_ => {}
}
}
@@ -94,8 +78,6 @@ fn parse_config(s: &str) -> Result<Config, String> {
base_url,
access_token,
user_id,
transcode_mbps,
transcode_default_on,
})
}
@@ -118,14 +100,6 @@ pub fn save_config(cfg: &Config) -> Result<(), ConfigError> {
out.push_str(&format!("user_id = \"{}\"\n", u));
}
if let Some(m) = cfg.transcode_mbps {
out.push_str(&format!("transcode_mbps = {}\n", m));
}
if let Some(b) = cfg.transcode_default_on {
out.push_str(&format!("transcode_default_on = {}\n", b));
}
std::fs::write(path, out)?;
Ok(())
}

View File

@@ -182,9 +182,6 @@ struct App {
autoplay_eof_latched: bool,
autoplay_prev_time_pos: Option<f64>,
transcode_on: bool,
transcode_mbps: u32,
// Cache: container item id -> has resumable episode under it
parent_progress: std::collections::HashMap<String, bool>,
@@ -200,16 +197,6 @@ impl Default for App {
fn default() -> Self {
let loaded = core::config::load_config().ok();
let default_mbps = loaded
.as_ref()
.and_then(|c| c.transcode_mbps)
.unwrap_or(5);
let default_on = loaded
.as_ref()
.and_then(|c| c.transcode_default_on)
.unwrap_or(false);
let (view_stack, config_status) = match &loaded {
Some(cfg) if cfg.access_token.is_some() => (
vec![View::Categories],
@@ -263,8 +250,6 @@ impl Default for App {
autoplay_next_index: None,
autoplay_eof_latched: false,
autoplay_prev_time_pos: None,
transcode_on: default_on,
transcode_mbps: default_mbps,
parent_progress: std::collections::HashMap::new(),
}
}
@@ -415,7 +400,6 @@ fn handle_key(app: &mut App, code: KeyCode) {
}
return;
}
KeyCode::Char('C') => { reload_config(app); return; }
KeyCode::Char(c) => {
match app.login_focus {
@@ -607,7 +591,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.mpv_now_playing = Some(item_name);
// If mpv running -> replace, else spawn
let url = build_jellyfin_play_url(&cfg, &item_id, app.transcode_on, app.transcode_mbps);
let url = build_jellyfin_play_url(&cfg, &item_id);
if let Some(mpv) = &app.mpv {
match mpv_load_url(&cfg, mpv, &url) {
@@ -646,7 +630,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.autoplay_next_index = if selected + 1 < len { Some(selected + 1) } else { None };
// load/replace or spawn
let url = build_jellyfin_play_url(&cfg, &item_id, app.transcode_on, app.transcode_mbps);
let url = build_jellyfin_play_url(&cfg, &item_id);
if let Some(mpv) = &app.mpv {
match mpv_load_url(&cfg, mpv, &url) {
@@ -666,15 +650,6 @@ fn handle_key(app: &mut App, code: KeyCode) {
}
return;
}
KeyCode::Char('t') => {
app.transcode_on = !app.transcode_on;
app.status = if app.transcode_on {
format!("Transcoding ON ({}Mbps)", app.transcode_mbps)
} else {
"Transcoding OFF".into()
};
return;
}
KeyCode::Char('r') => {
app.autoplay = false;
app.autoplay_next_index = None;
@@ -699,7 +674,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.mpv_now_playing = Some(item_name);
let url = build_jellyfin_play_url(&cfg, &item_id, app.transcode_on, app.transcode_mbps);
let url = build_jellyfin_play_url(&cfg, &item_id);
if let Some(mpv) = &app.mpv {
match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) {
@@ -753,7 +728,7 @@ fn handle_key(app: &mut App, code: KeyCode) {
app.autoplay_next_index = if selected + 1 < len { Some(selected + 1) } else { None };
app.autoplay_eof_latched = false;
let url = build_jellyfin_play_url(&cfg, &item_id, app.transcode_on, app.transcode_mbps);
let url = build_jellyfin_play_url(&cfg, &item_id);
if let Some(mpv) = &app.mpv {
match mpv_cmd(&mpv.socket_path, &format!(r#"{{"command":["loadfile","{}","replace"]}}"#, url)) {
@@ -1444,14 +1419,8 @@ fn ui(frame: &mut Frame, app: &App) {
.map(|c| c.base_url.as_str())
.unwrap_or("-");
let tx_line = if app.transcode_on {
format!("Transcoding On ({}Mbps)", app.transcode_mbps)
} else {
"Transcoding Off".to_string()
};
let right_text = format!(
"{connected}\n{tx_line}\n\np/P: play/autoplay\nq: quit\n/help: more help"
"{connected}\n\np/P: play/autoplay\nq: quit\n/help: more help"
);
frame.render_widget(
@@ -2034,7 +2003,7 @@ fn autoplay_tick(app: &mut App) {
};
// Load next item
let url = build_jellyfin_play_url(&cfg, &next_id, app.transcode_on, app.transcode_mbps);
let url = build_jellyfin_play_url(&cfg, &next_id);
if let Err(e) = mpv_load_url(&cfg, mpv, &url) {
app.autoplay = false;
@@ -2532,70 +2501,9 @@ fn clamp_mbps_to_bps(mbps: u32) -> u32 {
mbps.saturating_mul(1_000_000) // Mb/s -> bits/s
}
fn build_jellyfin_play_url(
cfg: &core::config::Config,
item_id: &str,
transcode_on: bool,
transcode_mbps: u32,
) -> String {
fn build_jellyfin_play_url(cfg: &core::config::Config, item_id: &str) -> String {
let base = cfg.base_url.trim_end_matches('/');
let token = cfg
.access_token
.as_deref()
.unwrap_or(""); // if empty, server likely rejects anyway
let bps = clamp_mbps_to_bps(transcode_mbps);
if !transcode_on {
// Direct stream.
//
// We intentionally keep this "static=true" so mpv can see the full timeline/duration
// (many servers provide Content-Length only for static streams).
//
// Resume is implemented via mpv seeking (not startTimeTicks in the URL) to avoid the
// Jellyfin behavior where startTimeTicks may be ignored for static streams.
return format!(
"{}/Videos/{}/stream?static=true&api_key={}",
base, item_id, token
);
}
// Transcoded stream via HLS (fixes "duration == buffered" + enables seeking).
//
// IMPORTANT: Use Jellyfin's documented query param names:
// - videoBitRate (capital R)
// - segmentContainer
//
// Resume is implemented via mpv seeking (not startTimeTicks in the URL).
let mut url = format!(
"{}/Videos/{}/master.m3u8?api_key={}&static=false",
base, item_id, token
);
url.push_str(&format!("&maxStreamingBitrate={}", bps));
url.push_str(&format!("&videoBitrate={}", bps)); // NOTE: lower-case 'r' matches your working version
url.push_str(&format!("&audioBitrate={}", bps));
url.push_str("&enableAutoStreamCopy=false");
url.push_str("&videoCodec=h264");
url.push_str("&audioCodec=aac");
url.push_str("&transcodingContainer=ts"); // NOTE: this is what your working build used
url
}
fn reload_config(app: &mut App) {
if let Ok(cfg) = core::config::load_config() {
app.transcode_mbps = cfg.transcode_mbps.unwrap_or(5);
app.transcode_on = cfg.transcode_default_on.unwrap_or(false);
app.config = Some(cfg);
app.status = "Reloaded config".into();
} else {
app.status = "Failed to reload config".into();
}
format!("{}/Items/{}/Download", base, item_id)
}
fn mpv_cmd_reply(socket: &PathBuf, cmd: &str) -> Result<Value, String> {