Mercurial > beefweb_mpris
comparison src/player.rs @ 0:d60ab8a4442f
*: check in
| author | Paper <paper@tflc.us> |
|---|---|
| date | Sat, 04 Apr 2026 12:32:50 -0400 |
| parents | |
| children | a5ee18c79a04 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d60ab8a4442f |
|---|---|
| 1 use crate::beefweb; | |
| 2 | |
| 3 use zbus::fdo; | |
| 4 use zbus::Result; | |
| 5 use std::collections::HashMap; | |
| 6 use std::io::Write; | |
| 7 use std::cell::RefCell; | |
| 8 | |
| 9 /* teehee */ | |
| 10 pub struct BeefwebPlayer { | |
| 11 bw: beefweb::Beefweb, | |
| 12 artcache: String, | |
| 13 /* artmap | |
| 14 * key: %path% column from beefweb | |
| 15 * value: local path of the artwork | |
| 16 * also LOL RUST FUCK */ | |
| 17 artmap: RefCell<HashMap<String, String>>, | |
| 18 } | |
| 19 | |
| 20 impl BeefwebPlayer { | |
| 21 pub fn new(base: &str, artcache: &str) -> BeefwebPlayer | |
| 22 { | |
| 23 return BeefwebPlayer { | |
| 24 bw: beefweb::Beefweb::new(base), | |
| 25 artcache: artcache.to_string(), | |
| 26 artmap: RefCell::new(HashMap::new()), | |
| 27 }; | |
| 28 } | |
| 29 | |
| 30 /* | |
| 31 async fn get_artwork(&self, playlist_id: &str, index: i64, path: &str) -> fdo::Result<String> | |
| 32 { | |
| 33 match self.artmap.borrow().get(path) { | |
| 34 Some(x) => return Ok(x.to_string()), | |
| 35 _ => (), | |
| 36 } | |
| 37 | |
| 38 /* Ok, art path isn't in the "cache". Ask beefweb for it. */ | |
| 39 let art = self.bw.artwork(playlist_id, index); | |
| 40 let artpath = format!("{}/{}", self.artcache, uuid::Uuid::new_v4()); | |
| 41 | |
| 42 /* XXX might be a good idea to check the bytes for an extension...? */ | |
| 43 let fr = std::fs::OpenOptions::new() | |
| 44 .write(true) | |
| 45 .open(&artpath); | |
| 46 | |
| 47 match fr { | |
| 48 Err(_) => return Err(fdo::Error::Failed("Failed to open file!".to_string())), | |
| 49 _ => (), | |
| 50 }; | |
| 51 | |
| 52 let mut f = fr.unwrap(); | |
| 53 | |
| 54 let artaw = art.await; | |
| 55 | |
| 56 match artaw { | |
| 57 Err(_) => return Err(fdo::Error::Failed("Uh oh".to_string())), | |
| 58 _ => (), | |
| 59 }; | |
| 60 | |
| 61 f.write(artaw.unwrap().as_ref()); | |
| 62 | |
| 63 self.artmap.borrow_mut().insert(path.to_string(), artpath.to_string()); | |
| 64 | |
| 65 return Ok(artpath); | |
| 66 } | |
| 67 */ | |
| 68 } | |
| 69 | |
| 70 fn secs_to_time(x: f64) -> mpris_server::Time | |
| 71 { | |
| 72 return mpris_server::Time::from_micros((x * 1000000.0).round() as i64); | |
| 73 } | |
| 74 | |
| 75 fn time_to_secs(x: mpris_server::Time) -> f64 | |
| 76 { | |
| 77 return (x.as_micros() as f64) / 1000000.0; | |
| 78 } | |
| 79 | |
| 80 impl mpris_server::LocalRootInterface for BeefwebPlayer { | |
| 81 async fn raise(&self) -> fdo::Result<()> | |
| 82 { | |
| 83 /* don't care */ | |
| 84 return Ok(()); | |
| 85 } | |
| 86 | |
| 87 async fn quit(&self) -> fdo::Result<()> | |
| 88 { | |
| 89 /* don't care */ | |
| 90 return Ok(()); | |
| 91 } | |
| 92 | |
| 93 async fn can_quit(&self) -> fdo::Result<bool> | |
| 94 { | |
| 95 /* don't care */ | |
| 96 return Ok(false); | |
| 97 } | |
| 98 | |
| 99 async fn fullscreen(&self) -> fdo::Result<bool> | |
| 100 { | |
| 101 /* don't care */ | |
| 102 return Ok(false); | |
| 103 } | |
| 104 | |
| 105 async fn set_fullscreen(&self, _fullscreen: bool) -> Result<()> | |
| 106 { | |
| 107 /* don't care */ | |
| 108 return Ok(()); | |
| 109 } | |
| 110 | |
| 111 async fn can_set_fullscreen(&self) -> fdo::Result<bool> | |
| 112 { | |
| 113 return Ok(false); | |
| 114 } | |
| 115 | |
| 116 async fn can_raise(&self) -> fdo::Result<bool> | |
| 117 { | |
| 118 return Ok(false); | |
| 119 } | |
| 120 | |
| 121 async fn has_track_list(&self) -> fdo::Result<bool> | |
| 122 { | |
| 123 /* ??? */ | |
| 124 return Ok(false); | |
| 125 } | |
| 126 | |
| 127 async fn identity(&self) -> fdo::Result<String> | |
| 128 { | |
| 129 /* TODO: allow changing this */ | |
| 130 return Ok("beefweb".into()); | |
| 131 } | |
| 132 | |
| 133 async fn desktop_entry(&self) -> fdo::Result<String> | |
| 134 { | |
| 135 return Ok("foobar2000".into()); | |
| 136 } | |
| 137 | |
| 138 async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> | |
| 139 { | |
| 140 return Ok([].to_vec()); | |
| 141 } | |
| 142 | |
| 143 async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> | |
| 144 { | |
| 145 /* needs moar */ | |
| 146 return Ok([].to_vec()); | |
| 147 } | |
| 148 } | |
| 149 | |
| 150 impl mpris_server::LocalPlayerInterface for BeefwebPlayer { | |
| 151 async fn next(&self) -> fdo::Result<()> | |
| 152 { | |
| 153 self.bw.next().await; | |
| 154 return Ok(()); | |
| 155 } | |
| 156 | |
| 157 async fn previous(&self) -> fdo::Result<()> | |
| 158 { | |
| 159 self.bw.previous().await; | |
| 160 return Ok(()); | |
| 161 } | |
| 162 | |
| 163 async fn pause(&self) -> fdo::Result<()> | |
| 164 { | |
| 165 self.bw.pause().await; | |
| 166 return Ok(()); | |
| 167 } | |
| 168 | |
| 169 async fn play_pause(&self) -> fdo::Result<()> | |
| 170 { | |
| 171 self.bw.play_pause().await; | |
| 172 return Ok(()); | |
| 173 } | |
| 174 | |
| 175 async fn stop(&self) -> fdo::Result<()> | |
| 176 { | |
| 177 self.bw.stop().await; | |
| 178 return Ok(()); | |
| 179 } | |
| 180 | |
| 181 async fn play(&self) -> fdo::Result<()> | |
| 182 { | |
| 183 self.bw.play().await; | |
| 184 return Ok(()); | |
| 185 } | |
| 186 | |
| 187 async fn seek(&self, offset: mpris_server::Time) -> fdo::Result<()> | |
| 188 { | |
| 189 let pl = self.bw.set_position(time_to_secs(offset)).await; | |
| 190 | |
| 191 match pl { | |
| 192 Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), | |
| 193 _ => (), | |
| 194 }; | |
| 195 | |
| 196 return Ok(()); | |
| 197 } | |
| 198 | |
| 199 async fn position(&self) -> fdo::Result<mpris_server::Time> | |
| 200 { | |
| 201 let pl = self.bw.player().await; | |
| 202 | |
| 203 match pl { | |
| 204 Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), | |
| 205 _ => (), | |
| 206 }; | |
| 207 | |
| 208 return Ok(secs_to_time(pl.unwrap().active_item.position)); | |
| 209 } | |
| 210 | |
| 211 async fn playback_status(&self) -> fdo::Result<mpris_server::PlaybackStatus> | |
| 212 { | |
| 213 let p = self.bw.player().await; | |
| 214 | |
| 215 match p { | |
| 216 Err(_) => return Err(fdo::Error::Failed("wtf".to_string())), | |
| 217 _ => (), | |
| 218 }; | |
| 219 | |
| 220 return match p.unwrap().playback_state.as_str() { | |
| 221 "playing" => Ok(mpris_server::PlaybackStatus::Playing), | |
| 222 "stopped" => Ok(mpris_server::PlaybackStatus::Stopped), | |
| 223 "paused" => Ok(mpris_server::PlaybackStatus::Paused), | |
| 224 _ => Err(fdo::Error::Failed("deez nuts".to_string())), | |
| 225 }; | |
| 226 } | |
| 227 | |
| 228 async fn loop_status(&self) -> fdo::Result<mpris_server::LoopStatus> | |
| 229 { | |
| 230 return Ok(mpris_server::LoopStatus::None); | |
| 231 } | |
| 232 | |
| 233 async fn set_loop_status(&self, loop_status: mpris_server::LoopStatus) -> Result<()> | |
| 234 { | |
| 235 return Ok(()); | |
| 236 } | |
| 237 | |
| 238 async fn shuffle(&self) -> fdo::Result<bool> | |
| 239 { | |
| 240 /* TODO */ | |
| 241 println!("Shuffle"); | |
| 242 return Ok(false); | |
| 243 } | |
| 244 | |
| 245 async fn set_shuffle(&self, shuffle: bool) -> Result<()> | |
| 246 { | |
| 247 /* TODO */ | |
| 248 println!("SetShuffle({shuffle})"); | |
| 249 return Ok(()); | |
| 250 } | |
| 251 | |
| 252 async fn metadata(&self) -> fdo::Result<mpris_server::Metadata> | |
| 253 { | |
| 254 let pl = self.bw.player().await; | |
| 255 | |
| 256 match pl { | |
| 257 Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), | |
| 258 _ => (), | |
| 259 }; | |
| 260 | |
| 261 let p = pl.unwrap(); | |
| 262 | |
| 263 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; | |
| 264 | |
| 265 match playlist_items_result { | |
| 266 Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())), | |
| 267 _ => (), | |
| 268 }; | |
| 269 | |
| 270 let playlist_items = playlist_items_result.unwrap(); | |
| 271 | |
| 272 let track = playlist_items.items.get(0).unwrap(); | |
| 273 | |
| 274 /* | |
| 275 let artwork = self.get_artwork(p.active_item.playlist_id.as_str(), p.active_item.index, track.columns.get(6).unwrap()); | |
| 276 */ | |
| 277 | |
| 278 let builder = mpris_server::Metadata::builder() | |
| 279 .length(secs_to_time(p.active_item.duration)) | |
| 280 .album(track.columns.get(2).unwrap()) | |
| 281 .artist([track.columns.get(1).unwrap()]) | |
| 282 .disc_number(track.columns.get(3).unwrap().parse::<i32>().unwrap()) | |
| 283 .track_number(track.columns.get(4).unwrap().parse::<i32>().unwrap()) | |
| 284 .title(track.columns.get(0).unwrap()) | |
| 285 .album_artist([track.columns.get(5).unwrap()]); | |
| 286 | |
| 287 /* | |
| 288 return match artwork.await { | |
| 289 Ok(x) => Ok(builder.art_url(urlencoding::encode(format!("file://{}", x).as_str())).build()), | |
| 290 _ => Ok(builder.build()), | |
| 291 }; | |
| 292 */ | |
| 293 return Ok(builder.build()); | |
| 294 } | |
| 295 | |
| 296 async fn volume(&self) -> fdo::Result<mpris_server::Volume> | |
| 297 { | |
| 298 return Ok(mpris_server::Volume::default()); | |
| 299 } | |
| 300 | |
| 301 async fn set_volume(&self, volume: mpris_server::Volume) -> Result<()> | |
| 302 { | |
| 303 return Ok(()); | |
| 304 } | |
| 305 | |
| 306 /* "can" functions -- all work */ | |
| 307 | |
| 308 async fn can_go_next(&self) -> fdo::Result<bool> | |
| 309 { | |
| 310 return Ok(true); | |
| 311 } | |
| 312 | |
| 313 async fn can_go_previous(&self) -> fdo::Result<bool> | |
| 314 { | |
| 315 return Ok(true); | |
| 316 } | |
| 317 | |
| 318 async fn can_play(&self) -> fdo::Result<bool> | |
| 319 { | |
| 320 return Ok(true); | |
| 321 } | |
| 322 | |
| 323 async fn can_pause(&self) -> fdo::Result<bool> | |
| 324 { | |
| 325 return Ok(true); | |
| 326 } | |
| 327 | |
| 328 async fn can_seek(&self) -> fdo::Result<bool> | |
| 329 { | |
| 330 return Ok(true); | |
| 331 } | |
| 332 | |
| 333 async fn can_control(&self) -> fdo::Result<bool> | |
| 334 { | |
| 335 return Ok(true); | |
| 336 } | |
| 337 | |
| 338 /* --- UNSUPPORTED */ | |
| 339 | |
| 340 async fn rate(&self) -> fdo::Result<mpris_server::PlaybackRate> | |
| 341 { | |
| 342 return Ok(mpris_server::PlaybackRate::default()); | |
| 343 } | |
| 344 | |
| 345 async fn set_rate(&self, rate: mpris_server::PlaybackRate) -> Result<()> | |
| 346 { | |
| 347 return Ok(()); | |
| 348 } | |
| 349 | |
| 350 async fn minimum_rate(&self) -> fdo::Result<mpris_server::PlaybackRate> | |
| 351 { | |
| 352 return Ok(mpris_server::PlaybackRate::default()); | |
| 353 } | |
| 354 | |
| 355 async fn maximum_rate(&self) -> fdo::Result<mpris_server::PlaybackRate> | |
| 356 { | |
| 357 return Ok(mpris_server::PlaybackRate::default()); | |
| 358 } | |
| 359 | |
| 360 /* WTF is this supposed to do? --paper */ | |
| 361 async fn set_position(&self, track_id: mpris_server::TrackId, position: mpris_server::Time) -> fdo::Result<()> | |
| 362 { | |
| 363 return Ok(()); | |
| 364 } | |
| 365 | |
| 366 /* Beefweb doesn't really have this, and it would be | |
| 367 * pointless anyway, unless we used winepath */ | |
| 368 async fn open_uri(&self, uri: String) -> fdo::Result<()> | |
| 369 { | |
| 370 return Ok(()); | |
| 371 } | |
| 372 } |
