Sfoglia il codice sorgente

First attempt at a better playback event system.

Simon Persson 7 anni fa
parent
commit
b0ee03112f
4 ha cambiato i file con 125 aggiunte e 45 eliminazioni
  1. 20 4
      playback/src/config.rs
  2. 48 39
      playback/src/player.rs
  3. 7 2
      src/main.rs
  4. 50 0
      src/player_event_handler.rs

+ 20 - 4
playback/src/config.rs

@@ -1,4 +1,6 @@
 use std::str::FromStr;
 use std::str::FromStr;
+use core::spotify_id::SpotifyId;
+use std::sync::mpsc::Sender;
 
 
 #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
 #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
 pub enum Bitrate {
 pub enum Bitrate {
@@ -25,19 +27,33 @@ impl Default for Bitrate {
     }
     }
 }
 }
 
 
+#[derive(Debug, Clone)]
+pub enum PlayerEvent {
+    Started {
+        track_id: SpotifyId,
+    },
+
+    Changed {
+        old_track_id: SpotifyId,
+        new_track_id: SpotifyId,
+    },
+
+    Stopped {
+        track_id: SpotifyId,
+    }
+}
+
 #[derive(Clone, Debug)]
 #[derive(Clone, Debug)]
 pub struct PlayerConfig {
 pub struct PlayerConfig {
     pub bitrate: Bitrate,
     pub bitrate: Bitrate,
-    pub onstart: Option<String>,
-    pub onstop: Option<String>,
+    pub event_sender : Option<Sender<PlayerEvent>>,
 }
 }
 
 
 impl Default for PlayerConfig {
 impl Default for PlayerConfig {
     fn default() -> PlayerConfig {
     fn default() -> PlayerConfig {
         PlayerConfig {
         PlayerConfig {
             bitrate: Bitrate::default(),
             bitrate: Bitrate::default(),
-            onstart: None,
-            onstop: None,
+            event_sender: None,
         }
         }
     }
     }
 }
 }

+ 48 - 39
playback/src/player.rs

@@ -4,12 +4,11 @@ use std;
 use std::borrow::Cow;
 use std::borrow::Cow;
 use std::io::{Read, Seek, SeekFrom, Result};
 use std::io::{Read, Seek, SeekFrom, Result};
 use std::mem;
 use std::mem;
-use std::process::Command;
 use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
 use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
 use std::thread;
 use std::thread;
 use std::time::Duration;
 use std::time::Duration;
 
 
-use config::{Bitrate, PlayerConfig};
+use config::{Bitrate, PlayerConfig, PlayerEvent};
 use core::session::Session;
 use core::session::Session;
 use core::spotify_id::SpotifyId;
 use core::spotify_id::SpotifyId;
 
 
@@ -121,14 +120,16 @@ type Decoder = VorbisDecoder<Subfile<AudioDecrypt<AudioFile>>>;
 enum PlayerState {
 enum PlayerState {
     Stopped,
     Stopped,
     Paused {
     Paused {
+        track_id: SpotifyId,
         decoder: Decoder,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
         end_of_track: oneshot::Sender<()>,
     },
     },
     Playing {
     Playing {
+        track_id: SpotifyId,
         decoder: Decoder,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
         end_of_track: oneshot::Sender<()>,
     },
     },
-
+    EndOfTrack { track_id: SpotifyId },
     Invalid,
     Invalid,
 }
 }
 
 
@@ -136,7 +137,7 @@ impl PlayerState {
     fn is_playing(&self) -> bool {
     fn is_playing(&self) -> bool {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match *self {
         match *self {
-            Stopped | Paused { .. } => false,
+            Stopped | EndOfTrack { .. } | Paused { .. } => false,
             Playing { .. } => true,
             Playing { .. } => true,
             Invalid => panic!("invalid state"),
             Invalid => panic!("invalid state"),
         }
         }
@@ -145,7 +146,7 @@ impl PlayerState {
     fn decoder(&mut self) -> Option<&mut Decoder> {
     fn decoder(&mut self) -> Option<&mut Decoder> {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match *self {
         match *self {
-            Stopped => None,
+            Stopped | EndOfTrack { .. } => None,
             Paused { ref mut decoder, .. } |
             Paused { ref mut decoder, .. } |
             Playing { ref mut decoder, .. } => Some(decoder),
             Playing { ref mut decoder, .. } => Some(decoder),
             Invalid => panic!("invalid state"),
             Invalid => panic!("invalid state"),
@@ -160,6 +161,7 @@ impl PlayerState {
                 let _ = end_of_track.send(());
                 let _ = end_of_track.send(());
             }
             }
 
 
+            EndOfTrack { .. } => warn!("signal_end_of_track from end of track state"),
             Stopped => warn!("signal_end_of_track from stopped state"),
             Stopped => warn!("signal_end_of_track from stopped state"),
             Invalid => panic!("invalid state"),
             Invalid => panic!("invalid state"),
         }
         }
@@ -168,10 +170,11 @@ impl PlayerState {
     fn paused_to_playing(&mut self) {
     fn paused_to_playing(&mut self) {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
         match ::std::mem::replace(self, Invalid) {
-            Paused { decoder, end_of_track } => {
+            Paused { decoder, end_of_track, track_id } => {
                 *self = Playing {
                 *self = Playing {
                     decoder: decoder,
                     decoder: decoder,
                     end_of_track: end_of_track,
                     end_of_track: end_of_track,
+                    track_id: track_id,
                 };
                 };
             }
             }
             _ => panic!("invalid state"),
             _ => panic!("invalid state"),
@@ -181,10 +184,11 @@ impl PlayerState {
     fn playing_to_paused(&mut self) {
     fn playing_to_paused(&mut self) {
         use self::PlayerState::*;
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
         match ::std::mem::replace(self, Invalid) {
-            Playing { decoder, end_of_track } => {
+            Playing { decoder, end_of_track, track_id } => {
                 *self = Paused {
                 *self = Paused {
                     decoder: decoder,
                     decoder: decoder,
                     end_of_track: end_of_track,
                     end_of_track: end_of_track,
+                    track_id: track_id,
                 };
                 };
             }
             }
             _ => panic!("invalid state"),
             _ => panic!("invalid state"),
@@ -274,9 +278,15 @@ impl PlayerInternal {
 
 
             None => {
             None => {
                 self.stop_sink();
                 self.stop_sink();
-                self.run_onstop();
 
 
-                let old_state = mem::replace(&mut self.state, PlayerState::Stopped);
+                let new_state = match self.state {
+                    PlayerState::Playing { track_id, .. }
+                    | PlayerState::Paused { track_id, .. } =>
+                        PlayerState::EndOfTrack { track_id },
+                    _ => PlayerState::Stopped,
+                };
+
+                let old_state = mem::replace(&mut self.state, new_state);
                 old_state.signal_end_of_track();
                 old_state.signal_end_of_track();
             }
             }
         }
         }
@@ -288,24 +298,35 @@ impl PlayerInternal {
             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.stop_sink_if_running();
                     self.stop_sink_if_running();
-                    self.run_onstop();
                 }
                 }
 
 
                 match self.load_track(track_id, position as i64) {
                 match self.load_track(track_id, position as i64) {
                     Some(decoder) => {
                     Some(decoder) => {
                         if play {
                         if play {
-                            self.run_onstart();
+                            match self.state {
+                                PlayerState::Playing { track_id: old_track_id, ..}
+                                | PlayerState::EndOfTrack { track_id: old_track_id, .. } =>
+                                    self.send_event(PlayerEvent::Changed {
+                                        old_track_id: old_track_id,
+                                        new_track_id: track_id
+                                    }),
+                                _ => self.send_event(PlayerEvent::Started { track_id }),
+                            }
+
                             self.start_sink();
                             self.start_sink();
 
 
                             self.state = PlayerState::Playing {
                             self.state = PlayerState::Playing {
+                                track_id: track_id,
                                 decoder: decoder,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                                 end_of_track: end_of_track,
                             };
                             };
                         } else {
                         } else {
                             self.state = PlayerState::Paused {
                             self.state = PlayerState::Paused {
+                                track_id: track_id,
                                 decoder: decoder,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                                 end_of_track: end_of_track,
                             };
                             };
+                            self.send_event(PlayerEvent::Stopped { track_id });
                         }
                         }
                     }
                     }
 
 
@@ -327,10 +348,10 @@ impl PlayerInternal {
             }
             }
 
 
             PlayerCommand::Play => {
             PlayerCommand::Play => {
-                if let PlayerState::Paused { .. } = self.state {
+                if let PlayerState::Paused { track_id, .. } = self.state {
                     self.state.paused_to_playing();
                     self.state.paused_to_playing();
 
 
-                    self.run_onstart();
+                    self.send_event(PlayerEvent::Started { track_id });
                     self.start_sink();
                     self.start_sink();
                 } else {
                 } else {
                     warn!("Player::play called from invalid state");
                     warn!("Player::play called from invalid state");
@@ -338,11 +359,11 @@ impl PlayerInternal {
             }
             }
 
 
             PlayerCommand::Pause => {
             PlayerCommand::Pause => {
-                if let PlayerState::Playing { .. } = self.state {
+                if let PlayerState::Playing { track_id, .. } = self.state {
                     self.state.playing_to_paused();
                     self.state.playing_to_paused();
 
 
                     self.stop_sink_if_running();
                     self.stop_sink_if_running();
-                    self.run_onstop();
+                    self.send_event(PlayerEvent::Stopped { track_id });
                 } else {
                 } else {
                     warn!("Player::pause called from invalid state");
                     warn!("Player::pause called from invalid state");
                 }
                 }
@@ -350,12 +371,11 @@ impl PlayerInternal {
 
 
             PlayerCommand::Stop => {
             PlayerCommand::Stop => {
                 match self.state {
                 match self.state {
-                    PlayerState::Playing { .. } => {
+                    PlayerState::Playing { track_id, .. }
+                    | PlayerState::Paused { track_id, .. }
+                    | PlayerState::EndOfTrack { track_id } => {
                         self.stop_sink_if_running();
                         self.stop_sink_if_running();
-                        self.run_onstop();
-                        self.state = PlayerState::Stopped;
-                    }
-                    PlayerState::Paused { .. } => {
+                        self.send_event(PlayerEvent::Stopped { track_id });
                         self.state = PlayerState::Stopped;
                         self.state = PlayerState::Stopped;
                     },
                     },
                     PlayerState::Stopped => {
                     PlayerState::Stopped => {
@@ -367,15 +387,14 @@ impl PlayerInternal {
         }
         }
     }
     }
 
 
-    fn run_onstart(&self) {
-        if let Some(ref program) = self.config.onstart {
-            run_program(program)
-        }
-    }
-
-    fn run_onstop(&self) {
-        if let Some(ref program) = self.config.onstop {
-            run_program(program)
+    fn send_event(&mut self, event: PlayerEvent) {
+        match self.config.event_sender {
+            Some(ref s) =>
+                match s.send(event.clone()) {
+                    Ok(_) => info!("Sent event {:?} to event listener.", event),
+                    Err(err) => error!("Failed to send event {:?} to listener: {:?}", event, err)
+                }
+            None => ()
         }
         }
     }
     }
 
 
@@ -509,13 +528,3 @@ impl<T: Read + Seek> Seek for Subfile<T> {
         }
         }
     }
     }
 }
 }
-
-fn run_program(program: &str) {
-    info!("Running {}", program);
-    let mut v: Vec<&str> = program.split_whitespace().collect();
-    let status = Command::new(&v.remove(0))
-            .args(&v)
-            .status()
-            .expect("program failed to start");
-    info!("Exit status: {}", status);
-}

+ 7 - 2
src/main.rs

@@ -31,6 +31,9 @@ use librespot::playback::mixer::{self, Mixer};
 use librespot::playback::player::Player;
 use librespot::playback::player::Player;
 use librespot::connect::spirc::{Spirc, SpircTask};
 use librespot::connect::spirc::{Spirc, SpircTask};
 
 
+mod player_event_handler;
+use player_event_handler::run_program_on_events;
+
 fn usage(program: &str, opts: &getopts::Options) -> String {
 fn usage(program: &str, opts: &getopts::Options) -> String {
     let brief = format!("Usage: {} [options]", program);
     let brief = format!("Usage: {} [options]", program);
     opts.usage(&brief)
     opts.usage(&brief)
@@ -94,6 +97,7 @@ fn setup(args: &[String]) -> Setup {
         .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
         .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
         .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM")
         .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM")
         .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM")
         .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM")
+        .optopt("", "onchange", "Run PROGRAM between two tracks.", "PROGRAM")
         .optflag("v", "verbose", "Enable verbose output")
         .optflag("v", "verbose", "Enable verbose output")
         .optopt("u", "username", "Username to sign in with", "USERNAME")
         .optopt("u", "username", "Username to sign in with", "USERNAME")
         .optopt("p", "password", "Password", "PASSWORD")
         .optopt("p", "password", "Password", "PASSWORD")
@@ -185,8 +189,9 @@ fn setup(args: &[String]) -> Setup {
 
 
         PlayerConfig {
         PlayerConfig {
             bitrate: bitrate,
             bitrate: bitrate,
-            onstart: matches.opt_str("onstart"),
-            onstop: matches.opt_str("onstop"),
+            event_sender: run_program_on_events(matches.opt_str("onstart"),
+                                                matches.opt_str("onstop"),
+                                                matches.opt_str("onchange"))
         }
         }
     };
     };
 
 

+ 50 - 0
src/player_event_handler.rs

@@ -0,0 +1,50 @@
+use std::process::Command;
+use std::sync::mpsc::{channel, Sender};
+use std::thread;
+use librespot::playback::config::PlayerEvent;
+
+fn run_program(program: &str, args: Vec<String>) {
+    info!("Running {}", program);
+    let mut v: Vec<&str> = program.split_whitespace().collect();
+    let status = Command::new(&v.remove(0))
+        .args(&v)
+        .args(args)
+        .status()
+        .expect("program failed to start");
+    info!("Exit status: {}", status);
+}
+
+pub fn run_program_on_events(onstart: Option<String>, 
+                             onstop: Option<String>,
+                             onchange: Option<String>) -> Option<Sender<PlayerEvent>> {
+    if onstart.is_none() && onstop.is_none() && onchange.is_none() {
+        None
+    } else {
+        let (sender, receiver) = channel();
+        thread::spawn(move || {
+            while let Ok(msg) = receiver.recv() {
+                match msg {
+                    PlayerEvent::Changed { old_track_id, new_track_id } => {
+                        let args = vec![old_track_id.to_base16(), new_track_id.to_base16()];
+                        if let Some(ref onchange) = onchange.as_ref() {
+                            run_program(onchange, args);
+                        }
+                    },
+                    PlayerEvent::Started { track_id } => {
+                        let args = vec![track_id.to_base16()];
+                        if let Some(ref onstart) = onstart.as_ref() {
+                            run_program(onstart, args);
+                        }
+                    }
+                    PlayerEvent::Stopped { track_id } =>  {
+                        let args = vec![track_id.to_base16()];
+                        if let Some(ref onstop) = onstop.as_ref() {
+                            run_program(onstop, args);
+                        }
+                    }
+                }
+            }
+        });
+        Some(sender)
+    }
+}