From 4cda8affcd188815ef552889676eca0b6bf1724f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20B=C3=A4chler?= Date: Wed, 29 Nov 2017 00:18:12 +0100 Subject: [PATCH] Handle audio sink errors in the player Failing to open or write to the audio sink is not necessarily a fatal and permanent error. When the audio sink fails, the player now tries to restart the sink periodically. --- src/player.rs | 77 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/src/player.rs b/src/player.rs index c07082f6..94df2e1c 100644 --- a/src/player.rs +++ b/src/player.rs @@ -2,8 +2,9 @@ use futures::sync::oneshot; use futures::{future, Future}; use std::borrow::Cow; use std::mem; -use std::sync::mpsc::{RecvError, TryRecvError}; +use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError}; use std::thread; +use std::time::Duration; use std; use core::config::{Bitrate, PlayerConfig}; @@ -28,6 +29,7 @@ struct PlayerInternal { state: PlayerState, sink: Box, + sink_running: bool, audio_filter: Option>, } @@ -57,6 +59,7 @@ impl Player { state: PlayerState::Stopped, sink: sink_builder(), + sink_running: false, audio_filter: audio_filter, }; @@ -191,10 +194,21 @@ impl PlayerInternal { fn run(mut self) { loop { let cmd = if self.state.is_playing() { - match self.commands.try_recv() { - Ok(cmd) => Some(cmd), - Err(TryRecvError::Empty) => None, - Err(TryRecvError::Disconnected) => return, + if self.sink_running + { + match self.commands.try_recv() { + Ok(cmd) => Some(cmd), + Err(TryRecvError::Empty) => None, + Err(TryRecvError::Disconnected) => return, + } + } + else + { + match self.commands.recv_timeout(Duration::from_secs(5)) { + Ok(cmd) => Some(cmd), + Err(RecvTimeoutError::Timeout) => None, + Err(RecvTimeoutError::Disconnected) => return, + } } } else { match self.commands.recv() { @@ -207,16 +221,42 @@ impl PlayerInternal { self.handle_command(cmd); } - let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state { - Some(decoder.next_packet().expect("Vorbis error")) - } else { None }; + if self.state.is_playing() && ! self.sink_running { + self.start_sink(); + } - if let Some(packet) = packet { - self.handle_packet(packet); + if self.sink_running { + let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state { + Some(decoder.next_packet().expect("Vorbis error")) + } else { + None + }; + + if let Some(packet) = packet { + self.handle_packet(packet); + } } } } + fn start_sink(&mut self) { + match self.sink.start() { + Ok(()) => self.sink_running = true, + Err(err) => error!("Could not start audio: {}", err), + } + } + + fn stop_sink_if_running(&mut self) { + if self.sink_running { + self.stop_sink(); + } + } + + fn stop_sink(&mut self) { + self.sink.stop().unwrap(); + self.sink_running = false; + } + fn handle_packet(&mut self, packet: Option) { match packet { Some(mut packet) => { @@ -224,11 +264,14 @@ impl PlayerInternal { editor.modify_stream(&mut packet.data_mut()) }; - self.sink.write(&packet.data()).unwrap(); + if let Err(err) = self.sink.write(&packet.data()) { + error!("Could not write audio: {}", err); + self.stop_sink(); + } } None => { - self.sink.stop().unwrap(); + self.stop_sink(); self.run_onstop(); let old_state = mem::replace(&mut self.state, PlayerState::Stopped); @@ -242,7 +285,7 @@ impl PlayerInternal { match cmd { PlayerCommand::Load(track_id, play, position, end_of_track) => { if self.state.is_playing() { - self.sink.stop().unwrap(); + self.stop_sink_if_running(); } match self.load_track(track_id, position as i64) { @@ -251,7 +294,7 @@ impl PlayerInternal { if !self.state.is_playing() { self.run_onstart(); } - self.sink.start().unwrap(); + self.start_sink(); self.state = PlayerState::Playing { decoder: decoder, @@ -294,7 +337,7 @@ impl PlayerInternal { self.state.paused_to_playing(); self.run_onstart(); - self.sink.start().unwrap(); + self.start_sink(); } else { warn!("Player::play called from invalid state"); } @@ -304,7 +347,7 @@ impl PlayerInternal { if let PlayerState::Playing { .. } = self.state { self.state.playing_to_paused(); - self.sink.stop().unwrap(); + self.stop_sink_if_running(); self.run_onstop(); } else { warn!("Player::pause called from invalid state"); @@ -314,7 +357,7 @@ impl PlayerInternal { PlayerCommand::Stop => { match self.state { PlayerState::Playing { .. } => { - self.sink.stop().unwrap(); + self.stop_sink_if_running(); self.run_onstop(); self.state = PlayerState::Stopped; }