Be able to add tracks, start, stop, and pause playback from the web api
This commit is contained in:
parent
cbe1a90fcb
commit
7da7ffcaa5
|
@ -11,7 +11,9 @@ use tokio::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
audio_control::AudioControl,
|
audio_control::AudioControl,
|
||||||
types::{AppError, AudioControlMessage, AudioStatusMessage, TrackInfo, TrackSpec, Volume},
|
types::{
|
||||||
|
AppError, AudioControlMessage, AudioState, AudioStatusMessage, TrackInfo, TrackSpec, Volume,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -58,18 +60,21 @@ impl App {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
async move {
|
async move {
|
||||||
println!("listener started");
|
println!("listener started");
|
||||||
loop {
|
while let Some(msg) = audio_status.recv().await {
|
||||||
match audio_status.recv().await {
|
match msg {
|
||||||
Some(AudioStatusMessage::Playing) => {
|
AudioStatusMessage::Playing => {
|
||||||
state.write().unwrap().playing = true;
|
state.write().unwrap().playing = true;
|
||||||
}
|
}
|
||||||
Some(AudioStatusMessage::Status(track_status)) => {
|
AudioStatusMessage::Status(AudioState {
|
||||||
println!("status: {:?}", track_status);
|
playing: _playing,
|
||||||
state.write().unwrap().track_status = track_status;
|
tracks,
|
||||||
|
}) => {
|
||||||
|
state.write().unwrap().track_status = tracks;
|
||||||
}
|
}
|
||||||
msg => println!("message received from audio controller: {:?}", msg),
|
msg => println!("message received from audio controller: {:?}", msg),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println!("listener exiting");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,6 +110,7 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn enable_track(&self, path: PathBuf) -> Result<(), AppError> {
|
pub async fn enable_track(&self, path: PathBuf) -> Result<(), AppError> {
|
||||||
|
println!("enabling track: {}", path.display());
|
||||||
self.audio_control
|
self.audio_control
|
||||||
.send(AudioControlMessage::EnableTrack(TrackSpec {
|
.send(AudioControlMessage::EnableTrack(TrackSpec {
|
||||||
path,
|
path,
|
||||||
|
@ -132,18 +138,26 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn play(&self) -> Result<(), AppError> {
|
pub async fn play(&self) -> Result<(), AppError> {
|
||||||
/*
|
self.audio_control
|
||||||
let st = self.internal.write().unwrap();
|
.send(AudioControlMessage::Play)
|
||||||
st.audio_control.play()?;
|
.await
|
||||||
*/
|
.expect("audio control send to succeed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop(&self) -> Result<(), AppError> {
|
pub async fn stop(&self) -> Result<(), AppError> {
|
||||||
/*
|
self.audio_control
|
||||||
let st = self.internal.write().unwrap();
|
.send(AudioControlMessage::Stop)
|
||||||
st.audio_control.stop()?;
|
.await
|
||||||
*/
|
.expect("audio control send to succeed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pause(&self) -> Result<(), AppError> {
|
||||||
|
self.audio_control
|
||||||
|
.send(AudioControlMessage::Pause)
|
||||||
|
.await
|
||||||
|
.expect("audio control send to succeed");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use gstreamer::{prelude::*, ClockTime, MessageType, MessageView};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
use crate::types::{AudioControlMessage, AudioStatusMessage, TrackSpec};
|
use crate::types::{AudioControlMessage, AudioState, AudioStatusMessage, Progress, TrackInfo, TrackSpec};
|
||||||
|
|
||||||
#[derive(Debug, Error, PartialEq)]
|
#[derive(Debug, Error, PartialEq)]
|
||||||
pub enum AudioError {
|
pub enum AudioError {
|
||||||
|
@ -20,33 +20,32 @@ pub enum AudioError {
|
||||||
InvalidState,
|
InvalidState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioControl {}
|
pub struct AudioControl {
|
||||||
|
backend: Box<dyn AudioControlBackend>,
|
||||||
/*
|
|
||||||
impl Default for AudioControl {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(GStreamerBackend::default())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
impl AudioControl {
|
impl AudioControl {
|
||||||
pub fn new() -> Self {
|
pub fn new(backend: impl AudioControlBackend + 'static) -> Self {
|
||||||
Self {}
|
Self {
|
||||||
|
backend: Box::new(backend),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen(&self, mut control_rx: Receiver<AudioControlMessage>) {
|
pub async fn listen(&self, mut control_rx: Receiver<AudioControlMessage>) {
|
||||||
println!("waiting for control message");
|
|
||||||
while let Some(msg) = control_rx.recv().await {
|
while let Some(msg) = control_rx.recv().await {
|
||||||
|
println!("control message: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
AudioControlMessage::Play => {
|
AudioControlMessage::Play => {
|
||||||
unimplemented!()
|
self.backend.play().unwrap();
|
||||||
|
}
|
||||||
|
AudioControlMessage::Stop => {
|
||||||
|
self.backend.stop().unwrap();
|
||||||
}
|
}
|
||||||
AudioControlMessage::Pause => {
|
AudioControlMessage::Pause => {
|
||||||
unimplemented!()
|
self.backend.pause().unwrap();
|
||||||
}
|
}
|
||||||
AudioControlMessage::EnableTrack(_) => {
|
AudioControlMessage::EnableTrack(spec) => {
|
||||||
unimplemented!()
|
self.backend.add_track(spec).unwrap();
|
||||||
}
|
}
|
||||||
AudioControlMessage::DisableTrack(_) => {
|
AudioControlMessage::DisableTrack(_) => {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -60,9 +59,11 @@ impl AudioControl {
|
||||||
|
|
||||||
pub async fn report(&self, status_tx: Sender<AudioStatusMessage>) {
|
pub async fn report(&self, status_tx: Sender<AudioStatusMessage>) {
|
||||||
loop {
|
loop {
|
||||||
println!("sending status message");
|
|
||||||
status_tx
|
status_tx
|
||||||
.send(AudioStatusMessage::Status(vec![]))
|
.send(AudioStatusMessage::Status(AudioState {
|
||||||
|
playing: self.backend.playing(),
|
||||||
|
tracks: self.backend.tracks()
|
||||||
|
}))
|
||||||
.await
|
.await
|
||||||
.expect("to successfully send a message");
|
.expect("to successfully send a message");
|
||||||
let _ = tokio::time::sleep(Duration::from_secs(1)).await;
|
let _ = tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
@ -91,21 +92,25 @@ impl AudioControl {
|
||||||
self.backend.write().unwrap().add_track(track)
|
self.backend.write().unwrap().add_track(track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
pub trait AudioControlBackend: Send + Sync {
|
pub trait AudioControlBackend: Send + Sync {
|
||||||
fn playing(&self) -> bool;
|
fn playing(&self) -> bool;
|
||||||
|
|
||||||
fn tracks(&self) -> Vec<TrackSpec>;
|
fn tracks(&self) -> Vec<TrackInfo>;
|
||||||
|
|
||||||
fn play(&self) -> Result<(), AudioError>;
|
fn play(&self) -> Result<(), AudioError>;
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), AudioError>;
|
fn stop(&self) -> Result<(), AudioError>;
|
||||||
|
|
||||||
fn add_track(&mut self, track: TrackSpec) -> Result<(), AudioError>;
|
fn pause(&self) -> Result<(), AudioError>;
|
||||||
|
|
||||||
fn remove_track(&mut self, track: TrackSpec) -> Result<(), AudioError>;
|
fn add_track(&self, track: TrackSpec) -> Result<(), AudioError>;
|
||||||
|
|
||||||
|
fn remove_track(&self, track: TrackSpec) -> Result<(), AudioError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
pub struct MemoryBackend {
|
pub struct MemoryBackend {
|
||||||
playing: Arc<RwLock<bool>>,
|
playing: Arc<RwLock<bool>>,
|
||||||
tracks: HashMap<PathBuf, TrackSpec>,
|
tracks: HashMap<PathBuf, TrackSpec>,
|
||||||
|
@ -170,6 +175,21 @@ impl AudioControlBackend for MemoryBackend {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct GStreamerBackendState {
|
||||||
|
playing: bool,
|
||||||
|
tracks: HashMap<PathBuf, TrackInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GStreamerBackendState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
playing: false,
|
||||||
|
tracks: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GStreamerBackend {
|
pub struct GStreamerBackend {
|
||||||
bus: gstreamer::Bus,
|
bus: gstreamer::Bus,
|
||||||
|
@ -178,7 +198,7 @@ pub struct GStreamerBackend {
|
||||||
audio_sink: gstreamer::Element,
|
audio_sink: gstreamer::Element,
|
||||||
monitor: std::thread::JoinHandle<()>,
|
monitor: std::thread::JoinHandle<()>,
|
||||||
|
|
||||||
playing: Arc<RwLock<bool>>,
|
state: Arc<RwLock<GStreamerBackendState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GStreamerBackend {
|
impl Default for GStreamerBackend {
|
||||||
|
@ -205,11 +225,11 @@ impl Default for GStreamerBackend {
|
||||||
pipeline.add(&audio_sink).unwrap();
|
pipeline.add(&audio_sink).unwrap();
|
||||||
mixer.link(&audio_sink).unwrap();
|
mixer.link(&audio_sink).unwrap();
|
||||||
|
|
||||||
let playing = Arc::new(RwLock::new(false));
|
let state = Arc::new(RwLock::new(GStreamerBackendState::default()));
|
||||||
|
|
||||||
let monitor = std::thread::spawn({
|
let monitor = std::thread::spawn({
|
||||||
let pipeline_object = pipeline.clone().upcast::<gstreamer::Object>();
|
let pipeline_object = pipeline.clone().upcast::<gstreamer::Object>();
|
||||||
let playing = playing.clone();
|
let state = state.clone();
|
||||||
let bus = bus.clone();
|
let bus = bus.clone();
|
||||||
move || loop {
|
move || loop {
|
||||||
if let Some(msg) = bus.timed_pop_filtered(
|
if let Some(msg) = bus.timed_pop_filtered(
|
||||||
|
@ -223,7 +243,7 @@ impl Default for GStreamerBackend {
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::StateChanged(st) => {
|
MessageView::StateChanged(st) => {
|
||||||
if msg.src() == Some(&pipeline_object) {
|
if msg.src() == Some(&pipeline_object) {
|
||||||
*playing.write().unwrap() =
|
state.write().unwrap().playing =
|
||||||
st.current() == gstreamer::State::Playing;
|
st.current() == gstreamer::State::Playing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,25 +269,23 @@ impl Default for GStreamerBackend {
|
||||||
|
|
||||||
monitor,
|
monitor,
|
||||||
|
|
||||||
playing: Arc::new(RwLock::new(false)),
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioControlBackend for GStreamerBackend {
|
impl AudioControlBackend for GStreamerBackend {
|
||||||
fn playing(&self) -> bool {
|
fn playing(&self) -> bool {
|
||||||
*self.playing.read().unwrap()
|
self.state.read().unwrap().playing
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tracks(&self) -> Vec<TrackSpec> {
|
fn tracks(&self) -> Vec<TrackInfo> {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play(&self) -> Result<(), AudioError> {
|
fn play(&self) -> Result<(), AudioError> {
|
||||||
let mut playing = self.playing.write().unwrap();
|
if !self.playing() {
|
||||||
if !*playing {
|
self.pipeline.set_state(gstreamer::State::Playing).unwrap();
|
||||||
// self.pipeline.set_state(gstreamer::State::Playing).unwrap();
|
|
||||||
*playing = true;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(AudioError::InvalidState)
|
Err(AudioError::InvalidState)
|
||||||
|
@ -275,17 +293,34 @@ impl AudioControlBackend for GStreamerBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), AudioError> {
|
fn stop(&self) -> Result<(), AudioError> {
|
||||||
let mut playing = self.playing.write().unwrap();
|
if self.playing() {
|
||||||
if *playing {
|
self.pipeline.set_state(gstreamer::State::Ready).unwrap();
|
||||||
// self.pipeline.set_state(gstreamer::State::Paused).unwrap();
|
|
||||||
*playing = false;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(AudioError::InvalidState)
|
Err(AudioError::InvalidState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_track(&mut self, track: TrackSpec) -> Result<(), AudioError> {
|
fn pause(&self) -> Result<(), AudioError> {
|
||||||
|
if self.playing() {
|
||||||
|
self.pipeline.set_state(gstreamer::State::Paused).unwrap();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(AudioError::InvalidState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_track(&self, track: TrackSpec) -> Result<(), AudioError> {
|
||||||
|
let mut st = self.state.write().unwrap();
|
||||||
|
st.tracks.insert(track.path.clone(), TrackInfo {
|
||||||
|
path: track.path.clone(),
|
||||||
|
volume: track.volume,
|
||||||
|
progress: Progress {
|
||||||
|
current: Duration::from_secs(0),
|
||||||
|
length: Duration::from_secs(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let source = gstreamer::ElementFactory::find("filesrc")
|
let source = gstreamer::ElementFactory::find("filesrc")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.load()
|
.load()
|
||||||
|
@ -312,7 +347,7 @@ impl AudioControlBackend for GStreamerBackend {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.create()
|
.create()
|
||||||
.property("mute", false)
|
.property("mute", false)
|
||||||
.property("volume", 0.75)
|
.property("volume", track.volume.as_f64())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.pipeline.add(&volume).unwrap();
|
self.pipeline.add(&volume).unwrap();
|
||||||
|
@ -326,9 +361,8 @@ impl AudioControlBackend for GStreamerBackend {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_track(&mut self, _path: TrackSpec) -> Result<(), AudioError> {
|
fn remove_track(&self, _path: TrackSpec) -> Result<(), AudioError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
/* Need to run EOS through to a probe on the trailing end of the volume element */
|
/* Need to run EOS through to a probe on the trailing end of the volume element */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::{
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
net::{Ipv6Addr, SocketAddrV6},
|
net::{Ipv6Addr, SocketAddrV6},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use audio_control::AudioControl;
|
use audio_control::{AudioControl, GStreamerBackend};
|
||||||
use pipewire::{context::Context, main_loop::MainLoop};
|
use pipewire::{context::Context, main_loop::MainLoop};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
@ -20,6 +21,10 @@ struct PlayTrackParams {
|
||||||
track_name: String,
|
track_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_app(app: Arc<App>) -> impl Filter<Extract = (Arc<App>,), Error = Infallible> + Clone {
|
||||||
|
warp::any().map(move || app.clone())
|
||||||
|
}
|
||||||
|
|
||||||
async fn server_main(app: Arc<App>) {
|
async fn server_main(app: Arc<App>) {
|
||||||
let localhost: Ipv6Addr = "::1".parse().unwrap();
|
let localhost: Ipv6Addr = "::1".parse().unwrap();
|
||||||
let server_addr = SocketAddrV6::new(localhost, 3001, 0, 0);
|
let server_addr = SocketAddrV6::new(localhost, 3001, 0, 0);
|
||||||
|
@ -43,37 +48,49 @@ async fn server_main(app: Arc<App>) {
|
||||||
let enable_track = warp::put()
|
let enable_track = warp::put()
|
||||||
.and(warp::path!("playing"))
|
.and(warp::path!("playing"))
|
||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
.map({
|
.and(with_app(app.clone()))
|
||||||
let app = app.clone();
|
.then(|params: PlayTrackParams, app: Arc<App>| async move {
|
||||||
move |params: PlayTrackParams| {
|
println!("enable track");
|
||||||
let _ = app.enable_track(PathBuf::from(params.track_name));
|
let _ = app.enable_track(PathBuf::from(params.track_name)).await;
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let disable_track = warp::delete()
|
let disable_track = warp::delete()
|
||||||
.and(warp::path!("playing"))
|
.and(warp::path!("playing"))
|
||||||
.and(warp::body::json())
|
.and(warp::body::json())
|
||||||
.map({
|
.and(with_app(app.clone()))
|
||||||
let app = app.clone();
|
.then(|params: PlayTrackParams, app: Arc<App>| async move {
|
||||||
move |params: PlayTrackParams| {
|
|
||||||
let _ = app.disable_track(¶ms.track_name);
|
let _ = app.disable_track(¶ms.track_name);
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let play_all = warp::put().and(warp::path!("playing")).map({
|
let play_all = warp::post()
|
||||||
let app = app.clone();
|
.and(warp::path!("play"))
|
||||||
move || {
|
.and(with_app(app.clone()))
|
||||||
let _ = app.play();
|
.then({
|
||||||
|
|app: Arc<App>| async move {
|
||||||
|
println!("play_all");
|
||||||
|
let _ = app.play().await;
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let stop_all = warp::delete().and(warp::path!("playing")).map({
|
let stop_all = warp::post()
|
||||||
let app = app.clone();
|
.and(warp::path!("stop"))
|
||||||
move || {
|
.and(with_app(app.clone()))
|
||||||
let _ = app.stop();
|
.then({
|
||||||
|
|app: Arc<App>| async move {
|
||||||
|
let _ = app.stop().await;
|
||||||
|
"".to_owned()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let pause = warp::post()
|
||||||
|
.and(warp::path!("pause"))
|
||||||
|
.and(with_app(app.clone()))
|
||||||
|
.then({
|
||||||
|
|app: Arc<App>| async move {
|
||||||
|
let _ = app.pause().await;
|
||||||
"".to_owned()
|
"".to_owned()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -90,6 +107,7 @@ async fn server_main(app: Arc<App>) {
|
||||||
.or(disable_track)
|
.or(disable_track)
|
||||||
.or(play_all)
|
.or(play_all)
|
||||||
.or(stop_all)
|
.or(stop_all)
|
||||||
|
.or(pause)
|
||||||
.or(now_playing);
|
.or(now_playing);
|
||||||
|
|
||||||
serve(routes).run(server_addr).await;
|
serve(routes).run(server_addr).await;
|
||||||
|
@ -142,7 +160,7 @@ async fn main() {
|
||||||
let (audio_status_tx, audio_status_rx) = tokio::sync::mpsc::channel(5);
|
let (audio_status_tx, audio_status_rx) = tokio::sync::mpsc::channel(5);
|
||||||
|
|
||||||
let app = Arc::new(App::new(audio_control_tx, audio_status_rx));
|
let app = Arc::new(App::new(audio_control_tx, audio_status_rx));
|
||||||
let audio_controller = Arc::new(AudioControl::new());
|
let audio_controller = Arc::new(AudioControl::new(GStreamerBackend::default()));
|
||||||
tokio::spawn({
|
tokio::spawn({
|
||||||
let audio_controller = audio_controller.clone();
|
let audio_controller = audio_controller.clone();
|
||||||
async move { audio_controller.listen(audio_control_rx).await }
|
async move { audio_controller.listen(audio_control_rx).await }
|
||||||
|
|
|
@ -43,6 +43,16 @@ pub enum VolumeError {
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||||
pub struct Volume(f32);
|
pub struct Volume(f32);
|
||||||
|
|
||||||
|
impl Volume {
|
||||||
|
pub fn as_f32(&self) -> f32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_f64(&self) -> f64 {
|
||||||
|
self.0.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<f32> for Volume {
|
impl TryFrom<f32> for Volume {
|
||||||
type Error = VolumeError;
|
type Error = VolumeError;
|
||||||
fn try_from(val: f32) -> Result<Self, Self::Error> {
|
fn try_from(val: f32) -> Result<Self, Self::Error> {
|
||||||
|
@ -64,6 +74,7 @@ impl From<Volume> for f32 {
|
||||||
pub enum AudioControlMessage {
|
pub enum AudioControlMessage {
|
||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
|
Stop,
|
||||||
EnableTrack(TrackSpec),
|
EnableTrack(TrackSpec),
|
||||||
DisableTrack(PathBuf),
|
DisableTrack(PathBuf),
|
||||||
ReportStatus,
|
ReportStatus,
|
||||||
|
@ -76,10 +87,16 @@ pub struct TrackInfo {
|
||||||
pub progress: Progress,
|
pub progress: Progress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AudioState {
|
||||||
|
pub playing: bool,
|
||||||
|
pub tracks: Vec<TrackInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AudioStatusMessage {
|
pub enum AudioStatusMessage {
|
||||||
Playing,
|
Playing,
|
||||||
Pausing,
|
Pausing,
|
||||||
Status(Vec<TrackInfo>),
|
Status(AudioState),
|
||||||
AudioError(AudioError),
|
AudioError(AudioError),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue