264 lines
6.9 KiB
Rust
264 lines
6.9 KiB
Rust
/*
|
|
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
|
|
|
|
This file is part of the Luminescent Dreams Tools.
|
|
|
|
Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
|
|
Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use serde::{Serialize, Serializer};
|
|
use std::time::Duration;
|
|
use thiserror::Error;
|
|
|
|
pub enum Message {
|
|
Quit,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Event {
|
|
Paused(TrackId, Duration),
|
|
Playing(TrackId, Duration),
|
|
Stopped,
|
|
Position(TrackId, Duration),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DeviceInformation {
|
|
pub address: String,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum AudioError {
|
|
#[error("DBus device was not found")]
|
|
DeviceNotFound,
|
|
|
|
#[error("DBus connection lost or otherwise failed")]
|
|
ConnectionLost,
|
|
|
|
#[error("Specified media cannot be found")]
|
|
MediaNotFound,
|
|
|
|
#[error("Play was ordered, but nothing is in the queue")]
|
|
NothingInQueue,
|
|
|
|
#[error("url parse error {0}")]
|
|
UrlError(url::ParseError),
|
|
}
|
|
|
|
impl From<url::ParseError> for AudioError {
|
|
fn from(err: url::ParseError) -> Self {
|
|
Self::UrlError(err)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize)]
|
|
pub struct TrackId(String);
|
|
|
|
impl TrackId {
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl Default for TrackId {
|
|
fn default() -> Self {
|
|
Self(uuid::Uuid::new_v4().as_hyphenated().to_string())
|
|
}
|
|
}
|
|
|
|
impl From<String> for TrackId {
|
|
fn from(id: String) -> Self {
|
|
Self(id)
|
|
}
|
|
}
|
|
|
|
impl AsRef<String> for TrackId {
|
|
fn as_ref<'a>(&'a self) -> &'a String {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
|
pub struct TrackInfo {
|
|
pub id: TrackId,
|
|
pub track_number: Option<i32>,
|
|
pub name: Option<String>,
|
|
pub album: Option<String>,
|
|
pub artist: Option<String>,
|
|
pub duration: Option<u32>,
|
|
#[serde(serialize_with = "serialize_mime")]
|
|
pub filetype: mime::Mime,
|
|
}
|
|
|
|
fn serialize_mime<S>(val: &mime::Mime, s: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
s.serialize_str(val.essence_str())
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub enum State {
|
|
Playing(TrackInfo),
|
|
Paused(TrackInfo),
|
|
Stopped,
|
|
}
|
|
|
|
/*
|
|
pub struct CurrentlyPlaying {
|
|
track: TrackInfo,
|
|
position: Duration,
|
|
}
|
|
*/
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Capabilities {
|
|
pub can_control: bool,
|
|
pub can_pause: bool,
|
|
pub can_play: bool,
|
|
pub supports_track_lists: bool,
|
|
}
|
|
|
|
pub trait AudioPlayer {
|
|
fn capabilities(&self) -> Result<Capabilities, AudioError>;
|
|
fn state(&self) -> Result<State, AudioError>;
|
|
fn play(&self, trackid: url::Url) -> Result<State, AudioError>;
|
|
fn play_pause(&self) -> Result<State, AudioError>;
|
|
}
|
|
|
|
/*
|
|
pub struct GStreamerPlayer {
|
|
url: url::Url,
|
|
}
|
|
|
|
impl AudioPlayer for GStreamerPlayer {
|
|
fn capabilities(&self) -> Result<Capabilities, AudioError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn state(&self) -> Result<State, AudioError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn play(&self, trackid: url::Url) -> Result<State, AudioError> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn play_pause(&self) -> Result<State, AudioError> {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
pub struct MprisDevice {
|
|
device_id: String,
|
|
player: Player,
|
|
state: State,
|
|
}
|
|
|
|
impl MprisDevice {
|
|
pub fn new(device_id: String) -> Result<MprisDevice, AudioError> {
|
|
let connection = Connection::new_session()?;
|
|
Ok(MprisDevice {
|
|
device_id: device_id.clone(),
|
|
player: mpris::Player::new(connection, device_id, 1000)?,
|
|
state: State::Stopped,
|
|
})
|
|
}
|
|
|
|
pub fn monitor(&mut self, control_channel: Receiver<Message>) {
|
|
let (tx, rx) = channel();
|
|
{
|
|
let device_id = self.device_id.clone();
|
|
let tx = tx.clone();
|
|
thread::spawn(move || {
|
|
MprisDevice::new(device_id)
|
|
.expect("connect to bus")
|
|
.monitor_progress(tx);
|
|
});
|
|
};
|
|
|
|
loop {
|
|
match control_channel.try_recv() {
|
|
Ok(Message::Quit) => return,
|
|
Err(TryRecvError::Empty) => {}
|
|
Err(TryRecvError::Disconnected) => return,
|
|
}
|
|
let event = rx.recv().expect("receive should never fail");
|
|
println!("event received: {:?}", event);
|
|
}
|
|
}
|
|
|
|
pub fn monitor_progress(&self, tx: Sender<Event>) {
|
|
let mut tracker = self
|
|
.player
|
|
.track_progress(1000)
|
|
.expect("can get an event stream");
|
|
loop {
|
|
let ProgressTick { progress, .. } = tracker.tick();
|
|
match progress.playback_status() {
|
|
PlaybackStatus::Playing => {
|
|
tx.send(Event::Playing(
|
|
Track::from(progress.metadata()),
|
|
progress.position(),
|
|
))
|
|
.expect("send to succeed");
|
|
}
|
|
PlaybackStatus::Paused => {
|
|
tx.send(Event::Paused(
|
|
Track::from(progress.metadata()),
|
|
progress.position(),
|
|
))
|
|
.expect("send to succeed");
|
|
}
|
|
PlaybackStatus::Stopped => {
|
|
tx.send(Event::Stopped).expect("send to succeed");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AudioPlayer for MprisDevice {
|
|
fn capabilities(&self) -> Result<Capabilities, AudioError> {
|
|
Ok(Capabilities {
|
|
can_control: self.player.can_control()?,
|
|
can_pause: self.player.can_pause()?,
|
|
can_play: self.player.can_play()?,
|
|
supports_track_lists: self.player.supports_track_lists(),
|
|
})
|
|
}
|
|
|
|
fn state(&self) -> Result<State, AudioError> {
|
|
println!(
|
|
"supports track lists: {:?}",
|
|
self.player.supports_track_lists()
|
|
);
|
|
let metadata = self.player.get_metadata()?;
|
|
println!("{:?}", metadata);
|
|
unimplemented!("AudioPlayer state")
|
|
}
|
|
|
|
fn play(&self, track_id: String) -> Result<State, AudioError> {
|
|
println!("playing: {}", track_id);
|
|
self.player
|
|
.go_to(&mpris::TrackID::from(dbus::Path::from(track_id)))?;
|
|
self.player.play();
|
|
Ok(State::Stopped)
|
|
}
|
|
|
|
fn play_pause(&self) -> Result<State, AudioError> {
|
|
self.player.play_pause()?;
|
|
Ok(State::Stopped)
|
|
}
|
|
}
|
|
*/
|