init
This commit is contained in:
commit
b0e8b24706
8 changed files with 2697 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
64
.vscode/launch.json
vendored
Normal file
64
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
// Используйте IntelliSense, чтобы узнать о возможных атрибутах.
|
||||||
|
// Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов.
|
||||||
|
// Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in library 'music_bot'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--lib",
|
||||||
|
"--package=music_bot"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "music_bot",
|
||||||
|
"kind": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug executable 'music_bot'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"--bin=music_bot",
|
||||||
|
"--package=music_bot"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "music_bot",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'music_bot'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--bin=music_bot",
|
||||||
|
"--package=music_bot"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "music_bot",
|
||||||
|
"kind": "bin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"./Cargo.toml",
|
||||||
|
"./Cargo.toml",
|
||||||
|
"./Cargo.toml",
|
||||||
|
"./Cargo.toml",
|
||||||
|
"./Cargo.toml",
|
||||||
|
"./Cargo.toml"
|
||||||
|
]
|
||||||
|
}
|
||||||
2335
Cargo.lock
generated
Normal file
2335
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "music_bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rodio = "0.17.3"
|
||||||
|
rustube = { version = "0.6.0", features = ["blocking"] }
|
||||||
94
src/channel.rs
Normal file
94
src/channel.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
|
||||||
|
pub struct Requester<T, R> {
|
||||||
|
tx: Sender<Request<T, R>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Responder<T, R> {
|
||||||
|
rx: Receiver<Request<T, R>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
tx: response_tx,
|
||||||
|
}) {
|
||||||
|
Ok(_) => Ok(Response { rx: response_rx }),
|
||||||
|
Err(e) => Err(SendError(e.0.data)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, R> Responder<T, R> {
|
||||||
|
pub fn try_recv(&self) -> Result<Request<T, R>, TryRecvError> {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => Err(TryRecvError::Disconnected {}),
|
||||||
|
Err(mpsc::TryRecvError::Empty) => Err(TryRecvError::Empty {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv(&self) -> Result<Request<T, R>, RecvError> {
|
||||||
|
match self.rx.recv() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(_) => Err(RecvError {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: T,
|
||||||
|
tx: Sender<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Response<R> {
|
||||||
|
rx: Receiver<R>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, R> Request<T, R> {
|
||||||
|
pub fn respond(&self, data: R) -> Result<(), SendError<R>> {
|
||||||
|
match self.tx.send(data) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(SendError(e.0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> Response<R> {
|
||||||
|
pub fn recv(&self) -> Result<R, RecvError> {
|
||||||
|
match self.rx.recv() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(_) => Err(RecvError {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_recv(&self) -> Result<R, TryRecvError> {
|
||||||
|
match self.rx.try_recv() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(mpsc::TryRecvError::Disconnected) => Err(TryRecvError::Disconnected {}),
|
||||||
|
Err(mpsc::TryRecvError::Empty) => Err(TryRecvError::Empty {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct RecvError {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum TryRecvError {
|
||||||
|
Empty,
|
||||||
|
Disconnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SendError<T>(pub T);
|
||||||
156
src/lib.rs
Normal file
156
src/lib.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
pub mod channel;
|
||||||
|
|
||||||
|
use channel::{Requester, Responder, TryRecvError};
|
||||||
|
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
fs::File,
|
||||||
|
io::BufReader,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
thread,
|
||||||
|
thread::JoinHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TrackInfo {
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackInfo {
|
||||||
|
pub fn new(path: &Path) -> TrackInfo {
|
||||||
|
TrackInfo {
|
||||||
|
path: PathBuf::from(path),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum WorkerRequest {
|
||||||
|
AddTrack(TrackInfo),
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
Stop,
|
||||||
|
SkipOne,
|
||||||
|
ListTracks,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum WorkerResponse {
|
||||||
|
TrackList(VecDeque<TrackInfo>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
use WorkerRequest::*;
|
||||||
|
use WorkerResponse::*;
|
||||||
|
|
||||||
|
pub struct MusicPlayer {
|
||||||
|
requester: Requester<WorkerRequest, WorkerResponse>,
|
||||||
|
_worker: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Worker {
|
||||||
|
_stream: OutputStream,
|
||||||
|
_handle: OutputStreamHandle,
|
||||||
|
sink: Sink,
|
||||||
|
queue: VecDeque<TrackInfo>,
|
||||||
|
responder: Responder<WorkerRequest, WorkerResponse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_source(track: TrackInfo) -> Decoder<BufReader<File>> {
|
||||||
|
let file = BufReader::new(File::open(track.path).unwrap());
|
||||||
|
Decoder::new(file).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Worker {
|
||||||
|
fn match_request(&mut self, req: WorkerRequest) -> WorkerResponse {
|
||||||
|
match req {
|
||||||
|
AddTrack(track) => {
|
||||||
|
self.queue.push_back(track);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Pause => {
|
||||||
|
self.sink.pause();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Play => {
|
||||||
|
self.sink.play();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Stop => {
|
||||||
|
self.sink.stop();
|
||||||
|
self.queue.clear();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
SkipOne => {
|
||||||
|
self.sink.skip_one();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
ListTracks => TrackList(self.queue.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(&mut self) {
|
||||||
|
loop {
|
||||||
|
match self.responder.try_recv() {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<TrackInfo> = VecDeque::new();
|
||||||
|
Worker {
|
||||||
|
_stream,
|
||||||
|
_handle,
|
||||||
|
sink,
|
||||||
|
queue,
|
||||||
|
responder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MusicPlayer {
|
||||||
|
pub fn new() -> MusicPlayer {
|
||||||
|
let (requester, responder) = channel::channel();
|
||||||
|
let _worker = thread::spawn(move || Worker::build(responder).main());
|
||||||
|
MusicPlayer { requester, _worker }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enqueue(&mut self, track: TrackInfo) {
|
||||||
|
self.requester.send(AddTrack(track)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip_one(&mut self) {
|
||||||
|
self.requester.send(SkipOne).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
self.requester.send(Pause).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&mut self) {
|
||||||
|
self.requester.send(Play).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.requester.send(Stop).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_tracks(&self) -> VecDeque<TrackInfo> {
|
||||||
|
let response = self.requester.send(ListTracks).unwrap();
|
||||||
|
match response.recv().unwrap() {
|
||||||
|
TrackList(tracks) => tracks,
|
||||||
|
r => panic!("On .list_tracks should get TrackList, not {:#?}.", r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main.rs
Normal file
27
src/main.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use music_bot::{MusicPlayer, TrackInfo};
|
||||||
|
use std::{path::Path, thread, time::Duration};
|
||||||
|
use rustube::{blocking::Video, url::Url};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// let url = "https://www.youtube.com/watch?v=UnIhRpIT7nc";
|
||||||
|
// let url = Url::parse(url).unwrap();
|
||||||
|
// let video = Video::from_url(&url).unwrap();
|
||||||
|
|
||||||
|
// let stream = video.best_audio().unwrap();
|
||||||
|
// let path = stream.blocking_download().unwrap();
|
||||||
|
|
||||||
|
// let path = path.to_str().unwrap();
|
||||||
|
|
||||||
|
// println!("{path}")
|
||||||
|
|
||||||
|
let mut player = MusicPlayer::new();
|
||||||
|
player.enqueue(TrackInfo::new(&Path::new("music.mp3")));
|
||||||
|
player.enqueue(TrackInfo::new(&Path::new("music.mp3")));
|
||||||
|
// MusicPlayer::play(TrackInfo::new(&Path::new("music.mp3")));
|
||||||
|
for track in player.list_tracks() {
|
||||||
|
println!("{}", track.path.into_os_string().into_string().unwrap())
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
player.skip_one();
|
||||||
|
thread::sleep(Duration::from_secs(4 * 60 + 13))
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue