use anyhow::{anyhow, bail, Result};
use gst::ClockTime;
use gstreamer as gst;
use gstreamer::prelude::*;
use gstreamer_pbutils as gst_pbutils;
use gstreamer_player as gst_player;
#[cfg(feature = "mpris")]
use crate::song::Song;
#[cfg(feature = "mpris")]
use crate::souvlaki::{
MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback, PlatformConfig,
};
#[cfg(feature = "mpris")]
use std::str::FromStr;
#[cfg(feature = "mpris")]
use std::sync::mpsc::{self, Receiver};
pub struct GStreamer {
player: gst_player::Player,
paused: bool,
volume: i32,
#[cfg(feature = "mpris")]
controls: MediaControls,
#[cfg(feature = "mpris")]
pub rx: Receiver<MediaControlEvent>,
}
impl GStreamer {
pub fn new() -> Self {
gst::init().expect("Couldn't initialize Gstreamer");
let dispatcher = gst_player::PlayerGMainContextSignalDispatcher::new(None);
let player = gst_player::Player::new(
None,
Some(&dispatcher.upcast::<gst_player::PlayerSignalDispatcher>()),
);
#[cfg(feature = "mpris")]
let config = PlatformConfig {
dbus_name: "termusic",
display_name: "Termuisc in Rust",
};
#[cfg(feature = "mpris")]
let mut controls = MediaControls::new(config);
#[cfg(feature = "mpris")]
let (tx, rx) = mpsc::sync_channel(32);
#[cfg(feature = "mpris")]
controls
.attach(move |event: MediaControlEvent| {
tx.send(event).ok();
})
.unwrap();
Self {
player,
paused: false,
volume: 50,
#[cfg(feature = "mpris")]
controls,
#[cfg(feature = "mpris")]
rx,
}
}
pub fn duration(song: &str) -> ClockTime {
let timeout: ClockTime = ClockTime::from_seconds(1);
let mut duration = ClockTime::from_seconds(0);
if let Ok(discoverer) = gst_pbutils::Discoverer::new(timeout) {
if let Ok(info) = discoverer.discover_uri(&format!("file:///{}", song)) {
if let Some(d) = info.duration() {
duration = d;
}
}
}
duration
}
pub fn add_and_play(&mut self, song_str: &str) {
self.player.set_uri(&format!("file:///{}", song_str));
self.paused = false;
self.player.play();
#[cfg(feature = "mpris")]
if let Ok(song) = Song::from_str(song_str) {
self.controls.set_metadata(MediaMetadata {
title: Some(song.title().unwrap_or("Unknown Title")),
artist: Some(song.artist().unwrap_or("Unknown Artist")),
album: Some(song.album().unwrap_or("")),
..MediaMetadata::default()
});
}
#[cfg(feature = "mpris")]
self.controls
.set_playback(MediaPlayback::Playing { progress: None })
.ok();
}
pub fn volume_up(&mut self) {
self.volume += 5;
if self.volume > 100 {
self.volume = 100;
}
self.player.set_volume(f64::from(self.volume) / 100.0);
}
pub fn volume_down(&mut self) {
self.volume -= 5;
if self.volume < 0 {
self.volume = 0;
}
self.player.set_volume(f64::from(self.volume) / 100.0);
}
pub const fn volume(&self) -> i32 {
self.volume
}
pub fn set_volume(&mut self, mut volume: i32) {
if volume > 100 {
volume = 100;
} else if volume < 0 {
volume = 0;
}
self.volume = volume;
self.player.set_volume(f64::from(volume) / 100.0);
}
pub fn pause(&mut self) {
self.paused = true;
self.player.pause();
#[cfg(feature = "mpris")]
self.controls
.set_playback(MediaPlayback::Paused { progress: None })
.ok();
}
pub fn resume(&mut self) {
self.paused = false;
self.player.play();
#[cfg(feature = "mpris")]
self.controls
.set_playback(MediaPlayback::Playing { progress: None })
.ok();
}
pub fn is_paused(&mut self) -> bool {
self.paused
}
pub fn seek(&mut self, secs: i64) -> Result<()> {
if let Ok((_, time_pos, duration)) = self.get_progress() {
let seek_pos: u64;
if secs >= 0 {
seek_pos = time_pos + secs.abs() as u64;
} else if time_pos > secs.abs() as u64 {
seek_pos = time_pos - secs.abs() as u64;
} else {
seek_pos = 0;
}
if seek_pos.cmp(&duration) == std::cmp::Ordering::Greater {
bail! {"exceed max length"};
}
self.player.seek(ClockTime::from_seconds(seek_pos as u64));
}
Ok(())
}
#[allow(clippy::cast_precision_loss)]
pub fn get_progress(&mut self) -> Result<(f64, u64, u64)> {
let time_pos = match self.player.position() {
Some(t) => ClockTime::seconds(t),
None => 0_u64,
};
let duration = match self.player.duration() {
Some(d) => ClockTime::seconds(d),
None => 119_u64,
};
let percent = time_pos as f64 / duration as f64;
if percent.is_nan() {
return Err(anyhow!("Divide error"));
}
Ok((percent, time_pos, duration))
}
}