aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_player.rs29
-rw-r--r--src/bot/master.rs22
-rw-r--r--src/bot/music.rs32
-rw-r--r--src/playlist.rs10
-rw-r--r--src/web_server.rs63
-rw-r--r--src/youtube_dl.rs28
6 files changed, 158 insertions, 26 deletions
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<f64>,
volume: gst::Element,
sender: Arc<RwLock<UnboundedSender<MusicBotMessage>>>,
+ currently_playing: RwLock<Option<AudioMetadata>>,
}
fn make_element(factoryname: &str, display_name: &str) -> Result<gst::Element, AudioPlayerError> {
@@ -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<AudioMetadata> {
+ 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<String> {
+ pub fn bot_datas(&self) -> Vec<crate::web_server::BotData> {
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<Seek, ()> {
}
}
-#[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<AudioMetadata> {
+ self.player.currently_playing()
+ }
+
+ pub fn playlist_to_vec(&self) -> Vec<AudioMetadata> {
+ 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<AudioMetadata> {
+ 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<Vec<String>, ()>;
-}
-
-#[get("/")]
-async fn index(bot: web::Data<Addr<BotExecutor>>) -> 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<Self>;
}
-impl Handler<GetNames> for BotExecutor {
- type Result = Result<Vec<String>, ()>;
+impl Handler<PlaylistRequest> for BotExecutor {
+ type Result = Result<Vec<BotData>, ()>;
- 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<Vec<BotData>, ()>;
+}
+
+#[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<AudioMetadata>,
+ pub playlist: Vec<AudioMetadata>,
+}
+
+#[get("/")]
+async fn index(bot: web::Data<Addr<BotExecutor>>) -> 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<String>,
+ #[serde(default, deserialize_with = "duration_deserialize")]
+ #[serde(serialize_with = "duration_serialize")]
+ pub duration: Option<Duration>,
+ #[serde(skip)]
+ pub added_by: String,
+}
+
+fn duration_serialize<S>(d: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
+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<Option<Duration>, D::Error>
+where
+ D: serde::Deserializer<'de>,
+{
+ let dur: Option<f64> = Deserialize::deserialize(deserializer)?;
+
+ Ok(dur.map(|v| Duration::from_secs_f64(v)))
}
pub async fn get_audio_download_url(uri: String) -> Result<AudioMetadata, String> {