diff --git a/dmp-core/Cargo.toml b/dmp-core/Cargo.toml
index 916eb73..4a2b78c 100644
--- a/dmp-core/Cargo.toml
+++ b/dmp-core/Cargo.toml
@@ -22,16 +22,13 @@ file-format = { version = "0.23.0", features = [
"reader-xml",
"serde",
] }
-lofty = "0.18.2"
+lofty = "0.21"
serde = { version = "1.0.195", features = ["derive"] }
walkdir = "2.4.0"
chrono = { version = "0.4.31", features = ["serde"] }
rayon = "1.8.0"
log = "0.4"
-base64 = "0.21.5"
rcue = "0.1.3"
-gstreamer = "0.21.3"
-glib = "0.18.5"
crossbeam-channel = "0.5.8"
crossbeam = "0.8.2"
quick-xml = "0.31.0"
@@ -44,8 +41,6 @@ serde_json = "1.0.111"
deunicode = "1.4.2"
opener = { version = "0.7.0", features = ["reveal"] }
tempfile = "3.10.1"
-listenbrainz = "0.7.0"
-discord-rpc-client = "0.4.0"
nestify = "0.3.3"
moro = "0.4.0"
moro-local = "0.4.0"
@@ -56,3 +51,4 @@ async-channel = "2.3.1"
ciborium = "0.2.2"
itertools = "0.13.0"
directories = "5.0.1"
+prismriver = { git = "https://github.com/Dangoware/prismriver.git" }
diff --git a/dmp-core/src/config/mod.rs b/dmp-core/src/config/mod.rs
index b11bb38..8897627 100644
--- a/dmp-core/src/config/mod.rs
+++ b/dmp-core/src/config/mod.rs
@@ -1,7 +1,7 @@
use std::{
fs::{self, File, OpenOptions},
io::{Error, Read, Write},
- path::{Path, PathBuf},
+ path::PathBuf,
};
use serde::{Deserialize, Serialize};
@@ -189,9 +189,7 @@ pub enum ConfigError {
pub mod tests {
use super::{Config, ConfigLibrary};
use crate::music_storage::library::MusicLibrary;
- use std::{
- path::PathBuf,
- };
+ use std::path::PathBuf;
pub fn new_config_lib() -> (Config, MusicLibrary) {
_ = std::fs::create_dir_all("test-config/music/");
diff --git a/dmp-core/src/lib.rs b/dmp-core/src/lib.rs
index 0f41571..cc2779c 100644
--- a/dmp-core/src/lib.rs
+++ b/dmp-core/src/lib.rs
@@ -14,9 +14,4 @@ pub mod music_controller {
pub mod queue;
}
-pub mod music_player {
- pub mod gstreamer;
- pub mod player;
-}
-
pub mod config;
diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs
index 3ddb4c1..729922d 100644
--- a/dmp-core/src/music_controller/controller.rs
+++ b/dmp-core/src/music_controller/controller.rs
@@ -3,37 +3,35 @@
//! other functions
#![allow(while_true)]
-use itertools::Itertools;
use kushi::{Queue, QueueItemType};
use kushi::{QueueError, QueueItem};
+use prismriver::{Prismriver, Volume};
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use serde::{Deserialize, Serialize};
use serde_json::to_string_pretty;
use std::error::Error;
use std::fs::OpenOptions;
use std::io::Write;
-use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use thiserror::Error;
use uuid::Uuid;
-use crate::config::{self, ConfigError};
-use crate::music_player::player::{Player, PlayerError};
+use crate::config::ConfigError;
use crate::music_storage::library::Song;
use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem};
use crate::{config::Config, music_storage::library::MusicLibrary};
use super::queue::{QueueAlbum, QueueSong};
-pub struct Controller<'a, P>(&'a PhantomData
);
+pub struct Controller();
#[derive(Error, Debug)]
pub enum ControllerError {
#[error("{0:?}")]
QueueError(#[from] QueueError),
#[error("{0:?}")]
- PlayerError(#[from] PlayerError),
+ PlayerError(#[from] prismriver::Error),
#[error("{0:?}")]
ConfigError(#[from] ConfigError),
}
@@ -213,7 +211,7 @@ impl ControllerState {
}
#[allow(unused_variables)]
-impl<'c, P: Player + Send + Sync> Controller<'c, P> {
+impl Controller {
pub async fn start(
ControllerInput {
player_mail,
@@ -222,10 +220,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
mut library,
config
}: ControllerInput
- ) -> Result<(), Box>
- where
- P: Player,
- {
+ ) -> Result<(), Box> {
let queue: Queue = Queue {
items: Vec::new(),
played: Vec::new(),
@@ -250,13 +245,13 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
futures::executor::block_on(async {
moro::async_scope!(|scope| {
println!("async scope created");
- let player = Arc::new(RwLock::new(P::new().unwrap()));
+ let player = Arc::new(RwLock::new(Prismriver::new()));
let _player = player.clone();
let _lib_mail = lib_mail.0.clone();
scope
.spawn(async move {
- Controller::::player_command_loop(
+ Controller::player_command_loop(
_player,
player_mail.1,
queue_mail.0,
@@ -268,7 +263,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
});
scope
.spawn(async move {
- Controller::
::player_event_loop(
+ Controller::player_event_loop(
player,
player_mail.0
)
@@ -277,7 +272,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
});
scope
.spawn(async {
- Controller::
::library_loop(
+ Controller::library_loop(
lib_mail.1,
&mut library,
config,
@@ -292,7 +287,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
let b = scope.spawn(|| {
futures::executor::block_on(async {
- Controller::
::queue_loop(queue, queue_mail.1).await;
+ Controller::queue_loop(queue, queue_mail.1).await;
})
});
a.join().unwrap();
@@ -303,7 +298,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
}
async fn player_command_loop(
- player: Arc>,
+ player: Arc>,
player_mail: MailMan,
queue_mail: MailMan,
lib_mail: MailMan,
@@ -311,9 +306,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
) -> Result<(), ()> {
let mut first = true;
{
- let volume = state.volume as f64;
- player.write().unwrap().set_volume(volume);
- println!("volume set to {volume}");
+ player.write().unwrap().set_volume(Volume::new(state.volume));
+ println!("volume set to {}", state.volume);
}
while true {
let _mail = player_mail.recv().await;
@@ -324,20 +318,24 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
queue_mail.send(QueueCommand::NowPlaying).await.unwrap();
let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() else { unimplemented!() };
let QueueItemType::Single(song) = item.item else { unimplemented!("This is temporary, handle queueItemTypes at some point") };
- player.write().unwrap().enqueue_next(song.song.primary_uri().unwrap().0).unwrap();
+
+ let prism_uri = prismriver::utils::path_to_uri(&song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
+ player.write().unwrap().load_new(&prism_uri).unwrap();
+ player.write().unwrap().play();
+
player_mail.send(PlayerResponse::NowPlaying(song.song)).await.unwrap();
first = false
} else {
- player.write().unwrap().play().unwrap();
+ player.write().unwrap().play();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
}
PlayerCommand::Pause => {
- player.write().unwrap().pause().unwrap();
+ player.write().unwrap().pause();
player_mail.send(PlayerResponse::Empty).await.unwrap();
}
PlayerCommand::SetVolume(volume) => {
- player.write().unwrap().set_volume(volume as f64);
+ player.write().unwrap().set_volume(Volume::new(volume));
println!("volume set to {volume}");
player_mail.send(PlayerResponse::Empty).await.unwrap();
@@ -352,7 +350,11 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
_ => unimplemented!(),
};
- player.write().unwrap().enqueue_next(uri).unwrap();
+
+ let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap();
+ player.write().unwrap().load_new(&prism_uri).unwrap();
+ player.write().unwrap().play();
+
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
// Append next song in library
@@ -398,6 +400,11 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
_ => unimplemented!(),
};
+
+ let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap();
+ player.write().unwrap().load_new(&prism_uri).unwrap();
+ player.write().unwrap().play();
+
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();
}
@@ -410,11 +417,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
if let QueueResponse::Item(item) = queue_mail.recv().await.unwrap() {
match item.item {
QueueItemType::Single(song) => {
- player
- .write()
- .unwrap()
- .enqueue_next(song.song.primary_uri().unwrap().0)
- .unwrap();
+ let prism_uri = prismriver::utils::path_to_uri(&song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
+ player.write().unwrap().load_new(&prism_uri).unwrap();
+ player.write().unwrap().play();
}
_ => unimplemented!(),
}
@@ -436,7 +441,10 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
unreachable!()
};
- player.write().unwrap().enqueue_next(song.primary_uri().unwrap().0).unwrap();
+ // TODO: Handle non Local URIs here, and whenever `load_new()` or `load_gapless()` is called
+ let prism_uri = prismriver::utils::path_to_uri(&song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
+ player.write().unwrap().load_new(&prism_uri).unwrap();
+ player.write().unwrap().play();
// how grab all the songs in a certain subset of the library, I reckon?
// ...
@@ -486,7 +494,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
async fn library_loop(
lib_mail: MailMan,
- library: &'c mut MusicLibrary,
+ library: &mut MusicLibrary,
config: Arc>,
) -> Result<(), ()> {
while true {
@@ -511,7 +519,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
}
LibraryCommand::Save => {
- library.save({config.read().unwrap().libraries.get_library(&library.uuid).unwrap().path.clone()}).unwrap();
+ library.save(config.read().unwrap().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap();
lib_mail.send(LibraryResponse::Ok).await.unwrap();
}
LibraryCommand::Playlists => {
@@ -527,7 +535,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
}
async fn player_event_loop(
- player: Arc>,
+ player: Arc>,
player_mail: MailMan,
) -> Result<(), ()> {
// just pretend this does something
@@ -586,82 +594,3 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
}
}
}
-
-#[cfg(test)]
-mod test_super {
- use std::{
- path::PathBuf,
- sync::{Arc, RwLock},
- thread::spawn,
- };
-
- use crate::{
- config::{tests::new_config_lib, Config},
- music_controller::controller::{
- LibraryCommand, LibraryResponse, MailMan, PlayerCommand, PlayerResponse, ControllerHandle
- },
- music_player::gstreamer::GStreamer,
- music_storage::library::MusicLibrary,
- };
-
- use super::Controller;
-
- #[tokio::test]
- async fn construct_controller() {
- // use if you don't have a config setup and add music to the music folder
- new_config_lib();
-
- let config = Config::read_file(PathBuf::from(std::env!("CONFIG-PATH"))).unwrap();
- let library = {
- MusicLibrary::init(
- config.libraries.get_default().unwrap().path.clone(),
- config.libraries.get_default().unwrap().uuid,
- )
- .unwrap()
- };
-
- let (handle, input) = ControllerHandle::new(library, Arc::new(RwLock::new(config)));
-
- let b = spawn(move || {
- futures::executor::block_on(async {
- handle.player_mail
- .send(PlayerCommand::SetVolume(0.01))
- .await
- .unwrap();
- loop {
- let buf: String = text_io::read!();
- dbg!(&buf);
- handle.player_mail
- .send(match buf.to_lowercase().as_str() {
- "next" => PlayerCommand::NextSong,
- "prev" => PlayerCommand::PrevSong,
- "pause" => PlayerCommand::Pause,
- "play" => PlayerCommand::Play,
- x if x.parse::().is_ok() => {
- PlayerCommand::Enqueue(x.parse::().unwrap())
- }
- _ => continue,
- })
- .await
- .unwrap();
- println!("sent it");
- println!("{:?}", handle.player_mail.recv().await.unwrap())
- }
- })
- });
-
- let a = spawn(move || {
- futures::executor::block_on(async {
-
-
- Controller::::start(input)
- .await
- .unwrap();
- });
- });
-
- b.join().unwrap();
- a.join().unwrap();
- }
-}
-
diff --git a/dmp-core/src/music_player/gstreamer.rs b/dmp-core/src/music_player/gstreamer.rs
deleted file mode 100644
index 7733d85..0000000
--- a/dmp-core/src/music_player/gstreamer.rs
+++ /dev/null
@@ -1,521 +0,0 @@
-// Crate things
-use crate::music_storage::library::URI;
-use crossbeam_channel::{unbounded, Receiver, Sender};
-use std::error::Error;
-use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
-
-// GStreamer things
-use glib::FlagsClass;
-use gst::{ClockTime, Element};
-use gstreamer as gst;
-use gstreamer::prelude::*;
-
-// Extra things
-use chrono::Duration;
-
-use super::player::{Player, PlayerCommand, PlayerError, PlayerState};
-
-impl From for PlayerState {
- fn from(value: gst::State) -> Self {
- match value {
- gst::State::VoidPending => Self::VoidPending,
- gst::State::Playing => Self::Playing,
- gst::State::Paused => Self::Paused,
- gst::State::Ready => Self::Ready,
- gst::State::Null => Self::Null,
- }
- }
-}
-
-impl TryInto for PlayerState {
- fn try_into(self) -> Result> {
- match self {
- Self::VoidPending => Ok(gst::State::VoidPending),
- Self::Playing => Ok(gst::State::Playing),
- Self::Paused => Ok(gst::State::Paused),
- Self::Ready => Ok(gst::State::Ready),
- Self::Null => Ok(gst::State::Null),
- state => Err(format!("Invalid gst::State: {:?}", state).into()),
- }
- }
-
- type Error = Box;
-}
-
-#[derive(Debug, PartialEq, Eq)]
-enum PlaybackInfo {
- Idle,
- Switching,
- Playing {
- start: Duration,
- end: Duration,
- },
-
- /// When this is sent, the thread will die! Use it when the [Player] is
- /// done playing
- Finished,
-}
-
-/// An instance of a music player with a GStreamer backend
-#[derive(Debug)]
-pub struct GStreamer {
- source: Option,
-
- message_rx: crossbeam::channel::Receiver,
- playback_tx: crossbeam::channel::Sender,
-
- playbin: Arc>,
- volume: f64,
- start: Option,
- end: Option,
- paused: Arc>,
- position: Arc>>,
-}
-
-impl From for PlayerError {
- fn from(value: gst::StateChangeError) -> Self {
- PlayerError::StateChange(value.to_string())
- }
-}
-
-impl From for PlayerError {
- fn from(value: glib::BoolError) -> Self {
- PlayerError::General(value.to_string())
- }
-}
-
-impl GStreamer {
- /// Set the playback URI
- fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
- if !source.exists().is_ok_and(|x| x) {
- // If the source doesn't exist, gstreamer will crash!
- return Err(PlayerError::NotFound);
- }
-
- // Make sure the playback tracker knows the stuff is stopped
- println!("Beginning switch");
- self.playback_tx.send(PlaybackInfo::Switching).unwrap();
-
- let uri = self.playbin.read().unwrap().property_value("current-uri");
- self.source = Some(source.clone());
- match source {
- URI::Cue { start, end, .. } => {
- self.playbin
- .write()
- .unwrap()
- .set_property("uri", source.as_uri());
-
- // Set the start and end positions of the CUE file
- self.start = Some(Duration::from_std(*start).unwrap());
- self.end = Some(Duration::from_std(*end).unwrap());
-
- // Send the updated position to the tracker
- self.playback_tx
- .send(PlaybackInfo::Playing {
- start: self.start.unwrap(),
- end: self.end.unwrap(),
- })
- .unwrap();
-
- // Wait for it to be ready, and then move to the proper position
- self.play().unwrap();
- let now = std::time::Instant::now();
- while now.elapsed() < std::time::Duration::from_millis(20) {
- if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
- return Ok(());
- }
- std::thread::sleep(std::time::Duration::from_millis(1));
- }
- //panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
- return Err(PlayerError::StateChange(
- "Could not seek to beginning of CUE track".into(),
- ));
- }
- _ => {
- self.playbin
- .write()
- .unwrap()
- .set_property("uri", source.as_uri());
-
- if self.state() != PlayerState::Playing {
- self.play().unwrap();
- }
-
- while self.raw_duration().is_none() {
- std::thread::sleep(std::time::Duration::from_millis(10));
- }
-
- self.start = Some(Duration::seconds(0));
- self.end = self.raw_duration();
-
- // Send the updated position to the tracker
- self.playback_tx
- .send(PlaybackInfo::Playing {
- start: self.start.unwrap(),
- end: self.end.unwrap(),
- })
- .unwrap();
- }
- }
-
- Ok(())
- }
-
- /// Gets a mutable reference to the playbin element
- fn playbin_mut(
- &mut self,
- ) -> Result, std::sync::PoisonError>>
- {
- let element = match self.playbin.write() {
- Ok(element) => element,
- Err(err) => return Err(err),
- };
- Ok(element)
- }
-
- /// Gets a read-only reference to the playbin element
- fn playbin(
- &self,
- ) -> Result, std::sync::PoisonError>>
- {
- let element = match self.playbin.read() {
- Ok(element) => element,
- Err(err) => return Err(err),
- };
- Ok(element)
- }
-
- /// Set volume of the internal playbin player, can be
- /// used to bypass the main volume control for seeking
- fn set_gstreamer_volume(&mut self, volume: f64) {
- self.playbin_mut().unwrap().set_property("volume", volume)
- }
-
- fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
- self.playbin_mut().unwrap().set_state(state)?;
-
- Ok(())
- }
-
- fn raw_duration(&self) -> Option {
- self.playbin()
- .unwrap()
- .query_duration::()
- .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
- }
-
- /// Get the current state of the playback
- fn state(&mut self) -> PlayerState {
- self.playbin().unwrap().current_state().into()
- /*
- match *self.buffer.read().unwrap() {
- None => self.playbin().unwrap().current_state().into(),
- Some(value) => PlayerState::Buffering(value),
- }
- */
- }
-
- fn property(&self, property: &str) -> glib::Value {
- self.playbin().unwrap().property_value(property)
- }
-
- fn ready(&mut self) -> Result<(), PlayerError> {
- self.set_state(gst::State::Ready)?;
- Ok(())
- }
-}
-
-impl Player for GStreamer {
- fn new() -> Result {
- // Initialize GStreamer, maybe figure out how to nicely fail here
- if let Err(err) = gst::init() {
- return Err(PlayerError::Init(err.to_string()));
- };
- let ctx = glib::MainContext::default();
- let _guard = ctx.acquire();
- let mainloop = glib::MainLoop::new(Some(&ctx), false);
-
- let playbin_arc = Arc::new(RwLock::new(
- match gst::ElementFactory::make("playbin3").build() {
- Ok(playbin) => playbin,
- Err(error) => return Err(PlayerError::Init(error.to_string())),
- },
- ));
-
- let playbin = playbin_arc.clone();
-
- let flags = playbin.read().unwrap().property_value("flags");
- let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
-
- // Set up the Playbin flags to only play audio
- let flags = flags_class
- .builder_with_value(flags)
- .ok_or(PlayerError::Build)?
- .set_by_nick("audio")
- .set_by_nick("download")
- .unset_by_nick("video")
- .unset_by_nick("text")
- .build()
- .ok_or(PlayerError::Build)?;
-
- playbin
- .write()
- .unwrap()
- .set_property_from_value("flags", &flags);
- //playbin.write().unwrap().set_property("instant-uri", true);
-
- let position = Arc::new(RwLock::new(None));
-
- // Set up the thread to monitor the position
- let (playback_tx, playback_rx) = unbounded();
- let (status_tx, status_rx) = unbounded::();
- let position_update = Arc::clone(&position);
-
- std::thread::spawn(|| {
- playback_monitor(playbin_arc, status_rx, playback_tx, position_update)
- });
-
- // Set up the thread to monitor bus messages
- let playbin_bus_ctrl = Arc::clone(&playbin);
- let paused = Arc::new(RwLock::new(false));
- let bus_paused = Arc::clone(&paused);
- let bus_watch = playbin
- .read()
- .unwrap()
- .bus()
- .expect("Failed to get GStreamer message bus")
- .add_watch(move |_bus, msg| {
- match msg.view() {
- gst::MessageView::Eos(_) => println!("End of stream"),
- gst::MessageView::StreamStart(_) => println!("Stream start"),
- gst::MessageView::Error(err) => {
- println!("Error recieved: {}", err);
- return glib::ControlFlow::Break;
- }
- gst::MessageView::Buffering(buffering) => {
- if *bus_paused.read().unwrap() == true {
- return glib::ControlFlow::Continue;
- }
-
- // If the player is not paused, pause it
- let percent = buffering.percent();
- if percent < 100 {
- playbin_bus_ctrl
- .write()
- .unwrap()
- .set_state(gst::State::Paused)
- .unwrap();
- } else if percent >= 100 {
- println!("Finished buffering");
- playbin_bus_ctrl
- .write()
- .unwrap()
- .set_state(gst::State::Playing)
- .unwrap();
- }
- }
- _ => (),
- }
- glib::ControlFlow::Continue
- })
- .expect("Failed to connect to GStreamer message bus");
-
- // Set up a thread to watch the messages
- std::thread::spawn(move || {
- let _watch = bus_watch;
- mainloop.run()
- });
-
- let source = None;
- Ok(Self {
- source,
- playbin,
- message_rx: playback_rx,
- playback_tx: status_tx,
- volume: 1.0,
- start: None,
- end: None,
- paused,
- position,
- })
- }
-
- fn source(&self) -> &Option {
- &self.source
- }
-
- fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
- println!("enqueuing in fn");
- self.set_source(next_track)
- }
-
- fn set_volume(&mut self, volume: f64) {
- self.volume = volume.clamp(0.0, 1.0);
- self.set_gstreamer_volume(self.volume);
- }
-
- fn volume(&self) -> f64 {
- self.volume
- }
-
- fn play(&mut self) -> Result<(), PlayerError> {
- if self.state() == PlayerState::Playing {
- return Ok(());
- }
- *self.paused.write().unwrap() = false;
- self.set_state(gst::State::Playing)?;
- Ok(())
- }
-
- fn pause(&mut self) -> Result<(), PlayerError> {
- if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
- return Ok(());
- }
- *self.paused.write().unwrap() = true;
- self.set_state(gst::State::Paused)?;
- Ok(())
- }
-
- fn is_paused(&self) -> bool {
- self.playbin().unwrap().current_state() == gst::State::Paused
- }
-
- fn position(&self) -> Option {
- *self.position.read().unwrap()
- }
-
- fn duration(&self) -> Option {
- if self.end.is_some() && self.start.is_some() {
- Some(self.end.unwrap() - self.start.unwrap())
- } else {
- self.raw_duration()
- }
- }
-
- fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError> {
- let time_pos = match *self.position.read().unwrap() {
- Some(pos) => pos,
- None => return Err(PlayerError::Seek("No position".into())),
- };
- let seek_pos = time_pos + seek_amount;
-
- self.seek_to(seek_pos)?;
- Ok(())
- }
-
- fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError> {
- let start = if self.start.is_none() {
- return Err(PlayerError::Seek("No START time".into()));
- } else {
- self.start.unwrap()
- };
-
- let end = if self.end.is_none() {
- return Err(PlayerError::Seek("No END time".into()));
- } else {
- self.end.unwrap()
- };
-
- let adjusted_target = target_pos + start;
- let clamped_target = adjusted_target.clamp(start, end);
-
- let seek_pos_clock =
- ClockTime::from_useconds(clamped_target.num_microseconds().unwrap() as u64);
-
- self.set_gstreamer_volume(0.0);
- self.playbin_mut()
- .unwrap()
- .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
- self.set_gstreamer_volume(self.volume);
- Ok(())
- }
-
- fn stop(&mut self) -> Result<(), PlayerError> {
- self.pause()?;
- self.ready()?;
-
- // Send the updated position to the tracker
- self.playback_tx.send(PlaybackInfo::Idle).unwrap();
-
- // Set all positions to none
- *self.position.write().unwrap() = None;
- self.start = None;
- self.end = None;
- Ok(())
- }
-
- fn message_channel(&self) -> &crossbeam::channel::Receiver {
- &self.message_rx
- }
-}
-
-impl Drop for GStreamer {
- /// Cleans up the `GStreamer` pipeline and the monitoring
- /// thread when [Player] is dropped.
- fn drop(&mut self) {
- self.playbin_mut()
- .unwrap()
- .set_state(gst::State::Null)
- .expect("Unable to set the pipeline to the `Null` state");
- let _ = self.playback_tx.send(PlaybackInfo::Finished);
- }
-}
-
-fn playback_monitor(
- playbin: Arc>,
- status_rx: Receiver,
- playback_tx: Sender,
- position: Arc>>,
-) {
- let mut stats = PlaybackInfo::Idle;
- let mut pos_temp;
- let mut sent_atf = false;
- loop {
- // Check for new messages to decide how to proceed
- if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(50)) {
- stats = result
- }
-
- pos_temp = playbin
- .read()
- .unwrap()
- .query_position::()
- .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
-
- match stats {
- PlaybackInfo::Playing { start, end } if pos_temp.is_some() => {
- // Check if the current playback position is close to the end
- let finish_point = end - Duration::milliseconds(2000);
- if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
- println!("MONITOR: End of stream");
- let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
- playbin
- .write()
- .unwrap()
- .set_state(gst::State::Ready)
- .expect("Unable to set the pipeline state");
- sent_atf = false
- } else if pos_temp.unwrap().num_microseconds() >= finish_point.num_microseconds()
- && !sent_atf
- {
- println!("MONITOR: About to finish");
- let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
- sent_atf = true;
- }
-
- // This has to be done AFTER the current time in the file
- // is calculated, or everything else is wrong
- pos_temp = Some(pos_temp.unwrap() - start)
- }
- PlaybackInfo::Finished => {
- println!("MONITOR: Shutting down");
- *position.write().unwrap() = None;
- break;
- }
- PlaybackInfo::Idle | PlaybackInfo::Switching => sent_atf = false,
- _ => (),
- }
-
- *position.write().unwrap() = pos_temp;
- }
-}
diff --git a/dmp-core/src/music_player/kira.rs b/dmp-core/src/music_player/kira.rs
deleted file mode 100644
index e69de29..0000000
diff --git a/dmp-core/src/music_player/player.rs b/dmp-core/src/music_player/player.rs
deleted file mode 100644
index 4b74498..0000000
--- a/dmp-core/src/music_player/player.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-use chrono::Duration;
-use thiserror::Error;
-
-use crate::music_storage::library::URI;
-
-#[derive(Error, Debug)]
-pub enum PlayerError {
- #[error("player initialization failed: {0}")]
- Init(String),
- #[error("could not change playback state")]
- StateChange(String),
- #[error("seeking failed: {0}")]
- Seek(String),
- #[error("the file or source is not found")]
- NotFound,
- #[error("failed to build gstreamer item")]
- Build,
- #[error("poison error")]
- Poison,
- #[error("general player error")]
- General(String),
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum PlayerState {
- Playing,
- Paused,
- Ready,
- Buffering(u8),
- Null,
- VoidPending,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum PlayerCommand {
- Play,
- Pause,
- EndOfStream,
- AboutToFinish,
-}
-
-pub trait Player {
- /// Create a new player.
- fn new() -> Result
- where
- Self: Sized;
-
- /// Get the currently playing [URI] from the player.
- fn source(&self) -> &Option;
-
- /// Insert a new [`URI`] to be played. This method should be called at the
- /// beginning to start playback of something, and once the [`PlayerCommand`]
- /// indicates the track is about to finish to enqueue gaplessly.
- ///
- /// For backends which do not support gapless playback, `AboutToFinish`
- /// will not be called, and the next [`URI`] should be enqueued once `Eos`
- /// occurs.
- fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
-
- /// Set the playback volume, accepts a float from `0` to `1`.
- ///
- /// Values outside the range of `0` to `1` will be capped.
- fn set_volume(&mut self, volume: f64);
-
- /// Returns the current volume level, a float from `0` to `1`.
- fn volume(&self) -> f64;
-
- /// If the player is paused or stopped, starts playback.
- fn play(&mut self) -> Result<(), PlayerError>;
-
- /// If the player is playing, pause playback.
- fn pause(&mut self) -> Result<(), PlayerError>;
-
- /// Stop the playback entirely, removing the current [`URI`] from the player.
- fn stop(&mut self) -> Result<(), PlayerError>;
-
- /// Convenience function to check if playback is paused.
- fn is_paused(&self) -> bool;
-
- /// Get the current playback position of the player.
- fn position(&self) -> Option;
-
- /// Get the duration of the currently playing track.
- fn duration(&self) -> Option;
-
- /// Seek relative to the current position.
- ///
- /// The position is capped at the duration of the song, and zero.
- fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
-
- /// Seek absolutely within the song.
- ///
- /// The position is capped at the duration of the song, and zero.
- fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
-
- /// Return a reference to the player message channel, which can be cloned
- /// in order to monitor messages from the player.
- fn message_channel(&self) -> &crossbeam::channel::Receiver;
-}
diff --git a/dmp-core/src/music_storage/db_reader/itunes/reader.rs b/dmp-core/src/music_storage/db_reader/itunes/reader.rs
index 05075f2..868d1d8 100644
--- a/dmp-core/src/music_storage/db_reader/itunes/reader.rs
+++ b/dmp-core/src/music_storage/db_reader/itunes/reader.rs
@@ -1,5 +1,7 @@
use file_format::FileFormat;
-use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
+use lofty::file::{AudioFile as _, TaggedFileExt as _};
+use lofty::probe::Probe;
+use lofty::tag::TagType;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
use std::collections::{BTreeMap, HashMap};
@@ -221,7 +223,7 @@ fn to_tag(string: String) -> Tag {
_ => Tag::Key(string),
}
}
-fn get_duration(file: &Path) -> Result {
+fn get_duration(file: &Path) -> Result {
let dur = match Probe::open(file)?.read() {
Ok(tagged_file) => tagged_file.properties().duration(),
@@ -229,12 +231,12 @@ fn get_duration(file: &Path) -> Result {
};
Ok(dur)
}
-fn get_art(file: &Path) -> Result, LoftyError> {
+fn get_art(file: &Path) -> Result, lofty::error::LoftyError> {
let mut album_art: Vec = Vec::new();
- let blank_tag = &lofty::Tag::new(TagType::Id3v2);
- let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
- let tagged_file: lofty::TaggedFile;
+ let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
+ let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
+ let tagged_file: lofty::file::TaggedFile;
let tag = match Probe::open(file)?.options(normal_options).read() {
Ok(e) => {
@@ -288,7 +290,7 @@ impl ITunesSong {
Default::default()
}
- fn from_hashmap(map: &mut HashMap) -> Result {
+ fn from_hashmap(map: &mut HashMap) -> Result {
let mut song = ITunesSong::new();
//get the path with the first bit chopped off
let path_: String = map.get_key_value("Location").unwrap().1.clone();
@@ -339,10 +341,7 @@ impl ITunesSong {
#[cfg(test)]
mod tests {
- use std::{
- path::{Path, PathBuf},
- sync::{Arc, RwLock},
- };
+ use std::path::{Path, PathBuf};
use crate::{
config::{Config, ConfigLibrary},
diff --git a/dmp-core/src/music_storage/library.rs b/dmp-core/src/music_storage/library.rs
index a494d6c..cb78b0d 100644
--- a/dmp-core/src/music_storage/library.rs
+++ b/dmp-core/src/music_storage/library.rs
@@ -1,12 +1,11 @@
use super::playlist::{Playlist, PlaylistFolder};
// Crate things
use super::utils::{find_images, normalize, read_file, write_file};
-use crate::config::Config;
use crate::music_storage::playlist::PlaylistFolderItem;
use std::cmp::Ordering;
// Various std things
-use std::collections::{BTreeMap, HashMap};
+use std::collections::BTreeMap;
use std::error::Error;
use std::io::Read;
use std::ops::ControlFlow::{Break, Continue};
@@ -14,9 +13,10 @@ use std::vec::IntoIter;
// Files
use file_format::{FileFormat, Kind};
-use glib::filename_to_uri;
-use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
+use lofty::file::{AudioFile as _, TaggedFileExt as _};
+use lofty::probe::Probe;
+use lofty::tag::{ItemKey, ItemValue, TagType};
use rcue::parser::parse_from_file;
use std::fs;
use std::path::{Path, PathBuf};
@@ -28,12 +28,11 @@ use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
use std::time::Duration;
// Serialization/Compression
-use base64::{engine::general_purpose, Engine as _};
use serde::{Deserialize, Serialize};
// Fun parallel stuff
use rayon::prelude::*;
-use std::sync::{Arc, Mutex, RwLock};
+use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum AlbumArt {
@@ -225,10 +224,10 @@ impl Song {
/// Creates a `Song` from a music file
pub fn from_file>(target_file: &P) -> Result> {
- let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
+ let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
- let blank_tag = &lofty::Tag::new(TagType::Id3v2);
- let tagged_file: lofty::TaggedFile;
+ let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
+ let tagged_file: lofty::file::TaggedFile;
let mut duration = Duration::from_secs(0);
let tag = match Probe::open(target_file)?.options(normal_options).read() {
Ok(file) => {
@@ -273,7 +272,7 @@ impl Song {
let value = match item.value() {
ItemValue::Text(value) => value.clone(),
ItemValue::Locator(value) => value.clone(),
- ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
+ ItemValue::Binary(_) => continue, // TODO: Ignoring binary values for now
};
tags.insert(key, value);
@@ -548,10 +547,10 @@ impl URI {
pub fn as_uri(&self) -> String {
let path_str = match self {
- URI::Local(location) => filename_to_uri(location, None)
+ URI::Local(location) => prismriver::utils::path_to_uri(location)
.expect("couldn't convert path to URI")
.to_string(),
- URI::Cue { location, .. } => filename_to_uri(location, None)
+ URI::Cue { location, .. } => prismriver::utils::path_to_uri(location)
.expect("couldn't convert path to URI")
.to_string(),
URI::Remote(_, location) => location.clone(),
diff --git a/dmp-core/src/music_storage/utils.rs b/dmp-core/src/music_storage/utils.rs
index 991d3d7..a91299d 100644
--- a/dmp-core/src/music_storage/utils.rs
+++ b/dmp-core/src/music_storage/utils.rs
@@ -94,4 +94,4 @@ pub fn find_images(song_path: &Path) -> Result, Box> {
}
Ok(images)
-}
\ No newline at end of file
+}
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index ec4e613..0bb656d 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -28,12 +28,12 @@ futures = "0.3.31"
crossbeam = "0.8.4"
directories = "5.0.1"
uuid = { version = "1.11.0", features = ["v4", "serde"] }
-ciborium = "0.2.2"
mime = "0.3.17"
file-format = "0.26.0"
chrono = { version = "0.4.38", features = ["serde"] }
itertools = "0.13.0"
rfd = "0.15.1"
+colog = "1.3.0"
[features]
default = [ "custom-protocol" ]
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index d51c41b..5147b00 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -2,8 +2,8 @@ use std::{fs, path::PathBuf, str::FromStr, thread::spawn};
use commands::{add_song_to_queue, play_now};
use crossbeam::channel::{unbounded, Receiver, Sender};
-use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}};
-use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry};
+use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_storage::library::MusicLibrary};
+use tauri::{http::Response, Emitter, Manager, State, WebviewWindowBuilder, Wry};
use uuid::Uuid;
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists};
@@ -55,7 +55,7 @@ pub fn run() {
handle_rx.send(handle).unwrap();
- let _controller = futures::executor::block_on(Controller::::start(input)).unwrap();
+ let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
});
let app = tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 0355cd9..63318ee 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -3,5 +3,7 @@
fn main() {
+ colog::init();
+
dango_music_player_lib::run()
}