php Music Server Player

INTRO

Like many others, I have converted all my music - CDs, Vinyl, Tape - to mp3 files, and then stored these (along with backups!) on a file server (linux LAMP server) for access and playback on local devices. However I wanted to be able to select directories and play back the music "on the server", which is hooked up to my home sound system. Looking around for solutions it seemed that using mplayer as the player, in slave mode, would handle the mp3 playback, and then mplayer could be sent commands from my device using php and bash scripting. I also wanted to be able to play internet radio. This guide is intended to demonstrate how I put all of this together. It is totally possible to do this just in a webviewer, using php, but I wanted to create an app that handled the work, to make this a proper AI2 guide ;)

I don't have a typical setup for all my music files, they are split into "singles" and "albums", and in the main organised chronologically and by genre. This results in there being a directory tree, at the end of which results in a directory containing mp3 files:


singles > 1970s > Pop > mp3 files

singles > 1980s > Rock > mp3 files

singles > UK_Number_Ones > 1970s > 1975 > mp3 files

albums > Pop > Abba_-_Greatest_Hits > mp3 files

albums > UK_Number_Ones > 1980s > 1985 > Sade_-_Promise > mp3 files


My file server is a headless Raspberry Pi (RPi) Model B (First Gen! - pretty ancient in today's terms, but more than capable of handling mp3 playback) to which is connected a Samsung D3 3TB external hard drive. The RPi runs a full LAMP stack using Apache for http, and the most up to date php the RPi repositories allow. Mplayer is also installed from the repositories. The D3 is mounted to /media/music. The RPi is connected to the local network and has outbound internet access (no port forwarding or DMZ). The audio out 3.5mm jack is connected to my home stereo system. The file server has a "sudo" user called pi. I access the RPi over ssh.


Requirements for the app:

  • Start mplayer in slave idle mode

  • Traverse directories on the server

  • Select a directory to play

  • Play that directory of files

  • Return a file list to display, along with the directory name

  • Display playback times and handle songs ending / starting

  • Select Internet radio station and playback

  • Control Volume, Pause/Play, Skip to Next Track, Stop mplayer


The basic premise is that the app will send a php GET request to the server, on receipt, the server sends a request for a bash script to run, the bash script tells mplayer what to do, mplayer provides feedback on what is happening, and this is returned to the app.

SETUP

The first task is to get php/Apache talking to Linux/Bash. Because this work is all happening locally on a personal network, I can afford to be slightly more relaxed about user and file permissions. I will also place all the files I need in the /var/www/html folder. php runs using the Apache user "www-data" on my systems. I need to give this user "sudo" rights with permissions to run sudo commands on the linux operating system. This is easily done by making www-data a sudoer!

ssh into the server:

ssh pi@192.168.1.95

Issue the following command to edit the /etc/sudoers file:

sudo visudo

add the following to the end of the file and then save out:

www-data ALL=(ALL) NOPASSWD: ALL

This give the Apache user, www-data sudo (superuser) rights, without needing to use a password (www-data does not, by default, have a password anyway!) This will enable me to call bash scripts through php, and also run commands as the user pi.


With that done, we now need to create some files on the server: php files, bash scripts, text files, and a fifo - a special file for passing commands to mplayer. I have chosen to run a separate php file and bash script file for each command to be sent from the app. Many of these could be amalgamated into one file and the bash scripts could be called directly from the php file, but I wanted separation....

cd /var/www/html

sudo mkfifo mplayer-control

sudo touch mplayer-output.txt playlist.txt playlist2.txt

sudo touch startIdle.php scanMusic.php mplayer-volume.php mplayer-radio.php mplayer-quit.php mplayer-pause.php mplayer-next.php mplayer-file-data.php loadlist.php

sudo touch startIdle.sh scanMusic.sh mplayer-volume.sh mplayer-radio.sh mplayer-quit.sh mplayer-pause.sh mplayer-next.sh mplayer-file-data.sh loadlist.sh

and now we make all these files read/write able, and the bash script files executable

sudo chmod 777 *.sh

sudo chmod 777 *.php

sudo chmod 777 *.txt

sudo chmod 777 mplayer-control

sudo chmod +x *.sh (just for good measure...)

(as I said previously, pretty relaxed)

Lastly, for this part, we need to create a directory:

sudo mkdir -p MUSIC

then traverse into this directory and create a symbolic link to the music files directories, which if you remember are mounted to /media/music

cd MUSIC

sudo ln -s /media/music allmusic

All this work has created all the files and links I need under /var/www/html. The next stage is to populate these files with the correct code!

Mplayer

The script to start mplayer in slave idle mode:

sudo nano startIdle.sh


#!/bin/bash


mypid=$(pidof mplayer)


if [ -z "$mypid" ]


then


mplayer -idle -slave -quiet -input file=/var/www/html/mplayer-control > /var/www/html/mplayer-output.txt 2>&1 &


mypid=$(pidof mplayer)

echo "mplayer now idling with pid $mypid"


else


mypid=$(pidof mplayer)

echo "mplayer already idling with pid $mypid"


fi


We check if mplayer is already running, if it is then we exit and return a message. If not running, then we start mplayer in slave idle mode, set the input file to the fifo - mplayer-control, and set the output file to mplayer-output.txt. The input fifo file is used to send messages/commands to mplayer, and the output text file is used to receive data back from mplayer (others use another fifo for this, but I found it easier to use a plain text file)

The corresponding php file:

sudo nano startIdle.php


<?php


$output = shell_exec("sudo -u pi /var/www/html/startIdle.sh 2>&1");

echo $output;


?>


When this php file is run, it will start mplayer in slave idle mode, and it will just sit there, waiting for a command to do something (play music!) You will notice that the shell_exec command being sent calls the corresponding script file, and uses sudo -u pi at the beginning. This tells linux to run the script as the user pi, overcoming any permissions issues. This approach is used in most of the other files that have a php and a sh file to execute a command.


Now for the real Daddy of a php file, and the heart of the setup, scanMusic.php. This file is a bit of an ouroboros (the eating its own tail part), in that it will keep opening itself if served with a directory, but will do something different if served with files. The file is well commented.

sudo nano scanMusic.php


<?php

// gets the music directory link supplied, or defaults to the base link

if(isset($_GET['dir'])){

$dir = $_GET['dir'];

} else {

$dir = 'MUSIC/allmusic/*';

}

//makes an array of the directories in the directory

$dirs = array_filter(glob($dir), 'is_dir');

// if no directories, makes a list of the files

if ( empty($dirs) ) {

$files = glob($dir.mp3);

// shuffles the files if they are not an album

if( strpos( $dir, 'album' ) !== false) {

} else {

shuffle($files);

}

// builds the playlist file for mplayer

file_put_contents('/var/www/html/playlist2.txt',"");

foreach ($files as $file) {

file_put_contents('/var/www/html/playlist2.txt', $file . "\n", FILE_APPEND);

}

// outputs the file array for screen with Files marker

echo json_encode([["Files"],$files,[$dir]]);


// outputs list of directories for screen with Directory marker

} else {

echo json_encode([["Directory"],$dirs,[$dir]]);

}

?>

When run, this php will keep producing a list of directories for the user to select, until if only finds files. At that point it will check if the files are in an album. if not in an album, then the files are shuffled and saved to the playlist2.txt file, if an album then the files are saved in their existing order. This creates the playlist for mplayer to use. The php will output a list of directories or files for the app to use for display / selection. You will note that all of this happens in the php file, no need for a bash script.


Next up are the loadlist files:

sudo nano loadlist.sh


#!/bin/bash

echo "loadlist /var/www/html/playlist2.txt 0" >/var/www/html/mplayer-control


sudo nano loadlist.php


<?php


//load file list (playlist2.txt) to mplayer and start playing

$output = shell_exec("sudo -u pi /var/www/html/loadlist.sh 2>&1");


?>

The loadlist command is sent to mplayer, which uses the playlist2.txt file as a playlist and it starts playing music, on the server


We need some feedback from mplayer; which file is playing, how long is it. To get this we need to send mplayer a command. Just to be difficult, mplayer only outputs the information in the place it is running on linux, so we have to redirect this output to the mplayer-output.txt file. We can then grab the last few lines of the output file and return these to the app for processing.

sudo nano mplayer-file-data.sh


#!/bin/bash


echo "get_file_name" >/var/www/html/mplayer-control\

&& echo "get_time_length" >/var/www/html/mplayer-control\

&& echo "get_time_pos" >/var/www/html/mplayer-control\

&& sleep 1\

&& tail -n 3 /var/www/html/mplayer-output.txt

We get the filename, the file length and the playing position of the file, have a little wait to give mplayer time to send the output to the mplayer-output.txt file, then fetch the last three lines of that file.

sudo nano mplayer-file-data.php


<?php


echo shell_exec("/var/www/html/mplayer-file-data.sh 2>&1");


?>

The php file will echo back the output of tail to the app.


The remaining php/sh combinations are for player control: pause/play/next(skip)/quit, all following the same format as above:

sh:

echo "<command>" >/var/www/html/mplayer-control

php

echo shell_exec("/var/www/html/<bash script>.sh 2>&1");


The final script combo is for playing internet radio. I only wanted a few radio stations, so having grabbed the required urls, set them into a list in the app.

sudo nano mplayer-radio.sh


#!/bin/bash


#radio play

echo "loadfile $1 0" >/var/www/html/mplayer-control


You will see this uses loadfile (in this case a url) instead of loadlist, and expects an argument(parameter) - $1


sudo nano mplayer-radio.php


<?php


if(isset($_GET['station'])){

$station = $_GET['station'];

}

//set radio station

echo shell_exec("/var/www/html/mplayer-radio.sh \"${station}\" 2>&1");


?>

In the app we pass the station parameter in the url, this is then passed on to the bash script.


Wow, that is a lot of setting up and preparation, but now we should have everything we need setup on the music server, time to get to work on the app!!


AI2

The app itself is a fairly straight forward affair, a row of buttons, with each button only being presented when it can be used. The buttons are used to send commands via php to the server and then onto mplayer

The main work done by the app is to:

  • grab the playlist data, once set, and display in a listview

  • grab the filename and display it, and select the item in the listview

  • grab the song duration and current position, using this info to run the slider progress and display the current/full time for each file.

  • call the file data script once the current time = song duration. This should always happen just after the next song has started playing, to ensure we get the correct data back.

  • most of the above is not done for radio play back, all the app does is show the selected station

Things I didn't do:

  • allow for the selection and playback of a single song from the displayed playlist

  • handle return to "where you are in the playback" after app sleeps, or is closed and restarted - because the music just carries on playing on the server :)

  • allow for "seeking" in the current music file

  • album/artist/station artwork

  • pretty print directory displays

SCREENS

Carousel imageCarousel imageCarousel imageCarousel imageCarousel imageCarousel imageCarousel imageCarousel imageCarousel image

BLOCKS