mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-28 14:31:06 +00:00
spotify: move Settings to common module
Will be used to implement the lyrics source element. Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1095>
This commit is contained in:
parent
cd177dee86
commit
93503c0ca9
3 changed files with 199 additions and 163 deletions
171
audio/spotify/src/common.rs
Normal file
171
audio/spotify/src/common.rs
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright (C) 2021 Guillaume Desmottes <guillaume@desmottes.be>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
use librespot::core::{
|
||||||
|
cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId,
|
||||||
|
};
|
||||||
|
use librespot::discovery::Credentials;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
cache_credentials: String,
|
||||||
|
cache_files: String,
|
||||||
|
cache_max_size: u64,
|
||||||
|
pub track: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn properties() -> Vec<glib::ParamSpec> {
|
||||||
|
vec![glib::ParamSpecString::builder("username")
|
||||||
|
.nick("Username")
|
||||||
|
.blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("password")
|
||||||
|
.nick("Password")
|
||||||
|
.blurb("Spotify device password from https://www.spotify.com/us/account/set-device-password/")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("cache-credentials")
|
||||||
|
.nick("Credentials cache")
|
||||||
|
.blurb("Directory where to cache Spotify credentials")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("cache-files")
|
||||||
|
.nick("Files cache")
|
||||||
|
.blurb("Directory where to cache downloaded files from Spotify")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecUInt64::builder("cache-max-size")
|
||||||
|
.nick("Cache max size")
|
||||||
|
.blurb("The max allowed size of the cache, in bytes, or 0 to disable the cache limit")
|
||||||
|
.default_value(0)
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("track")
|
||||||
|
.nick("Spotify URI")
|
||||||
|
.blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_property(&mut self, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"username" => {
|
||||||
|
self.username = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"password" => {
|
||||||
|
self.password = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-credentials" => {
|
||||||
|
self.cache_credentials = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-files" => {
|
||||||
|
self.cache_files = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-max-size" => {
|
||||||
|
self.cache_max_size = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"track" => {
|
||||||
|
self.track = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn property(&self, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"username" => self.username.to_value(),
|
||||||
|
"password" => self.password.to_value(),
|
||||||
|
"cache-credentials" => self.cache_credentials.to_value(),
|
||||||
|
"cache-files" => self.cache_files.to_value(),
|
||||||
|
"cache-max-size" => self.cache_max_size.to_value(),
|
||||||
|
"track" => self.track.to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_session<T>(
|
||||||
|
&self,
|
||||||
|
src: T,
|
||||||
|
cat: &gst::DebugCategory,
|
||||||
|
) -> anyhow::Result<Session>
|
||||||
|
where
|
||||||
|
T: glib::IsA<glib::Object>,
|
||||||
|
{
|
||||||
|
let credentials_cache = if self.cache_credentials.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.cache_credentials)
|
||||||
|
};
|
||||||
|
|
||||||
|
let files_cache = if self.cache_files.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.cache_files)
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_size = if self.cache_max_size != 0 {
|
||||||
|
Some(self.cache_max_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
||||||
|
|
||||||
|
let credentials = match cache.credentials() {
|
||||||
|
Some(cached_cred) => {
|
||||||
|
gst::debug!(cat, obj: &src, "reuse credentials from cache",);
|
||||||
|
cached_cred
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
gst::debug!(cat, obj: &src, "credentials not in cache",);
|
||||||
|
|
||||||
|
if self.username.is_empty() {
|
||||||
|
bail!("username is not set and credentials are not in cache");
|
||||||
|
}
|
||||||
|
if self.password.is_empty() {
|
||||||
|
bail!("password is not set and credentials are not in cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cred = Credentials::with_password(&self.username, &self.password);
|
||||||
|
cache.save_credentials(&cred);
|
||||||
|
cred
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (session, _credentials) =
|
||||||
|
Session::connect(SessionConfig::default(), credentials, Some(cache), false).await?;
|
||||||
|
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_id(&self) -> anyhow::Result<SpotifyId> {
|
||||||
|
if self.track.is_empty() {
|
||||||
|
bail!("track is not set");
|
||||||
|
}
|
||||||
|
let track = SpotifyId::from_uri(&self.track).map_err(|_| {
|
||||||
|
anyhow::anyhow!("failed to create Spotify URI from track {}", self.track)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
*/
|
*/
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
|
mod common;
|
||||||
mod spotifyaudiosrc;
|
mod spotifyaudiosrc;
|
||||||
|
|
||||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use futures::future::{AbortHandle, Abortable, Aborted};
|
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::{runtime, task::JoinHandle};
|
use tokio::{runtime, task::JoinHandle};
|
||||||
|
@ -18,10 +17,6 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_base::subclass::{base_src::CreateSuccess, prelude::*};
|
use gst_base::subclass::{base_src::CreateSuccess, prelude::*};
|
||||||
|
|
||||||
use librespot::core::{
|
|
||||||
cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId,
|
|
||||||
};
|
|
||||||
use librespot::discovery::Credentials;
|
|
||||||
use librespot::playback::{
|
use librespot::playback::{
|
||||||
audio_backend::{Sink, SinkResult},
|
audio_backend::{Sink, SinkResult},
|
||||||
config::PlayerConfig,
|
config::PlayerConfig,
|
||||||
|
@ -67,12 +62,7 @@ struct State {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
username: String,
|
common: crate::common::Settings,
|
||||||
password: String,
|
|
||||||
cache_credentials: String,
|
|
||||||
cache_files: String,
|
|
||||||
cache_max_size: u64,
|
|
||||||
track: String,
|
|
||||||
bitrate: Bitrate,
|
bitrate: Bitrate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,120 +89,39 @@ impl ObjectSubclass for SpotifyAudioSrc {
|
||||||
impl ObjectImpl for SpotifyAudioSrc {
|
impl ObjectImpl for SpotifyAudioSrc {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
let mut props = crate::common::Settings::properties();
|
||||||
let default = Settings::default();
|
let default = Settings::default();
|
||||||
|
|
||||||
vec![glib::ParamSpecString::builder("username")
|
props.push(
|
||||||
.nick("Username")
|
glib::ParamSpecEnum::builder_with_default::<Bitrate>("bitrate", default.bitrate)
|
||||||
.blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/")
|
.nick("Spotify bitrate")
|
||||||
.default_value(Some(default.username.as_str()))
|
.blurb("Spotify audio bitrate in kbit/s")
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecString::builder("password")
|
);
|
||||||
.nick("Password")
|
props
|
||||||
.blurb("Spotify device password from https://www.spotify.com/us/account/set-device-password/")
|
|
||||||
.default_value(Some(default.password.as_str()))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("cache-credentials")
|
|
||||||
.nick("Credentials cache")
|
|
||||||
.blurb("Directory where to cache Spotify credentials")
|
|
||||||
.default_value(Some(default.cache_credentials.as_str()))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("cache-files")
|
|
||||||
.nick("Files cache")
|
|
||||||
.blurb("Directory where to cache downloaded files from Spotify")
|
|
||||||
.default_value(Some(default.cache_files.as_str()))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecUInt64::builder("cache-max-size")
|
|
||||||
.nick("Cache max size")
|
|
||||||
.blurb("The max allowed size of the cache, in bytes, or 0 to disable the cache limit")
|
|
||||||
.default_value(default.cache_max_size)
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("track")
|
|
||||||
.nick("Spotify URI")
|
|
||||||
.blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'")
|
|
||||||
.default_value(Some(default.track.as_str()))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecEnum::builder_with_default::<Bitrate>("bitrate", default.bitrate)
|
|
||||||
.nick("Spotify bitrate")
|
|
||||||
.blurb("Spotify audio bitrate in kbit/s")
|
|
||||||
.mutable_ready()
|
|
||||||
.build()
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
PROPERTIES.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"username" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.username = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"password" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.password = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-credentials" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_credentials = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-files" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_files = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-max-size" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_max_size = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"track" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.track = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"bitrate" => {
|
"bitrate" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.bitrate = value.get().expect("type checked upstream");
|
settings.bitrate = value.get().expect("type checked upstream");
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => settings.common.set_property(value, pspec),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"username" => {
|
"bitrate" => settings.bitrate.to_value(),
|
||||||
let settings = self.settings.lock().unwrap();
|
_ => settings.common.property(pspec),
|
||||||
settings.username.to_value()
|
|
||||||
}
|
|
||||||
"password" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.password.to_value()
|
|
||||||
}
|
|
||||||
"cache-credentials" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_credentials.to_value()
|
|
||||||
}
|
|
||||||
"cache-files" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_files.to_value()
|
|
||||||
}
|
|
||||||
"cache-max-size" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_max_size.to_value()
|
|
||||||
}
|
|
||||||
"track" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.track.to_value()
|
|
||||||
}
|
|
||||||
"bitrate" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.bitrate.to_value()
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,10 +298,10 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
fn uri(&self) -> Option<String> {
|
fn uri(&self) -> Option<String> {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
if settings.track.is_empty() {
|
if settings.common.track.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(settings.track.clone())
|
Some(settings.common.track.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,63 +360,23 @@ impl SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (credentials, cache, track, bitrate) = {
|
let src = self.obj();
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
|
|
||||||
let credentials_cache = if settings.cache_credentials.is_empty() {
|
let (session, track, bitrate) = {
|
||||||
None
|
let (common, bitrate) = {
|
||||||
} else {
|
let settings = self.settings.lock().unwrap();
|
||||||
Some(&settings.cache_credentials)
|
let bitrate = settings.bitrate.into();
|
||||||
|
|
||||||
|
(settings.common.clone(), bitrate)
|
||||||
};
|
};
|
||||||
|
|
||||||
let files_cache = if settings.cache_files.is_empty() {
|
let session = common.connect_session(src.clone(), &CAT).await?;
|
||||||
None
|
let track = common.track_id()?;
|
||||||
} else {
|
|
||||||
Some(&settings.cache_files)
|
|
||||||
};
|
|
||||||
|
|
||||||
let max_size = if settings.cache_max_size != 0 {
|
|
||||||
Some(settings.cache_max_size)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
|
||||||
|
|
||||||
let credentials = match cache.credentials() {
|
|
||||||
Some(cached_cred) => {
|
|
||||||
gst::debug!(CAT, imp: self, "reuse credentials from cache",);
|
|
||||||
cached_cred
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
gst::debug!(CAT, imp: self, "credentials not in cache",);
|
|
||||||
|
|
||||||
if settings.username.is_empty() {
|
|
||||||
bail!("username is not set and credentials are not in cache");
|
|
||||||
}
|
|
||||||
if settings.password.is_empty() {
|
|
||||||
bail!("password is not set and credentials are not in cache");
|
|
||||||
}
|
|
||||||
|
|
||||||
let cred = Credentials::with_password(&settings.username, &settings.password);
|
|
||||||
cache.save_credentials(&cred);
|
|
||||||
cred
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if settings.track.is_empty() {
|
|
||||||
bail!("track is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
let bitrate = settings.bitrate.into();
|
|
||||||
gst::debug!(CAT, imp: self, "Requesting bitrate {:?}", bitrate);
|
gst::debug!(CAT, imp: self, "Requesting bitrate {:?}", bitrate);
|
||||||
|
|
||||||
(credentials, cache, settings.track.clone(), bitrate)
|
(session, track, bitrate)
|
||||||
};
|
};
|
||||||
|
|
||||||
let (session, _credentials) =
|
|
||||||
Session::connect(SessionConfig::default(), credentials, Some(cache), false).await?;
|
|
||||||
|
|
||||||
let player_config = PlayerConfig {
|
let player_config = PlayerConfig {
|
||||||
passthrough: true,
|
passthrough: true,
|
||||||
bitrate,
|
bitrate,
|
||||||
|
@ -523,11 +392,6 @@ impl SpotifyAudioSrc {
|
||||||
Box::new(BufferSink { sender })
|
Box::new(BufferSink { sender })
|
||||||
});
|
});
|
||||||
|
|
||||||
let track = match SpotifyId::from_uri(&track) {
|
|
||||||
Ok(track) => track,
|
|
||||||
Err(_) => bail!("Failed to create Spotify URI from track"),
|
|
||||||
};
|
|
||||||
|
|
||||||
player.load(track, true, 0);
|
player.load(track, true, 0);
|
||||||
|
|
||||||
let player_channel_handle = RUNTIME.spawn(async move {
|
let player_channel_handle = RUNTIME.spawn(async move {
|
||||||
|
|
Loading…
Reference in a new issue