Implement full async player API

This commit is contained in:
chmanie 2023-06-08 14:40:09 +02:00
parent 85d6d263e1
commit 983609e2f4
5 changed files with 253 additions and 103 deletions

1
Cargo.lock generated
View File

@ -180,6 +180,7 @@ dependencies = [
"stream-download", "stream-download",
"symphonia", "symphonia",
"thiserror", "thiserror",
"tracing",
"url", "url",
] ]

View File

@ -11,3 +11,4 @@ anyhow = "1.0.71"
url = "2.4.0" url = "2.4.0"
flume = "0.10.14" flume = "0.10.14"
thiserror = "1.0.40" thiserror = "1.0.40"
tracing = "0.1.37"

View File

@ -11,10 +11,11 @@ use symphonia::{
io::MediaSourceStream, io::MediaSourceStream,
meta::{MetadataOptions, MetadataRevision}, meta::{MetadataOptions, MetadataRevision},
probe::Hint, probe::Hint,
units::{self, Time, TimeBase}, units::{Time, TimeBase},
}, },
default::get_probe, default::get_probe,
}; };
use tracing::warn;
use rodio::Source; use rodio::Source;
@ -25,6 +26,7 @@ use crate::PlayerEngineCommand;
// But a decode error in more than 3 consecutive packets is fatal. // But a decode error in more than 3 consecutive packets is fatal.
const MAX_DECODE_ERRORS: usize = 3; const MAX_DECODE_ERRORS: usize = 3;
#[derive(Clone)]
pub struct MediaInfo { pub struct MediaInfo {
pub duration: Option<Duration>, pub duration: Option<Duration>,
pub metadata: Option<MetadataRevision>, pub metadata: Option<MetadataRevision>,
@ -97,7 +99,7 @@ impl SymphoniaDecoder {
.map(|frames| track.codec_params.start_ts + frames) .map(|frames| track.codec_params.start_ts + frames)
.unwrap_or_default(); .unwrap_or_default();
let mut elapsed = 0; let mut _elapsed = 0;
let mut decoder = symphonia::default::get_codecs() let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &DecoderOptions { verify: true })?; .make(&track.codec_params, &DecoderOptions { verify: true })?;
@ -105,7 +107,7 @@ impl SymphoniaDecoder {
let mut decode_errors: usize = 0; let mut decode_errors: usize = 0;
let decoded = loop { let decoded = loop {
let current_frame = probed.format.next_packet()?; let current_frame = probed.format.next_packet()?;
elapsed = current_frame.ts(); _elapsed = current_frame.ts();
match decoder.decode(&current_frame) { match decoder.decode(&current_frame) {
Ok(decoded) => break decoded, Ok(decoded) => break decoded,
Err(e) => match e { Err(e) => match e {
@ -142,7 +144,7 @@ impl SymphoniaDecoder {
spec, spec,
time_base, time_base,
duration, duration,
elapsed, elapsed: _elapsed,
metadata, metadata,
track, track,
tx, tx,
@ -259,7 +261,9 @@ impl Iterator for SymphoniaDecoder {
if err.kind() == std::io::ErrorKind::UnexpectedEof if err.kind() == std::io::ErrorKind::UnexpectedEof
&& err.to_string() == "end of stream" && err.to_string() == "end of stream"
{ {
self.tx.send(PlayerEngineCommand::Eos); self.tx
.send(PlayerEngineCommand::Eos)
.unwrap_or_else(|e| warn!("Send error {}", e));
return None; return None;
} }
} }

View File

@ -4,16 +4,16 @@ mod player_engine;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use anyhow::{anyhow, Result}; use anyhow::Result;
use decoder::MediaInfo; pub use decoder::MediaInfo;
use flume::{Receiver, Sender}; use flume::{Receiver, Sender};
pub use player_engine::PlayerMessage; pub use player_engine::PlayerMessage;
use player_engine::{PlayerEngine, PlayerEngineCommand}; use player_engine::{PlayerEngine, PlayerEngineCommand};
use tracing::warn;
// TODO: // TODO:
// * Emit buffering // * Emit buffering
// * Emit errors
pub enum PlayerError {} pub enum PlayerError {}
@ -35,26 +35,57 @@ impl Default for Player {
loop { loop {
match rx_engine.recv() { match rx_engine.recv() {
Ok(PlayerEngineCommand::Play(source_str, tx)) => { Ok(PlayerEngineCommand::Play(source_str, tx)) => {
let res = player.play(&source_str); tx.send(player.play(&source_str))
tx.send(res); .unwrap_or_else(|e| warn!("Send error {}", e));
} }
Ok(PlayerEngineCommand::Pause) => { Ok(PlayerEngineCommand::Pause(tx)) => {
player.pause(); tx.send(player.pause())
.unwrap_or_else(|e| warn!("Send error {}", e));
} }
Ok(PlayerEngineCommand::Unpause) => { Ok(PlayerEngineCommand::Unpause(tx)) => {
player.unpause(); tx.send(player.unpause())
.unwrap_or_else(|e| warn!("Send error {}", e));
} }
Ok(PlayerEngineCommand::Stop) => { Ok(PlayerEngineCommand::Stop(tx)) => {
player.stop(); tx.send(player.stop())
.unwrap_or_else(|e| warn!("Send error {}", e));
} }
Ok(PlayerEngineCommand::TogglePlay) => { Ok(PlayerEngineCommand::TogglePlay(tx)) => {
player.toggle_play(); tx.send(player.toggle_play())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::Restart(tx)) => {
tx.send(player.restart())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::GetDuration(tx)) => {
tx.send(player.duration())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::GetElapsed(tx)) => {
tx.send(player.elapsed())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::GetVolume(tx)) => {
tx.send(player.volume())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::GetPaused(tx)) => {
tx.send(player.is_paused())
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::SetVolume(volume, tx)) => {
tx.send(player.set_volume(volume))
.unwrap_or_else(|e| warn!("Send error {}", e));
}
Ok(PlayerEngineCommand::SetElapsed(elapsed)) => {
player.handle_elapsed(elapsed);
} }
Ok(PlayerEngineCommand::Eos) => { Ok(PlayerEngineCommand::Eos) => {
player.handle_eos(); player.handle_eos();
} }
Err(e) => { Err(e) => {
// FIXME: debug!(e); warn!("Recv error {}", e);
} }
} }
} }
@ -68,61 +99,72 @@ impl Default for Player {
} }
impl Player { impl Player {
// FIXME: this could check if the player started playing using a channel
// Then it would be async (wait for Playing for example)
pub async fn play(&self, source_str: &str) -> Result<MediaInfo> { pub async fn play(&self, source_str: &str) -> Result<MediaInfo> {
let (tx, rx) = flume::bounded(1); let (tx, rx) = flume::bounded(1);
self.tx_engine self.tx_engine
.send(PlayerEngineCommand::Play(source_str.to_string(), tx)); .send(PlayerEngineCommand::Play(source_str.to_string(), tx))?;
if let Ok(res) = rx.recv_async().await { rx.recv_async().await?
return res;
}
// FIXME: add error type
Err(anyhow!("Player channel error"))
} }
pub async fn elpased(&self) -> Duration { pub async fn restart(&self) -> Result<MediaInfo> {
// FIXME: implement let (tx, rx) = flume::bounded(1);
Duration::default() self.tx_engine.send(PlayerEngineCommand::Restart(tx))?;
rx.recv_async().await?
} }
pub async fn duration(&self) -> Duration { pub async fn elpased(&self) -> Result<Duration> {
// FIXME: implement let (tx, rx) = flume::bounded(1);
Duration::default() self.tx_engine.send(PlayerEngineCommand::GetElapsed(tx))?;
rx.recv_async().await?
} }
pub async fn volume(&self) -> f32 { pub async fn duration(&self) -> Result<Duration> {
// FIXME: implement let (tx, rx) = flume::bounded(1);
0.0 self.tx_engine.send(PlayerEngineCommand::GetDuration(tx))?;
rx.recv_async().await?
} }
pub async fn set_volume(&self) -> Result<()> { pub async fn volume(&self) -> Result<f32> {
// FIXME: implement let (tx, rx) = flume::bounded(1);
Ok(()) self.tx_engine.send(PlayerEngineCommand::GetVolume(tx))?;
rx.recv_async().await?
}
pub async fn is_paused(&self) -> Result<bool> {
let (tx, rx) = flume::bounded(1);
self.tx_engine.send(PlayerEngineCommand::GetPaused(tx))?;
rx.recv_async().await?
}
pub async fn set_volume(&self, volume: f32) -> Result<f32> {
let (tx, rx) = flume::bounded(1);
let vol = volume.clamp(0.0, 1.1);
self.tx_engine
.send(PlayerEngineCommand::SetVolume(vol, tx))?;
rx.recv_async().await?
} }
pub async fn pause(&self) -> Result<()> { pub async fn pause(&self) -> Result<()> {
self.tx_engine.send(PlayerEngineCommand::Pause); let (tx, rx) = flume::bounded(1);
Ok(()) self.tx_engine.send(PlayerEngineCommand::Pause(tx))?;
rx.recv_async().await?
} }
pub async fn unpause(&self) -> Result<()> { pub async fn unpause(&self) -> Result<()> {
self.tx_engine.send(PlayerEngineCommand::Unpause); let (tx, rx) = flume::bounded(1);
Ok(()) self.tx_engine.send(PlayerEngineCommand::Unpause(tx))?;
rx.recv_async().await?
} }
pub async fn toggle_play(&self) -> Result<()> { pub async fn toggle_play(&self) -> Result<bool> {
self.tx_engine.send(PlayerEngineCommand::TogglePlay); let (tx, rx) = flume::bounded(1);
Ok(()) self.tx_engine.send(PlayerEngineCommand::TogglePlay(tx))?;
rx.recv_async().await?
} }
pub async fn stop(&self) -> Result<()> { pub async fn stop(&self) -> Result<()> {
self.tx_engine.send(PlayerEngineCommand::Stop); let (tx, rx) = flume::bounded(1);
Ok(()) self.tx_engine.send(PlayerEngineCommand::Stop(tx))?;
} rx.recv_async().await?
pub async fn restart(&self) -> Result<()> {
// FIXME: implement
Ok(())
} }
} }

View File

@ -1,35 +1,44 @@
use flume::{Receiver, Sender}; use flume::Sender;
use std::fs::File; use std::fs::File;
use std::io::BufReader;
use std::path::Path; use std::path::Path;
use std::thread;
use std::time::Duration; use std::time::Duration;
use symphonia::core::probe::Hint; use symphonia::core::probe::Hint;
use tracing::warn;
use url::Url; use url::Url;
use crate::decoder::{MediaInfo, SymphoniaDecoder}; use crate::decoder::{MediaInfo, SymphoniaDecoder};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use rodio::source::{PeriodicAccess, SineWave}; use rodio::{OutputStream, Sink, Source};
use rodio::{OutputStream, OutputStreamHandle, Sink, Source};
use stream_download::StreamDownload; use stream_download::StreamDownload;
use symphonia::core::io::{ use symphonia::core::io::{
MediaSource, MediaSourceStream, MediaSourceStreamOptions, ReadOnlySource, MediaSource, MediaSourceStream, MediaSourceStreamOptions, ReadOnlySource,
}; };
use thiserror::Error;
pub enum PlayerEngineCommand { pub enum PlayerEngineCommand {
Play(String, Sender<Result<MediaInfo>>), Play(String, Sender<Result<MediaInfo>>),
Pause, SetVolume(f32, Sender<Result<f32>>),
Unpause, Pause(Sender<Result<()>>),
TogglePlay, Unpause(Sender<Result<()>>),
Stop, TogglePlay(Sender<Result<bool>>),
Restart(Sender<Result<MediaInfo>>),
Stop(Sender<Result<()>>),
GetDuration(Sender<Result<Duration>>),
GetElapsed(Sender<Result<Duration>>),
GetVolume(Sender<Result<f32>>),
GetPaused(Sender<Result<bool>>),
Eos, Eos,
SetElapsed(Duration),
} }
// FIXME: sort out media info size (probably send pointers to stuff on the heap)
pub enum PlayerMessage { pub enum PlayerMessage {
// MediaInfo(MediaInfo), Duration {
Duration(Duration), duration: Duration,
Elapsed(Duration), },
Elapsed {
duration: Duration,
elapsed: Duration,
},
Stopped, Stopped,
Paused, Paused,
Playing, Playing,
@ -40,15 +49,29 @@ pub enum PlayerMessage {
// * Emit buffering // * Emit buffering
pub struct PlayerEngine { pub struct PlayerEngine {
elapsed: Duration,
// FIXME: We only need this to re-start a track
// Might do that using seeking in the future
current_source: Option<String>,
media_info: Option<MediaInfo>,
sink: Option<Sink>, sink: Option<Sink>,
stream: Option<OutputStream>, stream: Option<OutputStream>,
tx_engine: Sender<PlayerEngineCommand>, tx_engine: Sender<PlayerEngineCommand>,
tx_player: Sender<PlayerMessage>, tx_player: Sender<PlayerMessage>,
} }
#[derive(Debug, Error)]
pub enum PlayerEngineError {
#[error("Sink is not playing")]
NotPlaying,
}
impl PlayerEngine { impl PlayerEngine {
pub fn new(tx_engine: Sender<PlayerEngineCommand>, tx_player: Sender<PlayerMessage>) -> Self { pub fn new(tx_engine: Sender<PlayerEngineCommand>, tx_player: Sender<PlayerMessage>) -> Self {
Self { Self {
current_source: None,
media_info: None,
elapsed: Duration::default(),
sink: None, sink: None,
stream: None, stream: None,
tx_engine, tx_engine,
@ -58,20 +81,33 @@ impl PlayerEngine {
pub fn play(&mut self, source_str: &str) -> Result<MediaInfo> { pub fn play(&mut self, source_str: &str) -> Result<MediaInfo> {
let tx_player = self.tx_player.clone(); let tx_player = self.tx_player.clone();
let tx_engine = self.tx_engine.clone();
let (stream, handle) = OutputStream::try_default()?; let (stream, handle) = OutputStream::try_default()?;
let mut sink = Sink::try_new(&handle)?; let sink = Sink::try_new(&handle)?;
let (source, hint) = self.get_source(source_str)?; let (source, hint) = self.get_source(source_str)?;
let mss = MediaSourceStream::new(source, MediaSourceStreamOptions::default()); let mss = MediaSourceStream::new(source, MediaSourceStreamOptions::default());
let decoder = SymphoniaDecoder::new(mss, hint, self.tx_engine.clone())?; let decoder = SymphoniaDecoder::new(mss, hint, self.tx_engine.clone())?;
let media_info = decoder.media_info(); let media_info = decoder.media_info();
let media_info_copy = media_info.clone();
let duration = media_info.duration.unwrap_or_default();
tx_player.send(PlayerMessage::Duration( self.media_info = Some(media_info);
media_info.duration.unwrap_or_default(),
));
tx_player
.send(PlayerMessage::Duration { duration })
.unwrap_or_else(|e| warn!("Send error {}", e));
// FIXME: regularly update metadata revision
let decoder = decoder.periodic_access(Duration::from_millis(250), move |src| { let decoder = decoder.periodic_access(Duration::from_millis(250), move |src| {
tx_player.send(PlayerMessage::Elapsed(src.elapsed())); let elapsed = src.elapsed();
tx_engine
.send(PlayerEngineCommand::SetElapsed(elapsed))
.unwrap_or_else(|e| warn!("Send error {}", e));
tx_player
.send(PlayerMessage::Elapsed { elapsed, duration })
.unwrap_or_else(|e| warn!("Send error {}", e));
}); });
sink.append(decoder); sink.append(decoder);
@ -80,65 +116,131 @@ impl PlayerEngine {
// The sink is used to control the stream // The sink is used to control the stream
self.sink = Some(sink); self.sink = Some(sink);
self.tx_player.send(PlayerMessage::Playing); self.tx_player
.send(PlayerMessage::Playing)
.unwrap_or_else(|e| warn!("Send error {}", e));
Ok(media_info) Ok(media_info_copy)
} }
pub fn pause(&mut self) { pub fn restart(&mut self) -> Result<MediaInfo> {
if let Some(source) = self.current_source.clone() {
self.reset()?;
return self.play(&source);
}
Err(PlayerEngineError::NotPlaying.into())
}
pub fn pause(&mut self) -> Result<()> {
if let Some(sink) = &self.sink { if let Some(sink) = &self.sink {
sink.pause(); sink.pause();
self.tx_player.send(PlayerMessage::Paused); self.tx_player
.send(PlayerMessage::Paused)
.unwrap_or_else(|e| warn!("Send error {}", e));
return Ok(());
} }
Err(PlayerEngineError::NotPlaying.into())
} }
pub fn unpause(&mut self) { pub fn unpause(&mut self) -> Result<()> {
if let Some(sink) = &self.sink { if let Some(sink) = &self.sink {
sink.play(); sink.play();
self.tx_player.send(PlayerMessage::Playing); self.tx_player
.send(PlayerMessage::Playing)
.unwrap_or_else(|e| warn!("Send error {}", e));
return Ok(());
} }
Err(PlayerEngineError::NotPlaying.into())
} }
pub fn toggle_play(&mut self) { pub fn toggle_play(&mut self) -> Result<bool> {
if self.is_stopped() {
return;
}
if self.is_paused() {
self.unpause();
} else {
self.pause();
}
}
pub fn stop(&mut self) {
if let Some(sink) = &self.sink { if let Some(sink) = &self.sink {
sink.stop(); if sink.is_paused() {
self.sink.take(); sink.play();
self.stream.take(); return Ok(true);
self.tx_player.send(PlayerMessage::Stopped); } else {
sink.pause();
return Ok(false);
}
} }
Err(PlayerEngineError::NotPlaying.into())
} }
pub fn handle_eos(&mut self) { pub fn stop(&mut self) -> Result<()> {
if let Some(sink) = &self.sink { self.reset()?;
sink.stop(); self.tx_player
self.sink.take(); .send(PlayerMessage::Stopped)
self.stream.take(); .unwrap_or_else(|e| warn!("Send error {}", e));
self.tx_player.send(PlayerMessage::EndOfStream); Ok(())
}
} }
pub fn is_paused(&self) -> bool { pub fn is_paused(&self) -> Result<bool> {
self.sink self.sink
.as_ref() .as_ref()
.map(|s| s.is_paused()) .map_or(Err(PlayerEngineError::NotPlaying.into()), |s| {
.unwrap_or_default() Ok(s.is_paused())
})
} }
pub fn is_stopped(&self) -> bool { pub fn is_stopped(&self) -> bool {
self.sink.is_none() self.sink.is_none()
} }
pub fn duration(&self) -> Result<Duration> {
self.media_info
.as_ref()
.map_or(Err(PlayerEngineError::NotPlaying.into()), |m| {
Ok(m.duration.unwrap_or_default())
})
}
pub fn elapsed(&self) -> Result<Duration> {
if self.is_stopped() {
return Err(PlayerEngineError::NotPlaying.into());
}
Ok(self.elapsed)
}
pub fn volume(&self) -> Result<f32> {
self.sink.as_ref().map_or(
Err(PlayerEngineError::NotPlaying.into()),
|s| Ok(s.volume()),
)
}
pub fn set_volume(&mut self, volume: f32) -> Result<f32> {
if let Some(sink) = &self.sink {
sink.set_volume(volume);
return Ok(sink.volume());
}
Err(PlayerEngineError::NotPlaying.into())
}
pub fn handle_eos(&mut self) {
self.reset().unwrap_or_else(|e| {
warn!("Sink error {}", e);
});
self.tx_player
.send(PlayerMessage::EndOfStream)
.unwrap_or_else(|e| warn!("Send error {}", e));
}
pub fn handle_elapsed(&mut self, elapsed: Duration) {
self.elapsed = elapsed;
}
fn reset(&mut self) -> Result<()> {
self.elapsed = Duration::default();
self.current_source = None;
if let Some(sink) = &self.sink {
sink.stop();
self.sink.take();
self.stream.take();
return Ok(());
}
Err(PlayerEngineError::NotPlaying.into())
}
fn get_source(&self, source_str: &str) -> Result<(Box<dyn MediaSource>, Hint)> { fn get_source(&self, source_str: &str) -> Result<(Box<dyn MediaSource>, Hint)> {
match Url::parse(source_str) { match Url::parse(source_str) {
Ok(url) => { Ok(url) => {