Modularize player

This commit is contained in:
hodasemi 2018-11-14 15:29:58 +01:00
parent 684f2a1fb4
commit f2ab6f1314
18 changed files with 717 additions and 650 deletions

View file

@ -45,3 +45,20 @@ macro_rules! display_error {
};
};
}
macro_rules! check_option {
($v:expr) => {
match $v {
Some(t) => t,
None => return,
}
};
}
macro_rules! print_error {
($v:expr) => {
if let Err(msg) = $v {
println!("{}", msg);
};
};
}

View file

@ -6,52 +6,14 @@ extern crate typemap;
mod confighandler;
mod macros;
mod player;
// Import the client's bridge to the voice manager. Since voice is a standalone
// feature, it's not as ergonomic to work with as it could be. The client
// provides a clean bridged integration with voice.
use serenity::client::bridge::voice::ClientVoiceManager;
use serenity::client::{Client, Context, EventHandler};
use serenity::client::Client;
use serenity::framework::StandardFramework;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
// Import the `Context` from the client and `parking_lot`'s `Mutex`.
//
// `parking_lot` offers much more efficient implementations of `std::sync`'s
// types. You can read more about it here:
//
// <https://github.com/Amanieu/parking_lot#features>
use serenity::prelude::Mutex;
use serenity::Result as SerenityResult;
use std::sync::Arc;
use typemap::Key;
use confighandler::*;
mod player;
mod youtube;
use player::*;
/*
const fn empty_vec<T>() -> Vec<T> {
Vec::new()
}
*/
pub struct VoiceManager;
impl Key for VoiceManager {
type Value = Arc<Mutex<ClientVoiceManager>>;
}
struct Handler;
impl EventHandler for Handler {
fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
use player::prelude::*;
struct Config {
token: String,
@ -127,7 +89,6 @@ fn main() {
"stop".to_string(),
"help".to_string(),
"list".to_string(),
"help".to_string(),
"skip".to_string(),
"ip".to_string(),
],
@ -142,10 +103,3 @@ fn main() {
.start()
.map_err(|why| println!("Client ended: {:?}", why));
}
/// Checks that a message successfully sent; if not, then logs why to stdout.
fn check_msg(result: SerenityResult<Message>) {
if let Err(why) = result {
println!("Error sending message: {:?}", why);
}
}

View file

@ -1,600 +0,0 @@
use parking_lot;
use serenity;
use serenity::client::CACHE;
use serenity::model::id::{ChannelId, GuildId};
use serenity::voice::{AudioSource, Handler, LockedAudio};
use std::cell::RefCell;
use std::ops::DerefMut;
use std::process::{Command, Stdio};
use std::str::from_utf8;
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::thread::JoinHandle;
use std::time;
macro_rules! check_option {
($v:expr) => {
match $v {
Some(t) => t,
None => return,
}
};
}
pub struct Song {
pub source: Box<AudioSource>,
pub name: String,
}
pub struct MediaData {
playlist: Mutex<RefCell<Vec<Song>>>,
current_song: Mutex<RefCell<Option<LockedAudio>>>,
watcher_thread: Mutex<RefCell<Option<JoinHandle<()>>>>,
}
fn guild_id(channel_id: ChannelId) -> Option<GuildId> {
match CACHE.read().guild_channel(channel_id) {
Some(channel) => Some(channel.read().guild_id),
None => {
super::check_msg(channel_id.say("Error finding channel info"));
None
}
}
}
fn handler<'a>(
guild_id: GuildId,
manager: &'a mut parking_lot::MutexGuard<
'_,
serenity::client::bridge::voice::ClientVoiceManager,
>,
) -> Option<&'a mut Handler> {
manager.get_mut(guild_id)
}
impl MediaData {
fn song_mut(&self) -> MutexGuard<RefCell<Option<LockedAudio>>> {
self.current_song.lock().unwrap()
}
fn playlist_mut(&self) -> MutexGuard<RefCell<Vec<Song>>> {
self.playlist.lock().unwrap()
}
fn reset_thread(&self) {
let thread_mutex = self.watcher_thread.lock().unwrap();
*thread_mutex.borrow_mut() = None;
}
fn start_thread(
media: &Arc<MediaData>,
channel_id: ChannelId,
manager_lock: &Arc<
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
>,
) {
let media_clone = media.clone();
let manager_clone = manager_lock.clone();
let thread_mutex = media.watcher_thread.lock().unwrap();
let mut watcher_mut = thread_mutex.borrow_mut();
if watcher_mut.is_none() {
*watcher_mut = Some(thread::spawn(move || loop {
{
let song_lock = media_clone.song_mut();
let mut borrow = song_lock.borrow_mut();
if let Some(ref mut song) = borrow.deref_mut() {
let song_lock = song.clone();
let audio = song_lock.lock();
if audio.finished {
let playlist = media_clone.playlist_mut();
if !Self::next_song(
song,
playlist.borrow_mut().deref_mut(),
channel_id,
&manager_clone,
) {
manager_clone
.lock()
.remove(check_option!(guild_id(channel_id)));
media_clone.reset_thread();
println!("left channel");
return;
}
}
continue;
}
{
let playlist_mutex = media_clone.playlist_mut();
let mut playlist = playlist_mutex.borrow_mut();
if !playlist.is_empty() {
let mut manager = manager_clone.lock();
if let Some(handler) =
handler(check_option!(guild_id(channel_id)), &mut manager)
{
let first = playlist.remove(0);
*borrow = Some(handler.play_returning(first.source));
super::check_msg(
channel_id.say(format!("Playing song: {}", first.name)),
);
} else {
return;
}
} else {
manager_clone
.lock()
.remove(check_option!(guild_id(channel_id)));
media_clone.reset_thread();
println!("left channel");
return;
}
}
}
let two_sec = time::Duration::from_secs(2);
thread::sleep(two_sec);
}));
}
}
fn next_song(
song: &mut LockedAudio,
playlist: &mut Vec<Song>,
channel_id: ChannelId,
manager_lock: &Arc<
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
>,
) -> bool {
let mut manager = manager_lock.lock();
let guild_id = match guild_id(channel_id) {
Some(id) => id,
None => return false,
};
if let Some(handler) = handler(guild_id, &mut manager) {
if playlist.is_empty() {
return false;
} else {
let first = playlist.remove(0);
handler.stop();
*song = handler.play_returning(first.source);
super::check_msg(channel_id.say(format!("Playing song: {}", first.name)));
return true;
}
}
return false;
}
}
impl Default for MediaData {
fn default() -> MediaData {
MediaData {
playlist: Mutex::new(RefCell::new(Vec::new())),
current_song: Mutex::new(RefCell::new(None)),
watcher_thread: Mutex::new(RefCell::new(None)),
}
}
}
unsafe impl Send for MediaData {}
unsafe impl Sync for MediaData {}
pub struct Play {
media: Arc<MediaData>,
}
impl Play {
pub fn new(media_data: Arc<MediaData>) -> Play {
Play { media: media_data }
}
fn check_for_continue(song_lock: MutexGuard<RefCell<Option<LockedAudio>>>) -> bool {
match song_lock.borrow_mut().deref_mut() {
Some(song) => {
let song_clone = song.clone();
let mut audio_lock = song_clone.lock();
audio_lock.play();
true
}
None => false,
}
}
fn check_join_channel(
manager_lock: &Arc<parking_lot::Mutex<serenity::client::bridge::voice::ClientVoiceManager>>,
msg: &serenity::model::channel::Message,
) {
let guild = match msg.guild() {
Some(guild) => guild,
None => {
display_error!(msg.channel_id.say("Groups and DMs not supported"));
return;
}
};
let guild_id = guild.read().id;
let channel_id = guild
.read()
.voice_states
.get(&msg.author.id)
.and_then(|voice_state| voice_state.channel_id);
let connect_to = match channel_id {
Some(channel) => channel,
None => {
display_error!(msg.reply("Not in a voice channel"));
return;
}
};
let mut manager = manager_lock.lock();
manager.join(guild_id, connect_to);
}
}
impl serenity::framework::standard::Command for Play {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
mut ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
mut args: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let url = match args.single::<String>() {
Ok(url) => {
if !url.starts_with("http") {
super::check_msg(msg.channel_id.say("Must provide a valid URL"));
return Ok(());
}
url
}
Err(_) => {
if !Self::check_for_continue(self.media.song_mut()) {
super::check_msg(msg.channel_id.say("Must provide a URL to a video or audio"));
}
return Ok(());
}
};
let mut source = match super::youtube::youtube_dl(&url) {
Ok(source) => source,
Err(why) => {
println!("Err starting source: {:?}", why);
super::check_msg(
msg.channel_id
.say(format!("Error using youtube-dl: {}", why)),
);
return Ok(());
}
};
{
let playlist_mutex = self.media.playlist_mut();
playlist_mutex.borrow_mut().append(&mut source);
}
let mut manager_lock = ctx
.data
.lock()
.get::<super::VoiceManager>()
.cloned()
.unwrap();
Self::check_join_channel(&manager_lock, msg);
MediaData::start_thread(&self.media, msg.channel_id, &manager_lock);
Ok(())
}
}
pub struct Pause {
media: Arc<MediaData>,
}
impl Pause {
pub fn new(media_data: Arc<MediaData>) -> Pause {
Pause { media: media_data }
}
}
impl serenity::framework::standard::Command for Pause {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
_: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let song_lock = self.media.song_mut();
if let Some(song) = song_lock.borrow_mut().deref_mut() {
let song_clone = song.clone();
let mut audio_lock = song_clone.lock();
audio_lock.pause();
}
Ok(())
}
}
pub struct List {
media: Arc<MediaData>,
}
impl List {
pub fn new(media_data: Arc<MediaData>) -> List {
List { media: media_data }
}
}
impl serenity::framework::standard::Command for List {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut output = String::new();
{
let playlist_mutex = self.media.playlist_mut();
let playlist = playlist_mutex.borrow();
output += &format!(
"{} {} queued\n",
playlist.len(),
if playlist.len() == 1 { "song" } else { "songs" }
);
for (i, song) in playlist.iter().enumerate() {
output += &format!("\t{}.\t{}\n", i + 1, song.name.clone());
}
}
super::check_msg(msg.channel_id.say(output));
Ok(())
}
}
pub struct Help {
prefix: String,
commands: Vec<String>,
}
impl Help {
pub fn new(prefix: &String, commands: Vec<String>) -> Help {
Help {
prefix: prefix.clone(),
commands: commands,
}
}
}
impl serenity::framework::standard::Command for Help {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut output = String::new();
output += "Available commands:\n";
for command in &self.commands {
output += &format!("\t{}{}\n", self.prefix, command);
}
super::check_msg(msg.channel_id.say(output));
Ok(())
}
}
pub struct Stop {
media: Arc<MediaData>,
}
impl Stop {
pub fn new(media_data: Arc<MediaData>) -> Stop {
Stop { media: media_data }
}
}
impl serenity::framework::standard::Command for Stop {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
{
let playlist = self.media.playlist_mut();
playlist.borrow_mut().clear();
}
{
let song = self.media.song_mut();
*song.borrow_mut() = None;
}
{
let mut manager_lock = ctx
.data
.lock()
.get::<super::VoiceManager>()
.cloned()
.unwrap();
let mut manager = manager_lock.lock();
let guild_id = match guild_id(msg.channel_id) {
Some(guild_id) => guild_id,
None => return Ok(()),
};
let handler = match handler(guild_id, &mut manager) {
Some(handler) => handler,
None => return Ok(()),
};
println!("stopped handler");
handler.stop();
}
Ok(())
}
}
pub struct Skip {
media: Arc<MediaData>,
}
impl Skip {
pub fn new(media_data: Arc<MediaData>) -> Skip {
Skip { media: media_data }
}
}
impl serenity::framework::standard::Command for Skip {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut manager_lock = ctx
.data
.lock()
.get::<super::VoiceManager>()
.cloned()
.unwrap();
let mut manager = manager_lock.lock();
let guild_id = match guild_id(msg.channel_id) {
Some(id) => id,
None => {
println!("error getting guild id");
super::check_msg(msg.channel_id.say("error getting guild id"));
return Ok(());
}
};
if let Some(handler) = handler(guild_id, &mut manager) {
let playlist_mutex = self.media.playlist_mut();
let mut playlist = playlist_mutex.borrow_mut();
let song_mutex = self.media.song_mut();
let mut song = song_mutex.borrow_mut();
if playlist.is_empty() {
super::check_msg(
msg.channel_id
.say("playlist is empty, no next song available"),
);
return Ok(());
} else {
let first = playlist.remove(0);
handler.stop();
*song = Some(handler.play_returning(first.source));
super::check_msg(
msg.channel_id
.say(format!("Skipped current song, now playing: {}", first.name)),
);
}
}
Ok(())
}
}
pub struct IP {}
impl IP {
pub fn new() -> IP {
IP {}
}
}
impl serenity::framework::standard::Command for IP {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let args = [
"@resolver1.opendns.com",
"AAAA",
"myip.opendns.com",
"+short",
];
let out = match Command::new("dig")
.args(&args)
.stdin(Stdio::null())
.output()
{
Ok(out) => out,
Err(_) => return Ok(()),
};
if !out.status.success() {
return Ok(());
}
match from_utf8(out.stdout.as_slice()) {
Ok(string) => super::check_msg(msg.channel_id.say(string)),
Err(_) => super::check_msg(msg.channel_id.say("error getting IP string")),
};
Ok(())
}
}

View file

@ -0,0 +1,37 @@
use serenity;
pub struct Help {
prefix: String,
commands: Vec<String>,
}
impl Help {
pub fn new(prefix: &String, commands: Vec<String>) -> Help {
Help {
prefix: prefix.clone(),
commands: commands,
}
}
}
impl serenity::framework::standard::Command for Help {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut output = String::new();
output += "Available commands:\n";
for command in &self.commands {
output += &format!("\t{}{}\n", self.prefix, command);
}
print_error!(msg.channel_id.say(output));
Ok(())
}
}

49
src/player/commands/ip.rs Normal file
View file

@ -0,0 +1,49 @@
use serenity;
use std::process::{Command, Stdio};
use std::str::from_utf8;
pub struct IP {}
impl IP {
pub fn new() -> IP {
IP {}
}
}
impl serenity::framework::standard::Command for IP {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let args = [
"@resolver1.opendns.com",
"AAAA",
"myip.opendns.com",
"+short",
];
let out = match Command::new("dig")
.args(&args)
.stdin(Stdio::null())
.output()
{
Ok(out) => out,
Err(_) => return Ok(()),
};
if !out.status.success() {
return Ok(());
}
match from_utf8(out.stdout.as_slice()) {
Ok(string) => print_error!(msg.channel_id.say(string)),
Err(_) => print_error!(msg.channel_id.say("error getting IP string")),
};
Ok(())
}
}

View file

@ -0,0 +1,46 @@
use serenity;
use std::sync::Arc;
use super::super::prelude::*;
pub struct List {
media: Arc<MediaData>,
}
impl List {
pub fn new(media_data: Arc<MediaData>) -> List {
List { media: media_data }
}
}
impl serenity::framework::standard::Command for List {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut output = String::new();
{
let playlist_mutex = self.media.playlist_mut();
let playlist = playlist_mutex.borrow();
output += &format!(
"{} {} queued\n",
playlist.len(),
if playlist.len() == 1 { "song" } else { "songs" }
);
for (i, song) in playlist.iter().enumerate() {
output += &format!("\t{}.\t{}\n", i + 1, song.name.clone());
}
}
print_error!(msg.channel_id.say(output));
Ok(())
}
}

View file

@ -0,0 +1,7 @@
pub mod help;
pub mod ip;
pub mod list;
pub mod pause;
pub mod play;
pub mod skip;
pub mod stop;

View file

@ -0,0 +1,37 @@
use serenity;
use std::ops::DerefMut;
use std::sync::Arc;
use super::super::prelude::*;
pub struct Pause {
media: Arc<MediaData>,
}
impl Pause {
pub fn new(media_data: Arc<MediaData>) -> Pause {
Pause { media: media_data }
}
}
impl serenity::framework::standard::Command for Pause {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
_: &mut serenity::client::Context,
_: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let song_lock = self.media.song_mut();
if let Some(song) = song_lock.borrow_mut().deref_mut() {
let song_clone = song.clone();
let mut audio_lock = song_clone.lock();
audio_lock.pause();
}
Ok(())
}
}

