mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
Updated music_player.rs
to more properly handle its own thread
This commit is contained in:
parent
7c5a9c282b
commit
ccbd11175b
2 changed files with 103 additions and 78 deletions
|
@ -1,7 +1,7 @@
|
||||||
// Crate things
|
// Crate things
|
||||||
//use crate::music_controller::config::Config;
|
//use crate::music_controller::config::Config;
|
||||||
use crate::music_storage::library::URI;
|
use crate::music_storage::library::URI;
|
||||||
use crossbeam_channel::bounded;
|
use crossbeam_channel::unbounded;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ pub enum PlayerCmd {
|
||||||
AboutToFinish,
|
AboutToFinish,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum PlayerState {
|
pub enum PlayerState {
|
||||||
Playing,
|
Playing,
|
||||||
Paused,
|
Paused,
|
||||||
|
@ -76,18 +76,29 @@ pub enum PlayerError {
|
||||||
General,
|
General,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum PlaybackStats {
|
||||||
|
Idle,
|
||||||
|
Switching,
|
||||||
|
Playing{
|
||||||
|
start: Duration,
|
||||||
|
end: Duration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An instance of a music player with a GStreamer backend
|
/// An instance of a music player with a GStreamer backend
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
source: Option<URI>,
|
source: Option<URI>,
|
||||||
//pub message_tx: Sender<PlayerCmd>,
|
//pub message_tx: Sender<PlayerCmd>,
|
||||||
pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
|
pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
|
||||||
playbin: Arc<RwLock<Element>>,
|
|
||||||
volume: f64,
|
playback_tx: crossbeam::channel::Sender<PlaybackStats>,
|
||||||
start: Arc<RwLock<Option<Duration>>>,
|
playbin: Arc<RwLock<Element>>,
|
||||||
end: Arc<RwLock<Option<Duration>>>,
|
volume: f64,
|
||||||
position: Arc<RwLock<Option<Duration>>>,
|
start: Option<Duration>,
|
||||||
buffer: Arc<RwLock<Option<u8>>>,
|
end: Option<Duration>,
|
||||||
paused: Arc<RwLock<bool>>,
|
paused: bool,
|
||||||
|
position: Arc<RwLock<Option<Duration>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -118,69 +129,58 @@ impl Player {
|
||||||
.build()
|
.build()
|
||||||
.ok_or(PlayerError::Build)?;
|
.ok_or(PlayerError::Build)?;
|
||||||
|
|
||||||
playbin
|
playbin.write().unwrap().set_property_from_value("flags", &flags);
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.set_property_from_value("flags", &flags);
|
|
||||||
|
|
||||||
playbin.write().unwrap().set_property("instant-uri", true);
|
playbin.write().unwrap().set_property("instant-uri", true);
|
||||||
|
|
||||||
let position = Arc::new(RwLock::new(None));
|
let position = Arc::new(RwLock::new(None));
|
||||||
let start = Arc::new(RwLock::new(None));
|
|
||||||
let end: Arc<RwLock<Option<Duration>>> = Arc::new(RwLock::new(None));
|
|
||||||
let buffer = Arc::new(RwLock::new(None));
|
|
||||||
let paused = Arc::new(RwLock::new(false));
|
|
||||||
|
|
||||||
// Set up the thread to monitor the position
|
// Set up the thread to monitor the position
|
||||||
let position_update = position.clone();
|
let (playback_tx, playback_rx) = unbounded();
|
||||||
let start_update = Arc::clone(&start);
|
let (stat_tx, stat_rx) = unbounded::<PlaybackStats>();
|
||||||
let end_update = Arc::clone(&end);
|
let position_update = Arc::clone(&position);
|
||||||
let (message_tx, message_rx) = bounded(1); //TODO: Maybe figure out a better method than making this bounded
|
let _playback_monitor = std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
|
||||||
std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
|
let mut stats = PlaybackStats::Idle;
|
||||||
|
let mut pos_temp;
|
||||||
loop {
|
loop {
|
||||||
let mut pos_temp = playbin_arc
|
match stat_rx.recv_timeout(std::time::Duration::from_millis(100)) {
|
||||||
|
Ok(res) => stats = res,
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
pos_temp = playbin_arc
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.query_position::<ClockTime>()
|
.query_position::<ClockTime>()
|
||||||
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
||||||
|
|
||||||
if pos_temp.is_some()
|
match stats {
|
||||||
&& start_update.read().unwrap().is_some()
|
PlaybackStats::Playing{start, end} if pos_temp.is_some() => {
|
||||||
&& end_update.read().unwrap().is_some()
|
// Check if the current playback position is close to the end
|
||||||
{
|
let finish_point = end - Duration::milliseconds(250);
|
||||||
let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(250);
|
if pos_temp.unwrap() >= end {
|
||||||
if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
|
let _ = playback_tx.try_send(PlayerCmd::Eos);
|
||||||
let _ = message_tx.try_send(PlayerCmd::Eos);
|
playbin_arc
|
||||||
playbin_arc
|
.write()
|
||||||
.write()
|
.unwrap()
|
||||||
.unwrap()
|
.set_state(gst::State::Ready)
|
||||||
.set_state(gst::State::Ready)
|
.expect("Unable to set the pipeline state");
|
||||||
.expect("Unable to set the pipeline state");
|
} else if pos_temp.unwrap() >= finish_point {
|
||||||
*start_update.write().unwrap() = None;
|
let _ = playback_tx.try_send(PlayerCmd::AboutToFinish);
|
||||||
*end_update.write().unwrap() = None;
|
}
|
||||||
} else if pos_temp.unwrap() >= atf {
|
|
||||||
let _ = message_tx.try_send(PlayerCmd::AboutToFinish);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This has to be done AFTER the current time in the file
|
// This has to be done AFTER the current time in the file
|
||||||
// is calculated, or everything else is wrong
|
// is calculated, or everything else is wrong
|
||||||
if let Some(time) = *start_update.read().unwrap() {
|
pos_temp = Some(pos_temp.unwrap() - start)
|
||||||
pos_temp = Some(pos_temp.unwrap() - time)
|
|
||||||
}
|
}
|
||||||
|
_ => println!("waiting!")
|
||||||
}
|
}
|
||||||
|
|
||||||
//println!("{:?}", pos_temp);
|
|
||||||
|
|
||||||
*position_update.write().unwrap() = pos_temp;
|
*position_update.write().unwrap() = pos_temp;
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up the thread to monitor bus messages
|
// Set up the thread to monitor bus messages
|
||||||
let playbin_bus_ctrl = Arc::clone(&playbin);
|
let playbin_bus_ctrl = Arc::clone(&playbin);
|
||||||
let buffer_bus_ctrl = Arc::clone(&buffer);
|
|
||||||
let paused_bus_ctrl = Arc::clone(&paused);
|
|
||||||
let bus_watch = playbin
|
let bus_watch = playbin
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -203,17 +203,16 @@ impl Player {
|
||||||
.set_state(gst::State::Playing)
|
.set_state(gst::State::Playing)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
/* TODO: Fix buffering!!
|
||||||
gst::MessageView::Buffering(buffering) => {
|
gst::MessageView::Buffering(buffering) => {
|
||||||
let percent = buffering.percent();
|
let percent = buffering.percent();
|
||||||
if percent < 100 {
|
if percent < 100 {
|
||||||
*buffer_bus_ctrl.write().unwrap() = Some(percent as u8);
|
|
||||||
playbin_bus_ctrl
|
playbin_bus_ctrl
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_state(gst::State::Paused)
|
.set_state(gst::State::Paused)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else if !(*paused_bus_ctrl.read().unwrap()) {
|
} else if !(buffering) {
|
||||||
*buffer_bus_ctrl.write().unwrap() = None;
|
|
||||||
playbin_bus_ctrl
|
playbin_bus_ctrl
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -221,6 +220,7 @@ impl Player {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
glib::ControlFlow::Continue
|
glib::ControlFlow::Continue
|
||||||
|
@ -237,13 +237,13 @@ impl Player {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
source,
|
source,
|
||||||
playbin,
|
playbin,
|
||||||
message_rx,
|
message_rx: playback_rx,
|
||||||
|
playback_tx: stat_tx,
|
||||||
volume: 1.0,
|
volume: 1.0,
|
||||||
start,
|
start: None,
|
||||||
end,
|
end: None,
|
||||||
paused,
|
paused: false,
|
||||||
position,
|
position,
|
||||||
buffer,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,6 +257,9 @@ impl Player {
|
||||||
|
|
||||||
/// Set the playback URI
|
/// Set the playback URI
|
||||||
fn set_source(&mut self, source: &URI) {
|
fn set_source(&mut self, source: &URI) {
|
||||||
|
// Make sure the playback tracker knows the stuff is stopped
|
||||||
|
self.playback_tx.send(PlaybackStats::Switching).unwrap();
|
||||||
|
|
||||||
let uri = self.playbin.read().unwrap().property_value("current-uri");
|
let uri = self.playbin.read().unwrap().property_value("current-uri");
|
||||||
self.source = Some(source.clone());
|
self.source = Some(source.clone());
|
||||||
match source {
|
match source {
|
||||||
|
@ -267,12 +270,17 @@ impl Player {
|
||||||
.set_property("uri", source.as_uri());
|
.set_property("uri", source.as_uri());
|
||||||
|
|
||||||
// Set the start and end positions of the CUE file
|
// Set the start and end positions of the CUE file
|
||||||
*self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
|
self.start = Some(Duration::from_std(*start).unwrap());
|
||||||
*self.end.write().unwrap() = Some(Duration::from_std(*end).unwrap());
|
self.end = Some(Duration::from_std(*end).unwrap());
|
||||||
|
|
||||||
self.play().unwrap();
|
// Send the updated position to the tracker
|
||||||
|
self.playback_tx.send(PlaybackStats::Playing{
|
||||||
|
start: self.start.unwrap(),
|
||||||
|
end: self.end.unwrap()
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
// Wait for it to be ready, and then move to the proper position
|
// Wait for it to be ready, and then move to the proper position
|
||||||
|
self.play().unwrap();
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
while now.elapsed() < std::time::Duration::from_millis(20) {
|
while now.elapsed() < std::time::Duration::from_millis(20) {
|
||||||
if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
|
if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
|
||||||
|
@ -297,8 +305,14 @@ impl Player {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.start.write().unwrap() = Some(Duration::seconds(0));
|
self.start = Some(Duration::seconds(0));
|
||||||
*self.end.write().unwrap() = self.raw_duration();
|
self.end = self.raw_duration();
|
||||||
|
|
||||||
|
// Send the updated position to the tracker
|
||||||
|
self.playback_tx.send(PlaybackStats::Playing{
|
||||||
|
start: self.start.unwrap(),
|
||||||
|
end: self.end.unwrap()
|
||||||
|
}).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,13 +375,13 @@ impl Player {
|
||||||
|
|
||||||
/// Pause, if playing
|
/// Pause, if playing
|
||||||
pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
|
pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
|
||||||
*self.paused.write().unwrap() = true;
|
//*self.paused.write().unwrap() = true;
|
||||||
self.set_state(gst::State::Paused)
|
self.set_state(gst::State::Paused)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resume from being paused
|
/// Resume from being paused
|
||||||
pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
|
pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
|
||||||
*self.paused.write().unwrap() = false;
|
//*self.paused.write().unwrap() = false;
|
||||||
self.set_state(gst::State::Playing)
|
self.set_state(gst::State::Playing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,8 +397,8 @@ impl Player {
|
||||||
|
|
||||||
/// Get the duration of the currently playing track
|
/// Get the duration of the currently playing track
|
||||||
pub fn duration(&mut self) -> Option<Duration> {
|
pub fn duration(&mut self) -> Option<Duration> {
|
||||||
if self.end.read().unwrap().is_some() && self.start.read().unwrap().is_some() {
|
if self.end.is_some() && self.start.is_some() {
|
||||||
Some(self.end.read().unwrap().unwrap() - self.start.read().unwrap().unwrap())
|
Some(self.end.unwrap() - self.start.unwrap())
|
||||||
} else {
|
} else {
|
||||||
self.raw_duration()
|
self.raw_duration()
|
||||||
}
|
}
|
||||||
|
@ -411,16 +425,16 @@ impl Player {
|
||||||
|
|
||||||
/// Seek absolutely
|
/// Seek absolutely
|
||||||
pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
|
pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
|
||||||
let start = if self.start.read().unwrap().is_none() {
|
let start = if self.start.is_none() {
|
||||||
return Err("Failed to seek: No START time".into());
|
return Err("Failed to seek: No START time".into());
|
||||||
} else {
|
} else {
|
||||||
self.start.read().unwrap().unwrap()
|
self.start.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let end = if self.end.read().unwrap().is_none() {
|
let end = if self.end.is_none() {
|
||||||
return Err("Failed to seek: No END time".into());
|
return Err("Failed to seek: No END time".into());
|
||||||
} else {
|
} else {
|
||||||
self.end.read().unwrap().unwrap()
|
self.end.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let adjusted_target = target_pos + start;
|
let adjusted_target = target_pos + start;
|
||||||
|
@ -439,10 +453,13 @@ impl Player {
|
||||||
|
|
||||||
/// Get the current state of the playback
|
/// Get the current state of the playback
|
||||||
pub fn state(&mut self) -> PlayerState {
|
pub fn state(&mut self) -> PlayerState {
|
||||||
|
self.playbin().unwrap().current_state().into()
|
||||||
|
/*
|
||||||
match *self.buffer.read().unwrap() {
|
match *self.buffer.read().unwrap() {
|
||||||
None => self.playbin().unwrap().current_state().into(),
|
None => self.playbin().unwrap().current_state().into(),
|
||||||
Some(value) => PlayerState::Buffering(value),
|
Some(value) => PlayerState::Buffering(value),
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn property(&self, property: &str) -> glib::Value {
|
pub fn property(&self, property: &str) -> glib::Value {
|
||||||
|
@ -454,10 +471,13 @@ impl Player {
|
||||||
self.pause()?;
|
self.pause()?;
|
||||||
self.ready()?;
|
self.ready()?;
|
||||||
|
|
||||||
|
// Send the updated position to the tracker
|
||||||
|
self.playback_tx.send(PlaybackStats::Idle).unwrap();
|
||||||
|
|
||||||
// Set all positions to none
|
// Set all positions to none
|
||||||
*self.position.write().unwrap() = None;
|
*self.position.write().unwrap() = None;
|
||||||
*self.start.write().unwrap() = None;
|
self.start = None;
|
||||||
*self.end.write().unwrap() = None;
|
self.end = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,7 +374,7 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes the database out to the file specified in the config
|
/// Serializes the database out to the file specified in the config
|
||||||
pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
|
pub fn save(&self, config: Config) -> Result<(), Box<dyn Error>> {
|
||||||
let path = config.libraries.get_library(&self.uuid)?.path;
|
let path = config.libraries.get_library(&self.uuid)?.path;
|
||||||
match path.try_exists() {
|
match path.try_exists() {
|
||||||
Ok(_) => write_file(self, path)?,
|
Ok(_) => write_file(self, path)?,
|
||||||
|
@ -389,6 +389,11 @@ impl MusicLibrary {
|
||||||
self.library.len()
|
self.library.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the library size in number of albums
|
||||||
|
pub fn len_albums(&self) -> usize {
|
||||||
|
self.albums().len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries for a [Song] by its [URI], returning a single `Song`
|
/// Queries for a [Song] by its [URI], returning a single `Song`
|
||||||
/// with the `URI` that matches along with its position in the library
|
/// with the `URI` that matches along with its position in the library
|
||||||
pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
|
pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
|
||||||
|
@ -641,7 +646,7 @@ impl MusicLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = match next_track.next() {
|
let duration = match next_track.next() {
|
||||||
Some(future) => match future.indices.get(0) {
|
Some(future) => match future.indices.first() {
|
||||||
Some(val) => val.1 - start,
|
Some(val) => val.1 - start,
|
||||||
None => Duration::from_secs(0),
|
None => Duration::from_secs(0),
|
||||||
},
|
},
|
||||||
|
@ -923,7 +928,7 @@ impl MusicLibrary {
|
||||||
},
|
},
|
||||||
// If the album is not in the list, make a new one and add it
|
// If the album is not in the list, make a new one and add it
|
||||||
None => {
|
None => {
|
||||||
let album_art = result.album_art.get(0);
|
let album_art = result.album_art.first();
|
||||||
|
|
||||||
let new_album = Album {
|
let new_album = Album {
|
||||||
title,
|
title,
|
||||||
|
|
Loading…
Reference in a new issue