//last modified 19 March 2005 /*----------------------------------- Step 1 - import critical external classes: -----------------------------------*/ //import Delegate class import mx.utils.Delegate; //import XPath classes import com.xfactorstudio.xml.xpath.*; /*----------------------------------- Step 2 - initialize movie environment -----------------------------------*/ // A - misc. //remove loading text loadmsg_txt.removeTextField(); //prohibit resizing movie Stage.scaleMode = "noScale"; //default play mode is continuous, so turn "on" the play button controls_mc.play_mc.gotoAndStop("lit"); // B - declare critical variables so they can be accessed from within functions: //the main Sound Object var myTunes:Sound; //the XML playlist ("pl" is short for "playlist") var plXML:XML; //a variable to store the current track ("ct") number var ct:Number; //a variable to store the maximum number of tracks ("mt") var mt:Number; //a variable to store position at wich the sound was paused var pauseTime:Number = 0; //the default starting volume - can be modified by volume slider var volume:Number = 50; //whether or not tracks are played in random order //default play mode is "linear" - this is modified by "shuffle_mc" var shuffle:Boolean = false; //whether the player is playing or stopped var stopped:Boolean = false; //a flag indicating whether the track has loaded o isr still streaming var trackLoaded:Boolean; //the width of the info text module var infoW:Number = info_mc._width; var streamingID:Number; var playingID:Number; var crawlerID:Number; // C - supply short convenient names for frequently used assets //the slider on the volume control var marker:MovieClip = controls_mc.volume_mc.slider_mc; //the bar that indicates how much of the song has played var pBar:MovieClip = loadStatus_mc.bars_mc.pBar_mc; //the bar that indicates how much of the song has downloaded var dBar:MovieClip = loadStatus_mc.bars_mc.dBar_mc; //the scrolling text field var infoTxt:TextField = info_mc.info_txt; //make infoTxt expandable: this permits the text field to be //as wide as necessary to contain variable amounts of text infoTxt.autoSize = "left"; /*the following two lines are useful for animating the infoTxt text field. We scroll the text field relative to the info_mc's coordinates, but code written on the main timeline will return info_mc's coordinates relative to the Stage. The globalToLocal method will convert stage coordinates to the clip's internal coordinates.*/ var myPoint:Object = {x:info_mc._x, y:info_mc._y}; info_mc.globalToLocal(myPoint); /* Please note that this will fail if this Player is embedded in another Flash movie. If you intend to do that, please ammend the code related to defining myPoint as follows: var myPoint:Object = {x:info_mc._x, y:info_mc._y}; _root.globalToLocal(myPoint); */ //Several times in the Player something is done based on the length //of a clip - the volume slider and the progress bars specifically. //Volume and percent are both based on a 0-100 scale, but your //clips might not be 100 pixels long. //The next two variables allow the Player to gracefully adjust //to any dimensions. var progBarFactor = dBar._width/100; var volTrackFactor = controls_mc.volume_mc.track_mc._width/100; // D - declare Arrays that will contain data from the XML playlist file: var artists:Array; var albums:Array; var titles:Array; var times:Array; var sizes:Array; var urls:Array; /*----------------------------------- Step 3 - define functions: -----------------------------------*/ // A - Primary Functions: //doPlay() is the central "engine" of the Player. It clears any //previous Sound Object, initializes a new one, starts the info display //animations and determines what to do when one song ends function doPlay(url:String):Void { //we always want to start with a fresh Sound object, //so intialize the environment: if (typeof myTunes == "object") { myTunes.stop (); delete myTunes; } displayInit(); stopped = false; trackLoaded=false; /*----------------------------- reset play button to default in case user had paused sound and then selected another track -----------------------------*/ extinguish(play_mc); controls_mc.play_mc.gotoAndStop("lit"); /*----------------------------- that ends the initialization. Now set up to play a new song -----------------------------*/ myTunes = new Sound (this); myTunes.onLoad = Delegate.create(this, doneStreaming); myTunes.loadSound (url, true); myTunes.setVolume (volume); //the setIntervals that display time data streamingID = setInterval (displayStreaming, 100, myTunes); playingID = setInterval (displayPosition, 100, myTunes); crawlerID = setInterval(textCrawl, 200); //display info in composite text field infoTxt.text = titles[ct].nodeValue+ "/"+artists[ct].nodeValue+ "/"+albums[ct].nodeValue; //Tell the Player what to do when the current track ends: //If the play mode is "shuffle", set the current track (ct) to a //random number between zero and the maximum (mt). Otherwise just //invoke nextTrack(). myTunes.onSoundComplete = function () { if (shuffle) { ct = random(mt+1); doPlay(urls[ct].nodeValue); } else { nextTrack (); } }; } //the callback function when XML is loaded. This populates the arrays //and concludes by making the initial call to doPlay(). Note that the //XML file isn't actually loaded until the next and final frame. //Pay attention to the XPathDocument syntax. It is interesting that when //XPath selects an attribute it stores the entire string including the quotes. //To get just the value without the quotes we have to tack ".nodeValue" to //every reference to the array content function songListLoaded(success:Boolean):Void { if(success){ //fill arrays using XPath artists = XPath.selectNodes(plXML, "/songs/song/@artist"); titles = XPath.selectNodes(plXML, "/songs/song/@title"); albums = XPath.selectNodes(plXML, "/songs/song/@album"); times = XPath.selectNodes(plXML, "/songs/song/@playtime"); sizes = XPath.selectNodes(plXML, "/songs/song/@filesize"); urls = XPath.selectNodes(plXML, "/songs/song/@url"); //define maximum track variable //subtract one to conform to zero-based Array countng mt = titles.length-1; //check shuffle variable if (shuffle){ ct = random(titles.length); doPlay(urls[ct].nodeValue); } else { ct = 0; doPlay(urls[ct].nodeValue); controls_mc.play_mc.gotoAndStop("unlit"); controls_mc.stop_mc.gotoAndStop("lit"); stopTrack(); } } } // B - Secondary Utility Functions: //the function that animates the dBar clip, indicating download //progress. Called by a setInterval in doPlay() function. function displayStreaming (theSource:Object) { //check to see if song has downloaded completely. if (trackLoaded) { clearInterval (streamingID); output_txt.text="loaded"; dBar._x = dBar._width; } else { //what to do while the song is still downloading output_txt.text="streaming"; var percentLoaded:Number = Math.floor((theSource.getBytesLoaded()/theSource.getBytesTotal())*100); dBar._x = percentLoaded*progBarFactor; } } //the function that terminates the displayStreaming interval when tune has completely loaded function doneStreaming():Void { //modify variable to announce that the track has loaded trackLoaded = true; } //the function that animates the pBar clip, indicating amount of //song that has been played. Called by a setInterval in doPlay() function function displayPosition (theSource:Object) { var trackTime:Number = times[ct].nodeValue; if (theSource.position > 0) { //what to do when the playhead gets to the end of the song if (theSource.position == theSource.duration && theSource.getBytesLoaded () == theSource.getBytesTotal ()) { clearInterval (playingID); pBar._x = pBar._width; } else { //what to do while the song is still playing var playProgress:Number = Math.floor((theSource.position/trackTime)*100); pBar._x = playProgress*progBarFactor; } } } //move info text field 5px to the left every tick of interval //smaller number slows this; larger number speeds it up. //Called by a setInterval in doPlay() function function textCrawl():Void { if(infoTxt.textWidth > infoW) { infoTxt._x -= 5; //redraw screen updateAfterEvent(); /*-------------------- check text field position; if it has moved its entire length, jump back to beginning. Note we use the myPoint Object to use coordinates relative to info_mc, NOT the Stage ----------------------*/ if(infoTxt._x < (myPoint.x - infoTxt.textWidth)) { infoTxt._x = myPoint.x + infoW; } } } //if a track is paused instead of stopped, store its position //so it can resume playing where it had been. Position is in //milliseconds, so divide by 1000 to get the offset value function pauseTrack():Void { stopped = false; pauseTime = myTunes.position/1000; myTunes.stop(); clearInterval(crawlerID); clearInterval(playingID); } function stopTrack():Void { myTunes.stop(); stopped = false; displayInit(); } //nextTrack() checks to see if the current track is the last track //in the array and if so resets it to the first in the list. Otherwise //it increments the current track variable by one. function nextTrack():Void { if (shuffle) { ct = random(titles.length); doPlay(urls[ct].nodeValue); } else { if (ct == mt) { ct = 0; doPlay(urls[ct].nodeValue); } else { ct++; doPlay(urls[ct].nodeValue); } } } //prevTrack() checks to see if the current track is the first track //in the array and if so resets it to the last in the list. Otherwise //it decrements the current track variable by one. function prevTrack():Void { if (shuffle) { ct = random(titles.length); doPlay(urls[ct].nodeValue); } else { if (ct == 0) { ct = mt; doPlay(urls[ct].nodeValue); } else { ct--; doPlay(urls[ct].nodeValue); } } } //playTrack() checks the state of the movie. If it is stopped, it //calls doPlay on the current track, starting it from the beginning. //If the movie is paused (if the value of the "stopped" variable is //false), it starts the track using pauseTime as the offset value. //It also resumes the setIntervals that animate the displays. function playTrack():Void { if (stopped) { doPlay(urls[ct].nodeValue); } else { clearInterval (streamingID); clearInterval (playingID); clearInterval (crawlerID); myTunes.start(pauseTime); playingID = setInterval (displayPosition, 100, myTunes); crawlerID = setInterval(textCrawl, 200); //if track hasn't finished downloading restart the displayStreaming function if(!trackLoaded){ streamingID = setInterval (displayStreaming, 100, myTunes); } } } //When the Sound Object is stopped, this function initializes all //the display elements (infoTxt, pBar and dBar) and terminates the //setIntervals. This function is also invoked as part of the //initialization routine that doPlay() begins with. function displayInit():Void { clearInterval (streamingID); clearInterval (playingID); clearInterval (crawlerID); //use myPoint to set position relative //to info_mc, NOT the Stage infoTxt._x = myPoint.x+4; pBar._x = 0; dBar._x = 0; } //extinguish() is a quick, easy way to turn "off" all the //button clips in the control_mc. Why? When you click "stop", //you expect the "play" button to turn off. When you click "play" //again, you expect "stop" to turn off. This makes sure ALL buttons //are off. The stop, pause and play buttons are each individually //responsible for setting themselves to the "on" state. function extinguish(src:MovieClip) { var context:MovieClip = src._parent; for (var item:String in context) { if (typeof context[item] == "movieclip") { context[item].gotoAndStop("unlit"); } } } /*----------------------------------- Step 4 - assign functionality to buttons -----------------------------------*/ //the slider on the volume control, used to modify "volume" variable marker.onPress = function ():Void { this.startDrag(false, 0, this._y, controls_mc.volume_mc.track_mc._width, this._y); this.onMouseMove = function():Void { volume = Math.floor(this._x/volTrackFactor); myTunes.setVolume(volume); updateAfterEvent(); } } marker.onRelease = marker.onReleaseOutside = function():Void { this.stopDrag(); delete this.onMouseMove; } //The scrubber. Well okay it's not REALLY a scrubber because a //real scrubber makes sound as you drag it. This is more of a //"nonlinear rewind/fast forward", but it's still cool. When you press //the pBar clip it stops the Sound Object and lets you drag it as far as //the dBar limit if the track hasn't completely downloaded. When you //release the pBar clip the pauseTime variable is updated and the song //continues from that point. pBar.onPress = function():Void { myTunes.stop(); extinguish(controls_mc.play_mc); clearInterval(playingID); clearInterval(streamingID); clearInterval(crawlerID); this.startDrag(false, 0, this._y, dBar._x, this._y); this.onMouseMove = function():Void { pauseTime = Math.round(((pBar._x/pBar._width)*times[ct].nodeValue)/1000); updateAfterEvent(); } } pBar.onRelease = pBar.onReleaseOutside = function():Void { this.stopDrag(); delete this.onMouseMove; controls_mc.play_mc.gotoAndStop("lit"); playTrack(); } //the next series of functions should be pretty easy to follow - //when clicked, the buttons call previously declared functions controls_mc.prev_mc.onRelease = function () { extinguish(this); prevTrack(); } controls_mc.prev_mc.onRollOver = function() { this.gotoAndStop("lit"); } controls_mc.prev_mc.onRollOut = function() { this.gotoAndStop("unlit"); } controls_mc.stop_mc.onRelease = function () { extinguish(this); this.gotoAndStop("lit"); stopTrack(); } controls_mc.pause_mc.onRelease = function () { extinguish(this); this.gotoAndStop("lit"); pauseTrack(); } controls_mc.play_mc.onRelease = function () { extinguish(this); this.gotoAndStop("lit"); playTrack(); } controls_mc.next_mc.onRelease = function () { extinguish(this); nextTrack(); } controls_mc.next_mc.onRollOver = function() { this.gotoAndStop("lit"); } controls_mc.next_mc.onRollOut = function() { this.gotoAndStop("unlit"); } //shuffle_mc invokes a switch statement which evaluates the current //frame of the clip. If it's frame one ("unlit"), we assume //the user wants to switch to shuffle mode and vice versa shuffle_mc.onRelease = function():Void { switch(this._currentframe) { case 1: shuffle = true; this.play(); break; case 2: shuffle = false; this.play(); break; } } /*----------------------------------- Step 5 - define the XPathDocument Object -----------------------------------*/ plXML = new XML(); plXML.ignoreWhite = true; plXML.onLoad = Delegate.create(this, songListLoaded);