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;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum SpotifyTrackType {
|
pub enum SpotifyAudioType {
|
||||||
Track,
|
Track,
|
||||||
Podcast,
|
Podcast,
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ pub enum SpotifyTrackType {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct SpotifyId {
|
pub struct SpotifyId {
|
||||||
pub id: u128,
|
pub id: u128,
|
||||||
pub track_type: SpotifyTrackType,
|
pub audio_type: SpotifyAudioType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -23,7 +23,7 @@ impl SpotifyId {
|
||||||
fn as_track(n: u128) -> SpotifyId {
|
fn as_track(n: u128) -> SpotifyId {
|
||||||
SpotifyId {
|
SpotifyId {
|
||||||
id: n.to_owned(),
|
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>>();
|
let parts = uri.split(":").collect::<Vec<&str>>();
|
||||||
if uri.contains(":show:") || uri.contains(":episode:") {
|
if uri.contains(":show:") || uri.contains(":episode:") {
|
||||||
let mut spotify_id = SpotifyId::from_base62(parts[2]).unwrap();
|
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)
|
Ok(spotify_id)
|
||||||
} else {
|
} else {
|
||||||
SpotifyId::from_base62(parts[2])
|
SpotifyId::from_base62(parts[2])
|
||||||
|
|
|
@ -13,7 +13,7 @@ use linear_map::LinearMap;
|
||||||
|
|
||||||
use librespot_core::mercury::MercuryError;
|
use librespot_core::mercury::MercuryError;
|
||||||
use librespot_core::session::Session;
|
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;
|
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
||||||
|
|
||||||
|
@ -52,13 +52,78 @@ where
|
||||||
&& (!has_allowed || countrylist_contains(allowed.as_str(), country))
|
&& (!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 {
|
pub trait Metadata: Send + Sized + 'static {
|
||||||
type Message: protobuf::Message;
|
type Message: protobuf::Message;
|
||||||
|
|
||||||
fn base_url() -> &'static str;
|
fn base_url() -> &'static str;
|
||||||
fn parse(msg: &Self::Message, session: &Session) -> Self;
|
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 uri = format!("{}/{}", Self::base_url(), id.to_base16());
|
||||||
let request = session.mercury().get(uri);
|
let request = session.mercury().get(uri);
|
||||||
|
|
||||||
|
@ -111,6 +176,7 @@ pub struct Episode {
|
||||||
pub struct Show {
|
pub struct Show {
|
||||||
pub id: SpotifyId,
|
pub id: SpotifyId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub publisher: String,
|
||||||
pub episodes: Vec<SpotifyId>,
|
pub episodes: Vec<SpotifyId>,
|
||||||
pub covers: Vec<FileId>,
|
pub covers: Vec<FileId>,
|
||||||
}
|
}
|
||||||
|
@ -323,6 +389,7 @@ impl Metadata for Show {
|
||||||
Show {
|
Show {
|
||||||
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
|
id: SpotifyId::from_raw(msg.get_gid()).unwrap(),
|
||||||
name: msg.get_name().to_owned(),
|
name: msg.get_name().to_owned(),
|
||||||
|
publisher: msg.get_publisher().to_owned(),
|
||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
covers: covers,
|
covers: covers,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ use std::time::Duration;
|
||||||
|
|
||||||
use config::{Bitrate, PlayerConfig};
|
use config::{Bitrate, PlayerConfig};
|
||||||
use librespot_core::session::Session;
|
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::{AudioDecrypt, AudioFile};
|
||||||
use audio::{VorbisDecoder, VorbisPacket};
|
use audio::{VorbisDecoder, VorbisPacket};
|
||||||
use audio_backend::Sink;
|
use audio_backend::Sink;
|
||||||
use metadata::{Episode, FileFormat, Metadata, Track};
|
use metadata::{AudioItem, FileFormat};
|
||||||
use mixer::AudioFilter;
|
use mixer::AudioFilter;
|
||||||
|
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
@ -512,87 +512,65 @@ impl PlayerInternal {
|
||||||
let _ = self.event_sender.unbounded_send(event.clone());
|
let _ = self.event_sender.unbounded_send(event.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_available_alternative<'a>(&self, track: &'a Track) -> Option<Cow<'a, Track>> {
|
fn find_available_alternative<'a>(&self, audio: &'a AudioItem) -> Option<Cow<'a, AudioItem>> {
|
||||||
if track.available {
|
if audio.available {
|
||||||
Some(Cow::Borrowed(track))
|
Some(Cow::Borrowed(audio))
|
||||||
} else {
|
} else {
|
||||||
let alternatives = track
|
if let Some(alternatives) = &audio.alternatives {
|
||||||
.alternatives
|
let alternatives = alternatives
|
||||||
.iter()
|
.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();
|
let alternatives = future::join_all(alternatives).wait().unwrap();
|
||||||
|
|
||||||
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
|
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)> {
|
fn load_track(&self, spotify_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
|
||||||
let file_id = self.get_file_id(spotify_id).unwrap();
|
let audio = AudioItem::get_audio_item(&self.session, spotify_id)
|
||||||
info!("{:?} -> {:?}", spotify_id, file_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 key = self.session.audio_key().request(spotify_id, file_id);
|
||||||
let encrypted_file = AudioFile::open(&self.session, file_id);
|
let encrypted_file = AudioFile::open(&self.session, file_id);
|
||||||
|
@ -619,9 +597,7 @@ impl PlayerInternal {
|
||||||
Err(err) => error!("Vorbis error: {:?}", err),
|
Err(err) => error!("Vorbis error: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
info!("<{}> loaded", audio.name);
|
||||||
// info!("Track \"{}\" loaded", track.name);
|
|
||||||
|
|
||||||
Some((decoder, normalisation_factor))
|
Some((decoder, normalisation_factor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ pub const FILES: &'static [(&'static str, u32)] = &[
|
||||||
("proto/authentication.proto", 2098196376),
|
("proto/authentication.proto", 2098196376),
|
||||||
("proto/keyexchange.proto", 451735664),
|
("proto/keyexchange.proto", 451735664),
|
||||||
("proto/mercury.proto", 709993906),
|
("proto/mercury.proto", 709993906),
|
||||||
("proto/metadata.proto", 1409162985),
|
("proto/metadata.proto", 334477813),
|
||||||
("proto/pubsub.proto", 2686584829),
|
("proto/pubsub.proto", 2686584829),
|
||||||
("proto/spirc.proto", 1587493382),
|
("proto/spirc.proto", 1587493382),
|
||||||
];
|
];
|
||||||
|
|
|
@ -207,6 +207,7 @@ enum MediaType {
|
||||||
enum PassthroughEnum {
|
enum PassthroughEnum {
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
NONE = 1;
|
NONE = 1;
|
||||||
|
ALLOWED = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Episode {
|
message Episode {
|
||||||
|
|
|
@ -8896,6 +8896,7 @@ impl ::protobuf::reflect::ProtobufValue for MediaType {
|
||||||
pub enum PassthroughEnum {
|
pub enum PassthroughEnum {
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
NONE = 1,
|
NONE = 1,
|
||||||
|
ALLOWED = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
||||||
|
@ -8907,6 +8908,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
||||||
match value {
|
match value {
|
||||||
0 => ::std::option::Option::Some(PassthroughEnum::UNKNOWN),
|
0 => ::std::option::Option::Some(PassthroughEnum::UNKNOWN),
|
||||||
1 => ::std::option::Option::Some(PassthroughEnum::NONE),
|
1 => ::std::option::Option::Some(PassthroughEnum::NONE),
|
||||||
|
2 => ::std::option::Option::Some(PassthroughEnum::ALLOWED),
|
||||||
_ => ::std::option::Option::None
|
_ => ::std::option::Option::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8915,6 +8917,7 @@ impl ::protobuf::ProtobufEnum for PassthroughEnum {
|
||||||
static values: &'static [PassthroughEnum] = &[
|
static values: &'static [PassthroughEnum] = &[
|
||||||
PassthroughEnum::UNKNOWN,
|
PassthroughEnum::UNKNOWN,
|
||||||
PassthroughEnum::NONE,
|
PassthroughEnum::NONE,
|
||||||
|
PassthroughEnum::ALLOWED,
|
||||||
];
|
];
|
||||||
values
|
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\
|
\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\
|
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\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\
|
\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\x1a\0B\0b\
|
ughEnum\x12\x0b\n\x07UNKNOWN\x10\0\x12\x08\n\x04NONE\x10\x01\x12\x0b\n\
|
||||||
\x06proto2\
|
\x07ALLOWED\x10\x02\x1a\0B\0b\x06proto2\
|
||||||
";
|
";
|
||||||
|
|
||||||
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
||||||
|
|
Loading…
Reference in a new issue