flex4+fms3.5+cs4开发实时音视频直播及点播详解
开发工具及环境:
1)flash builder4
2)flash cs4
3)flash media server3.5
fms部分
fms是adobe的流媒体服务器,不过是收费的,价格大概是oracle的一半,不过还是觉得挺贵的。adobe提供控制连接数的开发人员版,除了这点具体和完全版一样。服务器的配置网上很多,大家一搜就一大把。
其中关键的是:
由于需求是不仅要提供直播,而且还能点播历史音视频。而as的NetStream的publish方法提供的录制功能是将录制的视频放在直播的同一个文件夹下,所以要需要改一下fms配置。
1)fms的dvr默认是关闭的,需要修改C:/Program Files/Adobe/Flash Media Server 3.5/applications/live/main.far里的Application.xml,将文件中的
<StreamManager>
<StreamRecord override="no">false</StreamRecord>
</StreamManager>
改为
<StreamManager>
<StreamRecord override="yes">true</StreamRecord>
</StreamManager>
修改后重新加入main.far中
2)fms默认是将直播与点播文件目录分开的(live和vod),但是视频录制是在live文件加下,所以我们要修改点播目录
C:/Program Files/Adobe/Flash Media Server 3.5/conf/fms.ini文件中将
VOD_DIR 设置为 C:/Program Files/Adobe/Flash Media Server 3.5/applications/live
这样就可以实现直播点播同文件夹共存了
3)配置虚拟目录
打开C:/Program Files/Adobe/Flash Media Server 3.5/applications/live/Application.xml在<VirtualDirectory>中加入
<Streams>/;${VOD_COMMON_DIR}</Streams>
<Streams>/;${VOD_DIR}</Streams>
补充:如果在flex中直播视频时抛出noAccess错误,请在服务器上给录制机分配写入的权限。
通过以上配置fms就完成了
flex部分
<?xml version="1.0" encoding="utf-8"?> <s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" width="430" height="340" creationComplete="init_creationCompleteHandler(event)" closing="unonload()"> <s:layout> <s:BasicLayout/> </s:layout> <fx:Style source="media.css"/> <fx:Script> <!--[CDATA[ import flash.events.NetStatusEvent; import flash.net.NetConnection; import flash.net.NetStream; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.controls.Text; import mx.events.FlexEvent; private var nc:NetConnection; private var ns:NetStream; private var vi:Video; private var cam:Camera; private var microphone:Microphone; private var fmsArr:ArrayCollection=new ArrayCollection([]); private var sharedObject:SharedObject=SharedObject.getLocal("com.ue"); /** * 初始化 */ protected function init_creationCompleteHandler(event:FlexEvent):void{ var loader:URLLoader = new URLLoader(); loader.dataFormat=URLLoaderDataFormat.VARIABLES; loader.addEventListener(Event.COMPLETE, loader_complete); loader.load(new URLRequest("global.txt")); function loader_complete(e:Event):void { var variables:URLVariables=new URLVariables(loader.data); trace(variables["servers"]); var serversArr:Array = variables["servers"].toString().split("@#@"); for(var i:int=0;i<serversArr.length;i++){ var server:String = serversArr[i]; var sArr:Array = server.toString().split("$#$"); fmsArr.addItem({label:sArr[0], value:sArr[1]}); } } getCookie(); } public function getCookie():void{ if(sharedObject.data["pmhidh"]!=null){ pmhID.text = sharedObject.data["pmhidh"]; } if(sharedObject.data["spfwq"]!=null){ trace(sharedObject.data["spfwq"]); fms.selectedItem = sharedObject.data["spfwq"]; } if(sharedObject.data["yl"]!=null){ slider.value = sharedObject.data["yl"]; } } /** * 开始直播 */ protected function startButton_clickHandler(event:MouseEvent):void{ startButton.enabled=false; stopButton.enabled=true; nc = new NetConnection(); nc.addEventListener(NetStatusEvent.NET_STATUS,connectServerHander); nc.client = this; trace(fms.selectedItem.label); nc.connect(fms.selectedItem.value); slider.enabled=true; } /** * 回调函数 */ public function connectServerHander(evt:NetStatusEvent):void{ cam = Camera.getCamera(); microphone = Microphone.getMicrophone(); if(microphone!=null){ microphone.setSilenceLevel(0);//总是收集音频 microphone.rate=44; }else{ Alert.show("无法链接到麦克风!"); } if(cam != null){ cam.setMode(176,144,25); cam.setQuality(0,90); ns = new NetStream(nc); ns.attachCamera(cam); ns.attachAudio(microphone); ns.publish(pmhID.text,"append"); vi = new Video(); vi.width = player.width; vi.height = player.height; vi.attachCamera(cam); player.addChild(vi); }else{ Alert.show("无法链接到摄像头!"); } } /** * NetConnection回调函数 */ public function onBWDone():void{} /** * 保存音视频文件 */ // protected function browse_clickHandler(event:MouseEvent):void{ // var file:File = new File(); // file.addEventListener(Event.SELECT,dirSelected); // file.browseForSave("另存为"); // function dirSelected(e:Event):void { // trace(file.nativePath); // filefilePath.text = file.nativePath; // } // } /** * 音量控制 */ protected function slider_changeHandler(event:Event):void{ microphone.gain = slider.value; } /** * 停止发布 */ protected function stopButton_clickHandler(event:MouseEvent):void{ ns.close(); vi.visible=false; startButton.enabled=true; stopButton.enabled=false; } /** * 关闭窗口触发事件,保存配置 */ public function unonload():void{ if(sharedObject!=null){ sharedObject.clear(); } if(sharedObject.data["pmhidh"]==null){ sharedObject.data["pmhidh"] = pmhID.text; } if(sharedObject.data["spfwq"]==null){ sharedObject.data["spfwq"] = fms.selectedItem; } if(sharedObject.data["yl"]==null){ sharedObject.data["yl"] = slider.value; } sharedObject.flush(); } ]]--> </fx:Script> <fx:Declarations> <!-- 将非可视元素(例如服务、值对象)放在此处 --> </fx:Declarations> <s:Panel width="100%" height="100%" x="0" y="0" id="panel1"> <s:layout> <s:VerticalLayout paddingLeft="0" paddingRight="0" paddingBottom="0" paddingTop="0" requestedRowCount="7"/> </s:layout> <s:VGroup width="430" height="272" paddingTop="10" paddingBottom="0" paddingRight="0" paddingLeft="0"> <s:HGroup width="430" height="220" paddingLeft="5" paddingTop="2" paddingBottom="0" paddingRight="5"> <s:BorderContainer width="206" height="215"> <s:VideoDisplay width="193" height="202" scaleMode="letterbox" id="player" x="5" y="5"/> </s:BorderContainer> <s:BorderContainer width="206" height="215"> <s:VGroup width="193" height="208" paddingLeft="5" paddingTop="0" paddingBottom="0" paddingRight="5" gap="9" x="5" y="5"> <s:Label text="拍卖会ID号:" height="14" id="pmhIDLabel"/> <s:TextInput id="pmhID"/> <s:Label text="视频服务器:" height="14"/> <s:ComboBox id="fms" dataProvider="{fmsArr}"/> <s:Label text="音量" height="14"/> <s:HSlider id="slider" enabled="false" change="slider_changeHandler(event)" minimum="0" maximum="100" stepSize="10"/> </s:VGroup> </s:BorderContainer> </s:HGroup> <s:HGroup width="430" height="35" paddingTop="0" paddingBottom="0" paddingRight="5" paddingLeft="145" verticalAlign="middle" textAlign="center"> <s:Button id="startButton" label="开始发布" click="startButton_clickHandler(event)"/> <s:Button label="停止发布" id="stopButton" enabled="false" click="stopButton_clickHandler(event)"/> </s:HGroup> </s:VGroup> </s:Panel> </s:WindowedApplication>
效果如下:
flash部分/点播代码
import flash.net.*; import flash.events.*; var loader:URLLoader = new URLLoader(); loader.dataFormat=URLLoaderDataFormat.VARIABLES; loader.addEventListener(Event.COMPLETE, loader_complete); loader.load(new URLRequest("media2.txt")); function loader_complete(e:Event):void { var variables:URLVariables=new URLVariables(loader.data); myVideo2.source=variables["source"]; } myVideo2.isLive=false;
效果如下:
flash部分/直播代码
import flash.net.*; import flash.events.*; var loader:URLLoader = new URLLoader(); loader.dataFormat=URLLoaderDataFormat.VARIABLES; loader.addEventListener(Event.COMPLETE, loader_complete); loader.load(new URLRequest("media.txt")); function loader_complete(e:Event):void { var variables:URLVariables=new URLVariables(loader.data); myVideo.source=variables["source"]; } myVideo.isLive=true;
效果如下: