Ultrashock Tutorials > Flash 8 > Dynamic OOP Music Player  
 
by Julian Wilson, Neverrain.net
Download Source Files  
 
Dynamic OOP Music Player
 

 Introduction: Dynamic OOP Music Player
 Step 01: Directory Structure and creating your Index and Flash File

 Step 02: The Music Player Class
 Step 03: Code Explanations
 Step 04: The Library Class and Conclusion

Author:
Julian Wilson

Neverrain.net

- discuss this tutorial -

3. Code Explanations

Now it's time for us to take appart the code and explain what it does:

import Library;
import mx.transitions.Tween;
import mx.transitions.easing.*;

Here I’m importing a library class (we will create this later) that I use to keep methods (functions) I reuse for multiple classes, and the Macromedia tweening and easing classes to add a little animation to the player. OOP is about writing reusable code; you need to try to keep only the methods related to what you are doing in each class.

class MusicPlayer
{
//---------------------------------------------------------------------------
// Class Properties
//---------------------------------------------------------------------------
        
         //Target/Path to the player movieclip.
         private var __path:MovieClip;
         //As a shortcut to XML I've decided to define track(s) information in an object.
         private var __tracks:Object;
         //Allows for use of Sound class methods, such as attachSound and start.
         //You can't listen to audio without a Sound object.
         private var __music:Sound;
         //Current track position (in milliseconds)
         private var __position:Number;
         //Current track volume (on a ratio of 0 to 100). Without this property each
         //track would automatically start with a volume of 100.
         private var __volume:Number = 100;
         //Used to determine if pause has been executed or not. If so play will
         //perform different actions to start from the paused position.
         private var __paused:Boolean = false;
         //ID of current track
         private var __trackid:Number;

At the beginning of this block we’ve defined MusicPlayer as a new class; then proceed to define all “class properties” in the class body. Class properties are properties (variables) outside of any class methods, which is why they are defined in the “class body”. Note that you can only set these properties to numbers and strings in the body, once we have created the constructor you can set all other different types of data just as you can a normal property (as long as there isn’t a data type issue).

//---------------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------------
  
//Every classes constructor is automatically run when an instance
//of the class is created.
function MusicPlayer(path:MovieClip, tracks:Object, init:Number)
{
         //Sets constructor variables to the corresponding class properties.
         __path = path;
         __tracks = tracks;
         __trackid = (init-1);
        
         //Attaches actions to all UI controls.
         setupUI();
         //Begins playing the initial track.
         loadNextTrack();
         //Constantly updates scale of position and load bar.
         positionBar();
         loadBar();
}

As the comment says, this is the class constructor. Incase you don’t know what a constructor, it is a method that is run once every time a new instance of the class is created. Remember when you were making the Flash file and I told you how the MusicPlayer class has three parameters, this is where they are passed from the Flash file to the class. Once the constructor has been called I define the class properties that couldn’t be set in the class body since we didn’t have any of the parameters yet, and initialize a few methods to get things started.

Note: The reason I subtract one from the init parameter when setting __trackid is because I use the loadNextTrack method to start the initial track and it automatically adds one to the current track id.

//---------------------------------------------------------------------------
// Class methods
//---------------------------------------------------------------------------

private function changeVolume():Void
{
         var target = __path.volume;
         //Sets volume property to the ratio between where the bar was pressed and its total width.
         __volume = (target._xmouse/target._width)*100;
         //Slides bar to corresponding scale based on above ratio.
         var tween = new Tween(target.mask, "_xscale", Back.easeOut, target.mask._xscale, __volume, 10);
         var slider = new Tween(__path.slider_btn, "_x", Back.easeOut, __path.slider_btn._x, (__path.volume._x+target._xmouse)-(__path.slider_btn._width/2), 10);
         //Updates volume of the track.
         __music.setVolume(__volume);
}

Onto class methods! The first method as the name suggests, changes the volume of the tracks. To add a little more life to things I’ve made the bar scale smoothly according to the position of your mouse when the bar is clicked. This is accomplished by using a ratio of the _xmouse divided by the bar’s width. The bar’s mask is resized smoothly using the tween and easing classes we imported at the beginning of this class.

private function setupUI():Void
{
         //Attaches onRollOver events for all UI controls.
         //A loop is used because all UI events excluding onRelease are the same.
         for(var i in __path.controls)
         {
                  __path.controls[i].onRollOver = function()
                  {
                           this.gotoAndStop("hover");
                  }
                  __path.controls[i].onRollOut = function()
                  {
                           this.gotoAndStop("up");
                  }
         }
        
         __path.controls.play_btn.onRelease = __path.controls.play_btn.onReleaseOutside = Library.delegate(this, "playTrack");
         __path.controls.pause_btn.onRelease = __path.controls.pause_btn.onReleaseOutside = Library.delegate(this, "pauseTrack");
         //Plays the previous or next track. The next track is determined using the getTrackID method.
         __path.controls.prev_btn.onRelease = __path.controls.prev_btn.onReleaseOutside = Library.delegate(this, "loadPrevTrack");
         __path.controls.next_btn.onRelease = __path.controls.next_btn.onReleaseOutside = Library.delegate(this, "loadNextTrack");
         __path.volume.onPress = Library.delegate(this, "changeVolume");
}

This is where all the events are setup. The onRollOver and onRollOut actions for all the controls (play, pause, previous track, next track) are the same, which is why I’ve decided to use a for loop with the same actions that runs once for each movie clip inside the controls movie clip.

By now you’ve probably noticed the use of the Library class and an unmentioned method called delegate. If your familiar with Macromedia’s Delegate Class you know that delegate is used to fix object scope. I’m doing the same thing as Macromedia, except mine has the ability to pass parameters to the callback method. I will go into more detail about the delegate method later on after we’ve finished the MusicPlayer class.

private function pauseTrack():Void
{
         __paused = true;
         updateUIStatus();
         //Remembers position when paused. This is needed for when the track is re-started
         //so that it knows what position to start from.
         __position = __music.position;
         __music.stop();
}

Pausing a sound is actually recording the position (in milliseconds) of when the track was paused, keeping it from playing and it can then be re-started from the current position.

private function getTrackID(direction:Number):Number
{
         var id = __trackid;
         //Adds direction (-1 is previous and 1 is next) to the current track ID.
         id += direction;
         //Instead of stopping at the first or last track, loop.
         if(id < 0)
         {
                  //Sets ID to the last track ID if less than zero.
                  id += __tracks["titles"].length;
         }
         else
         {
                  //Sets  ID to the first track ID if greater than total tracks.
                  id %= __tracks["titles"].length;
         }
         return id;
}

This method is used to return the ID of the next or previous track, depending on the direction specified. I’ve added a check to see if ID goes below zero, and if so the number of total tracks is returned, otherwise I use the modulo assignment operator when setting the ID to return to ensure the ID stays below the total tracks. The reason for these checks are so I can create an endless loop instead of having to go through the tracks one at a time to get from the first to the last track and visa-versa.

//Updates status of the play and pause buttons according to if paused or not
private function updateUIStatus():Void
{
         if(__paused)
         {
                  __path.controls.play_btn.enabled = true;
                  __path.controls.play_btn.gotoAndStop("up");
                  __path.controls.pause_btn.gotoAndStop("hover");
                  __path.controls.pause_btn.enabled = false;
         }
         else
         {
                  __path.controls.pause_btn.enabled = true;
                  __path.controls.pause_btn.gotoAndStop("up");
                  __path.controls.play_btn.gotoAndStop("hover");
                  __path.controls.play_btn.enabled = false;
         }
}

To prevent from doing the same thing twice I don’t allow users to click the play button when playing or the pause button when paused, all other buttons do not need this since they need to be able to be pressed multiple times. Which button is disabled depends on whether the current track is paused or not. Once a determination has been made the method proceeds to disable the button using the enabled state and set it to its hover frame to make it different from the other buttons.

private function playTrack():Void
{
         //Checks if track is paused. If so start from pause position, otherwise start from the beginning.
         if(__paused)
         {
                  __music.start(__position/1000);
                  __paused = false;
                  updateUIStatus();
         }
         else
                  {
                  __music.start();
         }
}
private function beginLoad(id:Number):Void
{
         __trackid = id;
         //Resets sound. Without this position and duration will output the values
         //from the first track loaded.
         __music = new Sound();
         //When track is finished playing, play the next one.
         __music.onSoundComplete = Library.delegate(this, "loadNextTrack");
         //Starts streaming track
         __music.loadSound(__tracks["urls"][id], true);
         __music.setVolume(__volume);
         __paused = false;
         updateUIStatus();
        
         //Populates title and artist textfields with new track data.
         __path.title_txt.text = __tracks["titles"][id].toUpperCase();
         __path.artist_txt.text = __tracks["artists"][id].toUpperCase();
         //Resets position and load bar scale.
         __path.track.mask._xscale = __path.track.load_mask._xscale = 0;
}

The first method, playTrack, checks if the track is paused or not, if so the track is re-started from the pause position, otherwise it will be started from the beginning.

beginLoad is used by both loadNextTrack and loadPrevTrack to set everything up for a load. A new sound object is created each time a new track is loaded to prevent sounds from overlapping. After the track has finished it runs loadNextTrack again creating a loop.

private function loadNextTrack():Void
{
         //Starts loging the next track. The next tracks ID is processed by the getTrackID
         //method to enable looping.
         beginLoad(getTrackID(1));
}
private function loadPrevTrack():Void
{
         beginLoad(getTrackID(-1));
}

Loads previous and next tracks, simple huh?

private function positionBar():Void
{
         //Constantly updates bar scale.
         __path.track.mask.onEnterFrame = Library.delegate(this, "updatePositionBar");
}
private function updatePositionBar():Void
{
         //Finds percentage loaded of the track loading
         var percent = (__music.getBytesLoaded()/__music.getBytesTotal())*100;
         if(percent > 0)
         {
                  __path.track.mask._xscale = __music.position/(__music.duration/percent);
         }
}

This function updates the scale of the bar once every frame to mark the accurate duration of the track. A lot of people this is not possible to stream sound without using ID3 tags since Sound.duration returns the duration of how much of the track has loaded. The full duration cannot be calculated until the file has fully loaded. Another solution is to use the TLEN ID3 tag, the problem was that MP3s have this tag encoded and it wouldn’t work at all if this tag couldn’t be read. Instead I finally worked out a solution by taking the position divided by duration divided by percent loaded to get the correct ratio to scale the bar.

private function loadBar():Void
{
         __path.track.load_mask.onEnterFrame = Library.delegate(this, "updateLoadBar");
}
private function updateLoadBar():Void
{
         var percent = (__music.getBytesLoaded()/__music.getBytesTotal())*100;
         if(percent > 0)
         {
                  __path.track.load_mask._xscale = percent;
         }
}

The same method is applied for the load bar, except the ratio for the bar is simply the percentage loaded.

//Returns ID of the current track playing.
public function get trackid()
{
         return __trackid;
}

This becomes useful if you are trying to access the ID of the current track playing outside the class. You can do this by using player.trackid in the Flash file.


- discuss this tutorial -
 
©2006 Ultrashock.com - All rights reserved