#![warn(missing_docs)]
extern crate json;
extern crate kernel32;
extern crate reqwest;
extern crate time;
extern crate winapi;
mod connector;
pub mod status;
#[cfg(windows)]
mod windows_process;
use crate::connector::{InternalSpotifyError, SpotifyConnector};
use crate::status::{SpotifyStatus, SpotifyStatusChange};
use std::thread::{self, JoinHandle};
use std::time::Duration;
#[cfg(windows)]
use windows_process::WindowsProcess;
type Result<T> = std::result::Result<T, SpotifyError>;
#[derive(Debug)]
pub enum SpotifyError {
InternalError(InternalSpotifyError),
ClientNotRunning,
WebHelperNotRunning,
}
pub struct Spotify {
connector: SpotifyConnector,
}
fn get_status(connector: &SpotifyConnector) -> Result<SpotifyStatus> {
match connector.fetch_status_json() {
Ok(result) => Ok(SpotifyStatus::from(result)),
Err(error) => Err(SpotifyError::InternalError(error)),
}
}
impl Spotify {
#[cfg(windows)]
pub fn connect() -> Result<Spotify> {
if !Spotify::spotify_webhelper_alive() {
return Err(SpotifyError::WebHelperNotRunning);
}
Spotify::new_unchecked()
}
#[cfg(not(windows))]
pub fn connect() -> Result<Spotify> {
Spotify::new_unchecked()
}
fn new_unchecked() -> Result<Spotify> {
match SpotifyConnector::connect_new() {
Ok(result) => Ok(Spotify { connector: result }),
Err(error) => Err(SpotifyError::InternalError(error)),
}
}
pub fn poll<F: 'static>(self, f: F) -> JoinHandle<()>
where
F: Fn(&Spotify, SpotifyStatus, SpotifyStatusChange) -> bool,
F: std::marker::Send,
{
thread::spawn(move || {
let sleep_time = Duration::from_millis(250);
let mut last: Option<SpotifyStatus> = None;
let mut curr: Option<SpotifyStatus>;
let mut first = true;
loop {
curr = get_status(&self.connector).ok();
{
let last = last.clone();
if first && curr.is_some() {
let curr = curr.clone().unwrap();
if !f(&self, curr.clone(), SpotifyStatusChange::new_true()) {
break;
}
first = false;
} else if !first && curr.is_some() && last.is_some() {
let curr = curr.clone().unwrap();
let last = last.unwrap();
if !f(&self, curr.clone(), SpotifyStatusChange::from((curr, last))) {
break;
}
}
}
if curr.is_some() {
last = curr.clone();
}
thread::sleep(sleep_time);
}
})
}
pub fn status(&self) -> Result<SpotifyStatus> {
get_status(&self.connector)
}
pub fn play(&self, track: String) -> bool {
let track: String = {
let track = track
.replace("https://", "http://") .trim_start_matches("http://") .trim_start_matches("open.spotify.com") .replace('/', ":") .trim_start_matches(':') .to_owned();
if track.starts_with("spotify:") {
track
} else {
format!("spotify:{}", track) }
};
self.connector.request_play(track)
}
pub fn pause(&self) -> bool {
self.connector.request_pause(true)
}
pub fn resume(&self) -> bool {
self.connector.request_pause(false)
}
#[cfg(windows)]
fn spotify_webhelper_alive() -> bool {
let process = "SpotifyWebHelper.exe";
WindowsProcess::find_by_name(process).is_some()
}
}