mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
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:
parent
f250179fed
commit
4cda8affcd
1 changed files with 60 additions and 17 deletions
|
@ -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>,
|
||||
sink_running: bool,
|
||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||
}
|
||||
|
||||
|
@ -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<VorbisPacket>) {
|
||||
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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue