Flash MX MP3 player version 2, Pg.3
source: http://www.thegoldenmean.com
If you got to this page directly from a search engine or other link, be advised that the tutorial you are currently reading extends an earlier article which you can (and need to) read here
3 — PHP and ID3
We have come to the defining portion of part 2. The part that distinguishes this version of the player from Version One.
Recall that Version One built a mechanism to play MP3 files, and that Version One was easy to maintain because it got its information about what tracks to play from a small, easy to code XML file. Nothing about the music itself was built into the Flash movie: it consumed the xml file and learned all it need to know about what media resources were available from that file. Adding tracks was as easy as uploading new MP3 files to your web server and modifying one small file in a text editor. Simple, right?
Hold onto your hats ladies and gentlemen, because PHP is going to make it even easier!
ID3 - the hidden assets
The MP3 file specification permits a kind of metadata in the form of what are called ID3 tags. This means that, in addition to the data that relates to the music itself, MP3 files are capable of carrying data about a wide range of attributes associated with the media file including but not limited to the title, the artist, the album, the track number on the album, the creation date, the “genre” and a great many more things.
For the curious, id3.org contains a wealth of background and technical specifications about ID3 tags. It can be interesting reading.
Just because an MP3 file can contain this data doesn’t mean it does. And not all MP3 files carry the same attributes. And to further complicate things, there are two versions of ID3 tags: version one and version two. Version one (the older version) carries the tags at the end of the file making it harder to capture if the music streams. Version two tags (the newer specification) are located at the beginning of the file and are much more flexible. We as Flash developers are blessed because all the hard work involved in extracting the information contained in ID3 tags has been done and is available in the form of PHP classes. There are several class libraries available. This tutorial uses a relatively compact but powerful one written by Daniel Martinez–Morales, distributed by the phpclasses.org site. Mr. Martinez-Morales’ class claims to extract both version one and two tags. My (admittedly limited) experience indicates this to be true. Isn’t that wonderful?
Adding or editing ID3 tags in your MP3 files:
For any of this to work, your MP3 files need to have ID3 tags, and the ID3 tags need to contain at least the minimum information: title, artist and album. What if your MP3 files don’t? It turns out that freely available software including iTunes and WinAmp have very capable ID3 editing capabilities. Simply open an MP3 file in one of these programs (and there are many more besides these two) to create or modify the ID3 tags as you choose.
I am also advised that Windows XP permits one to edit ID3 version 2 tags via the File Explorer. Right click the file and select Properties>Advanced. The My Music folder will display the file name as well as Artist, Album, Year, Track Number and Duration. This is a very fast and direct means of modifying ID3 tags.
What data is contained in your music files? You will be astounded. Included in the demo folder is a small PHP file called test.php. Try it out. Put several MP3 music files in the folder that contains the PHP files. Upload the whole folder to a web server. Using the web browser of your choice, navigate to the test.php file in that folder and observe the wealth of information the PHP code is able to extract from your music files. This is a very helpful thing to do, because it will also show you exactly the structure of the arrays the id3v2 object will contain. I strongly encourage you to look at “test.php” right away, because it will help you visualize the data structure we will soon be tapping.
What? You don’t have the background (or the ambition) to do that yet? Okay, just because it is so important you can have a peek at mine. Click this link to have a look at how test.php rips out and organizes the information from the eight MP3 files the example on page one uses. Impressive isn’t it? After studying it, click your browser’s “back” button to continue reading this tutorial.
It is really very important for you to stare at and study the output from test.php. At first it is going to seem just like lines and lines of meaningless stuff, but it gives a very structured look at how this PHP class organizes what it extracts. It will turn out to be very important to know this in just a few paragraphs, because soon we are going to need to know how to get at specific bits for our own purposes. Study the organization of the arrays now — it will pay off soon.
selective extraction
We only need a small subset of the total information available, but we need it served in the form our Flash movie is coded to expect: an XML format with specific attribute tags. In order to get that information, presented in the way our Flash movie is set to parse it, we must write a relatively simple PHP script of our own which will mediate between the arrays which the ID3v2 class has populated and the Flash movie. In Version One of this series, the file the Flash Player consumed was named “songList.xml”. In order to be parsed by the PHP module of a web server, the file should have a .php suffix (so that the web server knows to pass this through its PHP processor). Accordingly, we will name the new PHP script “songList.xml.php”. The “.xml” is optional, but I like the reminder it gives about what sort of file this script is going to pretend to be.
the song list file
Start a new document in your favorite text editor and save it as “songList.xml.php”.
I had better say this now: all files must be placed in the same directory. That means the two PHP class files (id3v2.php and arrays.php), the .fla, .swf and HTML files, all the music files you want to hear and the songList.xml.php file we are about to write. You can certainly feel free to put things in different places on your server to meet your organizational requirements but it is up to you to modify my code specifying the correct paths so every file can find what it needs to work properly. This tutorial assumes all files are in the same directory.
The opening statement of our PHP file includes the opening PHP tag, followed by a request for the class code, followed by an instantiation of the class.
<?php /* Copyright © 2004 by Tilo Kussatz <http://www.kussatz.com> */ include_once("id3v2.php"); $mp3 = new id3v2();
Next we echo some strings that represent the opening tags of what will be the xml file, and we set the directory the script needs to search. The variable “$dir” uses the notation “./” which means “this (the current) directory”.
// Print opening XML tags echo('<?xml version="1.0" ?>' . "\n"); echo(' <songs>' . "\n"); // Set the directory which contains the MP3 files. //Don't forget the trailing slash! // "./" is the directory this PHP file lives in. $dir = './';
Now we want the script to rummage through all the files in the current directory. We use the opendir() function and set up a while() loop to read through the contents of the directory using PHP’s readdir() function. That means “do something to the first file, and to every other file until there aren’t any more files to do it to!” (In other words, the loop stops when it reaches the last file in the directory it is examining.) The “it” that we are doing to each file is extracting their ID3 information.
But wait! That directory has other stuff besides MP3 files! It has at least three PHP scripts, at least one .swf file and at least one HTML file. It might even have a nested directory or invisible “system file”. How does the script know to ignore everything except MP3 files? First we check to make sure it is not a directory, and then a simple regular expression makes sure it does end with a “.mp3” extension.
$handle = opendir($dir); // Loop through all the files found in this directory while (false !== ($file = readdir($handle))) { // Ignore any types of subdirectories and //only process files with ".mp3" extension if (!is_dir($file) && eregi("(.mp3)$", $file)) { $filesize = filesize($dir.$file); // Extract ID3 information from current file $mp3->GetInfo($dir.$file);
If that didn’t make sense, let me try expressing it another way.
if the file is not a directory and
if the file’s name does end in “.mp3”,
then run the GetInfo() method of the class on it and then check the next file if there is one.
Hah! That certainly clarifies things, doesn’t it?
The ID3v2 script trudges dutifully through every qualifying file in the directory, stuffing arrays with information contained in each MP3 file it encounters.
Now we are at the point of addressing that wealth of data stored in arrays by Mr. Martinez–Morales’ ID3v2 script and pulling out what we are interested in. This is where a familiarity with the organizational structure is critical. If you want to refresh your memory, here’s a link one final time to the test.php script. Let us begin by finding out if the “artist” data is available from $file’s ID3 tag, and retrieving it if it is:
// Build the <song> XML tag for the current song, // preferably using ID3v2 information, otherwise ID3v1 data if available. // Step 1: the "artist" attribute: echo ' <song artist="'; if (!empty($mp3->id3v2Info['TP1']['info'][0]['Value'])) echo $mp3->id3v2Info['TP1']['info'][0]['Value']; elseif (!empty($mp3->id3v2Info['TPE1']['info'][0]['Value'])) echo $mp3->id3v2Info['TPE1']['info'][0]['Value']; elseif (!empty($mp3->id3v1Info['artist'])) echo $mp3->id3v1Info['artist']; else echo 'artist not available';
Let’s look at this closely, because a significant portion of the songList script follows this form. At this point in the script we want to get the value of the “artist” ID3 tag if it exists. Martinez-Morales’ code creates an elaborate system of associative arrays. The primary array, the one that holds most of the ID3 tag information, is called “id3v2Info”. Since the tag could be a version one or a version two tag, and since the different versions use different tag names, the script checks first to see if the version two tag “TP1” contains information (or, as the script puts it, “is not empty”). If it finds nothing (if it is empty) it checks the id3v2Info array to see if an alternate version two tag “TPE1” contains information. Failing that it looks to see if there is anything in the ID3 version one array “id3v1Info” (which value would be stored in the “[artist]” element). Failing that, it writes a string announcing that the artist information is just not available.
Hmmm. If that didn't help to clarify things let me try to express it in a different way. You might know that the bracket operators, “[” and “]”, are used to identify elements in an array. Let’s just look at this pair of lines to keep it simple:
$mp3->id3v2Info['TP1']['info'][0]['Value']
echo $mp3->id3v2Info['TP1']['info'][0]['Value'];
Reading the first line from right to left it says in effect:
if there is a value in the first element of the info array which is an element of the TP1 array which is an element of the id3v2Info array which is a property of the Object stored in the $mp3 variable,
then…
And the second line of that pair just says “echo that value.”
Ahhh - but the observant of you protest that those two lines were wrapped in a conditional. They actually begin
“if (!empty(…”
In the sort of round–about way of scripting conditionals, that says “If there is not no value in that array element, then echo that value; otherwise forget about it. Move on and try the next line!”
Or: “don’t echo the value if the array element is empty!”
Whew! That was sort of gruesome I admit, but if you grasp that, the next three chunks follow exactly the same form, just addressing different elements of the primary array. Here is how we request the “title” information. (Note that we begin by echoing a string to close the previous (“artist”) attribute and start a new attribute for “title”.)
// Step 2: the "title" attribute: echo '" title="'; if (!empty($mp3->id3v2Info['TT2']['info'][0]['Value'])) echo $mp3->id3v2Info['TT2']['info'][0]['Value']; elseif (!empty($mp3->id3v2Info['TIT2']['info'][0]['Value'])) echo $mp3->id3v2Info['TIT2']['info'][0]['Value']; elseif (!empty($mp3->id3v1Info['title'])) echo $mp3->id3v1Info['title']; else echo 'title not available';
This is starting to look like a pattern! In like manner, we address the “album” information (again beginning by closing the previous attribute and opening a new one):
// Step 3: the "album" attribute: echo '" album="'; if (!empty($mp3->id3v2Info['TAL']['info'][0]['Value'])) echo $mp3->id3v2Info['TAL']['info'][0]['Value']; elseif (!empty($mp3->id3v2Info['TALB']['info'][0]['Value'])) echo $mp3->id3v2Info['TALB']['info'][0]['Value']; elseif (!empty($mp3->id3v1Info['album'])) echo $mp3->id3v1Info['album']; else echo 'album not available';
Nearly done. We have gotten everything we need from the primary id3v2Info array. Writing a few more values to the XML file will be helpful with our project. Then it’s time to move on to the next file in the directory (if there is one) or close the XML document and the directory if that was the last file.
// Step 4 - extract PlayTime //get it in milliseconds to match position property in ActionScript echo '" playtime="'.floor($mp3->mpegInfo['PlaySeconds']*1000); // Step 5 get file size, to use with graphical preload bar echo '" filesize="'.$filesize; // Step 6: the "url" attribute: echo '" url="'.$dir.$file.'" />' . "n"; // That's it for this file - on to the next one! } } closedir($handle); // Print closing tag - we're done! echo(' </songs>' . "n"); ?>
Note that “PlaySeconds” is a calculated value, and is found in a different array: “mpgInfo”. Also note that “$url” is simply the MP3 file’s name and that “$filesize” is a number expressed in kilobytes. These last two values are file properties which PHP can read from any file — they are not specific to MP3 files nor are they carried in ID3 tags. The only attributes we are harvesting that are actually carried in ID3 tags are “artist”, “title” and “album”.
The script then closes the XML node and begins again with the next file in the directory. When the script reaches the last qualifying file in the directory (that is, when “false !== ($file = readdir($handle))” ceases to return true) it safely closes the directory, writes the closing XML tags and exits.
And that concludes the songList.xml.php file my friends. If you made the modifications to your Flash movie as instructed on page two and you open that movie in a web browser (through a web server don’t forget), you should find that the ListBox is populated and the tunes play when clicked on! If you point your web browser to the songList.xml.php file instead of the HTML page that contains the Flash movie, you might see the actual XML file (if you are using Internet Explorer) or you might see… er, nothing! If you use a browser other than Explorer and see a blank page then look at the source of that page. You should be gazing in wonder at the XML file. Wonderful! Marvelous!
What? You’re too busy reading this tutorial to have written and uploaded your own script??? Sigh. Okay, click this link to look at the XML that drives the example on page one of this tutorial. And remember as you look at it that it was generated on the fly by PHP.
Remember, some browsers will render the XML visibly after clicking the link, but if the browser you are using doesn’t, look at the source of the page!
Now if you are observant, you will recall that the only information about each track the previous version of the hard–coded XML file carried was “title” and “url”. But this XML file has a lot more stuff: stuff like filesize, playtime, album and artist. What’s up with this? We’re building for the future at this point. Part three of this epic series will want the filesize and playtime in order to animate a graphical progress bar. You’ll have to wait a while for that. But the last page of this tutorial will add a text field that displays “title/artist/album”.
“Notices”
You might find yourself in this scenario: your ListBox component renders every item as “undefined”, and nothing plays. You look at your songList.xml.php file directly and instead of a nice tidy XML document you see rows and rows of messages that begin something like Notice: Undefined variable:…
What’s going on???
Our hero Tilo (the author of the songList.xml.php script) noticed that in certain configurations of the apache/php server, error reporting is set very strictly. We do want to know about errors but we can live without all these notices. The solution is to suppress these notices by adding the following line at the beginning of the songList.xml.php script
error_reporting(E_ALL ^ E_NOTICE);
songList.xml.php update:
kuckus has released an improved version of the “songList.xml.php” file. I have not yet had time to re-write this page to reflect the new code but I am making it available as a separate download here.
Briefly: readers began to report bafflement at how their songs appeared in the menu. In some cases they didn’t seem to be sorted alphabetically or numerically as expected. It was discovered by kuckus that some configurations of Apache organize directory contents by date the file was added! If you are finding that order is important and you want to sort by a specific criteria you will love what kuckus has done.
The updated script allows you to sort by any one of the following criteria:
- filename
- artist
- title
- album
- playtime
- filesize
You may download this updated file here. Instructions for modifying the script are included as comments. Please note that this update is not included in the project files download (never enough time to do everything…).
Please note the following change:
If you download the updated script please read the comments before simply replacing the “songList.xml.php” file in the original project file folder with this new one. The new script writes a path to the MP3 assets based on the assumption that all PHP scripts will be contained along with the MP3 files in a new folder called “tracks”. This departs from the original instructions for this tutorial, and has tripped up a number of people. We did it this way to help keep things more organized on your server.
To clarify: if you download the updated songList.xml.php file, you need to make a new directory at the same level as the .swf and html files. Name this new directory “tracks”, and into this new directory put all three PHP scripts and all MP3 files. Don’t forget to modify the .fla to locate the song list in its new location! I apologize for the confusion this has created, but I do believe it is a significant improvement in organization.
Thanks for your effort kuckus!
Let’s conclude Version Two of the Player with some cosmetic enhancements: we’ll replace the button components with MovieClips and we’ll add that additional information text field.
- page one - the introduction
- page two - minor modifications to the Flash movie
- page four - some cosmetic embellishments to the Flash movie
--top--