librespot/playback/src/player.rs

2447 lines
87 KiB
Rust
Raw Normal View History

use std::{
collections::HashMap,
fmt,
future::Future,
io::{self, Read, Seek, SeekFrom},
mem,
pin::Pin,
process::exit,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
task::{Context, Poll},
thread,
time::{Duration, Instant},
};
use futures_util::{
future, future::FusedFuture, stream::futures_unordered::FuturesUnordered, StreamExt,
TryFutureExt,
};
use parking_lot::Mutex;
use symphonia::core::io::MediaSource;
use tokio::sync::{mpsc, oneshot};
use crate::{
audio::{AudioDecrypt, AudioFetchParams, AudioFile, StreamLoaderController},
audio_backend::Sink,
config::{Bitrate, NormalisationMethod, NormalisationType, PlayerConfig},
convert::Converter,
core::{util::SeqGenerator, Error, Session, SpotifyId},
2022-01-25 19:46:10 +00:00
decoder::{AudioDecoder, AudioPacket, AudioPacketPosition, SymphoniaDecoder},
metadata::audio::{AudioFileFormat, AudioFiles, AudioItem},
mixer::VolumeGetter,
2019-11-11 07:22:41 +00:00
};
2022-01-25 19:46:10 +00:00
#[cfg(feature = "passthrough-decoder")]
use crate::decoder::PassthroughDecoder;
use crate::SAMPLES_PER_SECOND;
const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000;
pub const DB_VOLTAGE_RATIO: f64 = 20.0;
pub const PCM_AT_0DBFS: f64 = 1.0;
// Spotify inserts a custom Ogg packet at the start with custom metadata values, that you would
// otherwise expect in Vorbis comments. This packet isn't well-formed and players may balk at it.
const SPOTIFY_OGG_HEADER_END: u64 = 0xa7;
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>>,
thread_handle: Option<thread::JoinHandle<()>>,
}
2015-06-23 14:38:29 +00:00
2022-08-22 20:38:19 +00:00
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
2020-03-10 12:26:01 +00:00
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>,
load_handles: Arc<Mutex<HashMap<thread::ThreadId, thread::JoinHandle<()>>>>,
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>,
volume_getter: Box<dyn VolumeGetter + 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,
normalisation_integrator: f64,
normalisation_peak: f64,
auto_normalise_as_album: bool,
player_id: usize,
2023-06-13 14:30:56 +00:00
play_request_id_generator: SeqGenerator<u64>,
}
static PLAYER_COUNTER: AtomicUsize = AtomicUsize::new(0);
2015-07-09 20:08:14 +00:00
enum PlayerCommand {
Load {
track_id: SpotifyId,
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),
2023-06-13 14:52:17 +00:00
SetSession(Session),
2021-01-21 21:22:32 +00:00
AddEventSender(mpsc::UnboundedSender<PlayerEvent>),
2020-03-10 12:26:01 +00:00
SetSinkEventCallback(Option<SinkEventCallback>),
EmitVolumeChangedEvent(u16),
SetAutoNormaliseAsAlbum(bool),
EmitSessionDisconnectedEvent {
connection_id: String,
user_name: String,
},
EmitSessionConnectedEvent {
connection_id: String,
user_name: String,
},
EmitSessionClientChangedEvent {
client_id: String,
client_name: String,
client_brand_name: String,
client_model_name: String,
},
EmitFilterExplicitContentChangedEvent(bool),
EmitShuffleChangedEvent(bool),
EmitRepeatChangedEvent(bool),
EmitAutoPlayChangedEvent(bool),
}
2018-02-20 20:57:42 +00:00
#[derive(Debug, Clone)]
pub enum PlayerEvent {
2023-06-13 14:30:56 +00:00
// Play request id changed
PlayRequestIdChanged {
play_request_id: u64,
},
// 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 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,
},
// The player entered a paused state.
Paused {
play_request_id: u64,
track_id: SpotifyId,
position_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.
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.
VolumeChanged {
volume: u16,
},
PositionCorrection {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
},
Seeked {
play_request_id: u64,
track_id: SpotifyId,
position_ms: u32,
},
TrackChanged {
audio_item: Box<AudioItem>,
},
SessionConnected {
connection_id: String,
user_name: String,
},
SessionDisconnected {
connection_id: String,
user_name: String,
},
SessionClientChanged {
client_id: String,
client_name: String,
client_brand_name: String,
client_model_name: String,
},
ShuffleChanged {
shuffle: bool,
},
RepeatChanged {
repeat: bool,
},
AutoPlayChanged {
auto_play: bool,
},
FilterExplicitContentChanged {
filter: bool,
},
}
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, ..
}
| Playing {
play_request_id, ..
}
| TimeToPreloadNextTrack {
play_request_id, ..
}
| EndOfTrack {
play_request_id, ..
}
| Paused {
play_request_id, ..
}
| Stopped {
play_request_id, ..
}
| PositionCorrection {
play_request_id, ..
}
| Seeked {
play_request_id, ..
} => Some(*play_request_id),
_ => 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
}
pub fn duration_to_coefficient(duration: Duration) -> f64 {
f64::exp(-1.0 / (duration.as_secs_f64() * SAMPLES_PER_SECOND as f64))
}
pub fn coefficient_to_duration(coefficient: f64) -> Duration {
Duration::from_secs_f64(-1.0 / f64::ln(coefficient) / SAMPLES_PER_SECOND as f64)
}
2018-02-23 19:08:20 +00:00
#[derive(Clone, Copy, Debug)]
pub struct NormalisationData {
// Spotify provides these as `f32`, but audio metadata can contain up to `f64`.
// Also, this negates the need for casting during sample processing.
pub track_gain_db: f64,
pub track_peak: f64,
pub album_gain_db: f64,
pub album_peak: f64,
2018-02-23 19:08:20 +00:00
}
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 {
fn parse_from_ogg<T: Read + Seek>(mut file: T) -> io::Result<NormalisationData> {
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
const NORMALISATION_DATA_SIZE: usize = 16;
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
let mut buf = [0u8; NORMALISATION_DATA_SIZE];
file.read_exact(&mut buf)?;
2018-02-23 19:08:20 +00:00
let track_gain_db = f32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as f64;
let track_peak = f32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]) as f64;
let album_gain_db = f32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]) as f64;
let album_peak = f32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]) as f64;
Ok(Self {
2021-03-10 21:39:01 +00:00
track_gain_db,
track_peak,
album_gain_db,
album_peak,
})
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) = if config.normalisation_type == NormalisationType::Album {
(data.album_gain_db, data.album_peak)
} else {
(data.track_gain_db, data.track_peak)
};
2018-02-23 19:08:20 +00:00
// As per the ReplayGain 1.0 & 2.0 (proposed) spec:
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification#Clipping_prevention
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Clipping_prevention
let normalisation_factor = if config.normalisation_method == NormalisationMethod::Basic {
// For Basic Normalisation, factor = min(ratio of (ReplayGain + PreGain), 1.0 / peak level).
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification#Peak_amplitude
// https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Peak_amplitude
// We then limit that to 1.0 as not to exceed dBFS (0.0 dB).
let factor = f64::min(
db_to_ratio(gain_db + config.normalisation_pregain_db),
PCM_AT_0DBFS / gain_peak,
);
if factor > PCM_AT_0DBFS {
info!(
"Lowering gain by {:.2} dB for the duration of this track to avoid potentially exceeding dBFS.",
ratio_to_db(factor)
);
PCM_AT_0DBFS
} else {
factor
}
} else {
// For Dynamic Normalisation it's up to the player to decide,
// factor = ratio of (ReplayGain + PreGain).
// We then let the dynamic limiter handle gain reduction.
let factor = db_to_ratio(gain_db + config.normalisation_pregain_db);
let threshold_ratio = db_to_ratio(config.normalisation_threshold_dbfs);
if factor > PCM_AT_0DBFS {
let factor_db = gain_db + config.normalisation_pregain_db;
let limiting_db = factor_db + config.normalisation_threshold_dbfs.abs();
warn!(
"This track may exceed dBFS by {:.2} dB and be subject to {:.2} dB of dynamic limiting at its peak.",
factor_db, limiting_db
);
} else if factor > threshold_ratio {
let limiting_db = gain_db
+ config.normalisation_pregain_db
+ config.normalisation_threshold_dbfs.abs();
info!(
"This track may be subject to {:.2} dB of dynamic limiting at its peak.",
limiting_db
);
}
factor
};
2018-02-23 19:08:20 +00:00
debug!("Normalisation Data: {:?}", data);
debug!(
"Calculated Normalisation Factor for {:?}: {:.2}%",
config.normalisation_type,
normalisation_factor * 100.0
);
normalisation_factor
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,
volume_getter: Box<dyn VolumeGetter + Send>,
2018-02-26 01:50:41 +00:00
sink_builder: F,
2023-06-13 14:56:40 +00:00
) -> Arc<Self>
2018-02-26 01:50:41 +00:00
where
F: FnOnce() -> Box<dyn Sink> + Send + 'static,
{
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
if config.normalisation {
debug!("Normalisation Type: {:?}", config.normalisation_type);
debug!(
"Normalisation Pregain: {:.1} dB",
config.normalisation_pregain_db
);
debug!(
"Normalisation Threshold: {:.1} dBFS",
config.normalisation_threshold_dbfs
);
debug!("Normalisation Method: {:?}", config.normalisation_method);
if config.normalisation_method == NormalisationMethod::Dynamic {
// as_millis() has rounding errors (truncates)
debug!(
"Normalisation Attack: {:.0} ms",
coefficient_to_duration(config.normalisation_attack_cf).as_secs_f64() * 1000.
);
debug!(
"Normalisation Release: {:.0} ms",
coefficient_to_duration(config.normalisation_release_cf).as_secs_f64() * 1000.
);
debug!("Normalisation Knee: {} dB", config.normalisation_knee_db);
}
}
let handle = thread::spawn(move || {
let player_id = PLAYER_COUNTER.fetch_add(1, Ordering::AcqRel);
debug!("new Player [{}]", player_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,
load_handles: Arc::new(Mutex::new(HashMap::new())),
state: PlayerState::Stopped,
preload: PlayerPreload::None,
sink: sink_builder(),
sink_status: SinkStatus::Closed,
sink_event_callback: None,
volume_getter,
event_senders: 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,
normalisation_peak: 0.0,
normalisation_integrator: 0.0,
auto_normalise_as_album: false,
player_id,
2023-06-13 14:30:56 +00:00
play_request_id_generator: SeqGenerator::new(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.
let runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
runtime.block_on(internal);
debug!("PlayerInternal thread finished.");
2017-01-29 14:11:20 +00:00
});
2015-09-01 11:20:37 +00:00
2023-06-13 14:56:40 +00:00
Arc::new(Self {
commands: Some(cmd_tx),
thread_handle: Some(handle),
2023-06-13 14:56:40 +00:00
})
}
2023-06-13 14:38:41 +00:00
pub fn is_invalid(&self) -> bool {
if let Some(handle) = self.thread_handle.as_ref() {
return handle.is_finished();
}
true
}
2015-07-09 20:08:14 +00:00
fn command(&self, cmd: PlayerCommand) {
if let Some(commands) = self.commands.as_ref() {
if let Err(e) = commands.send(cmd) {
error!("Player Commands Error: {}", e);
}
}
}
2016-01-20 14:11:49 +00:00
2023-06-13 14:30:56 +00:00
pub fn load(&self, track_id: SpotifyId, start_playing: bool, position_ms: u32) {
self.command(PlayerCommand::Load {
track_id,
play: start_playing,
position_ms,
});
}
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));
}
2023-06-13 14:52:17 +00:00
pub fn set_session(&self, session: Session) {
self.command(PlayerCommand::SetSession(session));
}
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_changed_event(&self, volume: u16) {
self.command(PlayerCommand::EmitVolumeChangedEvent(volume));
}
pub fn set_auto_normalise_as_album(&self, setting: bool) {
self.command(PlayerCommand::SetAutoNormaliseAsAlbum(setting));
}
pub fn emit_filter_explicit_content_changed_event(&self, filter: bool) {
self.command(PlayerCommand::EmitFilterExplicitContentChangedEvent(filter));
}
pub fn emit_session_connected_event(&self, connection_id: String, user_name: String) {
self.command(PlayerCommand::EmitSessionConnectedEvent {
connection_id,
user_name,
});
}
pub fn emit_session_disconnected_event(&self, connection_id: String, user_name: String) {
self.command(PlayerCommand::EmitSessionDisconnectedEvent {
connection_id,
user_name,
});
}
pub fn emit_session_client_changed_event(
&self,
client_id: String,
client_name: String,
client_brand_name: String,
client_model_name: String,
) {
self.command(PlayerCommand::EmitSessionClientChangedEvent {
client_id,
client_name,
client_brand_name,
client_model_name,
});
}
pub fn emit_shuffle_changed_event(&self, shuffle: bool) {
self.command(PlayerCommand::EmitShuffleChangedEvent(shuffle));
}
pub fn emit_repeat_changed_event(&self, repeat: bool) {
self.command(PlayerCommand::EmitRepeatChangedEvent(repeat));
}
pub fn emit_auto_play_changed_event(&self, auto_play: bool) {
self.command(PlayerCommand::EmitAutoPlayChangedEvent(auto_play));
}
}
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() {
if let Err(e) = handle.join() {
error!("Player thread Error: {:?}", e);
}
}
}
}
struct PlayerLoadedTrackData {
decoder: Decoder,
normalisation_data: NormalisationData,
stream_loader_controller: StreamLoaderController,
audio_item: AudioItem,
bytes_per_second: usize,
duration_ms: u32,
stream_position_ms: u32,
is_explicit: bool,
}
enum PlayerPreload {
None,
Loading {
track_id: SpotifyId,
loader: Pin<Box<dyn FusedFuture<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,
loader: Pin<Box<dyn FusedFuture<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,
audio_item: AudioItem,
normalisation_data: NormalisationData,
normalisation_factor: f64,
2019-11-01 19:46:28 +00:00
stream_loader_controller: StreamLoaderController,
bytes_per_second: usize,
duration_ms: u32,
stream_position_ms: u32,
suggested_to_preload_next_track: bool,
is_explicit: 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_data: NormalisationData,
audio_item: AudioItem,
normalisation_factor: f64,
2019-11-01 19:46:28 +00:00
stream_loader_controller: StreamLoaderController,
bytes_per_second: usize,
duration_ms: u32,
stream_position_ms: u32,
reported_nominal_start_time: Option<Instant>,
suggested_to_preload_next_track: bool,
is_explicit: 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 => {
error!("PlayerState::is_playing in invalid state");
exit(1);
}
2017-01-29 14:11:20 +00:00
}
}
#[allow(dead_code)]
fn is_stopped(&self) -> bool {
use self::PlayerState::*;
2021-03-01 02:37:22 +00:00
matches!(self, Stopped)
}
#[allow(dead_code)]
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),
Invalid => {
error!("PlayerState::decoder in invalid state");
exit(1);
}
}
2017-01-29 14:11:20 +00:00
}
fn playing_to_end_of_track(&mut self) {
use self::PlayerState::*;
let new_state = mem::replace(self, Invalid);
match new_state {
2018-02-26 01:50:41 +00:00
Playing {
track_id,
play_request_id,
decoder,
duration_ms,
bytes_per_second,
normalisation_data,
stream_loader_controller,
stream_position_ms,
is_explicit,
audio_item,
2018-02-26 01:50:41 +00:00
..
} => {
*self = EndOfTrack {
track_id,
play_request_id,
loaded_track: PlayerLoadedTrackData {
decoder,
normalisation_data,
stream_loader_controller,
audio_item,
2021-05-09 10:59:34 +00:00
bytes_per_second,
duration_ms,
stream_position_ms,
is_explicit,
},
};
2018-02-26 01:50:41 +00:00
}
_ => {
error!(
"Called playing_to_end_of_track in non-playing state: {:?}",
new_state
);
exit(1);
}
}
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::*;
let new_state = mem::replace(self, Invalid);
match new_state {
2018-02-26 01:50:41 +00:00
Paused {
track_id,
play_request_id,
2018-02-26 01:50:41 +00:00
decoder,
audio_item,
normalisation_data,
2018-02-26 01:50:41 +00:00
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,
stream_position_ms,
suggested_to_preload_next_track,
is_explicit,
2018-02-26 01:50:41 +00:00
} => {
2017-01-29 14:11:20 +00:00
*self = Playing {
track_id,
play_request_id,
decoder,
audio_item,
normalisation_data,
normalisation_factor,
stream_loader_controller,
duration_ms,
bytes_per_second,
stream_position_ms,
reported_nominal_start_time: Instant::now()
.checked_sub(Duration::from_millis(stream_position_ms as u64)),
suggested_to_preload_next_track,
is_explicit,
2017-01-29 14:11:20 +00:00
};
}
_ => {
error!(
"PlayerState::paused_to_playing in invalid state: {:?}",
new_state
);
exit(1);
}
2017-01-29 14:11:20 +00:00
}
}
2017-01-29 14:11:20 +00:00
fn playing_to_paused(&mut self) {
use self::PlayerState::*;
let new_state = mem::replace(self, Invalid);
match new_state {
2018-02-26 01:50:41 +00:00
Playing {
track_id,
play_request_id,
2018-02-26 01:50:41 +00:00
decoder,
audio_item,
normalisation_data,
2018-02-26 01:50:41 +00:00
normalisation_factor,
2019-11-01 19:46:28 +00:00
stream_loader_controller,
duration_ms,
bytes_per_second,
stream_position_ms,
suggested_to_preload_next_track,
is_explicit,
..
2018-02-26 01:50:41 +00:00
} => {
2017-01-29 14:11:20 +00:00
*self = Paused {
track_id,
play_request_id,
decoder,
audio_item,
normalisation_data,
normalisation_factor,
stream_loader_controller,
duration_ms,
bytes_per_second,
stream_position_ms,
suggested_to_preload_next_track,
is_explicit,
2017-01-29 14:11:20 +00:00
};
}
_ => {
error!(
"PlayerState::playing_to_paused in invalid state: {:?}",
new_state
);
exit(1);
}
2017-01-29 14:11:20 +00:00
}
}
}
struct PlayerTrackLoader {
session: Session,
config: PlayerConfig,
}
impl PlayerTrackLoader {
async fn find_available_alternative(&self, audio_item: AudioItem) -> Option<AudioItem> {
if let Err(e) = audio_item.availability {
2021-12-27 08:35:11 +00:00
error!("Track is unavailable: {}", e);
None
} else if !audio_item.files.is_empty() {
Some(audio_item)
} else if let Some(alternatives) = &audio_item.alternatives {
2021-02-22 08:58:08 +00:00
let alternatives: FuturesUnordered<_> = alternatives
2021-01-21 21:22:32 +00:00
.iter()
.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()))
.filter(|x| future::ready(x.availability.is_ok()))
2021-02-22 08:58:08 +00:00
.next()
.await
} 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
}
}
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
}
2021-01-21 21:22:32 +00:00
async fn load_track(
&self,
spotify_id: SpotifyId,
position_ms: u32,
) -> Option<PlayerLoadedTrackData> {
let audio_item = match AudioItem::get_file(&self.session, spotify_id).await {
Ok(audio) => match self.find_available_alternative(audio).await {
Some(audio) => audio,
None => {
warn!(
"<{}> is not available",
spotify_id.to_uri().unwrap_or_default()
);
return None;
}
},
Err(e) => {
error!("Unable to load audio item: {:?}", e);
return None;
}
};
info!(
"Loading <{}> with Spotify URI <{}>",
audio_item.name, audio_item.uri
);
// (Most) podcasts seem to support only 96 kbps Ogg Vorbis, so fall back to it
let formats = match self.config.bitrate {
Bitrate::Bitrate96 => [
AudioFileFormat::OGG_VORBIS_96,
AudioFileFormat::MP3_96,
AudioFileFormat::OGG_VORBIS_160,
AudioFileFormat::MP3_160,
AudioFileFormat::MP3_256,
AudioFileFormat::OGG_VORBIS_320,
AudioFileFormat::MP3_320,
],
Bitrate::Bitrate160 => [
AudioFileFormat::OGG_VORBIS_160,
AudioFileFormat::MP3_160,
AudioFileFormat::OGG_VORBIS_96,
AudioFileFormat::MP3_96,
AudioFileFormat::MP3_256,
AudioFileFormat::OGG_VORBIS_320,
AudioFileFormat::MP3_320,
],
Bitrate::Bitrate320 => [
AudioFileFormat::OGG_VORBIS_320,
AudioFileFormat::MP3_320,
AudioFileFormat::MP3_256,
AudioFileFormat::OGG_VORBIS_160,
AudioFileFormat::MP3_160,
AudioFileFormat::OGG_VORBIS_96,
AudioFileFormat::MP3_96,
],
};
let (format, file_id) =
match formats
.iter()
.find_map(|format| match audio_item.files.get(format) {
Some(&file_id) => Some((*format, file_id)),
_ => None,
}) {
Some(t) => t,
None => {
warn!(
"<{}> is not available in any supported format",
audio_item.name
);
return None;
}
};
2021-02-09 08:15:55 +00:00
let bytes_per_second = self.stream_data_rate(format);
// This is only a loop to be able to reload the file if an error occurred
// while opening a cached file.
loop {
let encrypted_file = AudioFile::open(&self.session, file_id, bytes_per_second);
let encrypted_file = match encrypted_file.await {
Ok(encrypted_file) => encrypted_file,
Err(e) => {
error!("Unable to load encrypted file: {:?}", e);
return None;
}
};
2021-12-18 22:44:13 +00:00
let is_cached = encrypted_file.is_cached();
2021-12-18 22:44:13 +00:00
let stream_loader_controller = encrypted_file.get_stream_loader_controller().ok()?;
// Not all audio files are encrypted. If we can't get a key, try loading the track
// without decryption. If the file was encrypted after all, the decoder will fail
// parsing and bail out, so we should be safe from outputting ear-piercing noise.
2021-02-12 17:19:04 +00:00
let key = match self.session.audio_key().request(spotify_id, file_id).await {
Ok(key) => Some(key),
Err(e) => {
warn!("Unable to load key, continuing without decryption: {}", e);
None
}
};
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
let is_ogg_vorbis = AudioFiles::is_ogg_vorbis(format);
let (offset, mut normalisation_data) = if is_ogg_vorbis {
// Spotify stores normalisation data in a custom Ogg packet instead of Vorbis comments.
let normalisation_data =
NormalisationData::parse_from_ogg(&mut decrypted_file).ok();
(SPOTIFY_OGG_HEADER_END, normalisation_data)
} else {
(0, None)
};
let audio_file = match Subfile::new(
decrypted_file,
offset,
stream_loader_controller.len() as u64,
) {
Ok(audio_file) => audio_file,
Err(e) => {
error!("PlayerTrackLoader::load_track error opening subfile: {}", e);
return None;
}
};
2022-01-25 19:46:10 +00:00
let mut symphonia_decoder = |audio_file, format| {
SymphoniaDecoder::new(audio_file, format).map(|mut decoder| {
// For formats other that Vorbis, we'll try getting normalisation data from
// ReplayGain metadata fields, if present.
if normalisation_data.is_none() {
normalisation_data = decoder.normalisation_data();
}
Box::new(decoder) as Decoder
})
};
2021-01-07 06:42:38 +00:00
2022-01-25 19:46:10 +00:00
#[cfg(feature = "passthrough-decoder")]
let decoder_type = if self.config.passthrough {
PassthroughDecoder::new(audio_file, format).map(|x| Box::new(x) as Decoder)
} else {
symphonia_decoder(audio_file, format)
};
#[cfg(not(feature = "passthrough-decoder"))]
let decoder_type = symphonia_decoder(audio_file, format);
let normalisation_data = normalisation_data.unwrap_or_else(|| {
warn!("Unable to get normalisation data, continuing with defaults.");
NormalisationData::default()
});
2022-01-25 19:46:10 +00:00
let mut decoder = match decoder_type {
Ok(decoder) => decoder,
Err(e) if is_cached => {
warn!(
"Unable to read cached audio file: {}. Trying to download it.",
e
);
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;
}
}
// Just try it again
continue;
}
Err(e) => {
error!("Unable to read audio file: {}", e);
2021-02-02 01:18:58 +00:00
return None;
}
};
let duration_ms = audio_item.duration_ms;
// Don't try to seek past the track's duration.
// If the position is invalid just start from
// the beginning of the track.
let position_ms = if position_ms > duration_ms {
warn!("Invalid start position of {} ms exceeds track's duration of {} ms, starting track from the beginning", position_ms, duration_ms);
0
} else {
position_ms
};
2021-12-30 20:52:15 +00:00
// Ensure the starting position. Even when we want to play from the beginning,
// the cursor may have been moved by parsing normalisation data. This may not
// matter for playback (but won't hurt either), but may be useful for the
// passthrough decoder.
let stream_position_ms = match decoder.seek(position_ms) {
Ok(new_position_ms) => new_position_ms,
2021-12-30 20:52:15 +00:00
Err(e) => {
error!(
"PlayerTrackLoader::load_track error seeking to starting position {}: {}",
position_ms, e
2021-12-30 20:52:15 +00:00
);
return None;
}
};
// Ensure streaming mode now that we are ready to play from the requested position.
stream_loader_controller.set_stream_mode();
let is_explicit = audio_item.is_explicit;
info!("<{}> ({} ms) loaded", audio_item.name, duration_ms);
return Some(PlayerLoadedTrackData {
decoder,
normalisation_data,
stream_loader_controller,
audio_item,
bytes_per_second,
duration_ms,
stream_position_ms,
is_explicit,
});
}
}
}
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 {
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
}
// Handle loading of a new track to play
if let PlayerState::Loading {
ref mut loader,
track_id,
start_playback,
play_request_id,
} = self.state
{
// The loader may be terminated if we are trying to load the same track
// as before, and that track failed to open before.
if !loader.as_mut().is_terminated() {
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 {
error!("The state wasn't changed by start_playback()");
exit(1);
}
}
Poll::Ready(Err(e)) => {
error!(
"Skipping to next track, unable to load track <{:?}>: {:?}",
track_id, e
);
self.send_event(PlayerEvent::Unavailable {
track_id,
play_request_id,
})
}
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,
ref mut stream_position_ms,
ref mut reported_nominal_start_time,
2018-02-26 01:50:41 +00:00
..
} = self.state
{
match decoder.next_packet() {
Ok(result) => {
if let Some((ref packet_position, ref packet)) = result {
let new_stream_position_ms = packet_position.position_ms;
let expected_position_ms = std::mem::replace(
&mut *stream_position_ms,
new_stream_position_ms,
);
2022-01-06 21:11:53 +00:00
if !passthrough {
match packet.samples() {
Ok(_) => {
let new_stream_position = Duration::from_millis(
new_stream_position_ms as u64,
);
let now = Instant::now();
// Only notify if we're skipped some packets *or* we are behind.
// If we're ahead it's probably due to a buffer of the backend
// and we're actually in time.
let notify_about_position =
match *reported_nominal_start_time {
None => true,
Some(reported_nominal_start_time) => {
let mut notify = false;
if packet_position.skipped {
if let Some(ahead) = new_stream_position
.checked_sub(Duration::from_millis(
expected_position_ms as u64,
))
{
notify |=
ahead >= Duration::from_secs(1)
}
}
if let Some(lag) = now
.checked_duration_since(
reported_nominal_start_time,
)
{
if let Some(lag) =
lag.checked_sub(new_stream_position)
{
notify |=
lag >= Duration::from_secs(1)
}
}
notify
}
};
if notify_about_position {
*reported_nominal_start_time =
now.checked_sub(new_stream_position);
self.send_event(PlayerEvent::PositionCorrection {
play_request_id,
track_id,
2023-01-02 18:01:35 +00:00
position_ms: new_stream_position_ms,
});
}
}
Err(e) => {
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,
})
}
}
}
}
self.handle_packet(result, normalisation_factor);
}
Err(e) => {
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,
})
}
}
} else {
error!("PlayerInternal poll: Invalid PlayerState");
exit(1);
};
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,
stream_position_ms,
ref mut stream_loader_controller,
ref mut suggested_to_preload_next_track,
..
}
| PlayerState::Paused {
track_id,
play_request_id,
duration_ms,
stream_position_ms,
ref mut stream_loader_controller,
ref mut suggested_to_preload_next_track,
..
} = self.state
{
if (!*suggested_to_preload_next_track)
&& ((duration_ms as i64 - stream_position_ms 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,
});
}
}
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 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(e) => {
error!("{}", e);
self.handle_pause();
}
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(e) => {
error!("{}", e);
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 => {
error!("PlayerInternal::handle_player_stop in invalid state");
exit(1);
}
}
}
fn handle_play(&mut self) {
match self.state {
PlayerState::Paused {
track_id,
play_request_id,
stream_position_ms,
..
} => {
self.state.paused_to_playing();
self.send_event(PlayerEvent::Playing {
track_id,
play_request_id,
position_ms: stream_position_ms,
});
self.ensure_sink_running();
}
PlayerState::Loading {
ref mut start_playback,
..
} => {
*start_playback = true;
}
_ => error!("Player::play called from invalid state: {:?}", self.state),
}
}
fn handle_pause(&mut self) {
match self.state {
PlayerState::Paused { .. } => self.ensure_sink_stopped(false),
PlayerState::Playing {
track_id,
play_request_id,
stream_position_ms,
..
} => {
self.state.playing_to_paused();
self.ensure_sink_stopped(false);
self.send_event(PlayerEvent::Paused {
track_id,
play_request_id,
position_ms: stream_position_ms,
});
}
PlayerState::Loading {
ref mut start_playback,
..
} => {
*start_playback = false;
}
_ => error!("Player::pause called from invalid state: {:?}", self.state),
}
}
fn handle_packet(
&mut self,
packet: Option<(AudioPacketPosition, 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 {
// Get the volume for the packet.
// In the case of hardware volume control this will
// always be 1.0 (no change).
let volume = self.volume_getter.attenuation_factor();
// For the basic normalisation method, a normalisation factor of 1.0 indicates that
// there is nothing to normalise (all samples should pass unaltered). For the
// dynamic method, there may still be peaks that we want to shave off.
// No matter the case we apply volume attenuation last if there is any.
if !self.config.normalisation {
if volume < 1.0 {
for sample in data.iter_mut() {
*sample *= volume;
}
}
} else if self.config.normalisation_method == NormalisationMethod::Basic
&& (normalisation_factor < 1.0 || volume < 1.0)
{
for sample in data.iter_mut() {
*sample *= normalisation_factor * volume;
}
} else if self.config.normalisation_method == NormalisationMethod::Dynamic {
// zero-cost shorthands
let threshold_db = self.config.normalisation_threshold_dbfs;
let knee_db = self.config.normalisation_knee_db;
let attack_cf = self.config.normalisation_attack_cf;
let release_cf = self.config.normalisation_release_cf;
for sample in data.iter_mut() {
*sample *= normalisation_factor;
// Feedforward limiter in the log domain
// After: Giannoulis, D., Massberg, M., & Reiss, J.D. (2012). Digital Dynamic
// Range Compressor Design—A Tutorial and Analysis. Journal of The Audio
// Engineering Society, 60, 399-408.
// Some tracks have samples that are precisely 0.0. That's silence
// and we know we don't need to limit that, in which we can spare
// the CPU cycles.
//
// Also, calling `ratio_to_db(0.0)` returns `inf` and would get the
// peak detector stuck. Also catch the unlikely case where a sample
// is decoded as `NaN` or some other non-normal value.
let limiter_db = if sample.is_normal() {
// step 1-4: half-wave rectification and conversion into dB
// and gain computer with soft knee and subtractor
let bias_db = ratio_to_db(sample.abs()) - threshold_db;
let knee_boundary_db = bias_db * 2.0;
if knee_boundary_db < -knee_db {
0.0
} else if knee_boundary_db.abs() <= knee_db {
// The textbook equation:
// ratio_to_db(sample.abs()) - (ratio_to_db(sample.abs()) - (bias_db + knee_db / 2.0).powi(2) / (2.0 * knee_db))
// Simplifies to:
// ((2.0 * bias_db) + knee_db).powi(2) / (8.0 * knee_db)
// Which in our case further simplifies to:
// (knee_boundary_db + knee_db).powi(2) / (8.0 * knee_db)
// because knee_boundary_db is 2.0 * bias_db.
(knee_boundary_db + knee_db).powi(2) / (8.0 * knee_db)
} else {
// Textbook:
// ratio_to_db(sample.abs()) - threshold_db, which is already our bias_db.
bias_db
}
} else {
0.0
};
// Spare the CPU unless (1) the limiter is engaged, (2) we
// were in attack or (3) we were in release, and that attack/
// release wasn't finished yet.
if limiter_db > 0.0
|| self.normalisation_integrator > 0.0
|| self.normalisation_peak > 0.0
{
// step 5: smooth, decoupled peak detector
// Textbook:
// release_cf * self.normalisation_integrator + (1.0 - release_cf) * limiter_db
// Simplifies to:
// release_cf * self.normalisation_integrator - release_cf * limiter_db + limiter_db
self.normalisation_integrator = f64::max(
limiter_db,
release_cf * self.normalisation_integrator
- release_cf * limiter_db
+ limiter_db,
);
// Textbook:
// attack_cf * self.normalisation_peak + (1.0 - attack_cf) * self.normalisation_integrator
// Simplifies to:
// attack_cf * self.normalisation_peak - attack_cf * self.normalisation_integrator + self.normalisation_integrator
self.normalisation_peak = attack_cf * self.normalisation_peak
- attack_cf * self.normalisation_integrator
+ self.normalisation_integrator;
// step 6: make-up gain applied later (volume attenuation)
// Applying the standard normalisation factor here won't work,
// because there are tracks with peaks as high as 6 dB above
// the default threshold, so that would clip.
// steps 7-8: conversion into level and multiplication into gain stage
*sample *= db_to_ratio(-self.normalisation_peak);
}
2021-09-01 18:54:47 +00:00
*sample *= volume;
}
2021-09-01 18:54:47 +00:00
}
2018-02-23 19:08:20 +00:00
}
if let Err(e) = self.sink.write(packet, &mut self.converter) {
error!("{}", e);
self.handle_pause();
}
}
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 {
error!("PlayerInternal handle_packet: Invalid PlayerState");
exit(1);
}
}
}
}
fn start_playback(
&mut self,
track_id: SpotifyId,
play_request_id: u64,
loaded_track: PlayerLoadedTrackData,
start_playback: bool,
) {
let audio_item = Box::new(loaded_track.audio_item.clone());
self.send_event(PlayerEvent::TrackChanged { audio_item });
let position_ms = loaded_track.stream_position_ms;
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);
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,
});
self.state = PlayerState::Playing {
2021-03-10 21:39:01 +00:00
track_id,
play_request_id,
decoder: loaded_track.decoder,
audio_item: loaded_track.audio_item,
normalisation_data: loaded_track.normalisation_data,
normalisation_factor,
stream_loader_controller: loaded_track.stream_loader_controller,
duration_ms: loaded_track.duration_ms,
bytes_per_second: loaded_track.bytes_per_second,
stream_position_ms: loaded_track.stream_position_ms,
reported_nominal_start_time: Instant::now()
.checked_sub(Duration::from_millis(position_ms as u64)),
suggested_to_preload_next_track: false,
is_explicit: loaded_track.is_explicit,
};
} 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,
audio_item: loaded_track.audio_item,
normalisation_data: loaded_track.normalisation_data,
normalisation_factor,
stream_loader_controller: loaded_track.stream_loader_controller,
duration_ms: loaded_track.duration_ms,
bytes_per_second: loaded_track.bytes_per_second,
stream_position_ms: loaded_track.stream_position_ms,
suggested_to_preload_next_track: false,
is_explicit: loaded_track.is_explicit,
};
self.send_event(PlayerEvent::Paused {
track_id,
play_request_id,
position_ms,
});
2017-01-29 14:11:20 +00:00
}
}
fn handle_command_load(
&mut self,
track_id: SpotifyId,
2023-06-13 14:30:56 +00:00
play_request_id_option: Option<u64>,
play: bool,
position_ms: u32,
) -> PlayerResult {
2023-06-13 14:30:56 +00:00
let play_request_id =
play_request_id_option.unwrap_or(self.play_request_id_generator.get());
self.send_event(PlayerEvent::PlayRequestIdChanged { play_request_id });
if !self.config.gapless {
2020-03-10 12:53:58 +00:00
self.ensure_sink_stopped(play);
}
if matches!(self.state, PlayerState::Invalid { .. }) {
return Err(Error::internal(format!(
"Player::handle_command_load called from invalid state: {:?}",
self.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,
_ => {
return Err(Error::internal(format!("PlayerInternal::handle_command_load repeating the same track: invalid state: {:?}", self.state)));
}
};
if position_ms != loaded_track.stream_position_ms {
// This may be blocking.
loaded_track.stream_position_ms = loaded_track.decoder.seek(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 {
return Err(Error::internal(format!("PlayerInternal::handle_command_load repeating the same track: start_playback() did not transition to valid player state: {:?}", self.state)));
}
return Ok(());
}
}
// 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_ms,
ref mut decoder,
..
}
| PlayerState::Paused {
track_id: current_track_id,
ref mut stream_position_ms,
ref mut decoder,
..
} = self.state
{
if current_track_id == track_id {
// we can use the current decoder. Ensure it's at the correct position.
if position_ms != *stream_position_ms {
// This may be blocking.
*stream_position_ms = decoder.seek(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_ms,
decoder,
audio_item,
stream_loader_controller,
bytes_per_second,
duration_ms,
normalisation_data,
is_explicit,
..
}
| PlayerState::Paused {
stream_position_ms,
decoder,
audio_item,
stream_loader_controller,
bytes_per_second,
duration_ms,
normalisation_data,
is_explicit,
..
} = old_state
{
let loaded_track = PlayerLoadedTrackData {
decoder,
normalisation_data,
stream_loader_controller,
audio_item,
bytes_per_second,
duration_ms,
stream_position_ms,
is_explicit,
};
self.preload = PlayerPreload::None;
self.start_playback(track_id, play_request_id, loaded_track, play);
if let PlayerState::Invalid = self.state {
return Err(Error::internal(format!("PlayerInternal::handle_command_load already playing this track: start_playback() did not transition to valid player state: {:?}", self.state)));
}
return Ok(());
} else {
return Err(Error::internal(format!("PlayerInternal::handle_command_load already playing this track: invalid state: {:?}", self.state)));
}
}
}
// 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 position_ms != loaded_track.stream_position_ms {
// This may be blocking
loaded_track.stream_position_ms = loaded_track.decoder.seek(position_ms)?;
}
2021-01-22 21:51:41 +00:00
self.start_playback(track_id, play_request_id, *loaded_track, play);
return Ok(());
} else {
return Err(Error::internal(format!("PlayerInternal::handle_command_loading preloaded track: invalid state: {:?}", self.state)));
}
}
}
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,
};
Ok(())
}
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),
}
}
}
2021-12-18 22:44:13 +00:00
fn handle_command_seek(&mut self, position_ms: u32) -> PlayerResult {
// When we are still loading, the user may immediately ask to
// seek to another position yet the decoder won't be ready for
// that. In this case just restart the loading process but
// with the requested position.
if let PlayerState::Loading {
track_id,
play_request_id,
start_playback,
..
} = self.state
{
return self.handle_command_load(
track_id,
2023-06-13 14:30:56 +00:00
Some(play_request_id),
start_playback,
position_ms,
);
}
if let Some(decoder) = self.state.decoder() {
match decoder.seek(position_ms) {
Ok(new_position_ms) => {
if let PlayerState::Playing {
ref mut stream_position_ms,
track_id,
play_request_id,
..
2017-01-29 14:11:20 +00:00
}
| PlayerState::Paused {
ref mut stream_position_ms,
track_id,
play_request_id,
..
} = self.state
{
*stream_position_ms = new_position_ms;
self.send_event(PlayerEvent::Seeked {
play_request_id,
track_id,
position_ms: new_position_ms,
});
}
}
Err(e) => error!("PlayerInternal::handle_command_seek error: {}", e),
2017-01-29 14:11:20 +00:00
}
} else {
error!("Player::seek called from invalid state: {:?}", self.state);
}
// ensure we have a bit of a buffer of downloaded data
2021-12-18 22:44:13 +00:00
self.preload_data_before_playback()?;
if let PlayerState::Playing {
ref mut reported_nominal_start_time,
..
} = self.state
{
*reported_nominal_start_time =
Instant::now().checked_sub(Duration::from_millis(position_ms as u64));
}
2021-12-18 22:44:13 +00:00
Ok(())
}
2021-12-18 22:44:13 +00:00
fn handle_command(&mut self, cmd: PlayerCommand) -> PlayerResult {
debug!("command={:?}", cmd);
match cmd {
PlayerCommand::Load {
track_id,
play,
position_ms,
2023-06-13 14:30:56 +00:00
} => self.handle_command_load(track_id, None, play, position_ms)?,
PlayerCommand::Preload { track_id } => self.handle_command_preload(track_id),
2021-12-18 22:44:13 +00:00
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
2023-06-13 14:52:17 +00:00
PlayerCommand::SetSession(session) => self.session = session,
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::EmitVolumeChangedEvent(volume) => {
self.send_event(PlayerEvent::VolumeChanged { volume })
}
PlayerCommand::EmitRepeatChangedEvent(repeat) => {
self.send_event(PlayerEvent::RepeatChanged { repeat })
}
PlayerCommand::EmitShuffleChangedEvent(shuffle) => {
self.send_event(PlayerEvent::ShuffleChanged { shuffle })
}
PlayerCommand::EmitAutoPlayChangedEvent(auto_play) => {
self.send_event(PlayerEvent::AutoPlayChanged { auto_play })
}
PlayerCommand::EmitSessionClientChangedEvent {
client_id,
client_name,
client_brand_name,
client_model_name,
} => self.send_event(PlayerEvent::SessionClientChanged {
client_id,
client_name,
client_brand_name,
client_model_name,
}),
PlayerCommand::EmitSessionConnectedEvent {
connection_id,
user_name,
} => self.send_event(PlayerEvent::SessionConnected {
connection_id,
user_name,
}),
PlayerCommand::EmitSessionDisconnectedEvent {
connection_id,
user_name,
} => self.send_event(PlayerEvent::SessionDisconnected {
connection_id,
user_name,
}),
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => {
self.auto_normalise_as_album = setting
}
PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => {
self.send_event(PlayerEvent::FilterExplicitContentChanged { filter });
if filter {
if let PlayerState::Playing {
track_id,
play_request_id,
is_explicit,
..
}
| PlayerState::Paused {
track_id,
play_request_id,
is_explicit,
..
} = self.state
{
if is_explicit {
warn!("Currently loaded track is explicit, which client setting forbids -- skipping to next track.");
self.send_event(PlayerEvent::EndOfTrack {
track_id,
play_request_id,
})
}
}
}
}
2021-12-18 22:44:13 +00:00
};
Ok(())
}
2015-07-09 20:08:14 +00:00
fn send_event(&mut self, event: PlayerEvent) {
self.event_senders
.retain(|sender| sender.send(event.clone()).is_ok());
2019-11-01 19:46:28 +00:00
}
fn load_track(
&mut self,
2019-11-11 07:22:41 +00:00
spotify_id: SpotifyId,
2020-02-02 00:07:05 +00:00
position_ms: u32,
) -> impl FusedFuture<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
let load_handles_clone = self.load_handles.clone();
let handle = tokio::runtime::Handle::current();
let load_handle = thread::spawn(move || {
let data = handle.block_on(loader.load_track(spotify_id, position_ms));
if let Some(data) = data {
let _ = result_tx.send(data);
}
let mut load_handles = load_handles_clone.lock();
load_handles.remove(&thread::current().id());
});
2017-01-29 14:11:20 +00:00
let mut load_handles = self.load_handles.lock();
load_handles.insert(load_handle.thread().id(), load_handle);
result_rx.map_err(|_| ())
}
fn preload_data_before_playback(&mut self) -> PlayerResult {
if let PlayerState::Playing {
2020-01-17 17:11:07 +00:00
bytes_per_second,
ref mut stream_loader_controller,
..
} = self.state
{
let read_ahead_during_playback = AudioFetchParams::get().read_ahead_during_playback;
// Request our read ahead range
let request_data_length =
(read_ahead_during_playback.as_secs_f32() * bytes_per_second as f32) as usize;
2022-01-05 19:44:08 +00:00
// Request the part we want to wait for blocking. This effectively means we wait for the previous request to partially complete.
let wait_for_data_length =
(read_ahead_during_playback.as_secs_f32() * bytes_per_second as f32) as usize;
2021-12-18 22:44:13 +00:00
stream_loader_controller
2022-01-05 19:44:08 +00:00
.fetch_next_and_wait(request_data_length, wait_for_data_length)
.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 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.player_id);
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();
}
}
}
impl fmt::Debug for PlayerCommand {
2022-08-02 19:42:38 +00:00
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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(),
2023-06-13 14:52:17 +00:00
PlayerCommand::SetSession(_) => f.debug_tuple("SetSession").finish(),
PlayerCommand::AddEventSender(_) => f.debug_tuple("AddEventSender").finish(),
2020-03-10 12:26:01 +00:00
PlayerCommand::SetSinkEventCallback(_) => {
f.debug_tuple("SetSinkEventCallback").finish()
}
PlayerCommand::EmitVolumeChangedEvent(volume) => f
.debug_tuple("EmitVolumeChangedEvent")
.field(&volume)
.finish(),
PlayerCommand::SetAutoNormaliseAsAlbum(setting) => f
.debug_tuple("SetAutoNormaliseAsAlbum")
.field(&setting)
.finish(),
PlayerCommand::EmitFilterExplicitContentChangedEvent(filter) => f
.debug_tuple("EmitFilterExplicitContentChangedEvent")
.field(&filter)
.finish(),
PlayerCommand::EmitSessionConnectedEvent {
connection_id,
user_name,
} => f
.debug_tuple("EmitSessionConnectedEvent")
.field(&connection_id)
.field(&user_name)
.finish(),
PlayerCommand::EmitSessionDisconnectedEvent {
connection_id,
user_name,
} => f
.debug_tuple("EmitSessionDisconnectedEvent")
.field(&connection_id)
.field(&user_name)
.finish(),
PlayerCommand::EmitSessionClientChangedEvent {
client_id,
client_name,
client_brand_name,
client_model_name,
} => f
.debug_tuple("EmitSessionClientChangedEvent")
.field(&client_id)
.field(&client_name)
.field(&client_brand_name)
.field(&client_model_name)
.finish(),
PlayerCommand::EmitShuffleChangedEvent(shuffle) => f
.debug_tuple("EmitShuffleChangedEvent")
.field(&shuffle)
.finish(),
PlayerCommand::EmitRepeatChangedEvent(repeat) => f
.debug_tuple("EmitRepeatChangedEvent")
.field(&repeat)
.finish(),
PlayerCommand::EmitAutoPlayChangedEvent(auto_play) => f
.debug_tuple("EmitAutoPlayChangedEvent")
.field(&auto_play)
.finish(),
}
}
}
2018-02-10 15:26:08 +00:00
impl fmt::Debug for PlayerState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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,
length: u64,
2018-02-10 15:26:08 +00:00
}
impl<T: Read + Seek> Subfile<T> {
pub fn new(mut stream: T, offset: u64, length: u64) -> Result<Subfile<T>, io::Error> {
let target = SeekFrom::Start(offset);
stream.seek(target)?;
Ok(Subfile {
stream,
offset,
length,
})
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> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let pos = match pos {
SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset),
2022-01-13 18:12:48 +00:00
SeekFrom::End(offset) => {
if (self.length as i64 - offset) < self.offset as i64 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"newpos would be < self.offset",
));
}
pos
}
_ => pos,
};
let newpos = self.stream.seek(pos)?;
2022-01-13 18:12:48 +00:00
Ok(newpos - self.offset)
}
}
2018-02-10 15:26:08 +00:00
impl<R> MediaSource for Subfile<R>
where
2022-02-02 00:02:14 +00:00
R: Read + Seek + Send + Sync,
{
fn is_seekable(&self) -> bool {
true
}
fn byte_len(&self) -> Option<u64> {
Some(self.length)
2018-02-10 15:26:08 +00:00
}
}