mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
feat(app,player): choose audio and subtitle languages in desktop Settings
The phones have had this, and the desktop could only set languages from the player, after a file had already opened in the wrong one. Settings now has an audio language and a subtitle choice: off, the file's default, or a language. The names come from the list pstr-player already names tracks with, now exported as LANGUAGES.
This commit is contained in:
1 parent
eb67df0aa0
commit
5c15f45ace
4 files changed
+115
-8
No files matched your search
@@ -223,6 +223,13 @@ pub enum Action {
|
|||||||
PlayAdjacent(Adjacent),
|
PlayAdjacent(Adjacent),
|
||||||
/// Start or stop the next episode playing on its own at the end of one.
|
/// Start or stop the next episode playing on its own at the end of one.
|
||||||
SetAutoplay(bool),
|
SetAutoplay(bool),
|
||||||
|
/// The audio and subtitle languages a file is opened with, for titles
|
||||||
|
/// without a choice of their own. `subtitles` false is "off".
|
||||||
|
SetLanguages {
|
||||||
|
audio: Option<String>,
|
||||||
|
subtitles: bool,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
},
|
||||||
/// Play faster or slower, and keep doing so for the next file.
|
/// Play faster or slower, and keep doing so for the next file.
|
||||||
SetSpeed(f64),
|
SetSpeed(f64),
|
||||||
/// Seek past openings and credits without being asked.
|
/// Seek past openings and credits without being asked.
|
||||||
@@ -1877,6 +1884,17 @@ impl App {
|
|||||||
prefs.autoplay_next = autoplay;
|
prefs.autoplay_next = autoplay;
|
||||||
self.engine.set_playback_prefs(prefs, true);
|
self.engine.set_playback_prefs(prefs, true);
|
||||||
}
|
}
|
||||||
|
Action::SetLanguages {
|
||||||
|
audio,
|
||||||
|
subtitles,
|
||||||
|
subtitle,
|
||||||
|
} => {
|
||||||
|
let mut prefs = self.engine.playback_prefs();
|
||||||
|
prefs.audio_language = audio;
|
||||||
|
prefs.subtitles = subtitles;
|
||||||
|
prefs.subtitle_language = subtitle;
|
||||||
|
self.engine.set_playback_prefs(prefs, true);
|
||||||
|
}
|
||||||
Action::SetAppearance(appearance) => {
|
Action::SetAppearance(appearance) => {
|
||||||
self.engine.set_appearance(appearance);
|
self.engine.set_appearance(appearance);
|
||||||
theme::apply(ctx, appearance);
|
theme::apply(ctx, appearance);
|
||||||
@@ -2218,6 +2236,7 @@ impl eframe::App for App {
|
|||||||
let prefs = ui::settings::Prefs {
|
let prefs = ui::settings::Prefs {
|
||||||
autoplay: playback_prefs.autoplay_next,
|
autoplay: playback_prefs.autoplay_next,
|
||||||
auto_skip: playback_prefs.auto_skip,
|
auto_skip: playback_prefs.auto_skip,
|
||||||
|
playback: &playback_prefs,
|
||||||
appearance: self.engine.appearance(),
|
appearance: self.engine.appearance(),
|
||||||
desktop: self.engine.desktop_prefs(),
|
desktop: self.engine.desktop_prefs(),
|
||||||
cache_used: (self.page == Page::Settings)
|
cache_used: (self.page == Page::Settings)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
use pstr_core::appearance::{Accent, Appearance, Flavor, Tiles};
|
use pstr_core::appearance::{Accent, Appearance, Flavor, Tiles};
|
||||||
use pstr_core::metadata::{MetadataConfig, ProviderId, TitleNames};
|
use pstr_core::metadata::{MetadataConfig, ProviderId, TitleNames};
|
||||||
|
use pstr_core::prefs::PlaybackPrefs;
|
||||||
|
use pstr_player::LANGUAGES;
|
||||||
|
|
||||||
use crate::app::Action;
|
use crate::app::Action;
|
||||||
use crate::desktop_prefs::{CACHE_CHOICES, DesktopPrefs};
|
use crate::desktop_prefs::{CACHE_CHOICES, DesktopPrefs};
|
||||||
@@ -19,9 +21,11 @@ use crate::ui;
|
|||||||
/// they are read-only, and passing them one by one is what pushed this
|
/// they are read-only, and passing them one by one is what pushed this
|
||||||
/// function past the argument count anybody can read.
|
/// function past the argument count anybody can read.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Prefs {
|
pub struct Prefs<'a> {
|
||||||
pub autoplay: bool,
|
pub autoplay: bool,
|
||||||
pub auto_skip: bool,
|
pub auto_skip: bool,
|
||||||
|
/// The languages, read from here.
|
||||||
|
pub playback: &'a PlaybackPrefs,
|
||||||
pub appearance: Appearance,
|
pub appearance: Appearance,
|
||||||
pub desktop: DesktopPrefs,
|
pub desktop: DesktopPrefs,
|
||||||
/// What the streaming cache holds, once the shares are open.
|
/// What the streaming cache holds, once the shares are open.
|
||||||
@@ -35,7 +39,7 @@ const COLUMN: f32 = 760.0;
|
|||||||
pub fn show(
|
pub fn show(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
settings: &mut MetadataConfig,
|
settings: &mut MetadataConfig,
|
||||||
prefs: Prefs,
|
prefs: Prefs<'_>,
|
||||||
api_key: &mut String,
|
api_key: &mut String,
|
||||||
actions: &mut Vec<Action>,
|
actions: &mut Vec<Action>,
|
||||||
) {
|
) {
|
||||||
@@ -271,16 +275,18 @@ fn swatch(ui: &mut egui::Ui, appearance: Appearance, accent: Accent) -> egui::Re
|
|||||||
|
|
||||||
/// The playback settings that are not per-file.
|
/// The playback settings that are not per-file.
|
||||||
///
|
///
|
||||||
/// Volume, audio language, subtitle language and speed are all set from the
|
/// Volume and speed are set from the player itself, where a viewer can hear
|
||||||
/// player itself, where a viewer can hear the result.
|
/// the result. The languages are here as well as there: what a first episode
|
||||||
fn playback_form(ui: &mut egui::Ui, prefs: Prefs, actions: &mut Vec<Action>) {
|
/// opens with is decided before there is a player to choose from.
|
||||||
|
fn playback_form(ui: &mut egui::Ui, prefs: Prefs<'_>, actions: &mut Vec<Action>) {
|
||||||
ui::widgets::settings_group(
|
ui::widgets::settings_group(
|
||||||
ui,
|
ui,
|
||||||
"Playback",
|
"Playback",
|
||||||
Some(
|
Some(
|
||||||
"Volume, audio, subtitles and speed are set from the player, and remembered from there.",
|
"Volume and speed are set from the player. A language chosen in the player for one show is kept for that show.",
|
||||||
),
|
),
|
||||||
|ui| {
|
|ui| {
|
||||||
|
languages(ui, prefs.playback, actions);
|
||||||
let mut autoplay = prefs.autoplay;
|
let mut autoplay = prefs.autoplay;
|
||||||
ui::widgets::settings_row(
|
ui::widgets::settings_row(
|
||||||
ui,
|
ui,
|
||||||
@@ -322,8 +328,80 @@ fn playback_form(ui: &mut egui::Ui, prefs: Prefs, actions: &mut Vec<Action>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The audio and subtitle languages every file opens with, until a show has a
|
||||||
|
/// choice of its own.
|
||||||
|
fn languages(ui: &mut egui::Ui, playback: &PlaybackPrefs, actions: &mut Vec<Action>) {
|
||||||
|
let name = |tag: &Option<String>| match tag {
|
||||||
|
Some(tag) => pstr_player::language_name(tag),
|
||||||
|
None => "The file's default".to_owned(),
|
||||||
|
};
|
||||||
|
let audio = playback.audio_language.clone();
|
||||||
|
ui::widgets::settings_row(
|
||||||
|
ui,
|
||||||
|
"Audio language",
|
||||||
|
Some("Played when a file has it."),
|
||||||
|
|ui| {
|
||||||
|
egui::ComboBox::from_id_salt("audio-language")
|
||||||
|
.selected_text(name(&audio))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for choice in
|
||||||
|
std::iter::once(None).chain(LANGUAGES.map(|tag| Some(tag.to_owned())))
|
||||||
|
{
|
||||||
|
if ui
|
||||||
|
.selectable_label(audio == choice, name(&choice))
|
||||||
|
.clicked()
|
||||||
|
&& audio != choice
|
||||||
|
{
|
||||||
|
actions.push(Action::SetLanguages {
|
||||||
|
audio: choice,
|
||||||
|
subtitles: playback.subtitles,
|
||||||
|
subtitle: playback.subtitle_language.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Off, the file's default, or a language: "off" is a decision of its
|
||||||
|
// own, which no language expresses.
|
||||||
|
let subtitle = playback
|
||||||
|
.subtitles
|
||||||
|
.then(|| playback.subtitle_language.clone());
|
||||||
|
let subtitle_name = |choice: &Option<Option<String>>| match choice {
|
||||||
|
None => "Off".to_owned(),
|
||||||
|
Some(tag) => name(tag),
|
||||||
|
};
|
||||||
|
ui::widgets::settings_row(
|
||||||
|
ui,
|
||||||
|
"Subtitles",
|
||||||
|
Some("Shown when a file has them in this language."),
|
||||||
|
|ui| {
|
||||||
|
egui::ComboBox::from_id_salt("subtitle-language")
|
||||||
|
.selected_text(subtitle_name(&subtitle))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
let choices = [None, Some(None)]
|
||||||
|
.into_iter()
|
||||||
|
.chain(LANGUAGES.map(|tag| Some(Some(tag.to_owned()))));
|
||||||
|
for choice in choices {
|
||||||
|
if ui
|
||||||
|
.selectable_label(subtitle == choice, subtitle_name(&choice))
|
||||||
|
.clicked()
|
||||||
|
&& subtitle != choice
|
||||||
|
{
|
||||||
|
actions.push(Action::SetLanguages {
|
||||||
|
audio: playback.audio_language.clone(),
|
||||||
|
subtitles: choice.is_some(),
|
||||||
|
subtitle: choice.flatten(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// The streaming cache: how big it may get, and a way to empty it.
|
/// The streaming cache: how big it may get, and a way to empty it.
|
||||||
fn storage_form(ui: &mut egui::Ui, prefs: Prefs, actions: &mut Vec<Action>) {
|
fn storage_form(ui: &mut egui::Ui, prefs: Prefs<'_>, actions: &mut Vec<Action>) {
|
||||||
ui::widgets::settings_group(
|
ui::widgets::settings_group(
|
||||||
ui,
|
ui,
|
||||||
"Storage",
|
"Storage",
|
||||||
|
|||||||
@@ -52,4 +52,4 @@ pub use player::{
|
|||||||
};
|
};
|
||||||
pub use registry::{PROTOCOL, StreamHandle, StreamRegistry};
|
pub use registry::{PROTOCOL, StreamHandle, StreamRegistry};
|
||||||
pub use render::VideoRenderer;
|
pub use render::VideoRenderer;
|
||||||
pub use tracks::{Track, TrackKind, language_name, pick_track};
|
pub use tracks::{LANGUAGES, Track, TrackKind, language_name, pick_track};
|
||||||
@@ -190,6 +190,16 @@ fn flag(mpv: &Mpv, index: i64, field: &str) -> bool {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The languages offered by name where a preference is chosen ahead of any
|
||||||
|
/// file, as the three-letter tag a Matroska file carries. The same list
|
||||||
|
/// [`language_name`] knows, and the phones' `LANGUAGES`; mpv treats the two-
|
||||||
|
/// and three-letter forms as one.
|
||||||
|
pub const LANGUAGES: [&str; 29] = [
|
||||||
|
"jpn", "eng", "ger", "fre", "spa", "ita", "por", "dut", "rus", "pol", "swe", "dan", "nor",
|
||||||
|
"fin", "cze", "hun", "tur", "ara", "heb", "hin", "kor", "chi", "tha", "vie", "ukr", "rum",
|
||||||
|
"gre", "ind", "may",
|
||||||
|
];
|
||||||
|
|
||||||
/// A readable name for a language tag, falling back to the tag itself.
|
/// A readable name for a language tag, falling back to the tag itself.
|
||||||
///
|
///
|
||||||
/// Deliberately a short list rather than a full ISO 639 table: it covers what
|
/// Deliberately a short list rather than a full ISO 639 table: it covers what
|
||||||
|
|||||||
Reference in new issue
Block a user