From 93503c0ca9ab0d4383ce8fe3f003139f5d269a23 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 29 Dec 2021 16:54:29 +0100 Subject: [PATCH] spotify: move Settings to common module Will be used to implement the lyrics source element. Part-of: --- audio/spotify/src/common.rs | 171 ++++++++++++++++++++ audio/spotify/src/lib.rs | 1 + audio/spotify/src/spotifyaudiosrc/imp.rs | 190 ++++------------------- 3 files changed, 199 insertions(+), 163 deletions(-) create mode 100644 audio/spotify/src/common.rs diff --git a/audio/spotify/src/common.rs b/audio/spotify/src/common.rs new file mode 100644 index 00000000..ae2bd176 --- /dev/null +++ b/audio/spotify/src/common.rs @@ -0,0 +1,171 @@ +// Copyright (C) 2021 Guillaume Desmottes +// +// 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 +// . +// +// 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 { + 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( + &self, + src: T, + cat: &gst::DebugCategory, + ) -> anyhow::Result + where + T: glib::IsA, + { + 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 { + 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) + } +} diff --git a/audio/spotify/src/lib.rs b/audio/spotify/src/lib.rs index cfce28a5..f650ab78 100644 --- a/audio/spotify/src/lib.rs +++ b/audio/spotify/src/lib.rs @@ -14,6 +14,7 @@ */ use gst::glib; +mod common; mod spotifyaudiosrc; fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> { diff --git a/audio/spotify/src/spotifyaudiosrc/imp.rs b/audio/spotify/src/spotifyaudiosrc/imp.rs index f15ada69..fcaab88f 100644 --- a/audio/spotify/src/spotifyaudiosrc/imp.rs +++ b/audio/spotify/src/spotifyaudiosrc/imp.rs @@ -8,7 +8,6 @@ use std::sync::{mpsc, Arc, Mutex, MutexGuard}; -use anyhow::bail; use futures::future::{AbortHandle, Abortable, Aborted}; use once_cell::sync::Lazy; use tokio::{runtime, task::JoinHandle}; @@ -18,10 +17,6 @@ use gst::prelude::*; use gst::subclass::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::{ audio_backend::{Sink, SinkResult}, config::PlayerConfig, @@ -67,12 +62,7 @@ struct State { #[derive(Default)] struct Settings { - username: String, - password: String, - cache_credentials: String, - cache_files: String, - cache_max_size: u64, - track: String, + common: crate::common::Settings, bitrate: Bitrate, } @@ -99,120 +89,39 @@ impl ObjectSubclass for SpotifyAudioSrc { impl ObjectImpl for SpotifyAudioSrc { fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { + let mut props = crate::common::Settings::properties(); let default = Settings::default(); - vec![glib::ParamSpecString::builder("username") - .nick("Username") - .blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/") - .default_value(Some(default.username.as_str())) + props.push( + glib::ParamSpecEnum::builder_with_default::("bitrate", default.bitrate) + .nick("Spotify bitrate") + .blurb("Spotify audio bitrate in kbit/s") .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(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", default.bitrate) - .nick("Spotify bitrate") - .blurb("Spotify audio bitrate in kbit/s") - .mutable_ready() - .build() - ] + ); + props }); PROPERTIES.as_ref() } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { + let mut settings = self.settings.lock().unwrap(); + 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" => { - let mut settings = self.settings.lock().unwrap(); 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 { + let settings = self.settings.lock().unwrap(); + match pspec.name() { - "username" => { - let settings = self.settings.lock().unwrap(); - 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!(), + "bitrate" => settings.bitrate.to_value(), + _ => settings.common.property(pspec), } } } @@ -389,10 +298,10 @@ impl URIHandlerImpl for SpotifyAudioSrc { fn uri(&self) -> Option { let settings = self.settings.lock().unwrap(); - if settings.track.is_empty() { + if settings.common.track.is_empty() { None } else { - Some(settings.track.clone()) + Some(settings.common.track.clone()) } } @@ -451,63 +360,23 @@ impl SpotifyAudioSrc { } } - let (credentials, cache, track, bitrate) = { - let settings = self.settings.lock().unwrap(); + let src = self.obj(); - let credentials_cache = if settings.cache_credentials.is_empty() { - None - } else { - Some(&settings.cache_credentials) + let (session, track, bitrate) = { + let (common, bitrate) = { + let settings = self.settings.lock().unwrap(); + let bitrate = settings.bitrate.into(); + + (settings.common.clone(), bitrate) }; - let files_cache = if settings.cache_files.is_empty() { - None - } 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(); + let session = common.connect_session(src.clone(), &CAT).await?; + let track = common.track_id()?; 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 { passthrough: true, bitrate, @@ -523,11 +392,6 @@ impl SpotifyAudioSrc { 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); let player_channel_handle = RUNTIME.spawn(async move {