Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /// main.rs
- #[derive(Debug)]
- enum AppEvent {
- Key(crossterm::event::KeyEvent),
- SongFinished,
- Quit,
- }
- pub struct StatefulList<T> {
- pub state: ListState,
- pub items: Vec<T>,
- pub current: usize,
- }
- impl<T> StatefulList<T> {
- pub fn with_items(items: Vec<T>) -> StatefulList<T> {
- StatefulList {
- state: ListState::default(),
- items,
- current: 0,
- }
- }
- pub fn select_item(&mut self) {
- self.state.select(Some(self.current));
- }
- }
- pub struct App {
- exit: bool,
- controls: Controls,
- items: StatefulList<String>,
- autoplay: bool,
- event_sender: mpsc::Sender<AppEvent>,
- event_receiver: mpsc::Receiver<AppEvent>,
- last_song_finished: Option<Instant>,
- }
- impl App {
- pub fn new(controls: Controls) -> App {
- let songlist = controls.songlist.clone();
- let (event_sender, event_receiver) = mpsc::channel();
- let mut app = App {
- exit: false,
- controls,
- items: StatefulList::with_items(songlist),
- autoplay: true,
- event_sender,
- event_receiver,
- last_song_finished: None,
- };
- app.start_background_song_checker();
- app
- }
- fn start_background_song_checker(&self) {
- let event_sender = self.event_sender.clone();
- // Create a weak reference to the sink
- let sink_ref = Arc::new(Mutex::new(Some(self.controls.sink.clone())));
- let weak_sink = Arc::downgrade(&sink_ref);
- thread::spawn(move || {
- loop {
- println!("checking sink");
- // Use weak reference to check if sink is still valid
- if let Some(sink_guard) = weak_sink.upgrade() {
- let sink = sink_guard.lock().unwrap();
- // Check if the sink is empty
- if let Some(sink) = sink.as_ref() {
- if sink.lock().unwrap().empty() {
- println!("empty sink");
- // Send song finished event
- if let Err(e) = event_sender.send(AppEvent::SongFinished) {
- eprintln!("Error sending song finished event: {}", e);
- break;
- }
- }
- }
- } else {
- // Sink reference is no longer valid, exit thread
- break;
- }
- // Sleep to prevent constant checking
- thread::sleep(Duration::from_millis(500));
- }
- });
- }
- pub fn run(&mut self, terminal: &mut tui::Tui) -> Result<()> {
- // Input event handler thread
- let event_sender = self.event_sender.clone();
- thread::spawn(move || loop {
- match event::read() {
- Ok(Event::Key(key_event)) if key_event.kind == KeyEventKind::Press => {
- if let Err(e) = event_sender.send(AppEvent::Key(key_event)) {
- eprintln!("Error sending key event: {}", e);
- break;
- }
- }
- Err(e) => {
- eprintln!("Error reading event: {}", e);
- break;
- }
- _ => {}
- }
- });
- // Main event loop
- while !self.exit {
- terminal.draw(|frame| ui(frame, self))?;
- // Receive and process events
- match self.event_receiver.recv() {
- Ok(AppEvent::Key(key_event)) => {
- self.handle_key_event(key_event);
- }
- Ok(AppEvent::SongFinished) => {
- // Add a delay between song switches to prevent rapid switching
- let now = Instant::now();
- let can_switch = self.last_song_finished.map_or(true, |last| {
- now.duration_since(last) > Duration::from_secs(1)
- });
- if self.autoplay && can_switch {
- self.next();
- self.items.select_item();
- self.last_song_finished = Some(now);
- }
- }
- Ok(AppEvent::Quit) => {
- self.exit = true;
- }
- Err(_) => {
- // Channel closed, exit loop
- break;
- }
- }
- }
- Ok(())
- }
- fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {
- match key_event.code {
- KeyCode::Char('q') => self.exit = true,
- KeyCode::Char('p') => self.play_pause(),
- KeyCode::Char('n') => {
- self.next();
- self.items.select_item()
- }
- KeyCode::Char('m') => {
- self.previous();
- self.items.select_item()
- }
- KeyCode::Char('s') => {
- self.previous();
- self.items.select_item()
- }
- KeyCode::Char('r') => {
- self.repeat_infintie();
- }
- KeyCode::Char('a') => {
- // Toggle autoplay
- self.autoplay = !self.autoplay;
- }
- KeyCode::Right => {
- self.skip_duration();
- }
- _ => {}
- }
- }
- fn exit(&mut self) {
- self.exit = true;
- }
- fn play_pause(&self) {
- if self.controls.sink.lock().unwrap().is_paused() {
- self.controls.sink.lock().unwrap().play();
- } else {
- self.controls.sink.lock().unwrap().pause();
- }
- }
- fn next(&mut self) {
- self.items.current += 1;
- if self.items.current >= self.controls.playlistz.len() {
- self.items.current = 0;
- }
- self.start();
- }
- fn previous(&mut self) {
- if self.items.current == 0 {
- self.items.current = self.controls.playlistz.len() - 1;
- } else {
- self.items.current -= 1;
- }
- self.start();
- }
- fn repeat_infintie(&mut self) {
- let path = self.controls.playlistz[self.items.current].clone();
- let source = Controls::get_source(path).repeat_infinite();
- self.controls.sink.lock().unwrap().clear();
- self.controls.sink.lock().unwrap().append(source);
- self.controls.sink.lock().unwrap().play();
- }
- fn skip_duration(&mut self) {
- // let path = self.controls.playlistz[self.items.current].clone();
- // let source = Controls::get_source(path).skip_duration(Duration::new(5, 0));
- // self.controls.sink.lock().unwrap().clear();
- // self.controls.sink.lock().unwrap().append(source);
- // self.controls.sink.lock().unwrap().play();
- }
- fn start(&mut self) {
- let path = self.controls.playlistz[self.items.current].clone();
- let source = Controls::get_source(path);
- self.controls.sink.lock().unwrap().clear();
- self.controls.sink.lock().unwrap().append(source);
- self.controls.sink.lock().unwrap().play();
- }
- }
- fn ui(f: &mut Frame, app: &mut App) {
- let chunks = Layout::default()
- .direction(Direction::Horizontal)
- .constraints([Constraint::Percentage(25), Constraint::Percentage(75)])
- .split(f.size());
- let left_block = Block::default()
- .borders(Borders::RIGHT)
- .style(Style::default());
- let title_block = Block::default()
- .borders(Borders::NONE)
- .style(Style::default());
- let minichunks = Layout::default()
- .direction(Direction::Vertical)
- .constraints([Constraint::Percentage(10), Constraint::Percentage(90)])
- .split(chunks[0]);
- let title = Paragraph::new("RUMI: A rusty music player".bold().red())
- .block(title_block)
- .alignment(Alignment::Center);
- let image = StatefulImage::new(None);
- f.render_widget(left_block, chunks[0]);
- f.render_widget(title, minichunks[0]);
- let info_chunk = Layout::default()
- .direction(Direction::Vertical)
- .constraints([Constraint::Percentage(75), Constraint::Percentage(25)])
- .split(minichunks[1]);
- let image_chunk = Layout::default()
- .direction(Direction::Horizontal)
- .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
- .split(info_chunk[0]);
- let author_block = Block::default()
- .borders(Borders::NONE)
- .style(Style::default());
- let items: Vec<ListItem> = app
- .items
- .items
- .clone()
- .into_iter()
- .map(|i| ListItem::new(i).style(Style::default().fg(Color::Black).bg(Color::White)))
- .collect();
- let items_list = List::new(items)
- .block(Block::default().borders(Borders::TOP).title("PlayList"))
- .highlight_style(
- Style::default()
- .bg(Color::LightGreen)
- .add_modifier(Modifier::BOLD),
- )
- .highlight_symbol(">> ");
- f.render_stateful_widget(items_list, chunks[1], &mut app.items.state)
- }
- fn main() -> Result<()> {
- errors::install_hooks()?;
- let controls = Controls::new();
- // let app_list = list::App_list::new(controls.songlist.clone());
- let mut terminal = tui::init()?;
- let app_result = App::new(controls).run(&mut terminal);
- tui::restore()?;
- app_result
- }
- // audio.rs
- use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
- use std::fs::{self, File};
- use std::io::BufReader;
- use std::path::PathBuf;
- use std::sync::{Arc, Mutex};
- pub struct Controls {
- pub sink: Arc<Mutex<Sink>>,
- output_stream: OutputStream,
- stream_handle: OutputStreamHandle,
- pub playlistz: Vec<PathBuf>,
- pub songlist: Vec<String>,
- }
- impl Controls {
- pub fn new() -> Self {
- let (_stream, stream_handle) = OutputStream::try_default().unwrap();
- let sink = Arc::new(Mutex::new(Sink::try_new(&stream_handle).unwrap()));
- let mut playlistz: Vec<PathBuf> = Vec::new();
- let mut songlist: Vec<String> = Vec::new();
- let folder_path = PathBuf::from("music");
- for entry in fs::read_dir(folder_path).unwrap() {
- let path = entry.unwrap().path();
- if path.is_file() {
- if let Some(filename) = path.file_name() {
- if let Some(filename_str) = filename.to_str() {
- songlist.push(filename_str.to_string());
- }
- playlistz.push(path);
- }
- }
- }
- Controls {
- sink,
- output_stream: _stream,
- stream_handle,
- playlistz,
- songlist,
- }
- }
- pub fn get_source(path: PathBuf) -> Decoder<BufReader<File>> {
- let file = std::fs::File::open(&path).unwrap();
- let source: Decoder<BufReader<File>> = Decoder::new(BufReader::new(file)).unwrap();
- source
- }
- }
- impl Clone for Controls {
- fn clone(&self) -> Self {
- let (_stream, stream_handle) = OutputStream::try_default().unwrap();
- let sink = Arc::new(Mutex::new(Sink::try_new(&stream_handle).unwrap()));
- Controls {
- sink,
- output_stream: _stream,
- stream_handle,
- playlistz: self.playlistz.clone(),
- songlist: self.songlist.clone(),
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment