Compare commits

..

No commits in common. "17b426be66491d73ad68e3e4cec4ea59ae0fd0e0" and "ed7bd5efc8e51c088a9c1a160375e199d6fdaefb" have entirely different histories.

8 changed files with 59 additions and 133 deletions

View file

@ -12,11 +12,11 @@ impl<T, R> Requester<T, R> {
pub fn send(&self, data: T) -> Result<Response<R>, SendError<T>> {
let (response_tx, response_rx) = mpsc::channel();
match self.tx.send(Request {
data: Some(data),
data,
tx: response_tx,
}) {
Ok(_) => Ok(Response { rx: response_rx }),
Err(e) => Err(SendError(e.0.data.unwrap())),
Err(e) => Err(SendError(e.0.data)),
}
}
}
@ -40,14 +40,14 @@ impl<T, R> Responder<T, R> {
pub fn channel<T, R>() -> (Requester<T, R>, Responder<T, R>) {
let (requester_tx, responder_rx) = mpsc::channel();
(
return (
Requester { tx: requester_tx },
Responder { rx: responder_rx },
)
);
}
pub struct Request<T, R> {
pub data: Option<T>,
pub data: T,
tx: Sender<R>,
}

View file

@ -1,33 +1,11 @@
use std::{path::Path, process::Command};
use crate::{file::FileHandle, track::{Track, TrackInfo}};
use crate::track::TrackInfo;
#[derive(Debug, Clone, Copy)]
pub struct DownloadError;
pub fn download_from_youtube(url: &str) -> Result<Track, DownloadError> {
let output = Command::new("yt-dlp")
.args([
"--print",
"%(id)s %(duration)i",
"--no-playlist",
"--no-warnings",
"--",
url,
]).output().unwrap();
let items: Vec<String> = std::str::from_utf8(&output.stdout).unwrap().split(' ').map(|s| s.replace("\n", "")).collect();
let filename = items[0].clone() + ".mp3";
let duration: u32 = items[1].parse().unwrap();
println!("{}", filename);
println!("{}", duration);
if duration > 900 {
return Err(DownloadError);
}
pub fn download_from_youtube(url: &str) -> Result<TrackInfo, DownloadError> {
let output = Command::new("yt-dlp")
.args([
"-o",
@ -35,22 +13,22 @@ pub fn download_from_youtube(url: &str) -> Result<Track, DownloadError> {
"--extract-audio",
"--audio-format",
"mp3",
"--no-playlist",
"--no-warnings",
"--",
"--print",
"%(id)s",
"--no-simulate",
url,
])
.output()
.unwrap();
if !output.stderr.is_empty() || output.stdout.is_empty() {
println!("{}", std::str::from_utf8(output.stderr.as_slice()).unwrap());
println!("{}", std::str::from_utf8(output.stdout.as_slice()).unwrap());
return Err(DownloadError);
}
let file_handle = FileHandle::new(Path::new(filename.as_str()));
let info = TrackInfo::new(&filename);
let filename = std::str::from_utf8(output.stdout.as_slice())
.unwrap()
.replace('\n', "")
+ ".mp3";
Ok(Track::new(info, file_handle))
Ok(TrackInfo::new(&Path::new(filename.as_str())))
}

View file

@ -1,25 +0,0 @@
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct FileHandle {
path: PathBuf,
}
impl FileHandle {
pub fn new(path: &Path) -> FileHandle {
FileHandle {
path: PathBuf::from(path),
}
}
pub fn get_path(&self) -> &Path {
&self.path
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
let _ = fs::remove_file(self.path.clone());
}
}

View file

@ -1,8 +1,7 @@
pub mod channel;
pub mod download;
pub mod file;
pub mod player;
pub mod telegram;
mod track;
pub use track::Track;
pub use track::TrackInfo;

View file

@ -1,7 +1,7 @@
use music_bot::{player::MusicPlayer, telegram};
fn main() {
let player = MusicPlayer::build();
let player = MusicPlayer::new();
let mut bot = telegram::TelegramBot::build(player);
bot.telegram_main();
}

View file

@ -1,12 +1,12 @@
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
use std::{collections::VecDeque, fs::File, io::BufReader, thread, thread::JoinHandle};
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
use crate::channel::{self, Requester, Responder, TryRecvError};
use crate::track::{Track, TrackInfo};
use crate::track::TrackInfo;
#[derive(Debug)]
#[derive(Debug, Clone)]
enum WorkerRequest {
AddTrack(Track),
AddTrack(TrackInfo),
Pause,
Play,
Stop,
@ -14,7 +14,7 @@ enum WorkerRequest {
ListTracks,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
enum WorkerResponse {
TrackList(VecDeque<TrackInfo>),
None,
@ -25,20 +25,19 @@ use WorkerResponse::*;
pub struct MusicPlayer {
requester: Requester<WorkerRequest, WorkerResponse>,
worker: Option<JoinHandle<()>>,
_worker: JoinHandle<()>,
}
struct Worker {
_stream: OutputStream,
_handle: OutputStreamHandle,
sink: Sink,
queue: VecDeque<Track>,
current: Option<Track>,
queue: VecDeque<TrackInfo>,
responder: Responder<WorkerRequest, WorkerResponse>,
}
fn get_source(track: &Track) -> Decoder<BufReader<File>> {
let file = BufReader::new(File::open(track.file.get_path()).unwrap());
fn get_source(track: TrackInfo) -> Decoder<BufReader<File>> {
let file = BufReader::new(File::open(&track.path).unwrap());
Decoder::new(file).unwrap()
}
@ -60,34 +59,28 @@ impl Worker {
Stop => {
self.sink.stop();
self.queue.clear();
self.current.take();
None
}
SkipOne => {
self.sink.skip_one();
self.current.take();
None
}
ListTracks => TrackList(self.queue.iter().map(|track| track.info.clone()).collect::<VecDeque<TrackInfo>>().clone()),
ListTracks => TrackList(self.queue.clone()),
}
}
pub fn main(&mut self) {
loop {
match self.responder.try_recv() {
Ok(mut req) => {
let data = req.data.take().unwrap();
let _ = req.respond(self.match_request(data));
Ok(req) => {
let _ = req.respond(self.match_request(req.data.clone()));
}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => break,
};
if self.sink.empty() && !self.queue.is_empty() {
let next_track = self.queue.pop_front().unwrap();
self.sink.append(get_source(&next_track));
self.current = Some(next_track);
} else if self.sink.empty() {
self.current.take();
self.sink.append(get_source(next_track));
}
}
}
@ -95,26 +88,25 @@ impl Worker {
pub fn build(responder: Responder<WorkerRequest, WorkerResponse>) -> Worker {
let (_stream, _handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&_handle).unwrap();
let queue: VecDeque<Track> = VecDeque::new();
let queue: VecDeque<TrackInfo> = VecDeque::new();
Worker {
_stream,
_handle,
sink,
queue,
current: Option::None,
responder,
}
}
}
impl MusicPlayer {
pub fn build() -> MusicPlayer {
pub fn new() -> MusicPlayer {
let (requester, responder) = channel::channel();
let worker = thread::spawn(move || Worker::build(responder).main());
MusicPlayer { requester, worker: Some(worker) }
let _worker = thread::spawn(move || Worker::build(responder).main());
MusicPlayer { requester, _worker }
}
pub fn enqueue(&mut self, track: Track) {
pub fn enqueue(&mut self, track: TrackInfo) {
self.requester.send(AddTrack(track)).unwrap();
}
@ -142,10 +134,3 @@ impl MusicPlayer {
}
}
}
impl Drop for MusicPlayer {
fn drop(&mut self) {
self.stop();
self.worker.take().unwrap().join().unwrap();
}
}

View file

@ -1,7 +1,7 @@
use std::sync::{Arc, Mutex};
use teloxide::{prelude::*, utils::command::BotCommands};
use crate::{download, player::MusicPlayer};
use crate::{player::MusicPlayer, download};
#[derive(BotCommands, Clone)]
#[command(
@ -41,15 +41,17 @@ impl TelegramBot {
cmd: Command,
) -> Result<(), teloxide::RequestError> {
match cmd {
Command::Play(url) => match download::download_from_youtube(&url) {
Command::Play(url) => {
match download::download_from_youtube(&url) {
Ok(track_info) => {
player.lock().unwrap().enqueue(track_info);
bot.send_message(msg.chat.id, "Added to the queue.").await?;
}
Err(_) => {
bot.send_message(msg.chat.id, "Failed to download.").await?;
}
},
Err(_) => {
bot.send_message(msg.chat.id, format!("Failed to download.")).await?;
}
}
}
Command::Stop => {
player.lock().unwrap().stop();
bot.send_message(msg.chat.id, "Stopped.").await?;
@ -68,17 +70,15 @@ impl TelegramBot {
}
Command::List => {
let tracks = player.lock().unwrap().list_tracks();
let message: String;
if tracks.is_empty() {
message = String::from("The queue is empty.");
} else {
message = tracks
bot.send_message(
msg.chat.id,
tracks
.iter()
.map(|t| t.name.as_str())
.map(|t| t.path.to_str().unwrap())
.collect::<Vec<&str>>()
.join("\n");
}
bot.send_message(msg.chat.id, message).await?;
.join("\n"),
)
.await?;
}
};
Ok(())

View file

@ -1,24 +1,13 @@
use crate::file::FileHandle;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct TrackInfo {
pub name: String,
pub path: PathBuf,
}
impl TrackInfo {
pub fn new(name: &str) -> TrackInfo {
TrackInfo { name: String::from(name) }
}
}
#[derive(Debug)]
pub struct Track {
pub info: TrackInfo,
pub file: FileHandle,
}
impl Track {
pub fn new(info: TrackInfo, file: FileHandle) -> Track {
Track { info, file }
pub fn new(path: &Path) -> TrackInfo {
TrackInfo {
path: PathBuf::from(path),
}
}
}