mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Answer to basic Spirc requests.
This commit is contained in:
parent
7897070bb7
commit
45491925de
6 changed files with 184 additions and 28 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -15,6 +15,7 @@ dependencies = [
|
||||||
"rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rust-crypto 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
|
"rust-gmp 0.2.0 (git+https://github.com/plietar/rust-gmp.git)",
|
||||||
"shannon 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
|
"shannon 0.1.0 (git+https://github.com/plietar/rust-shannon.git)",
|
||||||
|
"time 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"vergen 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vergen 0.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"vorbis 0.0.11 (git+https://github.com/plietar/vorbis-rs)",
|
"vorbis 0.0.11 (git+https://github.com/plietar/vorbis-rs)",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,6 +14,7 @@ num = "*"
|
||||||
rand = "*"
|
rand = "*"
|
||||||
lazy_static = "0.1.*"
|
lazy_static = "0.1.*"
|
||||||
rust-crypto = "*"
|
rust-crypto = "*"
|
||||||
|
time = "*"
|
||||||
|
|
||||||
[dependencies.protobuf]
|
[dependencies.protobuf]
|
||||||
git = "https://github.com/stepancheg/rust-protobuf.git"
|
git = "https://github.com/stepancheg/rust-protobuf.git"
|
||||||
|
|
191
src/main.rs
191
src/main.rs
|
@ -17,6 +17,7 @@ extern crate shannon;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
extern crate readall;
|
extern crate readall;
|
||||||
extern crate vorbis;
|
extern crate vorbis;
|
||||||
|
extern crate time;
|
||||||
|
|
||||||
extern crate librespot_protocol;
|
extern crate librespot_protocol;
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ 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 std::sync::mpsc;
|
||||||
|
use protobuf::core::Message;
|
||||||
|
|
||||||
use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef};
|
use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef};
|
||||||
use session::{Config, Session};
|
use session::{Config, Session};
|
||||||
|
@ -64,34 +66,26 @@ fn main() {
|
||||||
session.login(username, password);
|
session.login(username, password);
|
||||||
session.poll();
|
session.poll();
|
||||||
|
|
||||||
let mut cache = MetadataCache::new(session.metadata.clone());
|
let ident = session.config.device_id.clone();
|
||||||
|
SpircManager{
|
||||||
|
session: session,
|
||||||
|
state_update_id: 0,
|
||||||
|
seq_nr: 0,
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
name: "BlaBla".to_string(),
|
||||||
|
ident: ident,
|
||||||
session.mercury.send(MercuryRequest{
|
device_type: 5,
|
||||||
method: MercuryMethod::SUB,
|
volume: 0x8000,
|
||||||
uri: "hm://remote/user/lietar/v23".to_string(),
|
can_play: true,
|
||||||
content_type: None,
|
is_active: false,
|
||||||
callback: Some(tx)
|
became_active_at: 0,
|
||||||
}).unwrap();
|
}.run();
|
||||||
|
|
||||||
for pkt in rx.iter() {
|
|
||||||
let frame : protocol::spirc::Frame =
|
|
||||||
protobuf::parse_from_bytes(pkt.payload.front().unwrap()).unwrap();
|
|
||||||
|
|
||||||
if frame.get_device_state().get_is_active() &&
|
|
||||||
frame.has_state() {
|
|
||||||
let index = frame.get_state().get_playing_track_index();
|
|
||||||
let ref track = frame.get_state().get_track()[index as usize];
|
|
||||||
println!("{}", frame.get_device_state().get_name());
|
|
||||||
print_track(&mut cache, SpotifyId::from_raw(track.get_gid()));
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
loop {
|
loop {
|
||||||
session.poll();
|
session.poll();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
|
fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
|
||||||
|
@ -120,3 +114,154 @@ fn print_track(cache: &mut MetadataCache, track_id: SpotifyId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SpircManager {
|
||||||
|
session: Session,
|
||||||
|
state_update_id: i64,
|
||||||
|
seq_nr: u32,
|
||||||
|
|
||||||
|
name: String,
|
||||||
|
ident: String,
|
||||||
|
device_type: u8,
|
||||||
|
|
||||||
|
volume: u16,
|
||||||
|
can_play: bool,
|
||||||
|
is_active: bool,
|
||||||
|
became_active_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpircManager {
|
||||||
|
fn run(&mut self) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
self.session.mercury.send(MercuryRequest{
|
||||||
|
method: MercuryMethod::SUB,
|
||||||
|
uri: "hm://remote/user/lietar/v23".to_string(),
|
||||||
|
content_type: None,
|
||||||
|
callback: Some(tx),
|
||||||
|
payload: Vec::new()
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
self.notify(None);
|
||||||
|
|
||||||
|
for pkt in rx.iter() {
|
||||||
|
let frame : protocol::spirc::Frame =
|
||||||
|
protobuf::parse_from_bytes(pkt.payload.front().unwrap()).unwrap();
|
||||||
|
|
||||||
|
println!("{:?} {} {} {}",
|
||||||
|
frame.get_typ(),
|
||||||
|
frame.get_device_state().get_name(),
|
||||||
|
frame.get_ident(),
|
||||||
|
frame.get_device_state().get_became_active_at());
|
||||||
|
|
||||||
|
if frame.get_ident() == self.ident ||
|
||||||
|
(frame.get_recipient().len() > 0 &&
|
||||||
|
!frame.get_recipient().contains(&self.ident)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(&mut self, frame: protocol::spirc::Frame) {
|
||||||
|
match frame.get_typ() {
|
||||||
|
protocol::spirc::MessageType::kMessageTypeHello => {
|
||||||
|
self.notify(Some(frame.get_ident()));
|
||||||
|
}
|
||||||
|
protocol::spirc::MessageType::kMessageTypeLoad => {
|
||||||
|
self.is_active = true;
|
||||||
|
self.became_active_at = {
|
||||||
|
let ts = time::now_utc().to_timespec();
|
||||||
|
ts.sec * 1000 + ts.nsec as i64 / 1000000
|
||||||
|
};
|
||||||
|
println!("{:?} {}", frame, self.became_active_at);
|
||||||
|
self.notify(None)
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notify(&mut self, recipient: Option<&str>) {
|
||||||
|
let device_state = self.device_state();
|
||||||
|
self.session.mercury.send(MercuryRequest{
|
||||||
|
method: MercuryMethod::SEND,
|
||||||
|
uri: "hm://remote/user/lietar".to_string(),
|
||||||
|
content_type: None,
|
||||||
|
callback: None,
|
||||||
|
payload: vec![
|
||||||
|
protobuf_init!(protocol::spirc::Frame::new(), {
|
||||||
|
version: 1,
|
||||||
|
ident: self.ident.clone(),
|
||||||
|
protocol_version: "2.0.0".to_string(),
|
||||||
|
seq_nr: { self.seq_nr += 1; self.seq_nr },
|
||||||
|
typ: protocol::spirc::MessageType::kMessageTypeNotify,
|
||||||
|
device_state: device_state,
|
||||||
|
recipient: protobuf::RepeatedField::from_vec(
|
||||||
|
recipient.map(|r| vec![r.to_string()] ).unwrap_or(vec![])
|
||||||
|
)
|
||||||
|
}).write_to_bytes().unwrap()
|
||||||
|
]
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_state(&mut self) -> protocol::spirc::DeviceState {
|
||||||
|
protobuf_init!(protocol::spirc::DeviceState::new(), {
|
||||||
|
sw_version: "librespot-0.1.0".to_string(),
|
||||||
|
is_active: self.is_active,
|
||||||
|
can_play: self.can_play,
|
||||||
|
volume: self.volume as u32,
|
||||||
|
name: self.name.clone(),
|
||||||
|
error_code: 0,
|
||||||
|
became_active_at: if self.is_active { self.became_active_at } else { 0 },
|
||||||
|
capabilities => [
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kCanBePlayer,
|
||||||
|
intValue => [0]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kDeviceType,
|
||||||
|
intValue => [ self.device_type as i64 ]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kGaiaEqConnectId,
|
||||||
|
intValue => [1]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kSupportsLogout,
|
||||||
|
intValue => [0]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kIsObservable,
|
||||||
|
intValue => [1]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kVolumeSteps,
|
||||||
|
intValue => [10]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kSupportedContexts,
|
||||||
|
stringValue => [
|
||||||
|
"album".to_string(),
|
||||||
|
"playlist".to_string(),
|
||||||
|
"search".to_string(),
|
||||||
|
"inbox".to_string(),
|
||||||
|
"toplist".to_string(),
|
||||||
|
"starred".to_string(),
|
||||||
|
"publishedstarred".to_string(),
|
||||||
|
"track".to_string(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
@{
|
||||||
|
typ: protocol::spirc::CapabilityType::kSupportedTypes,
|
||||||
|
stringValue => [
|
||||||
|
"audio/local".to_string(),
|
||||||
|
"audio/track".to_string(),
|
||||||
|
"local".to_string(),
|
||||||
|
"track".to_string(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,15 @@ pub enum MercuryMethod {
|
||||||
GET,
|
GET,
|
||||||
SUB,
|
SUB,
|
||||||
UNSUB,
|
UNSUB,
|
||||||
|
SEND,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MercuryRequest {
|
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 callback: Option<MercuryCallback>,
|
||||||
|
pub payload: Vec<Vec<u8>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -55,7 +57,8 @@ impl fmt::Display for MercuryMethod {
|
||||||
formatter.write_str(match *self {
|
formatter.write_str(match *self {
|
||||||
MercuryMethod::GET => "GET",
|
MercuryMethod::GET => "GET",
|
||||||
MercuryMethod::SUB => "SUB",
|
MercuryMethod::SUB => "SUB",
|
||||||
MercuryMethod::UNSUB => "UNSUB"
|
MercuryMethod::UNSUB => "UNSUB",
|
||||||
|
MercuryMethod::SEND => "SEND"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,7 +191,7 @@ impl MercuryManager {
|
||||||
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
packet.write_u16::<BigEndian>(seq.len() as u16).unwrap();
|
||||||
packet.write_all(seq).unwrap();
|
packet.write_all(seq).unwrap();
|
||||||
packet.write_u8(1).unwrap(); // Flags: FINAL
|
packet.write_u8(1).unwrap(); // Flags: FINAL
|
||||||
packet.write_u16::<BigEndian>(1).unwrap(); // Part count. Only header
|
packet.write_u16::<BigEndian>(1 + req.payload.len() as u16).unwrap(); // Part count
|
||||||
|
|
||||||
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
|
let mut header = protobuf_init!(protocol::mercury::Header::new(), {
|
||||||
uri: req.uri.clone(),
|
uri: req.uri.clone(),
|
||||||
|
@ -201,6 +204,11 @@ impl MercuryManager {
|
||||||
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
|
packet.write_u16::<BigEndian>(header.compute_size() as u16).unwrap();
|
||||||
header.write_to_writer(&mut packet).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
|
packet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,8 @@ impl MetadataManager {
|
||||||
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)
|
callback: Some(tx),
|
||||||
|
payload: Vec::new()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
let response = rx.recv().unwrap();
|
let response = rx.recv().unwrap();
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
config: Config,
|
pub config: Config,
|
||||||
|
|
||||||
packet_rx: mpsc::Receiver<Packet>,
|
packet_rx: mpsc::Receiver<Packet>,
|
||||||
pub packet_tx: mpsc::Sender<Packet>,
|
pub packet_tx: mpsc::Sender<Packet>,
|
||||||
|
|
Loading…
Reference in a new issue