音乐播放器
最近在做一个音乐播放器,纯粹练手,前端使用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测试数据ponents.Button;
21 import spark测试数据ponents.TextArea;
22 import spark测试数据ponents.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测试数据/mxml/2009"
3 xmlns:s ="library://ns.adobe测试数据/flex/spark"
4 xmlns:mx ="library://ns.adobe测试数据/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测试数据ponents.TextArea, required=true
28 name=stateButton, type=spark测试数据ponents.Button, required=true
29 name=nextButton, type=spark测试数据ponents.Button, required=true
30 name=preButton, type=spark测试数据ponents.Button, required=true
31 name=stopButton, type=spark测试数据ponents.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测试数据/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://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息