2015-12-28 16:47:53 +00:00
|
|
|
use eventual::{self, Async};
|
2016-02-05 20:54:47 +00:00
|
|
|
use std::borrow::Cow;
|
2016-01-20 15:47:05 +00:00
|
|
|
use std::sync::{mpsc, Mutex, Arc, MutexGuard};
|
2015-07-02 19:42:49 +00:00
|
|
|
use std::thread;
|
2016-03-13 15:15:15 +00:00
|
|
|
use std::io::{Read, Seek};
|
2015-09-01 11:20:37 +00:00
|
|
|
use vorbis;
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
use audio_decrypt::AudioDecrypt;
|
2016-03-20 16:16:11 +00:00
|
|
|
use audio_backend::Sink;
|
2016-01-02 02:30:24 +00:00
|
|
|
use metadata::{FileFormat, Track, TrackRef};
|
|
|
|
use session::{Bitrate, Session};
|
2016-05-04 08:11:03 +00:00
|
|
|
use util::{self, ReadSeek, SpotifyId, Subfile};
|
2016-04-24 13:48:15 +00:00
|
|
|
pub use spirc::PlayStatus;
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2016-03-13 15:15:15 +00:00
|
|
|
#[cfg(not(feature = "with-tremor"))]
|
|
|
|
fn vorbis_time_seek_ms<R>(decoder: &mut vorbis::Decoder<R>, ms: i64) -> Result<(), vorbis::VorbisError> where R: Read + Seek {
|
|
|
|
decoder.time_seek(ms as f64 / 1000f64)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(feature = "with-tremor"))]
|
|
|
|
fn vorbis_time_tell_ms<R>(decoder: &mut vorbis::Decoder<R>) -> Result<i64, vorbis::VorbisError> where R: Read + Seek {
|
2016-04-22 23:09:19 +00:00
|
|
|
decoder.time_tell().map(|t| (t * 1000f64) as i64)
|
2016-03-13 15:15:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "with-tremor")]
|
|
|
|
fn vorbis_time_seek_ms<R>(decoder: &mut vorbis::Decoder<R>, ms: i64) -> Result<(), vorbis::VorbisError> where R: Read + Seek {
|
|
|
|
decoder.time_seek(ms)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "with-tremor")]
|
|
|
|
fn vorbis_time_tell_ms<R>(decoder: &mut vorbis::Decoder<R>) -> Result<i64, vorbis::VorbisError> where R: Read + Seek {
|
|
|
|
decoder.time_tell()
|
|
|
|
}
|
|
|
|
|
2016-01-20 15:47:05 +00:00
|
|
|
pub type PlayerObserver = Box<Fn(&PlayerState) + Send>;
|
|
|
|
|
2016-04-24 13:48:15 +00:00
|
|
|
#[derive(Clone)]
|
2015-09-01 11:20:37 +00:00
|
|
|
pub struct Player {
|
2016-01-20 15:47:05 +00:00
|
|
|
state: Arc<Mutex<PlayerState>>,
|
|
|
|
observers: Arc<Mutex<Vec<PlayerObserver>>>,
|
2015-07-09 20:08:14 +00:00
|
|
|
|
|
|
|
commands: mpsc::Sender<PlayerCommand>,
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2016-02-22 05:41:37 +00:00
|
|
|
#[derive(Clone)]
|
2015-07-02 19:42:49 +00:00
|
|
|
pub struct PlayerState {
|
2016-04-24 13:48:15 +00:00
|
|
|
pub status: PlayStatus,
|
|
|
|
pub position_ms: u32,
|
|
|
|
pub position_measured_at: i64,
|
|
|
|
pub update_time: i64,
|
|
|
|
pub volume: u16,
|
|
|
|
pub track: Option<SpotifyId>,
|
|
|
|
|
|
|
|
pub end_of_track: bool,
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
struct PlayerInternal {
|
2016-01-20 15:47:05 +00:00
|
|
|
state: Arc<Mutex<PlayerState>>,
|
|
|
|
observers: Arc<Mutex<Vec<PlayerObserver>>>,
|
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
session: Session,
|
2015-07-02 19:42:49 +00:00
|
|
|
commands: mpsc::Receiver<PlayerCommand>,
|
|
|
|
}
|
|
|
|
|
2016-02-22 05:41:37 +00:00
|
|
|
#[derive(Debug)]
|
2015-07-09 20:08:14 +00:00
|
|
|
enum PlayerCommand {
|
|
|
|
Load(SpotifyId, bool, u32),
|
|
|
|
Play,
|
|
|
|
Pause,
|
2016-01-20 13:55:36 +00:00
|
|
|
Volume(u16),
|
2015-07-09 20:08:14 +00:00
|
|
|
Stop,
|
2016-01-02 15:19:39 +00:00
|
|
|
Seek(u32),
|
2016-04-24 13:48:15 +00:00
|
|
|
SeekAt(u32, i64),
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
impl Player {
|
2016-03-20 16:16:11 +00:00
|
|
|
pub fn new<F>(session: Session, sink_builder: F) -> Player
|
|
|
|
where F: FnOnce() -> Box<Sink> + Send + 'static {
|
2015-07-02 19:42:49 +00:00
|
|
|
let (cmd_tx, cmd_rx) = mpsc::channel();
|
|
|
|
|
2016-01-20 15:47:05 +00:00
|
|
|
let state = Arc::new(Mutex::new(PlayerState {
|
2015-07-02 19:42:49 +00:00
|
|
|
status: PlayStatus::kPlayStatusStop,
|
|
|
|
position_ms: 0,
|
|
|
|
position_measured_at: 0,
|
|
|
|
update_time: util::now_ms(),
|
2016-02-05 20:54:47 +00:00
|
|
|
volume: 0xFFFF,
|
2016-04-24 13:48:15 +00:00
|
|
|
track: None,
|
2015-07-09 22:09:40 +00:00
|
|
|
end_of_track: false,
|
2016-01-20 15:47:05 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
let observers = Arc::new(Mutex::new(Vec::new()));
|
2015-07-02 19:42:49 +00:00
|
|
|
|
|
|
|
let internal = PlayerInternal {
|
2016-01-01 23:16:12 +00:00
|
|
|
session: session,
|
2015-07-02 19:42:49 +00:00
|
|
|
commands: cmd_rx,
|
2016-01-02 15:19:39 +00:00
|
|
|
state: state.clone(),
|
2016-01-20 15:47:05 +00:00
|
|
|
observers: observers.clone(),
|
2015-07-02 19:42:49 +00:00
|
|
|
};
|
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
thread::spawn(move || internal.run(sink_builder()));
|
2015-09-01 11:20:37 +00:00
|
|
|
|
2015-07-02 19:42:49 +00:00
|
|
|
Player {
|
|
|
|
commands: cmd_tx,
|
|
|
|
state: state,
|
2016-01-20 15:47:05 +00:00
|
|
|
observers: observers,
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-09 20:08:14 +00:00
|
|
|
fn command(&self, cmd: PlayerCommand) {
|
2015-07-02 19:42:49 +00:00
|
|
|
self.commands.send(cmd).unwrap();
|
|
|
|
}
|
2016-01-20 14:11:49 +00:00
|
|
|
|
|
|
|
pub fn load(&self, track: SpotifyId, start_playing: bool, position_ms: u32) {
|
|
|
|
self.command(PlayerCommand::Load(track, start_playing, position_ms));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn play(&self) {
|
|
|
|
self.command(PlayerCommand::Play)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pause(&self) {
|
|
|
|
self.command(PlayerCommand::Pause)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stop(&self) {
|
|
|
|
self.command(PlayerCommand::Stop)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn seek(&self, position_ms: u32) {
|
|
|
|
self.command(PlayerCommand::Seek(position_ms));
|
|
|
|
}
|
|
|
|
|
2016-04-24 13:48:15 +00:00
|
|
|
pub fn seek_at(&self, position_ms: u32, measured_at: i64) {
|
|
|
|
self.command(PlayerCommand::SeekAt(position_ms, measured_at));
|
|
|
|
}
|
|
|
|
|
2016-02-22 05:41:37 +00:00
|
|
|
pub fn state(&self) -> PlayerState {
|
|
|
|
self.state.lock().unwrap().clone()
|
2016-01-20 14:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn volume(&self, vol: u16) {
|
|
|
|
self.command(PlayerCommand::Volume(vol));
|
|
|
|
}
|
|
|
|
|
2016-01-20 15:47:05 +00:00
|
|
|
pub fn add_observer(&self, observer: PlayerObserver) {
|
|
|
|
self.observers.lock().unwrap().push(observer);
|
2016-01-20 14:11:49 +00:00
|
|
|
}
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2016-02-05 20:54:47 +00:00
|
|
|
fn apply_volume(volume: u16, data: &[i16]) -> Cow<[i16]> {
|
|
|
|
// Fast path when volume is 100%
|
|
|
|
if volume == 0xFFFF {
|
|
|
|
Cow::Borrowed(data)
|
|
|
|
} else {
|
|
|
|
Cow::Owned(data.iter()
|
|
|
|
.map(|&x| {
|
|
|
|
(x as i32
|
|
|
|
* volume as i32
|
|
|
|
/ 0xFFFF) as i16
|
|
|
|
})
|
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 08:11:03 +00:00
|
|
|
fn find_available_alternative<'a>(session: &Session, track: &'a Track) -> Option<Cow<'a, Track>> {
|
|
|
|
if track.available {
|
|
|
|
Some(Cow::Borrowed(track))
|
|
|
|
} else {
|
|
|
|
let alternatives = track.alternatives
|
|
|
|
.iter()
|
|
|
|
.map(|alt_id| {
|
|
|
|
session.metadata::<Track>(*alt_id)
|
|
|
|
})
|
|
|
|
.collect::<Vec<TrackRef>>();
|
|
|
|
|
|
|
|
eventual::sequence(alternatives.into_iter()).iter().find(|alt| alt.available).map(Cow::Owned)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_track(session: &Session, track_id: SpotifyId) -> Option<vorbis::Decoder<Subfile<AudioDecrypt<Box<ReadSeek>>>>> {
|
|
|
|
let track = session.metadata::<Track>(track_id).await().unwrap();
|
|
|
|
|
|
|
|
let track = match find_available_alternative(session, &track) {
|
|
|
|
Some(track) => track,
|
|
|
|
None => {
|
|
|
|
warn!("Track \"{}\" is not available", track.name);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let format = match session.config().bitrate {
|
|
|
|
Bitrate::Bitrate96 => FileFormat::OGG_VORBIS_96,
|
|
|
|
Bitrate::Bitrate160 => FileFormat::OGG_VORBIS_160,
|
|
|
|
Bitrate::Bitrate320 => FileFormat::OGG_VORBIS_320,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-05-04 09:03:46 +00:00
|
|
|
let file_id = match track.files.get(&format) {
|
|
|
|
Some(&file_id) => file_id,
|
2016-05-04 08:11:03 +00:00
|
|
|
None => {
|
|
|
|
warn!("Track \"{}\" is not available in format {:?}", track.name, format);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let key = session.audio_key(track.id, file_id).await().unwrap();
|
|
|
|
|
|
|
|
let audio_file = Subfile::new(AudioDecrypt::new(key, session.audio_file(file_id)), 0xa7);
|
|
|
|
let decoder = vorbis::Decoder::new(audio_file).unwrap();
|
|
|
|
|
|
|
|
Some(decoder)
|
|
|
|
}
|
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
impl PlayerInternal {
|
2016-03-20 16:16:11 +00:00
|
|
|
fn run(self, sink: Box<Sink>) {
|
2015-07-02 19:42:49 +00:00
|
|
|
let mut decoder = None;
|
|
|
|
|
|
|
|
loop {
|
2016-01-20 15:47:05 +00:00
|
|
|
let playing = self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay;
|
2016-01-01 23:16:12 +00:00
|
|
|
let cmd = if playing {
|
2015-12-28 17:45:13 +00:00
|
|
|
self.commands.try_recv().ok()
|
|
|
|
} else {
|
|
|
|
Some(self.commands.recv().unwrap())
|
|
|
|
};
|
|
|
|
|
|
|
|
match cmd {
|
|
|
|
Some(PlayerCommand::Load(track_id, play, position)) => {
|
2015-07-09 21:04:19 +00:00
|
|
|
self.update(|state| {
|
|
|
|
if state.status == PlayStatus::kPlayStatusPlay {
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.stop().unwrap();
|
2015-07-09 21:04:19 +00:00
|
|
|
}
|
2015-07-09 22:09:40 +00:00
|
|
|
state.end_of_track = false;
|
2016-02-24 00:36:19 +00:00
|
|
|
state.status = PlayStatus::kPlayStatusPause;
|
2015-07-09 21:04:19 +00:00
|
|
|
state.position_ms = position;
|
|
|
|
state.position_measured_at = util::now_ms();
|
2016-04-24 13:48:15 +00:00
|
|
|
state.track = Some(track_id);
|
2016-01-02 15:48:44 +00:00
|
|
|
true
|
2015-07-09 21:04:19 +00:00
|
|
|
});
|
2015-12-28 16:47:53 +00:00
|
|
|
drop(decoder);
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2016-05-04 08:11:03 +00:00
|
|
|
decoder = match load_track(&self.session, track_id) {
|
|
|
|
Some(mut decoder) => {
|
|
|
|
vorbis_time_seek_ms(&mut decoder, position as i64).unwrap();
|
|
|
|
|
|
|
|
self.update(|state| {
|
|
|
|
state.status = if play {
|
|
|
|
sink.start().unwrap();
|
|
|
|
PlayStatus::kPlayStatusPlay
|
|
|
|
} else {
|
|
|
|
PlayStatus::kPlayStatusPause
|
|
|
|
};
|
|
|
|
state.position_ms = position;
|
|
|
|
state.position_measured_at = util::now_ms();
|
|
|
|
|
|
|
|
true
|
|
|
|
});
|
|
|
|
|
|
|
|
info!("Load Done");
|
|
|
|
Some(decoder)
|
|
|
|
}
|
2015-12-28 16:47:53 +00:00
|
|
|
|
2016-05-04 08:11:03 +00:00
|
|
|
None => {
|
|
|
|
self.update(|state| {
|
|
|
|
state.status = PlayStatus::kPlayStatusStop;
|
|
|
|
state.end_of_track = true;
|
|
|
|
true
|
|
|
|
});
|
2016-01-02 15:19:39 +00:00
|
|
|
|
2016-05-04 08:11:03 +00:00
|
|
|
None
|
|
|
|
}
|
2015-12-28 16:47:53 +00:00
|
|
|
}
|
2015-09-01 11:20:37 +00:00
|
|
|
|
2015-07-02 19:42:49 +00:00
|
|
|
|
|
|
|
}
|
2016-03-13 15:15:15 +00:00
|
|
|
Some(PlayerCommand::Seek(position)) => {
|
2016-03-13 15:23:56 +00:00
|
|
|
vorbis_time_seek_ms(decoder.as_mut().unwrap(), position as i64).unwrap();
|
2015-07-09 21:04:19 +00:00
|
|
|
self.update(|state| {
|
2016-03-13 15:15:15 +00:00
|
|
|
state.position_ms = vorbis_time_tell_ms(decoder.as_mut().unwrap()).unwrap() as u32;
|
2015-07-09 21:04:19 +00:00
|
|
|
state.position_measured_at = util::now_ms();
|
2016-01-02 15:48:44 +00:00
|
|
|
|
|
|
|
true
|
2015-07-09 21:04:19 +00:00
|
|
|
});
|
2016-01-02 15:19:39 +00:00
|
|
|
}
|
2016-04-24 13:48:15 +00:00
|
|
|
Some(PlayerCommand::SeekAt(position, measured_at)) => {
|
|
|
|
let position = (util::now_ms() - measured_at + position as i64) as u32;
|
|
|
|
|
|
|
|
vorbis_time_seek_ms(decoder.as_mut().unwrap(), position as i64).unwrap();
|
|
|
|
self.update(|state| {
|
|
|
|
state.position_ms = vorbis_time_tell_ms(decoder.as_mut().unwrap()).unwrap() as u32;
|
|
|
|
state.position_measured_at = util::now_ms();
|
|
|
|
|
|
|
|
true
|
|
|
|
});
|
|
|
|
}
|
2015-12-28 17:45:13 +00:00
|
|
|
Some(PlayerCommand::Play) => {
|
2015-07-09 21:04:19 +00:00
|
|
|
self.update(|state| {
|
|
|
|
state.status = PlayStatus::kPlayStatusPlay;
|
2016-03-13 15:15:15 +00:00
|
|
|
state.position_ms = vorbis_time_tell_ms(decoder.as_mut().unwrap()).unwrap() as u32;
|
2016-02-24 00:36:19 +00:00
|
|
|
state.position_measured_at = util::now_ms();
|
2016-01-02 15:48:44 +00:00
|
|
|
true
|
2015-07-09 21:04:19 +00:00
|
|
|
});
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.start().unwrap();
|
2016-01-02 15:19:39 +00:00
|
|
|
}
|
2015-12-28 17:45:13 +00:00
|
|
|
Some(PlayerCommand::Pause) => {
|
2015-07-09 21:04:19 +00:00
|
|
|
self.update(|state| {
|
|
|
|
state.status = PlayStatus::kPlayStatusPause;
|
|
|
|
state.update_time = util::now_ms();
|
2016-04-24 13:48:15 +00:00
|
|
|
state.position_ms = decoder.as_mut().map(|d| vorbis_time_tell_ms(d).unwrap()).unwrap_or(0) as u32;
|
2016-02-24 00:36:19 +00:00
|
|
|
state.position_measured_at = util::now_ms();
|
2016-01-02 15:48:44 +00:00
|
|
|
true
|
2015-07-09 21:04:19 +00:00
|
|
|
});
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.stop().unwrap();
|
2016-01-02 15:19:39 +00:00
|
|
|
}
|
2016-01-20 13:55:36 +00:00
|
|
|
Some(PlayerCommand::Volume(vol)) => {
|
|
|
|
self.update(|state| {
|
|
|
|
state.volume = vol;
|
|
|
|
true
|
|
|
|
});
|
2016-01-14 12:12:01 +00:00
|
|
|
}
|
2015-12-28 17:45:13 +00:00
|
|
|
Some(PlayerCommand::Stop) => {
|
2015-07-09 21:04:19 +00:00
|
|
|
self.update(|state| {
|
|
|
|
if state.status == PlayStatus::kPlayStatusPlay {
|
|
|
|
state.status = PlayStatus::kPlayStatusPause;
|
|
|
|
}
|
2016-02-24 00:36:19 +00:00
|
|
|
state.position_ms = 0;
|
|
|
|
state.position_measured_at = util::now_ms();
|
2016-01-02 15:48:44 +00:00
|
|
|
true
|
2015-07-09 21:04:19 +00:00
|
|
|
});
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.stop().unwrap();
|
2015-07-02 19:42:49 +00:00
|
|
|
decoder = None;
|
2016-01-02 15:19:39 +00:00
|
|
|
}
|
2015-12-28 17:45:13 +00:00
|
|
|
None => (),
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
2016-01-20 15:47:05 +00:00
|
|
|
if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay {
|
2016-03-14 00:49:21 +00:00
|
|
|
let packet = decoder.as_mut().unwrap().packets().next();
|
|
|
|
|
|
|
|
match packet {
|
2015-07-09 21:32:40 +00:00
|
|
|
Some(Ok(packet)) => {
|
2016-02-05 20:54:47 +00:00
|
|
|
let buffer = apply_volume(self.state.lock().unwrap().volume,
|
|
|
|
&packet.data);
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.write(&buffer).unwrap();
|
2016-04-24 13:48:15 +00:00
|
|
|
|
|
|
|
self.update(|state| {
|
|
|
|
state.position_ms = vorbis_time_tell_ms(decoder.as_mut().unwrap()).unwrap() as u32;
|
|
|
|
state.position_measured_at = util::now_ms();
|
|
|
|
|
|
|
|
false
|
|
|
|
});
|
2016-01-02 15:19:39 +00:00
|
|
|
}
|
2015-07-09 21:32:40 +00:00
|
|
|
Some(Err(vorbis::VorbisError::Hole)) => (),
|
|
|
|
Some(Err(e)) => panic!("Vorbis error {:?}", e),
|
|
|
|
None => {
|
|
|
|
self.update(|state| {
|
|
|
|
state.status = PlayStatus::kPlayStatusStop;
|
2015-07-09 22:09:40 +00:00
|
|
|
state.end_of_track = true;
|
2016-01-02 15:48:44 +00:00
|
|
|
true
|
2015-07-09 21:32:40 +00:00
|
|
|
});
|
|
|
|
|
2016-03-14 00:49:21 +00:00
|
|
|
sink.stop().unwrap();
|
2015-07-09 21:32:40 +00:00
|
|
|
decoder = None;
|
|
|
|
}
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-09 21:04:19 +00:00
|
|
|
|
|
|
|
fn update<F>(&self, f: F)
|
2016-01-02 15:19:39 +00:00
|
|
|
where F: FnOnce(&mut MutexGuard<PlayerState>) -> bool
|
|
|
|
{
|
2016-01-20 15:47:05 +00:00
|
|
|
let mut guard = self.state.lock().unwrap();
|
2015-07-09 21:04:19 +00:00
|
|
|
let update = f(&mut guard);
|
2016-01-20 15:47:05 +00:00
|
|
|
|
|
|
|
let observers = self.observers.lock().unwrap();
|
2015-07-09 21:04:19 +00:00
|
|
|
if update {
|
|
|
|
guard.update_time = util::now_ms();
|
2016-02-22 05:41:37 +00:00
|
|
|
let state = guard.clone();
|
|
|
|
drop(guard);
|
|
|
|
|
2016-01-20 15:47:05 +00:00
|
|
|
for observer in observers.iter() {
|
2016-02-22 05:41:37 +00:00
|
|
|
observer(&state);
|
2016-01-20 15:47:05 +00:00
|
|
|
}
|
2015-07-09 21:04:19 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
}
|
|
|
|
|
2016-01-20 14:11:49 +00:00
|
|
|
impl PlayerState {
|
|
|
|
pub fn status(&self) -> PlayStatus {
|
2016-01-02 15:48:44 +00:00
|
|
|
self.status
|
2015-07-09 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
2016-01-20 14:11:49 +00:00
|
|
|
pub fn position(&self) -> (u32, i64) {
|
2016-01-02 15:48:44 +00:00
|
|
|
(self.position_ms, self.position_measured_at)
|
2015-07-09 20:08:14 +00:00
|
|
|
}
|
2016-01-20 13:55:36 +00:00
|
|
|
|
2016-01-20 14:11:49 +00:00
|
|
|
pub fn volume(&self) -> u16 {
|
2016-01-20 13:55:36 +00:00
|
|
|
self.volume
|
2016-01-15 00:12:08 +00:00
|
|
|
}
|
2015-07-09 20:08:14 +00:00
|
|
|
|
2016-01-20 14:11:49 +00:00
|
|
|
pub fn update_time(&self) -> i64 {
|
2016-01-02 15:48:44 +00:00
|
|
|
self.update_time
|
2015-07-09 20:08:14 +00:00
|
|
|
}
|
2015-07-09 22:09:40 +00:00
|
|
|
|
2016-01-20 14:11:49 +00:00
|
|
|
pub fn end_of_track(&self) -> bool {
|
2016-01-02 15:48:44 +00:00
|
|
|
self.end_of_track
|
2015-07-09 22:09:40 +00:00
|
|
|
}
|
2015-07-09 20:08:14 +00:00
|
|
|
}
|