Red5+SpringMVC整合(RTMP+HTTP)搭建你的直播服务器
2017-04-10 11:22
302 查看
基本环境
Eclipse
Eclipse Java EE IDE for Web Developers.Version: Neon.3 Release (4.6.3)
Build id: 20170314-15
4000
00
地址:https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/neon/3/eclipse-jee-neon-3-win32-x86_64.zip
RED5 Server
我这里用的是 Red5 Server 1.0.9地址:https://github.com/Red5/red5-server/releases
解压server包,得到server目录
此时我们可以双击red5.bat,看看是否可以运行,如果失败,通常问题是提示jvm版本问题。
我这里用的是jdk1.8 64bit
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b15, mixed mode)
RED5-Eclipse-Plugin
地址:https://github.com/Red5/red5-eclipse-plugin插件的安装方法就不赘述了
插件有一个问题就是在安装后,创建项目新建server的时候会要求指向server目录,其中自动匹配red5.sh,这里是sh,我们是win平台
sh肯定是运行不了的。手动改成bat会无法进行下一步!我这个IDE是这样的或许你没事呢
![](http://static.blog.csdn.net/xheditor/xheditor_emot/default/titter.gif)
我们改一下他的插件
1. 导入插件到eclipse
2. 选择 org.leagueplanet.server.glassfish 项目
3. 打开red5.serverdef
4. 搜.sh
5. 把red5-debug.sh red5.-shutdown.sh 改为 .bat 结尾即可
这样下来,在配置server路径的时候我们把 .sh 改为 .bat 就不会有错误提示,也不会无法点下一步了!
开始搭建
项目创建
创建一个Dynamic Web Project 项目Project name: liveOnline
target runtime 选择 new runtime
Infrared5 下选择 red5 server, next
red5 Runtime 配置
选择jdk1.8 ,把red5目录指向,我们解压的red5 server文件夹配置red5 server,端口我选的默认,这里看红色框中默认是.sh 我们改为 bat后也依然可以next
![](http://static.blog.csdn.net/xheditor/xheditor_emot/default/laugh.gif)
回到创建project页面我们继续进行配置,自定义修改项目配置
勾选red5 application generation
点击完成项目创建
看项目列表,我们不仅得到了red5的项目结构,还得到了附赠的client测试端
.
测试RED5 server
我们先去server标签中启动red5服务,先跑一个空服务看看red5 server是否可以正确启动启动如果报错,说明路径有问题
启动成功后,访问 http://127.0.0.1:5080
下面是red5 启动成功的欢迎界面,如果没有这个界面说明red5 启动报错,仔细查查吧,通常是路径配置问题,如果提示不是有效的win32程序,则是路径配置中没有修改.sh .bat 的主程序指向。
red5-web.properties
在eclipse 中我们打开 liveOnline中 red5-web.propertieswebapp.virtualHosts属性表示了访问控制,默认red5给加了一个 192的ip,如果你的内网IP和它不同你可以修改或者直接改为 * (星号)
red5-web.xml
<bean id="web.handler" class="org.red5.core.Application" /> 可以看到这里配置了application.java 来得到red5的各种事件状态,当然这个org.red5.core.application已经被自动创建了,我们可以自己修改。web.xml 没什么好说的,暂时不去改它。
基本通讯
下面我们先尝试一下这个 liveOnline 能否完成基本的rtmp通讯我们将项目add到 red5 server,然后右键 publish
然后debug或start启动
打开http://127.0.0.1:5080/demos/publisher.html ,这是red5 提供的一个测试flex 可以完成推流拉流操作。
我们在location一行中,输入我们的项目名,再点击Connect,观察右侧console
提示
- Connecting to rtmp://localhost/liveOnline
- NetConnection.Connect.Success
证明已经成功通讯
下面我们可以切换到Video标签选择自己的摄像头,然后点start
之后修改name为9800(这个不过是一个rtmp的通讯key ,key key对应则建立推拉的都是一个流),之后选择发布,则已经开始直播了,
直播地址就是 rtmp://{ip}//liveOnlive ,key 就是输入的9800,当然有的地方叫做 filename
我们可以选择view来观看自己的直播,切换到view界面在name中改为9800,然后点击play即可
关于如何关闭red5 server
从eclipse server标签中关闭要stop好多次才可以成功,取个巧的办法是从任务管理器中删除java.exe 进程完善red5项目
我们先建立一下文件目录在liveOnline修改red5-web.properties 中webapp.virtualHosts 为 *
修改red5-web.xml 中 <bean id="web.handler" class="com.service.Application" />
package com.state; /** * 临时容器 * @author Allen 2017年3月31日 * */ import java.util.HashMap; import com.state.room.RoomVo; public class Ram { public static HashMap<String, RoomVo> roomHm = new HashMap<>(); }
package com.state.user; public class UserVo implements java.io.Serializable { /** * */ private static final long serialVersionUID = -6628674875994109212L; private String red5Id; private String red5Name; private Long red5CreateTime; public String getRed5Id() { return red5Id; } public void setRed5Id(String red5Id) { this.red5Id = red5Id; } public String getRed5Name() { return red5Name; } public void setRed5Name(String red5Name) { this.red5Name = red5Name; } public Long getRed5CreateTime() { return red5CreateTime; } public void setRed5CreateTime(Long red5CreateTime) { this.red5CreateTime = red5CreateTime; } }
package com.state.user; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.List; import com.state.Ram; /** * 用户操作 * * @author Allen 2017年3月31日 * */ public class UserState { /** * 插入用户到房间中 * @param red5Id * @param red5Name * @param red5CreateTime * @param roomKey * @return */ public boolean insert(String red5Id, String red5Name, Long red5CreateTime, String roomKey) { try { if (Ram.roomHm.containsKey(roomKey)) { UserVo uvo = new UserVo(); uvo.setRed5Id(red5Id); uvo.setRed5Name(red5Name); uvo.setRed5CreateTime(red5CreateTime); Ram.roomHm.get(roomKey).getUserList().add(uvo); return true; } selectAll(roomKey); } catch (Exception e) { e.printStackTrace(); } return false; } /** * 获取房间中全部用户 * @param roomKey * @return */ public List<UserVo> selectAll(String roomKey) { try { if (Ram.roomHm.containsKey(roomKey)) { Iterator<UserVo> it = Ram.roomHm.get(roomKey).getUserList().iterator(); System.out.println("================================================"); while (it.hasNext()) { UserVo temp = it.next(); System.out.println(temp.getRed5Id() + "," + temp.getRed5Name() + "," + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(temp.getRed5CreateTime()))); } System.out.println("================================================"); } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 删除房间中某用户 * @param redId * @param roomKey * @return */ public boolean delete(String redId, String roomKey) { try { if (Ram.roomHm.containsKey(roomKey)) { Iterator<UserVo> it = Ram.roomHm.get(roomKey).getUserList().iterator(); while (it.hasNext()) { if (it.next().getRed5Id().equals(redId)) { it.remove(); break; } } } return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * 统计房间中用户数 * @param roomKey * @return */ public int count(String roomKey) { return Ram.roomHm.containsKey(roomKey) ? Ram.roomHm.get(roomKey).getUserList().size() : 0; } }
package com.state.room; import java.util.ArrayList; import java.util.List; import com.state.user.UserVo; /** * 房间VO * * @author Allen 2017年3月31日 * */ public class RoomVo { private String roomKey;// 房间key private String roomName;// 房间名 private List<UserVo> userList=new ArrayList<>();// 房间内用户列表 public List<UserVo> getUserList() { return userList; } public void setUserList(List<UserVo> userList) { this.userList = userList; } public String getRoomKey() { return roomKey; } public void setRoomKey(String roomKey) { this.roomKey = roomKey; } public String getRoomName() { return roomName; } public void setRoomName(String roomName) { this.roomName = roomName; } }
package com.state.room; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import com.state.Ram; /** * 房间操作 * * @author Allen 2017年3月31日 * */ public class RoomState { /** * 创建一个房间信息 * * @param roomKey * @param roomName * @return */ public boolean insert(String roomKey, String roomName) { try { if (!Ram.roomHm.containsKey(roomKey)) { RoomVo rVo = new RoomVo(); rVo.setRoomName(roomName); Ram.roomHm.put(roomKey, rVo); return true; } } catch (Exception e) { e.printStackTrace(); } return false; } /** * 返回所有房间信息 * * @return */ public List<RoomVo> selectAll() { try { List<RoomVo> resultList = new ArrayList<>(); Iterator<Entry<String, RoomVo>> it = Ram.roomHm.entrySet().iterator(); while (it.hasNext()) { Entry<String, RoomVo> entry = it.next(); resultList.add(entry.getValue()); } return resultList; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 删除一个房间信息 * * @param red5Id * @return */ public boolean delete(String roomKey) { try { Ram.roomHm.remove(roomKey); return true; } catch (Exception e) { e.printStackTrace(); } return false; } /** * 统计房间总数 * * @return */ public int count() { return Ram.roomHm.size(); } }
package com.service; import java.text.SimpleDateFormat; import java.util.Date; 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.ISubscriberStream; import com.state.room.RoomState; import com.state.user.UserState; /** * * @author Allen 2017年4月7日 * */ public class Application extends MultiThreadedApplicationAdapter { @Override public boolean connect(IConnection conn) { System.out.println("connect"); return super.connect(conn); } @Override public void disconnect(IConnection arg0, IScope arg1) { System.out.println("disconnect"); new UserState().delete(arg0.getSessionId(), arg0.getAttribute(arg0.getSessionId()).toString()); super.disconnect(arg0, arg1); } /** * 开始发布直播 */ @Override public void streamPublishStart(IBroadcastStream stream) { System.out.println("[streamPublishStart]********** "); System.out.println("发布Key: " + stream.getPublishedName()); RoomState room = new RoomState(); room.insert(stream.getPublishedName(), "房间" + stream.getPublishedName()); System.out.println( "发布时间:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(stream.getCreationTime()))); System.out.println("****************************** "); } /** * 流结束 */ @Override public void streamBroadcastClose(IBroadcastStream arg0) { RoomState room = new RoomState(); room.delete(arg0.getPublishedName()); super.streamBroadcastClose(arg0); } /** * 用户断开播放 */ @Override public void streamSubscriberClose(ISubscriberStream arg0) { new UserState().delete(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("**************** "); return super.appConnect(arg0, arg1); } /** * 加入了rtmp服务器 */ @Override public boolean join(IClient arg0, IScope arg1) { // TODO Auto-generated method stub return super.join(arg0, arg1); } /** * 开始播放流 */ @Override public void streamSubscriberStart(ISubscriberStream stream) { S bfbd ystem.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); new UserState().insert(sessionId, sessionId, stream.getCreationTime(), stream.getBroadcastStreamPublishName()); super.streamSubscriberStart(stream); } /** * 离开了rtmp服务器 */ @Override public void leave(IClient arg0, IScope arg1) { System.out.println("leave"); super.leave(arg0, arg1); } }
重新发布启动后,如果新的application中的console没有打印,则可能是新版本发布失败。
我们删除 red5 server/ webapp / liveOnline 和 red5 server/ webapp /webapp / liveOnline 文件夹后再次在red5 server 中右键清理和发布
服务启动后我们再次使用 http://127.0.0.1:5080/demos/publisher.html 选择链接rtmp liveOnline服务
在这一步 eclipse console 如果提示 Scope not found 那就是web.handler 中class路径配错了
正确的话不仅会返回succss,而且eclipse console 会打印我们新建的application中的 System.out.print 信息
[appConnect]********** 请求域:/liveOnline id:1 name:1 ****************
SpringMVC
下面我们在red5项目中增加SpringMVC支持,来提供通过HTTP访问red5服务器内房间信息注意一下:Spring jar包我们放到WEB-INF/libs 中,然后引用它,这里如果使用默认的lib目录,最后发布red5+springMVC的时候red5的rtmp会无法访问,会抛出Scope not found!!!!虽然这个问题在google百度github都没有解释,但是我最后还是找到的解决方案!如果你用lib没有问题,那么只能说是系统环境了。。我用的是win7 64bit。linux或者mac或许没问题吧
首先我们把WEB-INF 下面的lib改成libs,如果没有lib新建一个libs即可
我们将spring的jar包拷贝到libs,为什么选择4.3.6因为我的red5 server下lib中的spring就是4.3.6的保持版本与red5一致!
Add JARS到项目
web.xml
我们在web.xml中增加<!-- ********************** Spring配置 ********************** --> <!-- 配置DispatchcerServlet --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置Spring mvc下的配置文件的位置和名称 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/springmvc.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <!-- 静态资源处理交给默认servlet --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.gif</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping>
springmvc.xml
新建一个springmvc.xml在WEB-INF目录下<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <!-- 配置@ResponseBody 保证返回值为UTF-8 --> <!-- 因为StringHttpMessageConverter默认是ISO8859-1 --> <bean id="utf8Charset" class="java.nio.charset.Charset" factory-method="forName"> <constructor-arg value="UTF-8" /> </bean> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"> <constructor-arg ref="utf8Charset" /> </bean> </mvc:message-converters> </mvc:annotation-driven> <!-- 配置自动扫描的包 --> <context:component-scan base-package="com.action"></context:component-scan> <!-- 配置视图解析器 如何把handler 方法返回值解析为实际的物理视图 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
测试类
package com.action.room; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.state.Ram; /** * * @author Allen 2017年4月10日 * */ @Controller public class room { @ResponseBody @RequestMapping("/roomsize") public String hello() { return "当前房间数: "+Ram.roomHm.size() ; } }
测试
重新发布重新启动red5 server访问:http://127.0.0.1:5080/liveOnline/roomsize
返回结果
再访问http://127.0.0.1:5080/demos/publisher.html
按照上面描述的方法,开启一个rtmp直播在liveOnline
我们再刷新roomsize发现房间数显示为1,然后我们关闭直播再刷新发现房间数返回为0。
结束
就到这里了red5提供rtmp服务,spring提供http服务,rtmp不仅可以传输stream还可以传输String,我们也可以用rtmp来获取项目中的房间数量,不过我认为还是通过http来管理这些请求比较好一些~相关文章推荐
- 利用nginx搭建http和rtmp协议的流媒体服务器
- 利用Nginx搭建http和rtmp协议的流媒体服务器
- nginx搭建支持http和rtmp协议的流媒体服务器之一
- 用nginx搭建http/rtmp/hls协议的MP4/FLV流媒体服务器
- 利用nginx搭建http和rtmp协议的流媒体服务器
- 搭建 Http Dynamic Streaming 点播/直播服务器
- rtmp直播服务器的搭建(小白踩坑)
- 利用Nginx搭建http和rtmp协议的流媒体服务器[转]
- nginx搭建支持http和rtmp协议的流媒体服务器之---环境搭建
- 利用nginx搭建http和rtmp协议的流媒体服务器
- 利用nginx搭建http和rtmp协议的流…
- 利用nginx搭建http和rtmp协议的流媒体服务器
- 利用nginx搭建http和rtmp协议的流媒体服务器
- 【转载】利用Nginx搭建http和rtmp协议的流媒体服务器
- nginx搭建支持http和rtmp协议的流媒体服务器之三
- 搭建 Http Dynamic Streaming 点播/直播服务器
- 利用nginx搭建http和rtmp协议的流媒体服务器
- nginx搭建支持http和rtmp协议的流媒体服务器之一
- 用nginx搭建基于rtmp或者http的flv、mp4流媒体服务器
- nginx搭建支持http和rtmp协议的流媒体服务器