mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Streamline and refactor Podcast support,
<Metadata> Add publisher to `Show` Add `ALLOWED` to `PassthroughEnum`
This commit is contained in:
parent
0cb7a3f7c8
commit
8eb51e9b55
6 changed files with 135 additions and 88 deletions
|
@ -2,7 +2,7 @@ use std;
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum SpotifyTrackType {
|
||||
pub enum SpotifyAudioType {
|
||||
Track,
|
||||
Podcast,
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub enum SpotifyTrackType {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SpotifyId {
|
||||
pub id: u128,
|
||||
pub track_type: SpotifyTrackType,
|
||||
pub audio_type: SpotifyAudioType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -23,7 +23,7 @@ impl SpotifyId {
|
|||
fn as_track(n: u128) -> SpotifyId {
|
||||
SpotifyId {
|
||||
id: n.to_owned(),
|
||||
track_type: SpotifyTrackType::Track,
|
||||
audio_type: SpotifyAudioType::Track,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ impl SpotifyId {
|
|||
let parts = uri.split(":").collect::<Vec<&str>>();
|
||||
if uri.contains(":show:") || uri.contains(":episode:") {
|
||||
let mut spotify_id = SpotifyId::from_base62(parts[2]).unwrap();
|
||||
spotify_id.track_type = SpotifyTrackType::Podcast;
|
||||
let _ = std::mem::replace(&mut spotify_id.audio_type, SpotifyAudioType::Podcast);
|
||||
Ok(spotify_id)
|
||||
} else {
|
||||
SpotifyId::from_base62(parts[2])
|
||||
|
|
|
@ -13,7 +13,7 @@ use linear_map::LinearMap;
|
|||
|
||||
use librespot_core::mercury::MercuryError;
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::{FileId, SpotifyId};
|
||||
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
|
||||
|
||||
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
||||
|
||||
|
@ -52,13 +52,78 @@ where
|
|||
&& (!has_allowed || countrylist_contains(allowed.as_str(), country))
|
||||
}
|
||||
|
||||
// A wrapper with fields the player needs
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AudioItem {
|
||||
pub id: SpotifyId,
|
||||
pub uri: String,
|
||||
pub files: LinearMap<FileFormat, FileId>,
|
||||
pub name: String,
|
||||
pub available: bool,
|
||||
pub alternatives: Option<Vec<SpotifyId>>,
|
||||
}
|
||||
|
||||
impl AudioItem {
|
||||
pub fn get_audio_item(
|
||||
session: &Session,
|
||||
id: SpotifyId,
|
||||
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
|
||||
match id.audio_type {
|
||||
SpotifyAudioType::Track => Track::get_audio_item(session, id),
|
||||
SpotifyAudioType::Podcast => Episode::get_audio_item(session, id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AudioFiles {
|
||||
fn get_audio_item(
|
||||
session: &Session,
|
||||
id: SpotifyId,
|
||||
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>>;
|
||||
}
|
||||
|
||||
impl AudioFiles for Track {
|
||||
fn get_audio_item(
|
||||
session: &Session,
|
||||
id: SpotifyId,
|
||||
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
|
||||
Box::new(Self::get(session, id).and_then(move |item| {
|
||||
Ok(AudioItem {
|
||||
id: id,
|
||||
uri: format!("spotify:track:{}", id.to_base62()),
|
||||
files: item.files,
|
||||
name: item.name,
|
||||
available: item.available,
|
||||
alternatives: Some(item.alternatives),
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioFiles for Episode {
|
||||
fn get_audio_item(
|
||||
session: &Session,
|
||||
id: SpotifyId,
|
||||
) -> Box<dyn Future<Item = AudioItem, Error = MercuryError>> {
|
||||
Box::new(Self::get(session, id).and_then(move |item| {
|
||||
Ok(AudioItem {
|
||||
id: id,
|
||||
uri: format!("spotify:episode:{}", id.to_base62()),
|
||||
files: item.files,
|
||||
name: item.name,
|
||||
available: item.available,
|
||||
alternatives: None,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
pub trait Metadata: Send + Sized + 'static {
|
||||
type Message: protobuf::Message;
|
||||
|
||||
fn base_url() -> &'static str;
|
||||
fn parse(msg: &Self::Message, session: &Session) -> Self;
|
||||
|
||||
fn get(session: &Session, id: SpotifyId) -> Box<Future<Item = Self, Error = MercuryError>> {
|
||||
fn get(session: &Session, id: SpotifyId) -> Box<dyn Future<Item = Self, Error = MercuryError>> {
|
||||
let uri = format!("{}/{}", Self::base_url(), id.to_base16());
|
||||
let request = session.mercury().get(uri);
|
||||
|
||||
|
@ -111,6 +176,7 @@ pub struct Episode {
|
|||
pub struct Show {
|
||||
pub id: SpotifyId,
|
||||
pub name: String,
|
||||
pub publisher: String,
|
||||
pub episodes: Vec<SpotifyId>,
|
||||
pub covers: Vec<FileId>,
|
||||
}
|
||||
|
@ -323,6 +389,7 @@ impl Metadata for Show {
|
|||
Show {
|
||||
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
|
||||
name: msg.get_name().to_owned(),
|
||||
publisher: msg.get_publisher().to_owned(),
|
||||
episodes: episodes,
|
||||
covers: covers,
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ use std::time::Duration;
|
|||
|
||||
use config::{Bitrate, PlayerConfig};
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::{FileId, SpotifyId, SpotifyTrackType};
|
||||
use librespot_core::spotify_id::SpotifyId;
|
||||
|
||||
use audio::{AudioDecrypt, AudioFile};
|
||||
use audio::{VorbisDecoder, VorbisPacket};
|
||||
use audio_backend::Sink;
|
||||
use metadata::{Episode, FileFormat, Metadata, Track};
|
||||
use metadata::{AudioItem, FileFormat};
|
||||
use mixer::AudioFilter;
|
||||
|
||||
pub struct Player {
|
||||
|
@ -512,87 +512,65 @@ impl PlayerInternal {
|
|||
let _ = self.event_sender.unbounded_send(event.clone());
|
||||
}
|
||||
|
||||
fn find_available_alternative<'a>(&self, track: &'a Track) -> Option<Cow<'a, Track>> {
|
||||
if track.available {
|
||||
Some(Cow::Borrowed(track))
|
||||
fn find_available_alternative<'a>(&self, audio: &'a AudioItem) -> Option<Cow<'a, AudioItem>> {
|
||||
if audio.available {
|
||||
Some(Cow::Borrowed(audio))
|
||||
} else {
|
||||
let alternatives = track
|
||||
.alternatives
|
||||
if let Some(alternatives) = &audio.alternatives {
|
||||
let alternatives = alternatives
|
||||
.iter()
|
||||
.map(|alt_id| Track::get(&self.session, *alt_id));
|
||||
.map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id));
|
||||
let alternatives = future::join_all(alternatives).wait().unwrap();
|
||||
|
||||
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_id(&self, spotify_id: SpotifyId) -> Option<FileId> {
|
||||
let file_id = match spotify_id.track_type {
|
||||
SpotifyTrackType::Track => {
|
||||
let track = Track::get(&self.session, spotify_id).wait().unwrap();
|
||||
|
||||
info!(
|
||||
"Loading track \"{}\" with Spotify URI \"spotify:track:{}\"",
|
||||
track.name,
|
||||
spotify_id.to_base62()
|
||||
);
|
||||
|
||||
let track = match self.find_available_alternative(&track) {
|
||||
Some(track) => track,
|
||||
None => {
|
||||
warn!("Track \"{}\" is not available", track.name);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let format = match self.config.bitrate {
|
||||
Bitrate::Bitrate96 => FileFormat::OGG_VORBIS_96,
|
||||
Bitrate::Bitrate160 => FileFormat::OGG_VORBIS_160,
|
||||
Bitrate::Bitrate320 => FileFormat::OGG_VORBIS_320,
|
||||
};
|
||||
match track.files.get(&format) {
|
||||
Some(&file_id) => file_id,
|
||||
None => {
|
||||
warn!("Track \"{}\" is not available in format {:?}", track.name, format);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This should be refactored!
|
||||
SpotifyTrackType::Podcast => {
|
||||
let episode = Episode::get(&self.session, spotify_id).wait().unwrap();
|
||||
info!("Episode {:?}", episode);
|
||||
|
||||
info!(
|
||||
"Loading episode \"{}\" with Spotify URI \"spotify:episode:{}\"",
|
||||
episode.name,
|
||||
spotify_id.to_base62()
|
||||
);
|
||||
|
||||
// Podcasts seem to have only 96 OGG_VORBIS support, other filetypes indicate
|
||||
// AAC_24, MP4_128, MP4_128_DUAL, MP3_96 among others
|
||||
let format = match self.config.bitrate {
|
||||
_ => FileFormat::OGG_VORBIS_96,
|
||||
};
|
||||
|
||||
match episode.files.get(&format) {
|
||||
Some(&file_id) => file_id,
|
||||
None => {
|
||||
warn!(
|
||||
"Episode \"{}\" is not available in format {:?}",
|
||||
episode.name, format
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return Some(file_id);
|
||||
}
|
||||
|
||||
fn load_track(&self, spotify_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
|
||||
let file_id = self.get_file_id(spotify_id).unwrap();
|
||||
info!("{:?} -> {:?}", spotify_id, file_id);
|
||||
let audio = AudioItem::get_audio_item(&self.session, spotify_id)
|
||||
.wait()
|
||||
.unwrap();
|
||||
info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri);
|
||||
|
||||
let audio = match self.find_available_alternative(&audio) {
|
||||
Some(audio) => audio,
|
||||
None => {
|
||||
warn!("<{}> is not available", audio.uri);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
// (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,
|
||||
],
|
||||
};
|
||||
let format = formats
|
||||
.iter()
|
||||
.find(|format| audio.files.contains_key(format))
|
||||
.unwrap();
|
||||
|
||||
let file_id = match audio.files.get(&format) {
|
||||
Some(&file_id) => file_id,
|
||||
None => {
|
||||
warn!("<{}> in not available in format {:?}", audio.name, format);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let key = self.session.audio_key().request(spotify_id, file_id);
|
||||
let encrypted_file = AudioFile::open(&self.session, file_id);
|
||||
|
@ -619,9 +597,7 @@ impl PlayerInternal {
|
|||
Err(err) => error!("Vorbis error: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
// info!("Track \"{}\" loaded", track.name);
|
||||
|
||||
info!("<{}> loaded", audio.name);
|
||||
Some((decoder, normalisation_factor))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ pub const FILES: &'static [(&'static str, u32)] = &[
|
|||
("proto/authentication.proto", 2098196376),
|
||||
("proto/keyexchange.proto", 451735664),
|
||||
("proto/mercury.proto", 709993906),
|
||||
("proto/metadata.proto", 1409162985),
|
||||
("proto/metadata.proto", 334477813),
|
||||
("proto/pubsub.proto", 2686584829),
|
||||
("proto/spirc.proto", 1587493382),
|
||||
];
|
||||
|
|
|
@ -207,6 +207,7 @@ enum MediaType {
|
|||
enum PassthroughEnum {
|
||||
UNKNOWN = 0;
|
||||
NONE = 1;
|
||||
ALLOWED = 2;
|
||||
}
|
||||
|
||||
message Episode {
|
||||
|
|
|
@ -8896,6 +8896,7 @@ impl ::protobuf::reflect::ProtobufValue for MediaType {
|
|||
pub enum PassthroughEnum {
|
||||
UNKNOWN = 0,
|
||||
NONE = 1,
|
||||
ALLOWED = 2,
|
||||
}
|
||||
|
||||
impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
||||
|
@ -8907,6 +8908,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
|||
match value {
|
||||
0 => ::std::option::Option::Some(PassthroughEnum::UNKNOWN),
|
||||
1 => ::std::option::Option::Some(PassthroughEnum::NONE),
|
||||
2 => ::std::option::Option::Some(PassthroughEnum::ALLOWED),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
|
@ -8915,6 +8917,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
|||
static values: &'static [PassthroughEnum] = &[
|
||||
PassthroughEnum::UNKNOWN,
|
||||
PassthroughEnum::NONE,
|
||||
PassthroughEnum::ALLOWED,
|
||||
];
|
||||
values
|
||||
}
|
||||
|
@ -9057,9 +9060,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
|||
\rOriginalAudio\x12\x0e\n\x04uuid\x18\x01\x20\x01(\x0cB\0:\0*>\n\x10Cons\
|
||||
umptionOrder\x12\x0e\n\nSEQUENTIAL\x10\x01\x12\x0c\n\x08EPISODIC\x10\x02\
|
||||
\x12\n\n\x06RECENT\x10\x03\x1a\0*.\n\tMediaType\x12\t\n\x05MIXED\x10\0\
|
||||
\x12\t\n\x05AUDIO\x10\x01\x12\t\n\x05VIDEO\x10\x02\x1a\0**\n\x0fPassthro\
|
||||
ughEnum\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04NONE\x10\x01\x1a\0B\0b\
|
||||
\x06proto2\
|
||||
\x12\t\n\x05AUDIO\x10\x01\x12\t\n\x05VIDEO\x10\x02\x1a\0*7\n\x0fPassthro\
|
||||
ughEnum\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04NONE\x10\x01\x12\x0b\n\
|
||||
\x07ALLOWED\x10\x02\x1a\0B\0b\x06proto2\
|
||||
";
|
||||
|
||||
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
||||
|
|
Loading…
Reference in a new issue