From 2831c2b60cb61a14c7efee4ab5c0389eb3ad5469 Mon Sep 17 00:00:00 2001 From: Jokler Date: Sun, 2 Feb 2020 19:50:33 +0100 Subject: Add a very basic template using available info --- src/audio_player.rs | 29 +++++++++++++++++++++++- src/bot/master.rs | 22 ++++++++++++++----- src/bot/music.rs | 32 +++++++++++++++++++++++++-- src/playlist.rs | 10 +++++++++ src/web_server.rs | 63 ++++++++++++++++++++++++++++++++++++++--------------- src/youtube_dl.rs | 28 ++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/audio_player.rs b/src/audio_player.rs index cdb04d7..4df213f 100644 --- a/src/audio_player.rs +++ b/src/audio_player.rs @@ -13,6 +13,8 @@ use log::{debug, error, info, warn}; use std::sync::{Arc, RwLock}; use tokio02::sync::mpsc::UnboundedSender; +use crate::youtube_dl::AudioMetadata; + static GST_INIT: Once = Once::new(); #[derive(Copy, Clone, Debug)] @@ -33,8 +35,10 @@ pub struct AudioPlayer { bus: gst::Bus, http_src: gst::Element, + volume_f64: RwLock, volume: gst::Element, sender: Arc>>, + currently_playing: RwLock>, } fn make_element(factoryname: &str, display_name: &str) -> Result { @@ -111,8 +115,10 @@ impl AudioPlayer { bus, http_src, + volume_f64: RwLock::new(0.0), volume, sender, + currently_playing: RwLock::new(None), }) } @@ -173,7 +179,16 @@ impl AudioPlayer { Ok((audio_bin, volume, ghost_pad)) } - pub fn set_source_url(&self, location: String) -> Result<(), AudioPlayerError> { + pub fn set_metadata(&self, data: AudioMetadata) -> Result<(), AudioPlayerError> { + self.set_source_url(data.url.clone())?; + + let mut currently_playing = self.currently_playing.write().unwrap(); + *currently_playing = Some(data); + + Ok(()) + } + + fn set_source_url(&self, location: String) -> Result<(), AudioPlayerError> { info!("Setting location URI: {}", location); self.http_src.set_property("location", &location)?; @@ -181,6 +196,7 @@ impl AudioPlayer { } pub fn set_volume(&self, volume: f64) -> Result<(), AudioPlayerError> { + *self.volume_f64.write().unwrap() = volume; let db = 50.0 * volume.log10(); info!("Setting volume: {} -> {} dB", volume, db); @@ -203,9 +219,20 @@ impl AudioPlayer { } } + pub fn volume(&self) -> f64 { + *self.volume_f64.read().unwrap() + } + + pub fn currently_playing(&self) -> Option { + self.currently_playing.read().unwrap().clone() + } + pub fn reset(&self) -> Result<(), AudioPlayerError> { info!("Setting pipeline state to null"); + let mut currently_playing = self.currently_playing.write().unwrap(); + *currently_playing = None; + self.pipeline.set_state(gst::State::Null)?; Ok(()) diff --git a/src/bot/master.rs b/src/bot/master.rs index bc38cca..10a7572 100644 --- a/src/bot/master.rs +++ b/src/bot/master.rs @@ -213,14 +213,24 @@ impl MasterBot { Ok(()) } - pub fn names(&self) -> Vec { + pub fn bot_datas(&self) -> Vec { let music_bots = self.music_bots.read().unwrap(); - music_bots - .connected_bots - .iter() - .map(|(_, b)| b.name().to_owned()) - .collect() + let len = music_bots.connected_bots.len(); + let mut result = Vec::with_capacity(len); + for (name, bot) in &music_bots.connected_bots { + let bot_data = crate::web_server::BotData { + name: name.clone(), + state: bot.state(), + volume: bot.volume(), + currently_playing: bot.currently_playing(), + playlist: bot.playlist_to_vec(), + }; + + result.push(bot_data); + } + + result } pub fn quit(&self, reason: String) { diff --git a/src/bot/music.rs b/src/bot/music.rs index 920f1cb..d53e4a8 100644 --- a/src/bot/music.rs +++ b/src/bot/music.rs @@ -44,7 +44,7 @@ fn parse_seek(mut amount: &str) -> Result { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum State { Playing, Paused, @@ -52,6 +52,18 @@ pub enum State { EndOfStream, } +impl std::fmt::Display for State { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + match self { + State::Playing => write!(fmt, "Playing"), + State::Paused => write!(fmt, "Paused"), + State::Stopped | State::EndOfStream => write!(fmt, "Stopped"), + }?; + + Ok(()) + } +} + #[derive(Debug)] pub enum MusicBotMessage { TextMessage(Message), @@ -176,7 +188,7 @@ impl MusicBot { self.send_message(&format!("Playing {}", ts::underline(&metadata.title))); self.set_description(&format!("Currently playing '{}'", metadata.title)); self.player.reset().unwrap(); - self.player.set_source_url(metadata.url).unwrap(); + self.player.set_metadata(metadata).unwrap(); self.player.play().unwrap(); } @@ -211,6 +223,22 @@ impl MusicBot { &self.name } + pub fn state(&self) -> State { + *self.state.read().expect("RwLock was not poisoned") + } + + pub fn volume(&self) -> f64 { + self.player.volume() + } + + pub fn currently_playing(&self) -> Option { + self.player.currently_playing() + } + + pub fn playlist_to_vec(&self) -> Vec { + self.playlist.read().unwrap().to_vec() + } + pub fn my_channel(&self) -> ChannelId { self.teamspeak .as_ref() diff --git a/src/playlist.rs b/src/playlist.rs index 87c1c98..445f8a5 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -28,6 +28,16 @@ impl Playlist { res } + pub fn to_vec(&self) -> Vec { + let (a, b) = self.data.as_slices(); + + let mut res = a.to_vec(); + res.extend_from_slice(b); + res.reverse(); + + res + } + pub fn is_empty(&self) -> bool { self.data.is_empty() } diff --git a/src/web_server.rs b/src/web_server.rs index 1edbc50..94f043a 100644 --- a/src/web_server.rs +++ b/src/web_server.rs @@ -1,21 +1,12 @@ use std::sync::Arc; use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext}; -use actix_web::{get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{get, middleware::Logger, web, App, HttpServer, Responder}; +use askama::actix_web::TemplateIntoResponse; +use askama::Template; use crate::bot::MasterBot; - -struct GetNames; - -impl Message for GetNames { - type Result = Result, ()>; -} - -#[get("/")] -async fn index(bot: web::Data>) -> impl Responder { - let names = bot.send(GetNames).await.unwrap().unwrap(); - HttpResponse::Ok().body(&format!("Music bots connected: {}", names.join(", "))) -} +use crate::youtube_dl::AudioMetadata; pub struct WebServerArgs { pub domain: String, @@ -33,6 +24,7 @@ pub async fn start(args: WebServerArgs) -> std::io::Result<()> { .data(bot_addr.clone()) .wrap(Logger::default()) .service(index) + .service(actix_files::Files::new("/static", "static/")) }) .bind(args.bind_address)? .run() @@ -49,12 +41,49 @@ impl Actor for BotExecutor { type Context = SyncContext; } -impl Handler for BotExecutor { - type Result = Result, ()>; +impl Handler for BotExecutor { + type Result = Result, ()>; - fn handle(&mut self, _: GetNames, _: &mut Self::Context) -> Self::Result { + fn handle(&mut self, _: PlaylistRequest, _: &mut Self::Context) -> Self::Result { let bot = &self.0; - Ok(bot.names()) + Ok(bot.bot_datas()) + } +} + +struct PlaylistRequest; + +impl Message for PlaylistRequest { + type Result = Result, ()>; +} + +#[derive(Template)] +#[template(path = "index.htm")] +struct PlaylistTemplate<'a> { + bots: &'a [BotData], +} + +#[derive(Debug)] +pub struct BotData { + pub name: String, + pub state: crate::bot::State, + pub volume: f64, + pub currently_playing: Option, + pub playlist: Vec, +} + +#[get("/")] +async fn index(bot: web::Data>) -> impl Responder { + let bot_datas = match bot.send(PlaylistRequest).await.unwrap() { + Ok(data) => data, + Err(_) => { + //error!("Playlist error: {}", e); + Vec::with_capacity(0) + } + }; + + PlaylistTemplate { + bots: &bot_datas[..], } + .into_response() } diff --git a/src/youtube_dl.rs b/src/youtube_dl.rs index b62d4b3..99e50e7 100644 --- a/src/youtube_dl.rs +++ b/src/youtube_dl.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use futures::compat::Future01CompatExt; use std::process::{Command, Stdio}; use tokio_process::CommandExt; @@ -9,7 +11,33 @@ use log::debug; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct AudioMetadata { pub url: String, + pub webpage_url: String, pub title: String, + pub thumbnail: Option, + #[serde(default, deserialize_with = "duration_deserialize")] + #[serde(serialize_with = "duration_serialize")] + pub duration: Option, + #[serde(skip)] + pub added_by: String, +} + +fn duration_serialize(d: &Option, s: S) -> Result +where + S: serde::Serializer, +{ + match d { + Some(d) => s.serialize_some(&d.as_secs_f64()), + None => s.serialize_none(), + } +} + +fn duration_deserialize<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let dur: Option = Deserialize::deserialize(deserializer)?; + + Ok(dur.map(|v| Duration::from_secs_f64(v))) } pub async fn get_audio_download_url(uri: String) -> Result { -- cgit v1.2.3-70-g09d2