Add user preferences for random playback

This commit is contained in:
Elias Projahn 2022-04-08 19:59:49 +02:00
parent 3ae5727f39
commit 5c64bdef7e
14 changed files with 402 additions and 156 deletions

View file

@ -1,7 +1,11 @@
use gio::traits::SettingsExt;
use log::warn;
use musicus_database::Database;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use std::{
cell::{Cell, RefCell},
path::PathBuf,
rc::Rc,
};
use tokio::sync::{broadcast, broadcast::Sender};
pub use musicus_database as db;
@ -55,6 +59,12 @@ pub struct Backend {
/// The player handling playlist and playback. This can be assumed to exist, when the state is
/// set to BackendState::Ready.
player: RefCell<Option<Rc<Player>>>,
/// Whether to keep playing random tracks after the playlist ends.
keep_playing: Cell<bool>,
/// Whether to choose full recordings for random playback.
play_full_recordings: Cell<bool>,
}
impl Backend {
@ -73,6 +83,8 @@ impl Backend {
library_updated_sender,
database: RefCell::new(None),
player: RefCell::new(None),
keep_playing: Cell::new(false),
play_full_recordings: Cell::new(true),
}
}
@ -83,8 +95,12 @@ impl Backend {
/// Initialize the backend. A state callback should already have been registered using
/// [`set_state_cb()`] to react to the result.
pub fn init(&self) -> Result<()> {
self.init_library()?;
pub fn init(self: Rc<Self>) -> Result<()> {
self.keep_playing.set(self.settings.boolean("keep-playing"));
self.play_full_recordings
.set(self.settings.boolean("play-full-recordings"));
Rc::clone(&self).init_library()?;
match self.get_music_library_path() {
None => self.set_state(BackendState::NoMusicLibrary),
@ -94,12 +110,68 @@ impl Backend {
Ok(())
}
/// Whether to keep playing random tracks after the playlist ends.
pub fn keep_playing(&self) -> bool {
self.keep_playing.get()
}
/// Set whether to keep playing random tracks after the playlist ends.
pub fn set_keep_playing(self: Rc<Self>, keep_playing: bool) {
if let Err(err) = self.settings.set_boolean("keep-playing", keep_playing) {
warn!(
"The preference \"keep-playing\" could not be saved using GSettings. It will most \
likely not be available at the next startup. Error message: {}",
err
);
}
self.keep_playing.set(keep_playing);
self.update_track_generator();
}
/// Whether to choose full recordings for random playback.
pub fn play_full_recordings(&self) -> bool {
self.play_full_recordings.get()
}
/// Set whether to choose full recordings for random playback.
pub fn set_play_full_recordings(self: Rc<Self>, play_full_recordings: bool) {
if let Err(err) = self
.settings
.set_boolean("play-full-recordings", play_full_recordings)
{
warn!(
"The preference \"play-full-recordings\" could not be saved using GSettings. It \
will most likely not be available at the next startup. Error message: {}",
err
);
}
self.play_full_recordings.set(play_full_recordings);
self.update_track_generator();
}
/// Set the current state and notify the user interface.
fn set_state(&self, state: BackendState) {
if let Some(cb) = &*self.state_cb.borrow() {
cb(state);
}
}
/// Apply the current track generation settings.
fn update_track_generator(self: Rc<Self>) {
if let Some(player) = self.get_player() {
if self.keep_playing() {
if self.play_full_recordings() {
player.set_track_generator(Some(RandomRecordingGenerator::new(self)));
} else {
player.set_track_generator(Some(RandomTrackGenerator::new(self)));
}
} else {
player.set_track_generator(None::<RandomRecordingGenerator>);
}
}
}
}
impl Default for Backend {

View file

@ -1,6 +1,5 @@
use crate::{Backend, BackendState, Player, Result};
use gio::prelude::*;
use glib::clone;
use log::warn;
use musicus_database::Database;
use std::path::PathBuf;
@ -8,7 +7,7 @@ use std::rc::Rc;
impl Backend {
/// Initialize the music library if it is set in the settings.
pub(super) fn init_library(&self) -> Result<()> {
pub(super) fn init_library(self: Rc<Self>) -> Result<()> {
let path = self.settings.string("music-library-path");
if !path.is_empty() {
self.set_music_library_path_priv(PathBuf::from(path.to_string()))?;
@ -18,7 +17,7 @@ impl Backend {
}
/// Set the path to the music library folder and connect to the database.
pub fn set_music_library_path(&self, path: PathBuf) -> Result<()> {
pub fn set_music_library_path(self: Rc<Self>, path: PathBuf) -> Result<()> {
if let Err(err) = self
.settings
.set_string("music-library-path", path.to_str().unwrap())
@ -34,7 +33,7 @@ impl Backend {
}
/// Set the path to the music library folder and and connect to the database.
pub fn set_music_library_path_priv(&self, path: PathBuf) -> Result<()> {
pub fn set_music_library_path_priv(self: Rc<Self>, path: PathBuf) -> Result<()> {
self.set_state(BackendState::Loading);
self.music_library_path.replace(Some(path.clone()));
@ -46,14 +45,10 @@ impl Backend {
self.database.replace(Some(Rc::clone(&database)));
let player = Player::new(path);
// Keep adding random tracks in case the playlist ends.
player.set_generate_next_track_cb(clone!(@weak database => @default-panic, move || {
database.random_track().unwrap()
}));
self.player.replace(Some(player));
Rc::clone(&self).update_track_generator();
self.set_state(BackendState::Ready);
Ok(())

View file

@ -1,4 +1,4 @@
use crate::{Error, Result};
use crate::{Backend, Error, Result};
use glib::clone;
use gstreamer_player::prelude::*;
use musicus_database::Track;
@ -17,7 +17,7 @@ pub struct Player {
current_track: Cell<Option<usize>>,
playing: Cell<bool>,
duration: Cell<u64>,
generate_next_track_cb: RefCell<Option<Box<dyn Fn() -> Track>>>,
track_generator: RefCell<Option<Box<dyn TrackGenerator>>>,
playlist_cbs: RefCell<Vec<Box<dyn Fn(Vec<Track>)>>>,
track_cbs: RefCell<Vec<Box<dyn Fn(usize)>>>,
duration_cbs: RefCell<Vec<Box<dyn Fn(u64)>>>,
@ -45,7 +45,7 @@ impl Player {
current_track: Cell::new(None),
playing: Cell::new(false),
duration: Cell::new(0),
generate_next_track_cb: RefCell::new(None),
track_generator: RefCell::new(None),
playlist_cbs: RefCell::new(Vec::new()),
track_cbs: RefCell::new(Vec::new()),
duration_cbs: RefCell::new(Vec::new()),
@ -146,8 +146,11 @@ impl Player {
result
}
pub fn set_generate_next_track_cb<F: Fn() -> Track + 'static>(&self, cb: F) {
self.generate_next_track_cb.replace(Some(Box::new(cb)));
pub fn set_track_generator<G: TrackGenerator + 'static>(&self, generator: Option<G>) {
self.track_generator.replace(match generator {
Some(generator) => Some(Box::new(generator)),
None => None,
});
}
pub fn add_playlist_cb<F: Fn(Vec<Track>) + 'static>(&self, cb: F) {
@ -190,12 +193,17 @@ impl Player {
self.playing.get()
}
pub fn add_item(&self, item: Track) -> Result<()> {
/// Add some items to the playlist.
pub fn add_items(&self, mut items: Vec<Track>) -> Result<()> {
if items.is_empty() {
return Ok(())
}
let was_empty = {
let mut playlist = self.playlist.borrow_mut();
let was_empty = playlist.is_empty();
playlist.push(item);
playlist.append(&mut items);
was_empty
};
@ -282,8 +290,8 @@ impl Player {
}
pub fn has_next(&self) -> bool {
if self.generate_next_track_cb.borrow().is_some() {
true
if let Some(generator) = &*self.track_generator.borrow() {
generator.has_next()
} else if let Some(current_track) = self.current_track.get() {
let playlist = self.playlist.borrow();
current_track + 1 < playlist.len()
@ -294,13 +302,19 @@ impl Player {
pub fn next(&self) -> Result<()> {
let current_track = self.current_track.get();
let cb = self.generate_next_track_cb.borrow();
let generator = self.track_generator.borrow();
if let Some(current_track) = current_track {
if current_track + 1 >= self.playlist.borrow().len() {
if let Some(cb) = &*cb {
let new_track = cb();
self.add_item(new_track)?;
if let Some(generator) = &*generator {
let items = generator.next();
if !items.is_empty() {
self.add_items(items)?;
} else {
return Err(Error::Other(String::from(
"Track generator failed to generate next track.",
)));
}
} else {
return Err(Error::Other(String::from("No existing next track.")));
}
@ -309,9 +323,15 @@ impl Player {
self.set_track(current_track + 1)?;
Ok(())
} else if let Some(cb) = &*cb {
let new_track = cb();
self.add_item(new_track)?;
} else if let Some(generator) = &*generator {
let items = generator.next();
if !items.is_empty() {
self.add_items(items)?;
} else {
return Err(Error::Other(String::from(
"Track generator failed to generate next track.",
)));
}
Ok(())
} else {
@ -406,3 +426,58 @@ impl Player {
self.mpris.set_can_play(false);
}
}
/// Generator for new tracks to be appended to the playlist.
pub trait TrackGenerator {
/// Whether the generator will provide a next track if asked.
fn has_next(&self) -> bool;
/// Provide the next track.
///
/// This function should always return at least one track in a state where
/// `has_next()` returns `true`.
fn next(&self) -> Vec<Track>;
}
/// A track generator that generates one random track per call.
pub struct RandomTrackGenerator {
backend: Rc<Backend>,
}
impl RandomTrackGenerator {
pub fn new(backend: Rc<Backend>) -> Self {
Self { backend }
}
}
impl TrackGenerator for RandomTrackGenerator {
fn has_next(&self) -> bool {
true
}
fn next(&self) -> Vec<Track> {
vec![self.backend.db().random_track().unwrap()]
}
}
/// A track generator that returns the tracks of one random recording per call.
pub struct RandomRecordingGenerator {
backend: Rc<Backend>,
}
impl RandomRecordingGenerator {
pub fn new(backend: Rc<Backend>) -> Self {
Self { backend }
}
}
impl TrackGenerator for RandomRecordingGenerator {
fn has_next(&self) -> bool {
true
}
fn next(&self) -> Vec<Track> {
let recording = self.backend.db().random_recording().unwrap();
self.backend.db().get_tracks(&recording.id).unwrap()
}
}

View file

@ -76,7 +76,7 @@ impl PersonOrEnsemble {
}
/// Database table data for a recording.
#[derive(Insertable, Queryable, Debug, Clone)]
#[derive(Insertable, Queryable, QueryableByName, Debug, Clone)]
#[table_name = "recordings"]
struct RecordingRow {
pub id: String,
@ -201,6 +201,17 @@ impl Database {
Ok(recording)
}
/// Get a random recording from the database.
pub fn random_recording(&self) -> Result<Recording> {
let row = diesel::sql_query("SELECT * FROM recordings ORDER BY RANDOM() LIMIT 1")
.load::<RecordingRow>(&self.connection)?
.into_iter()
.next()
.ok_or(Error::Other("Failed to find random recording."))?;
self.get_recording_data(row)
}
/// Retrieve all available information on a recording from related tables.
fn get_recording_data(&self, row: RecordingRow) -> Result<Recording> {
let mut performance_descriptions: Vec<Performance> = Vec::new();

View file

@ -5,5 +5,13 @@
<default>""</default>
<summary>Path to the music library folder</summary>
</key>
<key name="keep-playing" type="b">
<default>false</default>
<summary>Keep playing after the playlist ends</summary>
</key>
<key name="play-full-recordings" type="b">
<default>true</default>
<summary>Choose full recordings for random playback</summary>
</key>
</schema>
</schemalist>

View file

@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-10 12:32+0100\n"
"POT-Creation-Date: 2022-04-08 19:52+0200\n"
"PO-Revision-Date: 2022-02-10 12:34+0100\n"
"Last-Translator: \n"
"Language-Team: \n"
@ -29,13 +29,13 @@ msgid "Loading"
msgstr "Lade…"
#: ../res/ui/medium_preview.ui:149 ../res/ui/source_selector.ui:101
#: ../res/ui/medium_editor.ui:152 ../res/ui/medium_editor.ui:189
#: ../res/ui/medium_editor.ui:151 ../res/ui/medium_editor.ui:188
#: ../res/ui/editor.ui:68
msgid "Error"
msgstr "Fehler"
#: ../res/ui/medium_preview.ui:161 ../res/ui/source_selector.ui:113
#: ../res/ui/medium_editor.ui:164 ../res/ui/editor.ui:80
#: ../res/ui/medium_editor.ui:163 ../res/ui/editor.ui:80
msgid "Try again"
msgstr "Nochmal versuchen"
@ -43,29 +43,29 @@ msgstr "Nochmal versuchen"
msgid "Performance"
msgstr "Auftritt"
#: ../res/ui/performance_editor.ui:55
#: ../res/ui/performance_editor.ui:52
msgid "Select a person"
msgstr "Person auswählen"
#: ../res/ui/performance_editor.ui:59 ../res/ui/performance_editor.ui:72
#: ../res/ui/performance_editor.ui:92 ../res/ui/track_set_editor.ui:69
#: ../res/ui/import_screen.ui:154 ../res/ui/preferences.ui:23
#: ../res/ui/recording_editor.ui:83 ../res/ui/work_editor.ui:83
#: ../res/ui/performance_editor.ui:56 ../res/ui/performance_editor.ui:69
#: ../res/ui/performance_editor.ui:89 ../res/ui/track_set_editor.ui:67
#: ../res/ui/import_screen.ui:160 ../res/ui/preferences.ui:23
#: ../res/ui/recording_editor.ui:81 ../res/ui/work_editor.ui:81
#: ../src/import/source_selector.rs:51 ../src/screens/welcome.rs:56
#: ../src/preferences.rs:42
#: ../src/preferences.rs:44
msgid "Select"
msgstr "Auswählen"
#: ../res/ui/performance_editor.ui:68
#: ../res/ui/performance_editor.ui:65
msgid "Select an ensemble"
msgstr "Ensemble auswählen"
#: ../res/ui/performance_editor.ui:81
#: ../res/ui/performance_editor.ui:78
msgid "Select a role"
msgstr "Rolle auswählen"
#: ../res/ui/player_bar.ui:65 ../res/ui/player_screen.ui:97
#: ../res/ui/work_part_editor.ui:59 ../res/ui/work_editor.ui:92
#: ../res/ui/work_part_editor.ui:56 ../res/ui/work_editor.ui:90
msgid "Title"
msgstr "Titel"
@ -113,15 +113,15 @@ msgid "Select tracks"
msgstr "Tracks auswählen"
#: ../res/ui/track_set_editor.ui:51 ../res/ui/recording_editor.ui:18
#: ../res/ui/recording_editor.ui:155
#: ../res/ui/recording_editor.ui:154
msgid "Recording"
msgstr "Aufnahme"
#: ../res/ui/track_set_editor.ui:65
#: ../res/ui/track_set_editor.ui:63
msgid "Select a recording"
msgstr "Aufnahme auswählen"
#: ../res/ui/track_set_editor.ui:89 ../src/screens/recording.rs:30
#: ../res/ui/track_set_editor.ui:88 ../src/screens/recording.rs:30
msgid "Tracks"
msgstr "Tracks"
@ -133,31 +133,31 @@ msgstr "Werkabschnitt"
msgid "Matching metadata"
msgstr "Passende Metadaten"
#: ../res/ui/import_screen.ui:66
#: ../res/ui/import_screen.ui:64
msgid "Loading…"
msgstr "Lade…"
#: ../res/ui/import_screen.ui:87
#: ../res/ui/import_screen.ui:88
msgid "Error while searching for matching metadata"
msgstr "Fehler bei der Suche nach passenden Metadaten"
#: ../res/ui/import_screen.ui:110
#: ../res/ui/import_screen.ui:114
msgid "No matching metadata found"
msgstr "Keine passenden Metadaten gefunden"
#: ../res/ui/import_screen.ui:136
#: ../res/ui/import_screen.ui:144
msgid "Manually add metadata"
msgstr "Metadaten manuell hinzufügen"
#: ../res/ui/import_screen.ui:150
#: ../res/ui/import_screen.ui:156
msgid "Select existing medium"
msgstr "Existierendes Medium auswählen"
#: ../res/ui/import_screen.ui:163
#: ../res/ui/import_screen.ui:169
msgid "Add a new medium"
msgstr "Neues Medium hinzufügen"
#: ../res/ui/import_screen.ui:167
#: ../res/ui/import_screen.ui:173
msgid "Add"
msgstr "Hinzufügen"
@ -165,22 +165,22 @@ msgstr "Hinzufügen"
msgid "Medium"
msgstr "Medium"
#: ../res/ui/medium_editor.ui:75
#: ../res/ui/medium_editor.ui:73
msgid "Name of the medium"
msgstr "Name des Mediums"
#: ../res/ui/medium_editor.ui:99
#: ../res/ui/medium_editor.ui:98
msgid "Recordings"
msgstr "Aufnahmen"
#: ../res/ui/medium_editor.ui:201 ../res/ui/editor.ui:22
#: ../res/ui/medium_editor.ui:200 ../res/ui/editor.ui:22
#: ../src/import/source_selector.rs:50 ../src/screens/welcome.rs:55
#: ../src/preferences.rs:41
#: ../src/preferences.rs:43
msgid "Cancel"
msgstr "Abbrechen"
#: ../res/ui/preferences.ui:11 ../src/editors/ensemble.rs:34
#: ../src/editors/instrument.rs:34 ../src/editors/person.rs:38
#: ../res/ui/preferences.ui:11 ../src/editors/ensemble.rs:35
#: ../src/editors/instrument.rs:35 ../src/editors/person.rs:39
msgid "General"
msgstr "Allgemein"
@ -196,19 +196,41 @@ msgstr "Ordner der Musikbibliothek"
msgid "None selected"
msgstr "Keiner ausgewählt"
#: ../res/ui/preferences.ui:34
msgid "Playlist"
msgstr "Wiedergabeliste"
#: ../res/ui/preferences.ui:38
msgid "Keep playing"
msgstr "Weiter abspielen"
#: ../res/ui/preferences.ui:40
msgid "Whether to keep playing random tracks after the playlist ends."
msgstr "Nach dem Ende der Wiedergabeliste weiter im Zufallsmodus abspielen."
#: ../res/ui/preferences.ui:51
msgid "Choose full recordings"
msgstr "Komplette Aufnahmen abspielen"
#: ../res/ui/preferences.ui:53
msgid ""
"Whether to choose full recordings instead of single tracks for random "
"playback."
msgstr "Im Zufallsmodus vollständige Aufnahmen anstatt einzelner Tracks verwenden."
#: ../res/ui/recording_editor.ui:65 ../res/ui/work_editor.ui:65
msgid "Overview"
msgstr "Überblick"
#: ../res/ui/recording_editor.ui:79
#: ../res/ui/recording_editor.ui:77
msgid "Select a work"
msgstr "Werk auswählen"
#: ../res/ui/recording_editor.ui:92
#: ../res/ui/recording_editor.ui:90
msgid "Comment"
msgstr "Kommentar"
#: ../res/ui/recording_editor.ui:116
#: ../res/ui/recording_editor.ui:115
msgid "Performers"
msgstr "Interpreten"
@ -216,20 +238,20 @@ msgstr "Interpreten"
msgid "Search …"
msgstr "Suchen…"
#: ../res/ui/work_editor.ui:18 ../res/ui/work_editor.ui:182
#: ../res/ui/work_editor.ui:18 ../res/ui/work_editor.ui:181
#: ../src/editors/recording.rs:173
msgid "Work"
msgstr "Werk"
#: ../res/ui/work_editor.ui:79
#: ../res/ui/work_editor.ui:77
msgid "Select a composer"
msgstr "Komponisten auswählen"
#: ../res/ui/work_editor.ui:116
#: ../res/ui/work_editor.ui:115
msgid "Instruments"
msgstr "Instrumente"
#: ../res/ui/work_editor.ui:143
#: ../res/ui/work_editor.ui:142
msgid "Structure"
msgstr "Struktur"
@ -265,44 +287,44 @@ msgstr "Einstellungen"
msgid "About Musicus"
msgstr "Über Musicus"
#: ../src/editors/performance.rs:42
#: ../src/editors/performance.rs:43
msgid "Performer"
msgstr "Interpret"
#: ../src/editors/performance.rs:44
#: ../src/editors/performance.rs:45
msgid "Select either a person or an ensemble as a performer."
msgstr "Wählen Sie entweder eine Person oder ein Ensemble als Interpreten aus."
#: ../src/editors/performance.rs:62
#: ../src/editors/performance.rs:64
msgid "Role"
msgstr "Rolle"
#: ../src/editors/performance.rs:64
#: ../src/editors/performance.rs:66
msgid "Optionally, choose a role to specify what the performer does."
msgstr ""
"Wählen Sie optional eine Rolle aus, die angibt, was der Interpret macht."
#: ../src/editors/ensemble.rs:31 ../src/editors/instrument.rs:31
#: ../src/editors/ensemble.rs:32 ../src/editors/instrument.rs:32
msgid "Name"
msgstr "Name"
#: ../src/editors/ensemble.rs:65
#: ../src/editors/ensemble.rs:66
msgid "Failed to save ensemble!"
msgstr "Ensemble konnte nicht gespeichert werden!"
#: ../src/editors/instrument.rs:65
#: ../src/editors/instrument.rs:66
msgid "Failed to save instrument!"
msgstr "Instrument konnte nicht gespeichert werden!"
#: ../src/editors/person.rs:32
#: ../src/editors/person.rs:33
msgid "First name"
msgstr "Vorname"
#: ../src/editors/person.rs:33
#: ../src/editors/person.rs:34
msgid "Last name"
msgstr "Nachname"
#: ../src/editors/person.rs:72
#: ../src/editors/person.rs:73
msgid "Failed to save person!"
msgstr "Person konnte nicht gespeichert werden!"
@ -310,8 +332,8 @@ msgstr "Person konnte nicht gespeichert werden!"
msgid "Composer"
msgstr "Komponist"
#: ../src/import/track_set_editor.rs:133 ../src/import/medium_preview.rs:171
#: ../src/screens/medium.rs:74 ../src/screens/recording.rs:84
#: ../src/import/track_set_editor.rs:133 ../src/import/medium_preview.rs:170
#: ../src/screens/medium.rs:72 ../src/screens/recording.rs:82
msgid "Unknown"
msgstr "Unbekannt"
@ -339,11 +361,11 @@ msgstr "Person bearbeiten"
msgid "Delete person"
msgstr "Person löschen"
#: ../src/screens/recording.rs:55
#: ../src/screens/recording.rs:53
msgid "Edit recording"
msgstr "Aufnahme bearbeiten"
#: ../src/screens/recording.rs:65
#: ../src/screens/recording.rs:63
msgid "Delete recording"
msgstr "Aufnahme löschen"
@ -357,7 +379,7 @@ msgstr ""
"Musicus wird dort eine neue Datenbank anlegen oder eine bereits existierende "
"öffnen."
#: ../src/screens/welcome.rs:51 ../src/preferences.rs:37
#: ../src/screens/welcome.rs:51 ../src/preferences.rs:39
msgid "Select music library folder"
msgstr "Ordner der Musikbibliothek auswählen"
@ -369,15 +391,15 @@ msgstr "Werk bearbeiten"
msgid "Delete work"
msgstr "Werk löschen"
#: ../src/screens/main.rs:196
#: ../src/screens/main.rs:200
msgid "Musicus"
msgstr "Musicus"
#: ../src/screens/main.rs:198
#: ../src/screens/main.rs:202
msgid "The classical music player and organizer."
msgstr "Das Programm zum Abspielen und Organisieren von Klassik."
#: ../src/screens/main.rs:200
#: ../src/screens/main.rs:204
msgid "Further information and source code"
msgstr "Weitere Informationen und Quellcode"
@ -453,9 +475,6 @@ msgstr "Aufnahme auswählen"
#~ msgid "No works or recordings found."
#~ msgstr "Keine Werke oder Aufnahmen gefunden."
#~ msgid "Add to playlist"
#~ msgstr "Zur Wiedergabeliste hinzufügen"
#~ msgid "Select a composer on the left."
#~ msgstr "Wählen Sie einen Komponisten aus."

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-02-10 12:32+0100\n"
"POT-Creation-Date: 2022-04-08 19:52+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -30,13 +30,13 @@ msgid "Loading"
msgstr ""
#: ../res/ui/medium_preview.ui:149 ../res/ui/source_selector.ui:101
#: ../res/ui/medium_editor.ui:152 ../res/ui/medium_editor.ui:189
#: ../res/ui/medium_editor.ui:151 ../res/ui/medium_editor.ui:188
#: ../res/ui/editor.ui:68
msgid "Error"
msgstr ""
#: ../res/ui/medium_preview.ui:161 ../res/ui/source_selector.ui:113
#: ../res/ui/medium_editor.ui:164 ../res/ui/editor.ui:80
#: ../res/ui/medium_editor.ui:163 ../res/ui/editor.ui:80
msgid "Try again"
msgstr ""
@ -44,29 +44,29 @@ msgstr ""
msgid "Performance"
msgstr ""
#: ../res/ui/performance_editor.ui:55
#: ../res/ui/performance_editor.ui:52
msgid "Select a person"
msgstr ""
#: ../res/ui/performance_editor.ui:59 ../res/ui/performance_editor.ui:72
#: ../res/ui/performance_editor.ui:92 ../res/ui/track_set_editor.ui:69
#: ../res/ui/import_screen.ui:154 ../res/ui/preferences.ui:23
#: ../res/ui/recording_editor.ui:83 ../res/ui/work_editor.ui:83
#: ../res/ui/performance_editor.ui:56 ../res/ui/performance_editor.ui:69
#: ../res/ui/performance_editor.ui:89 ../res/ui/track_set_editor.ui:67
#: ../res/ui/import_screen.ui:160 ../res/ui/preferences.ui:23
#: ../res/ui/recording_editor.ui:81 ../res/ui/work_editor.ui:81
#: ../src/import/source_selector.rs:51 ../src/screens/welcome.rs:56
#: ../src/preferences.rs:42
#: ../src/preferences.rs:44
msgid "Select"
msgstr ""
#: ../res/ui/performance_editor.ui:68
#: ../res/ui/performance_editor.ui:65
msgid "Select an ensemble"
msgstr ""
#: ../res/ui/performance_editor.ui:81
#: ../res/ui/performance_editor.ui:78
msgid "Select a role"
msgstr ""
#: ../res/ui/player_bar.ui:65 ../res/ui/player_screen.ui:97
#: ../res/ui/work_part_editor.ui:59 ../res/ui/work_editor.ui:92
#: ../res/ui/work_part_editor.ui:56 ../res/ui/work_editor.ui:90
msgid "Title"
msgstr ""
@ -114,15 +114,15 @@ msgid "Select tracks"
msgstr ""
#: ../res/ui/track_set_editor.ui:51 ../res/ui/recording_editor.ui:18
#: ../res/ui/recording_editor.ui:155
#: ../res/ui/recording_editor.ui:154
msgid "Recording"
msgstr ""
#: ../res/ui/track_set_editor.ui:65
#: ../res/ui/track_set_editor.ui:63
msgid "Select a recording"
msgstr ""
#: ../res/ui/track_set_editor.ui:89 ../src/screens/recording.rs:30
#: ../res/ui/track_set_editor.ui:88 ../src/screens/recording.rs:30
msgid "Tracks"
msgstr ""
@ -134,31 +134,31 @@ msgstr ""
msgid "Matching metadata"
msgstr ""
#: ../res/ui/import_screen.ui:66
#: ../res/ui/import_screen.ui:64
msgid "Loading…"
msgstr ""
#: ../res/ui/import_screen.ui:87
#: ../res/ui/import_screen.ui:88
msgid "Error while searching for matching metadata"
msgstr ""
#: ../res/ui/import_screen.ui:110
#: ../res/ui/import_screen.ui:114
msgid "No matching metadata found"
msgstr ""
#: ../res/ui/import_screen.ui:136
#: ../res/ui/import_screen.ui:144
msgid "Manually add metadata"
msgstr ""
#: ../res/ui/import_screen.ui:150
#: ../res/ui/import_screen.ui:156
msgid "Select existing medium"
msgstr ""
#: ../res/ui/import_screen.ui:163
#: ../res/ui/import_screen.ui:169
msgid "Add a new medium"
msgstr ""
#: ../res/ui/import_screen.ui:167
#: ../res/ui/import_screen.ui:173
msgid "Add"
msgstr ""
@ -166,22 +166,22 @@ msgstr ""
msgid "Medium"
msgstr ""
#: ../res/ui/medium_editor.ui:75
#: ../res/ui/medium_editor.ui:73
msgid "Name of the medium"
msgstr ""
#: ../res/ui/medium_editor.ui:99
#: ../res/ui/medium_editor.ui:98
msgid "Recordings"
msgstr ""
#: ../res/ui/medium_editor.ui:201 ../res/ui/editor.ui:22
#: ../res/ui/medium_editor.ui:200 ../res/ui/editor.ui:22
#: ../src/import/source_selector.rs:50 ../src/screens/welcome.rs:55
#: ../src/preferences.rs:41
#: ../src/preferences.rs:43
msgid "Cancel"
msgstr ""
#: ../res/ui/preferences.ui:11 ../src/editors/ensemble.rs:34
#: ../src/editors/instrument.rs:34 ../src/editors/person.rs:38
#: ../res/ui/preferences.ui:11 ../src/editors/ensemble.rs:35
#: ../src/editors/instrument.rs:35 ../src/editors/person.rs:39
msgid "General"
msgstr ""
@ -197,19 +197,41 @@ msgstr ""
msgid "None selected"
msgstr ""
#: ../res/ui/preferences.ui:34
msgid "Playlist"
msgstr ""
#: ../res/ui/preferences.ui:38
msgid "Keep playing"
msgstr ""
#: ../res/ui/preferences.ui:40
msgid "Whether to keep playing random tracks after the playlist ends."
msgstr ""
#: ../res/ui/preferences.ui:51
msgid "Choose full recordings"
msgstr ""
#: ../res/ui/preferences.ui:53
msgid ""
"Whether to choose full recordings instead of single tracks for random "
"playback."
msgstr ""
#: ../res/ui/recording_editor.ui:65 ../res/ui/work_editor.ui:65
msgid "Overview"
msgstr ""
#: ../res/ui/recording_editor.ui:79
#: ../res/ui/recording_editor.ui:77
msgid "Select a work"
msgstr ""
#: ../res/ui/recording_editor.ui:92
#: ../res/ui/recording_editor.ui:90
msgid "Comment"
msgstr ""
#: ../res/ui/recording_editor.ui:116
#: ../res/ui/recording_editor.ui:115
msgid "Performers"
msgstr ""
@ -217,20 +239,20 @@ msgstr ""
msgid "Search …"
msgstr ""
#: ../res/ui/work_editor.ui:18 ../res/ui/work_editor.ui:182
#: ../res/ui/work_editor.ui:18 ../res/ui/work_editor.ui:181
#: ../src/editors/recording.rs:173
msgid "Work"
msgstr ""
#: ../res/ui/work_editor.ui:79
#: ../res/ui/work_editor.ui:77
msgid "Select a composer"
msgstr ""
#: ../res/ui/work_editor.ui:116
#: ../res/ui/work_editor.ui:115
msgid "Instruments"
msgstr ""
#: ../res/ui/work_editor.ui:143
#: ../res/ui/work_editor.ui:142
msgid "Structure"
msgstr ""
@ -264,43 +286,43 @@ msgstr ""
msgid "About Musicus"
msgstr ""
#: ../src/editors/performance.rs:42
#: ../src/editors/performance.rs:43
msgid "Performer"
msgstr ""
#: ../src/editors/performance.rs:44
#: ../src/editors/performance.rs:45
msgid "Select either a person or an ensemble as a performer."
msgstr ""
#: ../src/editors/performance.rs:62
#: ../src/editors/performance.rs:64
msgid "Role"
msgstr ""
#: ../src/editors/performance.rs:64
#: ../src/editors/performance.rs:66
msgid "Optionally, choose a role to specify what the performer does."
msgstr ""
#: ../src/editors/ensemble.rs:31 ../src/editors/instrument.rs:31
#: ../src/editors/ensemble.rs:32 ../src/editors/instrument.rs:32
msgid "Name"
msgstr ""
#: ../src/editors/ensemble.rs:65
#: ../src/editors/ensemble.rs:66
msgid "Failed to save ensemble!"
msgstr ""
#: ../src/editors/instrument.rs:65
#: ../src/editors/instrument.rs:66
msgid "Failed to save instrument!"
msgstr ""
#: ../src/editors/person.rs:32
#: ../src/editors/person.rs:33
msgid "First name"
msgstr ""
#: ../src/editors/person.rs:33
#: ../src/editors/person.rs:34
msgid "Last name"
msgstr ""
#: ../src/editors/person.rs:72
#: ../src/editors/person.rs:73
msgid "Failed to save person!"
msgstr ""
@ -308,8 +330,8 @@ msgstr ""
msgid "Composer"
msgstr ""
#: ../src/import/track_set_editor.rs:133 ../src/import/medium_preview.rs:171
#: ../src/screens/medium.rs:74 ../src/screens/recording.rs:84
#: ../src/import/track_set_editor.rs:133 ../src/import/medium_preview.rs:170
#: ../src/screens/medium.rs:72 ../src/screens/recording.rs:82
msgid "Unknown"
msgstr ""
@ -337,11 +359,11 @@ msgstr ""
msgid "Delete person"
msgstr ""
#: ../src/screens/recording.rs:55
#: ../src/screens/recording.rs:53
msgid "Edit recording"
msgstr ""
#: ../src/screens/recording.rs:65
#: ../src/screens/recording.rs:63
msgid "Delete recording"
msgstr ""
@ -352,7 +374,7 @@ msgid ""
"exists."
msgstr ""
#: ../src/screens/welcome.rs:51 ../src/preferences.rs:37
#: ../src/screens/welcome.rs:51 ../src/preferences.rs:39
msgid "Select music library folder"
msgstr ""
@ -364,15 +386,15 @@ msgstr ""
msgid "Delete work"
msgstr ""
#: ../src/screens/main.rs:196
#: ../src/screens/main.rs:200
msgid "Musicus"
msgstr ""
#: ../src/screens/main.rs:198
#: ../src/screens/main.rs:202
msgid "The classical music player and organizer."
msgstr ""
#: ../src/screens/main.rs:200
#: ../src/screens/main.rs:204
msgid "Further information and source code"
msgstr ""

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<requires lib="gtk" version="4.0" />
<requires lib="libadwaita" version="1.0" />
<object class="AdwPreferencesWindow" id="window">
<property name="modal">True</property>
<property name="default-width">400</property>
@ -29,6 +29,37 @@
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Playlist</property>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Keep playing</property>
<property name="activatable-widget">keep_playing_switch</property>
<property name="subtitle" translatable="yes">Whether to keep playing random tracks after the playlist ends.</property>
<child>
<object class="GtkSwitch" id="keep_playing_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="focusable">False</property>
<property name="title" translatable="yes">Choose full recordings</property>
<property name="activatable-widget">play_full_recordings_switch</property>
<property name="subtitle" translatable="yes">Whether to choose full recordings instead of single tracks for random playback.</property>
<child>
<object class="GtkSwitch" id="play_full_recordings_switch">
<property name="valign">center</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>

View file

@ -21,6 +21,8 @@ impl Preferences {
get_widget!(builder, adw::Window, window);
get_widget!(builder, adw::ActionRow, music_library_path_row);
get_widget!(builder, gtk::Button, select_music_library_path_button);
get_widget!(builder, gtk::Switch, keep_playing_switch);
get_widget!(builder, gtk::Switch, play_full_recordings_switch);
window.set_transient_for(Some(parent));
@ -48,7 +50,7 @@ impl Preferences {
if let gtk::ResponseType::Accept = response {
if let Some(file) = dialog.file() {
if let Some(path) = file.path() {
this.backend.set_music_library_path(path.clone()).unwrap();
Rc::clone(&this.backend).set_music_library_path(path.clone()).unwrap();
this.music_library_path_row.set_subtitle(path.to_str().unwrap());
}
}
@ -60,6 +62,14 @@ impl Preferences {
dialog.show();
}));
keep_playing_switch.connect_active_notify(clone!(@weak this => move |switch| {
Rc::clone(&this.backend).set_keep_playing(switch.is_active());
}));
play_full_recordings_switch.connect_active_notify(clone!(@weak this => move |switch| {
Rc::clone(&this.backend).set_play_full_recordings(switch.is_active());
}));
// Initialize
if let Some(path) = this.backend.get_music_library_path() {
@ -67,6 +77,9 @@ impl Preferences {
.set_subtitle(path.to_str().unwrap());
}
keep_playing_switch.set_active(this.backend.keep_playing());
play_full_recordings_switch.set_active(this.backend.play_full_recordings());
this
}

View file

@ -128,12 +128,16 @@ impl Screen<(), ()> for MainScreen {
search.is_empty() || title.contains(&search)
}));
this.handle.backend.pl().add_playlist_cb(clone!(@weak play_button_revealer => move |new_playlist| {
play_button_revealer.set_reveal_child(new_playlist.is_empty());
}));
this.handle.backend.pl().add_playlist_cb(
clone!(@weak play_button_revealer => move |new_playlist| {
play_button_revealer.set_reveal_child(new_playlist.is_empty());
}),
);
play_button.connect_clicked(clone!(@weak this => move |_| {
this.handle.backend.pl().play_pause().unwrap();
if let Ok(recording) = this.handle.backend.db().random_recording() {
this.handle.backend.pl().add_items(this.handle.backend.db().get_tracks(&recording.id).unwrap()).unwrap();
}
}));
this.navigator.set_back_cb(clone!(@weak this => move || {

View file

@ -55,9 +55,7 @@ impl Screen<Medium, ()> for MediumScreen {
section.add_action(
"media-playback-start-symbolic",
clone!(@weak this => move || {
for track in &this.medium.tracks {
this.handle.backend.pl().add_item(track.clone()).unwrap();
}
this.handle.backend.pl().add_items(this.medium.tracks.clone()).unwrap();
}),
);

View file

@ -41,9 +41,7 @@ impl Screen<Recording, ()> for RecordingScreen {
section.add_action(
"media-playback-start-symbolic",
clone!(@weak this => move || {
for track in &*this.tracks.borrow() {
this.handle.backend.pl().add_item(track.clone()).unwrap();
}
this.handle.backend.pl().add_items(this.tracks.borrow().clone()).unwrap();
}),
);

View file

@ -62,7 +62,7 @@ impl Screen<(), ()> for WelcomeScreen {
if let gtk::ResponseType::Accept = response {
if let Some(file) = dialog.file() {
if let Some(path) = file.path() {
this.handle.backend.set_music_library_path(path).unwrap();
Rc::clone(&this.handle.backend).set_music_library_path(path).unwrap();
}
}
}

View file

@ -61,7 +61,7 @@ impl Window {
}));
// Initialize the backend.
this.backend.init().unwrap();
Rc::clone(&this.backend).init().unwrap();
this
}