144
src/player/commands/play.rs Normal file
View file

@ -0,0 +1,144 @@
use parking_lot;
use serenity;
use serenity::voice::LockedAudio;
use std::cell::RefCell;
use std::ops::DerefMut;
use std::sync::{Arc, MutexGuard};
use super::super::prelude::*;
pub struct Play {
media: Arc<MediaData>,
}
impl Play {
pub fn new(media_data: Arc<MediaData>) -> Play {
Play { media: media_data }
}
fn check_for_continue(song_lock: MutexGuard<RefCell<Option<LockedAudio>>>) -> bool {
match song_lock.borrow_mut().deref_mut() {
Some(song) => {
let song_clone = song.clone();
let mut audio_lock = song_clone.lock();
audio_lock.play();
true
}
None => false,
}
}
fn check_join_channel(
manager_lock: &Arc<parking_lot::Mutex<serenity::client::bridge::voice::ClientVoiceManager>>,
msg: &serenity::model::channel::Message,
) {
let guild = match msg.guild() {
Some(guild) => guild,
None => {
display_error!(msg.channel_id.say("Groups and DMs not supported"));
return;
}
};
let guild_id = guild.read().id;
let channel_id = guild
.read()
.voice_states
.get(&msg.author.id)
.and_then(|voice_state| voice_state.channel_id);
let connect_to = match channel_id {
Some(channel) => channel,
None => {
display_error!(msg.reply("Not in a voice channel"));
return;
}
};
let mut manager = manager_lock.lock();
manager.join(guild_id, connect_to);
}
fn handle_http_request(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
url: &String,
) {
let mut source = match youtube_dl(&url) {
Ok(source) => source,
Err(why) => {
println!("Err starting source: {:?}", why);
print_error!(
msg.channel_id
.say(format!("Error using youtube-dl: {}", why))
);
return;
}
};
{
let playlist_mutex = self.media.playlist_mut();
playlist_mutex.borrow_mut().append(&mut source);
}
let manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
Self::check_join_channel(&manager_lock, msg);
MediaData::start_thread(&self.media, msg.channel_id, &manager_lock);
}
fn handle_local_request(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
) {
print_error!(self.media.reset(ctx, msg));
// TODO: read local file directory and play songs in arbitrary order
}
}
impl serenity::framework::standard::Command for Play {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
mut ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
mut args: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
if args.len() == 0 {
if !Self::check_for_continue(self.media.song_mut()) {
print_error!(msg.channel_id.say("Must provide a URL to a video or audio"));
}
} else if args.len() == 1 {
let arg = match args.single::<String>() {
Ok(arg) => arg,
// can't happen, since we tested for length == 1
Err(_) => return Ok(()),
};
if arg == "--local" {
self.handle_local_request(ctx, msg);
} else if arg.starts_with("http") {
self.handle_http_request(ctx, msg, &arg);
} else {
print_error!(msg.channel_id.say("Unsupported argument list"));
}
} else {
print_error!(msg.channel_id.say("Unsupported argument list"));
}
Ok(())
}
}

View file

@ -0,0 +1,68 @@
use serenity;
use std::sync::Arc;
use super::super::prelude::*;
pub struct Skip {
media: Arc<MediaData>,
}
impl Skip {
pub fn new(media_data: Arc<MediaData>) -> Skip {
Skip { media: media_data }
}
}
impl serenity::framework::standard::Command for Skip {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
let mut manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
let mut manager = manager_lock.lock();
let guild_id = match guild_id(msg.channel_id) {
Some(id) => id,
None => {
println!("error getting guild id");
print_error!(msg.channel_id.say("error getting guild id"));
return Ok(());
}
};
if let Some(handler) = handler(guild_id, &mut manager) {
let playlist_mutex = self.media.playlist_mut();
let mut playlist = playlist_mutex.borrow_mut();
let song_mutex = self.media.song_mut();
let mut song = song_mutex.borrow_mut();
if playlist.is_empty() {
print_error!(
msg.channel_id
.say("playlist is empty, no next song available")
);
return Ok(());
} else {
let first = playlist.remove(0);
handler.stop();
*song = Some(handler.play_returning(first.source));
print_error!(
msg.channel_id
.say(format!("Skipped current song, now playing: {}", first.name))
);
}
}
Ok(())
}
}

View file

@ -0,0 +1,29 @@
use serenity;
use std::sync::Arc;
use super::super::prelude::*;
pub struct Stop {
media: Arc<MediaData>,
}
impl Stop {
pub fn new(media_data: Arc<MediaData>) -> Stop {
Stop { media: media_data }
}
}
impl serenity::framework::standard::Command for Stop {
#[allow(unreachable_code, unused_mut)]
fn execute(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
_: serenity::framework::standard::Args,
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
print_error!(self.media.reset(ctx, msg));
Ok(())
}
}

View file

@ -0,0 +1,10 @@
use serenity::client::{Context, EventHandler};
use serenity::model::gateway::Ready;
pub struct Handler;
impl EventHandler for Handler {
fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}

208
src/player/mediadata.rs Normal file
View file

@ -0,0 +1,208 @@
use serenity;
use serenity::model::id::ChannelId;
use serenity::voice::{AudioSource, LockedAudio};
use std::cell::RefCell;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::thread::JoinHandle;
use std::time;
use super::prelude::*;
pub struct Song {
pub source: Box<AudioSource>,
pub name: String,
}
pub struct MediaData {
playlist: Mutex<RefCell<Vec<Song>>>,
current_song: Mutex<RefCell<Option<LockedAudio>>>,
watcher_thread: Mutex<RefCell<Option<JoinHandle<()>>>>,
}
impl MediaData {
pub fn reset(
&self,
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
) -> Result<(), String> {
{
let playlist = self.playlist_mut();
playlist.borrow_mut().clear();
}
{
let song = self.song_mut();
*song.borrow_mut() = None;
}
{
let manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
let mut manager = manager_lock.lock();
let guild_id = match guild_id(msg.channel_id) {
Some(guild_id) => guild_id,
None => return Ok(()),
};
let handler = match handler(guild_id, &mut manager) {
Some(handler) => handler,
None => return Ok(()),
};
println!("stopped handler");
handler.stop();
}
Ok(())
}
pub fn song_mut(&self) -> MutexGuard<RefCell<Option<LockedAudio>>> {
self.current_song.lock().unwrap()
}
pub fn playlist_mut(&self) -> MutexGuard<RefCell<Vec<Song>>> {
self.playlist.lock().unwrap()
}
pub fn reset_thread(&self) {
let thread_mutex = self.watcher_thread.lock().unwrap();
*thread_mutex.borrow_mut() = None;
}
pub fn start_thread(
media: &Arc<MediaData>,
channel_id: ChannelId,
manager_lock: &Arc<
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
>,
) {
let media_clone = media.clone();
let manager_clone = manager_lock.clone();
let thread_mutex = media.watcher_thread.lock().unwrap();
let mut watcher_mut = thread_mutex.borrow_mut();
if watcher_mut.is_none() {
*watcher_mut = Some(thread::spawn(move || loop {
{
let song_lock = media_clone.song_mut();
let mut borrow = song_lock.borrow_mut();
if let Some(ref mut song) = borrow.deref_mut() {
let song_lock = song.clone();
let audio = song_lock.lock();
if audio.finished {
let playlist = media_clone.playlist_mut();
if !Self::next_song(
song,
playlist.borrow_mut().deref_mut(),
channel_id,
&manager_clone,
) {
manager_clone
.lock()
.remove(check_option!(guild_id(channel_id)));
media_clone.reset_thread();
println!("left channel");
return;
}
}
continue;
}
{
let playlist_mutex = media_clone.playlist_mut();
let mut playlist = playlist_mutex.borrow_mut();
if !playlist.is_empty() {
let mut manager = manager_clone.lock();
if let Some(handler) =
handler(check_option!(guild_id(channel_id)), &mut manager)
{
let first = playlist.remove(0);
*borrow = Some(handler.play_returning(first.source));
print_error!(
channel_id.say(format!("Playing song: {}", first.name))
);
} else {
return;
}
} else {
manager_clone
.lock()
.remove(check_option!(guild_id(channel_id)));
media_clone.reset_thread();
println!("left channel");
return;
}
}
}
let two_sec = time::Duration::from_secs(2);
thread::sleep(two_sec);
}));
}
}
fn next_song(
song: &mut LockedAudio,
playlist: &mut Vec<Song>,
channel_id: ChannelId,
manager_lock: &Arc<
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
>,
) -> bool {
let mut manager = manager_lock.lock();
let guild_id = match guild_id(channel_id) {
Some(id) => id,
None => return false,
};
if let Some(handler) = handler(guild_id, &mut manager) {
if playlist.is_empty() {
return false;
} else {
let first = playlist.remove(0);
handler.stop();
*song = handler.play_returning(first.source);
print_error!(channel_id.say(format!("Playing song: {}", first.name)));
return true;
}
}
return false;
}
}
impl Default for MediaData {
fn default() -> MediaData {
MediaData {
playlist: Mutex::new(RefCell::new(Vec::new())),
current_song: Mutex::new(RefCell::new(None)),
watcher_thread: Mutex::new(RefCell::new(None)),
}
}
}
unsafe impl Send for MediaData {}
unsafe impl Sync for MediaData {}

