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.
This commit is contained in:
Thomas Bächler 2017-11-29 00:18:12 +01:00
parent f250179fed
commit 4cda8affcd

View file

@ -2,8 +2,9 @@ use futures::sync::oneshot;
use futures::{future, Future}; use futures::{future, Future};
use std::borrow::Cow; use std::borrow::Cow;
use std::mem; use std::mem;
use std::sync::mpsc::{RecvError, TryRecvError}; use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
use std::thread; use std::thread;
use std::time::Duration;
use std; use std;
use core::config::{Bitrate, PlayerConfig}; use core::config::{Bitrate, PlayerConfig};
@ -28,6 +29,7 @@ struct PlayerInternal {
state: PlayerState, state: PlayerState,
sink: Box<Sink>, sink: Box<Sink>,
sink_running: bool,
audio_filter: Option<Box<AudioFilter + Send>>, audio_filter: Option<Box<AudioFilter + Send>>,
} }
@ -57,6 +59,7 @@ impl Player {
state: PlayerState::Stopped, state: PlayerState::Stopped,
sink: sink_builder(), sink: sink_builder(),
sink_running: false,
audio_filter: audio_filter, audio_filter: audio_filter,
}; };
@ -191,11 +194,22 @@ impl PlayerInternal {
fn run(mut self) { fn run(mut self) {
loop { loop {
let cmd = if self.state.is_playing() { let cmd = if self.state.is_playing() {
if self.sink_running
{
match self.commands.try_recv() { match self.commands.try_recv() {
Ok(cmd) => Some(cmd), Ok(cmd) => Some(cmd),
Err(TryRecvError::Empty) => None, Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => return, 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 { } else {
match self.commands.recv() { match self.commands.recv() {
Ok(cmd) => Some(cmd), Ok(cmd) => Some(cmd),
@ -207,15 +221,41 @@ impl PlayerInternal {
self.handle_command(cmd); self.handle_command(cmd);
} }
if self.state.is_playing() && ! self.sink_running {
self.start_sink();
}
if self.sink_running {
let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state { let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state {
Some(decoder.next_packet().expect("Vorbis error")) Some(decoder.next_packet().expect("Vorbis error"))
} else { None }; } else {
None
};
if let Some(packet) = packet { if let Some(packet) = packet {
self.handle_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<VorbisPacket>) { fn handle_packet(&mut self, packet: Option<VorbisPacket>) {
match packet { match packet {
@ -224,11 +264,14 @@ impl PlayerInternal {
editor.modify_stream(&mut packet.data_mut()) 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 => { None => {
self.sink.stop().unwrap(); self.stop_sink();
self.run_onstop(); self.run_onstop();
let old_state = mem::replace(&mut self.state, PlayerState::Stopped); let old_state = mem::replace(&mut self.state, PlayerState::Stopped);
@ -242,7 +285,7 @@ impl PlayerInternal {
match cmd { match cmd {
PlayerCommand::Load(track_id, play, position, end_of_track) => { PlayerCommand::Load(track_id, play, position, end_of_track) => {
if self.state.is_playing() { if self.state.is_playing() {
self.sink.stop().unwrap(); self.stop_sink_if_running();
} }
match self.load_track(track_id, position as i64) { match self.load_track(track_id, position as i64) {
@ -251,7 +294,7 @@ impl PlayerInternal {
if !self.state.is_playing() { if !self.state.is_playing() {
self.run_onstart(); self.run_onstart();
} }
self.sink.start().unwrap(); self.start_sink();
self.state = PlayerState::Playing { self.state = PlayerState::Playing {
decoder: decoder, decoder: decoder,
@ -294,7 +337,7 @@ impl PlayerInternal {
self.state.paused_to_playing(); self.state.paused_to_playing();
self.run_onstart(); self.run_onstart();
self.sink.start().unwrap(); self.start_sink();
} else { } else {
warn!("Player::play called from invalid state"); warn!("Player::play called from invalid state");
} }
@ -304,7 +347,7 @@ impl PlayerInternal {
if let PlayerState::Playing { .. } = self.state { if let PlayerState::Playing { .. } = self.state {
self.state.playing_to_paused(); self.state.playing_to_paused();
self.sink.stop().unwrap(); self.stop_sink_if_running();
self.run_onstop(); self.run_onstop();
} else { } else {
warn!("Player::pause called from invalid state"); warn!("Player::pause called from invalid state");
@ -314,7 +357,7 @@ impl PlayerInternal {
PlayerCommand::Stop => { PlayerCommand::Stop => {
match self.state { match self.state {
PlayerState::Playing { .. } => { PlayerState::Playing { .. } => {
self.sink.stop().unwrap(); self.stop_sink_if_running();
self.run_onstop(); self.run_onstop();
self.state = PlayerState::Stopped; self.state = PlayerState::Stopped;
} }