Skip to content

Commit

Permalink
Send more detailed events from AudioPlayer
Browse files Browse the repository at this point in the history
  • Loading branch information
jpochyla committed Jan 4, 2021
1 parent ddb8a8a commit d136d3b
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 80 deletions.
140 changes: 86 additions & 54 deletions psst-core/src/audio_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,15 +219,16 @@ impl Player {
PlayerEvent::Preloaded { item, result } => {
self.handle_preloaded(item, result);
}
PlayerEvent::Playing { duration, path } => {
self.handle_playing(duration, path);
PlayerEvent::Progress { duration, path } => {
self.handle_progress(duration, path);
}
PlayerEvent::Finished { .. } => {
self.handle_finished();
}
PlayerEvent::Paused { .. } => {}
PlayerEvent::Started { .. } => {}
PlayerEvent::Loading { .. } => {}
PlayerEvent::Playing { .. } => {}
PlayerEvent::Pausing { .. } => {}
PlayerEvent::Resuming { .. } => {}
};
}

Expand Down Expand Up @@ -287,12 +288,16 @@ impl Player {
}
}

fn handle_playing(&mut self, progress: Duration, path: AudioPath) {
const PRELOAD_BEFORE_END_OF_TRACK: Duration = Duration::from_secs(30);

if let PlayerState::Playing { duration, .. } = &mut self.state {
*duration = progress;
fn handle_progress(&mut self, progress: Duration, path: AudioPath) {
match &mut self.state {
PlayerState::Playing { duration, .. } | PlayerState::Paused { duration, .. } => {
*duration = progress;
}
_ => {
log::warn!("received unexpected progress report");
}
}
const PRELOAD_BEFORE_END_OF_TRACK: Duration = Duration::from_secs(30);
if let Some(&item_to_preload) = self.queue.get_next() {
let time_until_end_of_track = path.duration.checked_sub(progress).unwrap_or_default();
if time_until_end_of_track <= PRELOAD_BEFORE_END_OF_TRACK {
Expand Down Expand Up @@ -383,13 +388,11 @@ impl Player {
fn play_loaded(&mut self, loaded_item: LoadedPlaybackItem) {
log::info!("starting playback");
let path = loaded_item.file.path();
let duration = Duration::default();
self.event_sender
.send(PlayerEvent::Started { path })
.expect("Failed to send PlayerEvent::Started");
self.state = PlayerState::Playing {
path,
duration: Duration::default(),
};
.send(PlayerEvent::Playing { path, duration })
.expect("Failed to send PlayerEvent::Playing");
self.state = PlayerState::Playing { path, duration };
self.audio_source
.lock()
.expect("Failed to acquire audio source lock")
Expand All @@ -402,7 +405,7 @@ impl Player {
PlayerState::Playing { path, duration } | PlayerState::Paused { path, duration } => {
log::info!("pausing playback");
self.event_sender
.send(PlayerEvent::Paused { path })
.send(PlayerEvent::Pausing { path, duration })
.expect("Failed to send PlayerEvent::Paused");
self.state = PlayerState::Paused { path, duration };
self.audio_output_remote.pause();
Expand All @@ -417,6 +420,9 @@ impl Player {
match mem::replace(&mut self.state, PlayerState::Invalid) {
PlayerState::Playing { path, duration } | PlayerState::Paused { path, duration } => {
log::info!("resuming playback");
self.event_sender
.send(PlayerEvent::Resuming { path, duration })
.expect("Failed to send PlayerEvent::Resuming");
self.state = PlayerState::Playing { path, duration };
self.audio_output_remote.resume();
}
Expand Down Expand Up @@ -455,7 +461,6 @@ impl Player {
}

fn seek(&mut self, position: Duration) {
// TODO: Clear audio output buffer.
self.audio_source
.lock()
.expect("Failed to acquire audio source lock")
Expand Down Expand Up @@ -510,27 +515,44 @@ pub enum PlayerCommand {

pub enum PlayerEvent {
Command(PlayerCommand),
/// Track has started loading. `Loaded` follows.
Loading {
item: PlaybackItem,
},
/// Track loading either succeeded or failed. `Playing` follows in case of
/// success.
Loaded {
item: PlaybackItem,
result: Result<LoadedPlaybackItem, Error>,
},
/// Next item in queue has been successfully preloaded.
Preloaded {
item: PlaybackItem,
result: Result<LoadedPlaybackItem, Error>,
},
Started {
/// Player has started playing new track. `Progress` events will follow.
Playing {
path: AudioPath,
duration: Duration,
},
Playing {
/// Player is in a paused state. `Resuming` might follow.
Pausing {
path: AudioPath,
duration: Duration,
},
Paused {
/// Player is resuming playback of a track
Resuming {
path: AudioPath,
duration: Duration,
},
/// Player is either reacting to a seek event in a paused or playing state,
/// or track is naturally progressing during playback.
Progress {
path: AudioPath,
duration: Duration,
},
/// Player has finished playing a track. `Loading` or `Playing` might
/// follow if the queue is not empty.
Finished,
}

Expand Down Expand Up @@ -584,18 +606,52 @@ impl PlayerAudioSource {

fn seek(&mut self, position: Duration) {
if let Some(current) = &mut self.current {
let pos_secs = position.as_secs_f64();
let pcm_frame = pos_secs * OUTPUT_SAMPLE_RATE as f64;
let samples = pcm_frame * OUTPUT_CHANNELS as f64;
current.source.seek(pcm_frame as u64);
let seconds = position.as_secs_f64();
let frames = seconds * OUTPUT_SAMPLE_RATE as f64;
let samples = frames * OUTPUT_CHANNELS as f64;
current.source.seek(frames as u64);
self.samples = samples as u64;
self.report_audio_position();
}
}

fn play_now(&mut self, item: LoadedPlaybackItem) {
self.current.replace(item);
self.samples = 0;
}

fn next_sample(&mut self) -> Option<AudioSample> {
if let Some(current) = self.current.as_mut() {
let sample = current.source.next();
if sample.is_some() {
self.samples += 1;
} else {
self.samples = 0;
self.current.take();
}
sample
} else {
None
}
}

fn report_audio_position(&self) {
if let Some(current) = self.current.as_ref() {
let duration = Duration::from_secs_f64(
self.samples as f64 / OUTPUT_SAMPLE_RATE as f64 / OUTPUT_CHANNELS as f64,
);
let path = current.file.path();
self.event_sender
.send(PlayerEvent::Progress { duration, path })
.expect("Failed to send PlayerEvent::Progress");
}
}

fn report_audio_end(&self) {
self.event_sender
.send(PlayerEvent::Finished)
.expect("Failed to send PlayerEvent::Finished");
}
}

impl AudioSource for PlayerAudioSource {
Expand All @@ -612,39 +668,15 @@ impl Iterator for PlayerAudioSource {
type Item = AudioSample;

fn next(&mut self) -> Option<Self::Item> {
// TODO:
// We could move all this into the decoder struct, use position information
// from the decoding and do all this work only per-packet, not per-sample.
if let Some(current) = &mut self.current {
let sample = current.source.next();
if sample.is_some() {
// We report playback progress every `PROGRESS_PRECISION_SAMPLES`th sample.
if self.samples % PROGRESS_PRECISION_SAMPLES == 0 {
self.event_sender
.send(PlayerEvent::Playing {
duration: Duration::from_secs_f64(
self.samples as f64
/ OUTPUT_SAMPLE_RATE as f64
/ OUTPUT_CHANNELS as f64,
),
path: current.file.path(),
})
.expect("Failed to send PlayerEvent::Playing");
}
self.samples += 1;
sample
} else {
// Current source ended, report audio end.
self.event_sender
.send(PlayerEvent::Finished)
.expect("Failed to send PlayerEvent::Finished");
self.current.take();
self.samples = 0;
None
let sample = self.next_sample();
if sample.is_some() {
if self.samples % PROGRESS_PRECISION_SAMPLES == 0 {
self.report_audio_position()
}
} else {
None
self.report_audio_end();
}
sample
}
}

Expand Down
6 changes: 4 additions & 2 deletions psst-gui/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ pub const UPDATE_PLAYLIST_TRACKS: Selector<(PlaylistLink, Result<Vector<Arc<Trac
// Playback state

pub const PLAYBACK_LOADING: Selector<TrackId> = Selector::new("app.playback-loading");
pub const PLAYBACK_PLAYING: Selector<TrackId> = Selector::new("app.playback-playing");
pub const PLAYBACK_PLAYING: Selector<(TrackId, AudioDuration)> =
Selector::new("app.playback-playing");
pub const PLAYBACK_PROGRESS: Selector<AudioDuration> = Selector::new("app.playback-progress");
pub const PLAYBACK_PAUSED: Selector = Selector::new("app.playback-paused");
pub const PLAYBACK_PAUSING: Selector = Selector::new("app.playback-pausing");
pub const PLAYBACK_RESUMING: Selector = Selector::new("app.playback-resuming");
pub const PLAYBACK_STOPPED: Selector = Selector::new("app.playback-stopped");
pub const UPDATE_AUDIO_ANALYSIS: Selector<(TrackId, Result<AudioAnalysis, Error>)> =
Selector::new("app.update-audio-analysis");
Expand Down
28 changes: 18 additions & 10 deletions psst-gui/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,40 +87,48 @@ impl Default for State {
}

impl State {
pub fn set_playback_loading(&mut self, item: Arc<Track>, origin: PlaybackOrigin) {
pub fn loading_playback(&mut self, item: Arc<Track>, origin: PlaybackOrigin) {
self.common_ctx.playback_item.take();
self.playback.state = PlaybackState::Loading;
self.playback.current.replace(CurrentPlayback {
item,
origin,
progress: Default::default(),
analysis: Promise::Empty,
progress: AudioDuration::default(),
analysis: Promise::default(),
});
}

pub fn set_playback_playing(&mut self, item: Arc<Track>, origin: PlaybackOrigin) {
pub fn start_playback(
&mut self,
item: Arc<Track>,
origin: PlaybackOrigin,
progress: AudioDuration,
) {
self.common_ctx.playback_item.replace(item.clone());
self.playback.state = PlaybackState::Playing;
self.playback.current.replace(CurrentPlayback {
item,
origin,
progress: Default::default(),
analysis: Promise::Empty,
progress,
analysis: Promise::default(),
});
}

pub fn set_playback_progress(&mut self, progress: AudioDuration) {
self.playback.state = PlaybackState::Playing;
pub fn progress_playback(&mut self, progress: AudioDuration) {
self.playback.current.as_mut().map(|current| {
current.progress = progress;
});
}

pub fn set_playback_paused(&mut self) {
pub fn pause_playback(&mut self) {
self.playback.state = PlaybackState::Paused;
}

pub fn set_playback_stopped(&mut self) {
pub fn resume_playback(&mut self) {
self.playback.state = PlaybackState::Playing;
}

pub fn stop_playback(&mut self) {
self.playback.state = PlaybackState::Stopped;
self.playback.current.take();
self.common_ctx.playback_item.take();
Expand Down
36 changes: 22 additions & 14 deletions psst-gui/src/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,23 @@ impl PlayerDelegate {
sink.submit_command(cmd::PLAYBACK_LOADING, item, Target::Auto)
.unwrap();
}
PlayerEvent::Started { path } => {
PlayerEvent::Playing { path, duration } => {
let item: TrackId = path.item_id.into();
sink.submit_command(cmd::PLAYBACK_PLAYING, item, Target::Auto)
let progress: AudioDuration = duration.to_owned().into();
sink.submit_command(cmd::PLAYBACK_PLAYING, (item, progress), Target::Auto)
.unwrap();
}
PlayerEvent::Playing { duration, .. } => {
let progress: AudioDuration = duration.to_owned().into();
sink.submit_command(cmd::PLAYBACK_PROGRESS, progress, Target::Auto)
PlayerEvent::Pausing { .. } => {
sink.submit_command(cmd::PLAYBACK_PAUSING, (), Target::Auto)
.unwrap();
}
PlayerEvent::Resuming { .. } => {
sink.submit_command(cmd::PLAYBACK_RESUMING, (), Target::Auto)
.unwrap();
}
PlayerEvent::Paused { .. } => {
sink.submit_command(cmd::PLAYBACK_PAUSED, (), Target::Auto)
PlayerEvent::Progress { duration, .. } => {
let progress: AudioDuration = duration.to_owned().into();
sink.submit_command(cmd::PLAYBACK_PROGRESS, progress, Target::Auto)
.unwrap();
}
PlayerEvent::Finished => {
Expand Down Expand Up @@ -770,14 +775,14 @@ impl Delegate {
fn command_playback(&mut self, _target: Target, cmd: &Command, data: &mut State) -> Handled {
if let Some(item) = cmd.get(cmd::PLAYBACK_LOADING) {
if let Some((origin, track)) = self.player.get_track(item) {
data.set_playback_loading(track, origin);
data.loading_playback(track, origin);
} else {
log::warn!("loaded item not found in playback queue");
}
Handled::Yes
} else if let Some(item) = cmd.get(cmd::PLAYBACK_PLAYING) {
} else if let Some((item, progress)) = cmd.get(cmd::PLAYBACK_PLAYING) {
if let Some((origin, track)) = self.player.get_track(item) {
data.set_playback_playing(track, origin);
data.start_playback(track, origin, progress.to_owned());
data.playback.current.as_mut().map(|current| {
current.analysis.defer(item.clone());
});
Expand All @@ -794,13 +799,16 @@ impl Delegate {
}
Handled::Yes
} else if let Some(progress) = cmd.get(cmd::PLAYBACK_PROGRESS).cloned() {
data.set_playback_progress(progress);
data.progress_playback(progress);
Handled::Yes
} else if cmd.is(cmd::PLAYBACK_PAUSING) {
data.pause_playback();
Handled::Yes
} else if cmd.is(cmd::PLAYBACK_PAUSED) {
data.set_playback_paused();
} else if cmd.is(cmd::PLAYBACK_RESUMING) {
data.resume_playback();
Handled::Yes
} else if cmd.is(cmd::PLAYBACK_STOPPED) {
data.set_playback_stopped();
data.stop_playback();
Handled::Yes
} else if let Some((track_id, result)) = cmd.get(cmd::UPDATE_AUDIO_ANALYSIS).cloned() {
data.playback.current.as_mut().map(|current| {
Expand Down

0 comments on commit d136d3b

Please sign in to comment.