Over winter break, some friends and I decided to spend a week working on a programming project. One challenge was playing music - deciding whose computer to use, which person’s music library to use, and so on. To make it easier for everyone to play the tracks they wanted, we set up a spare laptop as a jukebox.
A simple shell script which we’ll discuss below served as a server - people
could send it a URL (such as a YouTube video) via netcat and it would
automatically play each link using
mpv one at a time in a
This approach is definitely not the most robust - for one thing anyone could easily overload the server, skip other people’s tracks, or cause the server to exit. It makes the assumption that nobody on your network is a bad actor. But, for a temporary setup on a secure LAN, it served us well.
The first script,
muserver.sh, uses netcat to listen for incoming
connections, parse out either a command or a URL, then take the appropriate
action. In the case where the data the client sent was a URL, it gets sent into
a named pipe for the music playback script to pick up.
The second script,
muplayer.sh, reads the named pipe from the former one line
at a time. It tries to play each line using
mpv (which uses
under the hood to retrieve the music files). It waits until
mpv exists before
retrieving the next line.
On the server - the machine which you want the music to play out of, you just
need to run
muserver.sh path/to/fifo, where
path/to/fifo is where you would
like the named pipe to be placed. The server should print the IP and port
number which it is using.
Now, anyone who can access your IP can use netcat to send URLs to be played
echo 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' | nc the_ip the_port.
If you want to skip a track:
echo 'SKIP' | nc the_ip the_port
When you’re ready to close the server:
echo 'DIE' | nc the_ip the_port
#!/bin/sh # the user needs to tell us where to put the FIFO if [ $# -ne 1 ] ; then echo "$0 [FIFO path]" exit 1 fi FIFO="$1" # get the current LAN IP via get-ip: # https://git.sr.ht/%7Echarles/charles-util/tree/master/bin/get-ip IP="$(get-ip)" # set a port for netcat to use PORT=5656 # create the named pipe rm -f "$FIFO" mkfifo "$FIFO" # Keep the FIFO open - when we use 'echo >> thefifo' an EOF gets sent into the # FIFO implicitly, this causes it to close unless another process has an # open write handle; we spawn a background process that keeps a write handle # open but does not actually write any data to it. (while true ; do sleep 1 ; done > "$FIFO") & # we want to keep track of the PID of the background process so we can kill # it when we exit cleanly SLEEPER_PID=$! echo "sleeper PID=$SLEEPER_PID" # run the music player script in the background ./muplayer.sh "$FIFO" & # we want to keep track of the PID of the player so we can kill and restart # it for skips, and also when we exit cleanly PLAYER_PID=$! echo "player PID=$PLAYER_PID" echo "starting muserver... " echo "my IP is $IP" echo "listening on port $PORT" while true ; do # wait until a client sends us a message RECV="$(nc -l $PORT)" echo "muserver: got message '$RECV'" # if the client asked us to skip, kick over the music player and # restart it - we also kill mpv, since for some reason killing the # parent does not also kill mpv. if [ "$RECV" = "SKIP" ] ; then echo "muserver: skip requested, kicking over player ($PLAYER_PID)" kill -9 $PLAYER_PID pkill mpv ./muplayer.sh "$FIFO" & PLAYER_PID=$! echo "muserver: player PID=$PLAYER_PID" # if the client asks us to exit, kill all of the background proceses # and exit elif [ "$RECV" = "DIE" ] ; then echo "muserver: clean shutdown" kill -9 $PLAYER_PID kill -9 $SLEEPER_PID pkill mpv exit # assume the message is a link foor us to play and send it into the # FIFO else echo "muserver: appended to queue" echo "$RECV" >> "$FIFO" fi done
#!/bin/sh # make sure we were passed in a FIFO to read from if [ $# -ne 1 ] ; then echo "$0 [FIFO path]" exit 1 fi FIFO="$1" if [ ! -e "$FIFO" ] ; then echo "$FIFO does not exist" exit 1 fi # read a line at a time from the FIFO while read -r line ; do echo "muplayer: begin playing '$line'" # wait until mpv exits to proceed mpv --no-video "$line" --quiet | while read -r ol ; do echo "mpv: $ol" ; done echo "muplayer: finished playing (rc=$?)" done < "$FIFO"