2013年6月2日 星期日

FMS-FLASH MEDIA SERVER 視訊網站架設教學(3)

即然不小心標題多打了視訊2個字
現在要改也來不及惹QQ
但視訊網站的模式有很多種
我想 這樣好了
我們來做這個~
點擊房間列表裡的使用者 然後可以選 觀看對方視訊
而LAYOUT也要改一下
如果是觀看對方視訊的同時,聊天介面就會切成上下2半
上面是視訊
視訊再分成2半,左邊是對方,右邊是自已
然後要看對方視訊時,會有邀請的要求給對方
對方同意後 才會打開 視訊的介面出來
這時候第三方要邀請已經在視訊中的人,都會回傳 對方已在視訊聊天中
嗯嗯 就這樣~那就開始ㄅ

通常server code會比較容易看懂,也比較容易了解整個的流程
延展教學(2)的範例,新增了幾個主要的method
client.invite = function(id)//發起邀請
client.sendInviteResponse = function(id,isAccept)//接收方回應是否接受邀請
application.onPublish = function(client, myStream)//發佈視訊
client.exitPrivate = function()//通知要離開private視訊
exitPrivate(user)//離開private視訊

這裡比較重要的地方是,要發佈一個視訊,需要有一個stream name
可以自已定一個字串,但不可以重覆。
假如a發佈一個video_a的串流,那麼 其他使用者只要去聽這個串流名稱video_a就可以看到a所發佈的視訊

client的程式裡有一個特別的用法,你可以在限定參數的event function內,帶入各種想要的參數
Alert.show(userName+"邀請你進入視訊聊天,是否接受?", "hasInvite", Alert.OK|Alert.NO,null,addInviteArguments(onHasInvite,id));
private function addInviteArguments(method:Function, id:String):Function
{
return function(event:Event):void {method.apply(null, [event].concat(id));}
}
private function onHasInvite(e:CloseEvent,id:String):void{
mineNc.call("sendInviteResponse",null,id,Boolean(e.detail==Alert.OK));
}
常常會遇到這種onEvent的參數,不能多帶參數的情況,這時候使用這個方式,可以任意帶多個參數進去;就不用另外做一個變數來記著了。

其它部份就直接看code吧~

server code:

var roomUser = new Array();
var ERROR_REPEAT_LOGIN=0;

var USER_STATUS_NORMAL=0;
var USER_STATUS_PRIVATE=1;

var INVITE_RESULT_PRIVATE=0;
var INVITE_RESULT_SUCCESS=1;
var INVITE_RESULT_OFF_LINE=2;
var INVITE_RESULT_REJECT=3;
application.onAppStart = function (){ 
 
}
// Called when a client connects
application.onConnect = function(client,userName) {
 var _user=getUserByUserName(userName);
 if(_user)
 {
  application.rejectConnection(client,ERROR_REPEAT_LOGIN);
  return;
 }
 
 var user=new Object();
 user.userName=userName;
 user.id=client.id;
 user.client=client;
 user.status=USER_STATUS_NORMAL;
 user.streamId="";
 user.targetUserId=null;
 roomLogin(user);
 
 client.chatToAll = function(msg) {
  var user=getUserByClient(client);
  application.broadcastMsg("chatReceive",user.userName,msg);
 }
 //發起邀請
 client.invite = function(id) {
  var user=getUserByClient(client);
  var targetUser=getUserById(id);
  if(targetUser)
  {
   if(targetUser.status==USER_STATUS_PRIVATE)
    client.call("onInvite",null,INVITE_RESULT_PRIVATE);//回應 對方已在視訊中
   else
   {
    client.call("onInvite",null,INVITE_RESULT_SUCCESS);//回應 等待對方接受
    targetUser.client.call("hasInvite",null,user.id,user.userName);//通知 對方 有人邀請你
   }
  }
  else
   client.call("onInvite",null,INVITE_RESULT_OFF_LINE);//回應 對方已不在線上
 }
 //接收方回應是否接受邀請
 client.sendInviteResponse = function(id,isAccept) {
  var user=getUserByClient(client);
  var targetUser=getUserById(id);
  if(isAccept)//如果同意 就把雙方各建立一個stream的id,並讓雙方得知自已跟對方的stream id
  {
   var r=Math.floor(Math.random()*1000);
   user.streamId=r+user.id;
   targetUser.streamId=r+targetUser.id;
   //事實上這裡可以做個更好的加密 比如說拿雙方的id再加上一個變數來md5一下
   user.targetUserId=targetUser.id;
   targetUser.targetUserId=user.id;
   //互相記住對方的id,若斷線的時後可以通知對方
   client.call("intoPrivate",null,user.streamId,targetUser.streamId);
   targetUser.client.call("intoPrivate",null,targetUser.streamId,user.streamId);
   //進入雙人視訊模式 參數1是自已的stream id,參數2是對方的stream id
   user.status=USER_STATUS_PRIVATE;
   targetUser.status=USER_STATUS_PRIVATE;
  }
  else
   targetUser.client.call("onInvite",null,INVITE_RESULT_REJECT);
 }
 client.exitPrivate = function() {
  var user=getUserByClient(client);
  exitPrivate(user);
 }
}

