aboutsummaryrefslogtreecommitdiffstats
path: root/src/daemon.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon.rs')
-rw-r--r--src/daemon.rs126
1 files changed, 113 insertions, 13 deletions
diff --git a/src/daemon.rs b/src/daemon.rs
index 5ad6654..0590f71 100644
--- a/src/daemon.rs
+++ b/src/daemon.rs
@@ -15,8 +15,8 @@ use std::os::unix::net::{UnixListener, UnixStream};
15use std::path::{Path, PathBuf}; 15use std::path::{Path, PathBuf};
16use std::sync::atomic::{AtomicBool, Ordering}; 16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::{Arc, Mutex, RwLock}; 17use std::sync::{Arc, Mutex, RwLock};
18use std::thread;
19use std::time::{Duration, Instant, SystemTime}; 18use std::time::{Duration, Instant, SystemTime};
19use std::{fs, thread};
20 20
21pub struct ScrobbleState { 21pub struct ScrobbleState {
22 pub track: Option<Track>, 22 pub track: Option<Track>,
@@ -419,9 +419,19 @@ fn send_command_raw(cmd: String) -> Result<(), Box<dyn std::error::Error>> {
419 let mut stream = UnixStream::connect(socket_path)?; 419 let mut stream = UnixStream::connect(socket_path)?;
420 stream.write_all(cmd.as_bytes())?; 420 stream.write_all(cmd.as_bytes())?;
421 421
422 let mut response = String::new(); 422 let mut buf = [0u8; 4096];
423 stream.read_to_string(&mut response)?; 423 loop {
424 print!("{}", response); 424 match stream.read(&mut buf) {
425 Ok(0) => break,
426 Ok(n) => {
427 if let Ok(s) = std::str::from_utf8(&buf[..n]) {
428 print!("{}", s);
429 }
430 }
431 Err(_) => break,
432 }
433 }
434 let _ = std::io::stdout().flush();
425 435
426 Ok(()) 436 Ok(())
427} 437}
@@ -437,15 +447,104 @@ fn handle_client(
437 let mut buffer = [0; 1024]; 447 let mut buffer = [0; 1024];
438 if let Ok(size) = stream.read(&mut buffer) { 448 if let Ok(size) = stream.read(&mut buffer) {
439 let cmd = String::from_utf8_lossy(&buffer[..size]); 449 let cmd = String::from_utf8_lossy(&buffer[..size]);
440 let response = process_command( 450 let trimmed = cmd.trim();
441 cmd.trim(), 451 if trimmed == "lyrics" {
442 &audio, 452 handle_lyrics_stream(&mut stream, &audio);
443 &shutdown_flag, 453 } else {
444 &queue, 454 let response = process_command(
445 &scrobbler, 455 trimmed,
446 &scrobble_state, 456 &audio,
447 ); 457 &shutdown_flag,
448 let _ = stream.write_all(response.as_bytes()); 458 &queue,
459 &scrobbler,
460 &scrobble_state,
461 );
462 let _ = stream.write_all(response.as_bytes());
463 }
464 }
465}
466
467struct LrcLine {
468 time: f64,
469 text: String,
470}
471
472fn parse_lrc(content: &str) -> Vec<LrcLine> {
473 let mut lines: Vec<LrcLine> = Vec::new();
474 for line in content.lines() {
475 let line = line.trim();
476 if line.is_empty() {
477 continue;
478 }
479 if let Some(rest) = line.strip_prefix('[')
480 && let Some(idx) = rest.find(']')
481 {
482 let ts = &rest[..idx];
483 let text = rest[idx + 1..].trim().to_string();
484 if let Some(secs) = parse_lrc_timestamp(ts) {
485 lines.push(LrcLine { time: secs, text });
486 }
487 }
488 }
489 lines.sort_by(|a, b| {
490 a.time
491 .partial_cmp(&b.time)
492 .unwrap_or(std::cmp::Ordering::Equal)
493 });
494 lines
495}
496
497fn parse_lrc_timestamp(ts: &str) -> Option<f64> {
498 let ts = ts.trim();
499 if let Some((mm, rest)) = ts.split_once(':') {
500 let mm: f64 = mm.parse().ok()?;
501 let ss: f64 = rest.parse().ok()?;
502 Some(mm * 60.0 + ss)
503 } else {
504 None
505 }
506}
507
508fn handle_lyrics_stream(stream: &mut UnixStream, audio: &Arc<AudioEngine>) {
509 let track_path = match audio.current_track() {
510 Some(p) => p,
511 None => return,
512 };
513 let mut lrc_path = PathBuf::from(&track_path);
514 if !lrc_path.set_extension("lrc") || !lrc_path.exists() {
515 return;
516 }
517 let content = match fs::read_to_string(&lrc_path) {
518 Ok(c) => c,
519 Err(_) => return,
520 };
521 let lyrics = parse_lrc(&content);
522 if lyrics.is_empty() {
523 return;
524 }
525
526 loop {
527 let pos = audio.position();
528 let current_idx = lyrics.iter().rposition(|l| l.time <= pos).unwrap_or(0);
529 let start = current_idx.saturating_sub(1);
530 let end = (current_idx + 3).min(lyrics.len());
531
532 let mut output = String::new();
533 output.push_str("\x1b[2J\x1b[H");
534 for (idx, l) in lyrics.iter().enumerate().take(end).skip(start) {
535 if idx == current_idx {
536 output.push_str(&format!(" > {}\n", l.text));
537 } else {
538 output.push_str(&format!(" {}\n", l.text));
539 }
540 }
541 if stream.write_all(output.as_bytes()).is_err() {
542 break;
543 }
544 if stream.flush().is_err() {
545 break;
546 }
547 thread::sleep(Duration::from_millis(200));
449 } 548 }
450} 549}
451 550
@@ -592,6 +691,7 @@ fn process_command(
592 "Error accessing queue\n".to_string() 691 "Error accessing queue\n".to_string()
593 } 692 }
594 } 693 }
694
595 "shuffle" => { 695 "shuffle" => {
596 if let Ok(q) = queue.lock() { 696 if let Ok(q) = queue.lock() {
597 match q.shuffle_mode() { 697 match q.shuffle_mode() {