mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 17:42:55 -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
|
// Crate things
|
||||||
use crate::music_controller::config::Config;
|
//use crate::music_controller::config::Config;
|
||||||
use crate::music_storage::music_db::URI;
|
use crate::music_storage::music_db::URI;
|
||||||
use std::error::Error;
|
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
|
// GStreamer things
|
||||||
|
use glib::{FlagsClass, MainContext};
|
||||||
use gst::{ClockTime, Element};
|
use gst::{ClockTime, Element};
|
||||||
use gstreamer as gst;
|
use gstreamer as gst;
|
||||||
use gstreamer::prelude::*;
|
use gstreamer::prelude::*;
|
||||||
use glib::FlagsClass;
|
|
||||||
|
|
||||||
// Time things
|
// Time things
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
|
|
||||||
enum PlayerCmd {
|
#[derive(Debug)]
|
||||||
|
pub enum PlayerCmd {
|
||||||
Play,
|
Play,
|
||||||
|
Pause,
|
||||||
|
Eos,
|
||||||
|
AboutToFinish,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>,
|
||||||
events: Sender<PlayerCmd>,
|
//pub message_tx: Sender<PlayerCmd>,
|
||||||
|
pub message_rx: Receiver<PlayerCmd>,
|
||||||
playbin: Element,
|
playbin: Element,
|
||||||
position: Duration,
|
|
||||||
duration: Duration,
|
|
||||||
paused: bool,
|
paused: bool,
|
||||||
volume: f64,
|
volume: f64,
|
||||||
|
start: Option<Duration>,
|
||||||
|
end: Option<Duration>,
|
||||||
|
position: Option<Duration>,
|
||||||
gapless: bool,
|
gapless: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Player {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
// Initialize GStreamer
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
|
|
||||||
let playbin = gst::ElementFactory::make("playbin")
|
let playbin = gst::ElementFactory::make("playbin").build().unwrap();
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let flags = playbin.property_value("flags");
|
let flags = playbin.property_value("flags");
|
||||||
let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
|
let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
|
||||||
|
|
||||||
|
// Set up the Playbin flags to only play audio
|
||||||
let flags = flags_class
|
let flags = flags_class
|
||||||
.builder_with_value(flags)
|
.builder_with_value(flags)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -58,32 +58,55 @@ impl Player {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
playbin.set_property_from_value("flags", &flags);
|
playbin.set_property_from_value("flags", &flags);
|
||||||
|
|
||||||
playbin
|
|
||||||
.bus()
|
let (message_tx, message_rx) = std::sync::mpsc::channel();
|
||||||
.expect("Failed to get GStreamer message bus");
|
playbin.connect("about-to-finish", false, move |_| {
|
||||||
|
println!("test");
|
||||||
|
message_tx.send(PlayerCmd::AboutToFinish).unwrap();
|
||||||
|
None
|
||||||
|
});
|
||||||
|
|
||||||
let source = None;
|
let source = None;
|
||||||
let (tx, _): (Sender<PlayerCmd>, Receiver<PlayerCmd>) = mpsc::channel();
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
events: tx,
|
|
||||||
playbin,
|
playbin,
|
||||||
|
message_rx,
|
||||||
paused: false,
|
paused: false,
|
||||||
volume: 0.5,
|
volume: 0.5,
|
||||||
gapless: false,
|
gapless: false,
|
||||||
position: Duration::seconds(0),
|
start: None,
|
||||||
duration: Duration::seconds(0),
|
end: None,
|
||||||
|
position: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn source(&self) -> &Option<URI> {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
|
|
||||||
pub fn enqueue_next(&mut self, next_track: URI) {
|
pub fn enqueue_next(&mut self, next_track: URI) {
|
||||||
self.set_state(gst::State::Ready);
|
self.set_state(gst::State::Ready);
|
||||||
|
|
||||||
self.playbin.set_property("uri", next_track.as_uri());
|
self.set_source(next_track);
|
||||||
|
|
||||||
self.play();
|
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
|
/// Set the playback volume, accepts a float from 0 to 1
|
||||||
pub fn set_volume(&mut self, volume: f64) {
|
pub fn set_volume(&mut self, volume: f64) {
|
||||||
|
@ -130,27 +153,29 @@ impl Player {
|
||||||
self.playbin.current_state() == gst::State::Paused
|
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
|
/// Get the current playback position of the player
|
||||||
pub fn position(&mut self) -> Option<Duration> {
|
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
|
/// Get the duration of the currently playing track
|
||||||
pub fn duration(&mut self) -> Option<Duration> {
|
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
|
/// Seek relative to the current position
|
||||||
pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
|
pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
|
||||||
let time_pos = match self.position() {
|
let time_pos = match self.position() {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
None => return Err("No position".into())
|
None => return Err("No position".into()),
|
||||||
};
|
};
|
||||||
let seek_pos = time_pos + seek_amount;
|
let seek_pos = time_pos + seek_amount;
|
||||||
|
|
||||||
|
@ -159,19 +184,25 @@ impl Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seek absolutely
|
/// Seek absolutely
|
||||||
pub fn seek_to(&mut self, last_pos: Duration) -> Result<(), Box<dyn Error>> {
|
pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
|
||||||
let duration = match self.duration() {
|
let seek_pos_clock = ClockTime::from_useconds(target_pos.num_microseconds().unwrap() as u64);
|
||||||
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);
|
|
||||||
self.set_gstreamer_volume(0.0);
|
self.set_gstreamer_volume(0.0);
|
||||||
self
|
self.playbin
|
||||||
.playbin
|
|
||||||
.seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
|
.seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
|
||||||
self.set_gstreamer_volume(self.volume);
|
self.set_gstreamer_volume(self.volume);
|
||||||
Ok(())
|
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