package { import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.events.TimerEvent; import flash.media.ID3Info; import flash.media.Sound; import flash.media.SoundChannel; import flash.media.SoundLoaderContext; import flash.media.SoundMixer; import flash.net.URLRequest; import flash.utils.Timer; /** * Provides a slightly simpler interface to the sound-related classes in the * flash.media package. Dispatches "playProgress" ProgressEvents and adds * pause and resume functionality. */ public class actionscript_book_PodcastPlayer_com_example_programmingas3_podcastplayer_SoundFacade extends EventDispatcher { /** * The Sound object used to load the sound file. */ public var s:Sound; /** * The SoundChannel object used to play and track playback progess * of the sound. */ public var sc:SoundChannel; /** * The URL of the sound file to load. */ public var url:String; /** * The buffer time to use when loading this object's sound file. */ public var bufferTime:int = 1000; /** * Identifies when the sound file has been fully loaded. */ public var isLoaded:Boolean = false; /** * Identifies when the sound file has been fully loaded. */ public var isReadyToPlay:Boolean = false; /** * Identifies when the sound file is being played. */ public var isPlaying:Boolean = false; /** * Specifies that the sound file can be played while it is being loaded. */ public var isStreaming:Boolean = true; /** * Indicates that sound loading should start as soon as this object is created. */ public var autoLoad:Boolean = true; /** * Indicates that sound playing should start as soon as enough sound data has been loaded. * If this is a streaming sound, playback will begin as soon as enough data, as specified * by the bufferTime property, has been loaded. */ public var autoPlay:Boolean = true; /** * The position of the playhead in the sound data when the playback was last paused. */ public var pausePosition:int = 0; /** * Defines how often to dispatch the playback progress event. */ public var progressInterval:int = 1000; /** * Defines the "playProgress" event type. */ public static const PLAY_PROGRESS:String = "playProgress"; /** * The Timer that's used to update the progress display. */ public var playTimer:Timer; /** * Constructor. */ public function actionscript_book_PodcastPlayer_com_example_programmingas3_podcastplayer_SoundFacade(soundUrl:String, autoLoad:Boolean = true, autoPlay:Boolean = true, streaming:Boolean = true, bufferTime:int = -1):void { this.url = soundUrl; // sets boolean values that determine the behavior of this object this.autoLoad = autoLoad; this.autoPlay = autoPlay; this.isStreaming = streaming; // defaults to the global bufferTime value if (bufferTime < 0) { bufferTime = SoundMixer.bufferTime; } // keeps buffer time reasonable, between 0 and 30 seconds this.bufferTime = Math.min(Math.max(0, bufferTime), 30); if (autoLoad) { load(); } } public function load():void { if (this.isPlaying) { this.stop(); this.s.close(); this.pausePosition = 0; } this.isLoaded = false; this.s = new Sound(); this.s.addEventListener(ProgressEvent.PROGRESS, onLoadProgress); this.s.addEventListener(Event.OPEN, onLoadOpen); this.s.addEventListener(Event.COMPLETE, onLoadComplete); this.s.addEventListener(Event.ID3, onID3); this.s.addEventListener(IOErrorEvent.IO_ERROR, onIOError); this.s.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onIOError); var req:URLRequest = new URLRequest(this.url); var context:SoundLoaderContext = new SoundLoaderContext(this.bufferTime, true); this.s.load(req, context); } public function onLoadOpen(event:Event):void { if (this.isStreaming) { this.isReadyToPlay = true; if (autoPlay) { this.play(); } } this.dispatchEvent(event.clone()); } public function onLoadProgress(event:ProgressEvent):void { this.dispatchEvent(event.clone()); } public function onLoadComplete(event:Event):void { this.isReadyToPlay = true; this.isLoaded = true; this.dispatchEvent(event.clone()); // if the sound hasn't started playing yet, start it now if (autoPlay && !isPlaying) { play(); } } public function play(pos:int = 0):void { if (!this.isPlaying) { if (this.isReadyToPlay) { this.sc = this.s.play(pos); this.sc.addEventListener(Event.SOUND_COMPLETE, onPlayComplete); this.isPlaying = true; this.playTimer = new Timer(this.progressInterval); this.playTimer.addEventListener(TimerEvent.TIMER, onPlayTimer); this.playTimer.start(); } else if (this.isStreaming && !this.isLoaded) { // start loading again and play when ready // it appears to resume loading from the spot where it left off...cool this.load(); return; } } } public function stop(pos:int = 0):void { if (this.isPlaying) { this.pausePosition = pos; this.sc.stop(); this.playTimer.stop(); this.isPlaying = false; } if (this.isStreaming && !this.isLoaded) { // stop streaming this.s.close(); this.isReadyToPlay = false; } } public function pause():void { stop(this.sc.position); } public function resume():void { play(this.pausePosition); } public function get isPaused():Boolean { return (this.pausePosition > 0) } public function onPlayComplete(event:Event):void { this.pausePosition = 0; this.playTimer.stop(); this.isPlaying = false; this.dispatchEvent(event.clone()); } public function onID3(event:Event):void { try { var id3:ID3Info = event.target.id3; for (var propName:String in id3) { trace(propName + " = " + id3[propName]); } } catch (err:SecurityError) { trace("Could not retrieve ID3 data."); } } public function get id3():ID3Info { return this.s.id3; } public function onIOError(event:IOErrorEvent):void { trace("actionscript_book_PodcastPlayer_com_example_programmingas3_podcastplayer_SoundFacade.onIOError: " + event.text); this.dispatchEvent(event.clone()); } public function onPlayTimer(event:TimerEvent):void { var estimatedLength:int = Math.ceil(this.s.length / (this.s.bytesLoaded / this.s.bytesTotal)); var progEvent:ProgressEvent = new ProgressEvent(PLAY_PROGRESS, false, false, this.sc.position, estimatedLength); this.dispatchEvent(progEvent); } } }