Skip to content

Commit

Permalink
fix: better handling of mpris changes
Browse files Browse the repository at this point in the history
Replaces the active mpris with one that is playing when the current one is paused and also listens for new players
  • Loading branch information
wash2 committed Dec 9, 2023
1 parent 3eab6a9 commit 328d921
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 40 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

147 changes: 109 additions & 38 deletions cosmic-applet-audio/src/mpris_subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use mpris2_zbus::{
player::{PlaybackStatus, Player},
};
use tokio::join;
use zbus::Connection;
use zbus::{fdo::DBusProxy, Connection};

#[derive(Clone, Debug)]
pub struct PlayerStatus {
Expand All @@ -25,8 +25,8 @@ pub struct PlayerStatus {
}

impl PlayerStatus {
async fn new(player: Player) -> Self {
let metadata = player.metadata().await.unwrap();
async fn new(player: Player) -> Option<Self> {
let metadata = player.metadata().await.ok()?;
let title = metadata.title().map(Cow::from);
let artists = metadata
.artists()
Expand All @@ -49,7 +49,7 @@ impl PlayerStatus {
player.can_go_previous(),
player.can_go_next()
);
Self {
Some(Self {
icon,
title,
artists,
Expand All @@ -59,7 +59,7 @@ impl PlayerStatus {
can_go_previous: can_go_previous.unwrap_or_default(),
can_go_next: can_go_next.unwrap_or_default(),
player,
}
})
}
}

Expand All @@ -78,7 +78,7 @@ pub fn mpris_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
#[derive(Debug)]
pub enum State {
Setup,
Player(Player),
Player(Player, DBusProxy<'static>),
Finished,
}

Expand All @@ -102,22 +102,23 @@ async fn update(state: State, output: &mut futures::channel::mpsc::Sender<MprisU
State::Setup => {
let Ok(conn) = Connection::session().await else {
tracing::error!("Failed to connect to session bus.");
_ = output.send(MprisUpdate::Finished).await;
return State::Finished;
};
let mut players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn)
.await
.unwrap_or_else(|_| Vec::new());
let Ok(dbus_proxy) = zbus::fdo::DBusProxy::builder(&conn)
.path("/org/freedesktop/DBus")
.unwrap()
.build()
.await
else {
tracing::error!("Failed to create dbus proxy.");
return State::Finished;
};
if players.is_empty() {
let Ok(dbus) = zbus::fdo::DBusProxy::builder(&conn)
.path("/org/freedesktop/DBus")
.unwrap()
.build()
.await
else {
tracing::error!("Failed to create dbus proxy.");
return State::Finished;
};
let Ok(mut stream) = dbus.receive_name_owner_changed().await else {
let Ok(mut stream) = dbus_proxy.receive_name_owner_changed().await else {
tracing::error!("Failed to receive name owner changed signal.");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// restart from the beginning
Expand All @@ -126,61 +127,131 @@ async fn update(state: State, output: &mut futures::channel::mpsc::Sender<MprisU
while let Some(c) = stream.next().await {
if let Ok(args) = c.args() {
if args.name.contains("org.mpris.MediaPlayer2") {
if let Ok(p) =
MediaPlayer::new(&conn, args.name().to_owned().into()).await
{
players.push(p);
}
break;
}
}
}
if let Ok(p) = mpris2_zbus::media_player::MediaPlayer::new_all(&conn).await {
players = p;
} else {
// restart from the beginning
return State::Setup;
}
}

let Some(player) = find_active(players).await else {
tracing::error!("Failed to find active media player.");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
return State::Finished;
return State::Setup;
};

let player_status = PlayerStatus::new(player.clone()).await;
let Some(player_status) = PlayerStatus::new(player.clone()).await else {
tracing::error!("Failed to get player status.");
return State::Setup;
};

_ = output.send(MprisUpdate::Player(player_status)).await;
State::Player(player)
State::Player(player, dbus_proxy)
}
State::Player(player) => {
let mut paused = player.receive_playback_status_changed().await;
State::Player(player, dbus_proxy) => {
let Ok(mut name_owner_changed) = player.receive_owner_changed().await else {
tracing::error!("Failed to receive owner changed signal.");
// restart from the beginning
return State::Setup;
};
let mut metadata_changed = player.receive_metadata_changed().await;
let Ok(mut new_mpris) = dbus_proxy.receive_name_owner_changed().await else {
tracing::error!("Failed to receive name owner changed signal.");
// restart from the beginning
return State::Setup;
};
let conn = player.connection();
let media_players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn)

Check warning on line 168 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:168:81 | 168 | let media_players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn) | ^^^^^ help: change this to: `conn` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default

Check warning on line 168 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:168:81 | 168 | let media_players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn) | ^^^^^ help: change this to: `conn` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow = note: `#[warn(clippy::needless_borrow)]` on by default
.await
.unwrap_or_else(|_| Vec::new());

let mut players = Vec::with_capacity(media_players.len());
for p in media_players {
if let Ok(p) = p.player().await {
players.push(p);
}
}

loop {
let mut listeners = Vec::with_capacity(players.len());
for p in &players {
listeners.push(p.receive_playback_status_changed().await);
}
let mut player_state_changed_list = Vec::with_capacity(listeners.len());
for l in &mut listeners {
player_state_changed_list.push(Box::pin(async move {
let changed = l.next().await;
if let Some(c) = changed {
c.get().await.ok()
} else {
tracing::error!("Failed to receive playback status changed signal.");
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
None
}
}));
}
let any_player_state_changed =
futures::future::select_all(player_state_changed_list);
let keep_going = tokio::select! {
p = paused.next() => {
p.is_some()
},
m = metadata_changed.next() => {
m.is_some()
},
n = name_owner_changed.next() => {
n.map(|n| n.is_some()).unwrap_or_default()
},
_ = new_mpris.next() => {
true
},
_ = any_player_state_changed => {
true
},
};

if keep_going {
let update = PlayerStatus::new(player.clone()).await;
let stopped = update.status == PlaybackStatus::Stopped;
_ = output.send(MprisUpdate::Player(update)).await;
if stopped {
_ = output.send(MprisUpdate::Setup).await;
if !keep_going {
break;
}

if let Some(update) = PlayerStatus::new(player.clone()).await {
if matches!(update.status, PlaybackStatus::Stopped) {
break;
}

// if paused check if any players are playing
// if they are, break
if !matches!(update.status, PlaybackStatus::Playing) {
let conn = player.connection();
let players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn)

Check warning on line 227 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:227:87 | 227 | let players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn) | ^^^^^ help: change this to: `conn` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Check warning on line 227 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:227:87 | 227 | let players = mpris2_zbus::media_player::MediaPlayer::new_all(&conn) | ^^^^^ help: change this to: `conn` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
.await
.unwrap_or_else(|_| Vec::new());
if let Some(active) = find_active(players).await {
if active.destination() != player.destination() {
break;
}
}
}
_ = output.send(MprisUpdate::Player(update)).await;
} else {
break;
}
}
_ = output.send(MprisUpdate::Setup).await;
State::Setup
}
State::Finished => iced::futures::future::pending().await,
}
}

async fn find_active(players: Vec<MediaPlayer>) -> Option<Player> {
async fn find_active(mut players: Vec<MediaPlayer>) -> Option<Player> {
// pre-sort by path so that the same player is always selected
players.sort_by(|a, b| {
let a = a.destination();
let b = b.destination();
a.cmp(&b)

Check warning on line 253 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:253:15 | 253 | a.cmp(&b) | ^^ help: change this to: `b` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow

Check warning on line 253 in cosmic-applet-audio/src/mpris_subscription.rs

View workflow job for this annotation

GitHub Actions / linting

this expression creates a reference which is immediately dereferenced by the compiler

warning: this expression creates a reference which is immediately dereferenced by the compiler --> cosmic-applet-audio/src/mpris_subscription.rs:253:15 | 253 | a.cmp(&b) | ^^ help: change this to: `b` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow
});
let mut best = (0, None);
let eval = |p: Player| async move {
let v = {
Expand All @@ -202,7 +273,7 @@ async fn find_active(players: Vec<MediaPlayer>) -> Option<Player> {
Err(_) => continue,
};
let v = eval(p.clone()).await;
if v >= best.0 {
if v > best.0 {
best = (v, Some(p));
}
}
Expand Down

0 comments on commit 328d921

Please sign in to comment.