音乐播放器
最近在做一个音乐播放器,纯粹练手,前端使用FLex,后台使用JAVA,现在慢慢的在实现,主要涉及的技术还在不断学习中:
这里也一点一点记录下来和大家分享哈。
自定义组件 :(左边是一个播放列表,右边是音乐播放控件)
自定义组件要继承 SkinnableComponent 类。主要有两部分组成,一个是组件的功能逻辑,一个是皮肤。
功能逻辑和普通的as写法一样,要用到皮肤就需要遵守皮肤的契约.看看下面的代码:
( 音乐播放控件:PlayerControlBar.as ):
1 package components 2 { 3 import events.PlayEvent; 4 import events.StopEvent; 5 6 import flash.events.Event; 7 import flash.media.Sound; 8 import flash.media.SoundChannel; 9 import flash.net.URLRequest; 10 11 import mx.controls.Alert; 12 import mx.controls.HSlider; 13 import mx.controls.sliderClasses.Slider; 14 import mx.events.SliderEvent; 15 import mx.messaging.AbstractConsumer; 16 17 import service.IPlayController; 18 import service.impl.PlayController; 19 20 import spark.components.Button; 21 import spark.components.TextArea; 22 import spark.components.supportClasses.SkinnableComponent; 23 24 [SkinState( "stop" )] 25 [SkinState( "run" )] 26 /* *播放控制栏组件 */ 27 public class PlayerControlBar extends SkinnableComponent 28 { 29 [SkinPart(required= "true" )] 30 public var lyricText:TextArea; 31 [SkinPart(required= "true" )] 32 public var playSlider:HSlider; 33 [SkinPart(required= "true" )] 34 public var preButton:Button; 35 [SkinPart(required= "true" )] 36 public var stateButton:Button; 37 [SkinPart(required= "true" )] 38 public var nextButton:Button; 39 [SkinPart(required= "true" )] 40 public var stopButton:Button; 41 42 public function PlayerControlBar() 43 { 44 super (); 45 // 添加播放状态更改的监听器 46 this .addEventListener(PlayEvent.PLAY, handleStateButtonClick); 47 this .addEventListener(StopEvent.STOP, handleStopButtonClick); 48 this .addEventListener(SliderEvent.CHANGE, handlePlaySilderChange); 49 } 50 51 /* *是否在播放 */ 52 public var isStart: Boolean = false ; 53 /* *音乐播放控制器 */ 54 private var playController:IPlayController; 55 /* *播放状态改变的处理函数 */ 56 private function handleStateButtonClick(event:PlayEvent): void 57 { 58 if (! isStart) 59 { 60 // 加载音乐并开始播放 61 playController = new PlayController( this ); 62 playController.start( "gole.mp3" ); 63 isStart = true ; 64 // 改变皮肤的状态 65 this .skin.currentState= "stop" ; 66 } 67 else if ( this .skin.currentState == "stop" ) 68 { 69 // 暂停播放音乐 70 playController.pause(); 71 this .skin.currentState= "run" ; 72 } 73 else if ( this .skin.currentState == "run" ) 74 { 75 // 开始音乐播放 76 playController.play(); 77 this .skin.currentState= "stop" ; 78 } 79 } 80 81 private function handleStopButtonClick(e:StopEvent): void 82 { 83 isStart = false ; 84 this .skin.currentState = "run" ; 85 if (playController) 86 playController.stop( true ); 87 } 88 89 // 活动条拉动的触发函数,从指定位置开始播放 90 private function handlePlaySilderChange(e:SliderEvent): void 91 { 92 if (isStart) 93 { 94 (playController as PlayController).clickPlay(); 95 if ( this .skin.currentState == "run" ) 96 this .skin.currentState = "stop" ; 97 } 98 } 99 } 100 }
看到24~25行为自定义组件加了两个SkinState标注,这个是皮肤的状态,
第29~40行为几个组件加了[SkinPart(required="true")]标注,这个是皮肤必须要拥有的控件
皮肤的契约如下图:
利用flex builder的功能可以为自定义组件添加皮肤,它会根据皮肤的契约自动生成提示:
如下:
( 音乐播放控件的皮肤:PlayerControlBarSkin.mxml ):
1 <? xml version="1.0" encoding="utf-8" ?> 2 < s:Skin xmlns:fx ="http://ns.adobe.com/mxml/2009" 3 xmlns:s ="library://ns.adobe.com/flex/spark" 4 xmlns:mx ="library://ns.adobe.com/flex/mx" 5 creationComplete ="this.currentState = 'run'" 6 > 7 <!-- host component --> 8 < fx:Metadata > 9 [HostComponent("components.PlayerControlBar")] 10 </ fx:Metadata > 11 < fx:Script > 12 <![CDATA[ 13 import events.PlayEvent; 14 import events.StopEvent; 15 16 import mx.events.SliderEvent; 17 ]]> 18 </ fx:Script > 19 20 <!-- states --> 21 < s:states > 22 < s:State id ="runState" name ="run" /> 23 < s:State id ="stopState" name ="stop" /> 24 </ s:states > 25 26 <!-- SkinParts 27 name=lyricText, type=spark.components.TextArea, required=true 28 name=stateButton, type=spark.components.Button, required=true 29 name=nextButton, type=spark.components.Button, required=true 30 name=preButton, type=spark.components.Button, required=true 31 name=stopButton, type=spark.components.Button, required=true 32 --> 33 34 < s:Group width ="700" height ="600" > 35 < s:Image source ="asserts/img/background.jpg" alpha =".6" /> 36 37 < s:VGroup width ="100%" height ="100%" horizontalAlign ="center" paddingTop ="20" > 38 < s:Group width ="60%" height ="80%" horizontalCenter ="0" > 39 < s:TextArea id ="lyricText" width ="100%" height ="100%" alpha =".8" borderVisible ="false" > 40 </ s:TextArea > 41 </ s:Group > 42 < s:HGroup width ="55%" verticalAlign ="middle" > 43 < mx:HSlider id ="playSlider" width ="100%" height ="100%" minimum ="0" maximum ="100" 44 change ="dispatchEvent(new SliderEvent(SliderEvent.CHANGE,true))" /> 45 </ s:HGroup > 46 < s:HGroup width ="60%" horizontalAlign ="center" paddingBottom ="10" > 47 < s:Button id ="preButton" skinClass ="skins.PreButtonSkin" /> 48 < s:Button left ="15" id ="stateButton" skinClass.run ="skins.PlayButtonSkin" skinClass.stop ="skins.PauseButtonSkin" click ="dispatchEvent(new PlayEvent(PlayEvent.PLAY))" /> 49 < s:Button left ="15" id ="nextButton" skinClass ="skins.NextButtonSkin" /> 50 < s:Button left ="15" id ="stopButton" skinClass ="skins.StopButtonSkin" click ="dispatchEvent(new StopEvent(StopEvent.STOP))" /> 51 </ s:HGroup > 52 </ s:VGroup > 53 </ s:Group > 54 </ s:Skin >
自定义组件的好处是将逻辑内部封装好,安全也便于维护,通过皮肤改变外观也是很方便的,需要对外的服务只要提供接口就可以了。
在主界面使用自定义组件:
1 < s:HGroup horizontalAlign ="center" verticalAlign ="middle" horizontalCenter ="0" verticalCenter ="0" > 2 < components:MusicList skinClass ="skins.MusicListSkin" listContent ="{musicList}" creationComplete ="initMusicList()" /> 3 < components:PlayerControlBar skinClass ="skins.PlayeControlBarSkin" /> 4 </ s:HGroup >
运行效果(自定义组件PlayerControlBar) :
自定义事件和事件派发:
事件流有三个阶段:捕获阶段--->目标阶段--->冒泡阶段
1.捕获阶段(从根节点到子节点,检测对象是否注册了监听器,是则调用监听函数)
2.目标阶段(调用目标对象本身注册的监听程序)
3.冒泡阶段(从目标节点到根节点,检测对象是否注册了监听器,是则调用监听函数)
注:事件发生后,每个节点可以有2个机会(2选1)响应事件,默认关闭捕获阶段。
从上到下(从根到目标)是捕获阶段,到达了目标后是目标阶段,然后从目标向上返回是冒泡阶段。
这里需要注意的是:如果派发事件的源(调用dispatchEvent方法)没有在一组容器里,那么这组容器里面的控件是监听不到这个派发事件的。
如下,在点击stateButton按钮的时候会派发一个自定义的事件PlayEvent, 然后在自定义组件PlayControlBar中添加
监听并作出相应处理:
< s:Button left ="15" id ="stateButton" skinClass.run ="skins.PlayButtonSkin" skinClass.stop ="skins.PauseButtonSkin" click ="dispatchEvent(new PlayEvent(PlayEvent.PLAY))" />
( 自定义的事件:PlayEvent.as ) :
1 package events 2 { 3 import flash.events.Event; 4 5 import mx.states.OverrideBase; 6 7 public class PlayEvent extends Event 8 { 9 public static const PLAY:String = "play" ; 10 11 public function PlayEvent(type:String = "play" , bubbles: Boolean = true , cancelable: Boolean = false ) 12 { 13 super (type, bubbles, cancelable); 14 } 15 16 override public function clone():Event 17 { 18 return new PlayEvent(); 19 } 20 } 21 }
自定义事件要继承Event类和重写clone方法。构造函数的第二参数表示是否要执行冒泡,如果不冒泡,父容器就捕获不到事件
添加监听:
public function PlayerControlBar() { super (); // 添加播放状态更改的监听器 this .addEventListener(PlayEvent.PLAY, handleStateButtonClick); ……………… }
事件处理函数:
1 private function handleStateButtonClick(event:PlayEvent): void 2 { 3 if (! isStart) 4 { 5 // 加载音乐并开始播放 6 playController = new PlayController( this ); 7 playController.start( "gole.mp3" ); 8 isStart = true ; 9 // 改变皮肤的状态 10 this .skin.currentState= "stop" ; 11 } 12 else if ( this .skin.currentState == "stop" ) 13 { 14 // 暂停播放音乐 15 playController.pause(); 16 this .skin.currentState= "run" ; 17 } 18 else if ( this .skin.currentState == "run" ) 19 { 20 // 开始音乐播放 21 playController.play(); 22 this .skin.currentState= "stop" ; 23 } 24 }
音频播放的处理:
Flex中音频的播放主要是靠Sound和SoundChannel两个类来实现的。
具体的使用Adobe的官方文档讲得非常详细,地址是:
http://help.adobe.com/zh_CN/FlashPlatform/reference/actionscript/3/flash/media/Sound.html
( 控制音乐播放类:PlayController.as ):
1 package service.impl 2 { 3 import components.PlayerControlBar; 4 5 import flash.display.DisplayObject; 6 import flash.events.Event; 7 import flash.events.TimerEvent; 8 import flash.media.Sound; 9 import flash.media.SoundChannel; 10 import flash.net.URLRequest; 11 import flash.utils.Timer; 12 13 import mx.managers.CursorManager; 14 15 import service.IPlayController; 16 17 /* *音乐播放控制类 */ 18 public class PlayController implements IPlayController 19 { 20 private var sound:Sound; 21 private var soundChannel:SoundChannel; 22 private var _pausePosition:int; 23 private var _derectory:String = "music/" 24 /* *实时记录播放进度 */ 25 private var timer:Timer; 26 private var view:PlayerControlBar; 27 28 public function PlayController(view:DisplayObject) 29 { 30 this .view = view as PlayerControlBar; 31 } 32 33 34 /* *音乐播放暂停位置 */ 35 public function get pausePosition():int 36 { 37 return _pausePosition; 38 } 39 40 /* * 41 * @private 42 */ 43 public function set pausePosition(value:int): void 44 { 45 _pausePosition = value; 46 } 47 48 /* *音乐存放的目录 */ 49 public function get derectory():String 50 { 51 return _derectory; 52 } 53 54 public function set derectory(value:String): void 55 { 56 _derectory = value; 57 } 58 59 public function start(music:String): void 60 { 61 sound = new Sound(); 62 timer = new Timer(1000 ); 63 var urlRequest:URLRequest = new URLRequest(derectory + music); 64 sound.addEventListener(Event.COMPLETE, function handleStart(e:Event): void 65 { 66 soundChannel = sound.play(); 67 // 增加音乐播放完毕的监听器 68 soundChannel.addEventListener(Event.SOUND_COMPLETE, handlePlayEnd); 69 timer.start(); 70 sound.removeEventListener(Event.COMPLETE,handleStart); 71 } 72 ); 73 timer.addEventListener(TimerEvent.TIMER, handleTimmerWork); 74 sound.load(urlRequest); 75 } 76 77 /* 音乐播放结束处理函数 */ 78 private function handlePlayEnd(e:Event): void 79 { 80 stop( true ); 81 view.skin.currentState = "run" ; 82 view.isStart = false ; 83 soundChannel.removeEventListener(Event.SOUND_COMPLETE,handlePlayEnd); 84 } 85 86 /* 每隔一秒,刷新进度条 */ 87 private function handleTimmerWork(e:TimerEvent): void 88 { 89 var estimatedLength:int = Math.ceil(sound.length / (sound.bytesLoaded / sound.bytesTotal)); 90 var playbackPercent:uint = Math.round(100 * (soundChannel.position / estimatedLength)); 91 view.playSlider.value = playbackPercent; 92 } 93 94 public function pause(): void 95 { 96 if (soundChannel) 97 { 98 pausePosition = soundChannel.position; 99 stop(); 100 } 101 } 102 103 public function play(): void 104 { 105 if (sound) 106 { 107 soundChannel = sound.play(pausePosition); 108 soundChannel.addEventListener(Event.SOUND_COMPLETE, handlePlayEnd); 109 } 110 if (! timer.running) 111 timer.start(); 112 } 113 114 public function stop(isExit: Boolean = false ): void 115 { 116 if (soundChannel) 117 { 118 soundChannel.stop(); 119 soundChannel.removeEventListener(Event.SOUND_COMPLETE,handlePlayEnd); 120 } 121 if (timer.running) 122 timer.stop(); 123 if (isExit) 124 timer.removeEventListener(TimerEvent.TIMER,handleTimmerWork); 125 } 126 127 /* *由Slider触发的播放 */ 128 public function clickPlay(): void 129 { 130 // 根据拖动的位置计算实际音乐播放的位置 131 var percent: Number = view.playSlider.value / view.playSlider.maximum; 132 var estimatedLength:uint = Math.ceil(sound.length / (sound.bytesLoaded / sound.bytesTotal)); 133 var position:uint = Math.round(percent * estimatedLength); 134 pause(); 135 pausePosition = position; 136 play(); 137 } 138 } 139 }
第59至75行是第一次点击播放时调用的方法,第64行Sound增加了一个事件监听,是在音乐加载完后执行的,
这个如果要边加载边播放的时候不适用,可以参考官方文档来解决这个问题。第94~101行中是点击暂停时调用
的方法,暂停的时候要把音乐播放的位置记录下来,如98行,这是为了要在继续播放的时候找到起点。第103
至112行是继续播放函数。
Timer类的使用实现播放进度实时更新:
当音乐播放的时候,这个播放进度条会每个同步的移动位置,拖动进度条,音乐也会播放到相应的位置。
进度条控件:
< s:HGroup width ="55%" verticalAlign ="middle" > < mx:HSlider id ="playSlider" width ="100%" height ="100%" minimum ="0" maximum ="100" change ="dispatchEvent(new SliderEvent(SliderEvent.CHANGE,true))" /> </ s:HGroup >
当进度条通过拖动或者点击改变值的时候会派发自定义事件SliderEvent,这个在自定义组件 PlayerControlBar中进行监听和处理.
public function PlayerControlBar() { super(); ……………… this .addEventListener(SliderEvent.CHANGE, handlePlaySilderChange); }
处理函数:
// 进度条拉动的触发函数,从指定位置开始播放 private function handlePlaySilderChange(e:SliderEvent): void { if (isStart) { (playController as PlayController).clickPlay(); if ( this .skin.currentState == "run" ) this .skin.currentState = "stop" ; } }
clickplay方法:
1 /* *由Slider触发的播放 */ 2 public function clickPlay(): void 3 { 4 // 根据拖动的位置计算实际音乐播放的位置 5 var percent:Number = view.playSlider.value / view.playSlider.maximum; 6 var estimatedLength:uint = Math.ceil(sound.length / (sound.bytesLoaded / sound.bytesTotal)); 7 var position:uint = Math.round(percent * estimatedLength); 8 pause(); 9 pausePosition = position; 10 play(); 11 }
第5~7行是计算当前进度条拖动的进度对应的音乐播放位置。
实时更新播放进度条:
1 public function start(music:String): void 2 { 3 sound = new Sound(); 4 timer = new Timer(1000 ); 5 var urlRequest:URLRequest = new URLRequest(derectory + music); 6 sound.addEventListener(Event.COMPLETE, function handleStart(e:Event): void 7 { 8 soundChannel = sound.play(); 9 // 增加音乐播放完毕的监听器 10 soundChannel.addEventListener(Event.SOUND_COMPLETE, handlePlayEnd); 11 timer.start(); 12 sound.removeEventListener(Event.COMPLETE,handleStart); 13 } 14 ); 15 timer.addEventListener(TimerEvent.TIMER, handleTimmerWork); 16 sound.load(urlRequest); 17 }
第4行,在点击音乐播放的时候新建一个Timer类,并规定1秒执行一次,第11行是音乐播放的时候开始启动这个timer,第15行
中添加timer触发的事件。处理函数如下:
/* 每隔一秒,刷新进度条 */ private function handleTimmerWork(e:TimerEvent): void { var estimatedLength: int = Math.ceil(sound.length / (sound.bytesLoaded / sound.bytesTotal)); var playbackPercent:uint = Math.round(100 * (soundChannel.position / estimatedLength)); view.playSlider.value = playbackPercent; }
当停止音乐播放的时候也停止timer,开始播放音乐的时候启动timer:
1 public function stop(isExit: Boolean = false ): void 2 { 3 if (soundChannel) 4 { 5 soundChannel.stop(); 6 soundChannel.removeEventListener(Event.SOUND_COMPLETE,handlePlayEnd); 7 } 8 if (timer.running) 9 timer.stop(); 10 if (isExit) 11 timer.removeEventListener(TimerEvent.TIMER,handleTimmerWork); 12 }
1 public function play(): void 2 { 3 if (sound) 4 { 5 soundChannel = sound.play(pausePosition); 6 soundChannel.addEventListener(Event.SOUND_COMPLETE, handlePlayEnd); 7 } 8 if (! timer.running) 9 timer.start(); 10 }
前端(FLEX)和服务器端(JAVA)之间的通信:
这个是通过Socket来实现的.
JAVA端的Socket编程若要和Flex端通信并且传递对象,就需要用到AMF序列化,这个Adobe为我们实现了,
只需要调用接口就可以了。Adobe的这个框架叫做blazeds , 在官网可以下载到,为了方便大家,这里给出了
下载地址: blazeds.zip BlazeDS开发者指南
java服务端:
1 public class MusicServer { 2 private ServerSocket serverSocket; 3 private Socket clientSocket; 4 5 public MusicServer( int port) 6 { 7 try { 8 serverSocket = new ServerSocket(port); 9 clientSocket = serverSocket.accept(); 10 ClientSocketManager.addClient(clientSocket); 11 MusicListService.sendMusicList(clientSocket); 12 } catch (IOException e) { 13 e.printStackTrace(); 14 } 15 } 16 17 18 public static void main(String[] args) { 19 new MusicServer(9000 ); 20 } 21 }
第11行是socket输入,输出流处理的类,主要是把服务端里面的所有音乐的文件名发送给客户端。
ClientSocketManager.java:
1 public class MusicListService { 2 3 public static void sendMusicList(Socket socket) 4 { 5 try { 6 InputStream input = socket.getInputStream(); 7 OutputStream outputStream = socket.getOutputStream(); 8 Amf3Output amfoutput = new Amf3Output( new SerializationContext()); 9 10 while ( true ) 11 { 12 int index = 0 ; 13 byte [] buffer = new byte [100 ]; 14 StringBuffer requestState = new StringBuffer(); 15 while (-1 != (index = input.read(buffer, 0 , buffer.length))) 16 { 17 String value = new String(buffer, 0 , index); 18 requestState.append(value); 19 20 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); 21 DataOutputStream output = new DataOutputStream(byteOutput); 22 amfoutput.setOutputStream(output); 23 MusicList musicList = MusicListGet.getMusicList(); 24 amfoutput.writeObject(musicList); 25 output.flush(); 26 27 byte [] data = byteOutput.toByteArray(); 28 outputStream.write(data); 29 outputStream.flush(); 30 } 31 32 break ; 33 } 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } finally 37 { 38 try { 39 socket.close(); 40 ClientSocketManager.removeClient(socket); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 } 46 }
第8行中导入了 Amf3Output类,这个类就是 blazeds.zip 包下面的,导入到项目中就可以了。
Flex客户端:
1 public class SocketController extends EventDispatcher 2 { 3 private static var socket:Socket = null ; 4 private var view:DisplayObject; 5 6 public function SocketController(host:String = null , port:int = 0, view:DisplayObject = null ) 7 { 8 if (! socket) 9 socket = new Socket(); 10 this .view = view; 11 configureListener(); 12 if (host && port != 0 ) 13 { 14 socket.connect(host,port); 15 } 16 } 17 18 19 private function configureListener(): void 20 { 21 socket.addEventListener(Event.CONNECT, handleConnect); 22 socket.addEventListener(Event.CLOSE, handleClose); 23 socket.addEventListener(ProgressEvent.SOCKET_DATA, handleRecieve); 24 } 25 26 private function handleConnect(e:Event): void 27 { 28 socket.writeUTFBytes(RequestState.REQUESTLIST); 29 socket.flush(); 30 } 31 32 private function handleClose(e:Event): void 33 { 34 if (socket.connected) 35 socket.close(); 36 socket = null ; 37 } 38 39 private function handleRecieve(e:Event): void 40 { 41 var obj: Object = socket.readObject(); 42 if (socket.connected) 43 socket.close(); 44 var musicList:ArrayCollection = obj.musicNames; 45 if (view) 46 view.dispatchEvent( new MusicListEvent(MusicListEvent.LISTRECIEVE,musicList)); 47 } 48 49 }
Flex的socket都是异步方式来实现的,通过事件来处理,可以看到第21~23行为socket添加了几个事件监听,
第一个是建立连接成功的事件监听,第二个是连接关闭的监听,第三个是得到服务端返回消息的监听。
程序还在不断的完善,本人也在学习当中,如果有兴趣的朋友,可以告诉我好的学习资料,或有什么好的建议,也希望告诉我哦.
下面是程序的源码地址:
audioplayer.rar
一颗平常心,踏踏实实,平静对待一切
分类: flex&&ActionScript
标签: socket通信 , 事件流 , Timer , Slider , 自定义组件 , 音频
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息