您的位置:首页 > 其它

flex + red5实现视频会议

2013-05-14 13:36 369 查看
      公司最近要在系统中加视频会议的功能,让我探索,我选择了最流行的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>

 



大小: 51.7 KB



大小: 99.5 KB



大小: 84.3 KB

查看图片附件
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: