monorepo/music-player/server/src/core.rs

166 lines
4.4 KiB
Rust

use crate::{
database::MusicIndex,
media::{TrackId, TrackInfo},
playback::{Playback, PlaybackControl, PlaybackStatus},
scanner::MusicScanner,
Error, FatalError,
};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView};
use std::{
sync::{Arc, Mutex, RwLock},
thread,
time::{Duration, Instant},
};
use tokio::{
sync::mpsc::{channel, Receiver, Sender},
task::AbortHandle,
};
fn scan_frequency() -> Duration {
Duration::from_secs(60)
}
#[derive(Clone, Debug)]
pub enum ControlMsg {
PlayTrack(TrackId),
Exit,
}
#[derive(Clone, Debug)]
pub enum TrackMsg {
UpdateInProgress,
UpdateComplete,
}
#[derive(Clone, Debug)]
pub enum PlaybackMsg {
PositionUpdate,
Playing,
Pausing,
Stopping,
}
pub struct Core {
db: Arc<dyn MusicIndex>,
playback_controller: Option<Playback>,
next_scan: Arc<RwLock<Instant>>,
scanner_handle: thread::JoinHandle<()>,
}
impl Core {
pub fn new(
db: Arc<dyn MusicIndex>,
scanner: Arc<dyn MusicScanner>,
) -> Result<Core, FatalError> {
let db = db;
let next_scan = Arc::new(RwLock::new(Instant::now()));
let scanner_handle = {
let db = db.clone();
let next_scan = next_scan.clone();
thread::spawn(move || scan_loop(scanner, db, next_scan))
};
Ok(Core {
db,
playback_controller: None,
next_scan,
scanner_handle,
})
}
pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
self.db.list_tracks().map_err(Error::DatabaseError)
}
pub fn play_track<'a>(&'a mut self, id: TrackId) -> Result<(), Error> {
self.stop_playback()?;
self.playback_controller = Some(Playback::new(id)?);
Ok(())
}
pub fn stop_playback<'a>(&'a mut self) -> Result<(), Error> {
match self.playback_controller {
Some(ref controller) => controller.stop()?,
None => (),
}
self.playback_controller = None;
Ok(())
}
}
pub fn scan_loop(
scanner: Arc<dyn MusicScanner>,
db: Arc<dyn MusicIndex>,
next_scan: Arc<RwLock<Instant>>,
) {
loop {
if Instant::now() >= *next_scan.read().unwrap() {
let scan_start = Instant::now();
let mut counter = 0;
for track in scanner.scan() {
counter += 1;
match track {
Ok(track) => db.add_track(track),
Err(_) => ok(()),
};
}
*next_scan.write().unwrap() = Instant::now() + scan_frequency();
println!(
"scanned {} files in {:?}",
counter,
Instant::now() - scan_start
);
}
thread::sleep(Duration::from_secs(1));
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{database::MemoryIndex, media::TrackId, scanner::factories::MockScanner};
use std::collections::HashSet;
fn with_example_index<F>(f: F)
where
F: Fn(Core),
{
let index = MemoryIndex::new();
let scanner = MockScanner::new();
match Core::new(Arc::new(index), Arc::new(scanner)) {
Ok(core) => {
thread::sleep(Duration::from_millis(10));
f(core)
}
Err(error) => panic!("{:?}", error),
}
}
#[test]
fn it_lists_tracks() {
with_example_index(|core| match core.list_tracks() {
Flow::Ok(tracks) => {
let track_ids = tracks
.iter()
.map(|t| t.id.clone())
.collect::<HashSet<TrackId>>();
assert_eq!(track_ids.len(), 5);
assert_eq!(
track_ids,
HashSet::from([
TrackId::from("/home/savanni/Track 1.mp3".to_owned()),
TrackId::from("/home/savanni/Track 2.mp3".to_owned()),
TrackId::from("/home/savanni/Track 3.mp3".to_owned()),
TrackId::from("/home/savanni/Track 4.mp3".to_owned()),
TrackId::from("/home/savanni/Track 5.mp3".to_owned()),
])
);
}
Flow::Fatal(err) => panic!("fatal error: {:?}", err),
Flow::Err(err) => panic!("error: {:?}", err),
})
}
}