mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge pull request #236 from haroldmrt/master
Add option to specify preferred AP port
This commit is contained in:
commit
ee87904ac1
9 changed files with 53 additions and 26 deletions
|
@ -17,7 +17,11 @@ pub struct APResolveData {
|
||||||
ap_list: Vec<String>,
|
ap_list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String, Error = Error>> {
|
fn apresolve(
|
||||||
|
handle: &Handle,
|
||||||
|
proxy: &Option<Url>,
|
||||||
|
ap_port: &Option<u16>,
|
||||||
|
) -> Box<Future<Item = String, Error = Error>> {
|
||||||
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
||||||
let use_proxy = proxy.is_some();
|
let use_proxy = proxy.is_some();
|
||||||
|
|
||||||
|
@ -53,9 +57,15 @@ fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String,
|
||||||
let data =
|
let data =
|
||||||
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
||||||
|
|
||||||
|
let p = ap_port.clone();
|
||||||
|
|
||||||
let ap = data.and_then(move |data| {
|
let ap = data.and_then(move |data| {
|
||||||
let mut aps = data.ap_list.iter().filter(|ap| {
|
let mut aps = data.ap_list.iter().filter(|ap| {
|
||||||
if use_proxy {
|
if p.is_some() {
|
||||||
|
Uri::from_str(ap)
|
||||||
|
.ok()
|
||||||
|
.map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap()))
|
||||||
|
} else if use_proxy {
|
||||||
// It is unlikely that the proxy will accept CONNECT on anything other than 443.
|
// It is unlikely that the proxy will accept CONNECT on anything other than 443.
|
||||||
Uri::from_str(ap)
|
Uri::from_str(ap)
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -75,11 +85,12 @@ fn apresolve(handle: &Handle, proxy: &Option<Url>) -> Box<Future<Item = String,
|
||||||
pub(crate) fn apresolve_or_fallback<E>(
|
pub(crate) fn apresolve_or_fallback<E>(
|
||||||
handle: &Handle,
|
handle: &Handle,
|
||||||
proxy: &Option<Url>,
|
proxy: &Option<Url>,
|
||||||
|
ap_port: &Option<u16>,
|
||||||
) -> Box<Future<Item = String, Error = E>>
|
) -> Box<Future<Item = String, Error = E>>
|
||||||
where
|
where
|
||||||
E: 'static,
|
E: 'static,
|
||||||
{
|
{
|
||||||
let ap = apresolve(handle, proxy).or_else(|e| {
|
let ap = apresolve(handle, proxy, ap_port).or_else(|e| {
|
||||||
warn!("Failed to resolve Access Point: {}", e.description());
|
warn!("Failed to resolve Access Point: {}", e.description());
|
||||||
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
warn!("Using fallback \"{}\"", AP_FALLBACK);
|
||||||
Ok(AP_FALLBACK.into())
|
Ok(AP_FALLBACK.into())
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub struct SessionConfig {
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
pub device_id: String,
|
pub device_id: String,
|
||||||
pub proxy: Option<Url>,
|
pub proxy: Option<Url>,
|
||||||
|
pub ap_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SessionConfig {
|
impl Default for SessionConfig {
|
||||||
|
@ -19,6 +20,7 @@ impl Default for SessionConfig {
|
||||||
user_agent: version::version_string(),
|
user_agent: version::version_string(),
|
||||||
device_id: device_id,
|
device_id: device_id,
|
||||||
proxy: None,
|
proxy: None,
|
||||||
|
ap_port: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl Session {
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
) -> Box<Future<Item = Session, Error = io::Error>> {
|
) -> Box<Future<Item = Session, Error = io::Error>> {
|
||||||
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy);
|
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
||||||
|
|
||||||
let handle_ = handle.clone();
|
let handle_ = handle.clone();
|
||||||
let proxy = config.proxy.clone();
|
let proxy = config.proxy.clone();
|
||||||
|
|
|
@ -6,7 +6,7 @@ An AP is randomly picked from that list to connect to.
|
||||||
|
|
||||||
The connection is done using a bare TCP socket. Despite many APs using ports 80 and 443, neither HTTP nor TLS are used to connect.
|
The connection is done using a bare TCP socket. Despite many APs using ports 80 and 443, neither HTTP nor TLS are used to connect.
|
||||||
|
|
||||||
If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:80` is used as a fallback.
|
If `http://apresolve.spotify.com` is unresponsive, `ap.spotify.com:443` is used as a fallback.
|
||||||
|
|
||||||
## Connection Hello
|
## Connection Hello
|
||||||
The first 3 packets exchanged are unencrypted, and have the following format :
|
The first 3 packets exchanged are unencrypted, and have the following format :
|
||||||
|
|
|
@ -33,7 +33,8 @@ fn main() {
|
||||||
let backend = audio_backend::find(None).unwrap();
|
let backend = audio_backend::find(None).unwrap();
|
||||||
|
|
||||||
println!("Connecting ..");
|
println!("Connecting ..");
|
||||||
let session = core.run(Session::connect(session_config, credentials, None, handle))
|
let session = core
|
||||||
|
.run(Session::connect(session_config, credentials, None, handle))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None));
|
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None));
|
||||||
|
|
|
@ -110,13 +110,15 @@ impl Metadata for Track {
|
||||||
fn parse(msg: &Self::Message, session: &Session) -> Self {
|
fn parse(msg: &Self::Message, session: &Session) -> Self {
|
||||||
let country = session.country();
|
let country = session.country();
|
||||||
|
|
||||||
let artists = msg.get_artist()
|
let artists = msg
|
||||||
|
.get_artist()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|artist| artist.has_gid())
|
.filter(|artist| artist.has_gid())
|
||||||
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
|
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let files = msg.get_file()
|
let files = msg
|
||||||
|
.get_file()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|file| file.has_file_id())
|
.filter(|file| file.has_file_id())
|
||||||
.map(|file| {
|
.map(|file| {
|
||||||
|
@ -133,7 +135,8 @@ impl Metadata for Track {
|
||||||
album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
|
album: SpotifyId::from_raw(msg.get_album().get_gid()).unwrap(),
|
||||||
artists: artists,
|
artists: artists,
|
||||||
files: files,
|
files: files,
|
||||||
alternatives: msg.get_alternative()
|
alternatives: msg
|
||||||
|
.get_alternative()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
|
.map(|alt| SpotifyId::from_raw(alt.get_gid()).unwrap())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -150,20 +153,23 @@ impl Metadata for Album {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
||||||
let artists = msg.get_artist()
|
let artists = msg
|
||||||
|
.get_artist()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|artist| artist.has_gid())
|
.filter(|artist| artist.has_gid())
|
||||||
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
|
.map(|artist| SpotifyId::from_raw(artist.get_gid()).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let tracks = msg.get_disc()
|
let tracks = msg
|
||||||
|
.get_disc()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|disc| disc.get_track())
|
.flat_map(|disc| disc.get_track())
|
||||||
.filter(|track| track.has_gid())
|
.filter(|track| track.has_gid())
|
||||||
.map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
|
.map(|track| SpotifyId::from_raw(track.get_gid()).unwrap())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let covers = msg.get_cover_group()
|
let covers = msg
|
||||||
|
.get_cover_group()
|
||||||
.get_image()
|
.get_image()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|image| image.has_file_id())
|
.filter(|image| image.has_file_id())
|
||||||
|
@ -194,7 +200,8 @@ impl Metadata for Artist {
|
||||||
fn parse(msg: &Self::Message, session: &Session) -> Self {
|
fn parse(msg: &Self::Message, session: &Session) -> Self {
|
||||||
let country = session.country();
|
let country = session.country();
|
||||||
|
|
||||||
let top_tracks: Vec<SpotifyId> = match msg.get_top_track()
|
let top_tracks: Vec<SpotifyId> = match msg
|
||||||
|
.get_top_track()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
|
.find(|tt| !tt.has_country() || countrylist_contains(tt.get_country(), &country))
|
||||||
{
|
{
|
||||||
|
|
|
@ -557,7 +557,8 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = self.session
|
let key = self
|
||||||
|
.session
|
||||||
.audio_key()
|
.audio_key()
|
||||||
.request(track.id, file_id)
|
.request(track.id, file_id)
|
||||||
.wait()
|
.wait()
|
||||||
|
@ -599,7 +600,8 @@ impl Drop for PlayerInternal {
|
||||||
impl ::std::fmt::Debug for PlayerCommand {
|
impl ::std::fmt::Debug for PlayerCommand {
|
||||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
PlayerCommand::Load(track, play, position, _) => f.debug_tuple("Load")
|
PlayerCommand::Load(track, play, position, _) => f
|
||||||
|
.debug_tuple("Load")
|
||||||
.field(&track)
|
.field(&track)
|
||||||
.field(&play)
|
.field(&play)
|
||||||
.field(&position)
|
.field(&position)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Autogenerated by build.sh
|
// Autogenerated by build.sh
|
||||||
|
|
||||||
pub const FILES : &'static [(&'static str, u32)] = &[
|
pub const FILES: &'static [(&'static str, u32)] = &[
|
||||||
("proto/authentication.proto", 2098196376),
|
("proto/authentication.proto", 2098196376),
|
||||||
("proto/keyexchange.proto", 451735664),
|
("proto/keyexchange.proto", 451735664),
|
||||||
("proto/mercury.proto", 709993906),
|
("proto/mercury.proto", 709993906),
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -129,6 +129,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
||||||
.optopt("p", "password", "Password", "PASSWORD")
|
.optopt("p", "password", "Password", "PASSWORD")
|
||||||
.optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
|
.optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
|
||||||
|
.optopt("", "ap-port", "Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070", "AP_PORT")
|
||||||
.optflag("", "disable-discovery", "Disable discovery mode")
|
.optflag("", "disable-discovery", "Disable discovery mode")
|
||||||
.optopt(
|
.optopt(
|
||||||
"",
|
"",
|
||||||
|
@ -255,20 +256,23 @@ fn setup(args: &[String]) -> Setup {
|
||||||
proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map(
|
proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map(
|
||||||
|s| {
|
|s| {
|
||||||
match Url::parse(&s) {
|
match Url::parse(&s) {
|
||||||
Ok(url) => {
|
Ok(url) => {
|
||||||
if url.host().is_none() || url.port().is_none() {
|
if url.host().is_none() || url.port().is_none() {
|
||||||
panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
|
panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.scheme() != "http" {
|
if url.scheme() != "http" {
|
||||||
panic!("Only unsecure http:// proxies are supported");
|
panic!("Only unsecure http:// proxies are supported");
|
||||||
|
}
|
||||||
|
url
|
||||||
|
},
|
||||||
|
Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
|
||||||
}
|
}
|
||||||
url
|
|
||||||
},
|
|
||||||
Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ap_port: matches
|
||||||
|
.opt_str("ap-port")
|
||||||
|
.map(|port| port.parse::<u16>().expect("Invalid port")),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue