use crate::playlist_item::PlaylistItem; use fragile::Fragile; use gstreamer_play::gst; use gtk::{ gio, glib::{self, clone, subclass::Signal, Properties}, prelude::*, subclass::prelude::*, }; use mpris_player::{MprisPlayer, PlaybackStatus}; use once_cell::sync::Lazy; use std::{ cell::{Cell, OnceCell}, sync::Arc, }; mod imp { use mpris_player::Metadata; use super::*; #[derive(Properties, Debug, Default)] #[properties(wrapper_type = super::MusicusPlayer)] pub struct MusicusPlayer { #[property(get, set)] pub active: Cell, #[property(get, set)] pub playing: Cell, #[property(get, construct_only)] pub playlist: OnceCell, #[property(get, set = Self::set_current_index)] pub current_index: Cell, #[property(get, set)] pub duration_ms: Cell, #[property(get, set)] pub position_ms: Cell, pub play: OnceCell, pub play_signal_adapter: OnceCell, pub mpris: OnceCell>, } impl MusicusPlayer { pub fn set_current_index(&self, index: u32) { let playlist = self.playlist.get().unwrap(); if let Some(item) = playlist.item(index) { if let Some(old_item) = playlist.item(self.current_index.get()) { old_item .downcast::() .unwrap() .set_is_playing(false); } let item = item.downcast::().unwrap(); self.mpris.get().unwrap().set_metadata(Metadata { artist: Some(vec![item.make_title()]), title: item.make_subtitle(), ..Default::default() }); let uri = glib::filename_to_uri(item.path(), None) .expect("track path should be parsable as an URI"); let play = self.play.get().unwrap(); play.set_uri(Some(&uri)); if self.playing.get() { play.play(); } self.current_index.set(index); item.set_is_playing(true); } } } #[glib::object_subclass] impl ObjectSubclass for MusicusPlayer { const NAME: &'static str = "MusicusPlayer"; type Type = super::MusicusPlayer; } #[glib::derived_properties] impl ObjectImpl for MusicusPlayer { fn signals() -> &'static [Signal] { static SIGNALS: Lazy> = Lazy::new(|| vec![Signal::builder("raise").build()]); SIGNALS.as_ref() } fn constructed(&self) { self.parent_constructed(); let play = gstreamer_play::Play::new(None::); let mpris = MprisPlayer::new( "de.johrpan.musicus".to_string(), "Musicus".to_string(), "de.johrpan.musicus.desktop".to_string(), ); mpris.set_can_raise(true); mpris.set_can_play(true); mpris.set_can_pause(true); mpris.set_can_go_previous(true); mpris.set_can_go_next(true); mpris.set_can_seek(false); mpris.set_can_set_fullscreen(false); let obj = self.obj(); mpris.connect_raise(clone!(@weak obj => move || obj.emit_by_name::<()>("raise", &[]))); mpris.connect_play(clone!(@weak obj => move || obj.play())); mpris.connect_pause(clone!(@weak obj => move || obj.pause())); mpris.connect_play_pause(clone!(@weak obj => move || obj.play_pause())); mpris.connect_previous(clone!(@weak obj => move || obj.previous())); mpris.connect_next(clone!(@weak obj => move || obj.next())); self.mpris.set(mpris).expect("mpris should not be set"); let mut config = play.config(); config.set_position_update_interval(250); play.set_config(config).unwrap(); play.set_video_track_enabled(false); let play_signal_adapter = gstreamer_play::PlaySignalAdapter::new(&play); let obj = Fragile::new(self.obj().to_owned()); play_signal_adapter.connect_end_of_stream(move |_| { obj.get().next(); }); let obj = Fragile::new(self.obj().to_owned()); play_signal_adapter.connect_position_updated(move |_, position| { if let Some(position) = position { let obj = obj.get(); obj.imp().position_ms.set(position.mseconds()); obj.notify_position_ms(); } }); let obj = Fragile::new(self.obj().to_owned()); play_signal_adapter.connect_duration_changed(move |_, duration| { 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(); } }); self.play.set(play).unwrap(); self.play_signal_adapter.set(play_signal_adapter).unwrap(); } } } glib::wrapper! { pub struct MusicusPlayer(ObjectSubclass); } impl MusicusPlayer { pub fn new() -> Self { glib::Object::builder() .property("active", false) .property("playing", false) .property("playlist", gio::ListStore::new::()) .property("current-index", 0u32) .property("position-ms", 0u64) .property("duration-ms", 60_000u64) .build() } pub fn connect_raise(&self, f: F) -> glib::SignalHandlerId { self.connect_local("raise", true, move |values| { let obj = values[0].get::().unwrap(); f(&obj); None }) } pub fn append(&self, tracks: Vec) { let playlist = self.playlist(); for track in tracks { playlist.append(&track); } if !self.active() && playlist.n_items() > 0 { self.set_active(true); self.set_current_index(0); self.play(); } } pub fn play_pause(&self) { if self.playing() { self.pause(); } else { self.play(); } } pub fn play(&self) { let imp = self.imp(); imp.play.get().unwrap().play(); self.set_playing(true); imp.mpris .get() .unwrap() .set_playback_status(PlaybackStatus::Playing); } pub fn pause(&self) { let imp = self.imp(); imp.play.get().unwrap().pause(); self.set_playing(false); imp.mpris .get() .unwrap() .set_playback_status(PlaybackStatus::Paused); } pub fn seek_to(&self, time_ms: u64) { let imp = self.imp(); imp.play .get() .unwrap() .seek(gst::ClockTime::from_mseconds(time_ms)); } pub fn current_item(&self) -> Option { let imp = self.imp(); imp.playlist .get() .unwrap() .item(imp.current_index.get()) .and_downcast::() } pub fn next(&self) { if self.current_index() < self.playlist().n_items() - 1 { self.set_current_index(self.current_index() + 1); } } pub fn previous(&self) { if self.current_index() > 0 { self.set_current_index(self.current_index() - 1); } } } impl Default for MusicusPlayer { fn default() -> Self { Self::new() } }