flex + red5实现视频会议
公司最近要在系统中加视频会议的功能,让我探索,我选择了最流行的red5来实现,网上有一对一聊天的demo,找不到多对多聊天的,也没有具体介绍系统搭建的过程,我通过自己的摸索,将实现的过程和大家一起分享。java的web项目添加flex支持在此不再详述,项目文件结构如图:
web.xml文件:
- <?xml version=”1.0″ encoding=”UTF-8″?>
- <web-app
- version=”2.4″
- xmlns=”http://java.sun.com/xml/ns/j2ee”
- xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd”
- xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>
- <!–
- ** For use with servlet v2.5 replace the lines above with these
- version=”2.5″
- xmlns=”http://java.sun.com/xml/ns/javaee”
- xsi:schemaLocation=”http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”
- xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
- –>
- <display-name>Red5ChartRoom</display-name>
- <context-param>
- <param-name>globalScope</param-name>
- <param-value>default</param-value>
- </context-param>
- <context-param>
- <param-name>parentContextKey</param-name>
- <param-value>default.context</param-value>
- </context-param>
- <context-param>
- <param-name>webAppRootKey</param-name>
- <param-value>@webapp.root.key@</param-value>
- </context-param>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>WEB-INF/classes/*-web.xml</param-value>
- </context-param>
- <listener>
- <listener-class>org.red5.server.war.WarLoaderServlet</listener-class>
- </listener>
- <listener>
- <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
- </listener>
- <servlet>
- <servlet-name>gateway</servlet-name>
- <servlet-class>org.red5.server.net.servlet.AMFGatewayServlet</servlet-class>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet>
- <servlet-name>rtmpt</servlet-name>
- <servlet-class>org.red5.server.net.rtmpt.RTMPTServlet</servlet-class>
- <load-on-startup>2</load-on-startup>
- </servlet>
- <!– MessageBroker Servlet –>
- <servlet>
- <display-name>MessageBrokerServlet</display-name>
- <servlet-name>MessageBrokerServlet</servlet-name>
- <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
- <init-param>
- <param-name>services.configuration.file</param-name>
- <param-value>/WEB-INF/flex/services-config.xml</param-value>
- </init-param>
- <load-on-startup>11</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>gateway</servlet-name>
- <url-pattern>/gateway</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>rtmpt</servlet-name>
- <url-pattern>/fcs/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>rtmpt</servlet-name>
- <url-pattern>/open/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>rtmpt</servlet-name>
- <url-pattern>/idle/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>rtmpt</servlet-name>
- <url-pattern>/send/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>rtmpt</servlet-name>
- <url-pattern>/close/*</url-pattern>
- </servlet-mapping>
- <servlet-mapping>
- <servlet-name>MessageBrokerServlet</servlet-name>
- <url-pattern>/messagebroker/*</url-pattern>
- </servlet-mapping>
- <welcome-file-list>
- <welcome-file>login.html</welcome-file>
- <welcome-file>index.html</welcome-file>
- <welcome-file>index.htm</welcome-file>
- </welcome-file-list>
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Forbidden</web-resource-name>
- <url-pattern>/WEB-INF/*</url-pattern>
- </web-resource-collection>
- <auth-constraint />
- </security-constraint>
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Forbidden</web-resource-name>
- <url-pattern>/persistence/*</url-pattern>
- </web-resource-collection>
- <auth-constraint />
- </security-constraint>
- <security-constraint>
- <web-resource-collection>
- <web-resource-name>Forbidden</web-resource-name>
- <url-pattern>/streams/*</url-pattern>
- </web-resource-collection>
- <auth-constraint />
- </security-constraint>
- </web-app>
red5ChartRoom-web.xml
- <?xml version=”1.0″ encoding=”UTF-8″?>
- <beans xmlns=”http://www.springframework.org/schema/beans”
- xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
- xmlns:lang=”http://www.springframework.org/schema/lang”
- xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd”>
- <bean id=”web.context.chatroom” class=”org.red5.server.Context”>
- <property name=”scopeResolver” ref=”red5.scopeResolver”></property>
- <property name=”clientRegistry” ref=”global.clientRegistry”/>
- <property name=”serviceInvoker” ref=”global.serviceInvoker”/>
- <property name=”mappingStrategy” ref=”global.mappingStrategy”/>
- </bean>
- <bean id=”web.scope” class=”org.red5.server.WebScope” init-method=”register”>
- <property name=”server” ref=”red5.server”/>
- <property name=”parent” ref=”global.scope”/>
- <property name=”context” ref=”web.context.chatroom”/>
- <property name=”handler” ref=”web.handler.chatroom”/>
- <property name=”contextPath” value=”/Red5ChatRoom”/>
- <property name=”virtualHosts” value=”*,localhost,localhost:8080,127.0.0.1:8080″/>
- </bean>
- <bean id=”web.handler.chatroom” class=”com.chinahrt.chat.VedioChatApplication”/>
- </beans>
red5.properties
- # Socket policy
- policy.host=0.0.0.0
- policy.port=843
- # HTTP
- http.host=0.0.0.0
- http.port=5080
- https.port=8443
- # RTMP
- rtmp.host=0.0.0.0
- rtmp.port=1935
- rtmp.io_threads=16
- rtmp.connect_threads=4
- rtmp.send_buffer_size=271360
- rtmp.receive_buffer_size=65536
- rtmp.ping_interval=1000
- rtmp.max_inactivity=60000
- rtmp.tcp_nodelay=true
- # RTMPS
- rtmps.host=0.0.0.0
- rtmps.port=8443
- rtmps.ping_interval=5000
- rtmps.max_inactivity=60000
- rtmps.max_keep_alive_requests=-1
- rtmps.max_threads=20
- rtmps.acceptor_thread_count=2
- rtmps.processor_cache=20
- # RTMPS Keystore Password
- rtmps.keystorepass=password
- # RTMPT
- rtmpt.host=0.0.0.0
- rtmpt.port=8088
- rtmpt.ping_interval=5000
- rtmpt.max_inactivity=60000
- rtmpt.max_keep_alive_requests=-1
- rtmpt.max_threads=20
- rtmpt.acceptor_thread_count=2
- rtmpt.processor_cache=20
- # MRTMP
- mrtmp.host=0.0.0.0
- mrtmp.server=localhost
- mrtmp.port=9035
- mrtmp.event_threads_core=4
- mrtmp.event_threads_max=32
- # event threads queue: -1 unbounded, 0 direct (no queue), n bounded queue
- mrtmp.event_threads_queue=0
- mrtmp.event_threads_keepalive=60
- mrtmp.send_buffer_size=271360
- mrtmp.receive_buffer_size=65536
- mrtmp.ping_interval=5000
- mrtmp.max_inactivity=60000
- mrtmp.tcp_nodelay=true
- # Debug proxy (needs to be activated in red5-core.xml)
- proxy.source_host=127.0.0.1
- proxy.source_port=1936
- proxy.destination_host=127.0.0.1
- proxy.destination_port=1935
- # JMX
- jmx.rmi.port.registry=9999
- jmx.rmi.port.remoteobjects=
- jmx.rmi.host=127.0.0.1
- jmx.rmi.ssl=false
- red5.config_root=red5.config_root
- red5.root=E\:apache-tomcat-6.0.33
java代码
- package com.chinahrt.chat;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import org.python.antlr.PythonParser.return_stmt_return;
- import org.red5.server.adapter.ApplicationAdapter;
- import org.red5.server.api.IConnection;
- import org.red5.server.api.IScope;
- import org.red5.server.api.Red5;
- import org.red5.server.api.service.IServiceCapableConnection;
- import org.red5.server.api.so.ISharedObject;
- import org.red5.server.api.stream.IBroadcastStream;
- /**
- * createBy ZYN
- *
- * createTime 2011-9-16 下午03:33:15
- *
- * desc 视频聊天服务器
- *
- */
- public class VedioChatApplication extends ApplicationAdapter {
- private IScope appScope;
- private String userName;
- //共享存贮在线用户
- private ISharedObject listSO;
- private Map<String,IConnection> onlineList = new HashMap<String,IConnection> ();//在线用户表
- //程序运行
- //程序运行时志向
- public boolean appStart(IScope app) {
- if (!super.appStart(app)) {
- return false;
- }
- appScope = app;
- return true;
- }
- @Override
- public boolean appConnect(IConnection arg0, Object[] arg1) {
- /**
- * 用户首次连接server 时触发,检查用户是否重复登录,将用户添加到在线用户表中
- */
- String userId=arg0.getClient().getId();
- if(!super.appConnect(arg0, arg1)){
- return false;
- }
- if (arg1 != null ) {
- userName = (String) arg1[0];
- }
- if(onlineList.get(userName) != null){
- rejectClient(“请不要重复登录”);
- return false;
- }
- onlineList.put(userName, arg0);
- listSO = getSharedObject(appScope, ”listSO”, false);
- listSO.setAttribute(userId, userName);
- System.out.println(“The user:”+userName+”,”+userName+” logined successfully”);
- return true;
- }
- /**
- * 通知所有人当前用户登录
- * @param params
- */
- public void getOnloadUser(Object[] params) {
- String clientName = params[0].toString();
- if(null == clientName || ”".equals(clientName)) {
- return ;
- }
- //给所有客户端数据
- IScope scope = Red5.getConnectionLocal().getScope();
- Iterator it = scope.getConnections().iterator();
- for (;it.hasNext();) {
- Set connections = (Set)it.next();
- IConnection tempConn = (IConnection)connections.iterator().next();
- if (tempConn instanceof IServiceCapableConnection) {
- IServiceCapableConnection sc = (IServiceCapableConnection) tempConn;
- sc.invoke(“result_getOnloadUser”, new Object[]{clientName});
- }
- }
- }
- //聊天
- public void sayToAll(Object[] params) {
- IConnection conn = Red5.getConnectionLocal();
- String user_id = conn.getClient().getId();
- String clientName =(String) listSO.getAttribute(user_id);
- System.out.println(“************发言者是:”+clientName);
- String sayToName=params[0]==null?”":params[0].toString().trim();
- String sayWhat=params[1]==null?”":params[1].toString().trim();
- if(“”.equals(sayToName)||”All”.equals(sayToName))// 发消息给聊天室的所有人.
- {
- IScope scope = Red5.getConnectionLocal().getScope();
- Iterator it = scope.getConnections().iterator();
- for (;it.hasNext();) {
- Set connections = (Set)it.next();
- IConnection tempConn = (IConnection)connections.iterator().next();
- if (tempConn instanceof IServiceCapableConnection) {
- IServiceCapableConnection sc = (IServiceCapableConnection) tempConn;
- // 调用客户端showMessage方法。
- sc.invoke(“showMessage”, new Object[]{clientName+” to All:”+sayWhat});
- }
- }
- }else{
- IConnection tempConn=onlineList.get(sayToName);
- if (tempConn instanceof IServiceCapableConnection) {
- IServiceCapableConnection sc = (IServiceCapableConnection) tempConn;
- sc.invoke(“showMessage”, new Object[]{clientName+” to ”+sayToName+”:”+sayWhat});
- }
- IServiceCapableConnection sc = (IServiceCapableConnection) conn;
- sc.invoke(“showMessage”, new Object[]{clientName+” to ”+sayToName+”:”+sayWhat});
- }
- }
- // 用户断开连接的时候触发
- public void appDisconnect(IConnection conn) {
- String dis_user_id = conn.getClient().getId();
- String user = (String) listSO.getAttribute(dis_user_id);
- // 根据ID删除对应在线纪录
- onlineList.remove(user);
- // 删除用户列表共享对象的对应属性
- listSO.removeAttribute(dis_user_id);
- IScope scope = Red5.getConnectionLocal().getScope();
- Iterator it = scope.getConnections().iterator();
- for (;it.hasNext();) {
- Set connections = (Set)it.next();
- IConnection tempConn = (IConnection)connections.iterator().next();
- if (tempConn instanceof IServiceCapableConnection) {
- IServiceCapableConnection sc = (IServiceCapableConnection) tempConn;
- // 服务器端调用客户端flash方法。
- sc.invoke(“disconnectMessage”, new Object[]{user});
- }
- }
- }
- }
flex端代码
- <?xml version=”1.0″ encoding=”utf-8″?>
- <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”absolute”>
- <mx:Script>
- <![CDATA[
- import mx.collections.ArrayCollection;
- import mx.containers.HBox;
- import mx.controls.Alert;
- private var listSO:SharedObject;
- private var userArr:Array;
- private var conn:NetConnection;
- private var localUsername:String;
- [Bindable]
- private var cam:Camera;
- [Bindable]
- private var mic:Microphone;
- [Bindable]
- public var cards:ArrayCollection;
- public var videoUsers:Array;
- [Bindable]
- public var videoControlArr:Array;
- private var stm:NetStream;
- [Bindable]
- private var video_self:Video;
- protected function login(event:MouseEvent):void
- {
- localUsername = txt_name.text;
- if(localUsername== ”"){
- Alert.show(“用户名不能为空”);
- }else{
- if(conn == null){
- conn = new NetConnection();
- conn.client = this;
- conn.addEventListener(NetStatusEvent.NET_STATUS,_statusHandler);
- conn.connect(“rtmp://192.168.1.61/Red5ChatRoom”,localUsername);
- }
- }
- }
- //状态监听
- private function _statusHandler(evt:NetStatusEvent):void
- {
- if(evt.info.code == ”NetConnection.Connect.Success”){
- this.currentState = ”chat”;
- Alert.show(“连接成功”);
- video_clickHandler();
- this.showJoinInInfo(localUsername);
- _setListSO();
- }
- if(evt.info.code == ”NetConnection.Connect.Failed”){
- Alert.show(“连接失败”);
- }
- if(evt.info.code == ”NetConnection.Connect.Closed”){
- Alert.show(“连接关闭”);
- }
- }
- public function showJoinInInfo(message:String):void
- {
- conn.call(“getOnloadUser”,null,message);
- }
- public function result_getOnloadUser(str:String):void{
- txt_chatmsg.text += str + ”加入聊天室” + ”\n”;
- }
- //创建用户列表共享对象
- private function _setListSO():void
- {
- listSO = SharedObject.getRemote(“listSO”,conn.uri,false);
- listSO.connect(conn);
- listSO.addEventListener(SyncEvent.SYNC,_listSOSyncHandler);
- }
- //用户列表共享对象被更新之后的事件
- private function _listSOSyncHandler(evt:SyncEvent):void{
- _showUserList();//更新用户列表
- }
- private function _showUserList():void
- {
- cards = new ArrayCollection(
- [{label:"All"}]
- );
- userArr = new Array();
- //用户数组更新
- for(var tmp:String in listSO.data){
- userArr.push(listSO.data[tmp]);
- }
- //添加到arrayCollection
- for(var i:int = 0; i<userArr.length;i++){
- cards.addItem({label:userArr[i]});
- }
- //将数组添加到列表数组中显示出来
- userList.dataProvider = cards;
- users.dataProvider = cards;
- addVideo(cards);
- }
- public function showMessage(message:String):void
- {
- txt_chatmsg.text += message + ”\n”;
- }
- protected function sendMessage(event:MouseEvent):void
- {
- var sendString:String = txt_yousay.text;
- var sendTo:String = userList.selectedItem.label;
- txt_yousay.text = ”";
- conn.call(“sayToAll”,null,sendTo,sendString);
- }
- //断线通知
- public function disconnectMessage(disUser:String):void
- {
- txt_chatmsg.text += disUser+”退出聊天室\n”;
- }
- //进入视频会议
- public function video_clickHandler():void
- {
- stm = new NetStream(conn);
- cam = Camera.getCamera();
- if(cam==null){
- Alert.show(“没有可以使用的摄像头”);
- return;
- }else{
- Security.showSettings(SecurityPanel.PRIVACY);
- cam.addEventListener(StatusEvent.STATUS,statusHandler);
- cam.addEventListener(ActivityEvent.ACTIVITY,activityHandler);
- cam.setLoopback(true);
- cam.setMotionLevel(50,100);
- cam.setMode(1280,960,15,true);
- stm.attachCamera(cam);
- }
- mic = Microphone.getMicrophone();
- mic.addEventListener(StatusEvent.STATUS,micOnstatu);
- if(mic == null){
- Alert.show(“没有可以使用的麦克风”);
- }else{
- mic.setUseEchoSuppression(true);
- stm.attachAudio(mic);
- }
- stm.play(“chinahrt-”+txt_name.text);
- stm.publish(“chinahrt-”+txt_name.text,”live”);
- video_self = new Video();
- video_self.width = 320;
- video_self.height = 240;
- video_self.attachCamera(cam);
- my_video.addChild(video_self);
- }
- private function micOnstatu(e:StatusEvent):void
- {
- mic.setLoopBack(true);
- mic.gain = 66;
- mic.rate = 11;
- mic.setUseEchoSuppression(true);
- mic.setSilenceLevel(1,-1);
- }
- private function statusHandler(e:StatusEvent):void
- {
- }
- private function activityHandler(e:ActivityEvent):void
- {
- }
- private function addVideo(cards:ArrayCollection):void
- {
- label1.text = ”我的(“+localUsername+”)”;
- myBox.removeAllChildren();
- var otherPerson:ArrayCollection = new ArrayCollection();
- for(var i:int=0;i<cards.length;i++){
- var o:Object = cards.getItemAt(i);
- if(o["label"]!=localUsername&&o["label"]!=”All”){
- otherPerson.addItem(o);
- }
- }
- // Alert.show(otherPerson.length+”");
- var yushu:int = 0;
- var yushu:int= otherPerson.length%3;
- var rowNum:int = 0;
- var rowNum:int = otherPerson.length/3;
- if(yushu!=0){
- rowNum += 1;
- }
- if(yushu==0){
- for(var i:int=0;i<rowNum;i++){
- var hbox:HBox = new HBox();
- myBox.addChild(hbox);
- for(var ii:int=0;ii<3;ii++){
- var vbox:VBox = new VBox();
- hbox.addChild(vbox);
- var label:Label = new Label;
- label.text = otherPerson.getItemAt(i*3+ii)["label"];
- vbox.addChild(label);
- var videoDisplay:VideoDisplay = new VideoDisplay();
- videoDisplay.live = true;
- videoDisplay.width = 320;
- videoDisplay.height = 240;
- vbox.addChild(videoDisplay);
- var video:Video = new Video();
- video.width = 320;
- video.height = 240;
- var netStream:NetStream = new NetStream(conn);
- video.attachNetStream(netStream);
- netStream.play(“chinahrt-”+label.text);
- videoDisplay.addChild(video);
- }
- }
- }else{
- for(var i:int=0;i<rowNum-1;i++){
- var hbox:HBox = new HBox();
- myBox.addChild(hbox);
- for(var ii:int=0;ii<3;ii++){
- var vbox:VBox = new VBox();
- hbox.addChild(vbox);
- var label:Label = new Label;
- label.text = otherPerson.getItemAt(i*3+ii)["label"];
- vbox.addChild(label);
- var videoDisplay:VideoDisplay = new VideoDisplay();
- videoDisplay.live = true;
- videoDisplay.width = 320;
- videoDisplay.height = 240;
- vbox.addChild(videoDisplay);
- var video:Video = new Video();
- video.width = 320;
- video.height = 240;
- var netStream:NetStream = new NetStream(conn);
- video.attachNetStream(netStream);
- netStream.play(“chinahrt-”+label.text);
- videoDisplay.addChild(video);
- }
- }
- var hbox:HBox = new HBox();
- myBox.addChild(hbox);
- for(var i:int=0;i<yushu;i++){
- var vbox:VBox = new VBox();
- hbox.addChild(vbox);
- // myBox.addChild(vbox);
- var label:Label = new Label();
- label.text = otherPerson.getItemAt((rowNum-1)*3+i)["label"];
- vbox.addChild(label);
- var videoDisplay:VideoDisplay = new VideoDisplay();
- videoDisplay.live = true;
- videoDisplay.width = 320;
- videoDisplay.height = 240;
- vbox.addChild(videoDisplay);
- var video:Video = new Video();
- video.width = 320;
- video.height = 240;
- var netStream:NetStream = new NetStream(conn);
- // Alert.show(otherPerson.getItemAt((rowNum-1)*3+i)["label"]);
- video.attachNetStream(netStream);
- netStream.play(“chinahrt-”+label.text);
- videoDisplay.addChild(video);
- }
- }
- }
- ]]>
- </mx:Script>
- <mx:states>
- <mx:State id=”chatState” name=”chat”>
- <mx:SetProperty target=”{form1}” name=”width” value=”0″/>
- <mx:SetProperty target=”{form1}” name=”height” value=”0″/>
- <mx:SetProperty target=”{form1}” name=”x” value=”0″/>
- <mx:SetProperty target=”{form1}” name=”y” value=”0″/>
- <mx:AddChild position=”lastChild”>
- <mx:Panel x=”10″ y=”10″ width=”381″ height=”370″ layout=”absolute” title=”聊天信息”>
- <mx:TextArea x=”10″ y=”10″ width=”215″ height=”235″ id=”txt_chatmsg”/>
- <mx:ComboBox x=”233″ y=”34″ width=”118″ id=”userList”></mx:ComboBox>
- <mx:Label x=”233″ y=”11″ text=”用户列表”/>
- <mx:DataGrid x=”233″ y=”64″ height=”256″ id=”users” width=”118″>
- <mx:columns>
- <mx:DataGridColumn headerText=”用户名” dataField=”label”/>
- </mx:columns>
- </mx:DataGrid>
- <mx:TextInput x=”10″ y=”253″ height=”67″ width=”150″ id=”txt_yousay”/>
- <mx:Button x=”168″ y=”253″ label=”发送” width=”57″ click=”sendMessage(event)”/>
- <!– <mx:Button x=”168″ y=”298″ label=”进入视频会议” width=”57″ click=”video_clickHandler(event)”/>–>
- </mx:Panel>
- </mx:AddChild>
- <mx:AddChild position=”lastChild”>
- <mx:VideoDisplay live=”true” x=”10″ y=”410″ width=”320″ height=”240″ id=”my_video”/>
- </mx:AddChild>
- <mx:AddChild position=”lastChild”>
- <mx:Label id=”label1″ x=”10″ y=”390″ text=”我的”/>
- </mx:AddChild>
- <!–<mx:AddChild position=”lastChild”>
- <mx:VideoDisplay live=”true” x=”399″ y=”224″ width=”320″ height=”240″ id=”other_video”/>
- </mx:AddChild>
- <mx:AddChild position=”lastChild”>
- <mx:Label x=”399″ y=”198″ text=”对方的”/>
- </mx:AddChild> –>
- <mx:AddChild position=”lastChild”>
- <mx:VBox id=”myBox” x=”399″ y=”10″/>
- </mx:AddChild>
- </mx:State>
- </mx:states>
- <mx:Form x=”10″ y=”10″ width=”283″ height=”126″ id=”form1″>
- <mx:FormItem label=”用户名:”>
- <mx:TextInput id=”txt_name”/>
- </mx:FormItem>
- <mx:FormItem>
- <mx:Button label=”登陆” click=”login(event)”/>
- </mx:FormItem>
- </mx:Form>
- </mx:Application>
百度网盘:http://pan.baidu.com/s/1hqkgBly