Add default audio/subtitle language config and in-TUI settings editor
* New audio_lang / subtitle_lang fields in config (file, CLI flags, env vars). Forwarded to mpv's alang/slang as a comma-separated ISO 639 priority list, e.g. "jpn,eng". * New Settings screen (press `s` in the main view) for editing server, user, password, and language preferences without leaving the app. Credential changes trigger re-authentication against the new endpoint; on success the libraries/resume list is refreshed. Everything is persisted back to the config file the app loaded from. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
a9495389e8
commit
f0ebb4b9a4
8 files changed
+342
-13
No files matched your search
+20
-3
@@ -1,6 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use ratatui::widgets::ListState;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::emby::{Client, Item};
|
||||
use crate::player::Player;
|
||||
|
||||
@@ -40,10 +42,20 @@ pub struct App {
|
||||
/// brings you to the same season/series row, not row 0).
|
||||
pub selection_stack: Vec<usize>,
|
||||
pub status: String,
|
||||
/// Live, editable configuration. Mirrors what's on disk; the settings
|
||||
/// screen mutates this and persists it through `config_path`.
|
||||
pub config: Config,
|
||||
/// Where to persist updated settings. `None` when no writable location was
|
||||
/// determined (rare; the settings screen will then refuse to save).
|
||||
pub config_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub async fn new(client: Client) -> Result<Self> {
|
||||
pub async fn new(
|
||||
client: Client,
|
||||
config: Config,
|
||||
config_path: Option<PathBuf>,
|
||||
) -> Result<Self> {
|
||||
let libraries = client.views().await?;
|
||||
// Best-effort: don't fail startup if the resume endpoint hiccups.
|
||||
let resume = client.resume_items(20).await.unwrap_or_default();
|
||||
@@ -72,6 +84,8 @@ impl App {
|
||||
breadcrumbs: vec![],
|
||||
selection_stack: vec![],
|
||||
status,
|
||||
config,
|
||||
config_path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -228,7 +242,10 @@ impl App {
|
||||
self.status = format!("Playing {title}...");
|
||||
let start_seconds = (*start_ticks as f64) / 10_000_000.0;
|
||||
let reporter = self.client.reporter(id.clone());
|
||||
let mut player = Player::new()?;
|
||||
let mut player = Player::new(
|
||||
self.config.audio_lang.as_deref(),
|
||||
self.config.subtitle_lang.as_deref(),
|
||||
)?;
|
||||
let r = player.play_blocking(&url, title, start_seconds, Some(&reporter));
|
||||
self.status = match &r {
|
||||
Ok(_) => format!("Finished: {title}"),
|
||||
@@ -261,7 +278,7 @@ fn restore_selection(saved: Option<usize>, len: usize) -> Option<usize> {
|
||||
Some(saved.unwrap_or(0).min(len - 1))
|
||||
}
|
||||
|
||||
fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> {
|
||||
pub(crate) fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> {
|
||||
let mut entries: Vec<Item> = Vec::with_capacity(libraries.len() + 1);
|
||||
if !resume.is_empty() {
|
||||
entries.push(Item {
|
||||
|
||||
Reference in new issue
Block a user