diff --git a/src/home_page.rs b/src/home_page.rs index c10567c..75574e0 100644 --- a/src/home_page.rs +++ b/src/home_page.rs @@ -4,7 +4,6 @@ use crate::{ editor::{person_editor::MusicusPersonEditor, work_editor::MusicusWorkEditor}, library::{LibraryQuery, MusicusLibrary}, player::MusicusPlayer, - playlist_item::PlaylistItem, program::Program, program_tile::MusicusProgramTile, recording_tile::MusicusRecordingTile, @@ -38,6 +37,7 @@ mod imp { #[property(get, construct_only)] pub player: OnceCell, + pub programs: RefCell>, pub composers: RefCell>, pub performers: RefCell>, pub ensembles: RefCell>, @@ -110,18 +110,19 @@ mod imp { .build(); let settings = gio::Settings::new("de.johrpan.musicus"); - let program1 = Program::deserialize(&settings.string("program1")).unwrap(); - let program2 = Program::deserialize(&settings.string("program2")).unwrap(); - let program3 = Program::deserialize(&settings.string("program3")).unwrap(); - self.programs_flow_box - .append(&MusicusProgramTile::new(program1)); + let programs = vec![ + Program::deserialize(&settings.string("program1")).unwrap(), + Program::deserialize(&settings.string("program2")).unwrap(), + Program::deserialize(&settings.string("program3")).unwrap(), + ]; - self.programs_flow_box - .append(&MusicusProgramTile::new(program2)); + for program in &programs { + self.programs_flow_box + .append(&MusicusProgramTile::new(program.to_owned())); + } - self.programs_flow_box - .append(&MusicusProgramTile::new(program3)); + self.programs.replace(programs); self.obj().query(&LibraryQuery::default()); } @@ -179,6 +180,10 @@ impl MusicusHomePage { #[template_callback] fn play(&self, _: >k::Button) { log::info!("Play button clicked"); + + let program = Program::from_query(self.imp().search_entry.query()); + self.player().set_program(program); + self.player().play(); } @@ -187,7 +192,9 @@ impl MusicusHomePage { let imp = self.imp(); if imp.programs_flow_box.is_visible() { - log::info!("Program selected"); + if let Some(program) = imp.programs.borrow().first().cloned() { + self.player().set_program(program); + } } else { let (composer, performer, ensemble, work, recording, album) = { ( @@ -209,7 +216,7 @@ impl MusicusHomePage { } else if let Some(work) = work { search_entry.add_tag(Tag::Work(work)); } else if let Some(recording) = recording { - self.play_recording(&recording); + self.player().play_recording(&recording); } else if let Some(album) = album { self.show_album(&album); } @@ -218,10 +225,8 @@ impl MusicusHomePage { #[template_callback] fn program_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { - log::info!( - "Program selected: {:?}", - tile.downcast_ref::().unwrap().program() - ); + self.player() + .set_program(tile.downcast_ref::().unwrap().program()); } #[template_callback] @@ -233,7 +238,7 @@ impl MusicusHomePage { #[template_callback] fn recording_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { - self.play_recording( + self.player().play_recording( tile.downcast_ref::() .unwrap() .recording(), @@ -245,74 +250,6 @@ impl MusicusHomePage { self.show_album(tile.downcast_ref::().unwrap().album()); } - fn play_recording(&self, recording: &Recording) { - let tracks = &recording.tracks; - - if tracks.is_empty() { - log::warn!("Ignoring recording without tracks being added to the playlist."); - return; - } - - let title = format!( - "{}: {}", - recording.work.composers_string(), - recording.work.name.get(), - ); - - let performances = recording.performers_string(); - - let mut items = Vec::new(); - - if tracks.len() == 1 { - items.push(PlaylistItem::new( - true, - &title, - Some(&performances), - None, - &tracks[0].path, - )); - } else { - let mut tracks = tracks.into_iter(); - let first_track = tracks.next().unwrap(); - - let track_title = |track: &Track, number: usize| -> String { - let title = track - .works - .iter() - .map(|w| w.name.get().to_string()) - .collect::>() - .join(", "); - - if title.is_empty() { - format!("Track {number}") - } else { - title - } - }; - - items.push(PlaylistItem::new( - true, - &title, - Some(&performances), - Some(&track_title(&first_track, 1)), - &first_track.path, - )); - - for (index, track) in tracks.enumerate() { - items.push(PlaylistItem::new( - false, - &title, - Some(&performances), - // track number = track index + 1 (first track) + 1 (zero based) - Some(&track_title(&track, index + 2)), - &track.path, - )); - } - } - - self.player().append(items); - } - fn show_album(&self, _album: &Album) { todo!("Show album"); } diff --git a/src/library.rs b/src/library.rs index 29c32ca..b752721 100644 --- a/src/library.rs +++ b/src/library.rs @@ -8,7 +8,10 @@ use chrono::prelude::*; use diesel::{dsl::exists, prelude::*, QueryDsl, SqliteConnection}; use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*}; -use crate::db::{self, models::*, schema::*, tables, TranslatedString}; +use crate::{ + db::{self, models::*, schema::*, tables, TranslatedString}, + program::Program, +}; diesel::define_sql_function! { /// Represents the SQL RANDOM() function. @@ -380,19 +383,45 @@ impl MusicusLibrary { }) } - pub fn random_recording(&self, query: &LibraryQuery) -> Result { + pub fn generate_recording(&self, program: &Program) -> Result { let mut binding = self.imp().connection.borrow_mut(); let connection = &mut *binding.as_mut().unwrap(); - match query { - LibraryQuery { .. } => Recording::from_table( - recordings::table - .order(random()) - .first::(connection)?, - &self.folder(), - connection, - ), + let mut query = recordings::table + .inner_join(works::table.inner_join(work_persons::table)) + .inner_join(recording_persons::table) + .inner_join(recording_ensembles::table) + .inner_join(album_recordings::table) + .into_boxed(); + + if let Some(composer_id) = program.composer_id() { + query = query.filter(work_persons::person_id.eq(composer_id)); } + + if let Some(performer_id) = program.performer_id() { + query = query.filter(recording_persons::person_id.eq(performer_id)); + } + + if let Some(ensemble_id) = program.ensemble_id() { + query = query.filter(recording_ensembles::ensemble_id.eq(ensemble_id)); + } + + if let Some(work_id) = program.work_id() { + query = query.filter(recordings::work_id.eq(work_id)); + } + + if let Some(album_id) = program.album_id() { + query = query.filter(album_recordings::album_id.eq(album_id)); + } + + // TODO: Implement prefer_recently_added and prefer_least_recently_played. + + let row = query + .order(random()) + .select(tables::Recording::as_select()) + .first::(connection)?; + + Recording::from_table(row, &self.folder(), connection) } pub fn search_persons(&self, search: &str) -> Result> { diff --git a/src/player.rs b/src/player.rs index cb7dfa8..46134eb 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,4 +1,8 @@ -use crate::playlist_item::PlaylistItem; +use std::{ + cell::{Cell, OnceCell, RefCell}, + sync::Arc, +}; + use fragile::Fragile; use gstreamer_play::gst; use gtk::{ @@ -7,25 +11,30 @@ use gtk::{ prelude::*, subclass::prelude::*, }; -use mpris_player::{MprisPlayer, PlaybackStatus}; +use mpris_player::{Metadata, MprisPlayer, PlaybackStatus}; use once_cell::sync::Lazy; -use std::{ - cell::{Cell, OnceCell}, - sync::Arc, + +use crate::{ + db::models::{Recording, Track}, + library::MusicusLibrary, + playlist_item::PlaylistItem, + program::Program, }; mod imp { - use mpris_player::Metadata; - use super::*; #[derive(Properties, Debug, Default)] #[properties(wrapper_type = super::MusicusPlayer)] pub struct MusicusPlayer { + #[property(get, set)] + pub library: RefCell>, #[property(get, set)] pub active: Cell, #[property(get, set)] pub playing: Cell, + #[property(get, set = Self::set_program)] + pub program: RefCell>, #[property(get, construct_only)] pub playlist: OnceCell, #[property(get, set = Self::set_current_index)] @@ -41,6 +50,17 @@ mod imp { } impl MusicusPlayer { + pub fn set_program(&self, program: &Program) { + self.program.replace(Some(program.to_owned())); + + if !self.obj().active() { + self.obj().set_active(true); + self.obj().generate_items(program); + self.obj().set_current_index(0); + self.obj().play(); + } + } + pub fn set_current_index(&self, index: u32) { let playlist = self.playlist.get().unwrap(); @@ -144,10 +164,10 @@ mod imp { if let Some(duration) = duration { let obj = obj.get(); let imp = obj.imp(); - + imp.position_ms.set(0); obj.notify_position_ms(); - + imp.duration_ms.set(duration.mseconds()); obj.notify_duration_ms(); } @@ -183,6 +203,74 @@ impl MusicusPlayer { }) } + pub fn play_recording(&self, recording: &Recording) { + let tracks = &recording.tracks; + + if tracks.is_empty() { + log::warn!("Ignoring recording without tracks being added to the playlist."); + return; + } + + let title = format!( + "{}: {}", + recording.work.composers_string(), + recording.work.name.get(), + ); + + let performances = recording.performers_string(); + + let mut items = Vec::new(); + + if tracks.len() == 1 { + items.push(PlaylistItem::new( + true, + &title, + Some(&performances), + None, + &tracks[0].path, + )); + } else { + let mut tracks = tracks.into_iter(); + let first_track = tracks.next().unwrap(); + + let track_title = |track: &Track, number: usize| -> String { + let title = track + .works + .iter() + .map(|w| w.name.get().to_string()) + .collect::>() + .join(", "); + + if title.is_empty() { + format!("Track {number}") + } else { + title + } + }; + + items.push(PlaylistItem::new( + true, + &title, + Some(&performances), + Some(&track_title(&first_track, 1)), + &first_track.path, + )); + + for (index, track) in tracks.enumerate() { + items.push(PlaylistItem::new( + false, + &title, + Some(&performances), + // track number = track index + 1 (first track) + 1 (zero based) + Some(&track_title(&track, index + 2)), + &track.path, + )); + } + } + + self.append(items); + } + pub fn append(&self, tracks: Vec) { let playlist = self.playlist(); @@ -245,6 +333,9 @@ impl MusicusPlayer { pub fn next(&self) { if self.current_index() < self.playlist().n_items() - 1 { self.set_current_index(self.current_index() + 1); + } else if let Some(program) = self.program() { + self.generate_items(&program); + self.set_current_index(self.current_index() + 1); } } @@ -253,6 +344,14 @@ impl MusicusPlayer { self.set_current_index(self.current_index() - 1); } } + + fn generate_items(&self, program: &Program) { + if let Some(library) = self.library() { + // TODO: if program.play_full_recordings() { + let recording = library.generate_recording(program).unwrap(); + self.play_recording(&recording); + } + } } impl Default for MusicusPlayer { diff --git a/src/window.rs b/src/window.rs index 19bf774..7b14942 100644 --- a/src/window.rs +++ b/src/window.rs @@ -154,6 +154,7 @@ impl MusicusWindow { fn load_library(&self, path: impl AsRef) { let library = MusicusLibrary::new(path); + self.imp().player.set_library(&library); let navigation = self.imp().navigation_view.get(); navigation.replace(&[MusicusHomePage::new(&navigation, &library, &self.imp().player).into()]);