Merge branch 'dev' into rodiojack-backend

This commit is contained in:
Sasha Hilton 2021-02-10 01:07:02 +00:00
parent 52438b1cc2
commit aad4dba8a8
12 changed files with 143 additions and 107 deletions

View file

@ -41,7 +41,7 @@ jobs:
matrix:
os: [ubuntu-latest]
toolchain:
- 1.40.0 # MSRV (Minimum supported rust version)
- 1.42.0 # MSRV (Minimum supported rust version)
- stable
- beta
experimental: [false]

View file

@ -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

View file

@ -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://travis-ci.org/librespot-org/librespot.svg?branch=dev)](https://travis-ci.org/librespot-org/librespot)
[![Build Status](https://github.com/librespot-org/librespot/workflows/test/badge.svg)](https://github.com/librespot-org/librespot/actions)
[![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)
@ -112,4 +111,3 @@ functionality.
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
- [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.

View file

@ -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 {

View file

@ -75,7 +75,7 @@ impl Discovery {
"status": 101,
"statusString": "ERROR-OK",
"spotifyError": 0,
"version": "2.1.0",
"version": "2.7.1",
"deviceID": (self.0.device_id),
"remoteName": (self.0.config.name),
"activeUser": "",
@ -85,6 +85,9 @@ impl Discovery {
"accountReq": "PREMIUM",
"brandDisplayName": "librespot",
"modelDisplayName": "librespot",
"resolverVersion": "0",
"groupStatus": "NONE",
"voiceSupport": "NO",
});
let body = result.to_string();

View file

@ -7,6 +7,7 @@ use sha1::{Digest, Sha1};
use std::io::{self, Read};
use crate::protocol::authentication::AuthenticationType;
use crate::protocol::keyexchange::{APLoginFailed, ErrorCode};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials {
@ -164,3 +165,37 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
(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(),
}
}
}

View file

@ -162,4 +162,17 @@ impl Cache {
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
}
}
}

View file

@ -36,6 +36,16 @@ pub enum DeviceType {
AVR = 6,
STB = 7,
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 {
@ -51,6 +61,14 @@ impl FromStr for DeviceType {
"avr" => Ok(AVR),
"stb" => Ok(STB),
"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(()),
}
}
@ -69,6 +87,16 @@ impl fmt::Display for DeviceType {
AVR => f.write_str("AVR"),
STB => f.write_str("STB"),
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"),
}
}
}

View file

@ -13,7 +13,7 @@ use tokio_core::net::TcpStream;
use tokio_core::reactor::Handle;
use url::Url;
use crate::authentication::Credentials;
use crate::authentication::{AuthenticationError, Credentials};
use crate::version;
use crate::proxytunnel;
@ -66,7 +66,7 @@ pub fn authenticate(
transport: Transport,
credentials: Credentials,
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::keyexchange::APLoginFailed;
@ -101,6 +101,7 @@ pub fn authenticate(
transport
.send((cmd, data))
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
.map_err(|io_err| io_err.into())
.and_then(|(packet, transport)| match packet {
Some((0xac, data)) => {
let welcome_data: APWelcome =
@ -118,10 +119,7 @@ pub fn authenticate(
Some((0xad, data)) => {
let error_data: APLoginFailed =
protobuf::parse_from_bytes(data.as_ref()).unwrap();
panic!(
"Authentication failed with reason: {:?}",
error_data.get_error_code()
)
Err(error_data.into())
}
Some((cmd, _)) => panic!("Unexpected packet {:?}", cmd),

View file

@ -19,6 +19,8 @@ use crate::config::SessionConfig;
use crate::connection;
use crate::mercury::MercuryManager;
pub use crate::authentication::{AuthenticationError, AuthenticationErrorKind};
struct SessionData {
country: String,
time_delta: i64,
@ -53,16 +55,18 @@ impl Session {
credentials: Credentials,
cache: Option<Cache>,
handle: Handle,
) -> Box<dyn Future<Item = Session, Error = io::Error>> {
) -> Box<dyn Future<Item = Session, Error = AuthenticationError>> {
let access_point =
apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
let handle_ = handle.clone();
let proxy = config.proxy.clone();
let connection = access_point.and_then(move |addr| {
info!("Connecting to AP \"{}\"", addr);
connection::connect(addr, &handle_, &proxy)
});
let connection = access_point
.and_then(move |addr| {
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 authentication = connection.and_then(move |connection| {

View file

@ -654,20 +654,24 @@ impl PlayerTrackLoader {
FileFormat::OGG_VORBIS_96,
],
};
let format = formats
.iter()
.find(|format| audio.files.contains_key(format))
.unwrap();
let file_id = match audio.files.get(&format) {
Some(&file_id) => file_id,
let entry = formats.iter().find_map(|format| {
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 => {
warn!("<{}> in not available in format {:?}", audio.name, format);
warn!("<{}> is not available in any supported format", audio.name);
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 key = self.session.audio_key().request(spotify_id, file_id);
@ -685,6 +689,7 @@ impl PlayerTrackLoader {
return None;
}
};
let is_cached = encrypted_file.is_cached();
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 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 {
match decoder.seek(position_ms as i64) {
Ok(_) => (),
Err(err) => error!("Vorbis error: {:?}", err),
if let Err(err) = decoder.seek(position_ms as i64) {
error!("Vorbis error: {}", err);
}
stream_loader_controller.set_stream_mode();
}

View file

@ -3,7 +3,7 @@ use futures::{Async, Future, Poll, Stream};
use log::{error, info, trace, warn};
use sha1::{Digest, Sha1};
use std::env;
use std::io::{self, stderr, Write};
use std::io::{stderr, Write};
use std::mem;
use std::path::Path;
use std::process::exit;
@ -16,7 +16,7 @@ use url::Url;
use librespot::core::authentication::{get_credentials, Credentials};
use librespot::core::cache::Cache;
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::connect::discovery::{discovery, DiscoveryStream};
@ -436,7 +436,7 @@ struct Main {
spirc: Option<Spirc>,
spirc_task: Option<SpircTask>,
connect: Box<dyn Future<Item = Session, Error = io::Error>>,
connect: Box<dyn Future<Item = Session, Error = AuthenticationError>>,
shutdown: bool,
last_credentials: Option<Credentials>,