feat(mpris): Emit Seeked signal

* Add Seeked signal to Mpris interface

The Mpris2 spec includes a `Seeked` signal which should be fired when
the track position changes in an unexpected way i.e. when the user
seeks to a different part of the track.

This PR implements this signal on seek events and also when a new track
begins. The latter is not strictly required but has been observed in
other players (e.g. VLC).

Closes #1492

* chore: Use `send_mpris()` and `Duration` for conversion

* doc: Update CHANGELOG

---------

Co-authored-by: Henrik Friedrichsen <henrik@affekt.org>
This commit is contained in:
elParaguayo
2024-09-21 10:55:10 +01:00
committed by GitHub
parent 3893a0ef6d
commit 40644e1de1
4 changed files with 35 additions and 0 deletions

View File

@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Emit MPRIS `Seeked` signal
### Fixed ### Fixed
- Switch to OAuth2 login mechanism - Switch to OAuth2 login mechanism

View File

@@ -1,3 +1,5 @@
#![allow(clippy::use_self)]
use log::info; use log::info;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error; use std::error::Error;
@@ -6,6 +8,7 @@ use std::time::Duration;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
use zbus::object_server::SignalContext;
use zbus::zvariant::{ObjectPath, Value}; use zbus::zvariant::{ObjectPath, Value};
use zbus::{interface, ConnectionBuilder}; use zbus::{interface, ConnectionBuilder};
@@ -314,6 +317,9 @@ impl MprisPlayer {
self.queue.get_current().is_some() self.queue.get_current().is_some()
} }
#[zbus(signal)]
async fn seeked(context: &SignalContext<'_>, position: &i64) -> zbus::Result<()>;
fn next(&self) { fn next(&self) {
self.queue.next(true) self.queue.next(true)
} }
@@ -472,6 +478,8 @@ pub enum MprisCommand {
EmitVolumeStatus, EmitVolumeStatus,
/// Emit metadata /// Emit metadata
EmitMetadataStatus, EmitMetadataStatus,
/// Emit seeked position
EmitSeekedStatus(i64),
} }
/// An MPRIS server that internally manager a thread which can be sent commands. This is internally /// An MPRIS server that internally manager a thread which can be sent commands. This is internally
@@ -539,6 +547,10 @@ impl MprisManager {
Some(MprisCommand::EmitMetadataStatus) => { Some(MprisCommand::EmitMetadataStatus) => {
player_iface.metadata_changed(ctx).await?; player_iface.metadata_changed(ctx).await?;
} }
Some(MprisCommand::EmitSeekedStatus(pos)) => {
info!("sending MPRIS seeked signal");
MprisPlayer::seeked(ctx, &pos).await?;
}
None => break, None => break,
} }
} }

View File

@@ -313,6 +313,10 @@ impl Queue {
move || send_notification(&summary_txt, &body_txt, cover_url) move || send_notification(&summary_txt, &body_txt, cover_url)
}); });
} }
// Send a Seeked signal at start of new track
#[cfg(feature = "mpris")]
self.spotify.notify_seeked(0);
} }
if reshuffle && self.get_shuffle() { if reshuffle && self.get_shuffle() {

View File

@@ -415,6 +415,8 @@ impl Spotify {
/// Seek in the currently played [Playable] played by the [Player]. /// Seek in the currently played [Playable] played by the [Player].
pub fn seek(&self, position_ms: u32) { pub fn seek(&self, position_ms: u32) {
self.send_worker(WorkerCommand::Seek(position_ms)); self.send_worker(WorkerCommand::Seek(position_ms));
#[cfg(feature = "mpris")]
self.notify_seeked(position_ms);
} }
/// Seek relatively to the current playback position of the [Player]. /// Seek relatively to the current playback position of the [Player].
@@ -429,6 +431,19 @@ impl Spotify {
self.cfg.state().volume self.cfg.state().volume
} }
/// Send a Seeked signal on Mpris interface
#[cfg(feature = "mpris")]
pub fn notify_seeked(&self, position_ms: u32) {
let new_position = Duration::from_millis(position_ms.into());
let command = MprisCommand::EmitSeekedStatus(
new_position
.as_micros()
.try_into()
.expect("track position exceeds MPRIS datatype"),
);
self.send_mpris(command);
}
/// Set the current volume of the [Player]. If `notify` is true, also notify MPRIS clients about /// Set the current volume of the [Player]. If `notify` is true, also notify MPRIS clients about
/// the update. /// the update.
pub fn set_volume(&self, volume: u16, notify: bool) { pub fn set_volume(&self, volume: u16, notify: bool) {