In the first part of this series we made a script to react to mpd’s play, pause, and stop events by turning speakers on and off automatically. In the second part we improved it to tolerate short pauses, so that speakers don’t turn off when you pause the music just to answer the phone. This third part in the series generalizes the problem of  tolerating pauses and presents an elegant solution using native Linux tools.

Let’s state the problem

The mpdmonitor script runs a command on the start, play, and pause mpd events, i.e. when mpd starts, pauses, or stops playing music. But pauses or stops happen also when the user builds a new playlist or answers the phone. The monitor should wait a while, until the stop or pause is so long that it’s certain that the user is gone.

Keeping it general

Changing the mpdmonitor script is a nice and short way to solve the problem if all you want to do is turn speakers on and off. But if you want to control recording, control an “On Air” light, or automatically update your online status from “don’t disturb” to “it’s ok to call right now”, the mpdmonitor must react immediately to pauses and stops. It’s worth looking for another solution.

The idea

We shouldn’t change the mpdmonitor, but we can change the commands we give it for the onplay, onpause, and onstop events. Right now we use mpdmonitor like this:

 mpdmonitor localhost 6600 soundon soundoff soundoff 

which means “run soundon when music plays, soundoff when it pauses, and soundoff when it stops”. How about running it like this:

 mpdmonitor localhost 6600 soundon "sleep 300; soundoff" "sleep 300; soundoff" 

This should turn the speakers on immediately when mpd plays music, but will wait 5 minutes after music stops before turning them off. Solved, right? Not yet. What happens when the user resumes playing music before the 5 minutes are up? How to cancel the “sleep 300; soundoff” that mpdmonitor has spawned? Do we need to keep track of all the resulting child processes? There has to be a better way!

“At” to the rescue

In essence, the reason why we

sleep 300

is so that we can run the

soundoff

command at a future point in time. Sounds like scheduling a command. We also need to be able to cancel that scheduling. This is exactly what the “at” command does! Let’s install it:

sudo apt-get install at

The following script — let’s call it “soundoff-later”, schedules a “soundoff” command

soundoff-later, preliminary version:

#!/bin/bash

# an "at queue" is like a category of related commands
# let's use queue s for sound commands
ATQUEUE=s

# let's schedule the soundoff command to run in 15 minutes
echo "soundoff" | at -q $ATQUEUE now + 15min
exit 0

With this, the command-line for mpdmonitor now looks like this:

 mpdmonitor localhost 6600 soundon soundoff-later soundoff-later 

It makes sense: We want to turn the speakers on immediately when the music starts to play, so we do not need to schedule a soundon command. For turning sound off, we want to leave a buffer time, so we use soundoff-later. But, what if the user stops the music and then starts it again? Where does the scheduled soundoff get cancelled? Good catch! We need a script that not only will run soundon, but will also cancel any previous soundoffs. Let’s call it “soundon-now”

soundon-now, preliminary version

#!/bin/bash
#using the same queue for commands
ATQUEUE=s

# we do not schedule any soundon, so if there is any scheduled command, it must be a soundoff.
# check if we have any scheduled command already
TO_REMOVE="`atq -q $ATQUEUE|cut -f 1`"
if [ "$TO_REMOVE" != "" ]
then
        # This scheduled command must be a soundoff, so we should cancel it
        atrm $TO_REMOVE
fi

#now we are sure there's nothing scheduled that can turn speakers off
# let's turn them on and be done
soundon
exit 0

The mpdmonitor command now looks like this:

 mpdmonitor localhost 6600 soundon-now soundoff-later soundoff-later 

We could stop here, but let’s make a final improvement. What happens when a user pauses the music, then stops it? The soundoff-later command will run twice, scheduling two “soundoff”s. This should not be a problem, but why allow that to happen? Let’s cancel any pending soundoff’s before we schedule a new one. This will ensure that only one soundoff can be scheduled at any time. With some other improvements for command paths, here’s the final code for soundon-now and soundoff-later:

soundon-now, final version

#!/bin/bash
CUT="/usr/bin/cut"
ATQ="/usr/bin/atq"
ATRM="/usr/bin/atrm"

ATQUEUE=s

#first remove any scheduled job that will turn sound off
TO_REMOVE="`$ATQ -q $ATQUEUE|$CUT -f 1`"
if [ "$TO_REMOVE" != "" ]
then
        $ATRM $TO_REMOVE
fi

#now we are sure it will not turn off accidentally, so turn it on
soundon
exit 0

soundoff-later, final version

#!/bin/bash
CUT="/usr/bin/cut"
ATQ="/usr/bin/atq"
ATRM="/usr/bin/atrm"
AT="/usr/bin/at"

ATQUEUE=s

#first remove any scheduled job that will turn sound off, so we don't do it multiple times
TO_REMOVE="`$ATQ -q $ATQUEUE|$CUT -f 1`"
if [ "$TO_REMOVE" != "" ]
then
        $ATRM $TO_REMOVE
fi

#now we are sure it will not turn off accidentally, so schedule it to run
# after a while
echo "soundoff" | $AT -q $ATQUEUE now + 15min 2>&1 > /dev/null
exit 0

The final commandline for mpdmonitor is therefore:

 mpdmonitor localhost 6600 soundon-now soundoff-later soundoff-later 

Have fun for now, and in the fourth part I’ll show you how to run the monitor as a system service, with no interaction from you.