Mercurial > beefweb_mpris
diff src/beefweb.rs @ 0:d60ab8a4442f
*: check in
| author | Paper <paper@tflc.us> |
|---|---|
| date | Sat, 04 Apr 2026 12:32:50 -0400 |
| parents | |
| children | a5ee18c79a04 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/beefweb.rs Sat Apr 04 12:32:50 2026 -0400 @@ -0,0 +1,270 @@ +/* + * Tiny layer for interfacing with beefweb. Does basically the + * bare minimum, and does not expose the whole API. + * + * 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/>. +*/ + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PlayerInfo { + pub name: String, + pub title: String, + pub version: String, + // fuck camel case + #[serde(rename = "pluginVersion")] + pub plugin_version: String, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct ActiveItemInfo { + #[serde(rename = "playlistId")] + pub playlist_id: String, + #[serde(rename = "playlistIndex")] + pub playlist_index: i64, + pub index: i64, + pub position: f64, + pub duration: f64, + pub columns: Vec<String>, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub enum VolumeType { + #[serde(rename = "db")] + DB, + #[serde(rename = "linear")] + LINEAR, + #[serde(rename = "upDown")] + UPDOWN, /* ??? */ +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct VolumeInfo { + pub r#type: VolumeType, + pub min: f64, + pub max: f64, + pub value: f64, + #[serde(rename = "isMuted")] + pub muted: bool, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct ApiPermissions { + #[serde(rename = "changePlaylists")] + pub change_playlists: bool, + #[serde(rename = "changeOutput")] + pub change_output: bool, + #[serde(rename = "changeClientConfig")] + pub change_client_config: bool, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct Player { + pub info: PlayerInfo, + #[serde(rename = "activeItem")] + pub active_item: ActiveItemInfo, + /* XXX actually an enum */ + #[serde(rename = "playbackState")] + pub playback_state: String, + pub volume: VolumeInfo, + pub permissions: ApiPermissions, +} + +/* {"player": {struct Player}} */ +#[derive(serde::Serialize, serde::Deserialize, Debug)] +struct PlayerHelper { + player: Player, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde(untagged)] +enum SetPlayerOption { + Integer { id: String, value: i64 }, + Boolean { id: String, value: bool }, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +#[serde_with::skip_serializing_none] +struct SetPlayer { + /* dB */ + volume: Option<f64>, + /* ??? */ + #[serde(rename = "relativeVolume")] + relative_volume: Option<f64>, + #[serde(rename = "isMuted")] + muted: Option<bool>, + position: Option<f64>, + #[serde(rename = "relativePosition")] + relative_position: Option<f64>, + /* teehee */ + options: Option<Vec<SetPlayerOption>>, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PlaylistItem { + pub columns: Vec<String> +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PlaylistItems { + pub offset: i64, + #[serde(rename = "totalCount")] + pub total_count: i64, + pub items: Vec<PlaylistItem>, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +struct PlaylistItemsHelper { + #[serde(rename = "playlistItems")] + playlist_items: PlaylistItems, +} + +/* --- NOW, THE ACTUAL BEEFWEB THING */ + +pub struct Beefweb { + client: reqwest::Client, + base_url: String, +} + +impl Beefweb { + pub fn new(base: &str) -> Beefweb + { + return Beefweb { + client: reqwest::Client::new(), + base_url: base.to_string(), + }; + } + + pub async fn player(&self) -> Result<Player, anyhow::Error> + { + return Ok(self.client + .get(format!("{}/player", self.base_url)) + .send() + .await? + .json::<PlayerHelper>() + .await? + .player); + } + + pub async fn volume(&self) -> Result<VolumeInfo, anyhow::Error> + { + return Ok(self.player().await?.volume); + } + + pub async fn position(&self) -> Result<f64, anyhow::Error> + { + return Ok(self.player().await?.active_item.position); + } + + /* XXX might be able to use macros for this? idk --paper */ + async fn play_pause_stop(&self, x: &str) -> Result<(), anyhow::Error> + { + self.client + .post(format!("{}/player/{}", self.base_url, x)) + .send() + .await?; + + return Ok(()); + } + + pub async fn play(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("play").await; + } + + pub async fn pause(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("pause").await; + } + + pub async fn stop(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("stop").await; + } + + pub async fn play_pause(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("play-pause").await; + } + + pub async fn next(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("next").await; + } + + pub async fn previous(&self) -> Result<(), anyhow::Error> + { + return self.play_pause_stop("previous").await; + } + + /* XXX what should this return? */ + async fn set_player(&self, p: &SetPlayer) -> Result<(), anyhow::Error> + { + self.client + .post(format!("{}/player", self.base_url)) + .json::<SetPlayer>(p) + .send() + .await?; + + return Ok(()); + } + + pub async fn set_volume(&self, volume: f64) -> Result<(), anyhow::Error> + { + return self.set_player(&SetPlayer { + volume: Some(volume), + relative_volume: None, + muted: None, + position: None, + relative_position: None, + options: None, + }).await; + } + + pub async fn set_position(&self, position: f64) -> Result<(), anyhow::Error> + { + return self.set_player(&SetPlayer { + volume: None, + relative_volume: Some(position), + muted: None, + position: None, + relative_position: None, + options: None, + }).await; + } + + pub async fn playlist_items(&self, playlist_id: &str, offset: i64, count: i64, columns: Vec<&str>) -> Result<PlaylistItems, anyhow::Error> + { + return Ok(self.client + .get(format!("{}/playlists/{}/items/{}:{}", self.base_url, playlist_id, offset, count)) + .query(&[("columns", columns.join(","))]) + .send() + .await? + .json::<PlaylistItemsHelper>() + .await? + .playlist_items); + } + + pub async fn artwork(&self, playlist_id: &str, index: i64) -> Result<bytes::Bytes, anyhow::Error> + { + return Ok(self.client + .get(format!("{}/artwork/{}/{}", self.base_url, playlist_id, index)) + .send() + .await? + .bytes() + .await?); + } +}
