Mercurial > beefweb_mpris
view src/player.rs @ 1:a5ee18c79a04
license
| author | Paper <paper@tflc.us> |
|---|---|
| date | Sat, 04 Apr 2026 12:34:46 -0400 |
| parents | d60ab8a4442f |
| children | 594c0f9d7972 |
line wrap: on
line source
/* * Beefweb <-> mpris "compatibility" layer. * * Copyright (C) 2026 Paper * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see * <https://www.gnu.org/licenses/>. */ use crate::beefweb; use zbus::fdo; use zbus::Result; use std::collections::HashMap; use std::io::Write; use std::cell::RefCell; /* teehee */ pub struct BeefwebPlayer { bw: beefweb::Beefweb, artcache: String, /* artmap * key: %path% column from beefweb * value: local path of the artwork * also LOL RUST FUCK */ artmap: RefCell<HashMap<String, String>>, } impl BeefwebPlayer { pub fn new(base: &str, artcache: &str) -> BeefwebPlayer { return BeefwebPlayer { bw: beefweb::Beefweb::new(base), artcache: artcache.to_string(), artmap: RefCell::new(HashMap::new()), }; } /* async fn get_artwork(&self, playlist_id: &str, index: i64, path: &str) -> fdo::Result<String> { match self.artmap.borrow().get(path) { Some(x) => return Ok(x.to_string()), _ => (), } /* Ok, art path isn't in the "cache". Ask beefweb for it. */ let art = self.bw.artwork(playlist_id, index); let artpath = format!("{}/{}", self.artcache, uuid::Uuid::new_v4()); /* XXX might be a good idea to check the bytes for an extension...? */ let fr = std::fs::OpenOptions::new() .write(true) .open(&artpath); match fr { Err(_) => return Err(fdo::Error::Failed("Failed to open file!".to_string())), _ => (), }; let mut f = fr.unwrap(); let artaw = art.await; match artaw { Err(_) => return Err(fdo::Error::Failed("Uh oh".to_string())), _ => (), }; f.write(artaw.unwrap().as_ref()); self.artmap.borrow_mut().insert(path.to_string(), artpath.to_string()); return Ok(artpath); } */ } fn secs_to_time(x: f64) -> mpris_server::Time { return mpris_server::Time::from_micros((x * 1000000.0).round() as i64); } fn time_to_secs(x: mpris_server::Time) -> f64 { return (x.as_micros() as f64) / 1000000.0; } impl mpris_server::LocalRootInterface for BeefwebPlayer { async fn raise(&self) -> fdo::Result<()> { /* don't care */ return Ok(()); } async fn quit(&self) -> fdo::Result<()> { /* don't care */ return Ok(()); } async fn can_quit(&self) -> fdo::Result<bool> { /* don't care */ return Ok(false); } async fn fullscreen(&self) -> fdo::Result<bool> { /* don't care */ return Ok(false); } async fn set_fullscreen(&self, _fullscreen: bool) -> Result<()> { /* don't care */ return Ok(()); } async fn can_set_fullscreen(&self) -> fdo::Result<bool> { return Ok(false); } async fn can_raise(&self) -> fdo::Result<bool> { return Ok(false); } async fn has_track_list(&self) -> fdo::Result<bool> { /* ??? */ return Ok(false); } async fn identity(&self) -> fdo::Result<String> { /* TODO: allow changing this */ return Ok("beefweb".into()); } async fn desktop_entry(&self) -> fdo::Result<String> { return Ok("foobar2000".into()); } async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> { return Ok([].to_vec()); } async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> { /* needs moar */ return Ok([].to_vec()); } } impl mpris_server::LocalPlayerInterface for BeefwebPlayer { async fn next(&self) -> fdo::Result<()> { self.bw.next().await; return Ok(()); } async fn previous(&self) -> fdo::Result<()> { self.bw.previous().await; return Ok(()); } async fn pause(&self) -> fdo::Result<()> { self.bw.pause().await; return Ok(()); } async fn play_pause(&self) -> fdo::Result<()> { self.bw.play_pause().await; return Ok(()); } async fn stop(&self) -> fdo::Result<()> { self.bw.stop().await; return Ok(()); } async fn play(&self) -> fdo::Result<()> { self.bw.play().await; return Ok(()); } async fn seek(&self, offset: mpris_server::Time) -> fdo::Result<()> { let pl = self.bw.set_position(time_to_secs(offset)).await; match pl { Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), _ => (), }; return Ok(()); } async fn position(&self) -> fdo::Result<mpris_server::Time> { let pl = self.bw.player().await; match pl { Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), _ => (), }; return Ok(secs_to_time(pl.unwrap().active_item.position)); } async fn playback_status(&self) -> fdo::Result<mpris_server::PlaybackStatus> { let p = self.bw.player().await; match p { Err(_) => return Err(fdo::Error::Failed("wtf".to_string())), _ => (), }; return match p.unwrap().playback_state.as_str() { "playing" => Ok(mpris_server::PlaybackStatus::Playing), "stopped" => Ok(mpris_server::PlaybackStatus::Stopped), "paused" => Ok(mpris_server::PlaybackStatus::Paused), _ => Err(fdo::Error::Failed("deez nuts".to_string())), }; } async fn loop_status(&self) -> fdo::Result<mpris_server::LoopStatus> { return Ok(mpris_server::LoopStatus::None); } async fn set_loop_status(&self, loop_status: mpris_server::LoopStatus) -> Result<()> { return Ok(()); } async fn shuffle(&self) -> fdo::Result<bool> { /* TODO */ println!("Shuffle"); return Ok(false); } async fn set_shuffle(&self, shuffle: bool) -> Result<()> { /* TODO */ println!("SetShuffle({shuffle})"); return Ok(()); } async fn metadata(&self) -> fdo::Result<mpris_server::Metadata> { let pl = self.bw.player().await; match pl { Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), _ => (), }; let p = pl.unwrap(); let playlist_items_result = self.bw.playlist_items(p.active_item.playlist_id.as_str(), p.active_item.index, 1, ["%title%", "%artist%", "%album%", "%discnumber%", "%tracknumber%", "%album artist%", "%path%"].to_vec()).await; match playlist_items_result { Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), _ => (), }; let playlist_items = playlist_items_result.unwrap(); let track = playlist_items.items.get(0).unwrap(); /* let artwork = self.get_artwork(p.active_item.playlist_id.as_str(), p.active_item.index, track.columns.get(6).unwrap()); */ let builder = mpris_server::Metadata::builder() .length(secs_to_time(p.active_item.duration)) .album(track.columns.get(2).unwrap()) .artist([track.columns.get(1).unwrap()]) .disc_number(track.columns.get(3).unwrap().parse::<i32>().unwrap()) .track_number(track.columns.get(4).unwrap().parse::<i32>().unwrap()) .title(track.columns.get(0).unwrap()) .album_artist([track.columns.get(5).unwrap()]); /* return match artwork.await { Ok(x) => Ok(builder.art_url(urlencoding::encode(format!("file://{}", x).as_str())).build()), _ => Ok(builder.build()), }; */ return Ok(builder.build()); } async fn volume(&self) -> fdo::Result<mpris_server::Volume> { return Ok(mpris_server::Volume::default()); } async fn set_volume(&self, volume: mpris_server::Volume) -> Result<()> { return Ok(()); } /* "can" functions -- all work */ async fn can_go_next(&self) -> fdo::Result<bool> { return Ok(true); } async fn can_go_previous(&self) -> fdo::Result<bool> { return Ok(true); } async fn can_play(&self) -> fdo::Result<bool> { return Ok(true); } async fn can_pause(&self) -> fdo::Result<bool> { return Ok(true); } async fn can_seek(&self) -> fdo::Result<bool> { return Ok(true); } async fn can_control(&self) -> fdo::Result<bool> { return Ok(true); } /* --- UNSUPPORTED */ async fn rate(&self) -> fdo::Result<mpris_server::PlaybackRate> { return Ok(mpris_server::PlaybackRate::default()); } async fn set_rate(&self, rate: mpris_server::PlaybackRate) -> Result<()> { return Ok(()); } async fn minimum_rate(&self) -> fdo::Result<mpris_server::PlaybackRate> { return Ok(mpris_server::PlaybackRate::default()); } async fn maximum_rate(&self) -> fdo::Result<mpris_server::PlaybackRate> { return Ok(mpris_server::PlaybackRate::default()); } /* WTF is this supposed to do? --paper */ async fn set_position(&self, track_id: mpris_server::TrackId, position: mpris_server::Time) -> fdo::Result<()> { return Ok(()); } /* Beefweb doesn't really have this, and it would be * pointless anyway, unless we used winepath */ async fn open_uri(&self, uri: String) -> fdo::Result<()> { return Ok(()); } }
