This is part one of a multi-part tutorial showing how to build an AS 3.0, XML-supported MP3 player. I’m doing this mainly because I’ve had to do it myself for a couple of projects, secondly because I can’t find any tutorials that cover all the points I’ll cover here, and thirdly because it is ridiculous to have to PAY for this knowledge. Knowledge should be free, always.
Difficulty: Intermediate
Required knowledge: AS 3.0 class/method building, understanding of XML, AS 3.0 XML methods, basic knowledge of the Sound object, basic knowledge of the SoundSpectrum object
Here are the features that will be present in this MP3 Player:
- AS 3.0 compliant
- XML supported
- Stand-alone; able to be dropped into other programs with minimal conflict
- Controls for previous/next track
- Controls for stop/play/pause of current track
- Ability to scrub along length of current track
- Control for volume
- Display containing track title, track album, track artist
- Display time elapsed and time remaining
- Display current progress (how much of current track has played)
- Display frequency visualizer
Today’s files will contain only some of the basic controls, and the Actionscript will be on the first frame of the main timeline. Next week’s files will have the project spec’d out in OOP AS 3.0, with all the controls.
Let’s go!
First, the directory structure should be as illustrated below:

Notice we have subdirectories for the xml, images (album artwork), and the actual tracks. This will help to keep everything neat and tidy. In the next part of this tutorial, we’ll also have a folder for the various classes we’ll be creating from this original set of methods.
Second, create a main fla file. Mine is titled mp3player.fla. In this are all the physical assets that we’re working with, and will be used throughout the project. Or, just download the files at the bottom of this post, open it up, and follow along.
Now on to the code. First, we need to declare some variables to take care of requesting the XML document, loading the XML document (and inherently the data within), requesting the mp3 file, loading the mp3 file, and working with it as a sound object inside Flash. Some other things we need to keep in mind are tracking the index of the current track. In other words, a number that indicates to Flash where in the list of tracks it currently is so that we can effectively build control methods for the next and previous buttons on the mp3 player interface. All this code, at this point in time, is contained on the first frame of the main timeline on a layer I’ve labeled “as”. The second layer on the main timeline contains all the physical assets we’re working with.
First batch of code:
// various things
var trackReq:URLRequest;
var imgRequest:URLRequest; // future use
var imgLoader:Loader; // future use
var imgSprite:Sprite; // future use
var track:Sound = new Sound(); // sound object
var sc:SoundChannel; // enables control of sound
var currentSound:Sound = track; // reference var for play/pause/next/previous methods
var pos:Number; // position of the track
var isPlaying:Boolean = false; // assists with play/pause/stop buttons
var trackLength:Number; // for display purposes only
var currentIndex:Number = 0; // index number to assist in navigating in list of tracks
// visualizer stuff
var myByteArray:ByteArray = new ByteArray();
var _w:Number = 16;
var _mc:Sprite = new Sprite();
trackDisplay.bg.addChild(_mc);
_mc.x = (trackDisplay.bg.width/2) + 294 * -1;
_mc.y = 28;
// xml stuff
var xml:XML; // xml object
var loader:URLLoader = new URLLoader(); // loader for xml data
var trackList:XMLList; // xml list object
Now we need to build some methods to start the XML loading process, start the player once all that is loaded, and then include functionality so the user can do various things. Like dance. And HEY – I don’t have volume control, loading images, or playhead scrubbing listed right now because we’re not doing that this week. That’s next week. Chill.
Here is a list of methods we need to build:
- init – initiates the loading process
- onLoaded – takes action once the data is loaded
- nextTrack – goes to the next track
- previousTrack – goes to the previous track
- stopTrack – stops current track
- pauseTrack – pauses current track
- playTrack – play current track
- onTimer – updates timer display info
Inside the included fla, you’ll see that I’ve got assets just sitting on the main stage. This is for ease of use/coding at this point in time and will change in the future. But for now, just recognize that each of the four main buttons are on the stage, as is the display and the background of the player. Ok – back to the code…
init method:
function init() {
loader.addEventListener(Event.COMPLETE, onLoaded);
loader.load(new URLRequest("xml/playlist.xml"));
nextTrackBtn.addEventListener(MouseEvent.CLICK, nextTrack);
previousTrackBtn.addEventListener(MouseEvent.CLICK, previousTrack);
ppTrackBtn.addEventListener(MouseEvent.CLICK, pauseTrack);
stopTrackBtn.addEventListener(MouseEvent.CLICK, stopTrack);
}
First we add an EventListener to the loader so that it calls onLoaded when the loading of data is complete. Then we call the method load to begin the actual load process. After this are EventListeners for each of our main control buttons: next, previous, stop, and play/pause (ppTrackBtn). You’ll find these already on the stage, physically existing on top of the background for the player. The display movieclip is there as well.
onLoaded method:
function onLoaded(e:Event):void {
xml = new XML(e.target.data);
trackList = xml.trackItem;
trackReq = new URLRequest(trackList[currentIndex].trackPath);
track.load(trackReq);
trackLength = currentSound.length;
sc = track.play();
isPlaying = true;
trackDisplay.trackInfo.text = trackList[currentIndex].trackTitle + "\n" +
trackList[currentIndex].trackArtist;
sc.addEventListener(Event.SOUND_COMPLETE, nextTrack);
ppTrackBtn.icon.gotoAndStop(1);
trackDisplay.addEventListener(Event.ENTER_FRAME, updateTime);
trackDisplay.bg.addEventListener(Event.ENTER_FRAME, readSpectrum);
trackDisplay.playhead.addEventListener(Event.ENTER_FRAME, updatePlayhead);
}
So with just those two methods, the player is actually able to get going and play. However, what’s an mp3 player without controls for the user? Why, it’s the most annoying thing EVAH! Let’s put some control methods in there. Below is nextTrack, previousTrack, pauseTrack, playTrack and stopTrack.
function nextTrack(e:Event):void {
if (currentIndex < (trackList.length() - 1)) {
currentIndex++;
} else {
currentIndex = 0;
}
var newReq:URLRequest = new URLRequest(trackList[currentIndex].trackPath);
var newSound:Sound = new Sound(newReq);
sc.stop();
trackDisplay.trackInfo.text = trackList[currentIndex].trackTitle + "\n" +
trackList[currentIndex].trackArtist;
sc = newSound.play();
isPlaying = true;
currentSound = newSound;
trackLength = currentSound.length;
sc.addEventListener(Event.SOUND_COMPLETE, nextTrack);
}
function previousTrack(e:Event):void {
if (currentIndex > 0) {
currentIndex--;
} else {
currentIndex = trackList.length() - 1;
}
var newReq:URLRequest = new URLRequest(trackList[currentIndex].url);
var newSound:Sound = new Sound(newReq);
sc.stop();
trackDisplay.trackInfo.text = trackList[currentIndex].trackTitle + "\n" +
trackList[currentIndex].trackArtist;
sc = newSound.play();
isPlaying = true;
currentSound = newSound;
trackLength = currentSound.length;
sc.addEventListener(Event.SOUND_COMPLETE, nextTrack);
}
function pauseTrack(e:Event):void {
if(isPlaying == true) {
pos = sc.position;
sc.stop();
isPlaying = false;
ppTrackBtn.removeEventListener(MouseEvent.CLICK, pauseTrack);
ppTrackBtn.addEventListener(MouseEvent.CLICK, playTrack);
ppTrackBtn.icon.gotoAndStop(2);
}
}
function playTrack(e:Event):void {
if(isPlaying == false) {
sc = currentSound.play(pos);
sc.addEventListener(Event.SOUND_COMPLETE, nextTrack);
isPlaying = true;
ppTrackBtn.removeEventListener(MouseEvent.CLICK, playTrack);
ppTrackBtn.addEventListener(MouseEvent.CLICK, pauseTrack);
ppTrackBtn.icon.gotoAndStop(1);
}
}
function stopTrack(e:Event):void {
if(isPlaying == true) {
sc.stop();
pos = 0;
isPlaying = false;
ppTrackBtn.addEventListener(MouseEvent.CLICK, playTrack);
ppTrackBtn.icon.gotoAndStop(2);
}
}
Some things to note in the above code: first of all, for nextTrack and previousTrack, we immediately check currentIndex against the length of the XMLList. In other words, we see if, in the case of nextTrack, whether we’re already at the last track. If so, we need to set currentIndex to 0 in order to cycle back to the first track. We use the same logic, but in reverse, for previousTrack. If we’re already at the first track, then we set the currentIndex to the length of the list minus 1, because all arrays start counting with 0. Therefore, the last item’s index is the length of the list minus 1.
Also, in order to not inadvertently affect the current track playing, we generate a new request and a new loader on the fly to load the new track.
With pauseTrack, playTrack and stopTrack, we use the isPlaying Boolean to check whether we should allow that action to occur or not. In other words, a use will be able to press the stop button at any time, but ONLY if the track is playing will those actions take place. We don’t need Flash stop stop stopping all the time. Get it?
So now our player has basic mechanical interaction enabled. Neato. We need to give the user something to look at, time-wise. And it’d be nice to see that also reflected in the visual representation of the length of the track (by the light green bar) and where it’s at in its play progress (darker green bar). The next two methods are updateTime and updatePlayhead.
First, the updateTime method:
function updateTime(e:Event):void {
var elapsedText:String;
var remainingText:String;
var secFix:String;
var minFix:String;
var minTotal:Number = Math.floor(currentSound.length/1000) / 60 >> 0;
var secTotal:Number = Math.floor(currentSound.length/1000) % 60 >> 0;
var minElapsed:Number = Math.floor(sc.position / 1000) / 60 >> 0;
var secElapsed:Number = Math.floor(sc.position / 1000) % 60 >> 0;
var minRemaining:Number = int(minTotal - minElapsed);
var secRemaining:Number;
(secTotal >= secElapsed) ? secRemaining = int(secTotal - secElapsed) :
secRemaining = int((secTotal + 60) - secElapsed);
(secTotal >= secElapsed) ? minRemaining : minRemaining = minRemaining - 1;
(secElapsed < 10) ? secFix = "0" : secFix = "";
(minElapsed < 10) ? minFix = "0" : minFix = "";
elapsedText = minFix + minElapsed.toString() + ":" + secFix + secElapsed.toString();
(secRemaining < 10) ? secFix = "0" : secFix = "";
(minRemaining < 10) ? minFix = "0" : minFix = "";
remainingText = minFix + minRemaining.toString() + ":" + secFix + secRemaining.toString();
trackDisplay.timeRemaining.text = remainingText;
trackDisplay.timeElapsed.text = elapsedText;
}
What the hell is all that?! Ok, believe it or not, the little time displays you see in Flash, if coded properly, are tiny little pains in the asses. For one thing, when Flash has an amount less than 10, then it only outputs a single digit. For any clock or timer display, that doesn't work, so we have to add "0" to the front of the result.
Also, Flash counts time in milliseconds. It always has, and apparently, always will. I'm not really sure if any human other than Superman (human status questionable) or Neo (again, questionable, DUDE) can actually determine an amount of time equal to milliseconds, but there ya go. That's what we have to work with, and so....
In order to get any time amount in Flash into human legible form, we need to divide whatever time amount by 1000 to generate a result in seconds, and then divide again by 60 to determine the number of minutes. We then use right bitwise shifting to give us a whole integer result, rather than a decimal one. In order to get *just* the amount of seconds, minus the number of minutes, we divide the time amount by 1000, and then use the modulo operand (which only returns the remainder of a division result) to get the remainder generated and then again bitwise shift right two places to get a whole integer result.
However, we're not done yet. In order to make sure that we're not displaying a negative amount of time, like when the number of seconds elapsed is greater than the number of seconds remainder of the total amount, we need to make exception for that, and take proper action. On top of that, if the time remaining has cycled through 60 seconds, we need to make sure that the minute amount reflects that as well. This is one of the lines that take care of that:
(secTotal >= secElapsed) ? minRemaining : minRemaining = minRemaining - 1;
Instead of doing an if…else set of statements, I find using the conditional operand ( ?: ) to be easier and more succinct. The basic logic behind using the conditional operand looks like so:
(conditional statement) ? true : false;
Basically each line in the mp3player code is asking whether the conditional is true. If so, then the action to take is to the left of the colon. If the conditional is false, then the action to the right of the colon is taken.
And, even more important, recognize that we must subtract 1 from minRemaining rather than use the negative postfix operand. Example: x– If we use the negative postfix operand, we are literally changing the value of minRemaining to one less than its current state, which will result in it subtracting 1 from itself more than we’d like. Using a standard subtraction equation keeps us from accidentally doing that.
Last, but not least, we need to check if any of our results are less than 10 and if so, we need to place the string “0″ in front of the result so that it appears as a legible representation of time.
Ok. Got that? Time sucks ass. Tiny, little, milliseconds of ass.
Updating the playhead is almost dead simple compared to the above.
updatePlayhead method:
function updatePlayhead(e:Event):void {
var currentPercentComplete:Number =
((Math.floor(sc.position)/1000)/(Math.floor(currentSound.length)/1000) * 100);
trackDisplay.playhead.trackProgress.width =
(trackDisplay.playhead.trackLength.width * currentPercentComplete)/100;
}
Treating the playhead trackLength movieclip as a physical representation of the time length of the track, we can then use the percentage complete as a means of resizing the playhead trackProgress movieclip. Simple!
Almost last, and not least, just to have some fun, add in the methods for the visualizer, which draws on the display a frequency visualizer, giving the user something neato to stare at while they procrastinate on various projects.
function readSpectrum(e:Event):void {
SoundMixer.computeSpectrum(myByteArray, true, 0);
_mc.graphics.clear();
for (var i = 0; i < 256; i += 16) {
drawSpectrum(myByteArray.readFloat(), i);
}
}
function drawSpectrum(myReadFloat:Number, myIteration:Number) {
var myNumber:Number = myReadFloat * 190;
_mc.graphics.lineStyle(0, 0x7CBC14);
_mc.graphics.beginFill(0x000000, .08);
_mc.graphics.drawRect((16 * (myIteration / 16 )), 0, 16, -(myNumber / 5));
}
Those two methods enable the movieclip bg in the display to draw rectangles of varying length showing the amplitude of the range of frequencies of the current track playing in real time.
Finally, last, but not least, get the whole damn thing going with a call to the init method.
init();
And there ya go. A working soon-to-be OOP AS 3.0 compliant, XML-supported, BASIC mp3 player.
[ AS3.0 XML MP3 Player ]