Browse Source

First attempt at a better playback event system.

Simon Persson 7 years ago
parent
commit
b0ee03112f
4 changed files with 125 additions and 45 deletions
  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 core::spotify_id::SpotifyId;
+use std::sync::mpsc::Sender;
 
 #[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
 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)]
 pub struct PlayerConfig {
     pub bitrate: Bitrate,
-    pub onstart: Option<String>,
-    pub onstop: Option<String>,
+    pub event_sender : Option<Sender<PlayerEvent>>,
 }
 
 impl Default for PlayerConfig {
     fn default() -> PlayerConfig {
         PlayerConfig {
             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::io::{Read, Seek, SeekFrom, Result};
 use std::mem;
-use std::process::Command;
 use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
 use std::thread;
 use std::time::Duration;
 
-use config::{Bitrate, PlayerConfig};
+use config::{Bitrate, PlayerConfig, PlayerEvent};
 use core::session::Session;
 use core::spotify_id::SpotifyId;
 
@@ -121,14 +120,16 @@ type Decoder = VorbisDecoder<Subfile<AudioDecrypt<AudioFile>>>;
 enum PlayerState {
     Stopped,
     Paused {
+        track_id: SpotifyId,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
     },
     Playing {
+        track_id: SpotifyId,
         decoder: Decoder,
         end_of_track: oneshot::Sender<()>,
     },
-
+    EndOfTrack { track_id: SpotifyId },
     Invalid,
 }
 
@@ -136,7 +137,7 @@ impl PlayerState {
     fn is_playing(&self) -> bool {
         use self::PlayerState::*;
         match *self {
-            Stopped | Paused { .. } => false,
+            Stopped | EndOfTrack { .. } | Paused { .. } => false,
             Playing { .. } => true,
             Invalid => panic!("invalid state"),
         }
@@ -145,7 +146,7 @@ impl PlayerState {
     fn decoder(&mut self) -> Option<&mut Decoder> {
         use self::PlayerState::*;
         match *self {
-            Stopped => None,
+            Stopped | EndOfTrack { .. } => None,
             Paused { ref mut decoder, .. } |
             Playing { ref mut decoder, .. } => Some(decoder),
             Invalid => panic!("invalid state"),
@@ -160,6 +161,7 @@ impl PlayerState {
                 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"),
             Invalid => panic!("invalid state"),
         }
@@ -168,10 +170,11 @@ impl PlayerState {
     fn paused_to_playing(&mut self) {
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
-            Paused { decoder, end_of_track } => {
+            Paused { decoder, end_of_track, track_id } => {
                 *self = Playing {
                     decoder: decoder,
                     end_of_track: end_of_track,
+                    track_id: track_id,
                 };
             }
             _ => panic!("invalid state"),
@@ -181,10 +184,11 @@ impl PlayerState {
     fn playing_to_paused(&mut self) {
         use self::PlayerState::*;
         match ::std::mem::replace(self, Invalid) {
-            Playing { decoder, end_of_track } => {
+            Playing { decoder, end_of_track, track_id } => {
                 *self = Paused {
                     decoder: decoder,
                     end_of_track: end_of_track,
+                    track_id: track_id,
                 };
             }
             _ => panic!("invalid state"),
@@ -274,9 +278,15 @@ impl PlayerInternal {
 
             None => {
                 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();
             }
         }
@@ -288,24 +298,35 @@ impl PlayerInternal {
             PlayerCommand::Load(track_id, play, position, end_of_track) => {
                 if self.state.is_playing() {
                     self.stop_sink_if_running();
-                    self.run_onstop();
                 }
 
                 match self.load_track(track_id, position as i64) {
                     Some(decoder) => {
                         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.state = PlayerState::Playing {
+                                track_id: track_id,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                             };
                         } else {
                             self.state = PlayerState::Paused {
+                                track_id: track_id,
                                 decoder: decoder,
                                 end_of_track: end_of_track,
                             };
+                            self.send_event(PlayerEvent::Stopped { track_id });
                         }
                     }
 
@@ -327,10 +348,10 @@ impl PlayerInternal {
             }
 
             PlayerCommand::Play => {
-                if let PlayerState::Paused { .. } = self.state {
+                if let PlayerState::Paused { track_id, .. } = self.state {
                     self.state.paused_to_playing();
 
-                    self.run_onstart();
+                    self.send_event(PlayerEvent::Started { track_id });
                     self.start_sink();
                 } else {
                     warn!("Player::play called from invalid state");
@@ -338,11 +359,11 @@ impl PlayerInternal {
             }
 
             PlayerCommand::Pause => {
-                if let PlayerState::Playing { .. } = self.state {
+                if let PlayerState::Playing { track_id, .. } = self.state {
                     self.state.playing_to_paused();
 
                     self.stop_sink_if_running();
-                    self.run_onstop();
+                    self.send_event(PlayerEvent::Stopped { track_id });
                 } else {
                     warn!("Player::pause called from invalid state");
                 }
@@ -350,12 +371,11 @@ impl PlayerInternal {
 
             PlayerCommand::Stop => {
                 match self.state {
-                    PlayerState::Playing { .. } => {
+                    PlayerState::Playing { track_id, .. }
+                    | PlayerState::Paused { track_id, .. }
+                    | PlayerState::EndOfTrack { track_id } => {
                         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;
                     },
                     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::connect::spirc::{Spirc, SpircTask};
 
+mod player_event_handler;
+use player_event_handler::run_program_on_events;
+
 fn usage(program: &str, opts: &getopts::Options) -> String {
     let brief = format!("Usage: {} [options]", program);
     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("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM")
         .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM")
+        .optopt("", "onchange", "Run PROGRAM between two tracks.", "PROGRAM")
         .optflag("v", "verbose", "Enable verbose output")
         .optopt("u", "username", "Username to sign in with", "USERNAME")
         .optopt("p", "password", "Password", "PASSWORD")
@@ -185,8 +189,9 @@ fn setup(args: &[String]) -> Setup {
 
         PlayerConfig {
             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)
+    }
+}