mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge branch 'dev' into rodiojack-backend
This commit is contained in:
parent
52438b1cc2
commit
aad4dba8a8
12 changed files with 143 additions and 107 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
toolchain:
|
toolchain:
|
||||||
- 1.40.0 # MSRV (Minimum supported rust version)
|
- 1.42.0 # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
|
|
74
.travis.yml
74
.travis.yml
|
@ -1,74 +0,0 @@
|
||||||
language: rust
|
|
||||||
rust:
|
|
||||||
- 1.42.0
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
|
|
||||||
# Need to cache the whole `.cargo` directory to keep .crates.toml for
|
|
||||||
# cargo-update to work
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- /home/travis/.cargo
|
|
||||||
|
|
||||||
# But don't cache the cargo registry
|
|
||||||
before_cache:
|
|
||||||
- rm -rf /home/travis/.cargo/registry
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
# Performance tweak
|
|
||||||
fast_finish: true
|
|
||||||
# Ignore failures in nightly, not ideal, but necessary
|
|
||||||
allow_failures:
|
|
||||||
- rust: nightly
|
|
||||||
|
|
||||||
# Only run the formatting check for stable
|
|
||||||
include:
|
|
||||||
- name: 'Rust: format check'
|
|
||||||
rust: stable
|
|
||||||
install:
|
|
||||||
- rustup component add rustfmt
|
|
||||||
script:
|
|
||||||
- cargo fmt --verbose --all -- --check
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- gcc-arm-linux-gnueabihf
|
|
||||||
- libc6-dev-armhf-cross
|
|
||||||
- libpulse-dev
|
|
||||||
- portaudio19-dev
|
|
||||||
- libasound2-dev
|
|
||||||
- libsdl2-dev
|
|
||||||
- gstreamer1.0-dev
|
|
||||||
- libgstreamer-plugins-base1.0-dev
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- mkdir -p ~/.cargo
|
|
||||||
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
|
||||||
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
|
||||||
- rustup target add armv7-unknown-linux-gnueabihf
|
|
||||||
|
|
||||||
script:
|
|
||||||
- cargo build --locked --no-default-features
|
|
||||||
- cargo build --locked --examples
|
|
||||||
- cargo build --locked --no-default-features --features "with-tremor"
|
|
||||||
- cargo build --locked --no-default-features --features "with-vorbis"
|
|
||||||
- cargo build --locked --no-default-features --features "alsa-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "portaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "pulseaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "jackaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "rodiojack-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "rodio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "sdl-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "gstreamer-backend"
|
|
||||||
- cargo build --locked --no-default-features --target armv7-unknown-linux-gnueabihf
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- https://webhooks.gitter.im/e/780b178b15811059752e
|
|
||||||
on_success: change # options: [always|never|change] default: always
|
|
||||||
on_failure: always # options: [always|never|change] default: always
|
|
||||||
on_start: never # options: [always|never|change] default: always
|
|
|
@ -1,5 +1,4 @@
|
||||||
[![Build Status](https://img.shields.io/github/workflow/status/librespot-org/librespot/test/dev)](https://github.com/librespot-org/librespot/actions)
|
[![Build Status](https://github.com/librespot-org/librespot/workflows/test/badge.svg)](https://github.com/librespot-org/librespot/actions)
|
||||||
[![Build Status](https://travis-ci.org/librespot-org/librespot.svg?branch=dev)](https://travis-ci.org/librespot-org/librespot)
|
|
||||||
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
|
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
|
||||||
[![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot)
|
[![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot)
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ As the origin by [plietar](https://github.com/plietar/) is no longer actively ma
|
||||||
# Documentation
|
# Documentation
|
||||||
Documentation is currently a work in progress, contributions are welcome!
|
Documentation is currently a work in progress, contributions are welcome!
|
||||||
|
|
||||||
There is some brief documentation on how the protocol works in the [docs](https://github.com/librespot-org/librespot/tree/master/docs) folder,
|
There is some brief documentation on how the protocol works in the [docs](https://github.com/librespot-org/librespot/tree/master/docs) folder,
|
||||||
|
|
||||||
[COMPILING.md](https://github.com/librespot-org/librespot/blob/master/COMPILING.md) contains detailed instructions on setting up a development environment, and compiling librespot. More general usage and compilation information is available on the [wiki](https://github.com/librespot-org/librespot/wiki).
|
[COMPILING.md](https://github.com/librespot-org/librespot/blob/master/COMPILING.md) contains detailed instructions on setting up a development environment, and compiling librespot. More general usage and compilation information is available on the [wiki](https://github.com/librespot-org/librespot/wiki).
|
||||||
[CONTRIBUTING.md](https://github.com/librespot-org/librespot/blob/master/CONTRIBUTING.md) also contains our contributing guidelines.
|
[CONTRIBUTING.md](https://github.com/librespot-org/librespot/blob/master/CONTRIBUTING.md) also contains our contributing guidelines.
|
||||||
|
@ -32,7 +31,7 @@ If you wish to learn more about how librespot works overall, the best way is to
|
||||||
If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash.
|
If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash.
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
A quick walk through of the build process is outlined here, while a detailed compilation guide can be found [here](https://github.com/librespot-org/librespot/blob/master/COMPILING.md).
|
A quick walk through of the build process is outlined here, while a detailed compilation guide can be found [here](https://github.com/librespot-org/librespot/blob/master/COMPILING.md).
|
||||||
|
|
||||||
## Additional Dependencies
|
## Additional Dependencies
|
||||||
We recently switched to using [Rodio](https://github.com/tomaka/rodio) for audio playback by default, hence for macOS and Windows, you should just be able to clone and build librespot (with the command below).
|
We recently switched to using [Rodio](https://github.com/tomaka/rodio) for audio playback by default, hence for macOS and Windows, you should just be able to clone and build librespot (with the command below).
|
||||||
|
@ -112,4 +111,3 @@ functionality.
|
||||||
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
|
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
|
||||||
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
|
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
|
||||||
- [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure Librespot.
|
- [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure Librespot.
|
||||||
|
|
||||||
|
|
|
@ -459,6 +459,13 @@ impl AudioFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_cached(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AudioFile::Cached { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_range(session: &Session, file: FileId, offset: usize, length: usize) -> Channel {
|
fn request_range(session: &Session, file: FileId, offset: usize, length: usize) -> Channel {
|
||||||
|
|
|
@ -75,7 +75,7 @@ impl Discovery {
|
||||||
"status": 101,
|
"status": 101,
|
||||||
"statusString": "ERROR-OK",
|
"statusString": "ERROR-OK",
|
||||||
"spotifyError": 0,
|
"spotifyError": 0,
|
||||||
"version": "2.1.0",
|
"version": "2.7.1",
|
||||||
"deviceID": (self.0.device_id),
|
"deviceID": (self.0.device_id),
|
||||||
"remoteName": (self.0.config.name),
|
"remoteName": (self.0.config.name),
|
||||||
"activeUser": "",
|
"activeUser": "",
|
||||||
|
@ -85,6 +85,9 @@ impl Discovery {
|
||||||
"accountReq": "PREMIUM",
|
"accountReq": "PREMIUM",
|
||||||
"brandDisplayName": "librespot",
|
"brandDisplayName": "librespot",
|
||||||
"modelDisplayName": "librespot",
|
"modelDisplayName": "librespot",
|
||||||
|
"resolverVersion": "0",
|
||||||
|
"groupStatus": "NONE",
|
||||||
|
"voiceSupport": "NO",
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = result.to_string();
|
let body = result.to_string();
|
||||||
|
|
|
@ -7,6 +7,7 @@ use sha1::{Digest, Sha1};
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
|
|
||||||
use crate::protocol::authentication::AuthenticationType;
|
use crate::protocol::authentication::AuthenticationType;
|
||||||
|
use crate::protocol::keyexchange::{APLoginFailed, ErrorCode};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
|
@ -164,3 +165,37 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
|
||||||
(None, _, None) => None,
|
(None, _, None) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
types {
|
||||||
|
AuthenticationError, AuthenticationErrorKind, AuthenticationResultExt, AuthenticationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_links {
|
||||||
|
Io(::std::io::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
errors {
|
||||||
|
BadCredentials {
|
||||||
|
description("Bad credentials")
|
||||||
|
display("Authentication failed with error: Bad credentials")
|
||||||
|
}
|
||||||
|
PremiumAccountRequired {
|
||||||
|
description("Premium account required")
|
||||||
|
display("Authentication failed with error: Premium account required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<APLoginFailed> for AuthenticationError {
|
||||||
|
fn from(login_failure: APLoginFailed) -> Self {
|
||||||
|
let error_code = login_failure.get_error_code();
|
||||||
|
match error_code {
|
||||||
|
ErrorCode::BadCredentials => Self::from_kind(AuthenticationErrorKind::BadCredentials),
|
||||||
|
ErrorCode::PremiumAccountRequired => {
|
||||||
|
Self::from_kind(AuthenticationErrorKind::PremiumAccountRequired)
|
||||||
|
}
|
||||||
|
_ => format!("Authentication failed with error: {:?}", error_code).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -162,4 +162,17 @@ impl Cache {
|
||||||
warn!("Cannot save file to cache: {}", e)
|
warn!("Cannot save file to cache: {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_file(&self, file: FileId) -> bool {
|
||||||
|
if let Some(path) = self.file_path(file) {
|
||||||
|
if let Err(err) = fs::remove_file(path) {
|
||||||
|
warn!("Unable to remove file from cache: {}", err);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,16 @@ pub enum DeviceType {
|
||||||
AVR = 6,
|
AVR = 6,
|
||||||
STB = 7,
|
STB = 7,
|
||||||
AudioDongle = 8,
|
AudioDongle = 8,
|
||||||
|
GameConsole = 9,
|
||||||
|
CastAudio = 10,
|
||||||
|
CastVideo = 11,
|
||||||
|
Automobile = 12,
|
||||||
|
Smartwatch = 13,
|
||||||
|
Chromebook = 14,
|
||||||
|
UnknownSpotify = 100,
|
||||||
|
CarThing = 101,
|
||||||
|
Observer = 102,
|
||||||
|
HomeThing = 103,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for DeviceType {
|
impl FromStr for DeviceType {
|
||||||
|
@ -51,6 +61,14 @@ impl FromStr for DeviceType {
|
||||||
"avr" => Ok(AVR),
|
"avr" => Ok(AVR),
|
||||||
"stb" => Ok(STB),
|
"stb" => Ok(STB),
|
||||||
"audiodongle" => Ok(AudioDongle),
|
"audiodongle" => Ok(AudioDongle),
|
||||||
|
"gameconsole" => Ok(GameConsole),
|
||||||
|
"castaudio" => Ok(CastAudio),
|
||||||
|
"castvideo" => Ok(CastVideo),
|
||||||
|
"automobile" => Ok(Automobile),
|
||||||
|
"smartwatch" => Ok(Smartwatch),
|
||||||
|
"chromebook" => Ok(Chromebook),
|
||||||
|
"carthing" => Ok(CarThing),
|
||||||
|
"homething" => Ok(HomeThing),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +87,16 @@ impl fmt::Display for DeviceType {
|
||||||
AVR => f.write_str("AVR"),
|
AVR => f.write_str("AVR"),
|
||||||
STB => f.write_str("STB"),
|
STB => f.write_str("STB"),
|
||||||
AudioDongle => f.write_str("AudioDongle"),
|
AudioDongle => f.write_str("AudioDongle"),
|
||||||
|
GameConsole => f.write_str("GameConsole"),
|
||||||
|
CastAudio => f.write_str("CastAudio"),
|
||||||
|
CastVideo => f.write_str("CastVideo"),
|
||||||
|
Automobile => f.write_str("Automobile"),
|
||||||
|
Smartwatch => f.write_str("Smartwatch"),
|
||||||
|
Chromebook => f.write_str("Chromebook"),
|
||||||
|
UnknownSpotify => f.write_str("UnknownSpotify"),
|
||||||
|
CarThing => f.write_str("CarThing"),
|
||||||
|
Observer => f.write_str("Observer"),
|
||||||
|
HomeThing => f.write_str("HomeThing"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use tokio_core::net::TcpStream;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::authentication::Credentials;
|
use crate::authentication::{AuthenticationError, Credentials};
|
||||||
use crate::version;
|
use crate::version;
|
||||||
|
|
||||||
use crate::proxytunnel;
|
use crate::proxytunnel;
|
||||||
|
@ -66,7 +66,7 @@ pub fn authenticate(
|
||||||
transport: Transport,
|
transport: Transport,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
device_id: String,
|
device_id: String,
|
||||||
) -> Box<dyn Future<Item = (Transport, Credentials), Error = io::Error>> {
|
) -> Box<dyn Future<Item = (Transport, Credentials), Error = AuthenticationError>> {
|
||||||
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||||
use crate::protocol::keyexchange::APLoginFailed;
|
use crate::protocol::keyexchange::APLoginFailed;
|
||||||
|
|
||||||
|
@ -101,6 +101,7 @@ pub fn authenticate(
|
||||||
transport
|
transport
|
||||||
.send((cmd, data))
|
.send((cmd, data))
|
||||||
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
||||||
|
.map_err(|io_err| io_err.into())
|
||||||
.and_then(|(packet, transport)| match packet {
|
.and_then(|(packet, transport)| match packet {
|
||||||
Some((0xac, data)) => {
|
Some((0xac, data)) => {
|
||||||
let welcome_data: APWelcome =
|
let welcome_data: APWelcome =
|
||||||
|
@ -118,10 +119,7 @@ pub fn authenticate(
|
||||||
Some((0xad, data)) => {
|
Some((0xad, data)) => {
|
||||||
let error_data: APLoginFailed =
|
let error_data: APLoginFailed =
|
||||||
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||||
panic!(
|
Err(error_data.into())
|
||||||
"Authentication failed with reason: {:?}",
|
|
||||||
error_data.get_error_code()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((cmd, _)) => panic!("Unexpected packet {:?}", cmd),
|
Some((cmd, _)) => panic!("Unexpected packet {:?}", cmd),
|
||||||
|
|
|
@ -19,6 +19,8 @@ use crate::config::SessionConfig;
|
||||||
use crate::connection;
|
use crate::connection;
|
||||||
use crate::mercury::MercuryManager;
|
use crate::mercury::MercuryManager;
|
||||||
|
|
||||||
|
pub use crate::authentication::{AuthenticationError, AuthenticationErrorKind};
|
||||||
|
|
||||||
struct SessionData {
|
struct SessionData {
|
||||||
country: String,
|
country: String,
|
||||||
time_delta: i64,
|
time_delta: i64,
|
||||||
|
@ -53,16 +55,18 @@ impl Session {
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
) -> Box<dyn Future<Item = Session, Error = io::Error>> {
|
) -> Box<dyn Future<Item = Session, Error = AuthenticationError>> {
|
||||||
let access_point =
|
let access_point =
|
||||||
apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
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();
|
||||||
let connection = access_point.and_then(move |addr| {
|
let connection = access_point
|
||||||
info!("Connecting to AP \"{}\"", addr);
|
.and_then(move |addr| {
|
||||||
connection::connect(addr, &handle_, &proxy)
|
info!("Connecting to AP \"{}\"", addr);
|
||||||
});
|
connection::connect(addr, &handle_, &proxy)
|
||||||
|
})
|
||||||
|
.map_err(|io_err| io_err.into());
|
||||||
|
|
||||||
let device_id = config.device_id.clone();
|
let device_id = config.device_id.clone();
|
||||||
let authentication = connection.and_then(move |connection| {
|
let authentication = connection.and_then(move |connection| {
|
||||||
|
|
|
@ -654,20 +654,24 @@ impl PlayerTrackLoader {
|
||||||
FileFormat::OGG_VORBIS_96,
|
FileFormat::OGG_VORBIS_96,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let format = formats
|
|
||||||
.iter()
|
|
||||||
.find(|format| audio.files.contains_key(format))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let file_id = match audio.files.get(&format) {
|
let entry = formats.iter().find_map(|format| {
|
||||||
Some(&file_id) => file_id,
|
if let Some(&file_id) = audio.files.get(format) {
|
||||||
|
Some((*format, file_id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (format, file_id) = match entry {
|
||||||
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
warn!("<{}> in not available in format {:?}", audio.name, format);
|
warn!("<{}> is not available in any supported format", audio.name);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes_per_second = self.stream_data_rate(*format);
|
let bytes_per_second = self.stream_data_rate(format);
|
||||||
let play_from_beginning = position_ms == 0;
|
let play_from_beginning = position_ms == 0;
|
||||||
|
|
||||||
let key = self.session.audio_key().request(spotify_id, file_id);
|
let key = self.session.audio_key().request(spotify_id, file_id);
|
||||||
|
@ -685,6 +689,7 @@ impl PlayerTrackLoader {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let is_cached = encrypted_file.is_cached();
|
||||||
|
|
||||||
let mut stream_loader_controller = encrypted_file.get_stream_loader_controller();
|
let mut stream_loader_controller = encrypted_file.get_stream_loader_controller();
|
||||||
|
|
||||||
|
@ -718,12 +723,31 @@ impl PlayerTrackLoader {
|
||||||
|
|
||||||
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
||||||
|
|
||||||
let mut decoder = VorbisDecoder::new(audio_file).unwrap();
|
let mut decoder = match VorbisDecoder::new(audio_file) {
|
||||||
|
Ok(decoder) => decoder,
|
||||||
|
Err(e) if is_cached => {
|
||||||
|
warn!(
|
||||||
|
"Unable to read cached audio file: {}. Trying to download it.",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
|
||||||
|
// unwrap safety: The file is cached, so session must have a cache
|
||||||
|
if !self.session.cache().unwrap().remove_file(file_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just try it again
|
||||||
|
return self.load_track(spotify_id, position_ms);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to read audio file: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if position_ms != 0 {
|
if position_ms != 0 {
|
||||||
match decoder.seek(position_ms as i64) {
|
if let Err(err) = decoder.seek(position_ms as i64) {
|
||||||
Ok(_) => (),
|
error!("Vorbis error: {}", err);
|
||||||
Err(err) => error!("Vorbis error: {:?}", err),
|
|
||||||
}
|
}
|
||||||
stream_loader_controller.set_stream_mode();
|
stream_loader_controller.set_stream_mode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use futures::{Async, Future, Poll, Stream};
|
||||||
use log::{error, info, trace, warn};
|
use log::{error, info, trace, warn};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, stderr, Write};
|
use std::io::{stderr, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
@ -16,7 +16,7 @@ use url::Url;
|
||||||
use librespot::core::authentication::{get_credentials, Credentials};
|
use librespot::core::authentication::{get_credentials, Credentials};
|
||||||
use librespot::core::cache::Cache;
|
use librespot::core::cache::Cache;
|
||||||
use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig, VolumeCtrl};
|
use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig, VolumeCtrl};
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::{AuthenticationError, Session};
|
||||||
use librespot::core::version;
|
use librespot::core::version;
|
||||||
|
|
||||||
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
||||||
|
@ -436,7 +436,7 @@ struct Main {
|
||||||
|
|
||||||
spirc: Option<Spirc>,
|
spirc: Option<Spirc>,
|
||||||
spirc_task: Option<SpircTask>,
|
spirc_task: Option<SpircTask>,
|
||||||
connect: Box<dyn Future<Item = Session, Error = io::Error>>,
|
connect: Box<dyn Future<Item = Session, Error = AuthenticationError>>,
|
||||||
|
|
||||||
shutdown: bool,
|
shutdown: bool,
|
||||||
last_credentials: Option<Credentials>,
|
last_credentials: Option<Credentials>,
|
||||||
|
|
Loading…
Reference in a new issue