mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Remove unwraps from librespot-audio
This commit is contained in:
parent
d18a0d1803
commit
0d51fd43dc
9 changed files with 301 additions and 165 deletions
|
@ -25,16 +25,28 @@ use self::receive::audio_file_fetch;
|
||||||
|
|
||||||
use crate::range_set::{Range, RangeSet};
|
use crate::range_set::{Range, RangeSet};
|
||||||
|
|
||||||
|
pub type AudioFileResult = Result<(), AudioFileError>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum AudioFileError {
|
pub enum AudioFileError {
|
||||||
#[error("could not complete CDN request: {0}")]
|
#[error("could not complete CDN request: {0}")]
|
||||||
Cdn(hyper::Error),
|
Cdn(#[from] hyper::Error),
|
||||||
|
#[error("channel was disconnected")]
|
||||||
|
Channel,
|
||||||
#[error("empty response")]
|
#[error("empty response")]
|
||||||
Empty,
|
Empty,
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
#[error("output file unavailable")]
|
||||||
|
Output,
|
||||||
#[error("error parsing response")]
|
#[error("error parsing response")]
|
||||||
Parsing,
|
Parsing,
|
||||||
|
#[error("mutex was poisoned")]
|
||||||
|
Poisoned,
|
||||||
#[error("could not complete API request: {0}")]
|
#[error("could not complete API request: {0}")]
|
||||||
SpClient(#[from] SpClientError),
|
SpClient(#[from] SpClientError),
|
||||||
|
#[error("streamer did not report progress")]
|
||||||
|
Timeout,
|
||||||
#[error("could not get CDN URL: {0}")]
|
#[error("could not get CDN URL: {0}")]
|
||||||
Url(#[from] CdnUrlError),
|
Url(#[from] CdnUrlError),
|
||||||
}
|
}
|
||||||
|
@ -42,7 +54,7 @@ pub enum AudioFileError {
|
||||||
/// The minimum size of a block that is requested from the Spotify servers in one request.
|
/// The minimum size of a block that is requested from the Spotify servers in one request.
|
||||||
/// This is the block size that is typically requested while doing a `seek()` on a file.
|
/// This is the block size that is typically requested while doing a `seek()` on a file.
|
||||||
/// Note: smaller requests can happen if part of the block is downloaded already.
|
/// Note: smaller requests can happen if part of the block is downloaded already.
|
||||||
pub const MINIMUM_DOWNLOAD_SIZE: usize = 1024 * 256;
|
pub const MINIMUM_DOWNLOAD_SIZE: usize = 1024 * 128;
|
||||||
|
|
||||||
/// The amount of data that is requested when initially opening a file.
|
/// The amount of data that is requested when initially opening a file.
|
||||||
/// Note: if the file is opened to play from the beginning, the amount of data to
|
/// Note: if the file is opened to play from the beginning, the amount of data to
|
||||||
|
@ -142,23 +154,32 @@ impl StreamLoaderController {
|
||||||
self.file_size == 0
|
self.file_size == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range_available(&self, range: Range) -> bool {
|
pub fn range_available(&self, range: Range) -> Result<bool, AudioFileError> {
|
||||||
if let Some(ref shared) = self.stream_shared {
|
let available = if let Some(ref shared) = self.stream_shared {
|
||||||
let download_status = shared.download_status.lock().unwrap();
|
let download_status = shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
|
|
||||||
range.length
|
range.length
|
||||||
<= download_status
|
<= download_status
|
||||||
.downloaded
|
.downloaded
|
||||||
.contained_length_from_value(range.start)
|
.contained_length_from_value(range.start)
|
||||||
} else {
|
} else {
|
||||||
range.length <= self.len() - range.start
|
range.length <= self.len() - range.start
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(available)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn range_to_end_available(&self) -> bool {
|
pub fn range_to_end_available(&self) -> Result<bool, AudioFileError> {
|
||||||
self.stream_shared.as_ref().map_or(true, |shared| {
|
match self.stream_shared {
|
||||||
|
Some(ref shared) => {
|
||||||
let read_position = shared.read_position.load(atomic::Ordering::Relaxed);
|
let read_position = shared.read_position.load(atomic::Ordering::Relaxed);
|
||||||
self.range_available(Range::new(read_position, self.len() - read_position))
|
self.range_available(Range::new(read_position, self.len() - read_position))
|
||||||
})
|
}
|
||||||
|
None => Ok(true),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ping_time(&self) -> Duration {
|
pub fn ping_time(&self) -> Duration {
|
||||||
|
@ -179,7 +200,7 @@ impl StreamLoaderController {
|
||||||
self.send_stream_loader_command(StreamLoaderCommand::Fetch(range));
|
self.send_stream_loader_command(StreamLoaderCommand::Fetch(range));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_blocking(&self, mut range: Range) {
|
pub fn fetch_blocking(&self, mut range: Range) -> AudioFileResult {
|
||||||
// signal the stream loader to tech a range of the file and block until it is loaded.
|
// signal the stream loader to tech a range of the file and block until it is loaded.
|
||||||
|
|
||||||
// ensure the range is within the file's bounds.
|
// ensure the range is within the file's bounds.
|
||||||
|
@ -192,7 +213,11 @@ impl StreamLoaderController {
|
||||||
self.fetch(range);
|
self.fetch(range);
|
||||||
|
|
||||||
if let Some(ref shared) = self.stream_shared {
|
if let Some(ref shared) = self.stream_shared {
|
||||||
let mut download_status = shared.download_status.lock().unwrap();
|
let mut download_status = shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
|
|
||||||
while range.length
|
while range.length
|
||||||
> download_status
|
> download_status
|
||||||
.downloaded
|
.downloaded
|
||||||
|
@ -201,7 +226,7 @@ impl StreamLoaderController {
|
||||||
download_status = shared
|
download_status = shared
|
||||||
.cond
|
.cond
|
||||||
.wait_timeout(download_status, DOWNLOAD_TIMEOUT)
|
.wait_timeout(download_status, DOWNLOAD_TIMEOUT)
|
||||||
.unwrap()
|
.map_err(|_| AudioFileError::Timeout)?
|
||||||
.0;
|
.0;
|
||||||
if range.length
|
if range.length
|
||||||
> (download_status
|
> (download_status
|
||||||
|
@ -215,6 +240,8 @@ impl StreamLoaderController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_next(&self, length: usize) {
|
pub fn fetch_next(&self, length: usize) {
|
||||||
|
@ -223,17 +250,20 @@ impl StreamLoaderController {
|
||||||
start: shared.read_position.load(atomic::Ordering::Relaxed),
|
start: shared.read_position.load(atomic::Ordering::Relaxed),
|
||||||
length,
|
length,
|
||||||
};
|
};
|
||||||
self.fetch(range)
|
self.fetch(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_next_blocking(&self, length: usize) {
|
pub fn fetch_next_blocking(&self, length: usize) -> AudioFileResult {
|
||||||
if let Some(ref shared) = self.stream_shared {
|
match self.stream_shared {
|
||||||
|
Some(ref shared) => {
|
||||||
let range = Range {
|
let range = Range {
|
||||||
start: shared.read_position.load(atomic::Ordering::Relaxed),
|
start: shared.read_position.load(atomic::Ordering::Relaxed),
|
||||||
length,
|
length,
|
||||||
};
|
};
|
||||||
self.fetch_blocking(range);
|
self.fetch_blocking(range)
|
||||||
|
}
|
||||||
|
None => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,11 +340,9 @@ impl AudioFile {
|
||||||
let session_ = session.clone();
|
let session_ = session.clone();
|
||||||
session.spawn(complete_rx.map_ok(move |mut file| {
|
session.spawn(complete_rx.map_ok(move |mut file| {
|
||||||
if let Some(cache) = session_.cache() {
|
if let Some(cache) = session_.cache() {
|
||||||
if cache.file_path(file_id).is_some() {
|
if cache.save_file(file_id, &mut file) {
|
||||||
cache.save_file(file_id, &mut file);
|
|
||||||
debug!("File {} cached to {:?}", file_id, cache.file(file_id));
|
debug!("File {} cached to {:?}", file_id, cache.file(file_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Downloading file {} complete", file_id);
|
debug!("Downloading file {} complete", file_id);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -322,8 +350,8 @@ impl AudioFile {
|
||||||
Ok(AudioFile::Streaming(streaming.await?))
|
Ok(AudioFile::Streaming(streaming.await?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stream_loader_controller(&self) -> StreamLoaderController {
|
pub fn get_stream_loader_controller(&self) -> Result<StreamLoaderController, AudioFileError> {
|
||||||
match self {
|
let controller = match self {
|
||||||
AudioFile::Streaming(ref stream) => StreamLoaderController {
|
AudioFile::Streaming(ref stream) => StreamLoaderController {
|
||||||
channel_tx: Some(stream.stream_loader_command_tx.clone()),
|
channel_tx: Some(stream.stream_loader_command_tx.clone()),
|
||||||
stream_shared: Some(stream.shared.clone()),
|
stream_shared: Some(stream.shared.clone()),
|
||||||
|
@ -332,9 +360,11 @@ impl AudioFile {
|
||||||
AudioFile::Cached(ref file) => StreamLoaderController {
|
AudioFile::Cached(ref file) => StreamLoaderController {
|
||||||
channel_tx: None,
|
channel_tx: None,
|
||||||
stream_shared: None,
|
stream_shared: None,
|
||||||
file_size: file.metadata().unwrap().len() as usize,
|
file_size: file.metadata()?.len() as usize,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_cached(&self) -> bool {
|
pub fn is_cached(&self) -> bool {
|
||||||
|
@ -410,8 +440,8 @@ impl AudioFileStreaming {
|
||||||
read_position: AtomicUsize::new(0),
|
read_position: AtomicUsize::new(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
let write_file = NamedTempFile::new_in(session.config().tmp_dir.clone()).unwrap();
|
let write_file = NamedTempFile::new_in(session.config().tmp_dir.clone())?;
|
||||||
let read_file = write_file.reopen().unwrap();
|
let read_file = write_file.reopen()?;
|
||||||
|
|
||||||
let (stream_loader_command_tx, stream_loader_command_rx) =
|
let (stream_loader_command_tx, stream_loader_command_rx) =
|
||||||
mpsc::unbounded_channel::<StreamLoaderCommand>();
|
mpsc::unbounded_channel::<StreamLoaderCommand>();
|
||||||
|
@ -444,7 +474,12 @@ impl Read for AudioFileStreaming {
|
||||||
|
|
||||||
let length = min(output.len(), self.shared.file_size - offset);
|
let length = min(output.len(), self.shared.file_size - offset);
|
||||||
|
|
||||||
let length_to_request = match *(self.shared.download_strategy.lock().unwrap()) {
|
let length_to_request = match *(self
|
||||||
|
.shared
|
||||||
|
.download_strategy
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "mutex was poisoned"))?)
|
||||||
|
{
|
||||||
DownloadStrategy::RandomAccess() => length,
|
DownloadStrategy::RandomAccess() => length,
|
||||||
DownloadStrategy::Streaming() => {
|
DownloadStrategy::Streaming() => {
|
||||||
// Due to the read-ahead stuff, we potentially request more than the actual request demanded.
|
// Due to the read-ahead stuff, we potentially request more than the actual request demanded.
|
||||||
|
@ -468,14 +503,18 @@ impl Read for AudioFileStreaming {
|
||||||
let mut ranges_to_request = RangeSet::new();
|
let mut ranges_to_request = RangeSet::new();
|
||||||
ranges_to_request.add_range(&Range::new(offset, length_to_request));
|
ranges_to_request.add_range(&Range::new(offset, length_to_request));
|
||||||
|
|
||||||
let mut download_status = self.shared.download_status.lock().unwrap();
|
let mut download_status = self
|
||||||
|
.shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "mutex was poisoned"))?;
|
||||||
ranges_to_request.subtract_range_set(&download_status.downloaded);
|
ranges_to_request.subtract_range_set(&download_status.downloaded);
|
||||||
ranges_to_request.subtract_range_set(&download_status.requested);
|
ranges_to_request.subtract_range_set(&download_status.requested);
|
||||||
|
|
||||||
for &range in ranges_to_request.iter() {
|
for &range in ranges_to_request.iter() {
|
||||||
self.stream_loader_command_tx
|
self.stream_loader_command_tx
|
||||||
.send(StreamLoaderCommand::Fetch(range))
|
.send(StreamLoaderCommand::Fetch(range))
|
||||||
.unwrap();
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "tx channel is disconnected"))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
|
@ -484,7 +523,12 @@ impl Read for AudioFileStreaming {
|
||||||
|
|
||||||
let mut download_message_printed = false;
|
let mut download_message_printed = false;
|
||||||
while !download_status.downloaded.contains(offset) {
|
while !download_status.downloaded.contains(offset) {
|
||||||
if let DownloadStrategy::Streaming() = *self.shared.download_strategy.lock().unwrap() {
|
if let DownloadStrategy::Streaming() = *self
|
||||||
|
.shared
|
||||||
|
.download_strategy
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "mutex was poisoned"))?
|
||||||
|
{
|
||||||
if !download_message_printed {
|
if !download_message_printed {
|
||||||
debug!("Stream waiting for download of file position {}. Downloaded ranges: {}. Pending ranges: {}", offset, download_status.downloaded, download_status.requested.minus(&download_status.downloaded));
|
debug!("Stream waiting for download of file position {}. Downloaded ranges: {}. Pending ranges: {}", offset, download_status.downloaded, download_status.requested.minus(&download_status.downloaded));
|
||||||
download_message_printed = true;
|
download_message_printed = true;
|
||||||
|
@ -494,7 +538,7 @@ impl Read for AudioFileStreaming {
|
||||||
.shared
|
.shared
|
||||||
.cond
|
.cond
|
||||||
.wait_timeout(download_status, DOWNLOAD_TIMEOUT)
|
.wait_timeout(download_status, DOWNLOAD_TIMEOUT)
|
||||||
.unwrap()
|
.map_err(|_| io::Error::new(io::ErrorKind::Other, "timeout acquiring mutex"))?
|
||||||
.0;
|
.0;
|
||||||
}
|
}
|
||||||
let available_length = download_status
|
let available_length = download_status
|
||||||
|
@ -503,7 +547,7 @@ impl Read for AudioFileStreaming {
|
||||||
assert!(available_length > 0);
|
assert!(available_length > 0);
|
||||||
drop(download_status);
|
drop(download_status);
|
||||||
|
|
||||||
self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap();
|
self.position = self.read_file.seek(SeekFrom::Start(offset as u64))?;
|
||||||
let read_len = min(length, available_length);
|
let read_len = min(length, available_length);
|
||||||
let read_len = self.read_file.read(&mut output[..read_len])?;
|
let read_len = self.read_file.read(&mut output[..read_len])?;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,10 @@ use librespot_core::session::Session;
|
||||||
|
|
||||||
use crate::range_set::{Range, RangeSet};
|
use crate::range_set::{Range, RangeSet};
|
||||||
|
|
||||||
use super::{AudioFileShared, DownloadStrategy, StreamLoaderCommand, StreamingRequest};
|
use super::{
|
||||||
|
AudioFileError, AudioFileResult, AudioFileShared, DownloadStrategy, StreamLoaderCommand,
|
||||||
|
StreamingRequest,
|
||||||
|
};
|
||||||
use super::{
|
use super::{
|
||||||
FAST_PREFETCH_THRESHOLD_FACTOR, MAXIMUM_ASSUMED_PING_TIME, MAX_PREFETCH_REQUESTS,
|
FAST_PREFETCH_THRESHOLD_FACTOR, MAXIMUM_ASSUMED_PING_TIME, MAX_PREFETCH_REQUESTS,
|
||||||
MINIMUM_DOWNLOAD_SIZE, PREFETCH_THRESHOLD_FACTOR,
|
MINIMUM_DOWNLOAD_SIZE, PREFETCH_THRESHOLD_FACTOR,
|
||||||
|
@ -33,7 +36,7 @@ async fn receive_data(
|
||||||
shared: Arc<AudioFileShared>,
|
shared: Arc<AudioFileShared>,
|
||||||
file_data_tx: mpsc::UnboundedSender<ReceivedData>,
|
file_data_tx: mpsc::UnboundedSender<ReceivedData>,
|
||||||
mut request: StreamingRequest,
|
mut request: StreamingRequest,
|
||||||
) {
|
) -> AudioFileResult {
|
||||||
let requested_offset = request.offset;
|
let requested_offset = request.offset;
|
||||||
let requested_length = request.length;
|
let requested_length = request.length;
|
||||||
|
|
||||||
|
@ -97,7 +100,10 @@ async fn receive_data(
|
||||||
if request_length > 0 {
|
if request_length > 0 {
|
||||||
let missing_range = Range::new(data_offset, request_length);
|
let missing_range = Range::new(data_offset, request_length);
|
||||||
|
|
||||||
let mut download_status = shared.download_status.lock().unwrap();
|
let mut download_status = shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
download_status.requested.subtract_range(&missing_range);
|
download_status.requested.subtract_range(&missing_range);
|
||||||
shared.cond.notify_all();
|
shared.cond.notify_all();
|
||||||
}
|
}
|
||||||
|
@ -106,17 +112,24 @@ async fn receive_data(
|
||||||
.number_of_open_requests
|
.number_of_open_requests
|
||||||
.fetch_sub(1, Ordering::SeqCst);
|
.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
|
||||||
if let Err(e) = result {
|
match result {
|
||||||
error!(
|
Ok(()) => {
|
||||||
"Error from streamer for range {} (+{}): {:?}",
|
if request_length > 0 {
|
||||||
requested_offset, requested_length, e
|
|
||||||
);
|
|
||||||
} else if request_length > 0 {
|
|
||||||
warn!(
|
warn!(
|
||||||
"Streamer for range {} (+{}) received less data from server than requested.",
|
"Streamer for range {} (+{}) received less data from server than requested.",
|
||||||
requested_offset, requested_length
|
requested_offset, requested_length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"Error from streamer for range {} (+{}): {:?}",
|
||||||
|
requested_offset, requested_length, e
|
||||||
|
);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AudioFileFetch {
|
struct AudioFileFetch {
|
||||||
|
@ -137,24 +150,21 @@ enum ControlFlow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFileFetch {
|
impl AudioFileFetch {
|
||||||
fn get_download_strategy(&mut self) -> DownloadStrategy {
|
fn get_download_strategy(&mut self) -> Result<DownloadStrategy, AudioFileError> {
|
||||||
*(self.shared.download_strategy.lock().unwrap())
|
let strategy = self
|
||||||
|
.shared
|
||||||
|
.download_strategy
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
|
|
||||||
|
Ok(*(strategy))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_range(&mut self, offset: usize, mut length: usize) {
|
fn download_range(&mut self, offset: usize, mut length: usize) -> AudioFileResult {
|
||||||
if length < MINIMUM_DOWNLOAD_SIZE {
|
if length < MINIMUM_DOWNLOAD_SIZE {
|
||||||
length = MINIMUM_DOWNLOAD_SIZE;
|
length = MINIMUM_DOWNLOAD_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the values are within the bounds
|
|
||||||
if offset >= self.shared.file_size {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if length == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset + length > self.shared.file_size {
|
if offset + length > self.shared.file_size {
|
||||||
length = self.shared.file_size - offset;
|
length = self.shared.file_size - offset;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +172,11 @@ impl AudioFileFetch {
|
||||||
let mut ranges_to_request = RangeSet::new();
|
let mut ranges_to_request = RangeSet::new();
|
||||||
ranges_to_request.add_range(&Range::new(offset, length));
|
ranges_to_request.add_range(&Range::new(offset, length));
|
||||||
|
|
||||||
let mut download_status = self.shared.download_status.lock().unwrap();
|
let mut download_status = self
|
||||||
|
.shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
|
|
||||||
ranges_to_request.subtract_range_set(&download_status.downloaded);
|
ranges_to_request.subtract_range_set(&download_status.downloaded);
|
||||||
ranges_to_request.subtract_range_set(&download_status.requested);
|
ranges_to_request.subtract_range_set(&download_status.requested);
|
||||||
|
@ -205,9 +219,15 @@ impl AudioFileFetch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_fetch_more_data(&mut self, bytes: usize, max_requests_to_send: usize) {
|
fn pre_fetch_more_data(
|
||||||
|
&mut self,
|
||||||
|
bytes: usize,
|
||||||
|
max_requests_to_send: usize,
|
||||||
|
) -> AudioFileResult {
|
||||||
let mut bytes_to_go = bytes;
|
let mut bytes_to_go = bytes;
|
||||||
let mut requests_to_go = max_requests_to_send;
|
let mut requests_to_go = max_requests_to_send;
|
||||||
|
|
||||||
|
@ -216,7 +236,11 @@ impl AudioFileFetch {
|
||||||
let mut missing_data = RangeSet::new();
|
let mut missing_data = RangeSet::new();
|
||||||
missing_data.add_range(&Range::new(0, self.shared.file_size));
|
missing_data.add_range(&Range::new(0, self.shared.file_size));
|
||||||
{
|
{
|
||||||
let download_status = self.shared.download_status.lock().unwrap();
|
let download_status = self
|
||||||
|
.shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
missing_data.subtract_range_set(&download_status.downloaded);
|
missing_data.subtract_range_set(&download_status.downloaded);
|
||||||
missing_data.subtract_range_set(&download_status.requested);
|
missing_data.subtract_range_set(&download_status.requested);
|
||||||
}
|
}
|
||||||
|
@ -234,7 +258,7 @@ impl AudioFileFetch {
|
||||||
let range = tail_end.get_range(0);
|
let range = tail_end.get_range(0);
|
||||||
let offset = range.start;
|
let offset = range.start;
|
||||||
let length = min(range.length, bytes_to_go);
|
let length = min(range.length, bytes_to_go);
|
||||||
self.download_range(offset, length);
|
self.download_range(offset, length)?;
|
||||||
requests_to_go -= 1;
|
requests_to_go -= 1;
|
||||||
bytes_to_go -= length;
|
bytes_to_go -= length;
|
||||||
} else if !missing_data.is_empty() {
|
} else if !missing_data.is_empty() {
|
||||||
|
@ -242,20 +266,20 @@ impl AudioFileFetch {
|
||||||
let range = missing_data.get_range(0);
|
let range = missing_data.get_range(0);
|
||||||
let offset = range.start;
|
let offset = range.start;
|
||||||
let length = min(range.length, bytes_to_go);
|
let length = min(range.length, bytes_to_go);
|
||||||
self.download_range(offset, length);
|
self.download_range(offset, length)?;
|
||||||
requests_to_go -= 1;
|
requests_to_go -= 1;
|
||||||
bytes_to_go -= length;
|
bytes_to_go -= length;
|
||||||
} else {
|
} else {
|
||||||
return;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_file_data(&mut self, data: ReceivedData) -> ControlFlow {
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_file_data(&mut self, data: ReceivedData) -> Result<ControlFlow, AudioFileError> {
|
||||||
match data {
|
match data {
|
||||||
ReceivedData::ResponseTime(response_time) => {
|
ReceivedData::ResponseTime(response_time) => {
|
||||||
trace!("Ping time estimated as: {} ms", response_time.as_millis());
|
|
||||||
|
|
||||||
// prune old response times. Keep at most two so we can push a third.
|
// prune old response times. Keep at most two so we can push a third.
|
||||||
while self.network_response_times.len() >= 3 {
|
while self.network_response_times.len() >= 3 {
|
||||||
self.network_response_times.remove(0);
|
self.network_response_times.remove(0);
|
||||||
|
@ -276,24 +300,27 @@ impl AudioFileFetch {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
trace!("Ping time estimated as: {} ms", ping_time.as_millis());
|
||||||
|
|
||||||
// store our new estimate for everyone to see
|
// store our new estimate for everyone to see
|
||||||
self.shared
|
self.shared
|
||||||
.ping_time_ms
|
.ping_time_ms
|
||||||
.store(ping_time.as_millis() as usize, Ordering::Relaxed);
|
.store(ping_time.as_millis() as usize, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
ReceivedData::Data(data) => {
|
ReceivedData::Data(data) => {
|
||||||
self.output
|
match self.output.as_mut() {
|
||||||
.as_mut()
|
Some(output) => {
|
||||||
.unwrap()
|
output.seek(SeekFrom::Start(data.offset as u64))?;
|
||||||
.seek(SeekFrom::Start(data.offset as u64))
|
output.write_all(data.data.as_ref())?;
|
||||||
.unwrap();
|
}
|
||||||
self.output
|
None => return Err(AudioFileError::Output),
|
||||||
.as_mut()
|
}
|
||||||
.unwrap()
|
|
||||||
.write_all(data.data.as_ref())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut download_status = self.shared.download_status.lock().unwrap();
|
let mut download_status = self
|
||||||
|
.shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
|
|
||||||
let received_range = Range::new(data.offset, data.data.len());
|
let received_range = Range::new(data.offset, data.data.len());
|
||||||
download_status.downloaded.add_range(&received_range);
|
download_status.downloaded.add_range(&received_range);
|
||||||
|
@ -305,36 +332,50 @@ impl AudioFileFetch {
|
||||||
drop(download_status);
|
drop(download_status);
|
||||||
|
|
||||||
if full {
|
if full {
|
||||||
self.finish();
|
self.finish()?;
|
||||||
return ControlFlow::Break;
|
return Ok(ControlFlow::Break);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlFlow::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_stream_loader_command(&mut self, cmd: StreamLoaderCommand) -> ControlFlow {
|
Ok(ControlFlow::Continue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_stream_loader_command(
|
||||||
|
&mut self,
|
||||||
|
cmd: StreamLoaderCommand,
|
||||||
|
) -> Result<ControlFlow, AudioFileError> {
|
||||||
match cmd {
|
match cmd {
|
||||||
StreamLoaderCommand::Fetch(request) => {
|
StreamLoaderCommand::Fetch(request) => {
|
||||||
self.download_range(request.start, request.length);
|
self.download_range(request.start, request.length)?;
|
||||||
}
|
}
|
||||||
StreamLoaderCommand::RandomAccessMode() => {
|
StreamLoaderCommand::RandomAccessMode() => {
|
||||||
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::RandomAccess();
|
*(self
|
||||||
|
.shared
|
||||||
|
.download_strategy
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?) = DownloadStrategy::RandomAccess();
|
||||||
}
|
}
|
||||||
StreamLoaderCommand::StreamMode() => {
|
StreamLoaderCommand::StreamMode() => {
|
||||||
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::Streaming();
|
*(self
|
||||||
|
.shared
|
||||||
|
.download_strategy
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?) = DownloadStrategy::Streaming();
|
||||||
}
|
}
|
||||||
StreamLoaderCommand::Close() => return ControlFlow::Break,
|
StreamLoaderCommand::Close() => return Ok(ControlFlow::Break),
|
||||||
}
|
}
|
||||||
ControlFlow::Continue
|
Ok(ControlFlow::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&mut self) {
|
fn finish(&mut self) -> AudioFileResult {
|
||||||
let mut output = self.output.take().unwrap();
|
let mut output = self.output.take().ok_or(AudioFileError::Output)?;
|
||||||
let complete_tx = self.complete_tx.take().unwrap();
|
let complete_tx = self.complete_tx.take().ok_or(AudioFileError::Output)?;
|
||||||
|
|
||||||
output.seek(SeekFrom::Start(0)).unwrap();
|
output.seek(SeekFrom::Start(0))?;
|
||||||
let _ = complete_tx.send(output);
|
complete_tx
|
||||||
|
.send(output)
|
||||||
|
.map_err(|_| AudioFileError::Channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +386,7 @@ pub(super) async fn audio_file_fetch(
|
||||||
output: NamedTempFile,
|
output: NamedTempFile,
|
||||||
mut stream_loader_command_rx: mpsc::UnboundedReceiver<StreamLoaderCommand>,
|
mut stream_loader_command_rx: mpsc::UnboundedReceiver<StreamLoaderCommand>,
|
||||||
complete_tx: oneshot::Sender<NamedTempFile>,
|
complete_tx: oneshot::Sender<NamedTempFile>,
|
||||||
) {
|
) -> AudioFileResult {
|
||||||
let (file_data_tx, mut file_data_rx) = mpsc::unbounded_channel();
|
let (file_data_tx, mut file_data_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -353,7 +394,10 @@ pub(super) async fn audio_file_fetch(
|
||||||
initial_request.offset,
|
initial_request.offset,
|
||||||
initial_request.offset + initial_request.length,
|
initial_request.offset + initial_request.length,
|
||||||
);
|
);
|
||||||
let mut download_status = shared.download_status.lock().unwrap();
|
let mut download_status = shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
download_status.requested.add_range(&requested_range);
|
download_status.requested.add_range(&requested_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,25 +420,39 @@ pub(super) async fn audio_file_fetch(
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
cmd = stream_loader_command_rx.recv() => {
|
cmd = stream_loader_command_rx.recv() => {
|
||||||
if cmd.map_or(true, |cmd| fetch.handle_stream_loader_command(cmd) == ControlFlow::Break) {
|
match cmd {
|
||||||
|
Some(cmd) => {
|
||||||
|
if fetch.handle_stream_loader_command(cmd)? == ControlFlow::Break {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
data = file_data_rx.recv() => {
|
data = file_data_rx.recv() => {
|
||||||
if data.map_or(true, |data| fetch.handle_file_data(data) == ControlFlow::Break) {
|
match data {
|
||||||
|
Some(data) => {
|
||||||
|
if fetch.handle_file_data(data)? == ControlFlow::Break {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fetch.get_download_strategy() == DownloadStrategy::Streaming() {
|
if fetch.get_download_strategy()? == DownloadStrategy::Streaming() {
|
||||||
let number_of_open_requests =
|
let number_of_open_requests =
|
||||||
fetch.shared.number_of_open_requests.load(Ordering::SeqCst);
|
fetch.shared.number_of_open_requests.load(Ordering::SeqCst);
|
||||||
if number_of_open_requests < MAX_PREFETCH_REQUESTS {
|
if number_of_open_requests < MAX_PREFETCH_REQUESTS {
|
||||||
let max_requests_to_send = MAX_PREFETCH_REQUESTS - number_of_open_requests;
|
let max_requests_to_send = MAX_PREFETCH_REQUESTS - number_of_open_requests;
|
||||||
|
|
||||||
let bytes_pending: usize = {
|
let bytes_pending: usize = {
|
||||||
let download_status = fetch.shared.download_status.lock().unwrap();
|
let download_status = fetch
|
||||||
|
.shared
|
||||||
|
.download_status
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| AudioFileError::Poisoned)?;
|
||||||
download_status
|
download_status
|
||||||
.requested
|
.requested
|
||||||
.minus(&download_status.downloaded)
|
.minus(&download_status.downloaded)
|
||||||
|
@ -418,9 +476,11 @@ pub(super) async fn audio_file_fetch(
|
||||||
fetch.pre_fetch_more_data(
|
fetch.pre_fetch_more_data(
|
||||||
desired_pending_bytes - bytes_pending,
|
desired_pending_bytes - bytes_pending,
|
||||||
max_requests_to_send,
|
max_requests_to_send,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod fetch;
|
||||||
mod range_set;
|
mod range_set;
|
||||||
|
|
||||||
pub use decrypt::AudioDecrypt;
|
pub use decrypt::AudioDecrypt;
|
||||||
pub use fetch::{AudioFile, StreamLoaderController};
|
pub use fetch::{AudioFile, AudioFileError, StreamLoaderController};
|
||||||
pub use fetch::{
|
pub use fetch::{
|
||||||
READ_AHEAD_BEFORE_PLAYBACK, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
|
READ_AHEAD_BEFORE_PLAYBACK, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
|
||||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub struct Range {
|
||||||
|
|
||||||
impl fmt::Display for Range {
|
impl fmt::Display for Range {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
return write!(f, "[{}, {}]", self.start, self.start + self.length - 1);
|
write!(f, "[{}, {}]", self.start, self.start + self.length - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,16 +24,16 @@ impl Range {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RangeSet {
|
pub struct RangeSet {
|
||||||
ranges: Vec<Range>,
|
ranges: Vec<Range>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for RangeSet {
|
impl fmt::Display for RangeSet {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "(").unwrap();
|
write!(f, "(")?;
|
||||||
for range in self.ranges.iter() {
|
for range in self.ranges.iter() {
|
||||||
write!(f, "{}", range).unwrap();
|
write!(f, "{}", range)?;
|
||||||
}
|
}
|
||||||
write!(f, ")")
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,7 +350,7 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_path(&self, file: FileId) -> Option<PathBuf> {
|
fn file_path(&self, file: FileId) -> Option<PathBuf> {
|
||||||
self.audio_location.as_ref().map(|location| {
|
self.audio_location.as_ref().map(|location| {
|
||||||
let name = file.to_base16();
|
let name = file.to_base16();
|
||||||
let mut path = location.join(&name[0..2]);
|
let mut path = location.join(&name[0..2]);
|
||||||
|
@ -377,25 +377,23 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file<F: Read>(&self, file: FileId, contents: &mut F) {
|
pub fn save_file<F: Read>(&self, file: FileId, contents: &mut F) -> bool {
|
||||||
let path = if let Some(path) = self.file_path(file) {
|
if let Some(path) = self.file_path(file) {
|
||||||
path
|
if let Some(parent) = path.parent() {
|
||||||
} else {
|
if let Ok(size) = fs::create_dir_all(parent)
|
||||||
return;
|
|
||||||
};
|
|
||||||
let parent = path.parent().unwrap();
|
|
||||||
|
|
||||||
let result = fs::create_dir_all(parent)
|
|
||||||
.and_then(|_| File::create(&path))
|
.and_then(|_| File::create(&path))
|
||||||
.and_then(|mut file| io::copy(contents, &mut file));
|
.and_then(|mut file| io::copy(contents, &mut file))
|
||||||
|
{
|
||||||
if let Ok(size) = result {
|
|
||||||
if let Some(limiter) = self.size_limiter.as_deref() {
|
if let Some(limiter) = self.size_limiter.as_deref() {
|
||||||
limiter.add(&path, size);
|
limiter.add(&path, size);
|
||||||
limiter.prune();
|
limiter.prune();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_file(&self, file: FileId) -> Result<(), RemoveFileError> {
|
pub fn remove_file(&self, file: FileId) -> Result<(), RemoveFileError> {
|
||||||
let path = self.file_path(file).ok_or(RemoveFileError(()))?;
|
let path = self.file_path(file).ok_or(RemoveFileError(()))?;
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl CdnUrl {
|
||||||
return Err(CdnUrlError::Empty);
|
return Err(CdnUrlError::Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove expired URLs until the first one is current, or none are left
|
// prune expired URLs until the first one is current, or none are left
|
||||||
let now = Local::now();
|
let now = Local::now();
|
||||||
while !self.urls.is_empty() {
|
while !self.urls.is_empty() {
|
||||||
let maybe_expiring = self.urls[0].1;
|
let maybe_expiring = self.urls[0].1;
|
||||||
|
|
|
@ -14,7 +14,9 @@ use url::Url;
|
||||||
|
|
||||||
use std::env::consts::OS;
|
use std::env::consts::OS;
|
||||||
|
|
||||||
use crate::version::{SPOTIFY_MOBILE_VERSION, SPOTIFY_VERSION, VERSION_STRING};
|
use crate::version::{
|
||||||
|
FALLBACK_USER_AGENT, SPOTIFY_MOBILE_VERSION, SPOTIFY_VERSION, VERSION_STRING,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct HttpClient {
|
pub struct HttpClient {
|
||||||
user_agent: HeaderValue,
|
user_agent: HeaderValue,
|
||||||
|
@ -64,8 +66,8 @@ impl HttpClient {
|
||||||
|
|
||||||
let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| {
|
let user_agent = HeaderValue::from_str(user_agent_str).unwrap_or_else(|err| {
|
||||||
error!("Invalid user agent <{}>: {}", user_agent_str, err);
|
error!("Invalid user agent <{}>: {}", user_agent_str, err);
|
||||||
error!("Parts of the API will probably not work. Please report this as a bug.");
|
error!("Please report this as a bug.");
|
||||||
HeaderValue::from_static("")
|
HeaderValue::from_static(FALLBACK_USER_AGENT)
|
||||||
});
|
});
|
||||||
|
|
||||||
// configuring TLS is expensive and should be done once per process
|
// configuring TLS is expensive and should be done once per process
|
||||||
|
|
|
@ -21,3 +21,6 @@ pub const SPOTIFY_VERSION: u64 = 117300517;
|
||||||
|
|
||||||
/// The protocol version of the Spotify mobile app.
|
/// The protocol version of the Spotify mobile app.
|
||||||
pub const SPOTIFY_MOBILE_VERSION: &str = "8.6.84";
|
pub const SPOTIFY_MOBILE_VERSION: &str = "8.6.84";
|
||||||
|
|
||||||
|
/// The user agent to fall back to, if one could not be determined dynamically.
|
||||||
|
pub const FALLBACK_USER_AGENT: &str = "Spotify/117300517 Linux/0 (librespot)";
|
||||||
|
|
|
@ -10,9 +10,10 @@ use std::{mem, thread};
|
||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use futures_util::stream::futures_unordered::FuturesUnordered;
|
use futures_util::stream::futures_unordered::FuturesUnordered;
|
||||||
use futures_util::{future, StreamExt, TryFutureExt};
|
use futures_util::{future, StreamExt, TryFutureExt};
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
use crate::audio::{AudioDecrypt, AudioFile, AudioFileError, StreamLoaderController};
|
||||||
use crate::audio::{
|
use crate::audio::{
|
||||||
READ_AHEAD_BEFORE_PLAYBACK, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
|
READ_AHEAD_BEFORE_PLAYBACK, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
|
||||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
|
||||||
|
@ -32,6 +33,14 @@ use crate::{MS_PER_PAGE, NUM_CHANNELS, PAGES_PER_MS, SAMPLES_PER_SECOND};
|
||||||
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
|
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
|
||||||
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
|
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
|
||||||
|
|
||||||
|
pub type PlayerResult = Result<(), PlayerError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum PlayerError {
|
||||||
|
#[error("audio file error: {0}")]
|
||||||
|
AudioFile(#[from] AudioFileError),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
commands: Option<mpsc::UnboundedSender<PlayerCommand>>,
|
commands: Option<mpsc::UnboundedSender<PlayerCommand>>,
|
||||||
thread_handle: Option<thread::JoinHandle<()>>,
|
thread_handle: Option<thread::JoinHandle<()>>,
|
||||||
|
@ -216,6 +225,17 @@ pub struct NormalisationData {
|
||||||
album_peak: f32,
|
album_peak: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for NormalisationData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
track_gain_db: 0.0,
|
||||||
|
track_peak: 1.0,
|
||||||
|
album_gain_db: 0.0,
|
||||||
|
album_peak: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NormalisationData {
|
impl NormalisationData {
|
||||||
fn parse_from_file<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
|
fn parse_from_file<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
|
||||||
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
|
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
|
||||||
|
@ -698,19 +718,20 @@ impl PlayerTrackLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stream_data_rate(&self, format: AudioFileFormat) -> usize {
|
fn stream_data_rate(&self, format: AudioFileFormat) -> usize {
|
||||||
match format {
|
let kbps = match format {
|
||||||
AudioFileFormat::OGG_VORBIS_96 => 12 * 1024,
|
AudioFileFormat::OGG_VORBIS_96 => 12,
|
||||||
AudioFileFormat::OGG_VORBIS_160 => 20 * 1024,
|
AudioFileFormat::OGG_VORBIS_160 => 20,
|
||||||
AudioFileFormat::OGG_VORBIS_320 => 40 * 1024,
|
AudioFileFormat::OGG_VORBIS_320 => 40,
|
||||||
AudioFileFormat::MP3_256 => 32 * 1024,
|
AudioFileFormat::MP3_256 => 32,
|
||||||
AudioFileFormat::MP3_320 => 40 * 1024,
|
AudioFileFormat::MP3_320 => 40,
|
||||||
AudioFileFormat::MP3_160 => 20 * 1024,
|
AudioFileFormat::MP3_160 => 20,
|
||||||
AudioFileFormat::MP3_96 => 12 * 1024,
|
AudioFileFormat::MP3_96 => 12,
|
||||||
AudioFileFormat::MP3_160_ENC => 20 * 1024,
|
AudioFileFormat::MP3_160_ENC => 20,
|
||||||
AudioFileFormat::AAC_24 => 3 * 1024,
|
AudioFileFormat::AAC_24 => 3,
|
||||||
AudioFileFormat::AAC_48 => 6 * 1024,
|
AudioFileFormat::AAC_48 => 6,
|
||||||
AudioFileFormat::FLAC_FLAC => 112 * 1024, // assume 900 kbps on average
|
AudioFileFormat::FLAC_FLAC => 112, // assume 900 kbit/s on average
|
||||||
}
|
};
|
||||||
|
kbps * 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_track(
|
async fn load_track(
|
||||||
|
@ -805,9 +826,10 @@ impl PlayerTrackLoader {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_cached = encrypted_file.is_cached();
|
let is_cached = encrypted_file.is_cached();
|
||||||
|
|
||||||
let stream_loader_controller = encrypted_file.get_stream_loader_controller();
|
let stream_loader_controller = encrypted_file.get_stream_loader_controller().ok()?;
|
||||||
|
|
||||||
if play_from_beginning {
|
if play_from_beginning {
|
||||||
// No need to seek -> we stream from the beginning
|
// No need to seek -> we stream from the beginning
|
||||||
|
@ -830,13 +852,8 @@ impl PlayerTrackLoader {
|
||||||
let normalisation_data = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
let normalisation_data = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!("Unable to extract normalisation data, using default value.");
|
warn!("Unable to extract normalisation data, using default values.");
|
||||||
NormalisationData {
|
NormalisationData::default()
|
||||||
track_gain_db: 0.0,
|
|
||||||
track_peak: 1.0,
|
|
||||||
album_gain_db: 0.0,
|
|
||||||
album_peak: 1.0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -929,7 +946,9 @@ impl Future for PlayerInternal {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(cmd) = cmd {
|
if let Some(cmd) = cmd {
|
||||||
self.handle_command(cmd);
|
if let Err(e) = self.handle_command(cmd) {
|
||||||
|
error!("Error handling command: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle loading of a new track to play
|
// Handle loading of a new track to play
|
||||||
|
@ -1109,7 +1128,9 @@ impl Future for PlayerInternal {
|
||||||
if (!*suggested_to_preload_next_track)
|
if (!*suggested_to_preload_next_track)
|
||||||
&& ((duration_ms as i64 - Self::position_pcm_to_ms(stream_position_pcm) as i64)
|
&& ((duration_ms as i64 - Self::position_pcm_to_ms(stream_position_pcm) as i64)
|
||||||
< PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS as i64)
|
< PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS as i64)
|
||||||
&& stream_loader_controller.range_to_end_available()
|
&& stream_loader_controller
|
||||||
|
.range_to_end_available()
|
||||||
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
*suggested_to_preload_next_track = true;
|
*suggested_to_preload_next_track = true;
|
||||||
self.send_event(PlayerEvent::TimeToPreloadNextTrack {
|
self.send_event(PlayerEvent::TimeToPreloadNextTrack {
|
||||||
|
@ -1785,7 +1806,7 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_command_seek(&mut self, position_ms: u32) {
|
fn handle_command_seek(&mut self, position_ms: u32) -> PlayerResult {
|
||||||
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
||||||
stream_loader_controller.set_random_access_mode();
|
stream_loader_controller.set_random_access_mode();
|
||||||
}
|
}
|
||||||
|
@ -1818,7 +1839,7 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure we have a bit of a buffer of downloaded data
|
// ensure we have a bit of a buffer of downloaded data
|
||||||
self.preload_data_before_playback();
|
self.preload_data_before_playback()?;
|
||||||
|
|
||||||
if let PlayerState::Playing {
|
if let PlayerState::Playing {
|
||||||
track_id,
|
track_id,
|
||||||
|
@ -1851,11 +1872,13 @@ impl PlayerInternal {
|
||||||
duration_ms,
|
duration_ms,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_command(&mut self, cmd: PlayerCommand) {
|
fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult {
|
||||||
debug!("command={:?}", cmd);
|
debug!("command={:?}", cmd);
|
||||||
match cmd {
|
let result = match cmd {
|
||||||
PlayerCommand::Load {
|
PlayerCommand::Load {
|
||||||
track_id,
|
track_id,
|
||||||
play_request_id,
|
play_request_id,
|
||||||
|
@ -1865,7 +1888,7 @@ impl PlayerInternal {
|
||||||
|
|
||||||
PlayerCommand::Preload { track_id } => self.handle_command_preload(track_id),
|
PlayerCommand::Preload { track_id } => self.handle_command_preload(track_id),
|
||||||
|
|
||||||
PlayerCommand::Seek(position_ms) => self.handle_command_seek(position_ms),
|
PlayerCommand::Seek(position_ms) => self.handle_command_seek(position_ms)?,
|
||||||
|
|
||||||
PlayerCommand::Play => self.handle_play(),
|
PlayerCommand::Play => self.handle_play(),
|
||||||
|
|
||||||
|
@ -1884,7 +1907,9 @@ impl PlayerInternal {
|
||||||
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => {
|
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => {
|
||||||
self.auto_normalise_as_album = setting
|
self.auto_normalise_as_album = setting
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_event(&mut self, event: PlayerEvent) {
|
fn send_event(&mut self, event: PlayerEvent) {
|
||||||
|
@ -1928,7 +1953,7 @@ impl PlayerInternal {
|
||||||
result_rx.map_err(|_| ())
|
result_rx.map_err(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preload_data_before_playback(&mut self) {
|
fn preload_data_before_playback(&mut self) -> Result<(), PlayerError> {
|
||||||
if let PlayerState::Playing {
|
if let PlayerState::Playing {
|
||||||
bytes_per_second,
|
bytes_per_second,
|
||||||
ref mut stream_loader_controller,
|
ref mut stream_loader_controller,
|
||||||
|
@ -1951,7 +1976,11 @@ impl PlayerInternal {
|
||||||
* bytes_per_second as f32) as usize,
|
* bytes_per_second as f32) as usize,
|
||||||
(READ_AHEAD_BEFORE_PLAYBACK.as_secs_f32() * bytes_per_second as f32) as usize,
|
(READ_AHEAD_BEFORE_PLAYBACK.as_secs_f32() * bytes_per_second as f32) as usize,
|
||||||
);
|
);
|
||||||
stream_loader_controller.fetch_next_blocking(wait_for_data_length);
|
stream_loader_controller
|
||||||
|
.fetch_next_blocking(wait_for_data_length)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue