changeset 4:26f695129c86

*: fixes - seeking is actually relative - added track IDs (yes these weren't implemented yet even though they're required), of the format /org/foobar2000/foobar2000/trackids/(hex encoded file path) - added playlist fetching for a possible implementation of the playlist protocol
author Paper <paper@tflc.us>
date Sun, 05 Apr 2026 02:26:19 -0400
parents 18f743c980fa
children 8f71820abe71
files Cargo.toml src/beefweb.rs src/player.rs
diffstat 3 files changed, 96 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.toml	Sat Apr 04 17:03:22 2026 -0400
+++ b/Cargo.toml	Sun Apr 05 02:26:19 2026 -0400
@@ -17,3 +17,4 @@
 uuid = { version = "1.23.0", features = ["v4"] }
 bytes = "1.11.0"
 urlencoding = "2.1.3"
+hex = "0.4.3"
--- a/src/beefweb.rs	Sat Apr 04 17:03:22 2026 -0400
+++ b/src/beefweb.rs	Sun Apr 05 02:26:19 2026 -0400
@@ -185,6 +185,24 @@
 	playlist_items: PlaylistItems,
 }
 
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct Playlist {
+	pub id: String,
+	pub index: i64,
+	pub title: String,
+	#[serde(rename = "isCurrent")]
+	pub current: bool,
+	#[serde(rename = "itemCount")]
+	pub item_count: i64,
+	#[serde(rename = "totalTime")]
+	pub total_time: f64,
+}
+
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
+pub struct Playlists {
+	pub playlists: Vec<Playlist>,
+}
+
 /* --- NOW, THE ACTUAL BEEFWEB THING */
 
 pub struct Beefweb {
@@ -369,6 +387,18 @@
 		}).await;
 	}
 
+	pub async fn seek(&self, position: f64) -> Result<(), anyhow::Error>
+	{
+		return self.set_player(&SetPlayer {
+			volume: None,
+			relative_volume: None,
+			muted: None,
+			position: None,
+			relative_position: Some(position),
+			options: None,
+		}).await;
+	}
+
 /* TODO -- finish this; need to link between string and enum
 
 	pub async fn set_playback_order(&self, ) -> Result<(), anyhow::Error>
@@ -401,6 +431,17 @@
 		return self.playlist_items(playlist_id, index, 1, columns).await;
 	}
 
+	pub async fn playlists(&self) -> Result<Vec<Playlist>, anyhow::Error>
+	{
+		return Ok(self.client
+			.get(format!("{}/playlists", self.base_url))
+			.send()
+			.await?
+			.json::<Playlists>()
+			.await?
+			.playlists);
+	}
+
 	pub async fn artwork(&self, playlist_id: &str, index: i64) -> Result<bytes::Bytes, anyhow::Error>
 	{
 		return Ok(self.client
--- a/src/player.rs	Sat Apr 04 17:03:22 2026 -0400
+++ b/src/player.rs	Sun Apr 05 02:26:19 2026 -0400
@@ -206,7 +206,7 @@
 
 	async fn seek(&self, offset: mpris_server::Time) -> fdo::Result<()>
 	{
-		let pl = self.bw.set_position(time_to_secs(offset)).await;
+		let pl = self.bw.seek(time_to_secs(offset)).await;
 
 		match pl {
 			Err(_) => return Err(fdo::Error::Failed("uhoh".to_string())),
@@ -313,6 +313,7 @@
 				_ => (),
 			};
 			x.set_album_artist(Some([track.columns.get(5).unwrap()]));
+			x.set_trackid(format!("/org/foobar2000/foobar2000/trackids/{}", hex::encode(track.columns.get(6).unwrap())));
 			/* Why is this an i32 ??? It would make more sense as f32 or f64 */
 			match track.columns.get(7).unwrap().parse::<i32>() {
 				Ok(v) => { x.set_audio_bpm(Some(v)) },
@@ -420,16 +421,65 @@
 		return Ok(mpris_server::PlaybackRate::default());
 	}
 
-	/* WTF is this supposed to do?  --paper */
+	/* FIXME to implement this we would have to search through each playlist
+	 * for the track, and if it doesn't exist, append it to the current playlist.
+	 *
+	 * THEN we can add it directly into the play queue. */
 	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 */
+	/* TODO:
+	 *
+	 * We can effectively implement this "proper" by detecting a file://
+	 * path, URL decoding it, and prepending "Z:". */
 	async fn open_uri(&self, uri: String) -> fdo::Result<()>
 	{
 		return Ok(());
 	}
 }
+
+/* -- unfinished impl, don't mind this
+
+
+impl mpris_server::LocalPlaylistsInterface for BeefwebPlayer {
+	async fn activate_playlist(&self, playlist_id: mpris_server::PlaylistId) -> fdo::Result<()>
+	{
+		return Err(fdo::Error::Failed("uhoh".to_string()));
+	}
+
+	async fn get_playlists(&self, index: u32, max_count: u32, order: mpris_server::PlaylistOrdering, reverse_order: bool) -> fdo::Result<Vec<mpris_server::Playlist>>
+	{
+		let mut v: Vec<mpris_server::Playlist> = Vec::new();
+
+		let playlists = self.bw.playlists().await;
+
+		match playlists {
+			Ok(ref v) => (),
+			_ => return Err(fdo::Error::Failed("req failed".to_string())),
+		}
+
+		for playlist in playlists.unwrap() {
+
+		}
+
+		return Err(fdo::Error::Failed("uhoh".to_string()));
+	}
+
+	async fn playlist_count(&self) -> fdo::Result<u32>
+	{
+		return Err(fdo::Error::Failed("unimpl".to_string()));
+	}
+
+	async fn orderings(&self) -> fdo::Result<Vec<mpris_server::PlaylistOrdering>>
+	{
+		todo!()
+	}
+
+	async fn active_playlist(&self) -> fdo::Result<Option<mpris_server::Playlist>>
+	{
+		todo!()
+	}
+}
+*/