2021-12-26 20:18:42 +00:00
|
|
|
use std::{
|
|
|
|
cmp::max,
|
2021-12-27 23:04:09 +00:00
|
|
|
collections::HashMap,
|
2021-12-28 22:46:37 +00:00
|
|
|
fmt,
|
2021-12-26 20:18:42 +00:00
|
|
|
future::Future,
|
|
|
|
io::{self, Read, Seek, SeekFrom},
|
|
|
|
mem,
|
|
|
|
pin::Pin,
|
|
|
|
process::exit,
|
2021-12-27 23:04:09 +00:00
|
|
|
sync::Arc,
|
2021-12-26 20:18:42 +00:00
|
|
|
task::{Context, Poll},
|
|
|
|
thread,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2021-02-22 08:55:40 +00:00
|
|
|
|
|
|
|
use byteorder::{LittleEndian, ReadBytesExt};
|
2021-12-26 20:18:42 +00:00
|
|
|
use futures_util::{future, stream::futures_unordered::FuturesUnordered, StreamExt, TryFutureExt};
|
2021-12-27 23:04:09 +00:00
|
|
|
use parking_lot::Mutex;
|
2021-02-22 08:55:40 +00:00
|
|
|
use tokio::sync::{mpsc, oneshot};
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
use crate::{
|
|
|
|
audio::{
|
|
|
|
AudioDecrypt, AudioFile, StreamLoaderController, READ_AHEAD_BEFORE_PLAYBACK,
|
|
|
|
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
|
|
|
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
|
|
|
|
},
|
|
|
|
audio_backend::Sink,
|
|
|
|
config::{Bitrate, NormalisationMethod, NormalisationType, PlayerConfig},
|
|
|
|
convert::Converter,
|
|
|
|
core::{util::SeqGenerator, Error, Session, SpotifyId},
|
|
|
|
decoder::{AudioDecoder, AudioPacket, DecoderError, PassthroughDecoder, VorbisDecoder},
|
|
|
|
metadata::audio::{AudioFileFormat, AudioItem},
|
|
|
|
mixer::AudioFilter,
|
2019-11-11 07:22:41 +00:00
|
|
|
};
|
2016-03-13 15:15:15 +00:00
|
|
|
|
2021-09-20 17:29:12 +00:00
|
|
|
use crate::{MS_PER_PAGE, NUM_CHANNELS, PAGES_PER_MS, SAMPLES_PER_SECOND};
|
2021-03-12 22:05:38 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
|
2021-05-30 18:09:39 +00:00
|
|
|
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
pub type PlayerResult = Result<(), Error>;
|
2021-12-18 22:44:13 +00:00
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
pub struct Player {
|
2021-01-21 21:22:32 +00:00
|
|
|
commands: Option<mpsc::UnboundedSender<PlayerCommand>>,
|
2021-02-20 21:14:15 +00:00
|
|
|
thread_handle: Option<thread::JoinHandle<()>>,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id_generator: SeqGenerator<u64>,
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2020-03-10 12:26:01 +00:00
|
|
|
#[derive(PartialEq, Debug, Clone, Copy)]
|
|
|
|
pub enum SinkStatus {
|
|
|
|
Running,
|
|
|
|
Closed,
|
|
|
|
TemporarilyClosed,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type SinkEventCallback = Box<dyn Fn(SinkStatus) + Send>;
|
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
struct PlayerInternal {
|
|
|
|
session: Session,
|
2017-08-03 18:31:15 +00:00
|
|
|
config: PlayerConfig,
|
2021-01-21 21:22:32 +00:00
|
|
|
commands: mpsc::UnboundedReceiver<PlayerCommand>,
|
2021-12-27 23:04:09 +00:00
|
|
|
load_handles: Arc<Mutex<HashMap<thread::ThreadId, thread::JoinHandle<()>>>>,
|
2017-01-29 14:11:20 +00:00
|
|
|
|
|
|
|
state: PlayerState,
|
2020-01-31 21:41:11 +00:00
|
|
|
preload: PlayerPreload,
|
2021-04-01 16:41:01 +00:00
|
|
|
sink: Box<dyn Sink>,
|
2020-03-10 12:26:01 +00:00
|
|
|
sink_status: SinkStatus,
|
|
|
|
sink_event_callback: Option<SinkEventCallback>,
|
2019-10-08 09:31:18 +00:00
|
|
|
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
2021-01-21 21:22:32 +00:00
|
|
|
event_senders: Vec<mpsc::UnboundedSender<PlayerEvent>>,
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
converter: Converter,
|
2021-02-24 20:39:42 +00:00
|
|
|
|
|
|
|
limiter_active: bool,
|
|
|
|
limiter_attack_counter: u32,
|
|
|
|
limiter_release_counter: u32,
|
2021-05-30 18:09:39 +00:00
|
|
|
limiter_peak_sample: f64,
|
|
|
|
limiter_factor: f64,
|
|
|
|
limiter_strength: f64,
|
2021-09-20 17:22:02 +00:00
|
|
|
|
|
|
|
auto_normalise_as_album: bool,
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
2015-07-09 20:08:14 +00:00
|
|
|
enum PlayerCommand {
|
2020-01-31 21:41:11 +00:00
|
|
|
Load {
|
|
|
|
track_id: SpotifyId,
|
|
|
|
play_request_id: u64,
|
|
|
|
play: bool,
|
|
|
|
position_ms: u32,
|
|
|
|
},
|
|
|
|
Preload {
|
|
|
|
track_id: SpotifyId,
|
|
|
|
},
|
2015-07-09 20:08:14 +00:00
|
|
|
Play,
|
|
|
|
Pause,
|
|
|
|
Stop,
|
2016-01-02 15:19:39 +00:00
|
|
|
Seek(u32),
|
2021-01-21 21:22:32 +00:00
|
|
|
AddEventSender(mpsc::UnboundedSender<PlayerEvent>),
|
2020-03-10 12:26:01 +00:00
|
|
|
SetSinkEventCallback(Option<SinkEventCallback>),
|
2020-01-31 21:41:11 +00:00
|
|
|
EmitVolumeSetEvent(u16),
|
2021-09-20 17:22:02 +00:00
|
|
|
SetAutoNormaliseAsAlbum(bool),
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
2018-02-20 20:57:42 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub enum PlayerEvent {
|
2020-03-12 12:01:45 +00:00
|
|
|
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
|
2020-02-03 07:58:44 +00:00
|
|
|
Stopped {
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player started working on playback of a track while it was in a stopped state.
|
|
|
|
// This is always immediately followed up by a "Loading" or "Playing" event.
|
2020-02-03 07:58:44 +00:00
|
|
|
Started {
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
|
|
|
position_ms: u32,
|
2018-02-20 20:57:42 +00:00
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// Same as started but in the case that the player already had a track loaded.
|
|
|
|
// The player was either playing the loaded track or it was paused.
|
2018-02-20 20:57:42 +00:00
|
|
|
Changed {
|
|
|
|
old_track_id: SpotifyId,
|
|
|
|
new_track_id: SpotifyId,
|
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player is delayed by loading a track.
|
|
|
|
Loading {
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
2020-02-03 07:58:44 +00:00
|
|
|
position_ms: u32,
|
2020-01-31 21:41:11 +00:00
|
|
|
},
|
2020-12-10 21:17:41 +00:00
|
|
|
// The player is preloading a track.
|
|
|
|
Preloading {
|
|
|
|
track_id: SpotifyId,
|
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player is playing a track.
|
|
|
|
// This event is issued at the start of playback of whenever the position must be communicated
|
|
|
|
// because it is out of sync. This includes:
|
|
|
|
// start of a track
|
|
|
|
// un-pausing
|
|
|
|
// after a seek
|
|
|
|
// after a buffer-underrun
|
2020-02-03 07:58:44 +00:00
|
|
|
Playing {
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
2020-02-03 07:58:44 +00:00
|
|
|
position_ms: u32,
|
|
|
|
duration_ms: u32,
|
2020-01-31 21:41:11 +00:00
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player entered a paused state.
|
2020-01-31 21:41:11 +00:00
|
|
|
Paused {
|
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
|
|
|
position_ms: u32,
|
|
|
|
duration_ms: u32,
|
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player thinks it's a good idea to issue a preload command for the next track now.
|
|
|
|
// This event is intended for use within spirc.
|
2020-02-03 07:58:44 +00:00
|
|
|
TimeToPreloadNextTrack {
|
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
2020-05-13 09:49:26 +00:00
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The player reached the end of a track.
|
|
|
|
// This event is intended for use within spirc. Spirc will respond by issuing another command
|
|
|
|
// which will trigger another event (e.g. Changed or Stopped)
|
2020-02-03 07:58:44 +00:00
|
|
|
EndOfTrack {
|
2020-05-13 09:49:26 +00:00
|
|
|
play_request_id: u64,
|
|
|
|
track_id: SpotifyId,
|
2020-02-03 07:58:44 +00:00
|
|
|
},
|
2020-05-13 10:19:33 +00:00
|
|
|
// The player was unable to load the requested track.
|
|
|
|
Unavailable {
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
2018-02-20 20:57:42 +00:00
|
|
|
track_id: SpotifyId,
|
2018-02-26 01:50:41 +00:00
|
|
|
},
|
2020-03-12 12:01:45 +00:00
|
|
|
// The mixer volume was set to a new level.
|
2020-01-31 21:41:11 +00:00
|
|
|
VolumeSet {
|
|
|
|
volume: u16,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PlayerEvent {
|
|
|
|
pub fn get_play_request_id(&self) -> Option<u64> {
|
|
|
|
use PlayerEvent::*;
|
|
|
|
match self {
|
|
|
|
Loading {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
2020-05-13 09:49:26 +00:00
|
|
|
| Unavailable {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
| Started {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| Playing {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| TimeToPreloadNextTrack {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| EndOfTrack {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| Paused {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| Stopped {
|
|
|
|
play_request_id, ..
|
|
|
|
} => Some(*play_request_id),
|
2020-12-10 21:17:41 +00:00
|
|
|
Changed { .. } | Preloading { .. } | VolumeSet { .. } => None,
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-20 20:57:42 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 21:22:32 +00:00
|
|
|
pub type PlayerEventChannel = mpsc::UnboundedReceiver<PlayerEvent>;
|
2018-02-24 19:16:28 +00:00
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
pub fn db_to_ratio(db: f64) -> f64 {
|
|
|
|
f64::powf(10.0, db / DB_VOLTAGE_RATIO)
|
2021-05-24 13:53:32 +00:00
|
|
|
}
|
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
pub fn ratio_to_db(ratio: f64) -> f64 {
|
2021-05-24 13:53:32 +00:00
|
|
|
ratio.log10() * DB_VOLTAGE_RATIO
|
|
|
|
}
|
|
|
|
|
2018-02-23 19:08:20 +00:00
|
|
|
#[derive(Clone, Copy, Debug)]
|
2021-02-24 20:39:42 +00:00
|
|
|
pub struct NormalisationData {
|
2018-02-23 19:08:20 +00:00
|
|
|
track_gain_db: f32,
|
|
|
|
track_peak: f32,
|
|
|
|
album_gain_db: f32,
|
|
|
|
album_peak: f32,
|
|
|
|
}
|
|
|
|
|
2021-12-18 22:44:13 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 19:08:20 +00:00
|
|
|
impl NormalisationData {
|
2021-01-21 21:22:32 +00:00
|
|
|
fn parse_from_file<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
|
2018-02-24 15:30:24 +00:00
|
|
|
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
|
2021-12-28 22:46:37 +00:00
|
|
|
|
|
|
|
let newpos = file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?;
|
|
|
|
if newpos != SPOTIFY_NORMALIZATION_HEADER_START_OFFSET {
|
|
|
|
error!(
|
|
|
|
"NormalisationData::parse_from_file seeking to {} but position is now {}",
|
|
|
|
SPOTIFY_NORMALIZATION_HEADER_START_OFFSET, newpos
|
|
|
|
);
|
|
|
|
error!("Falling back to default (non-track and non-album) normalisation data.");
|
|
|
|
return Ok(NormalisationData::default());
|
|
|
|
}
|
2018-02-23 19:08:20 +00:00
|
|
|
|
2021-03-10 21:32:24 +00:00
|
|
|
let track_gain_db = file.read_f32::<LittleEndian>()?;
|
|
|
|
let track_peak = file.read_f32::<LittleEndian>()?;
|
|
|
|
let album_gain_db = file.read_f32::<LittleEndian>()?;
|
|
|
|
let album_peak = file.read_f32::<LittleEndian>()?;
|
2018-02-23 19:08:20 +00:00
|
|
|
|
2018-02-24 15:30:24 +00:00
|
|
|
let r = NormalisationData {
|
2021-03-10 21:39:01 +00:00
|
|
|
track_gain_db,
|
|
|
|
track_peak,
|
|
|
|
album_gain_db,
|
|
|
|
album_peak,
|
2018-02-24 15:30:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(r)
|
2018-02-23 19:08:20 +00:00
|
|
|
}
|
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f64 {
|
2021-05-17 19:27:34 +00:00
|
|
|
if !config.normalisation {
|
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
|
2021-09-20 17:22:02 +00:00
|
|
|
let [gain_db, gain_peak] = if config.normalisation_type == NormalisationType::Album {
|
|
|
|
[data.album_gain_db, data.album_peak]
|
|
|
|
} else {
|
|
|
|
[data.track_gain_db, data.track_peak]
|
2021-01-21 19:16:05 +00:00
|
|
|
};
|
2018-02-23 19:08:20 +00:00
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
let normalisation_power = gain_db as f64 + config.normalisation_pregain;
|
2021-05-24 13:53:32 +00:00
|
|
|
let mut normalisation_factor = db_to_ratio(normalisation_power);
|
2021-02-24 20:39:42 +00:00
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
if normalisation_factor * gain_peak as f64 > config.normalisation_threshold {
|
|
|
|
let limited_normalisation_factor = config.normalisation_threshold / gain_peak as f64;
|
2021-05-24 13:53:32 +00:00
|
|
|
let limited_normalisation_power = ratio_to_db(limited_normalisation_factor);
|
2021-02-24 20:39:42 +00:00
|
|
|
|
|
|
|
if config.normalisation_method == NormalisationMethod::Basic {
|
2021-04-16 18:49:21 +00:00
|
|
|
warn!("Limiting gain to {:.2} dB for the duration of this track to stay under normalisation threshold.", limited_normalisation_power);
|
2021-02-24 20:39:42 +00:00
|
|
|
normalisation_factor = limited_normalisation_factor;
|
|
|
|
} else {
|
|
|
|
warn!(
|
|
|
|
"This track will at its peak be subject to {:.2} dB of dynamic limiting.",
|
|
|
|
normalisation_power - limited_normalisation_power
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
warn!("Please lower pregain to avoid.");
|
2018-02-23 19:08:20 +00:00
|
|
|
}
|
|
|
|
|
2018-02-24 15:30:24 +00:00
|
|
|
debug!("Normalisation Data: {:?}", data);
|
2021-09-20 17:22:02 +00:00
|
|
|
debug!(
|
|
|
|
"Calculated Normalisation Factor for {:?}: {:.2}%",
|
|
|
|
config.normalisation_type,
|
|
|
|
normalisation_factor * 100.0
|
|
|
|
);
|
2018-02-24 15:30:24 +00:00
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
normalisation_factor as f64
|
2018-02-23 19:08:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-01 11:20:37 +00:00
|
|
|
impl Player {
|
2018-02-26 01:50:41 +00:00
|
|
|
pub fn new<F>(
|
|
|
|
config: PlayerConfig,
|
|
|
|
session: Session,
|
2019-10-08 09:31:18 +00:00
|
|
|
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
2018-02-26 01:50:41 +00:00
|
|
|
sink_builder: F,
|
|
|
|
) -> (Player, PlayerEventChannel)
|
|
|
|
where
|
2021-04-01 16:41:01 +00:00
|
|
|
F: FnOnce() -> Box<dyn Sink> + Send + 'static,
|
2017-08-03 18:31:15 +00:00
|
|
|
{
|
2021-02-22 08:55:40 +00:00
|
|
|
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
|
|
|
let (event_sender, event_receiver) = mpsc::unbounded_channel();
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2021-05-26 20:03:52 +00:00
|
|
|
if config.normalisation {
|
|
|
|
debug!("Normalisation Type: {:?}", config.normalisation_type);
|
2021-05-26 21:14:24 +00:00
|
|
|
debug!(
|
|
|
|
"Normalisation Pregain: {:.1} dB",
|
|
|
|
config.normalisation_pregain
|
|
|
|
);
|
2021-05-26 20:03:52 +00:00
|
|
|
debug!(
|
|
|
|
"Normalisation Threshold: {:.1} dBFS",
|
|
|
|
ratio_to_db(config.normalisation_threshold)
|
|
|
|
);
|
|
|
|
debug!("Normalisation Method: {:?}", config.normalisation_method);
|
|
|
|
|
|
|
|
if config.normalisation_method == NormalisationMethod::Dynamic {
|
2021-05-31 20:32:39 +00:00
|
|
|
debug!("Normalisation Attack: {:?}", config.normalisation_attack);
|
|
|
|
debug!("Normalisation Release: {:?}", config.normalisation_release);
|
2021-05-26 20:03:52 +00:00
|
|
|
debug!("Normalisation Knee: {:?}", config.normalisation_knee);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
let handle = thread::spawn(move || {
|
|
|
|
debug!("new Player[{}]", session.session_id());
|
|
|
|
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
let converter = Converter::new(config.ditherer);
|
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
let internal = PlayerInternal {
|
2021-03-01 02:37:22 +00:00
|
|
|
session,
|
|
|
|
config,
|
2021-02-20 21:14:15 +00:00
|
|
|
commands: cmd_rx,
|
2021-12-27 23:04:09 +00:00
|
|
|
load_handles: Arc::new(Mutex::new(HashMap::new())),
|
2021-02-20 21:14:15 +00:00
|
|
|
|
|
|
|
state: PlayerState::Stopped,
|
|
|
|
preload: PlayerPreload::None,
|
|
|
|
sink: sink_builder(),
|
|
|
|
sink_status: SinkStatus::Closed,
|
|
|
|
sink_event_callback: None,
|
2021-03-01 02:37:22 +00:00
|
|
|
audio_filter,
|
2021-02-20 21:14:15 +00:00
|
|
|
event_senders: [event_sender].to_vec(),
|
Implement dithering (#694)
Dithering lowers digital-to-analog conversion ("requantization") error, linearizing output, lowering distortion and replacing it with a constant, fixed noise level, which is more pleasant to the ear than the distortion.
Guidance:
- On S24, S24_3 and S24, the default is to use triangular dithering. Depending on personal preference you may use Gaussian dithering instead; it's not as good objectively, but it may be preferred subjectively if you are looking for a more "analog" sound akin to tape hiss.
- Advanced users who know that they have a DAC without noise shaping have a third option: high-passed dithering, which is like triangular dithering except that it moves dithering noise up in frequency where it is less audible. Note: 99% of DACs are of delta-sigma design with noise shaping, so unless you have a multibit / R2R DAC, or otherwise know what you are doing, this is not for you.
- Don't dither or shape noise on S32 or F32. On F32 it's not supported anyway (there are no integer conversions and so no rounding errors) and on S32 the noise level is so far down that it is simply inaudible even after volume normalisation and control.
New command line option:
--dither DITHER Specify the dither algorithm to use - [none, gpdf,
tpdf, tpdf_hp]. Defaults to 'tpdf' for formats S16
S24, S24_3 and 'none' for other formats.
Notes:
This PR also features some opportunistic improvements. Worthy of mention are:
- matching reference Vorbis sample conversion techniques for lower noise
- a cleanup of the convert API
2021-05-26 19:19:17 +00:00
|
|
|
converter,
|
2021-02-24 20:39:42 +00:00
|
|
|
|
|
|
|
limiter_active: false,
|
|
|
|
limiter_attack_counter: 0,
|
|
|
|
limiter_release_counter: 0,
|
|
|
|
limiter_peak_sample: 0.0,
|
|
|
|
limiter_factor: 1.0,
|
|
|
|
limiter_strength: 0.0,
|
2021-09-20 17:22:02 +00:00
|
|
|
|
|
|
|
auto_normalise_as_album: false,
|
2021-02-20 21:14:15 +00:00
|
|
|
};
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
// While PlayerInternal is written as a future, it still contains blocking code.
|
2021-02-22 08:55:40 +00:00
|
|
|
// It must be run by using block_on() in a dedicated thread.
|
2021-11-26 22:21:27 +00:00
|
|
|
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
|
|
|
|
runtime.block_on(internal);
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
debug!("PlayerInternal thread finished.");
|
2017-01-29 14:11:20 +00:00
|
|
|
});
|
2015-09-01 11:20:37 +00:00
|
|
|
|
2018-02-26 01:50:41 +00:00
|
|
|
(
|
|
|
|
Player {
|
|
|
|
commands: Some(cmd_tx),
|
2021-02-20 21:14:15 +00:00
|
|
|
thread_handle: Some(handle),
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id_generator: SeqGenerator::new(0),
|
2018-02-26 01:50:41 +00:00
|
|
|
},
|
|
|
|
event_receiver,
|
|
|
|
)
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
|
|
|
|
2015-07-09 20:08:14 +00:00
|
|
|
fn command(&self, cmd: PlayerCommand) {
|
2021-09-20 17:29:12 +00:00
|
|
|
if let Some(commands) = self.commands.as_ref() {
|
|
|
|
if let Err(e) = commands.send(cmd) {
|
|
|
|
error!("Player Commands Error: {}", e);
|
|
|
|
}
|
|
|
|
}
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2016-01-20 14:11:49 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
pub fn load(&mut self, track_id: SpotifyId, start_playing: bool, position_ms: u32) -> u64 {
|
|
|
|
let play_request_id = self.play_request_id_generator.get();
|
|
|
|
self.command(PlayerCommand::Load {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
play: start_playing,
|
|
|
|
position_ms,
|
|
|
|
});
|
|
|
|
|
|
|
|
play_request_id
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2020-02-02 00:07:05 +00:00
|
|
|
pub fn preload(&self, track_id: SpotifyId) {
|
|
|
|
self.command(PlayerCommand::Preload { track_id });
|
2016-01-20 14:11:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn play(&self) {
|
|
|
|
self.command(PlayerCommand::Play)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn pause(&self) {
|
|
|
|
self.command(PlayerCommand::Pause)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn stop(&self) {
|
|
|
|
self.command(PlayerCommand::Stop)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn seek(&self, position_ms: u32) {
|
|
|
|
self.command(PlayerCommand::Seek(position_ms));
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
|
|
|
|
pub fn get_player_event_channel(&self) -> PlayerEventChannel {
|
2021-02-22 08:55:40 +00:00
|
|
|
let (event_sender, event_receiver) = mpsc::unbounded_channel();
|
2020-01-31 21:41:11 +00:00
|
|
|
self.command(PlayerCommand::AddEventSender(event_sender));
|
|
|
|
event_receiver
|
|
|
|
}
|
|
|
|
|
2021-02-22 08:55:40 +00:00
|
|
|
pub async fn await_end_of_track(&self) {
|
2021-02-12 17:19:04 +00:00
|
|
|
let mut channel = self.get_player_event_channel();
|
2021-02-22 08:55:40 +00:00
|
|
|
while let Some(event) = channel.recv().await {
|
2021-02-13 09:29:00 +00:00
|
|
|
if matches!(
|
|
|
|
event,
|
|
|
|
PlayerEvent::EndOfTrack { .. } | PlayerEvent::Stopped { .. }
|
|
|
|
) {
|
2021-02-12 17:19:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-02-02 00:07:05 +00:00
|
|
|
}
|
|
|
|
|
2020-03-10 12:26:01 +00:00
|
|
|
pub fn set_sink_event_callback(&self, callback: Option<SinkEventCallback>) {
|
|
|
|
self.command(PlayerCommand::SetSinkEventCallback(callback));
|
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
pub fn emit_volume_set_event(&self, volume: u16) {
|
|
|
|
self.command(PlayerCommand::EmitVolumeSetEvent(volume));
|
|
|
|
}
|
2021-09-20 17:22:02 +00:00
|
|
|
|
|
|
|
pub fn set_auto_normalise_as_album(&self, setting: bool) {
|
|
|
|
self.command(PlayerCommand::SetAutoNormaliseAsAlbum(setting));
|
|
|
|
}
|
2015-07-02 19:42:49 +00:00
|
|
|
}
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2017-11-27 23:35:04 +00:00
|
|
|
impl Drop for Player {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
debug!("Shutting down player thread ...");
|
|
|
|
self.commands = None;
|
2021-02-20 21:14:15 +00:00
|
|
|
if let Some(handle) = self.thread_handle.take() {
|
|
|
|
match handle.join() {
|
|
|
|
Ok(_) => (),
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => error!("Player thread Error: {:?}", e),
|
2021-02-20 21:14:15 +00:00
|
|
|
}
|
2017-11-27 23:35:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
struct PlayerLoadedTrackData {
|
|
|
|
decoder: Decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data: NormalisationData,
|
2020-01-31 21:41:11 +00:00
|
|
|
stream_loader_controller: StreamLoaderController,
|
|
|
|
bytes_per_second: usize,
|
|
|
|
duration_ms: u32,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm: u64,
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum PlayerPreload {
|
|
|
|
None,
|
|
|
|
Loading {
|
|
|
|
track_id: SpotifyId,
|
2021-01-22 21:51:41 +00:00
|
|
|
loader: Pin<Box<dyn Future<Output = Result<PlayerLoadedTrackData, ()>> + Send>>,
|
2020-01-31 21:41:11 +00:00
|
|
|
},
|
|
|
|
Ready {
|
|
|
|
track_id: SpotifyId,
|
2021-01-22 21:51:41 +00:00
|
|
|
loaded_track: Box<PlayerLoadedTrackData>,
|
2020-01-31 21:41:11 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-01-07 06:42:38 +00:00
|
|
|
type Decoder = Box<dyn AudioDecoder + Send>;
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
enum PlayerState {
|
|
|
|
Stopped,
|
2020-01-31 21:41:11 +00:00
|
|
|
Loading {
|
|
|
|
track_id: SpotifyId,
|
|
|
|
play_request_id: u64,
|
|
|
|
start_playback: bool,
|
2021-01-22 21:51:41 +00:00
|
|
|
loader: Pin<Box<dyn Future<Output = Result<PlayerLoadedTrackData, ()>> + Send>>,
|
2020-01-31 21:41:11 +00:00
|
|
|
},
|
2017-01-29 14:11:20 +00:00
|
|
|
Paused {
|
2018-02-15 23:16:38 +00:00
|
|
|
track_id: SpotifyId,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
2017-01-29 14:11:20 +00:00
|
|
|
decoder: Decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data: NormalisationData,
|
2021-05-30 18:09:39 +00:00
|
|
|
normalisation_factor: f64,
|
2019-11-01 19:46:28 +00:00
|
|
|
stream_loader_controller: StreamLoaderController,
|
2019-11-07 13:02:53 +00:00
|
|
|
bytes_per_second: usize,
|
2020-01-31 21:41:11 +00:00
|
|
|
duration_ms: u32,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm: u64,
|
2020-01-31 21:41:11 +00:00
|
|
|
suggested_to_preload_next_track: bool,
|
2017-01-29 14:11:20 +00:00
|
|
|
},
|
|
|
|
Playing {
|
2018-02-15 23:16:38 +00:00
|
|
|
track_id: SpotifyId,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
2017-01-29 14:11:20 +00:00
|
|
|
decoder: Decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data: NormalisationData,
|
2021-05-30 18:09:39 +00:00
|
|
|
normalisation_factor: f64,
|
2019-11-01 19:46:28 +00:00
|
|
|
stream_loader_controller: StreamLoaderController,
|
2019-11-07 13:02:53 +00:00
|
|
|
bytes_per_second: usize,
|
2020-01-31 21:41:11 +00:00
|
|
|
duration_ms: u32,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm: u64,
|
2020-01-31 21:41:11 +00:00
|
|
|
reported_nominal_start_time: Option<Instant>,
|
|
|
|
suggested_to_preload_next_track: bool,
|
2017-01-29 14:11:20 +00:00
|
|
|
},
|
2018-02-26 01:50:41 +00:00
|
|
|
EndOfTrack {
|
|
|
|
track_id: SpotifyId,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: u64,
|
2020-03-12 12:01:45 +00:00
|
|
|
loaded_track: PlayerLoadedTrackData,
|
2018-02-26 01:50:41 +00:00
|
|
|
},
|
2017-01-29 14:11:20 +00:00
|
|
|
Invalid,
|
2016-02-05 20:54:47 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
impl PlayerState {
|
|
|
|
fn is_playing(&self) -> bool {
|
|
|
|
use self::PlayerState::*;
|
|
|
|
match *self {
|
2020-01-31 21:41:11 +00:00
|
|
|
Stopped | EndOfTrack { .. } | Paused { .. } | Loading { .. } => false,
|
2017-01-29 14:11:20 +00:00
|
|
|
Playing { .. } => true,
|
2021-09-20 17:29:12 +00:00
|
|
|
Invalid => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("PlayerState::is_playing in invalid state");
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
}
|
2017-01-05 13:25:14 +00:00
|
|
|
|
2020-05-09 11:59:28 +00:00
|
|
|
#[allow(dead_code)]
|
2020-01-31 21:41:11 +00:00
|
|
|
fn is_stopped(&self) -> bool {
|
2021-02-20 21:14:15 +00:00
|
|
|
use self::PlayerState::*;
|
2021-03-01 02:37:22 +00:00
|
|
|
matches!(self, Stopped)
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-09 11:59:28 +00:00
|
|
|
fn is_loading(&self) -> bool {
|
2021-02-20 21:14:15 +00:00
|
|
|
use self::PlayerState::*;
|
2021-03-01 02:37:22 +00:00
|
|
|
matches!(self, Loading { .. })
|
2020-05-09 11:59:28 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn decoder(&mut self) -> Option<&mut Decoder> {
|
2021-02-20 21:14:15 +00:00
|
|
|
use self::PlayerState::*;
|
2017-01-29 14:11:20 +00:00
|
|
|
match *self {
|
2020-01-31 21:41:11 +00:00
|
|
|
Stopped | EndOfTrack { .. } | Loading { .. } => None,
|
2019-10-08 09:31:18 +00:00
|
|
|
Paused {
|
|
|
|
ref mut decoder, ..
|
|
|
|
}
|
|
|
|
| Playing {
|
|
|
|
ref mut decoder, ..
|
|
|
|
} => Some(decoder),
|
2021-09-20 17:29:12 +00:00
|
|
|
Invalid => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("PlayerState::decoder in invalid state");
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
|
2019-11-01 19:46:28 +00:00
|
|
|
fn stream_loader_controller(&mut self) -> Option<&mut StreamLoaderController> {
|
|
|
|
use self::PlayerState::*;
|
|
|
|
match *self {
|
2020-01-31 21:41:11 +00:00
|
|
|
Stopped | EndOfTrack { .. } | Loading { .. } => None,
|
2019-11-11 07:22:41 +00:00
|
|
|
Paused {
|
|
|
|
ref mut stream_loader_controller,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Playing {
|
|
|
|
ref mut stream_loader_controller,
|
|
|
|
..
|
|
|
|
} => Some(stream_loader_controller),
|
2021-09-20 17:29:12 +00:00
|
|
|
Invalid => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("PlayerState::stream_loader_controller in invalid state");
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2019-11-01 19:46:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-20 20:58:02 +00:00
|
|
|
fn playing_to_end_of_track(&mut self) {
|
|
|
|
use self::PlayerState::*;
|
2021-12-28 22:46:37 +00:00
|
|
|
let new_state = mem::replace(self, Invalid);
|
|
|
|
match new_state {
|
2018-02-26 01:50:41 +00:00
|
|
|
Playing {
|
|
|
|
track_id,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id,
|
2020-02-02 14:37:17 +00:00
|
|
|
decoder,
|
|
|
|
duration_ms,
|
|
|
|
bytes_per_second,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-02-02 14:37:17 +00:00
|
|
|
stream_loader_controller,
|
|
|
|
stream_position_pcm,
|
2018-02-26 01:50:41 +00:00
|
|
|
..
|
|
|
|
} => {
|
2020-01-31 21:41:11 +00:00
|
|
|
*self = EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2020-03-12 12:01:45 +00:00
|
|
|
loaded_track: PlayerLoadedTrackData {
|
2020-02-02 14:37:17 +00:00
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-02-02 14:37:17 +00:00
|
|
|
stream_loader_controller,
|
2021-05-09 10:59:34 +00:00
|
|
|
bytes_per_second,
|
|
|
|
duration_ms,
|
2020-02-02 14:37:17 +00:00
|
|
|
stream_position_pcm,
|
2020-03-12 12:01:45 +00:00
|
|
|
},
|
2020-01-31 21:41:11 +00:00
|
|
|
};
|
2018-02-26 01:50:41 +00:00
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
_ => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!(
|
|
|
|
"Called playing_to_end_of_track in non-playing state: {:?}",
|
|
|
|
new_state
|
|
|
|
);
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn paused_to_playing(&mut self) {
|
|
|
|
use self::PlayerState::*;
|
2021-12-28 22:46:37 +00:00
|
|
|
let new_state = mem::replace(self, Invalid);
|
|
|
|
match new_state {
|
2018-02-26 01:50:41 +00:00
|
|
|
Paused {
|
|
|
|
track_id,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id,
|
2018-02-26 01:50:41 +00:00
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2018-02-26 01:50:41 +00:00
|
|
|
normalisation_factor,
|
2019-11-01 19:46:28 +00:00
|
|
|
stream_loader_controller,
|
2020-01-31 21:41:11 +00:00
|
|
|
duration_ms,
|
2019-11-11 07:22:41 +00:00
|
|
|
bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
suggested_to_preload_next_track,
|
2018-02-26 01:50:41 +00:00
|
|
|
} => {
|
2017-01-29 14:11:20 +00:00
|
|
|
*self = Playing {
|
2020-01-31 21:41:11 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-01-31 21:41:11 +00:00
|
|
|
normalisation_factor,
|
|
|
|
stream_loader_controller,
|
|
|
|
duration_ms,
|
|
|
|
bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
reported_nominal_start_time: None,
|
|
|
|
suggested_to_preload_next_track,
|
2017-01-29 14:11:20 +00:00
|
|
|
};
|
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
_ => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!(
|
|
|
|
"PlayerState::paused_to_playing in invalid state: {:?}",
|
|
|
|
new_state
|
|
|
|
);
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-08 18:49:17 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn playing_to_paused(&mut self) {
|
|
|
|
use self::PlayerState::*;
|
2021-12-28 22:46:37 +00:00
|
|
|
let new_state = mem::replace(self, Invalid);
|
|
|
|
match new_state {
|
2018-02-26 01:50:41 +00:00
|
|
|
Playing {
|
|
|
|
track_id,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id,
|
2018-02-26 01:50:41 +00:00
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2018-02-26 01:50:41 +00:00
|
|
|
normalisation_factor,
|
2019-11-01 19:46:28 +00:00
|
|
|
stream_loader_controller,
|
2020-01-31 21:41:11 +00:00
|
|
|
duration_ms,
|
2019-11-07 13:02:53 +00:00
|
|
|
bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
reported_nominal_start_time: _,
|
|
|
|
suggested_to_preload_next_track,
|
2018-02-26 01:50:41 +00:00
|
|
|
} => {
|
2017-01-29 14:11:20 +00:00
|
|
|
*self = Paused {
|
2020-01-31 21:41:11 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-01-31 21:41:11 +00:00
|
|
|
normalisation_factor,
|
|
|
|
stream_loader_controller,
|
|
|
|
duration_ms,
|
|
|
|
bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
suggested_to_preload_next_track,
|
2017-01-29 14:11:20 +00:00
|
|
|
};
|
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
_ => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!(
|
|
|
|
"PlayerState::playing_to_paused in invalid state: {:?}",
|
|
|
|
new_state
|
|
|
|
);
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
2016-09-08 18:49:17 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
struct PlayerTrackLoader {
|
|
|
|
session: Session,
|
|
|
|
config: PlayerConfig,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PlayerTrackLoader {
|
2021-02-22 08:58:08 +00:00
|
|
|
async fn find_available_alternative(&self, audio: AudioItem) -> Option<AudioItem> {
|
2021-12-27 08:35:11 +00:00
|
|
|
if let Err(e) = audio.availability {
|
|
|
|
error!("Track is unavailable: {}", e);
|
|
|
|
None
|
|
|
|
} else if !audio.files.is_empty() {
|
2021-02-22 08:58:08 +00:00
|
|
|
Some(audio)
|
2021-01-21 21:22:32 +00:00
|
|
|
} else if let Some(alternatives) = &audio.alternatives {
|
2021-02-22 08:58:08 +00:00
|
|
|
let alternatives: FuturesUnordered<_> = alternatives
|
2021-01-21 21:22:32 +00:00
|
|
|
.iter()
|
2021-12-07 22:22:24 +00:00
|
|
|
.map(|alt_id| AudioItem::get_file(&self.session, *alt_id))
|
2021-02-22 08:58:08 +00:00
|
|
|
.collect();
|
|
|
|
|
2021-01-21 21:22:32 +00:00
|
|
|
alternatives
|
2021-02-22 08:58:08 +00:00
|
|
|
.filter_map(|x| future::ready(x.ok()))
|
2021-12-07 22:22:24 +00:00
|
|
|
.filter(|x| future::ready(x.availability.is_ok()))
|
2021-02-22 08:58:08 +00:00
|
|
|
.next()
|
|
|
|
.await
|
2020-01-31 21:41:11 +00:00
|
|
|
} else {
|
2021-12-27 08:35:11 +00:00
|
|
|
error!("Track should be available, but no alternatives found.");
|
2021-01-21 21:22:32 +00:00
|
|
|
None
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-07 22:22:24 +00:00
|
|
|
fn stream_data_rate(&self, format: AudioFileFormat) -> usize {
|
2021-12-18 22:44:13 +00:00
|
|
|
let kbps = match format {
|
|
|
|
AudioFileFormat::OGG_VORBIS_96 => 12,
|
|
|
|
AudioFileFormat::OGG_VORBIS_160 => 20,
|
|
|
|
AudioFileFormat::OGG_VORBIS_320 => 40,
|
|
|
|
AudioFileFormat::MP3_256 => 32,
|
|
|
|
AudioFileFormat::MP3_320 => 40,
|
|
|
|
AudioFileFormat::MP3_160 => 20,
|
|
|
|
AudioFileFormat::MP3_96 => 12,
|
|
|
|
AudioFileFormat::MP3_160_ENC => 20,
|
|
|
|
AudioFileFormat::AAC_24 => 3,
|
|
|
|
AudioFileFormat::AAC_48 => 6,
|
|
|
|
AudioFileFormat::FLAC_FLAC => 112, // assume 900 kbit/s on average
|
|
|
|
};
|
|
|
|
kbps * 1024
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 21:22:32 +00:00
|
|
|
async fn load_track(
|
|
|
|
&self,
|
|
|
|
spotify_id: SpotifyId,
|
|
|
|
position_ms: u32,
|
|
|
|
) -> Option<PlayerLoadedTrackData> {
|
2021-12-07 22:22:24 +00:00
|
|
|
let audio = match AudioItem::get_file(&self.session, spotify_id).await {
|
2020-01-31 21:41:11 +00:00
|
|
|
Ok(audio) => audio,
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to load audio item: {:?}", e);
|
2020-01-31 21:41:11 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-07 22:22:24 +00:00
|
|
|
info!(
|
|
|
|
"Loading <{}> with Spotify URI <{}>",
|
|
|
|
audio.name, audio.spotify_uri
|
|
|
|
);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-22 08:58:08 +00:00
|
|
|
let audio = match self.find_available_alternative(audio).await {
|
2020-01-31 21:41:11 +00:00
|
|
|
Some(audio) => audio,
|
|
|
|
None => {
|
2021-12-26 20:18:42 +00:00
|
|
|
error!("<{}> is not available", spotify_id.to_uri());
|
2020-01-31 21:41:11 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-12-11 20:32:34 +00:00
|
|
|
if audio.duration < 0 {
|
|
|
|
error!(
|
|
|
|
"Track duration for <{}> cannot be {}",
|
|
|
|
spotify_id.to_uri(),
|
|
|
|
audio.duration
|
|
|
|
);
|
|
|
|
return None;
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
let duration_ms = audio.duration as u32;
|
|
|
|
|
2021-12-07 22:22:24 +00:00
|
|
|
// (Most) podcasts seem to support only 96 kbps Vorbis, so fall back to it
|
|
|
|
// TODO: update this logic once we also support MP3 and/or FLAC
|
2020-01-31 21:41:11 +00:00
|
|
|
let formats = match self.config.bitrate {
|
|
|
|
Bitrate::Bitrate96 => [
|
2021-12-07 22:22:24 +00:00
|
|
|
AudioFileFormat::OGG_VORBIS_96,
|
|
|
|
AudioFileFormat::OGG_VORBIS_160,
|
|
|
|
AudioFileFormat::OGG_VORBIS_320,
|
2020-01-31 21:41:11 +00:00
|
|
|
],
|
|
|
|
Bitrate::Bitrate160 => [
|
2021-12-07 22:22:24 +00:00
|
|
|
AudioFileFormat::OGG_VORBIS_160,
|
|
|
|
AudioFileFormat::OGG_VORBIS_96,
|
|
|
|
AudioFileFormat::OGG_VORBIS_320,
|
2020-01-31 21:41:11 +00:00
|
|
|
],
|
|
|
|
Bitrate::Bitrate320 => [
|
2021-12-07 22:22:24 +00:00
|
|
|
AudioFileFormat::OGG_VORBIS_320,
|
|
|
|
AudioFileFormat::OGG_VORBIS_160,
|
|
|
|
AudioFileFormat::OGG_VORBIS_96,
|
2020-01-31 21:41:11 +00:00
|
|
|
],
|
|
|
|
};
|
|
|
|
|
2021-02-09 08:15:55 +00:00
|
|
|
let entry = formats.iter().find_map(|format| {
|
|
|
|
if let Some(&file_id) = audio.files.get(format) {
|
|
|
|
Some((*format, file_id))
|
|
|
|
} else {
|
2021-02-02 01:19:15 +00:00
|
|
|
None
|
2021-02-09 08:15:55 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let (format, file_id) = match entry {
|
|
|
|
Some(t) => t,
|
2020-01-31 21:41:11 +00:00
|
|
|
None => {
|
2021-12-26 20:18:42 +00:00
|
|
|
error!("<{}> is not available in any supported format", audio.name);
|
2020-01-31 21:41:11 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-09 08:15:55 +00:00
|
|
|
let bytes_per_second = self.stream_data_rate(format);
|
2020-02-02 00:07:05 +00:00
|
|
|
let play_from_beginning = position_ms == 0;
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
// This is only a loop to be able to reload the file if an error occured
|
|
|
|
// while opening a cached file.
|
|
|
|
loop {
|
|
|
|
let encrypted_file = AudioFile::open(
|
|
|
|
&self.session,
|
|
|
|
file_id,
|
|
|
|
bytes_per_second,
|
|
|
|
play_from_beginning,
|
|
|
|
);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
let encrypted_file = match encrypted_file.await {
|
|
|
|
Ok(encrypted_file) => encrypted_file,
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to load encrypted file: {:?}", e);
|
2021-02-10 20:51:33 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
2021-12-18 22:44:13 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
let is_cached = encrypted_file.is_cached();
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-12-18 22:44:13 +00:00
|
|
|
let stream_loader_controller = encrypted_file.get_stream_loader_controller().ok()?;
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-12 17:19:04 +00:00
|
|
|
let key = match self.session.audio_key().request(spotify_id, file_id).await {
|
|
|
|
Ok(key) => key,
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to load decryption key: {:?}", e);
|
2021-02-12 17:19:04 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
// Parsing normalisation data and starting playback from *beyond* the beginning
|
|
|
|
// will trigger a seek() so always start in random access mode.
|
|
|
|
stream_loader_controller.set_random_access_mode();
|
|
|
|
|
2021-09-20 17:22:02 +00:00
|
|
|
let normalisation_data = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
|
|
|
Ok(data) => data,
|
2021-02-10 20:51:33 +00:00
|
|
|
Err(_) => {
|
2021-12-18 22:44:13 +00:00
|
|
|
warn!("Unable to extract normalisation data, using default values.");
|
|
|
|
NormalisationData::default()
|
2021-02-10 20:51:33 +00:00
|
|
|
}
|
|
|
|
};
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-23 13:45:01 +00:00
|
|
|
let result = if self.config.passthrough {
|
|
|
|
match PassthroughDecoder::new(audio_file) {
|
|
|
|
Ok(result) => Ok(Box::new(result) as Decoder),
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => Err(DecoderError::PassthroughDecoder(e.to_string())),
|
2021-02-23 13:45:01 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
match VorbisDecoder::new(audio_file) {
|
|
|
|
Ok(result) => Ok(Box::new(result) as Decoder),
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => Err(DecoderError::LewtonDecoder(e.to_string())),
|
2021-02-23 13:45:01 +00:00
|
|
|
}
|
|
|
|
};
|
2021-01-07 06:42:38 +00:00
|
|
|
|
2021-02-23 13:45:01 +00:00
|
|
|
let mut decoder = match result {
|
2021-02-10 20:51:33 +00:00
|
|
|
Ok(decoder) => decoder,
|
|
|
|
Err(e) if is_cached => {
|
|
|
|
warn!(
|
|
|
|
"Unable to read cached audio file: {}. Trying to download it.",
|
|
|
|
e
|
|
|
|
);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-09-20 17:29:12 +00:00
|
|
|
match self.session.cache() {
|
|
|
|
Some(cache) => {
|
|
|
|
if cache.remove_file(file_id).is_err() {
|
|
|
|
error!("Error removing file from cache");
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
error!("If the audio file is cached, a cache should exist");
|
|
|
|
return None;
|
|
|
|
}
|
2021-02-10 20:51:33 +00:00
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
// Just try it again
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to read audio file: {}", e);
|
2021-02-02 01:18:58 +00:00
|
|
|
return None;
|
|
|
|
}
|
2021-02-10 20:51:33 +00:00
|
|
|
};
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-12-28 22:46:37 +00:00
|
|
|
let mut stream_position_pcm = 0;
|
2021-09-20 17:29:12 +00:00
|
|
|
let position_pcm = PlayerInternal::position_ms_to_pcm(position_ms);
|
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
if !play_from_beginning {
|
2021-12-28 22:46:37 +00:00
|
|
|
match decoder.seek(position_pcm) {
|
|
|
|
Ok(_) => stream_position_pcm = position_pcm,
|
|
|
|
Err(e) => error!("PlayerTrackLoader::load_track error seeking: {}", e),
|
2021-02-10 20:51:33 +00:00
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
};
|
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
// Transition from random access mode to streaming mode now that
|
|
|
|
// we are ready to play from the requested position.
|
|
|
|
stream_loader_controller.set_stream_mode();
|
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
info!("<{}> ({} ms) loaded", audio.name, audio.duration);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-02-10 20:51:33 +00:00
|
|
|
return Some(PlayerLoadedTrackData {
|
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2021-02-10 20:51:33 +00:00
|
|
|
stream_loader_controller,
|
|
|
|
bytes_per_second,
|
|
|
|
duration_ms,
|
|
|
|
stream_position_pcm,
|
|
|
|
});
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Future for PlayerInternal {
|
2021-01-21 21:22:32 +00:00
|
|
|
type Output = ();
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-01-21 21:22:32 +00:00
|
|
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
2020-02-03 07:58:44 +00:00
|
|
|
// While this is written as a future, it still contains blocking code.
|
|
|
|
// It must be run on its own thread.
|
2021-02-23 13:45:01 +00:00
|
|
|
let passthrough = self.config.passthrough;
|
2020-02-03 07:58:44 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
loop {
|
|
|
|
let mut all_futures_completed_or_not_ready = true;
|
|
|
|
|
|
|
|
// process commands that were sent to us
|
2021-02-22 08:55:40 +00:00
|
|
|
let cmd = match self.commands.poll_recv(cx) {
|
2021-01-21 21:22:32 +00:00
|
|
|
Poll::Ready(None) => return Poll::Ready(()), // client has disconnected - shut down.
|
|
|
|
Poll::Ready(Some(cmd)) => {
|
2020-01-31 21:41:11 +00:00
|
|
|
all_futures_completed_or_not_ready = false;
|
|
|
|
Some(cmd)
|
2017-01-31 08:21:30 +00:00
|
|
|
}
|
2021-01-21 21:22:32 +00:00
|
|
|
_ => None,
|
2015-12-28 17:45:13 +00:00
|
|
|
};
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
if let Some(cmd) = cmd {
|
2021-12-18 22:44:13 +00:00
|
|
|
if let Err(e) = self.handle_command(cmd) {
|
|
|
|
error!("Error handling command: {}", e);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2016-05-04 08:11:03 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
// Handle loading of a new track to play
|
|
|
|
if let PlayerState::Loading {
|
|
|
|
ref mut loader,
|
|
|
|
track_id,
|
|
|
|
start_playback,
|
|
|
|
play_request_id,
|
|
|
|
} = self.state
|
|
|
|
{
|
2021-01-21 21:22:32 +00:00
|
|
|
match loader.as_mut().poll(cx) {
|
|
|
|
Poll::Ready(Ok(loaded_track)) => {
|
2020-01-31 21:41:11 +00:00
|
|
|
self.start_playback(
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
loaded_track,
|
|
|
|
start_playback,
|
|
|
|
);
|
|
|
|
if let PlayerState::Loading { .. } = self.state {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("The state wasn't changed by start_playback()");
|
|
|
|
exit(1);
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-12 19:01:05 +00:00
|
|
|
Poll::Ready(Err(e)) => {
|
2021-12-26 20:18:42 +00:00
|
|
|
error!(
|
2021-12-12 19:01:05 +00:00
|
|
|
"Skipping to next track, unable to load track <{:?}>: {:?}",
|
|
|
|
track_id, e
|
|
|
|
);
|
2021-12-11 20:32:34 +00:00
|
|
|
debug_assert!(self.state.is_loading());
|
2020-05-09 11:59:28 +00:00
|
|
|
self.send_event(PlayerEvent::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
})
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-01-21 21:22:32 +00:00
|
|
|
Poll::Pending => (),
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle pending preload requests.
|
|
|
|
if let PlayerPreload::Loading {
|
|
|
|
ref mut loader,
|
|
|
|
track_id,
|
|
|
|
} = self.preload
|
|
|
|
{
|
2021-01-21 21:22:32 +00:00
|
|
|
match loader.as_mut().poll(cx) {
|
|
|
|
Poll::Ready(Ok(loaded_track)) => {
|
2020-12-10 21:17:41 +00:00
|
|
|
self.send_event(PlayerEvent::Preloading { track_id });
|
2020-01-31 21:41:11 +00:00
|
|
|
self.preload = PlayerPreload::Ready {
|
|
|
|
track_id,
|
2021-01-22 21:51:41 +00:00
|
|
|
loaded_track: Box::new(loaded_track),
|
2020-01-31 21:41:11 +00:00
|
|
|
};
|
|
|
|
}
|
2021-01-21 21:22:32 +00:00
|
|
|
Poll::Ready(Err(_)) => {
|
2020-05-13 09:49:26 +00:00
|
|
|
debug!("Unable to preload {:?}", track_id);
|
|
|
|
self.preload = PlayerPreload::None;
|
|
|
|
// Let Spirc know that the track was unavailable.
|
2020-05-10 12:31:43 +00:00
|
|
|
if let PlayerState::Playing {
|
|
|
|
play_request_id, ..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
play_request_id, ..
|
|
|
|
} = self.state
|
|
|
|
{
|
2020-05-13 09:49:26 +00:00
|
|
|
self.send_event(PlayerEvent::Unavailable {
|
2020-05-10 12:31:43 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
});
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-01-21 21:22:32 +00:00
|
|
|
Poll::Pending => (),
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 23:11:27 +00:00
|
|
|
if self.state.is_playing() {
|
|
|
|
self.ensure_sink_running();
|
2018-02-23 19:08:20 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
if let PlayerState::Playing {
|
2020-01-31 21:41:11 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2018-02-26 01:50:41 +00:00
|
|
|
ref mut decoder,
|
|
|
|
normalisation_factor,
|
2020-02-02 00:07:05 +00:00
|
|
|
ref mut stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
ref mut reported_nominal_start_time,
|
|
|
|
duration_ms,
|
2018-02-26 01:50:41 +00:00
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
2021-09-20 17:29:12 +00:00
|
|
|
match decoder.next_packet() {
|
|
|
|
Ok(packet) => {
|
|
|
|
if !passthrough {
|
|
|
|
if let Some(ref packet) = packet {
|
|
|
|
match packet.samples() {
|
|
|
|
Ok(samples) => {
|
|
|
|
*stream_position_pcm +=
|
|
|
|
(samples.len() / NUM_CHANNELS as usize) as u64;
|
|
|
|
let stream_position_millis =
|
|
|
|
Self::position_pcm_to_ms(*stream_position_pcm);
|
|
|
|
|
|
|
|
let notify_about_position =
|
|
|
|
match *reported_nominal_start_time {
|
|
|
|
None => true,
|
|
|
|
Some(reported_nominal_start_time) => {
|
|
|
|
// only notify if we're behind. If we're ahead it's probably due to a buffer of the backend and we're actually in time.
|
|
|
|
let lag = (Instant::now()
|
|
|
|
- reported_nominal_start_time)
|
|
|
|
.as_millis()
|
|
|
|
as i64
|
|
|
|
- stream_position_millis as i64;
|
|
|
|
lag > Duration::from_secs(1).as_millis()
|
|
|
|
as i64
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if notify_about_position {
|
|
|
|
*reported_nominal_start_time = Some(
|
|
|
|
Instant::now()
|
|
|
|
- Duration::from_millis(
|
|
|
|
stream_position_millis as u64,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
self.send_event(PlayerEvent::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms: stream_position_millis as u32,
|
|
|
|
duration_ms,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
2021-12-26 20:18:42 +00:00
|
|
|
error!("Skipping to next track, unable to decode samples for track <{:?}>: {:?}", track_id, e);
|
2021-12-11 20:32:34 +00:00
|
|
|
self.send_event(PlayerEvent::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
})
|
2021-09-20 17:29:12 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
} else {
|
|
|
|
// position, even if irrelevant, must be set so that seek() is called
|
|
|
|
*stream_position_pcm = duration_ms.into();
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
|
|
|
|
self.handle_packet(packet, normalisation_factor);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
2021-12-26 20:18:42 +00:00
|
|
|
error!("Skipping to next track, unable to get next packet for track <{:?}>: {:?}", track_id, e);
|
2021-12-11 20:32:34 +00:00
|
|
|
self.send_event(PlayerEvent::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
})
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
2017-11-28 23:18:12 +00:00
|
|
|
} else {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("PlayerInternal poll: Invalid PlayerState");
|
|
|
|
exit(1);
|
2017-11-28 23:18:12 +00:00
|
|
|
};
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2018-04-21 15:46:29 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
if let PlayerState::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
duration_ms,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
ref mut stream_loader_controller,
|
|
|
|
ref mut suggested_to_preload_next_track,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
duration_ms,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
ref mut stream_loader_controller,
|
|
|
|
ref mut suggested_to_preload_next_track,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
if (!*suggested_to_preload_next_track)
|
2020-02-03 07:58:44 +00:00
|
|
|
&& ((duration_ms as i64 - Self::position_pcm_to_ms(stream_position_pcm) as i64)
|
2020-01-31 21:41:11 +00:00
|
|
|
< PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS as i64)
|
2021-12-26 20:18:42 +00:00
|
|
|
&& stream_loader_controller.range_to_end_available()
|
2020-01-31 21:41:11 +00:00
|
|
|
{
|
|
|
|
*suggested_to_preload_next_track = true;
|
|
|
|
self.send_event(PlayerEvent::TimeToPreloadNextTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-21 15:46:29 +00:00
|
|
|
if self.session.is_invalid() {
|
2021-01-21 21:22:32 +00:00
|
|
|
return Poll::Ready(());
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
if (!self.state.is_playing()) && all_futures_completed_or_not_ready {
|
2021-01-21 21:22:32 +00:00
|
|
|
return Poll::Pending;
|
2018-04-21 15:46:29 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2016-01-02 15:19:39 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
impl PlayerInternal {
|
2020-02-02 00:07:05 +00:00
|
|
|
fn position_pcm_to_ms(position_pcm: u64) -> u32 {
|
2021-09-20 17:29:12 +00:00
|
|
|
(position_pcm as f64 * MS_PER_PAGE) as u32
|
2020-02-02 00:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn position_ms_to_pcm(position_ms: u32) -> u64 {
|
2021-09-20 17:29:12 +00:00
|
|
|
(position_ms as f64 * PAGES_PER_MS) as u64
|
2020-02-02 00:07:05 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 23:11:27 +00:00
|
|
|
fn ensure_sink_running(&mut self) {
|
2020-03-10 12:26:01 +00:00
|
|
|
if self.sink_status != SinkStatus::Running {
|
2020-02-02 23:11:27 +00:00
|
|
|
trace!("== Starting sink ==");
|
2020-03-10 12:26:01 +00:00
|
|
|
if let Some(callback) = &mut self.sink_event_callback {
|
|
|
|
callback(SinkStatus::Running);
|
|
|
|
}
|
2020-02-02 23:11:27 +00:00
|
|
|
match self.sink.start() {
|
2020-03-10 12:26:01 +00:00
|
|
|
Ok(()) => self.sink_status = SinkStatus::Running,
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => {
|
|
|
|
error!("{}", e);
|
2021-06-18 18:25:09 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2020-02-02 23:11:27 +00:00
|
|
|
}
|
2017-11-28 23:18:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 12:26:01 +00:00
|
|
|
fn ensure_sink_stopped(&mut self, temporarily: bool) {
|
|
|
|
match self.sink_status {
|
|
|
|
SinkStatus::Running => {
|
|
|
|
trace!("== Stopping sink ==");
|
2021-06-18 18:25:09 +00:00
|
|
|
match self.sink.stop() {
|
|
|
|
Ok(()) => {
|
|
|
|
self.sink_status = if temporarily {
|
|
|
|
SinkStatus::TemporarilyClosed
|
|
|
|
} else {
|
|
|
|
SinkStatus::Closed
|
|
|
|
};
|
|
|
|
if let Some(callback) = &mut self.sink_event_callback {
|
|
|
|
callback(self.sink_status);
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
Err(e) => {
|
|
|
|
error!("{}", e);
|
2021-06-18 18:25:09 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2020-03-10 12:26:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
SinkStatus::TemporarilyClosed => {
|
|
|
|
if !temporarily {
|
|
|
|
self.sink_status = SinkStatus::Closed;
|
|
|
|
if let Some(callback) = &mut self.sink_event_callback {
|
|
|
|
callback(SinkStatus::Closed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SinkStatus::Closed => (),
|
2017-11-28 23:18:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn handle_player_stop(&mut self) {
|
|
|
|
match self.state {
|
|
|
|
PlayerState::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2020-02-02 14:37:17 +00:00
|
|
|
..
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
| PlayerState::Loading {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
} => {
|
2020-03-10 12:26:01 +00:00
|
|
|
self.ensure_sink_stopped(false);
|
2020-01-31 21:41:11 +00:00
|
|
|
self.send_event(PlayerEvent::Stopped {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
});
|
|
|
|
self.state = PlayerState::Stopped;
|
|
|
|
}
|
|
|
|
PlayerState::Stopped => (),
|
2021-09-20 17:29:12 +00:00
|
|
|
PlayerState::Invalid => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("PlayerInternal::handle_player_stop in invalid state");
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-02 14:37:17 +00:00
|
|
|
fn handle_play(&mut self) {
|
|
|
|
if let PlayerState::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
stream_position_pcm,
|
2020-02-03 07:58:44 +00:00
|
|
|
duration_ms,
|
2020-02-02 14:37:17 +00:00
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
self.state.paused_to_playing();
|
|
|
|
|
|
|
|
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
|
2020-02-03 07:58:44 +00:00
|
|
|
self.send_event(PlayerEvent::Playing {
|
2020-02-02 14:37:17 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
2020-02-03 07:58:44 +00:00
|
|
|
duration_ms,
|
2020-02-02 14:37:17 +00:00
|
|
|
});
|
2020-02-02 23:11:27 +00:00
|
|
|
self.ensure_sink_running();
|
2020-02-02 14:37:17 +00:00
|
|
|
} else {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("Player::play called from invalid state: {:?}", self.state);
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_pause(&mut self) {
|
|
|
|
if let PlayerState::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
stream_position_pcm,
|
|
|
|
duration_ms,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
self.state.playing_to_paused();
|
|
|
|
|
2020-03-10 12:26:01 +00:00
|
|
|
self.ensure_sink_stopped(false);
|
2020-02-02 14:37:17 +00:00
|
|
|
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
|
|
|
|
self.send_event(PlayerEvent::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
duration_ms,
|
|
|
|
});
|
|
|
|
} else {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("Player::pause called from invalid state: {:?}", self.state);
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-30 18:09:39 +00:00
|
|
|
fn handle_packet(&mut self, packet: Option<AudioPacket>, normalisation_factor: f64) {
|
2017-01-29 14:11:20 +00:00
|
|
|
match packet {
|
2017-08-03 20:22:08 +00:00
|
|
|
Some(mut packet) => {
|
2021-01-07 06:42:38 +00:00
|
|
|
if !packet.is_empty() {
|
|
|
|
if let AudioPacket::Samples(ref mut data) = packet {
|
2021-03-10 21:32:24 +00:00
|
|
|
if self.config.normalisation
|
2021-05-30 18:09:39 +00:00
|
|
|
&& !(f64::abs(normalisation_factor - 1.0) <= f64::EPSILON
|
2021-04-16 13:54:38 +00:00
|
|
|
&& self.config.normalisation_method == NormalisationMethod::Basic)
|
2021-03-10 21:32:24 +00:00
|
|
|
{
|
2021-02-24 20:39:42 +00:00
|
|
|
for sample in data.iter_mut() {
|
|
|
|
let mut actual_normalisation_factor = normalisation_factor;
|
|
|
|
if self.config.normalisation_method == NormalisationMethod::Dynamic
|
|
|
|
{
|
|
|
|
if self.limiter_active {
|
2021-03-14 13:28:16 +00:00
|
|
|
// "S"-shaped curve with a configurable knee during attack and release:
|
2021-02-24 20:39:42 +00:00
|
|
|
// - > 1.0 yields soft knees at start and end, steeper in between
|
|
|
|
// - 1.0 yields a linear function from 0-100%
|
|
|
|
// - between 0.0 and 1.0 yields hard knees at start and end, flatter in between
|
|
|
|
// - 0.0 yields a step response to 50%, causing distortion
|
|
|
|
// - Rates < 0.0 invert the limiter and are invalid
|
|
|
|
let mut shaped_limiter_strength = self.limiter_strength;
|
|
|
|
if shaped_limiter_strength > 0.0
|
|
|
|
&& shaped_limiter_strength < 1.0
|
|
|
|
{
|
|
|
|
shaped_limiter_strength = 1.0
|
|
|
|
/ (1.0
|
2021-05-30 18:09:39 +00:00
|
|
|
+ f64::powf(
|
2021-02-24 20:39:42 +00:00
|
|
|
shaped_limiter_strength
|
|
|
|
/ (1.0 - shaped_limiter_strength),
|
2021-05-30 18:09:39 +00:00
|
|
|
-self.config.normalisation_knee,
|
2021-02-24 20:39:42 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
actual_normalisation_factor =
|
|
|
|
(1.0 - shaped_limiter_strength) * normalisation_factor
|
|
|
|
+ shaped_limiter_strength * self.limiter_factor;
|
|
|
|
};
|
|
|
|
|
2021-05-31 20:32:39 +00:00
|
|
|
// Cast the fields here for better readability
|
|
|
|
let normalisation_attack =
|
|
|
|
self.config.normalisation_attack.as_secs_f64();
|
|
|
|
let normalisation_release =
|
|
|
|
self.config.normalisation_release.as_secs_f64();
|
|
|
|
let limiter_release_counter =
|
|
|
|
self.limiter_release_counter as f64;
|
|
|
|
let limiter_attack_counter = self.limiter_attack_counter as f64;
|
|
|
|
let samples_per_second = SAMPLES_PER_SECOND as f64;
|
|
|
|
|
2021-02-24 20:39:42 +00:00
|
|
|
// Always check for peaks, even when the limiter is already active.
|
|
|
|
// There may be even higher peaks than we initially targeted.
|
|
|
|
// Check against the normalisation factor that would be applied normally.
|
2021-05-30 18:09:39 +00:00
|
|
|
let abs_sample = f64::abs(*sample * normalisation_factor);
|
2021-02-24 20:39:42 +00:00
|
|
|
if abs_sample > self.config.normalisation_threshold {
|
|
|
|
self.limiter_active = true;
|
|
|
|
if self.limiter_release_counter > 0 {
|
|
|
|
// A peak was encountered while releasing the limiter;
|
|
|
|
// synchronize with the current release limiter strength.
|
2021-05-31 20:32:39 +00:00
|
|
|
self.limiter_attack_counter = (((samples_per_second
|
|
|
|
* normalisation_release)
|
|
|
|
- limiter_release_counter)
|
|
|
|
/ (normalisation_release / normalisation_attack))
|
2021-02-24 20:39:42 +00:00
|
|
|
as u32;
|
|
|
|
self.limiter_release_counter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.limiter_attack_counter =
|
|
|
|
self.limiter_attack_counter.saturating_add(1);
|
2021-05-31 20:32:39 +00:00
|
|
|
|
|
|
|
self.limiter_strength = limiter_attack_counter
|
|
|
|
/ (samples_per_second * normalisation_attack);
|
2021-02-24 20:39:42 +00:00
|
|
|
|
|
|
|
if abs_sample > self.limiter_peak_sample {
|
|
|
|
self.limiter_peak_sample = abs_sample;
|
|
|
|
self.limiter_factor =
|
|
|
|
self.config.normalisation_threshold
|
|
|
|
/ self.limiter_peak_sample;
|
|
|
|
}
|
|
|
|
} else if self.limiter_active {
|
|
|
|
if self.limiter_attack_counter > 0 {
|
|
|
|
// Release may start within the attack period, before
|
|
|
|
// the limiter reached full strength. For that reason
|
|
|
|
// start the release by synchronizing with the current
|
|
|
|
// attack limiter strength.
|
2021-05-31 20:32:39 +00:00
|
|
|
self.limiter_release_counter = (((samples_per_second
|
|
|
|
* normalisation_attack)
|
|
|
|
- limiter_attack_counter)
|
|
|
|
* (normalisation_release / normalisation_attack))
|
2021-02-24 20:39:42 +00:00
|
|
|
as u32;
|
|
|
|
self.limiter_attack_counter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.limiter_release_counter =
|
|
|
|
self.limiter_release_counter.saturating_add(1);
|
|
|
|
|
|
|
|
if self.limiter_release_counter
|
2021-05-31 20:32:39 +00:00
|
|
|
> (samples_per_second * normalisation_release) as u32
|
2021-02-24 20:39:42 +00:00
|
|
|
{
|
|
|
|
self.reset_limiter();
|
|
|
|
} else {
|
2021-05-31 20:32:39 +00:00
|
|
|
self.limiter_strength = ((samples_per_second
|
|
|
|
* normalisation_release)
|
|
|
|
- limiter_release_counter)
|
|
|
|
/ (samples_per_second * normalisation_release);
|
2021-02-24 20:39:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-30 18:09:39 +00:00
|
|
|
*sample *= actual_normalisation_factor;
|
2021-01-07 06:42:38 +00:00
|
|
|
}
|
2018-03-20 13:01:15 +00:00
|
|
|
}
|
2021-09-01 18:54:47 +00:00
|
|
|
|
|
|
|
if let Some(ref editor) = self.audio_filter {
|
|
|
|
editor.modify_stream(data)
|
|
|
|
}
|
2018-02-23 19:08:20 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 17:29:12 +00:00
|
|
|
if let Err(e) = self.sink.write(&packet, &mut self.converter) {
|
|
|
|
error!("{}", e);
|
2021-06-18 18:25:09 +00:00
|
|
|
exit(1);
|
2018-03-20 13:01:15 +00:00
|
|
|
}
|
2017-11-28 23:18:12 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
None => {
|
2018-02-20 20:58:02 +00:00
|
|
|
self.state.playing_to_end_of_track();
|
2020-01-31 21:41:11 +00:00
|
|
|
if let PlayerState::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2020-02-02 14:37:17 +00:00
|
|
|
..
|
2020-01-31 21:41:11 +00:00
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
self.send_event(PlayerEvent::EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
})
|
|
|
|
} else {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("PlayerInternal handle_packet: Invalid PlayerState");
|
|
|
|
exit(1);
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-24 20:39:42 +00:00
|
|
|
fn reset_limiter(&mut self) {
|
|
|
|
self.limiter_active = false;
|
|
|
|
self.limiter_release_counter = 0;
|
|
|
|
self.limiter_attack_counter = 0;
|
|
|
|
self.limiter_peak_sample = 0.0;
|
|
|
|
self.limiter_factor = 1.0;
|
|
|
|
self.limiter_strength = 0.0;
|
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn start_playback(
|
|
|
|
&mut self,
|
|
|
|
track_id: SpotifyId,
|
|
|
|
play_request_id: u64,
|
|
|
|
loaded_track: PlayerLoadedTrackData,
|
|
|
|
start_playback: bool,
|
|
|
|
) {
|
2020-02-02 00:07:05 +00:00
|
|
|
let position_ms = Self::position_pcm_to_ms(loaded_track.stream_position_pcm);
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-09-20 17:22:02 +00:00
|
|
|
let mut config = self.config.clone();
|
|
|
|
if config.normalisation_type == NormalisationType::Auto {
|
|
|
|
if self.auto_normalise_as_album {
|
|
|
|
config.normalisation_type = NormalisationType::Album;
|
|
|
|
} else {
|
|
|
|
config.normalisation_type = NormalisationType::Track;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let normalisation_factor =
|
|
|
|
NormalisationData::get_factor(&config, loaded_track.normalisation_data);
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
if start_playback {
|
2020-02-02 23:11:27 +00:00
|
|
|
self.ensure_sink_running();
|
2020-01-31 21:41:11 +00:00
|
|
|
|
|
|
|
self.send_event(PlayerEvent::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
duration_ms: loaded_track.duration_ms,
|
|
|
|
});
|
|
|
|
|
|
|
|
self.state = PlayerState::Playing {
|
2021-03-10 21:39:01 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2020-01-31 21:41:11 +00:00
|
|
|
decoder: loaded_track.decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data: loaded_track.normalisation_data,
|
|
|
|
normalisation_factor,
|
2020-01-31 21:41:11 +00:00
|
|
|
stream_loader_controller: loaded_track.stream_loader_controller,
|
|
|
|
duration_ms: loaded_track.duration_ms,
|
|
|
|
bytes_per_second: loaded_track.bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm: loaded_track.stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
reported_nominal_start_time: Some(
|
|
|
|
Instant::now() - Duration::from_millis(position_ms as u64),
|
|
|
|
),
|
|
|
|
suggested_to_preload_next_track: false,
|
|
|
|
};
|
|
|
|
} else {
|
2020-03-10 12:26:01 +00:00
|
|
|
self.ensure_sink_stopped(false);
|
2020-02-03 07:58:44 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
self.state = PlayerState::Paused {
|
2021-03-10 21:39:01 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
2020-01-31 21:41:11 +00:00
|
|
|
decoder: loaded_track.decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data: loaded_track.normalisation_data,
|
|
|
|
normalisation_factor,
|
2020-01-31 21:41:11 +00:00
|
|
|
stream_loader_controller: loaded_track.stream_loader_controller,
|
|
|
|
duration_ms: loaded_track.duration_ms,
|
|
|
|
bytes_per_second: loaded_track.bytes_per_second,
|
2020-02-02 00:07:05 +00:00
|
|
|
stream_position_pcm: loaded_track.stream_position_pcm,
|
2020-01-31 21:41:11 +00:00
|
|
|
suggested_to_preload_next_track: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
self.send_event(PlayerEvent::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
duration_ms: loaded_track.duration_ms,
|
|
|
|
});
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
fn handle_command_load(
|
|
|
|
&mut self,
|
|
|
|
track_id: SpotifyId,
|
|
|
|
play_request_id: u64,
|
|
|
|
play: bool,
|
|
|
|
position_ms: u32,
|
|
|
|
) {
|
2020-03-10 12:00:57 +00:00
|
|
|
if !self.config.gapless {
|
2020-03-10 12:53:58 +00:00
|
|
|
self.ensure_sink_stopped(play);
|
2020-03-10 12:00:57 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
// emit the correct player event
|
|
|
|
match self.state {
|
|
|
|
PlayerState::Playing {
|
|
|
|
track_id: old_track_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
track_id: old_track_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::EndOfTrack {
|
|
|
|
track_id: old_track_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Loading {
|
|
|
|
track_id: old_track_id,
|
|
|
|
..
|
|
|
|
} => self.send_event(PlayerEvent::Changed {
|
2021-03-10 21:39:01 +00:00
|
|
|
old_track_id,
|
2020-02-03 07:58:44 +00:00
|
|
|
new_track_id: track_id,
|
|
|
|
}),
|
|
|
|
PlayerState::Stopped => self.send_event(PlayerEvent::Started {
|
2020-01-31 21:41:11 +00:00
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
2020-02-03 07:58:44 +00:00
|
|
|
}),
|
2021-09-20 17:29:12 +00:00
|
|
|
PlayerState::Invalid { .. } => {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!(
|
|
|
|
"Player::handle_command_load called from invalid state: {:?}",
|
|
|
|
self.state
|
|
|
|
);
|
2021-09-20 17:29:12 +00:00
|
|
|
exit(1);
|
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2018-02-15 23:16:38 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// Now we check at different positions whether we already have a pre-loaded version
|
|
|
|
// of this track somewhere. If so, use it and return.
|
2020-02-02 14:37:17 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// Check if there's a matching loaded track in the EndOfTrack player state.
|
|
|
|
// This is the case if we're repeating the same track again.
|
|
|
|
if let PlayerState::EndOfTrack {
|
|
|
|
track_id: previous_track_id,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
if previous_track_id == track_id {
|
2020-03-12 12:01:45 +00:00
|
|
|
let mut loaded_track = match mem::replace(&mut self.state, PlayerState::Invalid) {
|
|
|
|
PlayerState::EndOfTrack { loaded_track, .. } => loaded_track,
|
2021-09-20 17:29:12 +00:00
|
|
|
_ => {
|
|
|
|
error!("PlayerInternal handle_command_load: Invalid PlayerState");
|
|
|
|
exit(1);
|
|
|
|
}
|
2020-03-12 12:01:45 +00:00
|
|
|
};
|
|
|
|
|
2021-09-20 17:29:12 +00:00
|
|
|
let position_pcm = Self::position_ms_to_pcm(position_ms);
|
|
|
|
|
|
|
|
if position_pcm != loaded_track.stream_position_pcm {
|
2020-03-12 12:01:45 +00:00
|
|
|
loaded_track
|
|
|
|
.stream_loader_controller
|
|
|
|
.set_random_access_mode();
|
2021-12-28 22:46:37 +00:00
|
|
|
// This may be blocking.
|
|
|
|
match loaded_track.decoder.seek(position_pcm) {
|
|
|
|
Ok(_) => loaded_track.stream_position_pcm = position_pcm,
|
|
|
|
Err(e) => error!("PlayerInternal handle_command_load: {}", e),
|
2021-09-20 17:29:12 +00:00
|
|
|
}
|
2020-03-12 12:01:45 +00:00
|
|
|
loaded_track.stream_loader_controller.set_stream_mode();
|
|
|
|
}
|
|
|
|
self.preload = PlayerPreload::None;
|
2020-03-20 06:31:18 +00:00
|
|
|
self.start_playback(track_id, play_request_id, loaded_track, play);
|
2020-03-12 12:01:45 +00:00
|
|
|
if let PlayerState::Invalid = self.state {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("start_playback() hasn't set a valid player state.");
|
|
|
|
exit(1);
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2020-03-12 12:01:45 +00:00
|
|
|
return;
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we are already playing the track. If so, just do a seek and update our info.
|
|
|
|
if let PlayerState::Playing {
|
|
|
|
track_id: current_track_id,
|
|
|
|
ref mut stream_position_pcm,
|
|
|
|
ref mut decoder,
|
|
|
|
ref mut stream_loader_controller,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
track_id: current_track_id,
|
|
|
|
ref mut stream_position_pcm,
|
|
|
|
ref mut decoder,
|
|
|
|
ref mut stream_loader_controller,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
if current_track_id == track_id {
|
|
|
|
// we can use the current decoder. Ensure it's at the correct position.
|
2021-09-20 17:29:12 +00:00
|
|
|
let position_pcm = Self::position_ms_to_pcm(position_ms);
|
|
|
|
|
|
|
|
if position_pcm != *stream_position_pcm {
|
2020-02-03 07:58:44 +00:00
|
|
|
stream_loader_controller.set_random_access_mode();
|
2021-12-28 22:46:37 +00:00
|
|
|
// This may be blocking.
|
|
|
|
match decoder.seek(position_pcm) {
|
|
|
|
Ok(_) => *stream_position_pcm = position_pcm,
|
|
|
|
Err(e) => {
|
|
|
|
error!("PlayerInternal::handle_command_load error seeking: {}", e)
|
|
|
|
}
|
2021-09-20 17:29:12 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
stream_loader_controller.set_stream_mode();
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// Move the info from the current state into a PlayerLoadedTrackData so we can use
|
|
|
|
// the usual code path to start playback.
|
|
|
|
let old_state = mem::replace(&mut self.state, PlayerState::Invalid);
|
|
|
|
|
2020-02-02 14:37:17 +00:00
|
|
|
if let PlayerState::Playing {
|
2020-02-03 07:58:44 +00:00
|
|
|
stream_position_pcm,
|
|
|
|
decoder,
|
|
|
|
stream_loader_controller,
|
|
|
|
bytes_per_second,
|
|
|
|
duration_ms,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-02-02 14:37:17 +00:00
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
2020-02-03 07:58:44 +00:00
|
|
|
stream_position_pcm,
|
|
|
|
decoder,
|
|
|
|
stream_loader_controller,
|
|
|
|
bytes_per_second,
|
|
|
|
duration_ms,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-02-02 14:37:17 +00:00
|
|
|
..
|
2020-02-03 07:58:44 +00:00
|
|
|
} = old_state
|
2020-02-02 14:37:17 +00:00
|
|
|
{
|
2020-02-03 07:58:44 +00:00
|
|
|
let loaded_track = PlayerLoadedTrackData {
|
|
|
|
decoder,
|
2021-09-20 17:22:02 +00:00
|
|
|
normalisation_data,
|
2020-02-03 07:58:44 +00:00
|
|
|
stream_loader_controller,
|
|
|
|
bytes_per_second,
|
|
|
|
duration_ms,
|
|
|
|
stream_position_pcm,
|
|
|
|
};
|
2020-02-03 03:31:15 +00:00
|
|
|
|
2020-02-07 12:52:20 +00:00
|
|
|
self.preload = PlayerPreload::None;
|
2020-02-03 07:58:44 +00:00
|
|
|
self.start_playback(track_id, play_request_id, loaded_track, play);
|
2020-02-03 03:31:15 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
if let PlayerState::Invalid = self.state {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("start_playback() hasn't set a valid player state.");
|
|
|
|
exit(1);
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2020-02-03 03:31:15 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
return;
|
|
|
|
} else {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("PlayerInternal handle_command_load: Invalid PlayerState");
|
|
|
|
exit(1);
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-03 03:31:15 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// Check if the requested track has been preloaded already. If so use the preloaded data.
|
|
|
|
if let PlayerPreload::Ready {
|
|
|
|
track_id: loaded_track_id,
|
|
|
|
..
|
|
|
|
} = self.preload
|
|
|
|
{
|
|
|
|
if track_id == loaded_track_id {
|
|
|
|
let preload = std::mem::replace(&mut self.preload, PlayerPreload::None);
|
|
|
|
if let PlayerPreload::Ready {
|
|
|
|
track_id,
|
|
|
|
mut loaded_track,
|
|
|
|
} = preload
|
|
|
|
{
|
2021-09-20 17:29:12 +00:00
|
|
|
let position_pcm = Self::position_ms_to_pcm(position_ms);
|
|
|
|
|
|
|
|
if position_pcm != loaded_track.stream_position_pcm {
|
2020-02-03 07:58:44 +00:00
|
|
|
loaded_track
|
|
|
|
.stream_loader_controller
|
|
|
|
.set_random_access_mode();
|
2021-12-28 22:46:37 +00:00
|
|
|
// This may be blocking
|
|
|
|
match loaded_track.decoder.seek(position_pcm) {
|
|
|
|
Ok(_) => loaded_track.stream_position_pcm = position_pcm,
|
|
|
|
Err(e) => error!("PlayerInternal handle_command_load: {}", e),
|
2021-09-20 17:29:12 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
loaded_track.stream_loader_controller.set_stream_mode();
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
2021-01-22 21:51:41 +00:00
|
|
|
self.start_playback(track_id, play_request_id, *loaded_track, play);
|
2020-02-03 07:58:44 +00:00
|
|
|
return;
|
|
|
|
} else {
|
2021-09-20 17:29:12 +00:00
|
|
|
error!("PlayerInternal handle_command_load: Invalid PlayerState");
|
|
|
|
exit(1);
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-02 14:37:17 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// We need to load the track - either from scratch or by completing a preload.
|
|
|
|
// In any case we go into a Loading state to load the track.
|
2020-03-10 12:26:01 +00:00
|
|
|
self.ensure_sink_stopped(play);
|
2020-02-03 07:58:44 +00:00
|
|
|
|
|
|
|
self.send_event(PlayerEvent::Loading {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Try to extract a pending loader from the preloading mechanism
|
|
|
|
let loader = if let PlayerPreload::Loading {
|
|
|
|
track_id: loaded_track_id,
|
|
|
|
..
|
|
|
|
} = self.preload
|
|
|
|
{
|
|
|
|
if (track_id == loaded_track_id) && (position_ms == 0) {
|
|
|
|
let mut preload = PlayerPreload::None;
|
|
|
|
std::mem::swap(&mut preload, &mut self.preload);
|
|
|
|
if let PlayerPreload::Loading { loader, .. } = preload {
|
|
|
|
Some(loader)
|
|
|
|
} else {
|
|
|
|
None
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
2020-02-02 14:37:17 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
self.preload = PlayerPreload::None;
|
2020-02-02 23:11:27 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// If we don't have a loader yet, create one from scratch.
|
2021-01-21 21:22:32 +00:00
|
|
|
let loader = loader.unwrap_or_else(|| Box::pin(self.load_track(track_id, position_ms)));
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// Set ourselves to a loading state.
|
|
|
|
self.state = PlayerState::Loading {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
start_playback: play,
|
|
|
|
loader,
|
|
|
|
};
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
fn handle_command_preload(&mut self, track_id: SpotifyId) {
|
|
|
|
debug!("Preloading track");
|
|
|
|
let mut preload_track = true;
|
|
|
|
// check whether the track is already loaded somewhere or being loaded.
|
|
|
|
if let PlayerPreload::Loading {
|
|
|
|
track_id: currently_loading,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerPreload::Ready {
|
|
|
|
track_id: currently_loading,
|
|
|
|
..
|
|
|
|
} = self.preload
|
|
|
|
{
|
|
|
|
if currently_loading == track_id {
|
|
|
|
// we're already preloading the requested track.
|
|
|
|
preload_track = false;
|
|
|
|
} else {
|
|
|
|
// we're preloading something else - cancel it.
|
|
|
|
self.preload = PlayerPreload::None;
|
|
|
|
}
|
|
|
|
}
|
2016-01-02 15:48:44 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
if let PlayerState::Playing {
|
|
|
|
track_id: current_track_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::Paused {
|
|
|
|
track_id: current_track_id,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| PlayerState::EndOfTrack {
|
|
|
|
track_id: current_track_id,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
if current_track_id == track_id {
|
|
|
|
// we already have the requested track loaded.
|
|
|
|
preload_track = false;
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2020-03-12 12:01:45 +00:00
|
|
|
// schedule the preload of the current track if desired.
|
2020-02-03 07:58:44 +00:00
|
|
|
if preload_track {
|
|
|
|
let loader = self.load_track(track_id, 0);
|
2021-01-21 21:22:32 +00:00
|
|
|
self.preload = PlayerPreload::Loading {
|
|
|
|
track_id,
|
|
|
|
loader: Box::pin(loader),
|
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-02 14:37:17 +00:00
|
|
|
|
2021-12-18 22:44:13 +00:00
|
|
|
fn handle_command_seek(&mut self, position_ms: u32) -> PlayerResult {
|
2020-02-03 07:58:44 +00:00
|
|
|
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
|
|
|
stream_loader_controller.set_random_access_mode();
|
|
|
|
}
|
|
|
|
if let Some(decoder) = self.state.decoder() {
|
2021-09-20 17:29:12 +00:00
|
|
|
let position_pcm = Self::position_ms_to_pcm(position_ms);
|
|
|
|
|
|
|
|
match decoder.seek(position_pcm) {
|
2020-02-03 07:58:44 +00:00
|
|
|
Ok(_) => {
|
|
|
|
if let PlayerState::Playing {
|
|
|
|
ref mut stream_position_pcm,
|
|
|
|
..
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
| PlayerState::Paused {
|
|
|
|
ref mut stream_position_pcm,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
2021-09-20 17:29:12 +00:00
|
|
|
*stream_position_pcm = position_pcm;
|
2020-02-02 14:37:17 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
Err(e) => error!("PlayerInternal::handle_command_seek error: {}", e),
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2020-02-03 07:58:44 +00:00
|
|
|
} else {
|
2021-12-28 22:46:37 +00:00
|
|
|
error!("Player::seek called from invalid state: {:?}", self.state);
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2016-04-24 13:48:15 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// If we're playing, ensure, that we have enough data leaded to avoid a buffer underrun.
|
|
|
|
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
|
|
|
stream_loader_controller.set_stream_mode();
|
|
|
|
}
|
2019-11-01 19:46:28 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
// ensure we have a bit of a buffer of downloaded data
|
2021-12-18 22:44:13 +00:00
|
|
|
self.preload_data_before_playback()?;
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
if let PlayerState::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
ref mut reported_nominal_start_time,
|
|
|
|
duration_ms,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
*reported_nominal_start_time =
|
|
|
|
Some(Instant::now() - Duration::from_millis(position_ms as u64));
|
|
|
|
self.send_event(PlayerEvent::Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
duration_ms,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if let PlayerState::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
duration_ms,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
self.send_event(PlayerEvent::Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
position_ms,
|
|
|
|
duration_ms,
|
|
|
|
});
|
|
|
|
}
|
2021-12-18 22:44:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
2020-02-03 07:58:44 +00:00
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
|
2021-12-18 22:44:13 +00:00
|
|
|
fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult {
|
2020-02-03 07:58:44 +00:00
|
|
|
debug!("command={:?}", cmd);
|
2021-12-18 22:44:13 +00:00
|
|
|
let result = match cmd {
|
2020-02-03 07:58:44 +00:00
|
|
|
PlayerCommand::Load {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
play,
|
|
|
|
position_ms,
|
|
|
|
} => self.handle_command_load(track_id, play_request_id, play, position_ms),
|
2015-07-02 19:42:49 +00:00
|
|
|
|
2020-02-03 07:58:44 +00:00
|
|
|
PlayerCommand::Preload { track_id } => self.handle_command_preload(track_id),
|
2016-04-24 13:48:15 +00:00
|
|
|
|
2021-12-18 22:44:13 +00:00
|
|
|
PlayerCommand::Seek(position_ms) => self.handle_command_seek(position_ms)?,
|
2020-02-03 07:58:44 +00:00
|
|
|
|
|
|
|
PlayerCommand::Play => self.handle_play(),
|
|
|
|
|
|
|
|
PlayerCommand::Pause => self.handle_pause(),
|
2016-04-24 13:48:15 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::Stop => self.handle_player_stop(),
|
2015-06-23 14:38:29 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::AddEventSender(sender) => self.event_senders.push(sender),
|
2016-01-20 13:55:36 +00:00
|
|
|
|
2020-03-10 12:26:01 +00:00
|
|
|
PlayerCommand::SetSinkEventCallback(callback) => self.sink_event_callback = callback,
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::EmitVolumeSetEvent(volume) => {
|
|
|
|
self.send_event(PlayerEvent::VolumeSet { volume })
|
2019-10-07 22:27:26 +00:00
|
|
|
}
|
2021-09-20 17:22:02 +00:00
|
|
|
|
|
|
|
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => {
|
|
|
|
self.auto_normalise_as_album = setting
|
|
|
|
}
|
2021-12-18 22:44:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(result)
|
2016-01-15 00:12:08 +00:00
|
|
|
}
|
2015-07-09 20:08:14 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn send_event(&mut self, event: PlayerEvent) {
|
|
|
|
let mut index = 0;
|
|
|
|
while index < self.event_senders.len() {
|
2021-02-22 08:55:40 +00:00
|
|
|
match self.event_senders[index].send(event.clone()) {
|
2020-01-31 21:41:11 +00:00
|
|
|
Ok(_) => index += 1,
|
|
|
|
Err(_) => {
|
|
|
|
self.event_senders.remove(index);
|
|
|
|
}
|
|
|
|
}
|
2019-11-01 19:46:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
fn load_track(
|
2021-12-27 23:04:09 +00:00
|
|
|
&mut self,
|
2019-11-11 07:22:41 +00:00
|
|
|
spotify_id: SpotifyId,
|
2020-02-02 00:07:05 +00:00
|
|
|
position_ms: u32,
|
2021-01-22 21:51:41 +00:00
|
|
|
) -> impl Future<Output = Result<PlayerLoadedTrackData, ()>> + Send + 'static {
|
2020-01-31 21:41:11 +00:00
|
|
|
// This method creates a future that returns the loaded stream and associated info.
|
|
|
|
// Ideally all work should be done using asynchronous code. However, seek() on the
|
|
|
|
// audio stream is implemented in a blocking fashion. Thus, we can't turn it into future
|
|
|
|
// easily. Instead we spawn a thread to do the work and return a one-shot channel as the
|
|
|
|
// future to work with.
|
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
let loader = PlayerTrackLoader {
|
|
|
|
session: self.session.clone(),
|
|
|
|
config: self.config.clone(),
|
|
|
|
};
|
2019-11-01 19:48:18 +00:00
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
let (result_tx, result_rx) = oneshot::channel();
|
2021-01-21 21:22:32 +00:00
|
|
|
|
2021-12-27 23:04:09 +00:00
|
|
|
let load_handles_clone = self.load_handles.clone();
|
2021-11-26 22:21:27 +00:00
|
|
|
let handle = tokio::runtime::Handle::current();
|
2021-12-27 23:04:09 +00:00
|
|
|
let load_handle = thread::spawn(move || {
|
2021-11-26 22:21:27 +00:00
|
|
|
let data = handle.block_on(loader.load_track(spotify_id, position_ms));
|
2021-03-10 21:32:24 +00:00
|
|
|
if let Some(data) = data {
|
|
|
|
let _ = result_tx.send(data);
|
|
|
|
}
|
2021-12-27 23:04:09 +00:00
|
|
|
|
|
|
|
let mut load_handles = load_handles_clone.lock();
|
|
|
|
load_handles.remove(&thread::current().id());
|
2021-02-20 21:14:15 +00:00
|
|
|
});
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2021-12-27 23:04:09 +00:00
|
|
|
let mut load_handles = self.load_handles.lock();
|
|
|
|
load_handles.insert(load_handle.thread().id(), load_handle);
|
|
|
|
|
2021-02-20 21:14:15 +00:00
|
|
|
result_rx.map_err(|_| ())
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2019-11-07 13:02:53 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn preload_data_before_playback(&mut self) -> PlayerResult {
|
2020-01-31 21:41:11 +00:00
|
|
|
if let PlayerState::Playing {
|
2020-01-17 17:11:07 +00:00
|
|
|
bytes_per_second,
|
2020-01-31 21:41:11 +00:00
|
|
|
ref mut stream_loader_controller,
|
|
|
|
..
|
|
|
|
} = self.state
|
|
|
|
{
|
|
|
|
// Request our read ahead range
|
|
|
|
let request_data_length = max(
|
|
|
|
(READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS
|
2021-05-31 20:32:39 +00:00
|
|
|
* stream_loader_controller.ping_time().as_secs_f32()
|
|
|
|
* bytes_per_second as f32) as usize,
|
|
|
|
(READ_AHEAD_DURING_PLAYBACK.as_secs_f32() * bytes_per_second as f32) as usize,
|
2020-01-31 21:41:11 +00:00
|
|
|
);
|
|
|
|
stream_loader_controller.fetch_next(request_data_length);
|
|
|
|
|
|
|
|
// Request the part we want to wait for blocking. This effecively means we wait for the previous request to partially complete.
|
|
|
|
let wait_for_data_length = max(
|
|
|
|
(READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS
|
2021-05-31 20:32:39 +00:00
|
|
|
* stream_loader_controller.ping_time().as_secs_f32()
|
|
|
|
* bytes_per_second as f32) as usize,
|
|
|
|
(READ_AHEAD_BEFORE_PLAYBACK.as_secs_f32() * bytes_per_second as f32) as usize,
|
2020-01-31 21:41:11 +00:00
|
|
|
);
|
2021-12-18 22:44:13 +00:00
|
|
|
stream_loader_controller
|
|
|
|
.fetch_next_blocking(wait_for_data_length)
|
2021-12-26 20:18:42 +00:00
|
|
|
.map_err(Into::into)
|
2021-12-18 22:44:13 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
2019-11-01 19:46:28 +00:00
|
|
|
}
|
2015-07-09 22:09:40 +00:00
|
|
|
}
|
2015-07-09 20:08:14 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-22 04:17:04 +00:00
|
|
|
impl Drop for PlayerInternal {
|
|
|
|
fn drop(&mut self) {
|
2020-01-31 21:41:11 +00:00
|
|
|
debug!("drop PlayerInternal[{}]", self.session.session_id());
|
2021-12-27 23:04:09 +00:00
|
|
|
|
|
|
|
let handles: Vec<thread::JoinHandle<()>> = {
|
|
|
|
// waiting for the thread while holding the mutex would result in a deadlock
|
|
|
|
let mut load_handles = self.load_handles.lock();
|
|
|
|
|
|
|
|
load_handles
|
|
|
|
.drain()
|
|
|
|
.map(|(_thread_id, handle)| handle)
|
|
|
|
.collect()
|
|
|
|
};
|
|
|
|
|
|
|
|
for handle in handles {
|
|
|
|
let _ = handle.join();
|
|
|
|
}
|
2017-02-22 04:17:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-28 22:46:37 +00:00
|
|
|
impl fmt::Debug for PlayerCommand {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2017-02-09 01:27:52 +00:00
|
|
|
match *self {
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::Load {
|
|
|
|
track_id,
|
|
|
|
play,
|
|
|
|
position_ms,
|
|
|
|
..
|
|
|
|
} => f
|
2018-07-03 11:08:42 +00:00
|
|
|
.debug_tuple("Load")
|
2020-01-31 21:41:11 +00:00
|
|
|
.field(&track_id)
|
2018-02-26 01:50:41 +00:00
|
|
|
.field(&play)
|
2020-01-31 21:41:11 +00:00
|
|
|
.field(&position_ms)
|
2018-02-26 01:50:41 +00:00
|
|
|
.finish(),
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::Preload { track_id } => {
|
|
|
|
f.debug_tuple("Preload").field(&track_id).finish()
|
|
|
|
}
|
2018-02-26 01:50:41 +00:00
|
|
|
PlayerCommand::Play => f.debug_tuple("Play").finish(),
|
|
|
|
PlayerCommand::Pause => f.debug_tuple("Pause").finish(),
|
|
|
|
PlayerCommand::Stop => f.debug_tuple("Stop").finish(),
|
|
|
|
PlayerCommand::Seek(position) => f.debug_tuple("Seek").field(&position).finish(),
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::AddEventSender(_) => f.debug_tuple("AddEventSender").finish(),
|
2020-03-10 12:26:01 +00:00
|
|
|
PlayerCommand::SetSinkEventCallback(_) => {
|
|
|
|
f.debug_tuple("SetSinkEventCallback").finish()
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
PlayerCommand::EmitVolumeSetEvent(volume) => {
|
|
|
|
f.debug_tuple("VolumeSet").field(&volume).finish()
|
|
|
|
}
|
2021-09-20 17:22:02 +00:00
|
|
|
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => f
|
|
|
|
.debug_tuple("SetAutoNormaliseAsAlbum")
|
|
|
|
.field(&setting)
|
|
|
|
.finish(),
|
2017-02-09 01:27:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-10 15:26:08 +00:00
|
|
|
|
2021-12-28 22:46:37 +00:00
|
|
|
impl fmt::Debug for PlayerState {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-05-09 11:59:28 +00:00
|
|
|
use PlayerState::*;
|
|
|
|
match *self {
|
|
|
|
Stopped => f.debug_struct("Stopped").finish(),
|
|
|
|
Loading {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
} => f
|
|
|
|
.debug_struct("Loading")
|
|
|
|
.field("track_id", &track_id)
|
|
|
|
.field("play_request_id", &play_request_id)
|
|
|
|
.finish(),
|
|
|
|
Paused {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
} => f
|
|
|
|
.debug_struct("Paused")
|
|
|
|
.field("track_id", &track_id)
|
|
|
|
.field("play_request_id", &play_request_id)
|
|
|
|
.finish(),
|
|
|
|
Playing {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
} => f
|
|
|
|
.debug_struct("Playing")
|
|
|
|
.field("track_id", &track_id)
|
|
|
|
.field("play_request_id", &play_request_id)
|
|
|
|
.finish(),
|
|
|
|
EndOfTrack {
|
|
|
|
track_id,
|
|
|
|
play_request_id,
|
|
|
|
..
|
|
|
|
} => f
|
|
|
|
.debug_struct("EndOfTrack")
|
|
|
|
.field("track_id", &track_id)
|
|
|
|
.field("play_request_id", &play_request_id)
|
|
|
|
.finish(),
|
|
|
|
Invalid => f.debug_struct("Invalid").finish(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-10 15:26:08 +00:00
|
|
|
struct Subfile<T: Read + Seek> {
|
|
|
|
stream: T,
|
|
|
|
offset: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Read + Seek> Subfile<T> {
|
|
|
|
pub fn new(mut stream: T, offset: u64) -> Subfile<T> {
|
2021-12-28 22:46:37 +00:00
|
|
|
let target = SeekFrom::Start(offset);
|
|
|
|
match stream.seek(target) {
|
|
|
|
Ok(pos) => {
|
|
|
|
if pos != offset {
|
|
|
|
error!(
|
|
|
|
"Subfile::new seeking to {:?} but position is now {:?}",
|
|
|
|
target, pos
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => error!("Subfile new Error: {}", e),
|
2021-09-20 17:29:12 +00:00
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
|
2021-03-10 21:39:01 +00:00
|
|
|
Subfile { stream, offset }
|
2018-02-10 15:26:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Read + Seek> Read for Subfile<T> {
|
2021-01-21 21:22:32 +00:00
|
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
2018-02-10 15:26:08 +00:00
|
|
|
self.stream.read(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Read + Seek> Seek for Subfile<T> {
|
2021-01-21 21:22:32 +00:00
|
|
|
fn seek(&mut self, mut pos: SeekFrom) -> io::Result<u64> {
|
2018-02-10 15:26:08 +00:00
|
|
|
pos = match pos {
|
|
|
|
SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset),
|
|
|
|
x => x,
|
|
|
|
};
|
|
|
|
|
2019-10-08 09:31:18 +00:00
|
|
|
let newpos = self.stream.seek(pos)?;
|
2021-12-28 22:46:37 +00:00
|
|
|
|
|
|
|
if newpos >= self.offset {
|
2018-02-10 15:26:08 +00:00
|
|
|
Ok(newpos - self.offset)
|
|
|
|
} else {
|
2021-12-28 22:46:37 +00:00
|
|
|
Err(io::Error::new(
|
|
|
|
io::ErrorKind::UnexpectedEof,
|
|
|
|
"newpos < self.offset",
|
|
|
|
))
|
2018-02-10 15:26:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|