mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 13:22:54 -05:00
Gapless playback now possible, can play CUE tracks
This commit is contained in:
parent
8071a90dfe
commit
7dbed859d4
1 changed files with 75 additions and 44 deletions
|
@ -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<URI>,
|
||||
events: Sender<PlayerCmd>,
|
||||
//pub message_tx: Sender<PlayerCmd>,
|
||||
pub message_rx: Receiver<PlayerCmd>,
|
||||
playbin: Element,
|
||||
position: Duration,
|
||||
duration: Duration,
|
||||
paused: bool,
|
||||
volume: f64,
|
||||
start: Option<Duration>,
|
||||
end: Option<Duration>,
|
||||
position: Option<Duration>,
|
||||
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<PlayerCmd>, Receiver<PlayerCmd>) = 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<URI> {
|
||||
&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<Duration> {
|
||||
self.playbin.query_position::<ClockTime>().map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
|
||||
self.position = self
|
||||
.playbin
|
||||
.query_position::<ClockTime>()
|
||||
.map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
|
||||
self.position
|
||||
}
|
||||
|
||||
/// Get the duration of the currently playing track
|
||||
pub fn duration(&mut self) -> Option<Duration> {
|
||||
self.playbin.query_duration::<ClockTime>().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<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
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<dyn Error>> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue