diff --git a/src/music_player.rs b/src/music_player.rs index 6ec9670..5fbe4d3 100644 --- a/src/music_player.rs +++ b/src/music_player.rs @@ -1,52 +1,52 @@ // Crate things -use crate::music_controller::config::Config; +//use crate::music_controller::config::Config; use crate::music_storage::music_db::URI; use std::error::Error; -use std::sync::mpsc::{Sender, self, Receiver}; +use std::sync::{Arc, Mutex}; +use std::sync::mpsc::{self, Receiver, Sender}; // GStreamer things +use glib::{FlagsClass, MainContext}; use gst::{ClockTime, Element}; use gstreamer as gst; use gstreamer::prelude::*; -use glib::FlagsClass; // Time things use chrono::Duration; -enum PlayerCmd { +#[derive(Debug)] +pub enum PlayerCmd { Play, + Pause, + Eos, + AboutToFinish, } /// An instance of a music player with a GStreamer backend pub struct Player { source: Option, - events: Sender, + //pub message_tx: Sender, + pub message_rx: Receiver, playbin: Element, - position: Duration, - duration: Duration, paused: bool, volume: f64, + start: Option, + end: Option, + position: Option, gapless: bool, } -impl Default for Player { - fn default() -> Self { - Self::new() - } -} - - impl Player { pub fn new() -> Self { + // Initialize GStreamer gst::init().unwrap(); - let playbin = gst::ElementFactory::make("playbin") - .build() - .unwrap(); + let playbin = gst::ElementFactory::make("playbin").build().unwrap(); let flags = playbin.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) .unwrap() @@ -58,32 +58,55 @@ impl Player { .unwrap(); playbin.set_property_from_value("flags", &flags); - playbin - .bus() - .expect("Failed to get GStreamer message bus"); + + let (message_tx, message_rx) = std::sync::mpsc::channel(); + playbin.connect("about-to-finish", false, move |_| { + println!("test"); + message_tx.send(PlayerCmd::AboutToFinish).unwrap(); + None + }); let source = None; - let (tx, _): (Sender, Receiver) = mpsc::channel(); Self { source, - events: tx, playbin, + message_rx, paused: false, volume: 0.5, gapless: false, - position: Duration::seconds(0), - duration: Duration::seconds(0), + start: None, + end: None, + position: None, } } + pub fn source(&self) -> &Option { + &self.source + } + pub fn enqueue_next(&mut self, next_track: URI) { self.set_state(gst::State::Ready); - self.playbin.set_property("uri", next_track.as_uri()); + self.set_source(next_track); self.play(); } + /// Set the playback URI + pub fn set_source(&mut self, source: URI) { + self.source = Some(source.clone()); + match source { + URI::Cue {start, ..} => { + self.playbin.set_property("uri", source.as_uri()); + self.play(); + while self.state() != gst::State::Playing { + std::thread::sleep(std::time::Duration::from_millis(10)); + }; + self.seek_to(Duration::from_std(start).unwrap()).unwrap(); + } + _ => self.playbin.set_property("uri", source.as_uri()), + } + } /// Set the playback volume, accepts a float from 0 to 1 pub fn set_volume(&mut self, volume: f64) { @@ -130,27 +153,29 @@ impl Player { self.playbin.current_state() == gst::State::Paused } - /// Set the playback URI - pub fn set_source(&mut self, source: URI) { - self.source = Some(source.clone()); - self.playbin.set_property("uri", source.as_uri()) - } - /// Get the current playback position of the player pub fn position(&mut self) -> Option { - self.playbin.query_position::().map(|pos| Duration::nanoseconds(pos.nseconds() as i64)) + self.position = self + .playbin + .query_position::() + .map(|pos| Duration::nanoseconds(pos.nseconds() as i64)); + self.position } /// Get the duration of the currently playing track pub fn duration(&mut self) -> Option { - self.playbin.query_duration::().map(|pos| Duration::milliseconds(pos.mseconds() as i64)) + if self.end.is_some() && self.start.is_some() { + Some(self.end.unwrap() - self.start.unwrap()) + } else { + None + } } /// Seek relative to the current position pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box> { let time_pos = match self.position() { Some(pos) => pos, - None => return Err("No position".into()) + None => return Err("No position".into()), }; let seek_pos = time_pos + seek_amount; @@ -159,19 +184,25 @@ impl Player { } /// Seek absolutely - pub fn seek_to(&mut self, last_pos: Duration) -> Result<(), Box> { - let duration = match self.duration() { - Some(dur) => dur, - None => return Err("No duration".into()) - }; - let seek_pos = last_pos.clamp(Duration::seconds(0), duration); - - let seek_pos_clock = ClockTime::from_mseconds(seek_pos.num_milliseconds() as u64); + pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box> { + let seek_pos_clock = ClockTime::from_useconds(target_pos.num_microseconds().unwrap() as u64); self.set_gstreamer_volume(0.0); - self - .playbin + self.playbin .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?; self.set_gstreamer_volume(self.volume); Ok(()) } + + pub fn state(&mut self) -> gst::State { + self.playbin.current_state() + } +} + +impl Drop for Player { + /// Cleans up `GStreamer` pipeline when `Backend` is dropped. + fn drop(&mut self) { + self.playbin + .set_state(gst::State::Null) + .expect("Unable to set the pipeline to the `Null` state"); + } }