基于stratus +flex+MySQL的简易在线随机视频聊天室的开发
?
前段时间听说flashplayer已经开始支持p2p了,对这块非常感兴趣于是开始玩flex,一玩下来不可自拔。用Stratus搭建p2p环境如此简单,双方只需要能连上Stratus服务器就能直接进行语音视频的聊天,不需要任何客户端。Adobe还真是NB,呵呵出了这么XX的东东,前途无可限量啊。 闲话少说,这几天小搞了下,用Mysql和flex弄了个简易随机视频聊天室,和有共同爱好的各位一起分享下。高手可以直接飞过哈,欢迎提出意见,毕竟刚玩没多久,问题很多。 准备工作: 1.我用的是Flash Builder 4开发的,默认用的是Flex SDK4.0 2.开发最好用flash payer 10 debug version(可以进行调试) 3.申请一个 Stratus develper key:8b0f114ef5a20c433d5c2a33-201aeea5601b(用这个也行-_-我申请的)或者在这申请 ?下面正式开始,程序主体主要分为3大块: 一、p2p语音视频功能模块 ?这个模块网上有很多教程了,Adobe官方的那个Sample就很好。我就是以此为基础进行开发的。可能有些朋友还不是很了解,为了每个人都能搞清楚,下面针对代码详细地进行下介绍。 由于是p2p模式,每个用户既是呼叫者又是被呼叫者。更具体点,举个例子有两个人A和B打电话。A呼叫B,此时A是呼叫者,B是被呼叫者。反之B是呼叫者,A是被呼叫者。因此在每个p2p模块中必须要有呼叫者和被呼叫者两个部分。 首先要连接上stratus服务器: private var netConnection:NetConnection; //监听信息流 private var listenStream:NetStream; //播放上面的监听流 private var controlStream:NetStream; //对外发布信息流 private var outgoingStream:NetStream; //接受外部发送的信息流 private var incomingStream:NetStream; //Adobe stratus 服务器地址 private const SERVER:String="rtmfp://stratus.adobe.com/"; //Developer Key,要自己申请 private const DEVKEY:String="8b0f114ef5a20c433d5c2a33-201aeea5601b"; //创建一个nectConnetion与stratus 服务器进行连接 netConnection=new NetConnection(); netConnection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); //连接Adobe stratus 服务器 netConnection.connect(SERVER+DEVKEY); ? ?被呼叫部分: //被呼叫者发布监听流,以便呼叫者连接 listenStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); listenStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); listenStream.publish(username); //呼叫者一旦订阅上面的监听流,就会触发onPeerConnect事件 var c:Object =new Object(); c.onPeerConnect = function(caller:NetStream):Boolean { if(callState == CallReady) { //接受呼叫者发布的视频语音流 callState = CallRinging; idManager.change(callState); busyState = busyOn; incomingStream = new NetStream(netConnection,caller.farID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); video =new Video(); video.attachNetStream(incomingStream); remoteVideoDisplay.addChild(video); incomingStream.play("caller"); var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value); incomingStream.soundTransform = st; //处理呼叫的方法 var i:Object = new Object(); i.onIncomingCall = function(caller:String):void { if(callState != CallRinging) { txtInfo.text += "onIncomingCall: Wrong call state: " + callState + "n"; return; } send_bn.enabled=true; txtInfo.text += caller + "已经成功与您连接上n"; partnername = caller; //outgoingStream.send("onCallAccepted",username); callState = CallEstablished; //idManager.change(callState); } i.onIm = function(caller:String,text:String):void { txtMessage.text += caller+": "+text+"n"; } i.onDisconnected = function(caller:String):void { txtInfo.text += caller+"和你断开连接n"; send_bn.enabled=false; stop(); } incomingStream.client = i; //对呼叫者发布自己的语音视频流 outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,callee_outgoingStreamHandler); outgoingStream.attachCamera(camera); outgoingStream.attachAudio(mic); outgoingStream.publish("callee"); return true; } txtInfo.text += "onPeerConnect: all rejected due to state: " + callState + "n"; return false; } listenStream.client = c; callState = CallReady; ? ?
呼叫部分:?
callState = CallCalling; idManager.change(callState); busyState = busyOn; //播放被呼叫者的监听流 controlStream = new NetStream(netConnection,farPeerID); controlStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); controlStream.play(partnername); //对被呼叫者发布自己的视频语音流 outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,caller_outgoingStreamHandler); outgoingStream.attachCamera(camera); outgoingStream.attachAudio(mic); outgoingStream.publish("caller"); txtInfo.text += "正在与"+partnername+"建立连接n"; //播放被呼叫者的视频语音流 incomingStream = new NetStream(netConnection,farPeerID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); video =new Video(); video.attachNetStream(incomingStream); remoteVideoDisplay.addChild(video); incomingStream.play("callee"); var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value); incomingStream.soundTransform = st; //处理呼叫的方法 var i:Object = new Object(); i.onIm = function(callee:String,text:String):void { txtMessage.text += callee+": "+text+"n"; } i.onCallAccepted = function(callee:String):void { if (callState != CallCalling) { txtInfo.text += "连接失败"; return; } send_bn.enabled=true; txtInfo.text += callee+"已经成功与您连接上n"; callState = CallEstablished; //idManager.change(callState); } i.onDisconnected = function(callee:String):void { txtInfo.text += callee+"和你断开连接n"; send_bn.enabled=false; stop(); } incomingStream.client = i; ? 二、用户数据库及webservice接口 ?? 这个项目我使用MySQL进行用户数据库设计,用户端产生相应行为后(如登录,下线,聊天,空闲等)会在数据库中更新自己的状态,其他用户根据查询得到的结果进行操作。数据库结构见下图: ?????????????????????????????????????????????????????????????????用户名?????????? peerID??????? 更新时间????????????? 在线状态????聊天状态 ?? 该项目只要进行简单的数据库操作就可以了,所以我使用了HTTPService组件和一个简单的php网页进行与数据库的交互。 ?? HTTPService组件位于mx.rpc.http包下,当调用 HTTPService 对象的 ?? HTTPService组件部分: mHttpService = new HTTPService(); mHttpService.url = mWebServiceUrl; ? //添加对结果事件的侦听 mHttpService.addEventListener("result",httpResult); mHttpService.addEventListener("fault",httpFault); //实例化request请求对象并发送 var request:Object = new Object(); var now:Date = new Date(); request.time = now.getTime(); request.username = user; request.identity = id; request.online = x; request.busy = x; mHttpService.cancel(); mHttpService.send(request); ? ? ? ?数据库返回所有用户结果如下: ?<?xml version="1.0" encoding="utf-8" ?> ? ? 三、主程序部分 ?????????????主程序界面实时显示在线用户状态,可以双击列表空闲用户进行聊天也可以点击随机按钮随机选取在线空闲用户进行聊天。 ???????????? 退出时记得点击离开正常退出哈。 ???????????? 本程序实现非常简单,只需要一个Mysql数据库即可,缺陷是如果用户异常退出,状态信息不能即时在数据库中更新,会出现问题(如用户直接关闭程序进程,实际应该下线,但未更新数据库,所以状态还显示为在线)。仅供测试玩 ??????????? Demo:http://flexmysql.online.cm/p2pChatRoom.swf?? ??????????? ?有问题欢迎交流-_-??? 转载请注明出处 by疯狂的八神庵 ???????????? flex 完整代码在下面: import flash.external.ExternalInterface; import mx.events.FlexEvent; import mx.events.ListEvent; import mx.events.SliderEvent; import mx.managers.PopUpManager; import spark.components.TitleWindow; private var netConnection:NetConnection; private var netStream:NetStream; private var publishStream:NetStream; private var receiveStream:NetStream; private var mic:Microphone; private var camera:Camera; private var video:Video; private var windowisopen:Boolean = false; private var listenStream:NetStream; private var controlStream:NetStream; private var outgoingStream:NetStream; private var incomingStream:NetStream; // login/registration state machine [Bindable] private var loginState:int; private const LoginNotConnected:int = 0; private const LoginConnected:int = 1; // call state machine [Bindable] private var busyState:int; private const busyOn:int = 1; private const busyOff:int = 0; private var callState:int; private const CallNotReady:int = 0; private const CallReady:int = 1; private const CallCalling:int = 2; private const CallRinging:int = 3; private const CallEstablished:int = 4; private const CallFailed:int = 5; [Bindable] private var username:String; [Bindable] private var partnername:String; private var partner:String; private var login:int; [Bindable] private var userArray:Array; [Bindable] private var chaterArray:Array; private var farPeerID:String; private var idManager:AbstractIdManager; [Bindable] private var number:int; [Bindable] private var onlinenum:int; [Bindable] private var chaternum:int; [Bindable] private var micNames:Array; private var micIndex:int=0; [Bindable] private var cameraNames:Array; private var cameraIndex:int=0; //[Bindable] private var neighborCount:int=0; //[Bindable] private var vacantCount:int=0; private const SERVER:String="rtmfp://stratus.adobe.com/"; private const DEVKEY:String="8b0f114ef5a20c433d5c2a33-201aeea5601b"; private const GROUP_PREFIX:String = "com.p2p.chatroom"; private const WebServiceUrl:String = "http://p2pchat.online.cm/reg.php"; /*private static var FUNCTION_USEREXIT:String = "document.insertScript = function () " + "{ " + "window.onbeforeunload = function() " + "{ " + "var flexObj = p2pChatRoom.checkExit(); " +//MyTest2是swf在html中object的id "if(flexObj != "") " + "{ " + "return flexObj; " + "}else{ " + "return; " + "} " + "} " + "} "; */ private static var FUNCTION_USEREXIT:String = "document.insertScript = function () " + "{ " + "window.onbeforeunload = function() " + "{ " + "var flexObj = p2pChatRoom.checkExit(); " +//MyTest2是swf在html中object的id "if(flexObj != "") " + "{ " + "return flexObj; " + "}else{ " + "return; " + "} " + "} " + "} "; private function innit():void { //disconnect_btn.enabled=false; clear_btn.enabled=true; send_bn.enabled=false; random_btn.enabled = false; loginState = LoginNotConnected; busyState = busyOff; txtInfo.text+="------------------------------------------------------偶素华丽的分割线------------------------------------------------------n欢迎加入p2p聊天世界,右边列表显示所有当前在线的用户。你可以直接鼠标双击目标用户直接进行视频语音聊天!退出时请点击离开防止信息丢失,玩的愉快n"; if(!windowisopen) { openLoginWindow(); } ExternalInterface.call(FUNCTION_USEREXIT); ExternalInterface.addCallback("checkExit",checkExit); } private function checkExit():String { doExit(); var userExitStr:String = "欢迎您下次光临!"; return userExitStr; } private function doExit():void { if (callState == CallEstablished && outgoingStream) { outgoingStream.send("onDisconnected",username); txtInfo.text += "和"+partnername+"断开连接n"; partnername = null; } if (idManager) { idManager.unregister(); } if(netConnection) { netConnection.close(); txtInfo.text+="正在与服务器断开连接n"; } innit(); } private function openLoginWindow():void { var loginWindow:myLoginWindow=myLoginWindow(PopUpManager.createPopUp(this,myLoginWindow,true)); loginWindow.txtInput = txtID ; PopUpManager.centerPopUp(loginWindow); loginWindow.addEventListener(MyEvent.Login_OK,connect_btn_clickHandler); windowisopen = true; } protected function connect_btn_clickHandler(e:MyEvent):void { windowisopen = false; username=txtID.text; txtMessage.text=""; //userList.doubleClickEnabled=true; innitApp(); netConnection=new NetConnection(); netConnection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); netConnection.connect(SERVER+DEVKEY); } private function innitApp():void { var mics:Array=Microphone.names; if(mics) { micNames=mics; } else { txtMessage.text+="没有检测到麦克风n"; } var cams:Array=Camera.names; if(cams) { cameraNames=cams; } else { txtMessage.text+="没有检测到摄像头n"; } micSelection.selectedIndex=micIndex; cameraSelection.selectedIndex=cameraIndex; mic=Microphone.getMicrophone(micIndex); camera=Camera.getCamera(cameraIndex.toString()); var speakerVolume:Number = 0.8; speakerVolumeSlider.value = speakerVolume; var micVolume:int = 20; micVolumeSlider.value = micVolume; if (mic) { mic.codec=SoundCodec.SPEEX; mic.encodeQuality=3; mic.setSilenceLevel(10); mic.setUseEchoSuppression(true); mic.gain = micVolume; //mic.addEventListener(StatusEvent.STATUS,onDeviceStatus); //mic.addEventListener(ActivityEvent.ACTIVITY,onDeviceStatus); } else { txtMessage.text+="启动本地麦克风失败n"; } if (camera) { //camera.addEventListener(StatusEvent.STATUS,onDeviceStatus); //camera.addEventListener(ActivityEvent.ACTIVITY,onDeviceStatus); camera.setMode(320,240,10); camera.setQuality(360000 / 8,0); camera.setKeyFrameInterval(15); myVideoDisplay.attachCamera(camera); } else { txtMessage.text+="启动本地摄像头失败n"; } } private function onDeviceStatus(e:StatusEvent):void { } private function initStream():void { listenStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); listenStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); listenStream.publish(username); var c:Object =new Object(); c.onPeerConnect = function(caller:NetStream):Boolean { if(callState == CallReady) { callState = CallRinging; idManager.change(callState); busyState = busyOn; incomingStream = new NetStream(netConnection,caller.farID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); video =new Video(); video.attachNetStream(incomingStream); remoteVideoDisplay.addChild(video); incomingStream.play("caller"); var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value); incomingStream.soundTransform = st; var i:Object = new Object(); i.onIncomingCall = function(caller:String):void { if(callState != CallRinging) { txtInfo.text += "onIncomingCall: Wrong call state: " + callState + "n"; return; } send_bn.enabled=true; txtInfo.text += caller + "已经成功与您连接上n"; partnername = caller; //outgoingStream.send("onCallAccepted",username); callState = CallEstablished; //idManager.change(callState); } i.onIm = function(caller:String,text:String):void { txtMessage.text += caller+": "+text+"n"; } i.onDisconnected = function(caller:String):void { txtInfo.text += caller+"和你断开连接n"; send_bn.enabled=false; stop(); } incomingStream.client = i; outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,callee_outgoingStreamHandler); outgoingStream.attachCamera(camera); outgoingStream.attachAudio(mic); outgoingStream.publish("callee"); return true; } txtInfo.text += "onPeerConnect: all rejected due to state: " + callState + "n"; return false; } listenStream.client = c; callState = CallReady; } private function call():void { callState = CallCalling; idManager.change(callState); busyState = busyOn; controlStream = new NetStream(netConnection,farPeerID); controlStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); controlStream.play(partnername); outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,caller_outgoingStreamHandler); outgoingStream.attachCamera(camera); outgoingStream.attachAudio(mic); outgoingStream.publish("caller"); //outgoingStream.send("onIncomingCall",username); txtInfo.text += "正在与"+partnername+"建立连接n"; //var o:Object = new Object(); //o.onPeerConnect = function(callee:NetStream):Boolean //{ incomingStream = new NetStream(netConnection,farPeerID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); video =new Video(); video.attachNetStream(incomingStream); remoteVideoDisplay.addChild(video); incomingStream.play("callee"); var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value); incomingStream.soundTransform = st; var i:Object = new Object(); i.onIm = function(callee:String,text:String):void { txtMessage.text += callee+": "+text+"n"; } i.onCallAccepted = function(callee:String):void { if (callState != CallCalling) { txtInfo.text += "连接失败"; return; } send_bn.enabled=true; txtInfo.text += callee+"已经成功与您连接上n"; callState = CallEstablished; //idManager.change(callState); } i.onDisconnected = function(callee:String):void { txtInfo.text += callee+"和你断开连接n"; send_bn.enabled=false; stop(); } incomingStream.client = i; // return true; // } // outgoingStream.client = o; } private function sendSomeData():void{ if(txtSend.text != "") { txtMessage.text += username + ":" + txtSend.text + "n"; outgoingStream.send("onIm",username,txtSend.text); txtSend.text = " "; } else txtMessage.text += "-_-请输入内容n"; } private function caller_outgoingStreamHandler(e:NetStatusEvent):void { if (e.info.code == "NetStream.Play.Start" ) { outgoingStream.send("onIncomingCall",username); } } private function callee_outgoingStreamHandler(e:NetStatusEvent):void { if (e.info.code == "NetStream.Play.Start" ) { outgoingStream.send("onCallAccepted",username); } } private function statusHandler(event:NetStatusEvent):void { trace(event.info.code); switch(event.info.code) { case"NetConnection.Connect.Success": onConnect(); break; case"NetConnection.Connect.Closed": onDisconnect(); break; case "NetConnection.Connect.Failed": case "NetConnection.Connect.Rejected": txtInfo.text+="连接到服务器失败n"; loginState = LoginNotConnected; break; //case "NetStream.Play.Start": //onPlay(); //break; //case "NetStream.Connect.Success": //break; case "NetStream.Connect.Rejected": case "NetStream.Connect.Failed": txtInfo.text+="呼叫失败n"; break; } } private function onConnect():void { userList.doubleClickEnabled=true; chatList.doubleClickEnabled=true; login=1; callState = CallNotReady; txtInfo.text+=username+"与服务器连接成功n"; idManager = new HttpIdManager(); idManager.addEventListener("registerSuccess",idManagerEvent); idManager.addEventListener("registerFailure",idManagerEvent); idManager.addEventListener("lookupFailure",idManagerEvent); idManager.addEventListener("lookupSuccess",idManagerEvent); idManager.addEventListener("idManagerError",idManagerEvent); idManager.addEventListener("fetchSuccess",idManagerEvent); idManager.addEventListener("fetchFailure",idManagerEvent); idManager.addEventListener("unregisterFailure",idManagerEvent); idManager.service = WebServiceUrl; idManager.register(username,netConnection.nearID); userList.addEventListener(ListEvent.ITEM_DOUBLE_CLICK,p2pHandler); chatList.addEventListener(ListEvent.ITEM_DOUBLE_CLICK,ppHandler); } private function onDisconnect():void { login=0; send_bn.enabled=false; txtInfo.text+= "与服务器断开连接n"; chatList.dataProvider = null; userList.dataProvider = null; partnername = null; number=0; onlinenum=0; idManager = null; callState = CallNotReady; if(video) { remoteVideoDisplay.removeChild(video); } if (incomingStream) { incomingStream.close(); incomingStream.removeEventListener(NetStatusEvent.NET_STATUS,statusHandler); } if (outgoingStream) { outgoingStream.close(); outgoingStream.removeEventListener(NetStatusEvent.NET_STATUS,statusHandler); } if (controlStream) { controlStream.close(); controlStream.removeEventListener(NetStatusEvent.NET_STATUS,statusHandler); } incomingStream = null; outgoingStream = null; controlStream = null; netConnection = null; innit(); } private function idManagerEvent(e:Event):void { //status("ID event: " + e.type + "n"); trace(e.type); switch(e.type) { case "registerSuccess": case "registerFailure": if(login == 1) { txtInfo.text+="恭喜"+username+"登陆成功n"; idManager.fetch(); initStream(); login++; loginState = LoginConnected; random_btn.enabled = true; } else if(login == 0) { return; } else { login++; trace("更新第"+login+"次"); idManager.fetch(); } break; case "lookupSuccess": // party query response var i:IdManagerEvent = e as IdManagerEvent; partnername=i.user; farPeerID=i.id; call(); break; case "fetchSuccess": var j:FetchDataEvent = e as FetchDataEvent; onlinenum = j.onlinenum; number = j.num; userArray = j.arr; chaternum = j.chaternum; chaterArray = j.chatarr; break; default: // all error messages ar IdManagerError type /* var error:IdManagerError = e as IdManagerError; trace("Error description: " + error.description + "n") */ login=0; txtInfo.text+="查询失败,发生错误n"; /* if (callState == CallEstablished && outgoingStream) { outgoingStream.send("onDisconnected",username); txtInfo.text += "和"+partnername+"断开连接n"; partnername = null; } if (idManager) { idManager.unregister(); } if(netConnection) { netConnection.close(); }; */ break; } } private function p2pHandler(evt:ListEvent):void { if((evt.target.selectedItem.label != username) && callState == 1) { idManager.lookup(evt.target.selectedItem.label); } if(evt.target.selectedItem.label == username) { txtMessage.text += "-_- 你不能与自己聊天n"; } if((evt.target.selectedItem.label != username) && (callState == 2 || callState == 3 || callState == 4)) { txtMessage.text += "-_- 聊天中无法和其他人聊天 ,请先停止n"; } } private function ppHandler(evt:ListEvent):void { if(evt.target.selectedItem.label == username) { txtMessage.text += "-_- 你不能与自己聊天n"; } else { txtMessage.text += "-_- 对方正在聊天中 无法打断n"; } } protected function clear_btn_clickHandler(event:MouseEvent):void { txtMessage.text=""; txtInfo.text = ""; } protected function disconnect_btn_clickHandler(event:MouseEvent):void { txtInfo.text+="正在与服务器断开连接n"; if (callState == CallEstablished && outgoingStream) { outgoingStream.send("onDisconnected",username); txtInfo.text += "和"+partnername+"断开连接n"; partnername = null; } if (idManager) { idManager.unregister(); } if(netConnection) { netConnection.close(); } } protected function stop_btn_clickHandler(event:MouseEvent):void { if (callState == CallEstablished && outgoingStream) { outgoingStream.send("onDisconnected",username); txtInfo.text += "和"+partnername+"断开连接n"; stop(); } } private function stop():void { partnername = null; callState = CallReady; busyState = busyOff; if(video) { remoteVideoDisplay.removeChild(video); video = null; } if (incomingStream) { incomingStream.close(); incomingStream.removeEventListener(NetStatusEvent.NET_STATUS,statusHandler); } idManager.change(callState); } protected function random_btn_clickHandler(event:MouseEvent):void { txtMessage.text += "正在查询空闲用户n"; if(callState != CallReady) { txtMessage.text += "请先停止聊天n"; } else { var i:int = int(Math.random()*userArray.length); if(userArray[i].label == username && userArray.length == 1) { txtMessage.text += "无空闲用户n"; } else if(userArray[i].label == username) { do { var i:int = int(Math.random()*userArray.length); } while(userArray[i].label == username); txtMessage.text += "查找成功n"; idManager.lookup(userArray[i].label); } else { txtMessage.text += "查找成功n"; idManager.lookup(userArray[i].label); } } } protected function cameraChanged():void { cameraIndex=cameraSelection.selectedIndex; camera=Camera.getCamera(cameraIndex.toString()); if (camera) { camera.setMode(320,0); camera.setKeyFrameInterval(15); myVideoDisplay.attachCamera(camera); if (callState == CallEstablished) { outgoingStream.attachCamera(camera); } } else { txtMessage.text+="启动本地摄像头失败"; } } private function micChanged():void { micIndex = micSelection.selectedIndex; mic = Microphone.getMicrophone(micIndex); if (mic) { mic.codec=SoundCodec.SPEEX; mic.encodeQuality=3; mic.setSilenceLevel(5); mic.setUseEchoSuppression(true); if (callState == CallEstablished) { outgoingStream.attachAudio(mic); } } else { txtMessage.text+="启动本地麦克风失败"; } } private function micVolumeChanged(e:SliderEvent):void { var mic:Microphone = Microphone.getMicrophone(micIndex); if (mic) { mic.gain = e.value; } } private function speakerVolumeChanged(e:SliderEvent):void { if (incomingStream) { var st:SoundTransform = new SoundTransform(e.value); incomingStream.soundTransform = st; } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |