Forráskód Böngészése

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.
Thomas Bächler 7 éve
szülő
commit
4cda8affcd
1 módosított fájl, 60 hozzáadás és 17 törlés
  1. 60 17
      src/player.rs

+ 60 - 17
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>,
+    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;
                     }