mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Notify track position after skipping malformed packets
This commit is contained in:
parent
67ae0fcf8d
commit
a33014f9c5
4 changed files with 67 additions and 13 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
mod passthrough_decoder;
|
mod passthrough_decoder;
|
||||||
|
@ -54,9 +56,31 @@ impl AudioPacket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
|
pub enum AudioPositionKind {
|
||||||
|
// the position is at the expected packet
|
||||||
|
Current,
|
||||||
|
// the decoder skipped some corrupted or invalid data,
|
||||||
|
// and the position is now later than expected
|
||||||
|
SkippedTo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AudioPacketPosition {
|
||||||
|
pub position_ms: u32,
|
||||||
|
pub kind: AudioPositionKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AudioPacketPosition {
|
||||||
|
type Target = u32;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.position_ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait AudioDecoder {
|
pub trait AudioDecoder {
|
||||||
fn seek(&mut self, position_ms: u32) -> Result<u32, DecoderError>;
|
fn seek(&mut self, position_ms: u32) -> Result<u32, DecoderError>;
|
||||||
fn next_packet(&mut self) -> DecoderResult<Option<(u32, AudioPacket)>>;
|
fn next_packet(&mut self) -> DecoderResult<Option<(AudioPacketPosition, AudioPacket)>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DecoderError> for librespot_core::error::Error {
|
impl From<DecoderError> for librespot_core::error::Error {
|
||||||
|
|
|
@ -7,7 +7,9 @@ use std::{
|
||||||
// TODO: move this to the Symphonia Ogg demuxer
|
// TODO: move this to the Symphonia Ogg demuxer
|
||||||
use ogg::{OggReadError, Packet, PacketReader, PacketWriteEndInfo, PacketWriter};
|
use ogg::{OggReadError, Packet, PacketReader, PacketWriteEndInfo, PacketWriter};
|
||||||
|
|
||||||
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
use super::{
|
||||||
|
AudioDecoder, AudioPacket, AudioPacketPosition, AudioPositionKind, DecoderError, DecoderResult,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
metadata::audio::{AudioFileFormat, AudioFiles},
|
metadata::audio::{AudioFileFormat, AudioFiles},
|
||||||
|
@ -137,7 +139,7 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_packet(&mut self) -> DecoderResult<Option<(u32, AudioPacket)>> {
|
fn next_packet(&mut self) -> DecoderResult<Option<(AudioPacketPosition, AudioPacket)>> {
|
||||||
// write headers if we are (re)starting
|
// write headers if we are (re)starting
|
||||||
if !self.bos {
|
if !self.bos {
|
||||||
self.wtr
|
self.wtr
|
||||||
|
@ -208,8 +210,14 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
||||||
|
|
||||||
if !data.is_empty() {
|
if !data.is_empty() {
|
||||||
let position_ms = Self::position_pcm_to_ms(pckgp_page);
|
let position_ms = Self::position_pcm_to_ms(pckgp_page);
|
||||||
|
let packet_position = AudioPacketPosition {
|
||||||
|
position_ms,
|
||||||
|
kind: AudioPositionKind::Current,
|
||||||
|
};
|
||||||
|
|
||||||
let ogg_data = AudioPacket::Raw(std::mem::take(data));
|
let ogg_data = AudioPacket::Raw(std::mem::take(data));
|
||||||
return Ok(Some((position_ms, ogg_data)));
|
|
||||||
|
return Ok(Some((packet_position, ogg_data)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@ use symphonia::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{AudioDecoder, AudioPacket, DecoderError, DecoderResult};
|
use super::{
|
||||||
|
AudioDecoder, AudioPacket, AudioPacketPosition, AudioPositionKind, DecoderError, DecoderResult,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
metadata::audio::{AudioFileFormat, AudioFiles},
|
metadata::audio::{AudioFileFormat, AudioFiles},
|
||||||
|
@ -170,7 +172,9 @@ impl AudioDecoder for SymphoniaDecoder {
|
||||||
Ok(self.ts_to_ms(seeked_to_ts.actual_ts))
|
Ok(self.ts_to_ms(seeked_to_ts.actual_ts))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_packet(&mut self) -> DecoderResult<Option<(u32, AudioPacket)>> {
|
fn next_packet(&mut self) -> DecoderResult<Option<(AudioPacketPosition, AudioPacket)>> {
|
||||||
|
let mut position_kind = AudioPositionKind::Current;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let packet = match self.format.next_packet() {
|
let packet = match self.format.next_packet() {
|
||||||
Ok(packet) => packet,
|
Ok(packet) => packet,
|
||||||
|
@ -187,6 +191,10 @@ impl AudioDecoder for SymphoniaDecoder {
|
||||||
};
|
};
|
||||||
|
|
||||||
let position_ms = self.ts_to_ms(packet.pts());
|
let position_ms = self.ts_to_ms(packet.pts());
|
||||||
|
let packet_position = AudioPacketPosition {
|
||||||
|
position_ms,
|
||||||
|
kind: position_kind,
|
||||||
|
};
|
||||||
|
|
||||||
match self.decoder.decode(&packet) {
|
match self.decoder.decode(&packet) {
|
||||||
Ok(decoded) => {
|
Ok(decoded) => {
|
||||||
|
@ -200,11 +208,14 @@ impl AudioDecoder for SymphoniaDecoder {
|
||||||
let sample_buffer = self.sample_buffer.as_mut().unwrap(); // guaranteed above
|
let sample_buffer = self.sample_buffer.as_mut().unwrap(); // guaranteed above
|
||||||
sample_buffer.copy_interleaved_ref(decoded);
|
sample_buffer.copy_interleaved_ref(decoded);
|
||||||
let samples = AudioPacket::Samples(sample_buffer.samples().to_vec());
|
let samples = AudioPacket::Samples(sample_buffer.samples().to_vec());
|
||||||
return Ok(Some((position_ms, samples)));
|
|
||||||
|
return Ok(Some((packet_position, samples)));
|
||||||
}
|
}
|
||||||
Err(Error::DecodeError(_)) => {
|
Err(Error::DecodeError(_)) => {
|
||||||
// The packet failed to decode due to corrupted or invalid data, get a new
|
// The packet failed to decode due to corrupted or invalid data, get a new
|
||||||
// packet and try again.
|
// packet and try again.
|
||||||
|
warn!("Skipping malformed audio packet at {} ms", position_ms);
|
||||||
|
position_kind = AudioPositionKind::SkippedTo;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
|
|
|
@ -29,7 +29,10 @@ use crate::{
|
||||||
config::{Bitrate, NormalisationMethod, NormalisationType, PlayerConfig},
|
config::{Bitrate, NormalisationMethod, NormalisationType, PlayerConfig},
|
||||||
convert::Converter,
|
convert::Converter,
|
||||||
core::{util::SeqGenerator, Error, Session, SpotifyId},
|
core::{util::SeqGenerator, Error, Session, SpotifyId},
|
||||||
decoder::{AudioDecoder, AudioPacket, PassthroughDecoder, SymphoniaDecoder},
|
decoder::{
|
||||||
|
AudioDecoder, AudioPacket, AudioPacketPosition, AudioPositionKind, PassthroughDecoder,
|
||||||
|
SymphoniaDecoder,
|
||||||
|
},
|
||||||
metadata::audio::{AudioFileFormat, AudioFiles, AudioItem},
|
metadata::audio::{AudioFileFormat, AudioFiles, AudioItem},
|
||||||
mixer::AudioFilter,
|
mixer::AudioFilter,
|
||||||
};
|
};
|
||||||
|
@ -1103,17 +1106,21 @@ impl Future for PlayerInternal {
|
||||||
{
|
{
|
||||||
match decoder.next_packet() {
|
match decoder.next_packet() {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
if let Some((new_stream_position_ms, ref packet)) = result {
|
if let Some((ref packet_position, ref packet)) = result {
|
||||||
|
let new_stream_position_ms = packet_position.position_ms;
|
||||||
*stream_position_ms = new_stream_position_ms;
|
*stream_position_ms = new_stream_position_ms;
|
||||||
|
|
||||||
if !passthrough {
|
if !passthrough {
|
||||||
match packet.samples() {
|
match packet.samples() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let notify_about_position =
|
// Only notify if we're skipped some packets *or* we are behind.
|
||||||
match *reported_nominal_start_time {
|
// If we're ahead it's probably due to a buffer of the backend
|
||||||
|
// and we're actually in time.
|
||||||
|
let notify_about_position = packet_position.kind
|
||||||
|
!= AudioPositionKind::Current
|
||||||
|
|| match *reported_nominal_start_time {
|
||||||
None => true,
|
None => true,
|
||||||
Some(reported_nominal_start_time) => {
|
Some(reported_nominal_start_time) => {
|
||||||
// only notify if we're behind. If we're ahead it's probably due to a buffer of the backend and we're actually in time.
|
|
||||||
let lag = (Instant::now()
|
let lag = (Instant::now()
|
||||||
- reported_nominal_start_time)
|
- reported_nominal_start_time)
|
||||||
.as_millis()
|
.as_millis()
|
||||||
|
@ -1340,7 +1347,11 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_packet(&mut self, packet: Option<(u32, AudioPacket)>, normalisation_factor: f64) {
|
fn handle_packet(
|
||||||
|
&mut self,
|
||||||
|
packet: Option<(AudioPacketPosition, AudioPacket)>,
|
||||||
|
normalisation_factor: f64,
|
||||||
|
) {
|
||||||
match packet {
|
match packet {
|
||||||
Some((_, mut packet)) => {
|
Some((_, mut packet)) => {
|
||||||
if !packet.is_empty() {
|
if !packet.is_empty() {
|
||||||
|
|
Loading…
Reference in a new issue