Skip to content

Commit 89577d1

Browse files
authored
Improve player (#823)
* Improve error handling * Harmonize `Seek`: Make the decoders and player use the same math for converting between samples and milliseconds * Reduce duplicate calls: Make decoder seek in PCM, not ms * Simplify decoder errors with `thiserror`
1 parent 949ca4f commit 89577d1

11 files changed

Lines changed: 280 additions & 242 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- [playback] Add `--normalisation-type auto` that switches between album and track automatically
1616

1717
### Changed
18-
- [audio, playback] Moved `VorbisDecoder`, `VorbisError`, `AudioPacket`, `PassthroughDecoder`, `PassthroughError`, `AudioError`, `AudioDecoder` and the `convert` module from `librespot-audio` to `librespot-playback`. The underlying crates `vorbis`, `librespot-tremor`, `lewton` and `ogg` should be used directly. (breaking)
18+
- [audio, playback] Moved `VorbisDecoder`, `VorbisError`, `AudioPacket`, `PassthroughDecoder`, `PassthroughError`, `DecoderError`, `AudioDecoder` and the `convert` module from `librespot-audio` to `librespot-playback`. The underlying crates `vorbis`, `librespot-tremor`, `lewton` and `ogg` should be used directly. (breaking)
1919
- [audio, playback] Use `Duration` for time constants and functions (breaking)
2020
- [connect, playback] Moved volume controls from `librespot-connect` to `librespot-playback` crate
2121
- [connect] Synchronize player volume with mixer volume on playback

playback/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ byteorder = "1.4"
2525
shell-words = "1.0.0"
2626
tokio = { version = "1", features = ["sync"] }
2727
zerocopy = { version = "0.3" }
28+
thiserror = { version = "1" }
2829

2930
# Backends
3031
alsa = { version = "0.5", optional = true }
@@ -40,7 +41,6 @@ glib = { version = "0.10", optional = true }
4041
# Rodio dependencies
4142
rodio = { version = "0.14", optional = true, default-features = false }
4243
cpal = { version = "0.13", optional = true }
43-
thiserror = { version = "1", optional = true }
4444

4545
# Decoder
4646
lewton = "0.10"
@@ -51,11 +51,11 @@ rand = "0.8"
5151
rand_distr = "0.4"
5252

5353
[features]
54-
alsa-backend = ["alsa", "thiserror"]
54+
alsa-backend = ["alsa"]
5555
portaudio-backend = ["portaudio-rs"]
56-
pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding", "thiserror"]
56+
pulseaudio-backend = ["libpulse-binding", "libpulse-simple-binding"]
5757
jackaudio-backend = ["jack"]
58-
rodio-backend = ["rodio", "cpal", "thiserror"]
59-
rodiojack-backend = ["rodio", "cpal/jack", "thiserror"]
58+
rodio-backend = ["rodio", "cpal"]
59+
rodiojack-backend = ["rodio", "cpal/jack"]
6060
sdl-backend = ["sdl2"]
6161
gstreamer-backend = ["gstreamer", "gstreamer-app", "glib"]

playback/src/audio_backend/jackaudio.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ impl Open for JackSink {
7171

7272
impl Sink for JackSink {
7373
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> io::Result<()> {
74-
let samples_f32: &[f32] = &converter.f64_to_f32(packet.samples());
74+
let samples = packet
75+
.samples()
76+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
77+
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
7578
for sample in samples_f32.iter() {
7679
let res = self.send.send(*sample);
7780
if res.is_err() {

playback/src/audio_backend/portaudio.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@ impl<'a> Sink for PortAudioSink<'a> {
148148
};
149149
}
150150

151-
let samples = packet.samples();
151+
let samples = packet
152+
.samples()
153+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
154+
152155
let result = match self {
153156
Self::F32(stream, _parameters) => {
154157
let samples_f32: &[f32] = &converter.f64_to_f32(samples);

playback/src/audio_backend/rodio.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ pub fn open(host: cpal::Host, device: Option<String>, format: AudioFormat) -> Ro
176176

177177
impl Sink for RodioSink {
178178
fn write(&mut self, packet: &AudioPacket, converter: &mut Converter) -> io::Result<()> {
179-
let samples = packet.samples();
179+
let samples = packet
180+
.samples()
181+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
180182
match self.format {
181183
AudioFormat::F32 => {
182184
let samples_f32: &[f32] = &converter.f64_to_f32(samples);

playback/src/audio_backend/sdl.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ impl Sink for SdlSink {
9292
}};
9393
}
9494

95-
let samples = packet.samples();
95+
let samples = packet
96+
.samples()
97+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
9698
match self {
9799
Self::F32(queue) => {
98100
let samples_f32: &[f32] = &converter.f64_to_f32(samples);
Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,46 @@
1-
use super::{AudioDecoder, AudioError, AudioPacket};
1+
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
22

3+
use lewton::audio::AudioReadError::AudioIsHeader;
34
use lewton::inside_ogg::OggStreamReader;
45
use lewton::samples::InterleavedSamples;
6+
use lewton::OggReadError::NoCapturePatternFound;
7+
use lewton::VorbisError::{BadAudio, OggError};
58

6-
use std::error;
7-
use std::fmt;
89
use std::io::{Read, Seek};
9-
use std::time::Duration;
1010

1111
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
12-
pub struct VorbisError(lewton::VorbisError);
1312

1413
impl<R> VorbisDecoder<R>
1514
where
1615
R: Read + Seek,
1716
{
18-
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
19-
Ok(VorbisDecoder(OggStreamReader::new(input)?))
17+
pub fn new(input: R) -> DecoderResult<VorbisDecoder<R>> {
18+
let reader =
19+
OggStreamReader::new(input).map_err(|e| DecoderError::LewtonDecoder(e.to_string()))?;
20+
Ok(VorbisDecoder(reader))
2021
}
2122
}
2223

2324
impl<R> AudioDecoder for VorbisDecoder<R>
2425
where
2526
R: Read + Seek,
2627
{
27-
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
28-
let absgp = Duration::from_millis(ms as u64 * crate::SAMPLE_RATE as u64).as_secs();
29-
match self.0.seek_absgp_pg(absgp as u64) {
30-
Ok(_) => Ok(()),
31-
Err(err) => Err(AudioError::VorbisError(err.into())),
32-
}
28+
fn seek(&mut self, absgp: u64) -> DecoderResult<()> {
29+
self.0
30+
.seek_absgp_pg(absgp)
31+
.map_err(|e| DecoderError::LewtonDecoder(e.to_string()))?;
32+
Ok(())
3333
}
3434

35-
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
36-
use lewton::audio::AudioReadError::AudioIsHeader;
37-
use lewton::OggReadError::NoCapturePatternFound;
38-
use lewton::VorbisError::{BadAudio, OggError};
35+
fn next_packet(&mut self) -> DecoderResult<Option<AudioPacket>> {
3936
loop {
4037
match self.0.read_dec_packet_generic::<InterleavedSamples<f32>>() {
4138
Ok(Some(packet)) => return Ok(Some(AudioPacket::samples_from_f32(packet.samples))),
4239
Ok(None) => return Ok(None),
43-
4440
Err(BadAudio(AudioIsHeader)) => (),
4541
Err(OggError(NoCapturePatternFound)) => (),
46-
Err(err) => return Err(AudioError::VorbisError(err.into())),
42+
Err(e) => return Err(DecoderError::LewtonDecoder(e.to_string())),
4743
}
4844
}
4945
}
5046
}
51-
52-
impl From<lewton::VorbisError> for VorbisError {
53-
fn from(err: lewton::VorbisError) -> VorbisError {
54-
VorbisError(err)
55-
}
56-
}
57-
58-
impl fmt::Debug for VorbisError {
59-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60-
fmt::Debug::fmt(&self.0, f)
61-
}
62-
}
63-
64-
impl fmt::Display for VorbisError {
65-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66-
fmt::Display::fmt(&self.0, f)
67-
}
68-
}
69-
70-
impl error::Error for VorbisError {
71-
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
72-
error::Error::source(&self.0)
73-
}
74-
}

playback/src/decoder/mod.rs

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
use std::fmt;
1+
use thiserror::Error;
22

33
mod lewton_decoder;
4-
pub use lewton_decoder::{VorbisDecoder, VorbisError};
4+
pub use lewton_decoder::VorbisDecoder;
55

66
mod passthrough_decoder;
7-
pub use passthrough_decoder::{PassthroughDecoder, PassthroughError};
7+
pub use passthrough_decoder::PassthroughDecoder;
8+
9+
#[derive(Error, Debug)]
10+
pub enum DecoderError {
11+
#[error("Lewton Decoder Error: {0}")]
12+
LewtonDecoder(String),
13+
#[error("Passthrough Decoder Error: {0}")]
14+
PassthroughDecoder(String),
15+
}
16+
17+
pub type DecoderResult<T> = Result<T, DecoderError>;
18+
19+
#[derive(Error, Debug)]
20+
pub enum AudioPacketError {
21+
#[error("Decoder OggData Error: Can't return OggData on Samples")]
22+
OggData,
23+
#[error("Decoder Samples Error: Can't return Samples on OggData")]
24+
Samples,
25+
}
26+
27+
pub type AudioPacketResult<T> = Result<T, AudioPacketError>;
828

929
pub enum AudioPacket {
1030
Samples(Vec<f64>),
@@ -17,17 +37,17 @@ impl AudioPacket {
1737
AudioPacket::Samples(f64_samples)
1838
}
1939

20-
pub fn samples(&self) -> &[f64] {
40+
pub fn samples(&self) -> AudioPacketResult<&[f64]> {
2141
match self {
22-
AudioPacket::Samples(s) => s,
23-
AudioPacket::OggData(_) => panic!("can't return OggData on samples"),
42+
AudioPacket::Samples(s) => Ok(s),
43+
AudioPacket::OggData(_) => Err(AudioPacketError::OggData),
2444
}
2545
}
2646

27-
pub fn oggdata(&self) -> &[u8] {
47+
pub fn oggdata(&self) -> AudioPacketResult<&[u8]> {
2848
match self {
29-
AudioPacket::Samples(_) => panic!("can't return samples on OggData"),
30-
AudioPacket::OggData(d) => d,
49+
AudioPacket::OggData(d) => Ok(d),
50+
AudioPacket::Samples(_) => Err(AudioPacketError::Samples),
3151
}
3252
}
3353

@@ -39,34 +59,7 @@ impl AudioPacket {
3959
}
4060
}
4161

42-
#[derive(Debug)]
43-
pub enum AudioError {
44-
PassthroughError(PassthroughError),
45-
VorbisError(VorbisError),
46-
}
47-
48-
impl fmt::Display for AudioError {
49-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50-
match self {
51-
AudioError::PassthroughError(err) => write!(f, "PassthroughError({})", err),
52-
AudioError::VorbisError(err) => write!(f, "VorbisError({})", err),
53-
}
54-
}
55-
}
56-
57-
impl From<VorbisError> for AudioError {
58-
fn from(err: VorbisError) -> AudioError {
59-
AudioError::VorbisError(err)
60-
}
61-
}
62-
63-
impl From<PassthroughError> for AudioError {
64-
fn from(err: PassthroughError) -> AudioError {
65-
AudioError::PassthroughError(err)
66-
}
67-
}
68-
6962
pub trait AudioDecoder {
70-
fn seek(&mut self, ms: i64) -> Result<(), AudioError>;
71-
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError>;
63+
fn seek(&mut self, absgp: u64) -> DecoderResult<()>;
64+
fn next_packet(&mut self) -> DecoderResult<Option<AudioPacket>>;
7265
}

0 commit comments

Comments
 (0)