Gapless playback now possible, can play CUE tracks

This commit is contained in:
G2-Games 2023-12-02 02:20:58 -06:00
parent 8071a90dfe
commit 7dbed859d4

View file

@ -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");
}
}