use std::{rc::Rc, sync::mpsc::Receiver, time::Duration};
use anyhow::Result;
use druid::{im, AppDelegate};
use souvlaki::{MediaControlEvent, MediaControls};
use tf_db::Song;
use tf_player::{player::Player, PlayerController};
use url::Url;
use uuid::Uuid;
use crate::{
command,
state::{SongEdit, SongListItem},
NewSong, State,
};
pub struct Delegate {
db: tf_db::Client,
player: PlayerController,
media_controls: MediaControls,
media_events: Receiver<MediaControlEvent>,
}
impl Delegate {
pub fn new(db: tf_db::Client) -> Result<Self> {
let (media_controls, media_events) = {
let config = souvlaki::PlatformConfig {
dbus_name: "tunefire",
display_name: "Tunefire",
hwnd: None,
};
let mut controls = MediaControls::new(config).unwrap();
let (to_handler, from_media_events) = std::sync::mpsc::sync_channel(32);
controls
.attach(move |e| {
to_handler.send(e).ok();
})
.unwrap();
(controls, from_media_events)
};
Ok(Self {
db,
player: Player::new()?,
media_controls,
media_events,
})
}
pub fn update_media_controls(&mut self, data: &State) {
use souvlaki::{MediaMetadata, MediaPlayback, MediaPosition};
match &data.current_song {
Some(song) => {
self.media_controls
.set_metadata(MediaMetadata {
title: Some(song.title.as_str()),
..Default::default()
})
.ok();
self.media_controls
.set_playback(MediaPlayback::Playing {
progress: Some(MediaPosition(Duration::from_secs(1))),
})
.ok();
}
None => {
self.media_controls
.set_metadata(MediaMetadata::default())
.ok();
self.media_controls
.set_playback(MediaPlayback::Stopped)
.ok();
}
}
}
}
impl AppDelegate<State> for Delegate {
fn event(
&mut self,
_ctx: &mut druid::DelegateCtx,
_window_id: druid::WindowId,
event: druid::Event,
_data: &mut State,
_env: &druid::Env,
) -> Option<druid::Event> {
Some(event)
}
fn command(
&mut self,
_ctx: &mut druid::DelegateCtx,
_target: druid::Target,
cmd: &druid::Command,
data: &mut State,
_env: &druid::Env,
) -> druid::Handled {
match cmd {
_ if cmd.is(command::QUERY_RUN) => {
match data.query.parse::<tf_db::Filter>() {
Ok(filter) => match self.db.list_filtered(&filter) {
Ok(songs) => {
data.songs = songs
.iter()
.map(|s| SongListItem {
song: Rc::new(s.clone()),
selected: false,
})
.collect();
}
Err(e) => println!("error while querying {:?}", e),
},
Err(e) => println!("invalid query {e:?}"),
}
druid::Handled::Yes
}
_ if cmd.is(command::QUERY_PLAY) => {
let mut queue: im::Vector<Rc<Song>> = data
.songs
.iter()
.filter_map(|item| self.db.get_song(item.song.id).ok())
.map(|s| Rc::new(s))
.collect();
if let Some(song) = queue.pop_front() {
self.player
.queue_song(Url::parse(&song.source).unwrap())
.unwrap();
data.current_song = Some(song);
self.update_media_controls(data);
}
data.queue = queue;
druid::Handled::No
}
_ if cmd.is(command::PLAYER_TICK) => {
match &self.media_events.try_recv() {
Ok(MediaControlEvent::Play) => self.player.play().unwrap(),
Ok(MediaControlEvent::Pause) => self.player.pause().unwrap(),
Ok(MediaControlEvent::Toggle) => self.player.play_pause().unwrap(),
_ => {}
}
let ps = (*self.player.state().read()).clone();
let until_empty = ps
.get_playing()
.map(|p| p.song.duration - p.offset)
.unwrap_or_default() + Duration::from_secs(
self.player.nb_queued() as u64 * 10000000,
);
if until_empty < Duration::from_secs(1) {
if let Some(song) = data.queue.pop_front() {
self.player
.queue_song(Url::parse(&song.source).unwrap())
.unwrap();
data.current_song = Some(song);
self.update_media_controls(data);
} else {
data.current_song = None;
self.update_media_controls(data);
}
}
data.player_state = Rc::new(ps);
druid::Handled::Yes
}
_ if cmd.is(command::SONG_PLAY) => {
let id = cmd.get::<Uuid>(command::SONG_PLAY).unwrap();
let song = self.db.get_song(*id).unwrap();
let url = Url::parse(&song.source).unwrap();
self.player.queue_song(url).unwrap();
data.current_song = Some(Rc::new(song.clone()));
self.update_media_controls(data);
self.player.play().unwrap();
druid::Handled::No
}
_ if cmd.is(command::PLAYER_PLAY_PAUSE) => {
self.player.play_pause().unwrap();
druid::Handled::Yes
}
_ if cmd.is(command::PLAYER_SEEK) => {
let pos = cmd.get::<Duration>(command::PLAYER_SEEK).unwrap();
self.player.seek(*pos).unwrap();
druid::Handled::Yes
}
_ if cmd.is(command::UI_SONG_EDIT_OPEN) => {
let id = cmd.get::<Uuid>(command::UI_SONG_EDIT_OPEN).unwrap();
data.songs.iter_mut().for_each(|item| {
item.selected = item.song.id == *id;
});
if let Ok(song) = self.db.get_song(*id) {
data.song_edit = Some(SongEdit::new(song));
}
druid::Handled::Yes
}
_ if cmd.is(command::UI_SONG_EDIT_CLOSE) => {
data.song_edit = None;
druid::Handled::Yes
}
_ if cmd.is(command::SONG_ADD) => {
let NewSong { source, title } = cmd.get::<NewSong>(command::SONG_ADD).unwrap();
if let Ok(id) = self.db.add_song(source, title) {
let song = self.db.get_song(id).unwrap();
data.songs.push_back(SongListItem {
song: Rc::new(song),
selected: true,
});
data.new_song = NewSong::default();
}
druid::Handled::Yes
}
_ if cmd.is(command::SONG_DELETE) => {
let id = cmd.get::<Uuid>(command::SONG_DELETE).unwrap();
if let Ok(()) = self.db.delete_song(*id) {
data.songs.retain(|item| item.song.id != *id);
}
druid::Handled::Yes
}
_ if cmd.is(command::TAG_ADD) => {
cmd.get::<Uuid>(command::TAG_ADD).unwrap();
data.song_edit
.as_mut()
.unwrap()
.tags
.push_back((String::new(), 0.5));
druid::Handled::Yes
}
_ if cmd.is(command::TAG_RENAME) => {
let song_edit = data.song_edit.as_mut().unwrap();
let (from, to) = cmd.get::<(String, String)>(command::TAG_RENAME).unwrap();
if from != "" {
self.db.set_tag(*song_edit.id, from, 0.0).unwrap();
}
let tag = song_edit.tags.iter().find(|tag| &tag.0 == to).unwrap();
self.db.set_tag(*song_edit.id, to, tag.1).unwrap();
druid::Handled::Yes
}
_ if cmd.is(command::TAG_TWEAK) => {
let song_edit = data.song_edit.as_mut().unwrap();
let (name, value) = cmd.get::<(String, f32)>(command::TAG_TWEAK).unwrap();
if name != "" {
self.db.set_tag(*song_edit.id, name, *value).unwrap();
}
druid::Handled::Yes
}
_ if cmd.is(command::TAG_DELETE) => {
let tag_name = cmd.get::<String>(command::TAG_DELETE).unwrap();
let song_edit = data.song_edit.as_mut().unwrap();
match song_edit.tags.iter().find(|tag| &tag.0 == tag_name) {
Some((name, _)) => {
self.db.set_tag(*song_edit.id, &name, 0.0).unwrap();
}
_ => {}
}
song_edit.tags.retain(|item| &item.0 != tag_name);
druid::Handled::Yes
}
_ => druid::Handled::No,
}
}
fn window_added(
&mut self,
_id: druid::WindowId,
_handle: druid::WindowHandle,
_data: &mut State,
_env: &druid::Env,
_ctx: &mut druid::DelegateCtx,
) {
}
fn window_removed(
&mut self,
_id: druid::WindowId,
_data: &mut State,
_env: &druid::Env,
_ctx: &mut druid::DelegateCtx,
) {
}
}