mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Support downloading Album Covers.
This commit is contained in:
parent
aa1d466e92
commit
4d712efb48
5 changed files with 97 additions and 54 deletions
27
src/album_cover.rs
Normal file
27
src/album_cover.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use eventual;
|
||||||
|
use std::io::Write;
|
||||||
|
use byteorder::{WriteBytesExt, BigEndian};
|
||||||
|
|
||||||
|
use session::Session;
|
||||||
|
use util::FileId;
|
||||||
|
use stream::StreamEvent;
|
||||||
|
|
||||||
|
pub fn get_album_cover(session: &Session, file_id: FileId)
|
||||||
|
-> eventual::Future<Vec<u8>, ()> {
|
||||||
|
|
||||||
|
let (channel_id, rx) = session.allocate_stream();
|
||||||
|
|
||||||
|
let mut req: Vec<u8> = Vec::new();
|
||||||
|
req.write_u16::<BigEndian>(channel_id).unwrap();
|
||||||
|
req.write_u16::<BigEndian>(0).unwrap();
|
||||||
|
req.write(&file_id.0).unwrap();
|
||||||
|
session.send_packet(0x19, &req).unwrap();
|
||||||
|
|
||||||
|
rx.map_err(|_| ())
|
||||||
|
.reduce(Vec::new(), |mut current, event| {
|
||||||
|
if let StreamEvent::Data(data) = event {
|
||||||
|
current.extend_from_slice(&data)
|
||||||
|
}
|
||||||
|
current
|
||||||
|
})
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ struct AudioFileShared {
|
||||||
impl AudioFileLoading {
|
impl AudioFileLoading {
|
||||||
fn new(session: &Session, file_id: FileId) -> AudioFileLoading {
|
fn new(session: &Session, file_id: FileId) -> AudioFileLoading {
|
||||||
let size = session.stream(file_id, 0, 1)
|
let size = session.stream(file_id, 0, 1)
|
||||||
.into_iter()
|
.iter()
|
||||||
.filter_map(|event| {
|
.filter_map(|event| {
|
||||||
match event {
|
match event {
|
||||||
StreamEvent::Header(id, ref data) if id == 0x3 => {
|
StreamEvent::Header(id, ref data) if id == 0x3 => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#[macro_use] pub mod util;
|
#[macro_use] pub mod util;
|
||||||
|
mod album_cover;
|
||||||
mod audio_decrypt;
|
mod audio_decrypt;
|
||||||
mod audio_file;
|
mod audio_file;
|
||||||
mod audio_key;
|
mod audio_key;
|
||||||
|
@ -13,6 +14,8 @@ pub mod player;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
pub mod spirc;
|
pub mod spirc;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
mod stream;
|
pub mod stream;
|
||||||
pub mod apresolve;
|
pub mod apresolve;
|
||||||
mod zeroconf;
|
mod zeroconf;
|
||||||
|
|
||||||
|
pub use album_cover::get_album_cover;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crypto::digest::Digest;
|
||||||
use crypto::sha1::Sha1;
|
use crypto::sha1::Sha1;
|
||||||
use crypto::hmac::Hmac;
|
use crypto::hmac::Hmac;
|
||||||
use crypto::mac::Mac;
|
use crypto::mac::Mac;
|
||||||
|
use eventual;
|
||||||
use eventual::Future;
|
use eventual::Future;
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
@ -20,10 +21,9 @@ use diffie_hellman::DHLocalKeys;
|
||||||
use mercury::{MercuryManager, MercuryRequest, MercuryResponse};
|
use mercury::{MercuryManager, MercuryRequest, MercuryResponse};
|
||||||
use metadata::{MetadataManager, MetadataRef, MetadataTrait};
|
use metadata::{MetadataManager, MetadataRef, MetadataTrait};
|
||||||
use protocol;
|
use protocol;
|
||||||
use stream::{StreamManager, StreamEvent};
|
use stream::{ChannelId, StreamManager, StreamEvent, StreamError};
|
||||||
use util::{self, SpotifyId, FileId, mkdir_existing};
|
use util::{self, SpotifyId, FileId, mkdir_existing};
|
||||||
|
|
||||||
|
|
||||||
pub enum Bitrate {
|
pub enum Bitrate {
|
||||||
Bitrate96,
|
Bitrate96,
|
||||||
Bitrate160,
|
Bitrate160,
|
||||||
|
@ -249,7 +249,7 @@ impl Session {
|
||||||
match cmd {
|
match cmd {
|
||||||
0x4 => self.send_packet(0x49, &data).unwrap(),
|
0x4 => self.send_packet(0x49, &data).unwrap(),
|
||||||
0x4a => (),
|
0x4a => (),
|
||||||
0x9 => self.0.stream.lock().unwrap().handle(cmd, data),
|
0x9 | 0xa => self.0.stream.lock().unwrap().handle(cmd, data),
|
||||||
0xd | 0xe => self.0.audio_key.lock().unwrap().handle(cmd, data),
|
0xd | 0xe => self.0.audio_key.lock().unwrap().handle(cmd, data),
|
||||||
0x1b => {
|
0x1b => {
|
||||||
self.0.data.write().unwrap().country = String::from_utf8(data).unwrap();
|
self.0.data.write().unwrap().country = String::from_utf8(data).unwrap();
|
||||||
|
@ -275,10 +275,14 @@ impl Session {
|
||||||
self.0.audio_file.lock().unwrap().request(self, file)
|
self.0.audio_file.lock().unwrap().request(self, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stream(&self, file: FileId, offset: u32, size: u32) -> mpsc::Receiver<StreamEvent> {
|
pub fn stream(&self, file: FileId, offset: u32, size: u32) -> eventual::Stream<StreamEvent, StreamError> {
|
||||||
self.0.stream.lock().unwrap().request(self, file, offset, size)
|
self.0.stream.lock().unwrap().request(self, file, offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocate_stream(&self) -> (ChannelId, eventual::Stream<StreamEvent, StreamError>) {
|
||||||
|
self.0.stream.lock().unwrap().allocate_stream()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> {
|
pub fn metadata<T: MetadataTrait>(&self, id: SpotifyId) -> MetadataRef<T> {
|
||||||
self.0.metadata.lock().unwrap().get(self, id)
|
self.0.metadata.lock().unwrap().get(self, id)
|
||||||
}
|
}
|
||||||
|
|
105
src/stream.rs
105
src/stream.rs
|
@ -1,7 +1,8 @@
|
||||||
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||||
use std::sync::mpsc;
|
use eventual::{self, Async};
|
||||||
|
|
||||||
use util::{ArcVec, FileId};
|
use util::{ArcVec, FileId};
|
||||||
use connection::PacketHandler;
|
use connection::PacketHandler;
|
||||||
|
@ -13,7 +14,10 @@ pub enum StreamEvent {
|
||||||
Data(ArcVec<u8>),
|
Data(ArcVec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelId = u16;
|
#[derive(Debug,Hash,PartialEq,Eq,Copy,Clone)]
|
||||||
|
pub struct StreamError;
|
||||||
|
|
||||||
|
pub type ChannelId = u16;
|
||||||
|
|
||||||
enum ChannelMode {
|
enum ChannelMode {
|
||||||
Header,
|
Header,
|
||||||
|
@ -22,7 +26,7 @@ enum ChannelMode {
|
||||||
|
|
||||||
struct Channel {
|
struct Channel {
|
||||||
mode: ChannelMode,
|
mode: ChannelMode,
|
||||||
callback: mpsc::Sender<StreamEvent>,
|
callback: Option<eventual::Sender<StreamEvent, StreamError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StreamManager {
|
pub struct StreamManager {
|
||||||
|
@ -38,17 +42,29 @@ impl StreamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allocate_stream(&mut self) -> (ChannelId, eventual::Stream<StreamEvent, StreamError>) {
|
||||||
|
let (tx, rx) = eventual::Stream::pair();
|
||||||
|
|
||||||
|
let channel_id = self.next_id;
|
||||||
|
self.next_id += 1;
|
||||||
|
|
||||||
|
self.channels.insert(channel_id,
|
||||||
|
Channel {
|
||||||
|
mode: ChannelMode::Header,
|
||||||
|
callback: Some(tx),
|
||||||
|
});
|
||||||
|
|
||||||
|
(channel_id, rx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn request(&mut self,
|
pub fn request(&mut self,
|
||||||
session: &Session,
|
session: &Session,
|
||||||
file: FileId,
|
file: FileId,
|
||||||
offset: u32,
|
offset: u32,
|
||||||
size: u32)
|
size: u32)
|
||||||
-> mpsc::Receiver<StreamEvent> {
|
-> eventual::Stream<StreamEvent, StreamError> {
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (channel_id, rx) = self.allocate_stream();
|
||||||
|
|
||||||
let channel_id = self.next_id;
|
|
||||||
self.next_id += 1;
|
|
||||||
|
|
||||||
let mut data: Vec<u8> = Vec::new();
|
let mut data: Vec<u8> = Vec::new();
|
||||||
data.write_u16::<BigEndian>(channel_id).unwrap();
|
data.write_u16::<BigEndian>(channel_id).unwrap();
|
||||||
|
@ -64,76 +80,69 @@ impl StreamManager {
|
||||||
|
|
||||||
session.send_packet(0x8, &data).unwrap();
|
session.send_packet(0x8, &data).unwrap();
|
||||||
|
|
||||||
self.channels.insert(channel_id,
|
|
||||||
Channel {
|
|
||||||
mode: ChannelMode::Header,
|
|
||||||
callback: tx,
|
|
||||||
});
|
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketHandler for StreamManager {
|
impl Channel {
|
||||||
fn handle(&mut self, _cmd: u8, data: Vec<u8>) {
|
fn handle_packet(&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]);
|
||||||
|
packet.read_u16::<BigEndian>().unwrap(); // Skip channel id
|
||||||
|
|
||||||
let id: ChannelId = packet.read_u16::<BigEndian>().unwrap();
|
if cmd == 0xa {
|
||||||
let mut close = false;
|
self.callback.take().map(|c| c.fail(StreamError));
|
||||||
{
|
} else {
|
||||||
let channel = match self.channels.get_mut(&id) {
|
match self.mode {
|
||||||
Some(ch) => ch,
|
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match channel.mode {
|
|
||||||
ChannelMode::Header => {
|
ChannelMode::Header => {
|
||||||
let mut length = 0;
|
let mut length = 0;
|
||||||
|
|
||||||
while packet.position() < data.len() as u64 && !close {
|
while packet.position() < data.len() as u64 {
|
||||||
length = packet.read_u16::<BigEndian>().unwrap();
|
length = packet.read_u16::<BigEndian>().unwrap();
|
||||||
if length > 0 {
|
if length > 0 {
|
||||||
let header_id = packet.read_u8().unwrap();
|
let header_id = packet.read_u8().unwrap();
|
||||||
channel.callback
|
let header_data = data.clone()
|
||||||
.send(StreamEvent::Header(
|
.offset(packet.position() as usize)
|
||||||
header_id,
|
.limit(length as usize - 1);
|
||||||
data.clone()
|
|
||||||
.offset(packet.position() as usize)
|
let header = StreamEvent::Header(header_id, header_data);
|
||||||
.limit(length as usize - 1)
|
|
||||||
))
|
self.callback = self.callback.take().and_then(|c| c.send(header).await().ok());
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
close = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
packet.seek(SeekFrom::Current(length as i64 - 1)).unwrap();
|
packet.seek(SeekFrom::Current(length as i64 - 1)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
channel.mode = ChannelMode::Data;
|
self.mode = ChannelMode::Data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelMode::Data => {
|
ChannelMode::Data => {
|
||||||
if packet.position() < data.len() as u64 {
|
if packet.position() < data.len() as u64 {
|
||||||
channel.callback
|
let event_data = data.clone().offset(packet.position() as usize);
|
||||||
.send(StreamEvent::Data(data.clone()
|
let event = StreamEvent::Data(event_data);
|
||||||
.offset(packet.position() as usize)))
|
|
||||||
.unwrap_or_else(|_| {
|
self.callback = self.callback.take().and_then(|c| c.send(event).await().ok());
|
||||||
close = true;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
close = true;
|
self.callback = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if close {
|
impl PacketHandler for StreamManager {
|
||||||
self.channels.remove(&id);
|
fn handle(&mut self, cmd: u8, data: Vec<u8>) {
|
||||||
|
let id: ChannelId = BigEndian::read_u16(&data[0..2]);
|
||||||
|
|
||||||
|
if let Entry::Occupied(mut entry) = self.channels.entry(id) {
|
||||||
|
entry.get_mut().handle_packet(cmd, data);
|
||||||
|
|
||||||
|
if entry.get().callback.is_none() {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue