فهرست منبع

Answer to basic Spirc requests.

Paul Lietar 9 سال پیش
والد
کامیت
45491925de
6فایلهای تغییر یافته به همراه184 افزوده شده و 28 حذف شده
  1. 1 0
      Cargo.lock
  2. 1 0
      Cargo.toml
  3. 168 23
      src/main.rs
  4. 11 3
      src/mercury.rs
  5. 2 1
      src/metadata.rs
  6. 1 1
      src/session.rs

+ 1 - 0
Cargo.lock

@@ -15,6 +15,7 @@ dependencies = [
  "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)",
  "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)",
  "vorbis 0.0.11 (git+https://github.com/plietar/vorbis-rs)",
 ]

+ 1 - 0
Cargo.toml

@@ -14,6 +14,7 @@ num = "*"
 rand = "*"
 lazy_static = "0.1.*"
 rust-crypto = "*"
+time = "*"
 
 [dependencies.protobuf]
 git = "https://github.com/stepancheg/rust-protobuf.git"

+ 168 - 23
src/main.rs

@@ -17,6 +17,7 @@ extern crate shannon;
 extern crate rand;
 extern crate readall;
 extern crate vorbis;
+extern crate time;
 
 extern crate librespot_protocol;
 
@@ -38,6 +39,7 @@ use std::fs::File;
 use std::io::{Read, Write};
 use std::path::Path;
 use std::sync::mpsc;
+use protobuf::core::Message;
 
 use metadata::{MetadataCache, AlbumRef, ArtistRef, TrackRef};
 use session::{Config, Session};
@@ -64,34 +66,26 @@ fn main() {
     session.login(username, password);
     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();
-
-    session.mercury.send(MercuryRequest{
-        method: MercuryMethod::SUB,
-        uri: "hm://remote/user/lietar/v23".to_string(),
-        content_type: None,
-        callback: Some(tx)
-    }).unwrap();
-
-    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!("");
-        }
-    }
+        name: "BlaBla".to_string(),
+        ident: ident,
+        device_type: 5,
+        volume: 0x8000,
+        can_play: true,
+        is_active: false,
+        became_active_at: 0,
+    }.run();
 
+    /*
     loop {
         session.poll();
     }
+    */
 }
 
 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(),
+                    ]
+                }
+            ],
+        })
+    }
+}
+

+ 11 - 3
src/mercury.rs

@@ -17,13 +17,15 @@ pub enum MercuryMethod {
     GET,
     SUB,
     UNSUB,
+    SEND,
 }
 
 pub struct MercuryRequest {
     pub method: MercuryMethod,
     pub uri: String,
     pub content_type: Option<String>,
-    pub callback: Option<MercuryCallback>
+    pub callback: Option<MercuryCallback>,
+    pub payload: Vec<Vec<u8>>
 }
 
 #[derive(Debug)]
@@ -55,7 +57,8 @@ impl fmt::Display for MercuryMethod {
         formatter.write_str(match *self {
             MercuryMethod::GET => "GET",
             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_all(seq).unwrap();
         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(), {
             uri: req.uri.clone(),
@@ -201,6 +204,11 @@ impl MercuryManager {
         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
     }
 }

+ 2 - 1
src/metadata.rs

@@ -237,7 +237,8 @@ impl MetadataManager {
                 method: MercuryMethod::GET,
                 uri: format!("{}/{}", T::base_url(), object.id.to_base16()),
                 content_type: None,
-                callback: Some(tx)
+                callback: Some(tx),
+                payload: Vec::new()
             }).unwrap();
 
             let response = rx.recv().unwrap();

+ 1 - 1
src/session.rs

@@ -22,7 +22,7 @@ pub struct Config {
 }
 
 pub struct Session {
-    config: Config,
+    pub config: Config,
 
     packet_rx: mpsc::Receiver<Packet>,
     pub packet_tx: mpsc::Sender<Packet>,