2024-06-10 16:57:36 +02:00
|
|
|
use std::{
|
|
|
|
|
cell::{Cell, OnceCell, RefCell},
|
|
|
|
|
sync::Arc,
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-03 17:48:27 +01:00
|
|
|
use fragile::Fragile;
|
2024-03-11 17:24:21 +01:00
|
|
|
use gstreamer_play::gst;
|
2023-11-03 17:48:27 +01:00
|
|
|
use gtk::{
|
|
|
|
|
gio,
|
2023-11-03 18:59:47 +01:00
|
|
|
glib::{self, clone, subclass::Signal, Properties},
|
2023-11-03 17:48:27 +01:00
|
|
|
prelude::*,
|
|
|
|
|
subclass::prelude::*,
|
|
|
|
|
};
|
2024-06-10 16:57:36 +02:00
|
|
|
use mpris_player::{Metadata, MprisPlayer, PlaybackStatus};
|
2023-11-03 18:59:47 +01:00
|
|
|
use once_cell::sync::Lazy;
|
2024-06-10 16:57:36 +02:00
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
db::models::{Recording, Track},
|
|
|
|
|
library::MusicusLibrary,
|
|
|
|
|
playlist_item::PlaylistItem,
|
|
|
|
|
program::Program,
|
2023-11-03 18:20:41 +01:00
|
|
|
};
|
2023-09-29 21:18:28 +02:00
|
|
|
|
|
|
|
|
mod imp {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[derive(Properties, Debug, Default)]
|
|
|
|
|
#[properties(wrapper_type = super::MusicusPlayer)]
|
|
|
|
|
pub struct MusicusPlayer {
|
2024-06-10 16:57:36 +02:00
|
|
|
#[property(get, set)]
|
|
|
|
|
pub library: RefCell<Option<MusicusLibrary>>,
|
2023-09-29 21:18:28 +02:00
|
|
|
#[property(get, set)]
|
|
|
|
|
pub active: Cell<bool>,
|
|
|
|
|
#[property(get, set)]
|
|
|
|
|
pub playing: Cell<bool>,
|
2024-06-10 16:57:36 +02:00
|
|
|
#[property(get, set = Self::set_program)]
|
|
|
|
|
pub program: RefCell<Option<Program>>,
|
2023-10-25 17:45:32 +02:00
|
|
|
#[property(get, construct_only)]
|
|
|
|
|
pub playlist: OnceCell<gio::ListStore>,
|
2023-10-26 11:48:42 +02:00
|
|
|
#[property(get, set = Self::set_current_index)]
|
2023-10-25 17:45:32 +02:00
|
|
|
pub current_index: Cell<u32>,
|
2023-11-03 16:22:58 +01:00
|
|
|
#[property(get, set)]
|
2023-11-03 17:48:27 +01:00
|
|
|
pub duration_ms: Cell<u64>,
|
2023-11-03 16:22:58 +01:00
|
|
|
#[property(get, set)]
|
2023-11-07 15:57:56 +01:00
|
|
|
pub position_ms: Cell<u64>,
|
2023-11-03 18:20:41 +01:00
|
|
|
|
2024-03-11 17:24:21 +01:00
|
|
|
pub play: OnceCell<gstreamer_play::Play>,
|
|
|
|
|
pub play_signal_adapter: OnceCell<gstreamer_play::PlaySignalAdapter>,
|
2023-11-03 18:20:41 +01:00
|
|
|
pub mpris: OnceCell<Arc<MprisPlayer>>,
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-26 11:48:42 +02:00
|
|
|
impl MusicusPlayer {
|
2024-06-10 16:57:36 +02:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 11:48:42 +02:00
|
|
|
pub fn set_current_index(&self, index: u32) {
|
|
|
|
|
let playlist = self.playlist.get().unwrap();
|
2023-10-27 12:32:40 +02:00
|
|
|
|
2023-10-26 11:48:42 +02:00
|
|
|
if let Some(item) = playlist.item(index) {
|
2023-11-03 17:48:27 +01:00
|
|
|
if let Some(old_item) = playlist.item(self.current_index.get()) {
|
2023-11-03 18:20:41 +01:00
|
|
|
old_item
|
|
|
|
|
.downcast::<PlaylistItem>()
|
2023-11-03 17:48:27 +01:00
|
|
|
.unwrap()
|
|
|
|
|
.set_is_playing(false);
|
|
|
|
|
}
|
2023-11-03 18:20:41 +01:00
|
|
|
|
2023-11-03 17:48:27 +01:00
|
|
|
let item = item.downcast::<PlaylistItem>().unwrap();
|
2023-11-03 18:20:41 +01:00
|
|
|
self.mpris.get().unwrap().set_metadata(Metadata {
|
|
|
|
|
artist: Some(vec![item.make_title()]),
|
|
|
|
|
title: item.make_subtitle(),
|
|
|
|
|
..Default::default()
|
|
|
|
|
});
|
|
|
|
|
|
2023-11-25 15:05:40 +01:00
|
|
|
let uri = glib::filename_to_uri(item.path(), None)
|
2023-11-03 18:20:41 +01:00
|
|
|
.expect("track path should be parsable as an URI");
|
|
|
|
|
|
2024-03-11 17:24:21 +01:00
|
|
|
let play = self.play.get().unwrap();
|
|
|
|
|
play.set_uri(Some(&uri));
|
2023-11-03 17:48:27 +01:00
|
|
|
if self.playing.get() {
|
2024-03-11 17:24:21 +01:00
|
|
|
play.play();
|
2023-11-03 17:48:27 +01:00
|
|
|
}
|
2023-11-03 18:20:41 +01:00
|
|
|
|
2023-11-03 17:48:27 +01:00
|
|
|
self.current_index.set(index);
|
|
|
|
|
item.set_is_playing(true);
|
2023-10-26 11:48:42 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-29 21:18:28 +02:00
|
|
|
#[glib::object_subclass]
|
|
|
|
|
impl ObjectSubclass for MusicusPlayer {
|
|
|
|
|
const NAME: &'static str = "MusicusPlayer";
|
|
|
|
|
type Type = super::MusicusPlayer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::derived_properties]
|
2023-11-03 17:48:27 +01:00
|
|
|
impl ObjectImpl for MusicusPlayer {
|
2023-11-03 18:59:47 +01:00
|
|
|
fn signals() -> &'static [Signal] {
|
|
|
|
|
static SIGNALS: Lazy<Vec<Signal>> =
|
|
|
|
|
Lazy::new(|| vec![Signal::builder("raise").build()]);
|
|
|
|
|
|
|
|
|
|
SIGNALS.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 17:48:27 +01:00
|
|
|
fn constructed(&self) {
|
|
|
|
|
self.parent_constructed();
|
|
|
|
|
|
2024-03-11 17:24:21 +01:00
|
|
|
let play = gstreamer_play::Play::new(None::<gstreamer_play::PlayVideoRenderer>);
|
|
|
|
|
|
2023-11-03 18:20:41 +01:00
|
|
|
let mpris = MprisPlayer::new(
|
|
|
|
|
"de.johrpan.musicus".to_string(),
|
|
|
|
|
"Musicus".to_string(),
|
|
|
|
|
"de.johrpan.musicus.desktop".to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
2023-11-03 18:59:47 +01:00
|
|
|
mpris.set_can_raise(true);
|
2023-11-03 18:20:41 +01:00
|
|
|
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();
|
2023-11-03 18:59:47 +01:00
|
|
|
mpris.connect_raise(clone!(@weak obj => move || obj.emit_by_name::<()>("raise", &[])));
|
2023-11-03 18:20:41 +01:00
|
|
|
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");
|
|
|
|
|
|
2024-03-11 17:24:21 +01:00
|
|
|
let mut config = play.config();
|
2023-11-03 17:48:27 +01:00
|
|
|
config.set_position_update_interval(250);
|
2024-03-11 17:24:21 +01:00
|
|
|
play.set_config(config).unwrap();
|
|
|
|
|
play.set_video_track_enabled(false);
|
|
|
|
|
|
|
|
|
|
let play_signal_adapter = gstreamer_play::PlaySignalAdapter::new(&play);
|
2023-11-03 17:48:27 +01:00
|
|
|
|
|
|
|
|
let obj = Fragile::new(self.obj().to_owned());
|
2024-03-11 17:24:21 +01:00
|
|
|
play_signal_adapter.connect_end_of_stream(move |_| {
|
2023-11-03 17:48:27 +01:00
|
|
|
obj.get().next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let obj = Fragile::new(self.obj().to_owned());
|
2024-03-11 17:24:21 +01:00
|
|
|
play_signal_adapter.connect_position_updated(move |_, position| {
|
2023-11-07 15:57:56 +01:00
|
|
|
if let Some(position) = position {
|
2023-11-03 17:48:27 +01:00
|
|
|
let obj = obj.get();
|
2023-11-07 15:57:56 +01:00
|
|
|
obj.imp().position_ms.set(position.mseconds());
|
|
|
|
|
obj.notify_position_ms();
|
2023-11-03 17:48:27 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let obj = Fragile::new(self.obj().to_owned());
|
2024-03-11 17:24:21 +01:00
|
|
|
play_signal_adapter.connect_duration_changed(move |_, duration| {
|
2023-11-03 17:48:27 +01:00
|
|
|
if let Some(duration) = duration {
|
|
|
|
|
let obj = obj.get();
|
|
|
|
|
let imp = obj.imp();
|
2024-06-10 16:57:36 +02:00
|
|
|
|
2023-11-07 15:57:56 +01:00
|
|
|
imp.position_ms.set(0);
|
|
|
|
|
obj.notify_position_ms();
|
2024-06-10 16:57:36 +02:00
|
|
|
|
2023-11-07 15:57:56 +01:00
|
|
|
imp.duration_ms.set(duration.mseconds());
|
2023-11-03 17:48:27 +01:00
|
|
|
obj.notify_duration_ms();
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-03-11 17:24:21 +01:00
|
|
|
|
|
|
|
|
self.play.set(play).unwrap();
|
|
|
|
|
self.play_signal_adapter.set(play_signal_adapter).unwrap();
|
2023-11-03 17:48:27 +01:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct MusicusPlayer(ObjectSubclass<imp::MusicusPlayer>);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MusicusPlayer {
|
|
|
|
|
pub fn new() -> Self {
|
2023-10-25 17:45:32 +02:00
|
|
|
glib::Object::builder()
|
|
|
|
|
.property("active", false)
|
|
|
|
|
.property("playing", false)
|
|
|
|
|
.property("playlist", gio::ListStore::new::<PlaylistItem>())
|
|
|
|
|
.property("current-index", 0u32)
|
2023-11-07 15:57:56 +01:00
|
|
|
.property("position-ms", 0u64)
|
2023-11-03 17:48:27 +01:00
|
|
|
.property("duration-ms", 60_000u64)
|
2023-10-25 17:45:32 +02:00
|
|
|
.build()
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 18:59:47 +01:00
|
|
|
pub fn connect_raise<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
|
|
|
|
self.connect_local("raise", true, move |values| {
|
|
|
|
|
let obj = values[0].get::<Self>().unwrap();
|
|
|
|
|
f(&obj);
|
|
|
|
|
None
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-10 16:57:36 +02:00
|
|
|
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::<Vec<String>>()
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 17:45:32 +02:00
|
|
|
pub fn append(&self, tracks: Vec<PlaylistItem>) {
|
|
|
|
|
let playlist = self.playlist();
|
2023-10-27 12:32:40 +02:00
|
|
|
|
2023-10-25 17:45:32 +02:00
|
|
|
for track in tracks {
|
|
|
|
|
playlist.append(&track);
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 17:48:27 +01:00
|
|
|
if !self.active() && playlist.n_items() > 0 {
|
|
|
|
|
self.set_active(true);
|
|
|
|
|
self.set_current_index(0);
|
|
|
|
|
self.play();
|
|
|
|
|
}
|
2023-10-25 17:45:32 +02:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 18:20:41 +01:00
|
|
|
pub fn play_pause(&self) {
|
|
|
|
|
if self.playing() {
|
|
|
|
|
self.pause();
|
|
|
|
|
} else {
|
|
|
|
|
self.play();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 17:45:32 +02:00
|
|
|
pub fn play(&self) {
|
2024-03-11 17:24:21 +01:00
|
|
|
let imp = self.imp();
|
|
|
|
|
imp.play.get().unwrap().play();
|
2023-11-03 17:48:27 +01:00
|
|
|
self.set_playing(true);
|
2024-03-11 17:24:21 +01:00
|
|
|
imp.mpris
|
2023-11-03 18:59:47 +01:00
|
|
|
.get()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_playback_status(PlaybackStatus::Playing);
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn pause(&self) {
|
2024-03-11 17:24:21 +01:00
|
|
|
let imp = self.imp();
|
|
|
|
|
imp.play.get().unwrap().pause();
|
2023-11-03 17:48:27 +01:00
|
|
|
self.set_playing(false);
|
2024-03-11 17:24:21 +01:00
|
|
|
imp.mpris
|
2023-11-03 18:59:47 +01:00
|
|
|
.get()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_playback_status(PlaybackStatus::Paused);
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
2023-10-27 12:32:40 +02:00
|
|
|
|
2023-11-07 15:57:56 +01:00
|
|
|
pub fn seek_to(&self, time_ms: u64) {
|
2024-03-11 17:24:21 +01:00
|
|
|
let imp = self.imp();
|
|
|
|
|
imp.play
|
|
|
|
|
.get()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.seek(gst::ClockTime::from_mseconds(time_ms));
|
2023-11-07 15:57:56 +01:00
|
|
|
}
|
|
|
|
|
|
2023-10-27 12:32:40 +02:00
|
|
|
pub fn current_item(&self) -> Option<PlaylistItem> {
|
|
|
|
|
let imp = self.imp();
|
|
|
|
|
imp.playlist
|
|
|
|
|
.get()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.item(imp.current_index.get())
|
|
|
|
|
.and_downcast::<PlaylistItem>()
|
|
|
|
|
}
|
2023-11-03 16:22:58 +01:00
|
|
|
|
|
|
|
|
pub fn next(&self) {
|
|
|
|
|
if self.current_index() < self.playlist().n_items() - 1 {
|
|
|
|
|
self.set_current_index(self.current_index() + 1);
|
2024-06-10 16:57:36 +02:00
|
|
|
} else if let Some(program) = self.program() {
|
|
|
|
|
self.generate_items(&program);
|
|
|
|
|
self.set_current_index(self.current_index() + 1);
|
2023-11-03 16:22:58 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn previous(&self) {
|
|
|
|
|
if self.current_index() > 0 {
|
|
|
|
|
self.set_current_index(self.current_index() - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-10 16:57:36 +02:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-29 21:18:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for MusicusPlayer {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|