polybar / polybar-scripts

This is a community project. We write and collect scripts for polybar!
The Unlicense
2.44k stars 339 forks source link

player-mpris-tail: should sort players by recency #335

Open victorz opened 3 years ago

victorz commented 3 years ago

When I'm playing Spotify and another thing, e.g. Pocket Casts web player, or YouTube, etc., there are multiple players. If I click the player-mpris-tail module to pause say currently-playing Pocket Casts, then Spotify suddenly shows up in the module. Possibly due to the formatted output by the module? But pressing unpause on my media keys again actually plays Pocket Casts again, yet clicking the module would resume Spotify playback instead.

It would be good to sort the players which have been "touched"/interacted with by recency, so that the module doesn't flip between players when interacting with it. The only thing that should change the order is by manually starting or stopping another player externally.

Thanks for your consideration.

x70b1 commented 3 years ago

Do you want to work on a PR?

victorz commented 3 years ago

Do you want to work on a PR?

Sure? I need someone to explain how the plugin works though. I tried to study the code but I can't follow along in it. Been a while since I did any python.

x70b1 commented 2 years ago

Maybe its an idea for @Cybolic or somebody else.

zjeffer commented 2 years ago

@victorz I use a workaround for this: a script that keeps the most recent player in a file, and my media keys will interact with that player. Here is my script:

#!/bin/sh

get_players(){
    # gets a list of MPRIS compatible players
    echo `qdbus | egrep -i 'org.mpris.MediaPlayer2|plasma.browser_integration'`
}

get_status(){
    # get the current playback status of the given player
    echo `qdbus $1 /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlaybackStatus 2>&1`
}

setFirstPausedPlayer() {
    for player in $(get_players) ; do
        if [[ $(get_status $player) == 'Paused' ]]; then
            echo "$player" > ~/.config/activePlayer/currentPlaying.txt
            break
        fi
    done
}

while true ; do
    for player in $(get_players) ; do
        if [[ $(get_status $player) == 'Playing' ]]; then
            # if the player is playing, set it as the current playing player
            echo $player > ~/.config/activePlayer/currentPlaying.txt
            break
        elif [[ $(get_status $player) == 'Stopped' ]]; then
            # if a player is stopped, we don't want to start it again
            setFirstPausedPlayer
        fi
    done
    # if the current player does not exist anymore, look for a new one
    currentPlayer=`cat ~/.config/activePlayer/currentPlaying.txt`
    if [[ $(get_status $currentPlayer) == *"does not exist"* ]]; then
        # if a player is stopped, search for the next paused player
        setFirstPausedPlayer
    fi
    sleep 0.25
done

This script runs in the background and is started when I log in to my X session.

To control that latest player, I use this script:

#!/bin/sh

#Get command
case $1 in
    'play-pause')
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause';;
    'next')
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next';;
    'previous')
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous';;
    'stop')
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Stop';;
    'skipForward')
        # skip 5 second forward
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Seek 5000000';;
    'skipBackward')
        # skip 5 second backward
        cmd='/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Seek -5000000';;
    *)
esac

#Send command to qdbus
if [[ `qdbus | egrep -i 'org.mpris.MediaPlayer2|plasma-browser-integration' | wc -l` -eq 1 ]]; then
    # if only one player is detected
    qdbus `qdbus | egrep -i 'org.mpris.MediaPlayer2|plasma-browser-integration'` $cmd
else
    # if multiple players are detected, interact with the most recent active player
    qdbus `cat ~/.config/activePlayer/currentPlaying.txt` $cmd
fi

unset cmd

In sxhkdrc (the config file for bspwm's hotkey daemon), I define these actions:

########################################## MEDIA KEYS ##########################################

# Media control
XF86Audio{Play,Prev,Next,Stop}
    ~/.config/activePlayer/control.sh {play-pause,previous,next,stop}

# Fastforward/Rewind x seconds
shift + XF86Audio{Next,Prev}
    ~/.config/activePlayer/control.sh {skipForward, skipBackward}
victorz commented 2 years ago

@zjeffer

That's one solution. Although my issue is that this module does the wrong thing. My media keys do the right thing, and control the expected player (most recent). I use playerctl and set my keys up in my i3 config. I use playerctl without the player flag, so that I don't limit the command/keys to only one player. But this module seems to flip between players when I pause the current player externally (e.g. via media keys).

Expected behavior would be:

If one player is playing, show that.

If multiple players are playing, show the most recently interacted-with player.

If no players are playing, show the most recently interacted-with player.

I hope that makes sense and is achievable. Thanks!

zjeffer commented 2 years ago

If one player is playing, show that. If multiple players are playing, show the most recently interacted-with player. If no players are playing, show the most recently interacted-with player.

This is what my script fixes, but indeed only with media keys. To fix this in the module, we could implement the above bash script into the python script. I'm going to look into it.

zjeffer commented 2 years ago

I managed to implement a way to keep the most recent players, the problem is that the module's buttons will start a new instance of the Python script, so that instance never knows which players were interacted with recently (as they are kept in memory).

I'll try saving the most recent players in a file for now, and reading them from that file every time a module button is pressed.

victorz commented 2 years ago

Would it be possible to use the "tail" feature of polybar instead? So that a single instance is always running, but each new line of output will be the new state in the bar? That way you could keep the player state in the script and not have to use a file on disk for this. I don't know, just spit-balling. :-)

zjeffer commented 2 years ago

I thought so too, but I have no idea how to program that :)

victorz commented 2 years ago

I made a few custom scripts that do this, and all it requires is that the script keeps running, with a loop or has its input piped to it or something, and that it keeps outputting lines of statuses (separated by newlines). So if you can accomplish that with this script, I think we'd be golden.

Although obviously this would be a major version as it breaks existing configs. But maybe that's okay? I don't know how you feel about that.

zjeffer commented 2 years ago

Doesn't sound that difficult, I'll look into that in the next couple of days.

Do you have a link to those scripts you made?

victorz commented 2 years ago

Hmm, I don't have them in a repo I don't think. I'll look into that tomorrow to see how I can best get those to you. 🙂 Cheers!

victorz commented 2 years ago

Sorry for the delay on this! Vacation came and went, finally have some time at the computer.

One example of this is a script I have called dbus-listen.sh, which looks like this:

#!/bin/bash
watchexpr="$1"
shift 1
dbus-monitor --profile "$watchexpr" |
  while read -r line
  do
    sh -c "$*"
  done

The script monitors a dbus interface given by the parameters to the script, and for every line that the monitor reads, it executes a command, also given by (the rest of) the parameters to the script.

dbus-monitor simply runs forever until killed, and the command is expected to print one polybar format line per dbus monitor trigger.

I use the script in my polybar for various things, one of which is to mute the microphone system-wide, like this:

[module/mic-mute]
type = custom/script
exec = dbus-listen.sh sender=user.victor,member=MicMuteToggle "pamixer --get-mute --default-source | sed -e 's/true/%{F#f05050}%{F-}/;>
tail = true
click-left = mic-mute-toggle.sh
click-right = pavucontrol -t 4

A simpler script for testing purposes would probably be something like this though:

#!/bin/bash

val=true
while true
do
  print $val
  if [[ val = true ]]
  then
    val=false
  else
    val=true
  fi
done

I hope this helps!