最近在做一个音乐播放器,纯粹练手,前端使用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添加了几个事件监听,
第一个是建立连接成功的事件监听,第二个是连接关闭的监听,第三个是得到服务端返回消息的监听。
程序还在不断的完善,本人也在学习当中,如果有兴趣的朋友,可以告诉我好的学习资料,或有什么好的建议,也希望告诉我哦.