mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Reorganize the spirc/player interaction.
Updates are now sent through a callback mechanism rather than a channel.
This commit is contained in:
parent
463ce97661
commit
8fed885595
3 changed files with 136 additions and 86 deletions
|
@ -1,6 +1,6 @@
|
|||
#![crate_name = "librespot"]
|
||||
|
||||
#![feature(plugin,zero_one,iter_arith,mpsc_select)]
|
||||
#![feature(plugin,zero_one,iter_arith)]
|
||||
|
||||
#![plugin(protobuf_macros)]
|
||||
#![plugin(json_macros)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use eventual::{self, Async};
|
||||
use portaudio;
|
||||
use std::sync::{mpsc, Mutex, Arc, Condvar, MutexGuard};
|
||||
use std::sync::{mpsc, Mutex, Arc, MutexGuard};
|
||||
use std::thread;
|
||||
use vorbis;
|
||||
|
||||
|
@ -10,8 +10,11 @@ use audio_decrypt::AudioDecrypt;
|
|||
use util::{self, SpotifyId, Subfile};
|
||||
use spirc::PlayStatus;
|
||||
|
||||
pub type PlayerObserver = Box<Fn(&PlayerState) + Send>;
|
||||
|
||||
pub struct Player {
|
||||
state: Arc<(Mutex<PlayerState>, Condvar)>,
|
||||
state: Arc<Mutex<PlayerState>>,
|
||||
observers: Arc<Mutex<Vec<PlayerObserver>>>,
|
||||
|
||||
commands: mpsc::Sender<PlayerCommand>,
|
||||
}
|
||||
|
@ -27,7 +30,9 @@ pub struct PlayerState {
|
|||
}
|
||||
|
||||
struct PlayerInternal {
|
||||
state: Arc<(Mutex<PlayerState>, Condvar)>,
|
||||
state: Arc<Mutex<PlayerState>>,
|
||||
observers: Arc<Mutex<Vec<PlayerObserver>>>,
|
||||
|
||||
session: Session,
|
||||
commands: mpsc::Receiver<PlayerCommand>,
|
||||
}
|
||||
|
@ -45,20 +50,22 @@ impl Player {
|
|||
pub fn new(session: Session) -> Player {
|
||||
let (cmd_tx, cmd_rx) = mpsc::channel();
|
||||
|
||||
let state = Arc::new((Mutex::new(PlayerState {
|
||||
let state = Arc::new(Mutex::new(PlayerState {
|
||||
status: PlayStatus::kPlayStatusStop,
|
||||
position_ms: 0,
|
||||
position_measured_at: 0,
|
||||
update_time: util::now_ms(),
|
||||
volume: 0x8000,
|
||||
end_of_track: false,
|
||||
}),
|
||||
Condvar::new()));
|
||||
}));
|
||||
|
||||
let observers = Arc::new(Mutex::new(Vec::new()));
|
||||
|
||||
let internal = PlayerInternal {
|
||||
session: session,
|
||||
commands: cmd_rx,
|
||||
state: state.clone(),
|
||||
observers: observers.clone(),
|
||||
};
|
||||
|
||||
thread::spawn(move || internal.run());
|
||||
|
@ -66,6 +73,7 @@ impl Player {
|
|||
Player {
|
||||
commands: cmd_tx,
|
||||
state: state,
|
||||
observers: observers,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,31 +102,15 @@ impl Player {
|
|||
}
|
||||
|
||||
pub fn state(&self) -> MutexGuard<PlayerState> {
|
||||
self.state.0.lock().unwrap()
|
||||
self.state.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn volume(&self, vol: u16) {
|
||||
self.command(PlayerCommand::Volume(vol));
|
||||
}
|
||||
|
||||
pub fn updates(&self) -> mpsc::Receiver<i64> {
|
||||
let state = self.state.clone();
|
||||
let (update_tx, update_rx) = mpsc::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut guard = state.0.lock().unwrap();
|
||||
let mut last_update;
|
||||
loop {
|
||||
last_update = guard.update_time;
|
||||
update_tx.send(guard.update_time).unwrap();
|
||||
|
||||
while last_update >= guard.update_time {
|
||||
guard = state.1.wait(guard).unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
update_rx
|
||||
pub fn add_observer(&self, observer: PlayerObserver) {
|
||||
self.observers.lock().unwrap().push(observer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +127,7 @@ impl PlayerInternal {
|
|||
let mut decoder = None;
|
||||
|
||||
loop {
|
||||
let playing = self.state.0.lock().unwrap().status == PlayStatus::kPlayStatusPlay;
|
||||
let playing = self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay;
|
||||
let cmd = if playing {
|
||||
self.commands.try_recv().ok()
|
||||
} else {
|
||||
|
@ -252,14 +244,14 @@ impl PlayerInternal {
|
|||
None => (),
|
||||
}
|
||||
|
||||
if self.state.0.lock().unwrap().status == PlayStatus::kPlayStatusPlay {
|
||||
if self.state.lock().unwrap().status == PlayStatus::kPlayStatusPlay {
|
||||
match decoder.as_mut().unwrap().packets().next() {
|
||||
Some(Ok(packet)) => {
|
||||
let buffer = packet.data
|
||||
.iter()
|
||||
.map(|&x| {
|
||||
(x as i32
|
||||
* self.state.0.lock().unwrap().volume as i32
|
||||
* self.state.lock().unwrap().volume as i32
|
||||
/ 0xFFFF) as i16
|
||||
})
|
||||
.collect::<Vec<i16>>();
|
||||
|
@ -307,11 +299,15 @@ impl PlayerInternal {
|
|||
fn update<F>(&self, f: F)
|
||||
where F: FnOnce(&mut MutexGuard<PlayerState>) -> bool
|
||||
{
|
||||
let mut guard = self.state.0.lock().unwrap();
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
let update = f(&mut guard);
|
||||
|
||||
let observers = self.observers.lock().unwrap();
|
||||
if update {
|
||||
guard.update_time = util::now_ms();
|
||||
self.state.1.notify_all();
|
||||
for observer in observers.iter() {
|
||||
observer(&guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
164
src/spirc.rs
164
src/spirc.rs
|
@ -6,16 +6,19 @@ use session::Session;
|
|||
use util::SpotifyId;
|
||||
use util::version::version_string;
|
||||
use mercury::{MercuryRequest, MercuryMethod};
|
||||
use player::Player;
|
||||
use player::{Player, PlayerState};
|
||||
|
||||
use std::sync::{Mutex, Arc};
|
||||
|
||||
use librespot_protocol as protocol;
|
||||
pub use librespot_protocol::spirc::PlayStatus;
|
||||
|
||||
pub struct SpircManager {
|
||||
pub struct SpircManager(Arc<Mutex<SpircInternal>>);
|
||||
|
||||
struct SpircInternal {
|
||||
player: Player,
|
||||
session: Session,
|
||||
|
||||
state_update_id: i64,
|
||||
seq_nr: u32,
|
||||
|
||||
name: String,
|
||||
|
@ -41,11 +44,10 @@ impl SpircManager {
|
|||
let ident = session.0.data.read().unwrap().device_id.clone();
|
||||
let name = session.0.config.device_name.clone();
|
||||
|
||||
SpircManager {
|
||||
SpircManager(Arc::new(Mutex::new(SpircInternal {
|
||||
player: player,
|
||||
session: session,
|
||||
|
||||
state_update_id: 0,
|
||||
seq_nr: 0,
|
||||
|
||||
name: name,
|
||||
|
@ -64,56 +66,64 @@ impl SpircManager {
|
|||
|
||||
tracks: Vec::new(),
|
||||
index: 0,
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
let rx = self.session.mercury_sub(format!("hm://remote/user/{}/",
|
||||
self.session
|
||||
.0
|
||||
.data
|
||||
.read()
|
||||
.unwrap()
|
||||
.canonical_username
|
||||
.clone()));
|
||||
let updates = self.player.updates();
|
||||
let rx = {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
|
||||
self.notify(true, None);
|
||||
let rx = internal.session.mercury_sub(internal.uri());
|
||||
|
||||
loop {
|
||||
select! {
|
||||
pkt = rx.recv() => {
|
||||
let frame = protobuf::parse_from_bytes::<protocol::spirc::Frame>(
|
||||
pkt.unwrap().payload.first().unwrap()).unwrap();
|
||||
internal.notify(true, None);
|
||||
|
||||
println!("{:?} {} {} {} {}",
|
||||
frame.get_typ(),
|
||||
frame.get_device_state().get_name(),
|
||||
frame.get_ident(),
|
||||
frame.get_seq_nr(),
|
||||
frame.get_state_update_id());
|
||||
if frame.get_ident() != self.ident &&
|
||||
(frame.get_recipient().len() == 0 ||
|
||||
frame.get_recipient().contains(&self.ident)) {
|
||||
self.handle(frame);
|
||||
}
|
||||
},
|
||||
update_time = updates.recv() => {
|
||||
let end_of_track = self.player.state().end_of_track();
|
||||
if end_of_track {
|
||||
self.index = (self.index + 1) % self.tracks.len() as u32;
|
||||
let track = self.tracks[self.index as usize];
|
||||
self.player.load(track, true, 0);
|
||||
} else {
|
||||
self.state_update_id = update_time.unwrap();
|
||||
self.notify(false, None);
|
||||
}
|
||||
// Use a weak pointer to avoid creating an Rc cycle between the player and the
|
||||
// SpircManager
|
||||
let _self = Arc::downgrade(&self.0);
|
||||
internal.player.add_observer(Box::new(move |state| {
|
||||
if let Some(_self) = _self.upgrade() {
|
||||
let mut internal = _self.lock().unwrap();
|
||||
internal.on_update(state);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
rx
|
||||
};
|
||||
|
||||
for pkt in rx {
|
||||
let data = pkt.payload.first().unwrap();
|
||||
let frame = protobuf::parse_from_bytes::<protocol::spirc::Frame>(data).unwrap();
|
||||
|
||||
println!("{:?} {} {} {} {}",
|
||||
frame.get_typ(),
|
||||
frame.get_device_state().get_name(),
|
||||
frame.get_ident(),
|
||||
frame.get_seq_nr(),
|
||||
frame.get_state_update_id());
|
||||
|
||||
self.0.lock().unwrap().handle(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpircInternal {
|
||||
fn on_update(&mut self, player_state: &PlayerState) {
|
||||
let end_of_track = player_state.end_of_track();
|
||||
if end_of_track {
|
||||
self.index = (self.index + 1) % self.tracks.len() as u32;
|
||||
let track = self.tracks[self.index as usize];
|
||||
self.player.load(track, true, 0);
|
||||
} else {
|
||||
self.notify_with_player_state(false, None, player_state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle(&mut self, frame: protocol::spirc::Frame) {
|
||||
if frame.get_ident() == self.ident ||
|
||||
(frame.get_recipient().len() > 0 && !frame.get_recipient().contains(&self.ident)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if frame.get_recipient().len() > 0 {
|
||||
self.last_command_ident = frame.get_ident().to_owned();
|
||||
self.last_command_msgid = frame.get_seq_nr();
|
||||
|
@ -179,7 +189,12 @@ impl SpircManager {
|
|||
.map(|track| SpotifyId::from_raw(track.get_gid()))
|
||||
.collect();
|
||||
}
|
||||
|
||||
// FIXME: this entire function is duplicated in notify_with_player_state, but the borrow
|
||||
// checker makes it hard to refactor
|
||||
fn notify(&mut self, hello: bool, recipient: Option<&str>) {
|
||||
let player_state = self.player.state();
|
||||
|
||||
let mut pkt = protobuf_init!(protocol::spirc::Frame::new(), {
|
||||
version: 1,
|
||||
ident: self.ident.clone(),
|
||||
|
@ -191,22 +206,21 @@ impl SpircManager {
|
|||
protocol::spirc::MessageType::kMessageTypeNotify
|
||||
},
|
||||
|
||||
device_state: self.device_state(),
|
||||
device_state: self.device_state(&player_state),
|
||||
recipient: protobuf::RepeatedField::from_vec(
|
||||
recipient.map(|r| vec![r.to_owned()] ).unwrap_or(vec![])
|
||||
),
|
||||
state_update_id: self.state_update_id as i64
|
||||
state_update_id: player_state.update_time() as i64
|
||||
});
|
||||
|
||||
if self.is_active {
|
||||
pkt.set_state(self.spirc_state());
|
||||
pkt.set_state(self.spirc_state(&player_state));
|
||||
}
|
||||
|
||||
self.session
|
||||
.mercury(MercuryRequest {
|
||||
method: MercuryMethod::SEND,
|
||||
uri: format!("hm://remote/user/{}",
|
||||
self.session.0.data.read().unwrap().canonical_username.clone()),
|
||||
uri: self.uri(),
|
||||
content_type: None,
|
||||
payload: vec![pkt.write_to_bytes().unwrap()],
|
||||
})
|
||||
|
@ -214,12 +228,47 @@ impl SpircManager {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
fn spirc_state(&self) -> protocol::spirc::State {
|
||||
let state = self.player.state();
|
||||
let (position_ms, position_measured_at) = state.position();
|
||||
fn notify_with_player_state(&mut self,
|
||||
hello: bool,
|
||||
recipient: Option<&str>,
|
||||
player_state: &PlayerState) {
|
||||
let mut pkt = protobuf_init!(protocol::spirc::Frame::new(), {
|
||||
version: 1,
|
||||
ident: self.ident.clone(),
|
||||
protocol_version: "2.0.0".to_owned(),
|
||||
seq_nr: { self.seq_nr += 1; self.seq_nr },
|
||||
typ: if hello {
|
||||
protocol::spirc::MessageType::kMessageTypeHello
|
||||
} else {
|
||||
protocol::spirc::MessageType::kMessageTypeNotify
|
||||
},
|
||||
|
||||
device_state: self.device_state(&player_state),
|
||||
recipient: protobuf::RepeatedField::from_vec(
|
||||
recipient.map(|r| vec![r.to_owned()] ).unwrap_or(vec![])
|
||||
),
|
||||
state_update_id: player_state.update_time() as i64
|
||||
});
|
||||
|
||||
if self.is_active {
|
||||
pkt.set_state(self.spirc_state(&player_state));
|
||||
}
|
||||
|
||||
self.session
|
||||
.mercury(MercuryRequest {
|
||||
method: MercuryMethod::SEND,
|
||||
uri: self.uri(),
|
||||
content_type: None,
|
||||
payload: vec![pkt.write_to_bytes().unwrap()],
|
||||
})
|
||||
.fire();
|
||||
}
|
||||
|
||||
fn spirc_state(&self, player_state: &PlayerState) -> protocol::spirc::State {
|
||||
let (position_ms, position_measured_at) = player_state.position();
|
||||
|
||||
protobuf_init!(protocol::spirc::State::new(), {
|
||||
status: state.status(),
|
||||
status: player_state.status(),
|
||||
position_ms: position_ms,
|
||||
position_measured_at: position_measured_at as u64,
|
||||
|
||||
|
@ -240,12 +289,12 @@ impl SpircManager {
|
|||
})
|
||||
}
|
||||
|
||||
fn device_state(&self) -> protocol::spirc::DeviceState {
|
||||
fn device_state(&self, player_state: &PlayerState) -> protocol::spirc::DeviceState {
|
||||
protobuf_init!(protocol::spirc::DeviceState::new(), {
|
||||
sw_version: version_string(),
|
||||
is_active: self.is_active,
|
||||
can_play: self.can_play,
|
||||
volume: self.player.state().volume() as u32,
|
||||
volume: player_state.volume() as u32,
|
||||
name: self.name.clone(),
|
||||
error_code: 0,
|
||||
became_active_at: if self.is_active { self.became_active_at as i64 } else { 0 },
|
||||
|
@ -299,4 +348,9 @@ impl SpircManager {
|
|||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn uri(&self) -> String {
|
||||
format!("hm://remote/user/{}",
|
||||
self.session.0.data.read().unwrap().canonical_username.clone())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue