package { import dw.IDisposable; import dw.cm.content.IXmlConfigurable; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.ProgressEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundTransform; import flash.net.URLRequest; //CORE MP3 PLAYER CLASS, THIS IS THE ACTUAL ENGINE THAT PLAYS THE SOUND //ON TOP OF THIS THERE'S AN MP3PLAYERFRONTEND CLASS WITCH DEALS WITH THE UI FOR THIS HEADLESS MP3PLAYER //COMPLETLY DECOUPLED FROM THE UI CLASS public class Mp3Player extends EventDispatcher implements IXmlConfigurable, IDisposable { public static const PLAYSTATE_PAUSED:int = 0; public static const PLAYSTATE_NOTPLAYING:int = 1; public static const PLAYSTATE_PLAYING:int = 2; private var _xmlData:XML; private var _channel:SoundChannel = new SoundChannel(); private var _sound:Sound = new Sound(); private var _position:int; private var _currentTrack:int = 0; private var _currentPosition:int = 0; private var _volume:Number; private var _tracksStarted:int = 0; private var _repeatTrack:Boolean = false; private var _loading:Boolean = false; private var _playState:int = PLAYSTATE_NOTPLAYING; private var _playlist:Playlist; public function Mp3Player() { } public function set xmlData(v:XML):void { _xmlData = v; for(var i:int = 0; i<_xmlData.mp3.length(); i++) { _xmlData.mp3[i].@uid = i; } dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.SETTINGS_RECEIEVED,false,false,0,0)); } public function get xmlData():XML { return _xmlData; } public function playTrack(trackUid:int):void { dispatchEvent( new Mp3PlayerEvent(Mp3PlayerEvent.NEW_TRACK_STARTED,false,false,_currentTrack,trackUid)); _currentTrack = trackUid; internalPlayTrack(_xmlData.mp3.(@uid == _currentTrack)[0]); } //PLAYST A TRACK DESCRIBED BY THE XML private function internalPlayTrack(track:XML):void { if(_loading) { _sound.close(); _sound.removeEventListener(ProgressEvent.PROGRESS, evthndlLoadingTrack); _loading = false; } _currentPosition = 0; _channel.stop(); _sound = new Sound(new URLRequest(track.@url)); _sound.addEventListener(ProgressEvent.PROGRESS, evthndlLoadingTrack); internalStartPlaying(); } // HANDLES NEW LOADING OF TRACK PROGRESS, EACH TIME A PACKET ARRIVES, NEW EXTENDED INFO MUST BE SAVED, UI MONITORS EXTENDED_TRACK_INFO AND UPDATES ITSELF private function evthndlLoadingTrack(e:ProgressEvent):void { if(e.bytesLoaded == e.bytesTotal) { (e.target as Sound).removeEventListener(ProgressEvent.PROGRESS, evthndlLoadingTrack); } var loadPercent:Number = e.bytesLoaded/e.bytesTotal; _xmlData.mp3[_currentTrack].@length = _sound.length / loadPercent; _xmlData.mp3[_currentTrack].@percentLoaded = loadPercent; dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.EXTENDED_TRACK_INFO,false,false,_currentTrack,_currentTrack)); } public function pause():void { if(_playState == PLAYSTATE_PLAYING) { _playState = PLAYSTATE_PAUSED; _currentPosition = _channel.position; _channel.stop(); dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.PAUSED,false,false,_currentTrack)); } } public function set volume(v:Number):void { if(_volume != v) { _volume = v; _channel.soundTransform = new SoundTransform(_volume); dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.VOLUME_CHANGE,false,false,_currentTrack,_currentTrack)); } } public function get volume():Number { return _volume; } public function resume():void { if(_playState == PLAYSTATE_PAUSED) { internalStartPlaying(_currentPosition); } else if(_playState == PLAYSTATE_NOTPLAYING){ playTrack(_currentTrack); } } //THE PLAYER INSTANCE IS MANAGED BY THE PLAYLIST, THE PLAYER ITSELF CANNOT DECIDE WHICH IS THE PREV/NEXT TRACK, BUT ANNOUNCES THE PLAYLIST THAT THE PREV/NEXT TRACK MUST BE STARTED. PLAYLIST CALLS THE PLAYER BACK GIVING IT THE TRACK(XML) TO PLAY public function prev():void { dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.REQUEST_PREV_TRACK,false,false,_currentTrack)); /* _currentTrack = (_currentTrack-1)%_xmlData.mp3.length(); if(_currentTrack < 0) _currentTrack = _xmlData.mp3.length()-1; playTrack(_currentTrack);*/ } //THE PLAYER INSTANCE IS MANAGED BY THE PLAYLIST, THE PLAYER ITSELF CANNOT DECIDE WHICH IS THE PREV/NEXT TRACK, BUT ANNOUNCES THE PLAYLIST THAT THE PREV/NEXT TRACK MUST BE STARTED. PLAYLIST CALLS THE PLAYER BACK GIVING IT THE TRACK(XML) TO PLAY public function next():void { dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.REQUEST_NEXT_TRACK,false,false,_currentTrack)); /* _currentTrack = (_currentTrack+1)%_xmlData.mp3.length(); if(_currentTrack >_xmlData.mp3.length()-1 ) _currentTrack = 0; playTrack(_currentTrack);*/ } public function get trackPosition():Number { if(_channel == null) return 0; return _channel.position; } //LETS THE LISTENERS KNOW THAT THE PLAYER WILL PLAY THE SAME TRACK OVER AND OVER AGAIN, UNTIL REPEAT TRACK IS SET TO FALSE public function set repeatTrack(v:Boolean):void { if(v == _repeatTrack) return; _repeatTrack = v; dispatchEvent(new Mp3PlayerEvent(_repeatTrack?Mp3PlayerEvent.REPEAT_TRACK_ON:Mp3PlayerEvent.REPEAT_TRACK_OFF,false,false,_currentTrack,_currentTrack)); } public function get repeatTrack():Boolean { return _repeatTrack; } //THIS IS THE STARTING POINT FOR THE ACTUAL PLAYING, CALLED FROM DIFFERENT PLACES LIKE USER CLICK ACTION ON THE PLAY BUTTON, ROUTED BY THE MP3PLAYERFRONTEND, OR NEXT TRACK, PLAYLISTFRONTED, SCRUBBING THE PROGRESS BAR, ETC. private function internalStartPlaying(position:Number = 0):void { if(_channel != null) { _channel.stop(); _channel.removeEventListener(Event.SOUND_COMPLETE, evthndlTrackCompleted); } _channel = _sound.play(position); _channel.soundTransform = new SoundTransform(_volume); _playState = PLAYSTATE_PLAYING; _channel.addEventListener(Event.SOUND_COMPLETE, evthndlTrackCompleted); volume = _volume; _tracksStarted++; if(position != 0) dispatchEvent(new Mp3PlayerEvent(Mp3PlayerEvent.RESUMED,false,false,_currentTrack)); } private function evthndlTrackCompleted(e:Event):void { if(_repeatTrack) { playTrack(_currentTrack); } else { next(); } } //EXPOSES THE ACTUAL PLAYHEAD POSITION IN THE TRACK public function get tracksStartedCounter():int { return _tracksStarted; } public function set trackPosition(v:Number):void { if(_sound != null) { _currentPosition = v<0?0:(v>_sound.length?_sound.length:v); internalStartPlaying(_currentPosition); } } public function get currentTrack():int { return _currentTrack; } public function get isPaused():Boolean { return _playState != PLAYSTATE_PLAYING; } public function dispose():void { if(_sound != null) _sound.removeEventListener(ProgressEvent.PROGRESS, evthndlLoadingTrack); if(_channel != null) _channel.stop(); } } }