Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fern = "0.7"
futures = "0.3"
ioctl-rs = {version = "0.2", optional = true}
libc = "0.2.175"
librespot-connect = "0.7.0"
librespot-core = "0.7.0"
librespot-oauth = "0.7.0"
librespot-playback = {version = "0.7.0", default-features = false, features = ["native-tls"]}
Expand Down
14 changes: 8 additions & 6 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,18 @@ impl Application {
}
for event in self.event_manager.msg_iter() {
match event {
Event::Player(state) => {
trace!("event received: {state:?}");
self.spotify.update_status(state.clone());
Event::Player(event) => {
trace!("player event received: {event:?}");
self.spotify.handle_player_event(event.clone());

#[cfg(unix)]
if let Some(ref ipc) = self.ipc {
ipc.publish(&state, self.queue.get_current());
if let Some(ref ipc) = self.ipc
&& let PlayerEvent::StatusChanged(ref status) = event
{
ipc.publish(status, self.queue.get_current());
}

if state == PlayerEvent::FinishedTrack {
if event == PlayerEvent::FinishedTrack {
self.queue.next(false);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ impl CommandManager {
.spotify
.volume()
.saturating_add(VOLUME_PERCENT * amount);
self.spotify.set_volume(volume, true);
self.spotify.set_volume(volume);
Ok(None)
}
Command::VolumeDown(amount) => {
Expand All @@ -202,7 +202,7 @@ impl CommandManager {
.volume()
.saturating_sub(VOLUME_PERCENT * amount);
debug!("vol {volume}");
self.spotify.set_volume(volume, true);
self.spotify.set_volume(volume);
Ok(None)
}
Command::Help => {
Expand Down
8 changes: 4 additions & 4 deletions src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tokio_util::codec::{FramedRead, FramedWrite, LinesCodec};

use crate::events::{Event, EventManager};
use crate::model::playable::Playable;
use crate::spotify::PlayerEvent;
use crate::spotify::PlayerStatus;

pub struct IpcSocket {
tx: Sender<Status>,
Expand All @@ -20,7 +20,7 @@ pub struct IpcSocket {

#[derive(Clone, Debug, Serialize)]
struct Status {
mode: PlayerEvent,
mode: PlayerStatus,
playable: Option<Playable>,
}

Expand All @@ -46,7 +46,7 @@ impl IpcSocket {
info!("Creating IPC domain socket at {path:?}");

let status = Status {
mode: PlayerEvent::Stopped,
mode: PlayerStatus::Stopped,
playable: None,
};

Expand All @@ -65,7 +65,7 @@ impl IpcSocket {
std::os::unix::net::UnixStream::connect(path).is_ok()
}

pub fn publish(&self, event: &PlayerEvent, playable: Option<Playable>) {
pub fn publish(&self, event: &PlayerStatus, playable: Option<Playable>) {
let status = Status {
mode: event.clone(),
playable,
Expand Down
10 changes: 5 additions & 5 deletions src/mpris.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ use crate::model::playlist::Playlist;
use crate::model::show::Show;
use crate::model::track::Track;
use crate::queue::RepeatSetting;
use crate::spotify::UriType;
use crate::spotify::{PlayerStatus, UriType};
use crate::spotify_url::SpotifyUrl;
use crate::traits::ListItem;
use crate::{
events::EventManager,
queue::Queue,
spotify::{PlayerEvent, Spotify, VOLUME_PERCENT},
spotify::{Spotify, VOLUME_PERCENT},
};

struct MprisRoot {}
Expand Down Expand Up @@ -81,8 +81,8 @@ impl MprisPlayer {
#[zbus(property)]
fn playback_status(&self) -> &str {
match self.spotify.get_current_status() {
PlayerEvent::Playing(_) | PlayerEvent::FinishedTrack => "Playing",
PlayerEvent::Paused(_) => "Paused",
PlayerStatus::Playing(_) => "Playing",
PlayerStatus::Paused(_) => "Paused",
_ => "Stopped",
}
}
Expand Down Expand Up @@ -278,7 +278,7 @@ impl MprisPlayer {
log::info!("set volume: {volume}");
let clamped = volume.clamp(0.0, 1.0);
let vol = (VOLUME_PERCENT as f64) * clamped * 100.0;
self.spotify.set_volume(vol as u16, false);
self.spotify.set_volume(vol as u16);
self.event.trigger();
}

Expand Down
7 changes: 3 additions & 4 deletions src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use strum_macros::Display;
use crate::config::Config;
use crate::library::Library;
use crate::model::playable::Playable;
use crate::spotify::PlayerEvent;
use crate::spotify::PlayerStatus;
use crate::spotify::Spotify;
use crate::traits::ListItem;

Expand Down Expand Up @@ -329,14 +329,13 @@ impl Queue {
/// play the next song if one is available, or restart from the start.
pub fn toggleplayback(&self) {
match self.spotify.get_current_status() {
PlayerEvent::Playing(_) | PlayerEvent::Paused(_) => {
PlayerStatus::Playing(_) | PlayerStatus::Paused(_) => {
self.spotify.toggleplayback();
}
PlayerEvent::Stopped => match self.next_index() {
PlayerStatus::Stopped => match self.next_index() {
Some(_) => self.next(false),
None => self.play(0, false, false),
},
_ => (),
}
}

Expand Down
76 changes: 42 additions & 34 deletions src/spotify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,20 @@ use crate::traits::ListItem;
/// percent.
pub const VOLUME_PERCENT: u16 = ((u16::MAX as f64) * 1.0 / 100.0) as u16;

/// Events sent by the [Player].
/// Player status
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum PlayerEvent {
pub enum PlayerStatus {
Playing(SystemTime),
Paused(Duration),
Stopped,
}

/// Events sent by the [Player].
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum PlayerEvent {
StatusChanged(PlayerStatus),
FinishedTrack,
VolumeChanged(u16),
}

/// Wrapper around a worker thread that exposes methods to safely control it.
Expand All @@ -54,7 +61,7 @@ pub struct Spotify {
credentials: Credentials,
cfg: Arc<config::Config>,
/// Playback status of the [Player] owned by the worker thread.
status: Arc<RwLock<PlayerEvent>>,
status: Arc<RwLock<PlayerStatus>>,
pub api: WebApi,
/// The amount of the current [Playable] that had elapsed when last paused.
elapsed: Arc<RwLock<Option<Duration>>>,
Expand All @@ -76,7 +83,7 @@ impl Spotify {
mpris: Default::default(),
credentials,
cfg: cfg.clone(),
status: Arc::new(RwLock::new(PlayerEvent::Stopped)),
status: Arc::new(RwLock::new(PlayerStatus::Stopped)),
api: WebApi::new(),
elapsed: Arc::new(RwLock::new(None)),
since: Arc::new(RwLock::new(None)),
Expand All @@ -87,7 +94,7 @@ impl Spotify {
spotify.start_worker(Some(user_tx))?;
let user = ASYNC_RUNTIME.get().unwrap().block_on(user_rx).ok();
let volume = cfg.state().volume;
spotify.set_volume(volume, true);
spotify.set_volume(volume);

spotify.api.set_worker_channel(spotify.channel.clone());
spotify
Expand Down Expand Up @@ -163,10 +170,7 @@ impl Spotify {

/// Create a [Session] that respects the user configuration in `cfg` and with the given
/// credentials.
async fn create_session(
cfg: &config::Config,
credentials: Credentials,
) -> Result<Session, librespot_core::Error> {
async fn create_session(cfg: &config::Config) -> Session {
let librespot_cache_path = config::cache_path("librespot");
let audio_cache_path = match cfg.values().audio_cache {
Some(false) => None,
Expand All @@ -183,8 +187,7 @@ impl Spotify {
.expect("Could not create cache");
debug!("opening spotify session");
let session_config = Self::session_config(cfg);
let session = Session::new(session_config, Some(cache));
session.connect(credentials, true).await.map(|_| session)
Session::new(session_config, Some(cache))
}

/// Create and initialize the requested audio backend.
Expand Down Expand Up @@ -243,9 +246,7 @@ impl Spotify {
..Default::default()
};

let session = Self::create_session(&cfg, credentials)
.await
.expect("Could not create session");
let session = Self::create_session(&cfg).await;
user_tx.map(|tx| tx.send(session.username()));

let mixer_factory_opt = librespot_playback::mixer::find(Some(SoftMixer::NAME));
Expand All @@ -265,12 +266,14 @@ impl Spotify {

let mut worker = Worker::new(
events.clone(),
credentials,
player_events,
commands,
session,
player,
mixer,
);
)
.await;
debug!("worker thread ready.");
worker.run_loop().await;

Expand All @@ -280,7 +283,7 @@ impl Spotify {
}

/// Get the current playback status of the [Player].
pub fn get_current_status(&self) -> PlayerEvent {
pub fn get_current_status(&self) -> PlayerStatus {
let status = self.status.read().unwrap();
(*status).clone()
}
Expand Down Expand Up @@ -338,24 +341,29 @@ impl Spotify {
/// Update the cached status of the [Player]. This makes sure the status
/// doesn't have to be retrieved every time from the thread, which would be harder and more
/// expensive.
pub fn update_status(&self, new_status: PlayerEvent) {
match new_status {
PlayerEvent::Paused(position) => {
pub fn handle_player_event(&self, event: PlayerEvent) {
match event {
PlayerEvent::StatusChanged(PlayerStatus::Paused(position)) => {
self.set_elapsed(Some(position));
self.set_since(None);
}
PlayerEvent::Playing(playback_start) => {
PlayerEvent::StatusChanged(PlayerStatus::Playing(playback_start)) => {
self.set_since(Some(playback_start));
self.set_elapsed(None);
}
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => {
PlayerEvent::StatusChanged(PlayerStatus::Stopped) | PlayerEvent::FinishedTrack => {
self.set_elapsed(None);
self.set_since(None);
}
PlayerEvent::VolumeChanged(volume) => {
self.update_volume(volume);
}
}

let mut status = self.status.write().unwrap();
*status = new_status;
if let PlayerEvent::StatusChanged(new_status) = event {
let mut status = self.status.write().unwrap();
*status = new_status;
};

#[cfg(feature = "mpris")]
self.send_mpris(MprisCommand::EmitPlaybackStatus);
Expand All @@ -377,8 +385,8 @@ impl Spotify {
/// Toggle playback (play/pause) of the [Player].
pub fn toggleplayback(&self) {
match self.get_current_status() {
PlayerEvent::Playing(_) => self.pause(),
PlayerEvent::Paused(_) => self.play(),
PlayerStatus::Playing(_) => self.pause(),
PlayerStatus::Paused(_) => self.play(),
_ => (),
}
}
Expand Down Expand Up @@ -442,6 +450,14 @@ impl Spotify {
self.cfg.state().volume
}

pub fn update_volume(&self, volume: u16) {
info!("setting volume to {volume}");
self.cfg.with_state_mut(|s| s.volume = volume);

#[cfg(feature = "mpris")]
self.send_mpris(MprisCommand::EmitVolumeStatus)
}

/// Send a Seeked signal on Mpris interface
#[cfg(feature = "mpris")]
pub fn notify_seeked(&self, position_ms: u32) {
Expand All @@ -457,16 +473,8 @@ impl Spotify {

/// Set the current volume of the [Player]. If `notify` is true, also notify MPRIS clients about
/// the update.
pub fn set_volume(&self, volume: u16, notify: bool) {
info!("setting volume to {volume}");
self.cfg.with_state_mut(|s| s.volume = volume);
pub fn set_volume(&self, volume: u16) {
self.send_worker(WorkerCommand::SetVolume(volume));
// HACK: This is a bit of a hack to prevent duplicate update signals when updating from the
// MPRIS implementation.
if notify {
#[cfg(feature = "mpris")]
self.send_mpris(MprisCommand::EmitVolumeStatus)
}
}

/// Preload the given [Playable] in the [Player]. This makes sure it can be played immediately
Expand Down
Loading
Loading