diff --git a/backend/src/lib.rs b/backend/src/lib.rs index aab03ff..06fe06a 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -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>>, + + /// Whether to keep playing random tracks after the playlist ends. + keep_playing: Cell, + + /// Whether to choose full recordings for random playback. + play_full_recordings: Cell, } 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) -> 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, 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, 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) { + 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::); + } + } + } } impl Default for Backend { diff --git a/backend/src/library.rs b/backend/src/library.rs index 9bfc1a3..e4db783 100644 --- a/backend/src/library.rs +++ b/backend/src/library.rs @@ -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) -> 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, 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, 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(()) diff --git a/backend/src/player.rs b/backend/src/player.rs index 0e6be54..41d2262 100644 --- a/backend/src/player.rs +++ b/backend/src/player.rs @@ -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>, playing: Cell, duration: Cell, - generate_next_track_cb: RefCell Track>>>, + track_generator: RefCell>>, playlist_cbs: RefCell)>>>, track_cbs: RefCell>>, duration_cbs: RefCell>>, @@ -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 Track + 'static>(&self, cb: F) { - self.generate_next_track_cb.replace(Some(Box::new(cb))); + pub fn set_track_generator(&self, generator: Option) { + self.track_generator.replace(match generator { + Some(generator) => Some(Box::new(generator)), + None => None, + }); } pub fn add_playlist_cb) + '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) -> 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; +} + +/// A track generator that generates one random track per call. +pub struct RandomTrackGenerator { + backend: Rc, +} + +impl RandomTrackGenerator { + pub fn new(backend: Rc) -> Self { + Self { backend } + } +} + +impl TrackGenerator for RandomTrackGenerator { + fn has_next(&self) -> bool { + true + } + + fn next(&self) -> Vec { + 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, +} + +impl RandomRecordingGenerator { + pub fn new(backend: Rc) -> Self { + Self { backend } + } +} + +impl TrackGenerator for RandomRecordingGenerator { + fn has_next(&self) -> bool { + true + } + + fn next(&self) -> Vec { + let recording = self.backend.db().random_recording().unwrap(); + self.backend.db().get_tracks(&recording.id).unwrap() + } +} diff --git a/database/src/recordings.rs b/database/src/recordings.rs index 1838ea8..1313440 100644 --- a/database/src/recordings.rs +++ b/database/src/recordings.rs @@ -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 { + let row = diesel::sql_query("SELECT * FROM recordings ORDER BY RANDOM() LIMIT 1") + .load::(&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 { let mut performance_descriptions: Vec = Vec::new(); diff --git a/musicus/data/de.johrpan.musicus.gschema.xml b/musicus/data/de.johrpan.musicus.gschema.xml index 2609270..39e6bce 100644 --- a/musicus/data/de.johrpan.musicus.gschema.xml +++ b/musicus/data/de.johrpan.musicus.gschema.xml @@ -5,5 +5,13 @@ "" Path to the music library folder + + false + Keep playing after the playlist ends + + + true + Choose full recordings for random playback + diff --git a/musicus/po/de.po b/musicus/po/de.po index 81b13cc..056cbb0 100644 --- a/musicus/po/de.po +++ b/musicus/po/de.po @@ -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." diff --git a/musicus/po/musicus.pot b/musicus/po/musicus.pot index aa07872..471a397 100644 --- a/musicus/po/musicus.pot +++ b/musicus/po/musicus.pot @@ -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 \n" "Language-Team: LANGUAGE \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 "" diff --git a/musicus/res/ui/preferences.ui b/musicus/res/ui/preferences.ui index 960faca..b1ae579 100644 --- a/musicus/res/ui/preferences.ui +++ b/musicus/res/ui/preferences.ui @@ -1,7 +1,7 @@ - - + + True 400 @@ -29,6 +29,37 @@ + + + Playlist + + + False + Keep playing + keep_playing_switch + Whether to keep playing random tracks after the playlist ends. + + + center + + + + + + + False + Choose full recordings + play_full_recordings_switch + Whether to choose full recordings instead of single tracks for random playback. + + + center + + + + + + diff --git a/musicus/src/preferences.rs b/musicus/src/preferences.rs index 9d1b21d..2fed422 100644 --- a/musicus/src/preferences.rs +++ b/musicus/src/preferences.rs @@ -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 } diff --git a/musicus/src/screens/main.rs b/musicus/src/screens/main.rs index 31d46d8..0ea5446 100644 --- a/musicus/src/screens/main.rs +++ b/musicus/src/screens/main.rs @@ -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 || { diff --git a/musicus/src/screens/medium.rs b/musicus/src/screens/medium.rs index 3ee64b5..130a849 100644 --- a/musicus/src/screens/medium.rs +++ b/musicus/src/screens/medium.rs @@ -55,9 +55,7 @@ impl Screen 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(); }), ); diff --git a/musicus/src/screens/recording.rs b/musicus/src/screens/recording.rs index 3df831e..36a9322 100644 --- a/musicus/src/screens/recording.rs +++ b/musicus/src/screens/recording.rs @@ -41,9 +41,7 @@ impl Screen 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(); }), ); diff --git a/musicus/src/screens/welcome.rs b/musicus/src/screens/welcome.rs index 93e74ae..05af7d0 100644 --- a/musicus/src/screens/welcome.rs +++ b/musicus/src/screens/welcome.rs @@ -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(); } } } diff --git a/musicus/src/window.rs b/musicus/src/window.rs index 7d31f11..b496575 100644 --- a/musicus/src/window.rs +++ b/musicus/src/window.rs @@ -61,7 +61,7 @@ impl Window { })); // Initialize the backend. - this.backend.init().unwrap(); + Rc::clone(&this.backend).init().unwrap(); this }