9
src/player/mod.rs Normal file
View file

@ -0,0 +1,9 @@
pub mod eventhandler;
pub mod mediadata;
pub mod player;
pub mod voicemanager;
mod youtube;
pub mod commands;
pub mod prelude;

26
src/player/player.rs Normal file
View file

@ -0,0 +1,26 @@
use parking_lot;
use serenity;
use serenity::client::CACHE;
use serenity::model::id::{ChannelId, GuildId};
use serenity::voice::Handler;
pub fn guild_id(channel_id: ChannelId) -> Option<GuildId> {
match CACHE.read().guild_channel(channel_id) {
Some(channel) => Some(channel.read().guild_id),
None => {
print_error!(channel_id.say("Error finding channel info"));
None
}
}
}
pub fn handler<'a>(
guild_id: GuildId,
manager: &'a mut parking_lot::MutexGuard<
'_,
serenity::client::bridge::voice::ClientVoiceManager,
>,
) -> Option<&'a mut Handler> {
manager.get_mut(guild_id)
}

16
src/player/prelude.rs Normal file
View file

@ -0,0 +1,16 @@
pub use super::mediadata::{MediaData, Song};
pub use super::commands::help::Help;
pub use super::commands::ip::IP;
pub use super::commands::list::List;
pub use super::commands::pause::Pause;
pub use super::commands::play::Play;
pub use super::commands::skip::Skip;
pub use super::commands::stop::Stop;
pub use super::eventhandler::Handler;
pub use super::voicemanager::VoiceManager;
pub use super::youtube::youtube_dl;
pub use super::player::{guild_id, handler};

View file

@ -0,0 +1,10 @@
use serenity::client::bridge::voice::ClientVoiceManager;
use serenity::prelude::Mutex;
use std::sync::Arc;
use typemap::Key;
pub struct VoiceManager;
impl Key for VoiceManager {
type Value = Arc<Mutex<ClientVoiceManager>>;
}

View file

@ -2,7 +2,7 @@ use serenity::voice::ffmpeg;
use std::process::{Command, Output, Stdio};
use std::str::from_utf8;
use player::Song;
use super::prelude::*;
const FIRST_LOAD_PREFIX: &str = "[download] Destination:";
const RELOAD_SUFFIX: &str = " has already been downloaded";