// Called when the client publishes
application.onPublish = function(client, myStream) {
  nc = new NetConnection();
  ns = new NetStream(nc);
  ns.setBufferTime(2);
  ns.attach(myStream);
  ns.publish( myStream.name, "live" );
}

application.onUnpublish = function( client, myStream ) {
var user=getUserByClient(client);
application.broadcastMsg("systemMessage",user.userName+":"+myStream.name);
    trace(myStream.name + " is unpublishing"  );
}

// Called when a client disconnects
application.onDisconnect = function(client) {
 var user=getUserByClient(client);
 exitPrivate(user);
 removeUser(user);
}

function exitPrivate(user)
{
 user.status=USER_STATUS_NORMAL;
 user.targetUserId=null;
 var targetUser=getUserById(user.targetUserId);
 if(targetUser)
 {
  targetUser.status=USER_STATUS_NORMAL;
  targetUser.targetUserId=null;
  targetUser.client.call("onExitPrivate",null);
 }
}

function roomLogin(user)
{
 application.acceptConnection(user.client);
 application.broadcastMsg("systemMessage", user.userName+" in room");
 roomUser.push(user);
 application.broadcastMsg("userList",roomUser);
 user.client.call("loginSuccess",null,user);
}

function removeUser(user){
 var ind=roomUser.indexOf(user);
 if(ind!=-1)
 {
  roomUser.splice(ind,1);
  application.broadcastMsg("userList",roomUser);
 }

}

function getUserByUserName(userName){
 for(var i=0;i<roomUser.length;i++)
  {
   if(roomUser[i].userName==userName)
   {
    return roomUser[i];
   }
  }
  return null;
}

function getUserByClient(client){
 for(var i=0;i<roomUser.length;i++)
  {
   if(roomUser[i].client==client)
   {
    return roomUser[i];
   }
  }
  return null;
}

function getUserById(id){
 for(var i=0;i<roomUser.length;i++)
  {
   if(roomUser[i].id==id)
   {
    return roomUser[i];
   }
  }
  return null;
}




client code:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
      xmlns:s="library://ns.adobe.com/flex/spark" applicationComplete="application1_applicationCompleteHandler(event)"
      xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
 <fx:Script>
  <![CDATA[
   import flash.net.NetStream;
   import flash.utils.setTimeout;
   
   import mx.collections.ArrayCollection;
   import mx.controls.Alert;
   import mx.events.CloseEvent;
   import mx.events.FlexEvent;
   
   private var server_ip:String="rtmp://localhost/chat";
   private var mineNc:NetConnection;
   private var mineNs:NetStream;
   private var mineVideo:Video;
   private var mineCamera:Camera;
   private var mineMic:Microphone;
   private const cameraW:Number=400;
   private const cameraH:Number=300;
   
   private var targetVideo:Video;
   private var targetNs:NetStream;
   
   private var STATE_MODE_NOTPRIVATE:String="notPrivate";
   private var STATE_MODE_ISPRIVATE:String="isPrivate";
   [Bindable]
   private var user:Object;
   [Bindable]
   private var isLoign:Boolean=false;
   [Bindable]
   private var userArr:ArrayCollection=new ArrayCollection();
   
   //跟server對應的狀態變數
   private const INVITE_RESULT_PRIVATE:int=0;
   private const INVITE_RESULT_SUCCESS:int=1;
   private const INVITE_RESULT_OFF_LINE:int=2;
   private const INVITE_RESULT_REJECT:int=3;
   
   protected function application1_applicationCompleteHandler(event:FlexEvent):void
   {
    chat_input.addEventListener(KeyboardEvent.KEY_UP,onChatKeyUp);
   }
   private function login():void
   {
    var name:String=name_txt.text;
    if(name!="")
    {
     mineNc = new NetConnection();
     mineNc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
     mineNc.connect(server_ip,name);
     login_group.visible=false;
     
     var ncClient:Object = new Object(); 
     mineNc.client = ncClient; 
     ncClient.systemMessage = systemMessage;
     ncClient.userList = userList;
     ncClient.chatReceive = chatReceive;
     ncClient.onInvite = onInvite;
     ncClient.hasInvite = hasInvite;
     ncClient.intoPrivate = intoPrivate;
     ncClient.loginSuccess = loginSuccess;
     ncClient.onExitPrivate = onExitPrivate;
    }
    else
     Alert.show("請輸入登入別名", "ERROR", Alert.OK,null);
   }
   
   
   private function onExitPrivate():void
   {
    this.currentState=STATE_MODE_NOTPRIVATE;
    mineNs.attachCamera(null);
   }
   private function loginSuccess(user:Object):void
   {
    this.user=user;
   }
   
   private function intoPrivate(streamId:String,targetStreamId:String):void
   {
    systemMessage("my straemId:"+streamId+",target StreamId:"+targetStreamId);
    this.currentState=STATE_MODE_ISPRIVATE;
    publishLiveStream(streamId);
    this.addRemoteVideo(targetStreamId);
   }
   
   private function publishLiveStream(streamName:String):void {
    mineNs = new NetStream(mineNc);
    mineNs.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
    
    mineCamera = Camera.getCamera();
    mineMic = Microphone.getMicrophone();
    if (mineCamera != null){
     mineCamera.setQuality(0,100);
     mineCamera.setMode(cameraW,cameraH,28);
     mineCamera.setKeyFrameInterval(60);
     mineCamera.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
     mineVideo = new Video();
     mineVideo.attachCamera(mineCamera);
     mineVideo.width=cameraW;
     mineVideo.height=cameraH;
     mineVideo.smoothing = true;
     mineNs.attachCamera(mineCamera);
     mineVideoDisplay.addChild(mineVideo);
    }
    
    if (mineMic != null) {
     mineMic.addEventListener(ActivityEvent.ACTIVITY, activityHandler);
     mineNs.attachAudio(mineMic);
    }
    if (mineCamera != null || mineMic != null){
     mineNs.publish(streamName, "live");
    } else {
     trace("Please check your camera and microphone");
    }
   }
   
   private function activityHandler(event:ActivityEvent):void {
    trace("activityHandler: " + event);
    trace("activating: " + event.activating);
   }
   private function addRemoteVideo(streamName:String,id:String="",name:String=""):void{
    targetNs=new NetStream(mineNc);
    targetNs.play(streamName);
    targetVideo = new Video();
    targetVideo.width=cameraW;
    targetVideo.height=cameraH;
    targetVideo.attachNetStream(targetNs);
    targetVideo.smoothing = true;
    tagetVideoDisplay.addChild(targetVideo);
    tagetVideoDisplay.visible=true;
   }
   
   private function hasInvite(id:String,userName:String):void
   {
    Alert.show(userName+"邀請你進入視訊聊天,是否接受?", "hasInvite", Alert.OK|Alert.NO,null,addInviteArguments(onHasInvite,id));
    //這裡有一個特別的用法,學會它,你可以在限定參數的event function內,帶入各種想要的參數
   }
   private function addInviteArguments(method:Function, id:String):Function 
   {
    return function(event:Event):void {method.apply(null, [event].concat(id));}
   }
   private function onHasInvite(e:CloseEvent,id:String):void{
    mineNc.call("sendInviteResponse",null,id,Boolean(e.detail==Alert.OK));
   }
   
   private function onInvite(resultCode:int):void
   {
    switch(resultCode)
    {
     case INVITE_RESULT_PRIVATE:
      Alert.show("對方已在視訊聊天中", "onInvite", Alert.OK,null);
      break;
     case INVITE_RESULT_SUCCESS:
      Alert.show("等待對方回應中...", "onInvite", Alert.OK,null);
      break;
     case INVITE_RESULT_OFF_LINE:
      Alert.show("對方已不在線上", "onInvite", Alert.OK,null);
      break;
     case INVITE_RESULT_REJECT:
      Alert.show("對方捥拒你的邀請", "onInvite", Alert.OK,null);
      break;
     default:
      Alert.show("未知錯誤", "onInvite", Alert.OK,null);
      break;
    }
   }
   private function chatReceive(senderName:String,msg:String):void
   {
    msg=senderName+":"+msg+String.fromCharCode(13);
    content_txt.text+=msg;
   }
   
   private function netStatusHandler(event:NetStatusEvent):void
   {
    trace(event.info.code);
    switch (event.info.code)
    {
     case "NetConnection.Connect.Success":
      isLoign=true;
      break;
     case "NetConnection.Connect.Closed":
      isLoign=false;
      Alert.show("Closed", "ERROR", Alert.OK,null);
      break;
     case "NetConnection.Connect.Rejected":
      isLoign=false;
      loginFail(event.info.application); 
      break;
    }
   }
   private function loginFail(type:int):void{
    switch(type)
    {
     case 0:
      Alert.show("重覆登入", "ERROR", Alert.OK,null);
      break;
     default:
      Alert.show("Rejected", "ERROR", Alert.OK,null);
      break;
    }
   }
   private function onChatKeyUp(evt:KeyboardEvent):void
   {   
    if (evt.keyCode == Keyboard.ENTER)
    {
     mineNc.call("chatToAll", null,chat_input.text);
     chat_input.text="";
    }
   }
   
   private function systemMessage(msg:String):void
   { 
    msg+=String.fromCharCode(13);
    content_txt.text+=msg;
   }
   
   private function userList(list:Object):void
   {
    userArr=new ArrayCollection;
    for(var i:int=0;i<list.length;i++)
    {
     userArr.addItem(list[i]);
    }
   }
   
   private function invite():void
   {
    if(room_list.selectedIndex<0)return;
    var user:Object=userArr.getItemAt(room_list.selectedIndex);
    if(user.id==this.user.id)
    {
     Alert.show("不能邀請自已", "ERROR", Alert.OK,null);
     return;
    }
    if(this.currentState==STATE_MODE_ISPRIVATE)
    {
     Alert.show("你已在private中", "ERROR", Alert.OK,null);
     return;
    }
    if(user)
    {
     mineNc.call("invite", null,user.id);
    }
   }
   
   private function exitPrivate():void
   {
    mineNc.call("exitPrivate", null);
    onExitPrivate();
   }
  ]]>
 </fx:Script>
 <s:states>
  <s:State name="notPrivate" />
  <s:State name="isPrivate" />
 </s:states>
 <s:HGroup width="100%" height="100%">
  <s:VGroup width="20%" height="100%">
   <s:List id="room_list" width="100%" height="95%" dataProvider="{userArr}" itemRenderer="userItem" verticalScrollPolicy="auto" horizontalScrollPolicy="off" contentBackgroundAlpha="0.0" borderVisible="false"/>
   <s:HGroup width="100%" height="5%">
    <s:Button label="邀請視訊" click="invite()" enabled="{isLoign}" />
   </s:HGroup>
  </s:VGroup>
  <s:VGroup  width="80%" height="100%">
   <s:HGroup width="100%" height="100%" includeIn="isPrivate">
    <mx:VideoDisplay id="tagetVideoDisplay" width="{cameraW}" height="{cameraH}"/>
    <mx:VideoDisplay id="mineVideoDisplay" width="{cameraW}" height="{cameraH}"/>
    <s:Button label="exit" click="exitPrivate()"/>
   </s:HGroup>
   <s:TextArea editable="false" id="content_txt" width="100%" height="90%"/>
   <s:TextInput id="chat_input"  width="100%" height="10%"/>
  </s:VGroup>
 </s:HGroup>
 <s:HGroup id="login_group" right="10" top="10">
  <s:TextInput width="100" id="name_txt"/>
  <s:Button label="login" click="login()"/>
 </s:HGroup>
</s:Application>




下一次,來增加在server上呼叫php api的功能,順便補上一對一密語的功能。~

沒有留言:

張貼留言