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