FFPLAY – loop sound until signal received

I have a bash script which starts ffplay in background, looping a short sound sample indefinitely. Then it does some other work, and finally it kills ffplay.

foo() {
    ffplay -loop 0 sound.wav &>/dev/null &
    trap "kill $!" RETURN

    (do work...)
}

I want ffplay to finish playing the currently played sample, and only then die. Is this possible?

You can get continuous statistics or debug output from ffplay which
consists of lines ending carriage-return (^M) like this:

    nan M-A:    nan fd=   0 aq=    0KB vq=    0KB sq=    0B f=0/0   ^M
  -0.07 M-A: -0.000 fd=   0 aq=    0KB vq=    0KB sq=    0B f=0/0   ^M
  -0.01 M-A: -0.000 fd=   0 aq=    0KB vq=    0KB sq=    0B f=0/0   ^M
   0.02 M-A:  0.000 fd=   0 aq=    0KB vq=    0KB sq=    0B f=0/0   ^M

It seems the first column is "nan", not-a-number, or negative at the start
of a loop. You could check for this and kill the process when it is seen.
The following bash script demonstrates this.

#!/bin/bash
sound=somefile.wav
touch flagfile
(sleep 4; rm -f flagfile) &
ffplay -nodisp -stats -loop 0 "$sound" 2>&1 |
while read -d $'r' time rest
do  case $time in
    nan|-*|0.00) [ ! -f flagfile ] && exit ;;
    esac
done

It creates a file flagfile, which you need to remove to say you want to
stop the loop. In this demo, it is just done after 4 seconds.
ffplay is run with option -stats. The statistics are written on stderr so we
redirect that to stdout with 2>&1.
The statistics are piped to a while loop that uses read with
delimiter carriage-return ($'r') putting the first word in variable
time and the rest in rest.

The case compares the first word with the pattern nan or anything
beginning with -, or the number 0, and if the flagfile no longer exists,
the script exits.

After a few more writes of statistics, ffplay will be killed with signal
SIGPIPE. You will have to decide if this short delay is good enough for
you. It depends how noisy the first few milliseconds of the sound file are.


If your ffplay does not respond to SIGPIPE, you can run your own
kill command by noting the process id of the command in a temporary file
pidfile.
Note the use of $BASHPID rather than $$, as the latter does not change within a sub-shell.

#!/bin/bash
sound=somefile.wav
touch flagfile
(sleep 4; rm -f flagfile) &
( echo $BASHPID >pidfile
  exec ffplay -nodisp -stats -loop 0 $sound 2>&1 ) |
while read -d $'r' time rest
do  case $time in
    nan|-*|0.00) [ ! -f flagfile ] && break ;;
    esac
done
kill -hup $(<pidfile)
rm -f pidfile
Answered By: meuh
Categories: Answers Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.