librespot/playback/src/player.rs

1946 lines
68 KiB
Rust
Raw Normal View History

use std::cmp::max;
use std::future::Future;
use std::io::{self, Read, Seek, SeekFrom};
use std::pin::Pin;
use std::process::exit;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use std::{mem, thread};
use byteorder::{LittleEndian, ReadBytesExt};
2021-02-22 08:58:08 +00:00
use futures_util::stream::futures_unordered::FuturesUnordered;
use futures_util::{future, StreamExt, TryFutureExt};
use tokio::sync::{mpsc, oneshot};
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
use crate::audio::{
READ_AHEAD_BEFORE_PLAYBACK, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK,
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS,
2019-11-11 07:22:41 +00:00
};
use crate::audio_backend::Sink;
use crate::config::{Bitrate, NormalisationMethod, NormalisationType, PlayerConfig};
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
use crate::convert::Converter;
use crate::core::session::Session;
use crate::core::spotify_id::SpotifyId;
use crate::core::util::SeqGenerator;
2021-04-13 08:29:34 +00:00
use crate::decoder::{AudioDecoder, AudioError, AudioPacket, PassthroughDecoder, VorbisDecoder};
use crate::metadata::{AudioItem, FileFormat};
use crate::mixer::AudioFilter;
use crate::{NUM_CHANNELS, SAMPLES_PER_SECOND};
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
2015-09-01 11:20:37 +00:00
pub struct Player {
2021-01-21 21:22:32 +00:00
commands: Option<mpsc::UnboundedSender<PlayerCommand>>,
thread_handle: Option<thread::JoinHandle<()>>,
play_request_id_generator: SeqGenerator<u64>,
}
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,
config: PlayerConfig,
2021-01-21 21:22:32 +00:00
commands: mpsc::UnboundedReceiver<PlayerCommand>,
2017-01-29 14:11:20 +00:00
state: PlayerState,
preload: PlayerPreload,
sink: Box<dyn Sink>,
2020-03-10 12:26:01 +00:00
sink_status: SinkStatus,
sink_event_callback: Option<SinkEventCallback>,
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,
limiter_active: bool,
limiter_attack_counter: u32,
limiter_release_counter: u32,
limiter_peak_sample: f64,
limiter_factor: f64,
limiter_strength: f64,
}
2015-07-09 20:08:14 +00:00
enum PlayerCommand {
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>),
EmitVolumeSetEvent(u16),
}
2018-02-20 20:57:42 +00:00
#[derive(Debug, Clone)]
pub enum PlayerEvent {
// Fired when the player is stopped (e.g. by issuing a "stop" command to the player).
Stopped {
play_request_id: u64,
track_id: SpotifyId,
},
// 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.
Started {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
2018-02-20 20:57:42 +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,
},
// The player is delayed by loading a track.
Loading {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
},
// The player is preloading a track.
Preloading {
track_id: SpotifyId,
},
// 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
Playing {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
duration_ms: u32,
},
// The player entered a paused state.
Paused {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
duration_ms: u32,
},
// 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.
TimeToPreloadNextTrack {
play_request_id: u64,
track_id: SpotifyId,
2020-05-13 09:49:26 +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)
EndOfTrack {
2020-05-13 09:49:26 +00:00
play_request_id: u64,
track_id: SpotifyId,
},
// The player was unable to load the requested track.
Unavailable {
play_request_id: u64,
2018-02-20 20:57:42 +00:00
track_id: SpotifyId,
2018-02-26 01:50:41 +00:00
},
// The mixer volume was set to a new level.
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, ..
}
| 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),
Changed { .. } | Preloading { .. } | VolumeSet { .. } => None,
}
}
2018-02-20 20:57:42 +00:00
}
2021-01-21 21:22:32 +00:00
pub type PlayerEventChannel = mpsc::UnboundedReceiver<PlayerEvent>;
pub fn db_to_ratio(db: f64) -> f64 {
f64::powf(10.0, db / DB_VOLTAGE_RATIO)
}
pub fn ratio_to_db(ratio: f64) -> f64 {
ratio.log10() * DB_VOLTAGE_RATIO
}
2018-02-23 19:08:20 +00:00
#[derive(Clone, Copy, Debug)]
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,
}
impl NormalisationData {
2021-01-21 21:22:32 +00:00
fn parse_from_file<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET))?;
2018-02-23 19:08:20 +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
let r = NormalisationData {
2021-03-10 21:39:01 +00:00
track_gain_db,
track_peak,
album_gain_db,
album_peak,
};
Ok(r)
2018-02-23 19:08:20 +00:00
}
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f64 {
if !config.normalisation {
return 1.0;
}
let [gain_db, gain_peak] = match config.normalisation_type {
NormalisationType::Album => [data.album_gain_db, data.album_peak],
NormalisationType::Track => [data.track_gain_db, data.track_peak],
};
2018-02-23 19:08:20 +00:00
let normalisation_power = gain_db as f64 + config.normalisation_pregain;
let mut normalisation_factor = db_to_ratio(normalisation_power);
if normalisation_factor * gain_peak as f64 > config.normalisation_threshold {
let limited_normalisation_factor = config.normalisation_threshold / gain_peak as f64;
let limited_normalisation_power = ratio_to_db(limited_normalisation_factor);
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);
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
}
debug!("Normalisation Data: {:?}", data);
debug!("Normalisation Factor: {:.2}%", normalisation_factor * 100.0);
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,
audio_filter: Option<Box<dyn AudioFilter + Send>>,
2018-02-26 01:50:41 +00:00
sink_builder: F,
) -> (Player, PlayerEventChannel)
where
F: FnOnce() -> Box<dyn Sink> + Send + 'static,
{
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let (event_sender, event_receiver) = mpsc::unbounded_channel();
if config.normalisation {
debug!("Normalisation Type: {:?}", config.normalisation_type);
debug!(
"Normalisation Pregain: {:.1} dB",
config.normalisation_pregain
);
debug!(
"Normalisation Threshold: {:.1} dBFS",
ratio_to_db(config.normalisation_threshold)
);
debug!("Normalisation Method: {:?}", config.normalisation_method);
if config.normalisation_method == NormalisationMethod::Dynamic {
debug!("Normalisation Attack: {:?}", config.normalisation_attack);
debug!("Normalisation Release: {:?}", config.normalisation_release);
debug!("Normalisation Knee: {:?}", config.normalisation_knee);
}
}
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);
let internal = PlayerInternal {
2021-03-01 02:37:22 +00:00
session,
config,
commands: cmd_rx,
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,
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,
limiter_active: false,
limiter_attack_counter: 0,
limiter_release_counter: 0,
limiter_peak_sample: 0.0,
limiter_factor: 1.0,
limiter_strength: 0.0,
};
2017-01-29 14:11:20 +00:00
// While PlayerInternal is written as a future, it still contains blocking code.
// It must be run by using block_on() in a dedicated thread.
futures_executor::block_on(internal);
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),
thread_handle: Some(handle),
play_request_id_generator: SeqGenerator::new(0),
2018-02-26 01:50:41 +00:00
},
event_receiver,
)
}
2015-07-09 20:08:14 +00:00
fn command(&self, cmd: PlayerCommand) {
self.commands.as_ref().unwrap().send(cmd).unwrap();
}
2016-01-20 14:11:49 +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));
}
pub fn get_player_event_channel(&self) -> PlayerEventChannel {
let (event_sender, event_receiver) = mpsc::unbounded_channel();
self.command(PlayerCommand::AddEventSender(event_sender));
event_receiver
}
pub async fn await_end_of_track(&self) {
2021-02-12 17:19:04 +00:00
let mut channel = self.get_player_event_channel();
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));
}
pub fn emit_volume_set_event(&self, volume: u16) {
self.command(PlayerCommand::EmitVolumeSetEvent(volume));
}
}
2015-06-23 14:38:29 +00:00
impl Drop for Player {
fn drop(&mut self) {
debug!("Shutting down player thread ...");
self.commands = None;
if let Some(handle) = self.thread_handle.take() {
match handle.join() {
Ok(_) => (),
Err(_) => error!("Player thread panicked!"),
}
}
}
}
struct PlayerLoadedTrackData {
decoder: Decoder,
normalisation_factor: f64,
stream_loader_controller: StreamLoaderController,
bytes_per_second: usize,
duration_ms: u32,
2020-02-02 00:07:05 +00:00
stream_position_pcm: u64,
}
enum PlayerPreload {
None,
Loading {
track_id: SpotifyId,
2021-01-22 21:51:41 +00:00
loader: Pin<Box<dyn Future<Output = Result<PlayerLoadedTrackData, ()>> + Send>>,
},
Ready {
track_id: SpotifyId,
2021-01-22 21:51:41 +00:00
loaded_track: Box<PlayerLoadedTrackData>,
},
}
2021-01-07 06:42:38 +00:00
type Decoder = Box<dyn AudioDecoder + Send>;
2017-01-29 14:11:20 +00:00
enum PlayerState {
Stopped,
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>>,
},
2017-01-29 14:11:20 +00:00
Paused {
track_id: SpotifyId,
play_request_id: u64,
2017-01-29 14:11:20 +00:00
decoder: Decoder,
normalisation_factor: f64,
2019-11-01 19:46:28 +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,
suggested_to_preload_next_track: bool,
2017-01-29 14:11:20 +00:00
},
Playing {
track_id: SpotifyId,
play_request_id: u64,
2017-01-29 14:11:20 +00:00
decoder: Decoder,
normalisation_factor: f64,
2019-11-01 19:46:28 +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,
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,
play_request_id: u64,
loaded_track: PlayerLoadedTrackData,
2018-02-26 01:50:41 +00:00
},
2017-01-29 14:11:20 +00:00
Invalid,
}
2017-01-29 14:11:20 +00:00
impl PlayerState {
fn is_playing(&self) -> bool {
use self::PlayerState::*;
match *self {
Stopped | EndOfTrack { .. } | Paused { .. } | Loading { .. } => false,
2017-01-29 14:11:20 +00:00
Playing { .. } => true,
Invalid => panic!("invalid state"),
}
}
#[allow(dead_code)]
fn is_stopped(&self) -> bool {
use self::PlayerState::*;
2021-03-01 02:37:22 +00:00
matches!(self, Stopped)
}
fn is_loading(&self) -> bool {
use self::PlayerState::*;
2021-03-01 02:37:22 +00:00
matches!(self, Loading { .. })
}
2017-01-29 14:11:20 +00:00
fn decoder(&mut self) -> Option<&mut Decoder> {
use self::PlayerState::*;
2017-01-29 14:11:20 +00:00
match *self {
Stopped | EndOfTrack { .. } | Loading { .. } => None,
Paused {
ref mut decoder, ..
}
| Playing {
ref mut decoder, ..
} => Some(decoder),
2017-01-29 14:11:20 +00:00
Invalid => panic!("invalid state"),
}
2017-01-29 14:11:20 +00:00
}
2019-11-01 19:46:28 +00:00
fn stream_loader_controller(&mut self) -> Option<&mut StreamLoaderController> {
use self::PlayerState::*;
match *self {
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),
2019-11-01 19:46:28 +00:00
Invalid => panic!("invalid state"),
}
}
fn playing_to_end_of_track(&mut self) {
use self::PlayerState::*;
2018-02-23 19:16:03 +00:00
match mem::replace(self, Invalid) {
2018-02-26 01:50:41 +00:00
Playing {
track_id,
play_request_id,
decoder,
duration_ms,
bytes_per_second,
normalisation_factor,
stream_loader_controller,
stream_position_pcm,
2018-02-26 01:50:41 +00:00
..
} => {
*self = EndOfTrack {
track_id,
play_request_id,
loaded_track: PlayerLoadedTrackData {
decoder,
normalisation_factor,
stream_loader_controller,
2021-05-09 10:59:34 +00:00
bytes_per_second,
duration_ms,
stream_position_pcm,
},
};
2018-02-26 01:50:41 +00:00
}
_ => panic!("Called playing_to_end_of_track in non-playing state."),
}
2017-01-29 14:11:20 +00:00
}
2017-01-29 14:11:20 +00:00
fn paused_to_playing(&mut self) {
use self::PlayerState::*;
match ::std::mem::replace(self, Invalid) {
2018-02-26 01:50:41 +00:00
Paused {
track_id,
play_request_id,
2018-02-26 01:50:41 +00:00
decoder,
normalisation_factor,
2019-11-01 19:46:28 +00:00
stream_loader_controller,
duration_ms,
2019-11-11 07:22:41 +00:00
bytes_per_second,
2020-02-02 00:07:05 +00:00
stream_position_pcm,
suggested_to_preload_next_track,
2018-02-26 01:50:41 +00:00
} => {
2017-01-29 14:11:20 +00:00
*self = Playing {
track_id,
play_request_id,
decoder,
normalisation_factor,
stream_loader_controller,
duration_ms,
bytes_per_second,
2020-02-02 00:07:05 +00:00
stream_position_pcm,
reported_nominal_start_time: None,
suggested_to_preload_next_track,
2017-01-29 14:11:20 +00:00
};
}
_ => panic!("invalid state"),
}
}
2017-01-29 14:11:20 +00:00
fn playing_to_paused(&mut self) {
use self::PlayerState::*;
match ::std::mem::replace(self, Invalid) {
2018-02-26 01:50:41 +00:00
Playing {
track_id,
play_request_id,
2018-02-26 01:50:41 +00:00
decoder,
normalisation_factor,
2019-11-01 19:46:28 +00:00
stream_loader_controller,
duration_ms,
bytes_per_second,
2020-02-02 00:07:05 +00:00
stream_position_pcm,
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 {
track_id,
play_request_id,
decoder,
normalisation_factor,
stream_loader_controller,
duration_ms,
bytes_per_second,
2020-02-02 00:07:05 +00:00
stream_position_pcm,
suggested_to_preload_next_track,
2017-01-29 14:11:20 +00:00
};
}
_ => panic!("invalid state"),
}
}
}
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> {
if audio.available {
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-02-22 08:58:08 +00:00
.map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id))
.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()))
.filter(|x| future::ready(x.available))
.next()
.await
} else {
2021-01-21 21:22:32 +00:00
None
}
}
fn stream_data_rate(&self, format: FileFormat) -> usize {
match format {
FileFormat::OGG_VORBIS_96 => 12 * 1024,
FileFormat::OGG_VORBIS_160 => 20 * 1024,
FileFormat::OGG_VORBIS_320 => 40 * 1024,
FileFormat::MP3_256 => 32 * 1024,
FileFormat::MP3_320 => 40 * 1024,
FileFormat::MP3_160 => 20 * 1024,
FileFormat::MP3_96 => 12 * 1024,
FileFormat::MP3_160_ENC => 20 * 1024,
FileFormat::AAC_24 => 3 * 1024,
FileFormat::AAC_48 => 6 * 1024,
FileFormat::FLAC_FLAC => 112 * 1024, // assume 900 kbps on average
}
}
2021-01-21 21:22:32 +00:00
async fn load_track(
&self,
spotify_id: SpotifyId,
position_ms: u32,
) -> Option<PlayerLoadedTrackData> {
let audio = match AudioItem::get_audio_item(&self.session, spotify_id).await {
Ok(audio) => audio,
Err(_) => {
error!("Unable to load audio item.");
return None;
}
};
info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri);
2021-02-22 08:58:08 +00:00
let audio = match self.find_available_alternative(audio).await {
Some(audio) => audio,
None => {
2021-02-22 08:58:08 +00:00
warn!("<{}> is not available", spotify_id.to_uri());
return None;
}
};
assert!(audio.duration >= 0);
let duration_ms = audio.duration as u32;
// (Most) podcasts seem to support only 96 bit Vorbis, so fall back to it
let formats = match self.config.bitrate {
Bitrate::Bitrate96 => [
FileFormat::OGG_VORBIS_96,
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_320,
],
Bitrate::Bitrate160 => [
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_96,
FileFormat::OGG_VORBIS_320,
],
Bitrate::Bitrate320 => [
FileFormat::OGG_VORBIS_320,
FileFormat::OGG_VORBIS_160,
FileFormat::OGG_VORBIS_96,
],
};
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,
None => {
2021-02-09 08:15:55 +00:00
warn!("<{}> is not available in any supported format", audio.name);
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;
// 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,
);
let encrypted_file = match encrypted_file.await {
Ok(encrypted_file) => encrypted_file,
Err(_) => {
error!("Unable to load encrypted file.");
return None;
}
};
let is_cached = encrypted_file.is_cached();
2021-03-01 02:37:22 +00:00
let stream_loader_controller = encrypted_file.get_stream_loader_controller();
if play_from_beginning {
// No need to seek -> we stream from the beginning
stream_loader_controller.set_stream_mode();
} else {
// we need to seek -> we set stream mode after the initial seek.
stream_loader_controller.set_random_access_mode();
}
2021-02-13 09:29:00 +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,
Err(_) => {
error!("Unable to load decryption key");
return None;
}
};
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file)
{
Ok(normalisation_data) => {
NormalisationData::get_factor(&self.config, normalisation_data)
}
Err(_) => {
warn!("Unable to extract normalisation data, using default value.");
1.0
}
};
let audio_file = Subfile::new(decrypted_file, 0xa7);
let result = if self.config.passthrough {
match PassthroughDecoder::new(audio_file) {
Ok(result) => Ok(Box::new(result) as Decoder),
Err(e) => Err(AudioError::PassthroughError(e)),
}
} else {
match VorbisDecoder::new(audio_file) {
Ok(result) => Ok(Box::new(result) as Decoder),
Err(e) => Err(AudioError::VorbisError(e)),
}
};
2021-01-07 06:42:38 +00:00
let mut decoder = match result {
Ok(decoder) => decoder,
Err(e) if is_cached => {
warn!(
"Unable to read cached audio file: {}. Trying to download it.",
e
);
2021-04-10 17:00:25 +00:00
if self
.session
.cache()
.expect("If the audio file is cached, a cache should exist")
.remove_file(file_id)
.is_err()
{
return None;
}
// Just try it again
continue;
}
Err(e) => {
error!("Unable to read audio file: {}", e);
2021-02-02 01:18:58 +00:00
return None;
}
};
if position_ms != 0 {
if let Err(err) = decoder.seek(position_ms as i64) {
error!("Vorbis error: {}", err);
}
stream_loader_controller.set_stream_mode();
}
let stream_position_pcm = PlayerInternal::position_ms_to_pcm(position_ms);
info!("<{}> ({} ms) loaded", audio.name, audio.duration);
return Some(PlayerLoadedTrackData {
decoder,
normalisation_factor,
stream_loader_controller,
bytes_per_second,
duration_ms,
stream_position_pcm,
});
}
}
}
impl Future for PlayerInternal {
2021-01-21 21:22:32 +00:00
type Output = ();
2021-01-21 21:22:32 +00:00
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// While this is written as a future, it still contains blocking code.
// It must be run on its own thread.
let passthrough = self.config.passthrough;
loop {
let mut all_futures_completed_or_not_ready = true;
// process commands that were sent to us
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)) => {
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,
};
2017-01-29 14:11:20 +00:00
if let Some(cmd) = cmd {
self.handle_command(cmd);
}
// 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)) => {
self.start_playback(
track_id,
play_request_id,
loaded_track,
start_playback,
);
if let PlayerState::Loading { .. } = self.state {
panic!("The state wasn't changed by start_playback()");
}
}
2021-01-21 21:22:32 +00:00
Poll::Ready(Err(_)) => {
2020-05-13 09:49:26 +00:00
warn!("Unable to load <{:?}>\nSkipping to next track", track_id);
assert!(self.state.is_loading());
self.send_event(PlayerEvent::EndOfTrack {
track_id,
play_request_id,
})
}
2021-01-21 21:22:32 +00:00
Poll::Pending => (),
}
}
// 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)) => {
self.send_event(PlayerEvent::Preloading { track_id });
self.preload = PlayerPreload::Ready {
track_id,
2021-01-22 21:51:41 +00:00
loaded_track: Box::new(loaded_track),
};
}
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.
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 {
track_id,
play_request_id,
});
}
}
2021-01-21 21:22:32 +00:00
Poll::Pending => (),
}
}
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
if let PlayerState::Playing {
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,
ref mut reported_nominal_start_time,
duration_ms,
2018-02-26 01:50:41 +00:00
..
} = self.state
{
let packet = decoder.next_packet().expect("Vorbis error");
if !passthrough {
2021-01-07 06:42:38 +00:00
if let Some(ref packet) = packet {
*stream_position_pcm +=
(packet.samples().len() / NUM_CHANNELS as usize) as u64;
2021-01-07 06:42:38 +00:00
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.
2021-01-07 06:42:38 +00:00
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
}
2021-01-07 06:42:38 +00:00
};
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,
});
}
}
2021-01-07 06:42:38 +00:00
} else {
// position, even if irrelevant, must be set so that seek() is called
*stream_position_pcm = duration_ms.into();
}
self.handle_packet(packet, normalisation_factor);
} else {
unreachable!();
};
2017-01-29 14:11:20 +00:00
}
2018-04-21 15:46:29 +00:00
if let PlayerState::Playing {
track_id,
play_request_id,
duration_ms,
2020-02-02 00:07:05 +00:00
stream_position_pcm,
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,
ref mut stream_loader_controller,
ref mut suggested_to_preload_next_track,
..
} = self.state
{
if (!*suggested_to_preload_next_track)
&& ((duration_ms as i64 - Self::position_pcm_to_ms(stream_position_pcm) as i64)
< PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS as i64)
&& stream_loader_controller.range_to_end_available()
{
*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(());
}
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
}
}
}
2016-01-02 15:19:39 +00:00
impl PlayerInternal {
2020-02-02 00:07:05 +00:00
fn position_pcm_to_ms(position_pcm: u64) -> u32 {
(position_pcm * 10 / 441) as u32
}
fn position_ms_to_pcm(position_ms: u32) -> u64 {
position_ms as u64 * 441 / 10
}
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,
Err(err) => {
error!("Fatal error, could not start audio sink: {}", err);
exit(1);
}
2020-02-02 23:11:27 +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 ==");
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);
}
}
Err(err) => {
error!("Fatal error, could not stop audio sink: {}", err);
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 => (),
}
}
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,
..
}
| PlayerState::Loading {
track_id,
play_request_id,
..
} => {
2020-03-10 12:26:01 +00:00
self.ensure_sink_stopped(false);
self.send_event(PlayerEvent::Stopped {
track_id,
play_request_id,
});
self.state = PlayerState::Stopped;
}
PlayerState::Stopped => (),
PlayerState::Invalid => panic!("invalid state"),
}
}
fn handle_play(&mut self) {
if let PlayerState::Paused {
track_id,
play_request_id,
stream_position_pcm,
duration_ms,
..
} = self.state
{
self.state.paused_to_playing();
let position_ms = Self::position_pcm_to_ms(stream_position_pcm);
self.send_event(PlayerEvent::Playing {
track_id,
play_request_id,
position_ms,
duration_ms,
});
2020-02-02 23:11:27 +00:00
self.ensure_sink_running();
} else {
warn!("Player::play called from invalid state");
}
}
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);
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 {
warn!("Player::pause called from invalid state");
}
}
fn handle_packet(&mut self, packet: Option<AudioPacket>, normalisation_factor: f64) {
2017-01-29 14:11:20 +00:00
match packet {
Some(mut packet) => {
2021-01-07 06:42:38 +00:00
if !packet.is_empty() {
if let AudioPacket::Samples(ref mut data) = packet {
if let Some(ref editor) = self.audio_filter {
editor.modify_stream(data)
}
if self.config.normalisation
&& !(f64::abs(normalisation_factor - 1.0) <= f64::EPSILON
2021-04-16 13:54:38 +00:00
&& self.config.normalisation_method == NormalisationMethod::Basic)
{
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:
// - > 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
+ f64::powf(
shaped_limiter_strength
/ (1.0 - shaped_limiter_strength),
-self.config.normalisation_knee,
));
}
actual_normalisation_factor =
(1.0 - shaped_limiter_strength) * normalisation_factor
+ shaped_limiter_strength * self.limiter_factor;
};
// 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;
// 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.
let abs_sample = f64::abs(*sample * normalisation_factor);
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.
self.limiter_attack_counter = (((samples_per_second
* normalisation_release)
- limiter_release_counter)
/ (normalisation_release / normalisation_attack))
as u32;
self.limiter_release_counter = 0;
}
self.limiter_attack_counter =
self.limiter_attack_counter.saturating_add(1);
self.limiter_strength = limiter_attack_counter
/ (samples_per_second * normalisation_attack);
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.
self.limiter_release_counter = (((samples_per_second
* normalisation_attack)
- limiter_attack_counter)
* (normalisation_release / normalisation_attack))
as u32;
self.limiter_attack_counter = 0;
}
self.limiter_release_counter =
self.limiter_release_counter.saturating_add(1);
if self.limiter_release_counter
> (samples_per_second * normalisation_release) as u32
{
self.reset_limiter();
} else {
self.limiter_strength = ((samples_per_second
* normalisation_release)
- limiter_release_counter)
/ (samples_per_second * normalisation_release);
}
}
}
*sample *= actual_normalisation_factor;
2021-03-02 19:21:05 +00:00
// Extremely sharp attacks, however unlikely, *may* still clip and provide
// undefined results, so strictly enforce output within [-1.0, 1.0].
2021-03-02 19:21:05 +00:00
if *sample < -1.0 {
*sample = -1.0;
} else if *sample > 1.0 {
*sample = 1.0;
}
2021-01-07 06:42:38 +00:00
}
}
2018-02-23 19:08:20 +00:00
}
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
if let Err(err) = self.sink.write(&packet, &mut self.converter) {
error!("Fatal error, could not write audio to audio sink: {}", err);
exit(1);
}
}
2017-01-29 14:11:20 +00:00
}
None => {
self.state.playing_to_end_of_track();
if let PlayerState::EndOfTrack {
track_id,
play_request_id,
..
} = self.state
{
self.send_event(PlayerEvent::EndOfTrack {
track_id,
play_request_id,
})
} else {
unreachable!();
}
}
}
}
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;
}
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);
if start_playback {
2020-02-02 23:11:27 +00:00
self.ensure_sink_running();
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,
decoder: loaded_track.decoder,
normalisation_factor: loaded_track.normalisation_factor,
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,
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);
self.state = PlayerState::Paused {
2021-03-10 21:39:01 +00:00
track_id,
play_request_id,
decoder: loaded_track.decoder,
normalisation_factor: loaded_track.normalisation_factor,
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,
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
}
}
fn handle_command_load(
&mut self,
track_id: SpotifyId,
play_request_id: u64,
play: bool,
position_ms: u32,
) {
if !self.config.gapless {
2020-03-10 12:53:58 +00:00
self.ensure_sink_stopped(play);
}
// 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,
new_track_id: track_id,
}),
PlayerState::Stopped => self.send_event(PlayerEvent::Started {
track_id,
play_request_id,
position_ms,
}),
PlayerState::Invalid { .. } => panic!("Player is in an invalid state."),
}
// Now we check at different positions whether we already have a pre-loaded version
// of this track somewhere. If so, use it and return.
// 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 {
let mut loaded_track = match mem::replace(&mut self.state, PlayerState::Invalid) {
PlayerState::EndOfTrack { loaded_track, .. } => loaded_track,
_ => unreachable!(),
};
if Self::position_ms_to_pcm(position_ms) != loaded_track.stream_position_pcm {
loaded_track
.stream_loader_controller
.set_random_access_mode();
let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking.
// But most likely the track is fully
// loaded already because we played
// to the end of it.
loaded_track.stream_loader_controller.set_stream_mode();
loaded_track.stream_position_pcm = Self::position_ms_to_pcm(position_ms);
}
self.preload = PlayerPreload::None;
2020-03-20 06:31:18 +00:00
self.start_playback(track_id, play_request_id, loaded_track, play);
if let PlayerState::Invalid = self.state {
panic!("start_playback() hasn't set a valid player state.");
}
return;
}
}
// 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.
if Self::position_ms_to_pcm(position_ms) != *stream_position_pcm {
stream_loader_controller.set_random_access_mode();
let _ = decoder.seek(position_ms as i64); // This may be blocking.
stream_loader_controller.set_stream_mode();
*stream_position_pcm = Self::position_ms_to_pcm(position_ms);
}
2017-01-29 14:11:20 +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);
if let PlayerState::Playing {
stream_position_pcm,
decoder,
stream_loader_controller,
bytes_per_second,
duration_ms,
normalisation_factor,
..
}
| PlayerState::Paused {
stream_position_pcm,
decoder,
stream_loader_controller,
bytes_per_second,
duration_ms,
normalisation_factor,
..
} = old_state
{
let loaded_track = PlayerLoadedTrackData {
decoder,
normalisation_factor,
stream_loader_controller,
bytes_per_second,
duration_ms,
stream_position_pcm,
};
self.preload = PlayerPreload::None;
self.start_playback(track_id, play_request_id, loaded_track, play);
if let PlayerState::Invalid = self.state {
panic!("start_playback() hasn't set a valid player state.");
}
return;
} else {
unreachable!();
}
}
}
// 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
{
if Self::position_ms_to_pcm(position_ms) != loaded_track.stream_position_pcm {
loaded_track
.stream_loader_controller
.set_random_access_mode();
let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking
loaded_track.stream_loader_controller.set_stream_mode();
}
2021-01-22 21:51:41 +00:00
self.start_playback(track_id, play_request_id, *loaded_track, play);
return;
} else {
unreachable!();
}
}
}
// 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);
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
}
} else {
None
}
} else {
None
};
self.preload = PlayerPreload::None;
2020-02-02 23:11:27 +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)));
// Set ourselves to a loading state.
self.state = PlayerState::Loading {
track_id,
play_request_id,
start_playback: play,
loader,
};
}
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
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;
}
}
// schedule the preload of the current track if desired.
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),
}
}
}
fn handle_command_seek(&mut self, position_ms: u32) {
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() {
match decoder.seek(position_ms as i64) {
Ok(_) => {
if let PlayerState::Playing {
ref mut stream_position_pcm,
..
2017-01-29 14:11:20 +00:00
}
| PlayerState::Paused {
ref mut stream_position_pcm,
..
} = self.state
{
*stream_position_pcm = Self::position_ms_to_pcm(position_ms);
}
}
Err(err) => error!("Vorbis error: {:?}", err),
2017-01-29 14:11:20 +00:00
}
} else {
warn!("Player::seek called from invalid state");
}
// 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
// ensure we have a bit of a buffer of downloaded data
self.preload_data_before_playback();
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,
});
}
}
fn handle_command(&mut self, cmd: PlayerCommand) {
debug!("command={:?}", cmd);
match cmd {
PlayerCommand::Load {
track_id,
play_request_id,
play,
position_ms,
} => self.handle_command_load(track_id, play_request_id, play, position_ms),
PlayerCommand::Preload { track_id } => self.handle_command_preload(track_id),
PlayerCommand::Seek(position_ms) => self.handle_command_seek(position_ms),
PlayerCommand::Play => self.handle_play(),
PlayerCommand::Pause => self.handle_pause(),
PlayerCommand::Stop => self.handle_player_stop(),
2015-06-23 14:38:29 +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,
PlayerCommand::EmitVolumeSetEvent(volume) => {
self.send_event(PlayerEvent::VolumeSet { volume })
}
2017-01-29 14:11:20 +00:00
}
}
2015-07-09 20:08:14 +00:00
fn send_event(&mut self, event: PlayerEvent) {
let mut index = 0;
while index < self.event_senders.len() {
match self.event_senders[index].send(event.clone()) {
Ok(_) => index += 1,
Err(_) => {
self.event_senders.remove(index);
}
}
2019-11-01 19:46:28 +00:00
}
}
fn load_track(
2019-11-11 07:22:41 +00:00
&self,
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 {
// 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.
let loader = PlayerTrackLoader {
session: self.session.clone(),
config: self.config.clone(),
};
2019-11-01 19:48:18 +00:00
let (result_tx, result_rx) = oneshot::channel();
2021-01-21 21:22:32 +00:00
std::thread::spawn(move || {
let data = futures_executor::block_on(loader.load_track(spotify_id, position_ms));
if let Some(data) = data {
let _ = result_tx.send(data);
}
});
2017-01-29 14:11:20 +00:00
result_rx.map_err(|_| ())
}
fn preload_data_before_playback(&mut self) {
if let PlayerState::Playing {
2020-01-17 17:11:07 +00:00
bytes_per_second,
ref mut stream_loader_controller,
..
} = self.state
{
// Request our read ahead range
let request_data_length = max(
(READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS
* 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,
);
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
* 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,
);
stream_loader_controller.fetch_next_blocking(wait_for_data_length);
2019-11-01 19:46:28 +00:00
}
}
2015-07-09 20:08:14 +00:00
}
2017-01-29 14:11:20 +00:00
impl Drop for PlayerInternal {
fn drop(&mut self) {
debug!("drop PlayerInternal[{}]", self.session.session_id());
}
}
impl ::std::fmt::Debug for PlayerCommand {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
PlayerCommand::Load {
track_id,
play,
position_ms,
..
} => f
2018-07-03 11:08:42 +00:00
.debug_tuple("Load")
.field(&track_id)
2018-02-26 01:50:41 +00:00
.field(&play)
.field(&position_ms)
2018-02-26 01:50:41 +00:00
.finish(),
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(),
PlayerCommand::AddEventSender(_) => f.debug_tuple("AddEventSender").finish(),
2020-03-10 12:26:01 +00:00
PlayerCommand::SetSinkEventCallback(_) => {
f.debug_tuple("SetSinkEventCallback").finish()
}
PlayerCommand::EmitVolumeSetEvent(volume) => {
f.debug_tuple("VolumeSet").field(&volume).finish()
}
}
}
}
2018-02-10 15:26:08 +00:00
impl ::std::fmt::Debug for PlayerState {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
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> {
stream.seek(SeekFrom::Start(offset)).unwrap();
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,
};
let newpos = self.stream.seek(pos)?;
2018-02-10 15:26:08 +00:00
if newpos > self.offset {
Ok(newpos - self.offset)
} else {
Ok(0)
}
}
}