mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Refactor the whole architecture.
Use less threads, makes it much simpler to reason about.
This commit is contained in:
parent
c1ce87dbbd
commit
2a2f227bef
10 changed files with 299 additions and 542 deletions
|
@ -5,22 +5,25 @@ use std::io::{self, SeekFrom};
|
||||||
use std::slice::bytes::copy_memory;
|
use std::slice::bytes::copy_memory;
|
||||||
use std::sync::{Arc, Condvar, Mutex};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
use std::sync::mpsc::{self, TryRecvError};
|
use std::sync::mpsc::{self, TryRecvError};
|
||||||
|
|
||||||
use stream::{StreamRequest, StreamEvent};
|
|
||||||
use util::FileId;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
|
use stream::StreamEvent;
|
||||||
|
use util::FileId;
|
||||||
|
use session::Session;
|
||||||
|
|
||||||
const CHUNK_SIZE : usize = 0x40000;
|
const CHUNK_SIZE : usize = 0x40000;
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub struct AudioFile<'s> {
|
||||||
pub struct AudioFile {
|
|
||||||
position: usize,
|
position: usize,
|
||||||
seek: mpsc::Sender<u64>,
|
seek: mpsc::Sender<u64>,
|
||||||
shared: Arc<AudioFileShared>,
|
shared: Arc<AudioFileShared>,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
thread: thread::JoinGuard<'s, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioFileShared {
|
struct AudioFileShared {
|
||||||
fileid: FileId,
|
file_id: FileId,
|
||||||
size: usize,
|
size: usize,
|
||||||
data: Mutex<AudioFileData>,
|
data: Mutex<AudioFileData>,
|
||||||
cond: Condvar
|
cond: Condvar
|
||||||
|
@ -31,38 +34,24 @@ struct AudioFileData {
|
||||||
bitmap: BitSet,
|
bitmap: BitSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFile {
|
impl <'s> AudioFile <'s> {
|
||||||
pub fn new(fileid: FileId, streams: mpsc::Sender<StreamRequest>) -> AudioFile {
|
pub fn new(session: &Session, file_id: FileId) -> AudioFile {
|
||||||
let (tx, rx) = mpsc::channel();
|
let mut it = session.stream(file_id, 0, 1).into_iter()
|
||||||
|
.filter_map(|event| {
|
||||||
streams.send(StreamRequest {
|
|
||||||
id: fileid,
|
|
||||||
offset: 0,
|
|
||||||
size: 1,
|
|
||||||
callback: tx
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
let size = {
|
|
||||||
let mut size = None;
|
|
||||||
for event in rx.iter() {
|
|
||||||
match event {
|
match event {
|
||||||
StreamEvent::Header(id, data) => {
|
StreamEvent::Header(id, ref data) if id == 0x3 => {
|
||||||
if id == 0x3 {
|
Some(BigEndian::read_u32(data) as usize * 4)
|
||||||
size = Some(BigEndian::read_u32(&data) * 4);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
_ => None
|
||||||
StreamEvent::Data(_) => break
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
size.unwrap() as usize
|
|
||||||
};
|
let size = it.next().unwrap();
|
||||||
|
|
||||||
let bufsize = size + (CHUNK_SIZE - size % CHUNK_SIZE);
|
let bufsize = size + (CHUNK_SIZE - size % CHUNK_SIZE);
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
let shared = Arc::new(AudioFileShared {
|
let shared = Arc::new(AudioFileShared {
|
||||||
fileid: fileid,
|
file_id: file_id,
|
||||||
size: size,
|
size: size,
|
||||||
data: Mutex::new(AudioFileData {
|
data: Mutex::new(AudioFileData {
|
||||||
buffer: vec![0u8; bufsize],
|
buffer: vec![0u8; bufsize],
|
||||||
|
@ -71,25 +60,23 @@ impl AudioFile {
|
||||||
cond: Condvar::new(),
|
cond: Condvar::new(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let file = AudioFile {
|
let shared_ = shared.clone();
|
||||||
position: 0,
|
let (seek_tx, seek_rx) = mpsc::channel();
|
||||||
seek: tx,
|
|
||||||
shared: shared.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
thread::spawn( move || { AudioFile::fetch(shared, streams, rx); });
|
let file = AudioFile {
|
||||||
|
thread: thread::scoped( move || { AudioFile::fetch(session, shared_, seek_rx); }),
|
||||||
|
position: 0,
|
||||||
|
seek: seek_tx,
|
||||||
|
shared: shared,
|
||||||
|
};
|
||||||
|
|
||||||
file
|
file
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_chunk(shared: &Arc<AudioFileShared>, streams: &mpsc::Sender<StreamRequest>, index: usize) {
|
fn fetch_chunk(session: &Session, shared: &Arc<AudioFileShared>, index: usize) {
|
||||||
let (tx, rx) = mpsc::channel();
|
let rx = session.stream(shared.file_id,
|
||||||
streams.send(StreamRequest {
|
(index * CHUNK_SIZE / 4) as u32,
|
||||||
id: shared.fileid,
|
(CHUNK_SIZE / 4) as u32);
|
||||||
offset: (index * CHUNK_SIZE / 4) as u32,
|
|
||||||
size: (CHUNK_SIZE / 4) as u32,
|
|
||||||
callback: tx
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
let mut offset = 0usize;
|
let mut offset = 0usize;
|
||||||
for event in rx.iter() {
|
for event in rx.iter() {
|
||||||
|
@ -114,7 +101,7 @@ impl AudioFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(shared: Arc<AudioFileShared>, streams: mpsc::Sender<StreamRequest>, seek: mpsc::Receiver<u64>) {
|
fn fetch(session: &Session, shared: Arc<AudioFileShared>, seek: mpsc::Receiver<u64>) {
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
loop {
|
loop {
|
||||||
index = if index * CHUNK_SIZE < shared.size {
|
index = if index * CHUNK_SIZE < shared.size {
|
||||||
|
@ -138,13 +125,13 @@ impl AudioFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
if index * CHUNK_SIZE < shared.size {
|
if index * CHUNK_SIZE < shared.size {
|
||||||
AudioFile::fetch_chunk(&shared, &streams, index)
|
AudioFile::fetch_chunk(session, &shared, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Read for AudioFile {
|
impl <'s> io::Read for AudioFile <'s> {
|
||||||
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
||||||
let index = self.position / CHUNK_SIZE;
|
let index = self.position / CHUNK_SIZE;
|
||||||
let offset = self.position % CHUNK_SIZE;
|
let offset = self.position % CHUNK_SIZE;
|
||||||
|
@ -163,7 +150,7 @@ impl io::Read for AudioFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Seek for AudioFile {
|
impl <'s> io::Seek for AudioFile <'s> {
|
||||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
let newpos = match pos {
|
let newpos = match pos {
|
||||||
SeekFrom::Start(offset) => offset as i64,
|
SeekFrom::Start(offset) => offset as i64,
|
||||||
|
|
|
@ -1,109 +1,63 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::mpsc;
|
use std::sync::{mpsc, Future};
|
||||||
use std::io::{Cursor, Write};
|
use std::io::{Cursor, Write};
|
||||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
||||||
use readall::ReadAllExt;
|
use readall::ReadAllExt;
|
||||||
|
|
||||||
use connection::Packet;
|
use util::{SpotifyId, FileId, IgnoreExt};
|
||||||
use util::{SpotifyId, FileId};
|
use session::Session;
|
||||||
use util::Either::{Left, Right};
|
use connection::PacketHandler;
|
||||||
use subsystem::Subsystem;
|
|
||||||
|
|
||||||
pub struct AudioKeyRequest {
|
|
||||||
pub track: SpotifyId,
|
|
||||||
pub file: FileId,
|
|
||||||
pub callback: AudioKeyCallback,
|
|
||||||
}
|
|
||||||
pub type AudioKey = [u8; 16];
|
pub type AudioKey = [u8; 16];
|
||||||
pub struct AudioKeyResponse(pub AudioKey);
|
|
||||||
pub type AudioKeyCallback = mpsc::Sender<AudioKeyResponse>;
|
|
||||||
|
|
||||||
type AudioKeyId = u32;
|
type AudioKeyId = u32;
|
||||||
|
|
||||||
pub struct AudioKeyManager {
|
pub struct AudioKeyManager {
|
||||||
next_seq: AudioKeyId,
|
next_seq: AudioKeyId,
|
||||||
callbacks: HashMap<AudioKeyId, AudioKeyCallback>,
|
callbacks: HashMap<AudioKeyId, mpsc::Sender<AudioKey>>,
|
||||||
|
|
||||||
requests: mpsc::Receiver<AudioKeyRequest>,
|
|
||||||
packet_rx: mpsc::Receiver<Packet>,
|
|
||||||
packet_tx: mpsc::Sender<Packet>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioKeyManager {
|
impl AudioKeyManager {
|
||||||
pub fn new(tx: mpsc::Sender<Packet>) -> (AudioKeyManager,
|
pub fn new() -> AudioKeyManager {
|
||||||
mpsc::Sender<AudioKeyRequest>,
|
AudioKeyManager {
|
||||||
mpsc::Sender<Packet>) {
|
|
||||||
let (req_tx, req_rx) = mpsc::channel();
|
|
||||||
let (pkt_tx, pkt_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
(AudioKeyManager {
|
|
||||||
next_seq: 1,
|
next_seq: 1,
|
||||||
callbacks: HashMap::new(),
|
callbacks: HashMap::new(),
|
||||||
|
}
|
||||||
requests: req_rx,
|
|
||||||
packet_rx: pkt_rx,
|
|
||||||
packet_tx: tx
|
|
||||||
}, req_tx, pkt_tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&mut self, req: AudioKeyRequest) {
|
pub fn request(&mut self, session: &Session, track: SpotifyId, file: FileId)
|
||||||
|
-> Future<AudioKey> {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let seq = self.next_seq;
|
let seq = self.next_seq;
|
||||||
self.next_seq += 1;
|
self.next_seq += 1;
|
||||||
|
|
||||||
let mut data : Vec<u8> = Vec::new();
|
let mut data : Vec<u8> = Vec::new();
|
||||||
data.write(&req.file).unwrap();
|
data.write(&file).unwrap();
|
||||||
data.write(&req.track.to_raw()).unwrap();
|
data.write(&track.to_raw()).unwrap();
|
||||||
data.write_u32::<BigEndian>(seq).unwrap();
|
data.write_u32::<BigEndian>(seq).unwrap();
|
||||||
data.write_u16::<BigEndian>(0x0000).unwrap();
|
data.write_u16::<BigEndian>(0x0000).unwrap();
|
||||||
|
|
||||||
self.packet_tx.send(Packet {
|
session.send_packet(0xc, &data).unwrap();
|
||||||
cmd: 0xc,
|
|
||||||
data: data
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
self.callbacks.insert(seq, req.callback);
|
self.callbacks.insert(seq, tx);
|
||||||
|
|
||||||
|
Future::from_receiver(rx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn packet(&mut self, packet: Packet) {
|
impl PacketHandler for AudioKeyManager {
|
||||||
assert_eq!(packet.cmd, 0xd);
|
fn handle(&mut self, cmd: u8, data: Vec<u8>) {
|
||||||
|
assert_eq!(cmd, 0xd);
|
||||||
|
|
||||||
let mut data = Cursor::new(&packet.data as &[u8]);
|
let mut data = Cursor::new(data);
|
||||||
let seq = data.read_u32::<BigEndian>().unwrap();
|
let seq = data.read_u32::<BigEndian>().unwrap();
|
||||||
let mut key = [0u8; 16];
|
let mut key = [0u8; 16];
|
||||||
data.read_all(&mut key).unwrap();
|
data.read_all(&mut key).unwrap();
|
||||||
|
|
||||||
match self.callbacks.remove(&seq) {
|
match self.callbacks.remove(&seq) {
|
||||||
Some(callback) => callback.send(AudioKeyResponse(key)).unwrap(),
|
Some(callback) => callback.send(key).ignore(),
|
||||||
None => ()
|
None => ()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Subsystem for AudioKeyManager {
|
|
||||||
fn run(mut self) {
|
|
||||||
loop {
|
|
||||||
match {
|
|
||||||
let requests = &self.requests;
|
|
||||||
let packets = &self.packet_rx;
|
|
||||||
|
|
||||||
select!{
|
|
||||||
r = requests.recv() => {
|
|
||||||
Left(r.unwrap())
|
|
||||||
},
|
|
||||||
p = packets.recv() => {
|
|
||||||
Right(p.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
Left(req) => {
|
|
||||||
self.request(req);
|
|
||||||
}
|
|
||||||
Right(pkt) => {
|
|
||||||
self.packet(pkt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::mpsc;
|
|
||||||
|
|
||||||
use keys::SharedKeys;
|
use keys::SharedKeys;
|
||||||
|
|
||||||
|
@ -84,7 +83,7 @@ impl PlainConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CipherConnection {
|
impl CipherConnection {
|
||||||
pub fn send_encrypted_packet(&mut self, cmd: u8, data: &[u8]) -> Result<()> {
|
pub fn send_packet(&mut self, cmd: u8, data: &[u8]) -> Result<()> {
|
||||||
try!(self.stream.write_u8(cmd)); try!(self.stream.write_u16::<BigEndian>(data.len() as u16));
|
try!(self.stream.write_u8(cmd)); try!(self.stream.write_u16::<BigEndian>(data.len() as u16));
|
||||||
try!(self.stream.write(data));
|
try!(self.stream.write(data));
|
||||||
|
|
||||||
|
@ -107,70 +106,15 @@ impl CipherConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Packet {
|
pub trait PacketHandler {
|
||||||
pub cmd: u8,
|
fn handle(&mut self, cmd: u8, data: Vec<u8>);
|
||||||
pub data: Vec<u8>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SendThread {
|
/*
|
||||||
connection: CipherConnection,
|
|
||||||
receiver: mpsc::Receiver<Packet>,
|
|
||||||
}
|
|
||||||
impl SendThread {
|
|
||||||
pub fn new(connection: CipherConnection)
|
|
||||||
-> (SendThread, mpsc::Sender<Packet>) {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
(SendThread {
|
|
||||||
connection: connection,
|
|
||||||
receiver: rx
|
|
||||||
}, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(mut self) {
|
|
||||||
for req in self.receiver {
|
|
||||||
self.connection.send_encrypted_packet(
|
|
||||||
req.cmd, &req.data).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PacketDispatch {
|
|
||||||
pub main: mpsc::Sender<Packet>,
|
|
||||||
pub stream: mpsc::Sender<Packet>,
|
|
||||||
pub mercury: mpsc::Sender<Packet>,
|
|
||||||
pub audio_key: mpsc::Sender<Packet>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RecvThread {
|
|
||||||
connection: CipherConnection,
|
|
||||||
dispatch: PacketDispatch
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RecvThread {
|
|
||||||
pub fn new(connection: CipherConnection, dispatch: PacketDispatch)
|
|
||||||
-> RecvThread {
|
|
||||||
RecvThread {
|
|
||||||
connection: connection,
|
|
||||||
dispatch: dispatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(mut self) {
|
|
||||||
loop {
|
|
||||||
let (cmd, data) = self.connection.recv_packet().unwrap();
|
|
||||||
let packet = Packet {
|
|
||||||
cmd: cmd,
|
|
||||||
data: data
|
|
||||||
};
|
|
||||||
|
|
||||||
match packet.cmd {
|
match packet.cmd {
|
||||||
0x09 => &self.dispatch.stream,
|
0x09 => &self.dispatch.stream,
|
||||||
0xd | 0xe => &self.dispatch.audio_key,
|
0xd | 0xe => &self.dispatch.audio_key,
|
||||||
0xb2...0xb6 => &self.dispatch.mercury,
|
0xb2...0xb6 => &self.dispatch.mercury,
|
||||||
_ => &self.dispatch.main,
|
_ => &self.dispatch.main,
|
||||||
}.send(packet).unwrap();
|
}.send(packet).unwrap();
|
||||||
|
*/
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
64
src/main.rs
64
src/main.rs
|
@ -1,7 +1,8 @@
|
||||||
#![crate_name = "librespot"]
|
#![crate_name = "librespot"]
|
||||||
|
|
||||||
#![feature(plugin,zero_one,iter_arith,slice_position_elem,slice_bytes,bitset,mpsc_select,arc_weak,append)]
|
#![feature(plugin,scoped,zero_one,iter_arith,slice_position_elem,slice_bytes,bitset,arc_weak,append,future)]
|
||||||
#![allow(unused_imports,dead_code)]
|
#![allow(deprecated)]
|
||||||
|
//#![allow(unused_imports,dead_code)]
|
||||||
|
|
||||||
#![plugin(protobuf_macros)]
|
#![plugin(protobuf_macros)]
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use] extern crate lazy_static;
|
||||||
|
@ -38,10 +39,10 @@ use std::clone::Clone;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
|
||||||
use protobuf::core::Message;
|
use protobuf::core::Message;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef};
|
use metadata::{AlbumRef, ArtistRef, TrackRef};
|
||||||
use session::{Config, Session};
|
use session::{Config, Session};
|
||||||
use util::SpotifyId;
|
use util::SpotifyId;
|
||||||
use util::version::version_string;
|
use util::version::version_string;
|
||||||
|
@ -69,12 +70,17 @@ fn main() {
|
||||||
session.login(username.clone(), password);
|
session.login(username.clone(), password);
|
||||||
session.poll();
|
session.poll();
|
||||||
|
|
||||||
let ident = session.config.device_id.clone();
|
let poll_thread = thread::scoped(|| {
|
||||||
|
loop {
|
||||||
|
session.poll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
SpircManager {
|
SpircManager {
|
||||||
session: session,
|
session: &session,
|
||||||
username: username.clone(),
|
username: username.clone(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
ident: ident,
|
ident: session.config.device_id.clone(),
|
||||||
device_type: 5,
|
device_type: 5,
|
||||||
|
|
||||||
state_update_id: 0,
|
state_update_id: 0,
|
||||||
|
@ -88,21 +94,17 @@ fn main() {
|
||||||
state: PlayerState::new()
|
state: PlayerState::new()
|
||||||
}.run();
|
}.run();
|
||||||
|
|
||||||
/*
|
poll_thread.join();
|
||||||
loop {
|
|
||||||
session.poll();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
|
fn print_track(session: &Session, track_id: SpotifyId) {
|
||||||
let track : TrackRef = cache.get(track_id);
|
let track : TrackRef = session.metadata(track_id);
|
||||||
|
|
||||||
let album : AlbumRef = {
|
let album : AlbumRef = {
|
||||||
let handle = track.wait();
|
let handle = track.wait();
|
||||||
let data = handle.unwrap();
|
let data = handle.unwrap();
|
||||||
eprintln!("{}", data.name);
|
eprintln!("{}", data.name);
|
||||||
cache.get(data.album)
|
session.metadata(data.album)
|
||||||
};
|
};
|
||||||
|
|
||||||
let artists : Vec<ArtistRef> = {
|
let artists : Vec<ArtistRef> = {
|
||||||
|
@ -110,7 +112,7 @@ fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
|
||||||
let data = handle.unwrap();
|
let data = handle.unwrap();
|
||||||
eprintln!("{}", data.name);
|
eprintln!("{}", data.name);
|
||||||
data.artists.iter().map(|id| {
|
data.artists.iter().map(|id| {
|
||||||
cache.get(*id)
|
session.metadata(*id)
|
||||||
}).collect()
|
}).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,7 +161,6 @@ impl PlayerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import(&mut self, state: &protocol::spirc::State) {
|
fn import(&mut self, state: &protocol::spirc::State) {
|
||||||
//println!("{:?}", state);
|
|
||||||
self.status = state.get_status();
|
self.status = state.get_status();
|
||||||
|
|
||||||
self.context_uri = state.get_context_uri().to_string();
|
self.context_uri = state.get_context_uri().to_string();
|
||||||
|
@ -203,8 +204,8 @@ impl PlayerState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SpircManager {
|
struct SpircManager<'s> {
|
||||||
session: Session,
|
session: &'s Session,
|
||||||
username: String,
|
username: String,
|
||||||
state_update_id: i64,
|
state_update_id: i64,
|
||||||
seq_nr: u32,
|
seq_nr: u32,
|
||||||
|
@ -221,24 +222,16 @@ struct SpircManager {
|
||||||
state: PlayerState
|
state: PlayerState
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpircManager {
|
impl <'s> SpircManager<'s> {
|
||||||
fn run(&mut self) {
|
fn run(&mut self) {
|
||||||
let (tx, rx) = mpsc::channel();
|
let rx = self.session
|
||||||
|
.mercury_sub(format!("hm://remote/user/{}/v23", self.username))
|
||||||
self.session.mercury.send(MercuryRequest{
|
.into_iter().map(|pkt| {
|
||||||
method: MercuryMethod::SUB,
|
|
||||||
uri: format!("hm://remote/user/{}/v23", self.username),
|
|
||||||
content_type: None,
|
|
||||||
callback: Some(tx),
|
|
||||||
payload: Vec::new()
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
self.notify(None);
|
|
||||||
|
|
||||||
let rx = rx.into_iter().map(|pkt| {
|
|
||||||
protobuf::parse_from_bytes::<protocol::spirc::Frame>(pkt.payload.front().unwrap()).unwrap()
|
protobuf::parse_from_bytes::<protocol::spirc::Frame>(pkt.payload.front().unwrap()).unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.notify(None);
|
||||||
|
|
||||||
for frame in rx {
|
for frame in rx {
|
||||||
println!("{:?} {} {} {} {}",
|
println!("{:?} {} {} {} {}",
|
||||||
frame.get_typ(),
|
frame.get_typ(),
|
||||||
|
@ -328,13 +321,12 @@ impl SpircManager {
|
||||||
pkt.set_state(self.state.export());
|
pkt.set_state(self.state.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.session.mercury.send(MercuryRequest{
|
self.session.mercury(MercuryRequest{
|
||||||
method: MercuryMethod::SEND,
|
method: MercuryMethod::SEND,
|
||||||
uri: format!("hm://remote/user/{}", self.username),
|
uri: format!("hm://remote/user/{}", self.username),
|
||||||
content_type: None,
|
content_type: None,
|
||||||
callback: None,
|
|
||||||
payload: vec![ pkt.write_to_bytes().unwrap() ]
|
payload: vec![ pkt.write_to_bytes().unwrap() ]
|
||||||
}).unwrap();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn device_state(&mut self) -> protocol::spirc::DeviceState {
|
fn device_state(&mut self) -> protocol::spirc::DeviceState {
|
||||||
|
|
153
src/mercury.rs
153
src/mercury.rs
|
@ -5,12 +5,12 @@ use std::collections::{HashMap, LinkedList};
|
||||||
use std::io::{Cursor, Read, Write};
|
use std::io::{Cursor, Read, Write};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::sync::mpsc;
|
use std::sync::{mpsc, Future};
|
||||||
|
|
||||||
use connection::Packet;
|
|
||||||
use librespot_protocol as protocol;
|
use librespot_protocol as protocol;
|
||||||
use subsystem::Subsystem;
|
use session::Session;
|
||||||
use util::Either::{Left, Right};
|
use connection::PacketHandler;
|
||||||
|
use util::IgnoreExt;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum MercuryMethod {
|
pub enum MercuryMethod {
|
||||||
|
@ -24,7 +24,6 @@ pub struct MercuryRequest {
|
||||||
pub method: MercuryMethod,
|
pub method: MercuryMethod,
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub callback: Option<MercuryCallback>,
|
|
||||||
pub payload: Vec<Vec<u8>>
|
pub payload: Vec<Vec<u8>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,22 +33,16 @@ pub struct MercuryResponse {
|
||||||
pub payload: LinkedList<Vec<u8>>
|
pub payload: LinkedList<Vec<u8>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MercuryCallback = mpsc::Sender<MercuryResponse>;
|
|
||||||
|
|
||||||
pub struct MercuryPending {
|
pub struct MercuryPending {
|
||||||
parts: LinkedList<Vec<u8>>,
|
parts: LinkedList<Vec<u8>>,
|
||||||
partial: Option<Vec<u8>>,
|
partial: Option<Vec<u8>>,
|
||||||
callback: Option<MercuryCallback>
|
callback: Option<mpsc::Sender<MercuryResponse>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MercuryManager {
|
pub struct MercuryManager {
|
||||||
next_seq: u32,
|
next_seq: u32,
|
||||||
pending: HashMap<Vec<u8>, MercuryPending>,
|
pending: HashMap<Vec<u8>, MercuryPending>,
|
||||||
subscriptions: HashMap<String, MercuryCallback>,
|
subscriptions: HashMap<String, mpsc::Sender<MercuryResponse>>,
|
||||||
|
|
||||||
requests: mpsc::Receiver<MercuryRequest>,
|
|
||||||
packet_tx: mpsc::Sender<Packet>,
|
|
||||||
packet_rx: mpsc::Receiver<Packet>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MercuryMethod {
|
impl fmt::Display for MercuryMethod {
|
||||||
|
@ -64,24 +57,17 @@ impl fmt::Display for MercuryMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MercuryManager {
|
impl MercuryManager {
|
||||||
pub fn new(tx: mpsc::Sender<Packet>) -> (MercuryManager,
|
pub fn new() -> MercuryManager {
|
||||||
mpsc::Sender<MercuryRequest>,
|
MercuryManager {
|
||||||
mpsc::Sender<Packet>) {
|
|
||||||
let (req_tx, req_rx) = mpsc::channel();
|
|
||||||
let (pkt_tx, pkt_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
(MercuryManager {
|
|
||||||
next_seq: 0,
|
next_seq: 0,
|
||||||
pending: HashMap::new(),
|
pending: HashMap::new(),
|
||||||
subscriptions: HashMap::new(),
|
subscriptions: HashMap::new(),
|
||||||
|
}
|
||||||
requests: req_rx,
|
|
||||||
packet_rx: pkt_rx,
|
|
||||||
packet_tx: tx,
|
|
||||||
}, req_tx, pkt_tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&mut self, req: MercuryRequest) {
|
pub fn request(&mut self, session: &Session, req: MercuryRequest)
|
||||||
|
-> Future<MercuryResponse> {
|
||||||
|
|
||||||
let mut seq = [0u8; 4];
|
let mut seq = [0u8; 4];
|
||||||
BigEndian::write_u32(&mut seq, self.next_seq);
|
BigEndian::write_u32(&mut seq, self.next_seq);
|
||||||
self.next_seq += 1;
|
self.next_seq += 1;
|
||||||
|
@ -93,20 +79,31 @@ impl MercuryManager {
|
||||||
_ => 0xb2,
|
_ => 0xb2,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.packet_tx.send(Packet {
|
session.send_packet(cmd, &data).unwrap();
|
||||||
cmd: cmd,
|
|
||||||
data: data
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
if req.method != MercuryMethod::SUB {
|
let (tx, rx) = mpsc::channel();
|
||||||
self.pending.insert(seq.to_vec(), MercuryPending{
|
self.pending.insert(seq.to_vec(), MercuryPending{
|
||||||
parts: LinkedList::new(),
|
parts: LinkedList::new(),
|
||||||
partial: None,
|
partial: None,
|
||||||
callback: req.callback,
|
callback: Some(tx),
|
||||||
});
|
});
|
||||||
} else if let Some(cb) = req.callback {
|
|
||||||
self.subscriptions.insert(req.uri, cb);
|
Future::from_receiver(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(&mut self, session: &Session, uri: String)
|
||||||
|
-> mpsc::Receiver<MercuryResponse> {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
self.subscriptions.insert(uri.clone(), tx);
|
||||||
|
|
||||||
|
self.request(session, MercuryRequest{
|
||||||
|
method: MercuryMethod::SUB,
|
||||||
|
uri: uri,
|
||||||
|
content_type: None,
|
||||||
|
payload: Vec::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_part(mut s: &mut Read) -> Vec<u8> {
|
fn parse_part(mut s: &mut Read) -> Vec<u8> {
|
||||||
|
@ -133,14 +130,44 @@ impl MercuryManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref ch) = callback {
|
if let Some(ref ch) = callback {
|
||||||
|
// Ignore send error.
|
||||||
|
// It simply means the receiver was closed
|
||||||
ch.send(MercuryResponse{
|
ch.send(MercuryResponse{
|
||||||
uri: header.get_uri().to_string(),
|
uri: header.get_uri().to_string(),
|
||||||
payload: pending.parts
|
payload: pending.parts
|
||||||
}).unwrap();
|
}).ignore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_packet(&mut self, cmd: u8, data: Vec<u8>) {
|
fn encode_request(&self, seq: &[u8], req: &MercuryRequest) -> Vec<u8> {
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
||||||
|
packet.write_all(seq).unwrap();
|
||||||
|
packet.write_u8(1).unwrap(); // Flags: FINAL
|
||||||
|
packet.write_u16::<BigEndian>(1 + req.payload.len() as u16).unwrap(); // Part count
|
||||||
|
|
||||||
|
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
|
||||||
|
uri: req.uri.clone(),
|
||||||
|
method: req.method.to_string(),
|
||||||
|
});
|
||||||
|
if let Some(ref content_type) = req.content_type {
|
||||||
|
header.set_content_type(content_type.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
|
||||||
|
header.write_to_writer(&mut packet).unwrap();
|
||||||
|
|
||||||
|
for p in &req.payload {
|
||||||
|
packet.write_u16::<BigEndian>(p.len() as u16).unwrap();
|
||||||
|
packet.write(&p).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketHandler for MercuryManager {
|
||||||
|
fn handle(&mut self, cmd: u8, data: Vec<u8>) {
|
||||||
let mut packet = Cursor::new(data);
|
let mut packet = Cursor::new(data);
|
||||||
|
|
||||||
let seq = {
|
let seq = {
|
||||||
|
@ -185,59 +212,5 @@ impl MercuryManager {
|
||||||
self.pending.insert(seq, pending);
|
self.pending.insert(seq, pending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_request(&self, seq: &[u8], req: &MercuryRequest) -> Vec<u8> {
|
|
||||||
let mut packet = Vec::new();
|
|
||||||
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
|
||||||
packet.write_all(seq).unwrap();
|
|
||||||
packet.write_u8(1).unwrap(); // Flags: FINAL
|
|
||||||
packet.write_u16::<BigEndian>(1 + req.payload.len() as u16).unwrap(); // Part count
|
|
||||||
|
|
||||||
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
|
|
||||||
uri: req.uri.clone(),
|
|
||||||
method: req.method.to_string(),
|
|
||||||
});
|
|
||||||
if let Some(ref content_type) = req.content_type {
|
|
||||||
header.set_content_type(content_type.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
|
|
||||||
header.write_to_writer(&mut packet).unwrap();
|
|
||||||
|
|
||||||
for p in &req.payload {
|
|
||||||
packet.write_u16::<BigEndian>(p.len() as u16).unwrap();
|
|
||||||
packet.write(&p).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
packet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Subsystem for MercuryManager {
|
|
||||||
fn run(mut self) {
|
|
||||||
loop {
|
|
||||||
match {
|
|
||||||
let requests = &self.requests;
|
|
||||||
let packets = &self.packet_rx;
|
|
||||||
|
|
||||||
select!{
|
|
||||||
r = requests.recv() => {
|
|
||||||
Left(r.unwrap())
|
|
||||||
},
|
|
||||||
p = packets.recv() => {
|
|
||||||
Right(p.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
Left(req) => {
|
|
||||||
self.request(req);
|
|
||||||
}
|
|
||||||
Right(pkt) => {
|
|
||||||
self.handle_packet(pkt.cmd, pkt.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
102
src/metadata.rs
102
src/metadata.rs
|
@ -3,13 +3,13 @@ use std::any::{Any, TypeId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::slice::bytes::copy_memory;
|
use std::slice::bytes::copy_memory;
|
||||||
use std::sync::{mpsc, Arc, Condvar, Mutex, MutexGuard, Weak};
|
use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use librespot_protocol as protocol;
|
use librespot_protocol as protocol;
|
||||||
use mercury::{MercuryRequest, MercuryMethod};
|
use mercury::{MercuryRequest, MercuryMethod};
|
||||||
use subsystem::Subsystem;
|
|
||||||
use util::{SpotifyId, FileId};
|
use util::{SpotifyId, FileId};
|
||||||
|
use session::Session;
|
||||||
|
|
||||||
pub trait MetadataTrait : Send + Any + 'static {
|
pub trait MetadataTrait : Send + Any + 'static {
|
||||||
type Message: protobuf::MessageStatic;
|
type Message: protobuf::MessageStatic;
|
||||||
|
@ -119,40 +119,6 @@ pub type TrackRef = MetadataRef<Track>;
|
||||||
pub type AlbumRef = MetadataRef<Album>;
|
pub type AlbumRef = MetadataRef<Album>;
|
||||||
pub type ArtistRef = MetadataRef<Artist>;
|
pub type ArtistRef = MetadataRef<Artist>;
|
||||||
|
|
||||||
pub struct MetadataCache {
|
|
||||||
metadata: mpsc::Sender<MetadataRequest>,
|
|
||||||
cache: HashMap<(SpotifyId, TypeId), Box<Any + 'static>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetadataCache {
|
|
||||||
pub fn new(metadata: mpsc::Sender<MetadataRequest>) -> MetadataCache {
|
|
||||||
MetadataCache {
|
|
||||||
metadata: metadata,
|
|
||||||
cache: HashMap::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get<T: MetadataTrait>(&mut self, id: SpotifyId)
|
|
||||||
-> MetadataRef<T> {
|
|
||||||
let key = (id, TypeId::of::<T>());
|
|
||||||
|
|
||||||
self.cache.get(&key)
|
|
||||||
.and_then(|x| x.downcast_ref::<Weak<Metadata<T>>>())
|
|
||||||
.and_then(|x| x.upgrade())
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
let x : MetadataRef<T> = Arc::new(Metadata{
|
|
||||||
id: id,
|
|
||||||
state: Mutex::new(MetadataState::Loading),
|
|
||||||
cond: Condvar::new()
|
|
||||||
});
|
|
||||||
|
|
||||||
self.cache.insert(key, Box::new(x.downgrade()));
|
|
||||||
self.metadata.send(T::request(x.clone())).unwrap();
|
|
||||||
x
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <T: MetadataTrait> Metadata<T> {
|
impl <T: MetadataTrait> Metadata<T> {
|
||||||
pub fn id(&self) -> SpotifyId {
|
pub fn id(&self) -> SpotifyId {
|
||||||
self.id
|
self.id
|
||||||
|
@ -214,34 +180,46 @@ pub enum MetadataRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MetadataManager {
|
pub struct MetadataManager {
|
||||||
requests: mpsc::Receiver<MetadataRequest>,
|
cache: HashMap<(SpotifyId, TypeId), Box<Any + Send>>
|
||||||
mercury: mpsc::Sender<MercuryRequest>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetadataManager {
|
impl MetadataManager {
|
||||||
pub fn new(mercury: mpsc::Sender<MercuryRequest>) -> (MetadataManager,
|
pub fn new() -> MetadataManager {
|
||||||
mpsc::Sender<MetadataRequest>) {
|
MetadataManager {
|
||||||
let (tx, rx) = mpsc::channel();
|
cache: HashMap::new()
|
||||||
(MetadataManager {
|
}
|
||||||
requests: rx,
|
|
||||||
mercury: mercury
|
|
||||||
}, tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load<T: MetadataTrait> (&self, object: MetadataRef<T>) {
|
pub fn get<T: MetadataTrait>(&mut self, session: &Session, id: SpotifyId)
|
||||||
let mercury = self.mercury.clone();
|
-> MetadataRef<T> {
|
||||||
thread::spawn(move || {
|
let key = (id, TypeId::of::<T>());
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
mercury.send(MercuryRequest {
|
self.cache.get(&key)
|
||||||
|
.and_then(|x| x.downcast_ref::<Weak<Metadata<T>>>())
|
||||||
|
.and_then(|x| x.upgrade())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let x : MetadataRef<T> = Arc::new(Metadata{
|
||||||
|
id: id,
|
||||||
|
state: Mutex::new(MetadataState::Loading),
|
||||||
|
cond: Condvar::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
self.cache.insert(key, Box::new(x.downgrade()));
|
||||||
|
self.load(session, x.clone());
|
||||||
|
x
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load<T: MetadataTrait> (&self, session: &Session, object: MetadataRef<T>) {
|
||||||
|
let rx = session.mercury(MercuryRequest {
|
||||||
method: MercuryMethod::GET,
|
method: MercuryMethod::GET,
|
||||||
uri: format!("{}/{}", T::base_url(), object.id.to_base16()),
|
uri: format!("{}/{}", T::base_url(), object.id.to_base16()),
|
||||||
content_type: None,
|
content_type: None,
|
||||||
callback: Some(tx),
|
|
||||||
payload: Vec::new()
|
payload: Vec::new()
|
||||||
}).unwrap();
|
});
|
||||||
|
|
||||||
let response = rx.recv().unwrap();
|
thread::spawn(move || {
|
||||||
|
let response = rx.into_inner();
|
||||||
|
|
||||||
let msg : T::Message = protobuf::parse_from_bytes(
|
let msg : T::Message = protobuf::parse_from_bytes(
|
||||||
response.payload.front().unwrap()).unwrap();
|
response.payload.front().unwrap()).unwrap();
|
||||||
|
@ -251,21 +229,3 @@ impl MetadataManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subsystem for MetadataManager {
|
|
||||||
fn run(self) {
|
|
||||||
for req in self.requests.iter() {
|
|
||||||
match req {
|
|
||||||
MetadataRequest::Artist(artist) => {
|
|
||||||
self.load(artist)
|
|
||||||
}
|
|
||||||
MetadataRequest::Album(album) => {
|
|
||||||
self.load(album)
|
|
||||||
}
|
|
||||||
MetadataRequest::Track(track) => {
|
|
||||||
self.load(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use portaudio;
|
use portaudio;
|
||||||
use std::sync::mpsc;
|
|
||||||
use vorbis;
|
use vorbis;
|
||||||
|
|
||||||
use audio_key::{AudioKeyRequest, AudioKeyResponse};
|
|
||||||
use metadata::TrackRef;
|
use metadata::TrackRef;
|
||||||
use session::Session;
|
use session::Session;
|
||||||
use audio_file::AudioFile;
|
use audio_file::AudioFile;
|
||||||
|
@ -15,24 +13,13 @@ impl Player {
|
||||||
pub fn play(session: &Session, track: TrackRef) {
|
pub fn play(session: &Session, track: TrackRef) {
|
||||||
let file_id = *track.wait().unwrap().files.first().unwrap();
|
let file_id = *track.wait().unwrap().files.first().unwrap();
|
||||||
|
|
||||||
let key = {
|
let key = session.audio_key(track.id(), file_id).into_inner();
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
session.audio_key.send(AudioKeyRequest {
|
|
||||||
track: track.id(),
|
|
||||||
file: file_id,
|
|
||||||
callback: tx
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
let AudioKeyResponse(key) = rx.recv().unwrap();
|
|
||||||
key
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut decoder =
|
let mut decoder =
|
||||||
vorbis::Decoder::new(
|
vorbis::Decoder::new(
|
||||||
Subfile::new(
|
Subfile::new(
|
||||||
AudioDecrypt::new(key,
|
AudioDecrypt::new(key,
|
||||||
AudioFile::new(file_id, session.stream.clone())), 0xa7)).unwrap();
|
AudioFile::new(session, file_id)), 0xa7)).unwrap();
|
||||||
//decoder.time_seek(60f64).unwrap();
|
//decoder.time_seek(60f64).unwrap();
|
||||||
|
|
||||||
portaudio::initialize().unwrap();
|
portaudio::initialize().unwrap();
|
||||||
|
|
131
src/session.rs
131
src/session.rs
|
@ -2,17 +2,19 @@ use crypto::digest::Digest;
|
||||||
use crypto::sha1::Sha1;
|
use crypto::sha1::Sha1;
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::sync::mpsc;
|
use std::sync::{Mutex, Arc, Future, mpsc};
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use audio_key;
|
use connection::{self, PlainConnection, CipherConnection};
|
||||||
use connection::{PlainConnection, Packet, PacketDispatch, SendThread, RecvThread};
|
|
||||||
use keys::PrivateKeys;
|
use keys::PrivateKeys;
|
||||||
use librespot_protocol as protocol;
|
use librespot_protocol as protocol;
|
||||||
use mercury;
|
use util::{SpotifyId, FileId};
|
||||||
use metadata;
|
|
||||||
use stream;
|
use mercury::{MercuryManager, MercuryRequest, MercuryResponse};
|
||||||
use subsystem::Subsystem;
|
use metadata::{MetadataManager, MetadataRef, MetadataTrait};
|
||||||
|
use stream::{StreamManager, StreamEvent};
|
||||||
|
use audio_key::{AudioKeyManager, AudioKey};
|
||||||
|
use connection::PacketHandler;
|
||||||
|
|
||||||
use util;
|
use util;
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
@ -24,15 +26,16 @@ pub struct Config {
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
|
|
||||||
packet_rx: mpsc::Receiver<Packet>,
|
mercury: Mutex<MercuryManager>,
|
||||||
pub packet_tx: mpsc::Sender<Packet>,
|
metadata: Mutex<MetadataManager>,
|
||||||
|
stream: Mutex<StreamManager>,
|
||||||
pub audio_key: mpsc::Sender<audio_key::AudioKeyRequest>,
|
audio_key: Mutex<AudioKeyManager>,
|
||||||
pub mercury: mpsc::Sender<mercury::MercuryRequest>,
|
rx_connection: Mutex<CipherConnection>,
|
||||||
pub metadata: mpsc::Sender<metadata::MetadataRequest>,
|
tx_connection: Mutex<CipherConnection>,
|
||||||
pub stream: mpsc::Sender<stream::StreamRequest>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionRef = Arc<Session>;
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn new(mut config: Config) -> Session {
|
pub fn new(mut config: Config) -> Session {
|
||||||
config.device_id = {
|
config.device_id = {
|
||||||
|
@ -105,41 +108,16 @@ impl Session {
|
||||||
|
|
||||||
let cipher_connection = connection.setup_cipher(shared_keys);
|
let cipher_connection = connection.setup_cipher(shared_keys);
|
||||||
|
|
||||||
let (send_thread, tx) = SendThread::new(cipher_connection.clone());
|
|
||||||
|
|
||||||
let (main_tx, rx) = mpsc::channel();
|
|
||||||
let (mercury, mercury_req, mercury_pkt)
|
|
||||||
= mercury::MercuryManager::new(tx.clone());
|
|
||||||
let (metadata, metadata_req)
|
|
||||||
= metadata::MetadataManager::new(mercury_req.clone());
|
|
||||||
let (stream, stream_req, stream_pkt)
|
|
||||||
= stream::StreamManager::new(tx.clone());
|
|
||||||
let (audio_key, audio_key_req, audio_key_pkt)
|
|
||||||
= audio_key::AudioKeyManager::new(tx.clone());
|
|
||||||
|
|
||||||
let recv_thread = RecvThread::new(cipher_connection, PacketDispatch {
|
|
||||||
main: main_tx,
|
|
||||||
stream: stream_pkt,
|
|
||||||
mercury: mercury_pkt,
|
|
||||||
audio_key: audio_key_pkt
|
|
||||||
});
|
|
||||||
|
|
||||||
thread::spawn(move || send_thread.run());
|
|
||||||
thread::spawn(move || recv_thread.run());
|
|
||||||
|
|
||||||
mercury.start();
|
|
||||||
metadata.start();
|
|
||||||
stream.start();
|
|
||||||
audio_key.start();
|
|
||||||
|
|
||||||
Session {
|
Session {
|
||||||
config: config,
|
config: config,
|
||||||
packet_rx: rx,
|
|
||||||
packet_tx: tx,
|
rx_connection: Mutex::new(cipher_connection.clone()),
|
||||||
mercury: mercury_req,
|
tx_connection: Mutex::new(cipher_connection),
|
||||||
metadata: metadata_req,
|
|
||||||
stream: stream_req,
|
mercury: Mutex::new(MercuryManager::new()),
|
||||||
audio_key: audio_key_req,
|
metadata: Mutex::new(MetadataManager::new()),
|
||||||
|
stream: Mutex::new(StreamManager::new()),
|
||||||
|
audio_key: Mutex::new(AudioKeyManager::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,32 +144,47 @@ impl Session {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.packet_tx.send(Packet {
|
self.send_packet(0xab, &packet.write_to_bytes().unwrap()).unwrap();
|
||||||
cmd: 0xab,
|
|
||||||
data: packet.write_to_bytes().unwrap()
|
|
||||||
}).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll(&self) {
|
pub fn poll(&self) {
|
||||||
let packet = self.packet_rx.recv().unwrap();
|
let (cmd, data) =
|
||||||
|
self.rx_connection.lock().unwrap().recv_packet().unwrap();
|
||||||
|
|
||||||
match packet.cmd {
|
match cmd {
|
||||||
0x4 => { // PING
|
0x4 => self.send_packet(0x49, &data).unwrap(),
|
||||||
self.packet_tx.send(Packet {
|
0x4a => (),
|
||||||
cmd: 0x49,
|
0x9 => self.stream.lock().unwrap().handle(cmd, data),
|
||||||
data: packet.data
|
0xd | 0xe => self.audio_key.lock().unwrap().handle(cmd, data),
|
||||||
}).unwrap();
|
0xb2...0xb6 => self.mercury.lock().unwrap().handle(cmd, data),
|
||||||
}
|
0xac => eprintln!("Authentication succeedded"),
|
||||||
0x4a => { // PONG
|
0xad => eprintln!("Authentication failed"),
|
||||||
}
|
|
||||||
0xac => { // AUTHENTICATED
|
|
||||||
eprintln!("Authentication succeedded");
|
|
||||||
}
|
|
||||||
0xad => {
|
|
||||||
eprintln!("Authentication failed");
|
|
||||||
}
|
|
||||||
_ => ()
|
_ => ()
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_packet(&self, cmd: u8, data: &[u8]) -> connection::Result<()> {
|
||||||
|
self.tx_connection.lock().unwrap().send_packet(cmd, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn audio_key(&self, track: SpotifyId, file: FileId) -> Future<AudioKey> {
|
||||||
|
self.audio_key.lock().unwrap().request(self, track, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stream(&self, file: FileId, offset: u32, size: u32) -> mpsc::Receiver<StreamEvent> {
|
||||||
|
self.stream.lock().unwrap().request(self, file, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> {
|
||||||
|
self.metadata.lock().unwrap().get(self, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mercury(&self, req: MercuryRequest) -> Future<MercuryResponse> {
|
||||||
|
self.mercury.lock().unwrap().request(self, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mercury_sub(&self, uri: String) -> mpsc::Receiver<MercuryResponse> {
|
||||||
|
self.mercury.lock().unwrap().subscribe(self, uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,18 +3,9 @@ use std::collections::HashMap;
|
||||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use connection::Packet;
|
|
||||||
use util::{ArcVec, FileId};
|
use util::{ArcVec, FileId};
|
||||||
use util::Either::{Left, Right};
|
use connection::PacketHandler;
|
||||||
use subsystem::Subsystem;
|
use session::Session;
|
||||||
|
|
||||||
pub type StreamCallback = mpsc::Sender<StreamEvent>;
|
|
||||||
pub struct StreamRequest {
|
|
||||||
pub id: FileId,
|
|
||||||
pub offset: u32,
|
|
||||||
pub size: u32,
|
|
||||||
pub callback: StreamCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum StreamEvent {
|
pub enum StreamEvent {
|
||||||
|
@ -31,36 +22,28 @@ enum ChannelMode {
|
||||||
|
|
||||||
struct Channel {
|
struct Channel {
|
||||||
mode: ChannelMode,
|
mode: ChannelMode,
|
||||||
callback: StreamCallback
|
callback: mpsc::Sender<StreamEvent>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamManager {
|
pub struct StreamManager {
|
||||||
next_id: ChannelId,
|
next_id: ChannelId,
|
||||||
channels: HashMap<ChannelId, Channel>,
|
channels: HashMap<ChannelId, Channel>,
|
||||||
|
|
||||||
requests: mpsc::Receiver<StreamRequest>,
|
|
||||||
packet_rx: mpsc::Receiver<Packet>,
|
|
||||||
packet_tx: mpsc::Sender<Packet>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamManager {
|
impl StreamManager {
|
||||||
pub fn new(tx: mpsc::Sender<Packet>) -> (StreamManager,
|
pub fn new() -> StreamManager {
|
||||||
mpsc::Sender<StreamRequest>,
|
StreamManager {
|
||||||
mpsc::Sender<Packet>) {
|
|
||||||
let (req_tx, req_rx) = mpsc::channel();
|
|
||||||
let (pkt_tx, pkt_rx) = mpsc::channel();
|
|
||||||
|
|
||||||
(StreamManager {
|
|
||||||
next_id: 0,
|
next_id: 0,
|
||||||
channels: HashMap::new(),
|
channels: HashMap::new(),
|
||||||
|
}
|
||||||
requests: req_rx,
|
|
||||||
packet_rx: pkt_rx,
|
|
||||||
packet_tx: tx
|
|
||||||
}, req_tx, pkt_tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(&mut self, req: StreamRequest) {
|
pub fn request(&mut self, session: &Session,
|
||||||
|
file: FileId, offset: u32, size: u32)
|
||||||
|
-> mpsc::Receiver<StreamEvent> {
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
let channel_id = self.next_id;
|
let channel_id = self.next_id;
|
||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
|
|
||||||
|
@ -72,22 +55,23 @@ impl StreamManager {
|
||||||
data.write_u32::<BigEndian>(0x00000000).unwrap();
|
data.write_u32::<BigEndian>(0x00000000).unwrap();
|
||||||
data.write_u32::<BigEndian>(0x00009C40).unwrap();
|
data.write_u32::<BigEndian>(0x00009C40).unwrap();
|
||||||
data.write_u32::<BigEndian>(0x00020000).unwrap();
|
data.write_u32::<BigEndian>(0x00020000).unwrap();
|
||||||
data.write(&req.id).unwrap();
|
data.write(&file).unwrap();
|
||||||
data.write_u32::<BigEndian>(req.offset).unwrap();
|
data.write_u32::<BigEndian>(offset).unwrap();
|
||||||
data.write_u32::<BigEndian>(req.offset + req.size).unwrap();
|
data.write_u32::<BigEndian>(offset + size).unwrap();
|
||||||
|
|
||||||
self.packet_tx.send(Packet {
|
session.send_packet(0x8, &data).unwrap();
|
||||||
cmd: 0x8,
|
|
||||||
data: data
|
|
||||||
}).unwrap();
|
|
||||||
|
|
||||||
self.channels.insert(channel_id, Channel {
|
self.channels.insert(channel_id, Channel {
|
||||||
mode: ChannelMode::Header,
|
mode: ChannelMode::Header,
|
||||||
callback: req.callback
|
callback: tx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
rx
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn packet(&mut self, data: Vec<u8>) {
|
impl PacketHandler for StreamManager {
|
||||||
|
fn handle(&mut self, _cmd: u8, data: Vec<u8>) {
|
||||||
let data = ArcVec::new(data);
|
let data = ArcVec::new(data);
|
||||||
let mut packet = Cursor::new(&data as &[u8]);
|
let mut packet = Cursor::new(&data as &[u8]);
|
||||||
|
|
||||||
|
@ -140,27 +124,3 @@ impl StreamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subsystem for StreamManager {
|
|
||||||
fn run(mut self) {
|
|
||||||
loop {
|
|
||||||
match {
|
|
||||||
let requests = &self.requests;
|
|
||||||
let packets = &self.packet_rx;
|
|
||||||
|
|
||||||
select!{
|
|
||||||
r = requests.recv() => {
|
|
||||||
Left(r.unwrap())
|
|
||||||
},
|
|
||||||
p = packets.recv() => {
|
|
||||||
Right(p.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
Left(req) => self.request(req),
|
|
||||||
Right(pkt) => self.packet(pkt.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -47,11 +47,6 @@ pub mod version {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Either<S,T> {
|
|
||||||
Left(S),
|
|
||||||
Right(T)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hexdump(data: &[u8]) {
|
pub fn hexdump(data: &[u8]) {
|
||||||
for b in data.iter() {
|
for b in data.iter() {
|
||||||
eprint!("{:02X} ", b);
|
eprint!("{:02X} ", b);
|
||||||
|
@ -59,3 +54,15 @@ pub fn hexdump(data: &[u8]) {
|
||||||
eprintln!("");
|
eprintln!("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IgnoreExt {
|
||||||
|
fn ignore(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T, E> IgnoreExt for Result<T, E> {
|
||||||
|
fn ignore(self) {
|
||||||
|
match self {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue