Red5二次开发实现用户自定义逻辑
上一篇文章介绍了Red5服务器的搭建是使用,这一篇文章将介绍如何在Red5中实现自己的逻辑。本文章是以第一篇为基础,默认已搭建好Red5服务器,不再介绍Red5服务器的搭建与使用。
准备工作
开发工具:Eclipse
Red5 Server:Red5 Server 1.0.9 https://github.com/Red5/red5-server
red5-eclipse-plugin:https://github.com/Red5/red5-eclipse-plugin
开发工具使用的Eclipse,因为需要使用Red5插件,而Red5插件仅支持Eclipse所以只能选择Eclipse
安装Eclipse插件
根据上面提供的链接下载Red5-eclipse-plugin插件,压缩包解压之后将发现目录中存在一个screenshots文件夹,里面为插件安装步骤的截图,Github中的Readme中也有插件安装的讲解,这里就不再演示插件的具体安装步骤。
注意:在使用之前需要对插件进行简单的修改,否则在配置Red5-server的时候会出现无法进行下一步的问题,以Windows为例,打开org.leagueplanet.server.glassfish目录,找到red5.serverdef并编辑,将文件中的所有.sh结尾的配置全部替换为.bat结尾。Linux系统同理,将.bat修改为.sh
安装完成后重启eclipse,window->show view->servers->new server.出现Red5 Server Runtime选项,说明安装成功。
创建Web项目
新建项目选择Dynamic Web project,第一次配置需要配置一下三处
首先Target runtime可能是空的,这时我们就需要选择New Runtime进行配置运行时环境,如下图所示,选择Infrared5中的Red5 Server RunTime,点击下一步
接下来还需要配置JRE和Red5 Server的路径,Runtime Directory就是选择我们解压的Red5 Server的目录
配置完成后可能下图所示的错误,这是因为我们下载的Red5 Server中相关jar包的命名包含版本信息,例如red5-io-1.8.0-Realease.jar,这时候就要将其修改为red5-io.jar
继续下一步,配置Red5的启动关闭文件以及相关端口号
现在我们回到了创建项目的页面,这是还需要对Configuration进行修改,点击Modify,按照下图进行勾选
现在项目就创建完成了,生成服务端同时还生成了客户端
测试运行
Run Server选择Red5 Server Runtime
使用浏览器打开http://127.0.0.1:5080/demos/publisher.html,发布直播收看直播正常
实现自己的逻辑
在实现这些功能之前我们需要先了解一下Red5响应请求的具体流程
- Red5在启动时会调用RTMPMinaTransport的start()方法,该方法会开启rmtp的socket监听端口(默认是1935),然后使用Mina 的api 将RTMPMinaIoHandler 绑定到该端口。
- RTMPMinaIoHandler 上定义了messageReceived()、messageSent()、sessionOpened()和sessionClosed()等方法,当有socket请求时,相应的方法会被调用。这时RTMPMinaIoHandler 会使用当前的socket连接来创建一个(或者使用之前创建好的)RTMPMinaConnection,并将其作为参数传递给定义于RTMPHandler类上的相应的messageReceived()、messageSent()、connectionOpened()和connectionClosed()方法
- RTMPHandler会调用Server类的lookupGlobal()方法获得当前的GlobalScope,然后再利用GlobalScope找到当前socket请求应该使用的WebScope(这个WebScope 由开发者在WEB-INF\red5-web.xml中定义)。最后,RTMPHandler会调用RTMPMinaConnection 的connect ()方法连接到相应的WebScope。
- 通常来说,WebScope又会将请求转移给ApplicationAdapter,由它来最终响应请求,而项目中通过重载ApplicationAdapter的方法来实现自己的逻辑。至此,控制流进入了开发者的项目中。一般在具体使用中多会使用ApplicationAdapter的父类MultiThreadedApplicationAdapter,因为它是Red5应用程序的基础类,并且支持多线程,性能上要优于Application(未验证)。它提供了操作SharedObjects 和 streams的方法,还有连接和服务列表,是一个应用程序基本的Iscope。它实现了IstreamAwareScopeHandler接口,提供了在应用程序种控制流的方法。它还提供了一个很有用的事件控制器,可以拦截流、授权用户访问等。可以在其子类中添加各种方法,还可在客户端上通过NetConnection.call()方法调用服务器端的方法。
ApplicationAdapter核心方法
- public boolean appStart(IScope app);Red5应用程序启动时自动执行此方法,可以再这里执行一些初始化操作。
- public void appStop(IScope arg0);Red5应用程序停止时自动执行此方法。
- public boolean appConnect(IConnection arg0, Object[] arg1);当客户端连接本应用程序时自执行此方法。
- public boolean appDisconnect(Iconnection conn);当客户端断开连接时自动执行此方法。(如关闭浏览器、关闭FLASH PLAYER等特殊情况,均会触发该方法)。
- public boolean join(IClient arg0, IScope arg1);当有新的连接加入进来时自动调用。
- public void leave(IClient arg0, IScope arg1);连接断开
- public void streamPublishStart(IBroadcastStream stream);开始发布直播
- public void streamSubscriberStart(ISubscriberStream stream);开始播放流
- public void streamBroadcastClose(IBroadcastStream arg0);流结束
- public void streamSubscriberStart(ISubscriberStream stream);用户播放流
- public void streamSubscriberClose(ISubscriberStream arg0) ;用户断开播放
主要API简介
- IConnection:连接对象。每个连接都有一个关联的客户端和域。连接可能是持续型、轮询型、或短暂型。建立此接口的目的,是为了给不同的子类,如 RTMPConnection,RemotingConnection,AJAXConnection, HttpConnection 等,提供基础通用的方法。它提供getClient()方法来获取客服端对象。
- IScope :每个Red5应用程序至少有一个域,用来搭建处理器、环景、服务器之间的连接。域可以构成树形结构,所有客户端都可以作为其节点共享域内的对象(比如流和数据)。所有的客服端(client)通过连接(connection)连接到域中。对于单一域,每个连接对应一个客服端,每个客服端对应一个id,简单的应用,操作就针对一个id和一个连接进行。
- IServiceCapableConnection :获取有效连接。代码中先获取到连接实例,然后判断是否是有效连接并强制类型转换,之后调取客户端相应函数。
- IClient :客户端对象代表某单一客户端。一个客户端可以和同一主机下不同的域分别建立连接。客户端对象和HTTP session 很相像。可以使用IClientRegistry.newClient(Object[])方法来创建IClient对象。
- ApplicationAdapter:ApplicationAdapter是应用层级的IScope。若要处理流进程,需实现 IStreamAwareScopeHandler接口中的相应处理方法。ApplicationAdapter还提供了有效的事件处理机制,来进行截取流、确认用户等操作。同时,其子类中引入的方法均可在客户端通过 NetConnection 调取。在Aodbe 的FMS 中必须在服务器端维护客户端对象,与之相较,Red5 为您的远程请求提供了更加方便快捷的操作方法。
了解了实现流程和主要方法API后下面就开始
检查修改red5-web.xml文件,检查路径是否正确
<bean id="web.handler" class="org.red5.core.Application" />
检查修改red5-web.properties文件,这个文件是配置contextPath和Host,根据自己的需要设置,我的设置如下
webapp.contextPath=/red5-live
webapp.virtualHosts=*
然后就是创建Application类集成ApplicationAdapter类,重写相关方法,是先自定义逻辑
package org.red5.examples.springmvc;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import org.red5.logging.Red5LoggerFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.stream.IBroadcastStream;
import org.red5.server.api.stream.IServerStream;
//import org.slf4j.Logger;
import org.red5.server.api.stream.ISubscriberStream;
import org.springframework.stereotype.Component;
/**
* Sample application that uses the client manager.
*
* @author The Red5 Project (red5@osflash.org)
*/
@Component
public class Application extends MultiThreadedApplicationAdapter {
//private static Logger log = Red5LoggerFactory.getLogger(Application.class);
/** {@inheritDoc} */
@Override
public boolean connect(IConnection conn, IScope scope, Object[] params) {
//log.info("appConnect");
System.out.println("***********************************red5 connection...");
return true;
}
/** {@inheritDoc} */
@Override
public void disconnect(IConnection conn, IScope scope) {
//log.info("disconnect");
System.out.println("red5 disconnect...");
super.disconnect(conn, scope);
}
// 连接时触发的函数,定义本过程中的username
/**
* 开始发布直播
*/
@Override
public void streamPublishStart(IBroadcastStream stream) {
System.out.println("[streamPublishStart]********** ");
System.out.println("发布Key: " + stream.getPublishedName());
}
/**
* 流结束
*/
@Override
public void streamBroadcastClose(IBroadcastStream arg0) {
System.out.println("streamBroadcastClose...");
super.streamBroadcastClose(arg0);
}
/**
* 用户断开播放
*/
@Override
public void streamSubscriberClose(ISubscriberStream arg0) {
System.out.println("streamBroadcastClose..."+arg0.getConnection().getSessionId()+" "+arg0.getBroadcastStreamPublishName());
super.streamSubscriberClose(arg0);
}
/**
* 链接rtmp服务器
*/
@Override
public boolean appConnect(IConnection arg0, Object[] arg1) {
// TODO Auto-generated method stub
System.out.println("[appConnect]********** ");
System.out.println("请求域:" + arg0.getScope().getContextPath());
System.out.println("id:" + arg0.getClient().getId());
System.out.println("name:" + arg0.getClient().getId());
System.out.println("**************** ");
Map<String, Object> hm = arg0.getConnectParams();
String host = (String) hm.get("pageUrl");
System.out.println("**************host:"+host);
String allowHost = "http://localhost:18082/red5/";
boolean ret = false;
if(allowHost == null || (allowHost!=null&&allowHost!="")){
ret = true;
}else {
String[] args = allowHost.split(",");
ret = false;
if(host != null && (host!=null&&host!="") ){
for(int i=0;i<args.length;++i){
if(host.indexOf( args[i])>=0){
ret = true;
break;
}
}
}
}
if(ret){
return super.appConnect(arg0, arg1);
}else {
return false;
}
}
/**
* 加入了rtmp服务器
*/
@Override
public boolean join(IClient arg0, IScope arg1) {
// TODO Auto-generated method stub
System.out.println("**************** 加入了RTMP服务器");
return super.join(arg0, arg1);
}
/**
* 开始播放流
*/
@Override
public void streamSubscriberStart(ISubscriberStream stream) {
System.out.println("[streamSubscriberStart]********** ");
System.out.println("播放域:" + stream.getScope().getContextPath());
System.out.println("播放Key:" + stream.getBroadcastStreamPublishName());
System.out.println("********************************* ");
String sessionId = stream.getConnection().getSessionId();
stream.getConnection().setAttribute(null, null);
System.out.println("sessionId"+sessionId);
String path = stream.getConnection().getPath();
String address = stream.getConnection().getRemoteAddress();
System.out.println("address"+address);
System.out.println("path"+path);
String param = (String) stream.getConnection().getConnectParams().get("queryString");
System.out.println("param"+param);
System.out.println("********************************* "+stream.getConnection().getHost());
super.streamSubscriberStart(stream);
}
/**
* 离开了rtmp服务器
*/
@Override
public void leave(IClient arg0, IScope arg1) {
System.out.println("*****************"+arg0.getCreationTime());
System.out.println("leave");
super.leave(arg0, arg1);
}
@Override
public boolean appStart(IScope app) {
System.out.println("*****************app启动");
return super.appStart(app);
}
}
下面就是启动服务器进行测试
相关信息都打印到了控制台,说明自定义的逻辑已经成功执行!
以上就是Red5服务器实现自定义逻辑的方法,下一篇介绍使用Eclipse将普通Java项目转为Maven项目,并集成Spring MVC