src/main/java/com/hbbh/adapter/AdapterApplication.java
@@ -3,6 +3,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import springfox.documentation.swagger2.annotations.EnableSwagger2; @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class AdapterApplication { src/main/java/com/hbbh/adapter/config/Swagger2Conf.java
New file @@ -0,0 +1,49 @@ package com.hbbh.adapter.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2Conf extends WebMvcConfigurationSupport { @Bean public Docket getUserDocket(){ ApiInfo apiInfo=new ApiInfoBuilder() .title("Monica监控")//api标题 .description("Monica监控管理相关接口描述")//api描述 .version("1.0.0")//版本号 .contact("xc")//本API负责人的联系信息 .build(); return new Docket(DocumentationType.SWAGGER_2)//文档类型(swagger2) .apiInfo(apiInfo)//设置包含在json ResourceListing响应中的api元信息 .select()//启动用于api选择的构建器 .apis(RequestHandlerSelectors.basePackage("com.hbbh.adapter.controller"))//扫描接口的包 .paths(PathSelectors.any())//路径过滤器(扫描所有路径) .build(); } /** * 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源 * * @param registry */ public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations( "classpath:/static/"); registry.addResourceHandler("swagger-ui.html").addResourceLocations( "classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations( "classpath:/META-INF/resources/webjars/"); super.addResourceHandlers(registry); } } src/main/java/com/hbbh/adapter/controller/MonibucaController.java
@@ -3,13 +3,15 @@ import com.hbbh.adapter.controller.api.MonibucaAPI; import com.hbbh.adapter.manager.MonibucaManager; import com.hbbh.adapter.vo.CallResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import java.util.List; @CrossOrigin(origins = "*", maxAge = 3600) @Api @@ -23,14 +25,21 @@ MonibucaManager monibucaManager; @Override public String parseVideoRTSP() { @RequestMapping(value = "/test/video", method = RequestMethod.GET) @ApiOperation("test") public CallResult<String> parseVideoRTSP() { try { String result = monibucaManager.parseVideo(); CallResult<String> result = monibucaManager.parseVideo(); return result; } catch (Exception e) { log.error("接口:com.hbbh.adapter.controller.api.MonibucaAPIController.parseVideoRTSP 调用失败"); e.printStackTrace(); return "-1"; return null; } } @Override public void stopStream(String param) { } } src/main/java/com/hbbh/adapter/controller/api/MonibucaAPI.java
@@ -1,11 +1,22 @@ package com.hbbh.adapter.controller.api; import com.hbbh.adapter.vo.CallResult; /** * 注意: * 调用此接口需要启动Monica实例 */ public interface MonibucaAPI { /** * @return * -1 解析失败 * */ public String parseVideoRTSP(); public CallResult<String> parseVideoRTSP(); /** * @param param streamPath地址 * 示例: http://localhost:8081/api/stop?stream=live/test */ public void stopStream(String param); } src/main/java/com/hbbh/adapter/manager/MonibucaManager.java
@@ -1,7 +1,24 @@ package com.hbbh.adapter.manager; import com.hbbh.adapter.vo.CallResult; public interface MonibucaManager { // monica监控 String parseVideo(); /** * monica监控 * * @return 解析后的地址 * * 返回实例: * ws://127.0.0.1:8080/live/test.flv * * 讲解: * ws:// jessibuca格式开头 * 127.0.0.1 Monica实例启动地址 * :8080 启动Monica实例配置端口 * live/test StreamPath 是发布流的唯一标识,我们可以把它理解为网页的 url ,它是流传输服务中的 url 。你要发布一个流,就需要一个 StreamPath ,这里类比后端服务的发布,需要 ip 和对外暴露的端口一起组成一个 url 。 * .flv 视频流格式 * */ CallResult<String> parseVideo(); } src/main/java/com/hbbh/adapter/manager/impl/MonibucaManagerImpl.java
@@ -1,9 +1,15 @@ package com.hbbh.adapter.manager.impl; import com.alibaba.fastjson.JSON; import com.google.common.collect.Maps; import com.hbbh.adapter.manager.MonibucaManager; import com.hbbh.adapter.utils.HttpClientUtil; import com.hbbh.adapter.vo.CallResult; import com.hbbh.adapter.vo.MessageVO; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.util.LinkedHashMap; import java.util.Map; @@ -11,19 +17,37 @@ public class MonibucaManagerImpl implements MonibucaManager { //入参 rtsp视频流 private String rtspDemo="rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"; //返回结果,通过配置文件即可反推得到 前提是调用 rtsp/pull 接口成功 private String jessibucaDemo="ws://localhost:8080/live/test.flv"; // =========== monica监控 =========== /** * 调用Gateway API * 成功返回 {"code":0} * 失败返回 {"code":1,"msg":"publish badname"} * @return jessibucaDemo */ @Override public String parseVideo() { public CallResult<String> parseVideo() { String url="http://localhost:8081/rtsp/pull"; Map<String, String> params = Maps.newLinkedHashMap(); params.put("target",rtspDemo); params.put("streamPath","live/test"); //TODO:解析rtsp视频 return null; String result = HttpClientUtil.doGet(url, params); Map map = JSON.parseObject(result, Map.class); String code = map.get("code")+""; String msg = map.get("msg")+""; if (!StringUtils.equals("0",code)){ CallResult.success(msg,"-1"); } return CallResult.success(jessibucaDemo); } } src/main/java/com/hbbh/adapter/utils/HttpClientUtil.java
New file @@ -0,0 +1,104 @@ package com.hbbh.adapter.utils; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; //httpclientutil 工具类 public class HttpClientUtil { public static String doGet(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); String resultString = ""; CloseableHttpResponse response = null; try { // 创建uri URIBuilder builder = new URIBuilder(url); if (param != null) { for (String key : param.keySet()) { builder.addParameter(key, param.get(key)); } } URI uri = builder.build(); // 创建http GET请求 HttpGet httpGet = new HttpGet(uri); // 执行请求 response = httpclient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (response != null) { response.close(); } httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } public static String doGet(String url) { return doGet(url, null); } public static String doPost(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建参数列表 if (param != null) { List<NameValuePair> paramList = new ArrayList<NameValuePair>(); for (String key : param.keySet()) { paramList.add(new BasicNameValuePair(key, param.get(key))); } // 模拟表单 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList,"utf-8"); httpPost.setEntity(entity); } // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return resultString; } public static String doPost(String url) { return doPost(url, null); } } src/main/java/com/hbbh/adapter/vo/CallResult.java
New file @@ -0,0 +1,81 @@ package com.hbbh.adapter.vo; import lombok.Data; import java.io.Serializable; /** * 消息体 * @param <T> */ @Data public class CallResult<T> implements Serializable { //private static final long serialVersionUID = 3213383838022375087L; public static String SUCCESS = "SUCCESS"; /** * 返回代码 */ private String code; /** * 返回结果描述 */ private String message; /** * 返回的具体数据 */ private T data; @Override public String toString() { return "MessageVO{" + "code='" + code + '\'' + ", message='" + message + '\'' + ", data=" + data + '}'; } /** * 指定返回代码、消息和数据对象创建 * @param code * @param message * @param data */ public CallResult(String code, String message, T data){ this.setCode(code); this.setMessage(message); this.setData(data); } /** * 成功 * @return */ public static <E> CallResult<E> success(){ return new CallResult<E>(MessageVO.SUCCESS, null, null); } /** * 成功的结果 * @param data * @return */ public static <E> CallResult<E> success(E data){ return new CallResult<E>(MessageVO.SUCCESS, null, data); } /** * 成功的结果 * @param message 返回的消息 * @param data * @return */ public static <E> CallResult<E> success(String message, E data){ return new CallResult<E>(MessageVO.SUCCESS, message, data); } } src/main/java/com/hbbh/adapter/vo/MessageVO.java
New file @@ -0,0 +1,44 @@ package com.hbbh.adapter.vo; import java.io.Serializable; public class MessageVO implements Serializable { private static final long serialVersionUID = 5717188735020653310L; public static String SUCCESS = "SUCCESS"; private String code; private String message; private Object data; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } @Override public String toString() { return "MessageVO{" + "code='" + code + '\'' + ", message='" + message + '\'' + ", data=" + data + '}'; } } src/test/java/com/hbbh/adapter/HttpClientUtilTest.java
New file @@ -0,0 +1,207 @@ package com.hbbh.adapter; import com.google.common.collect.Maps; import com.hbbh.adapter.utils.HttpClientUtil; import org.junit.jupiter.api.Test; import java.util.Map; public class HttpClientUtilTest { /** * GET /api/instance/create?path=E:\store\live&name=live&info=%5BMonibuca%5D%0A%23%20%E6%98%AF%E5%90%A6%E7%AD%89%E5%BE%85%E6%B5%81%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%BAtrue%E5%88%99%E8%AE%A2%E9%98%85%E4%B8%80%E4%B8%AA%E5%B0%9A%E6%9C%AA%E5%8F%91%E5%B8%83%E7%9A%84%E6%B5%81%E4%BC%9A%E8%BF%9B%E5%85%A5%E7%AD%89%E5%BE%85%E5%8F%91%E5%B8%83%E7%9A%84%E7%8A%B6%E6%80%81%EF%BC%8C%E5%90%A6%E5%88%99%E8%BF%94%E5%9B%9E%E8%AE%A2%E9%98%85%E5%A4%B1%E8%B4%A5%0AEnableWaitStream%20%3D%20true%0AEnableAudio%20%3D%20true%0AEnableVideo%20%3D%20true%0A%23%20%E7%BC%93%E5%86%B2%E7%8E%AF%E5%A4%A7%E5%B0%8F%E9%BB%98%E8%AE%A4%E6%98%AF2%E7%9A%8410%E6%AC%A1%E6%96%B9%0ARingSize%20%3D%2010%0A%23%20%E5%8F%91%E5%B8%83%E6%B5%81%E9%BB%98%E8%AE%A4%E8%BF%87%E6%9C%9F%E6%97%B6%E9%97%B4%201%E5%88%86%E9%92%9F%0APublishTimeout%20%3D%2060000000000%0A%5BRTSP%5D%0A%23%20%E7%AB%AF%E5%8F%A3%E6%8E%A5%E6%94%B6%E6%8E%A8%E6%B5%81%0AListenAddr%20%3D%20%22%3A554%22%0A%23%20%E5%BC%80%E5%90%AF%E8%87%AA%E5%8A%A8%E6%8B%89%E6%B5%81%E5%90%8E%EF%BC%8C%E4%B8%80%E6%97%A6%E6%9C%89%E8%AE%A2%E9%98%85%E6%B5%81%EF%BC%8C%E5%B0%B1%E4%BC%9A%E4%BB%8ERemoteAddr%E8%BF%9B%E8%A1%8C%E6%8B%89%E6%B5%81%0AAutoPull%20%3D%20false%0AReconnect%20%3D%20true%0A%23%20%E8%BF%9C%E7%A8%8B%E5%9C%B0%E5%9D%80%E9%85%8D%E5%90%88AutoPull%E9%85%8D%E7%BD%AE%E8%B5%B7%E4%BD%9C%E7%94%A8%0ARemoteAddr%20%3D%20%22rtsp%3A%2F%2Fadmin%3Aadmin%40192.168.1.212%3A554%2Fcam%2Frealmonitor%3Fchannel%3D1%26subtype%3D1%22%0A%23%20%E5%8F%91%E5%B8%83%E6%B5%81%E7%9A%84%E5%90%8D%E7%A7%B0%E9%85%8D%E5%90%88AutoPull%E9%85%8D%E7%BD%AE%E8%B5%B7%E4%BD%9C%E7%94%A8%20%E8%BF%9C%E7%A8%8B%E6%8B%89%E6%B5%81%E5%90%8E%E5%9C%A8%E6%9C%AC%E5%9C%B0%E8%B5%B7%E7%9A%84%E6%B5%81%E5%90%8D%E7%A7%B0%0AStreamPath%20%3D%20%22live%2Frtsp%22%0A%23%5B%5BRTSP.AutoPullList%5D%5D%0A%23URL%20%3D%20%22rtsp%3A%2F%2Fadmin%3Aadmin%40192.168.1.212%3A554%2Fcam%2Frealmonitor%3Fchannel%3D1%26subtype%3D1%22%0A%23StreamPath%20%3D%20%22live%2Frtsp2%22&clear=true HTTP/1.1 * Host: 192.168.1.109:3000 * Connection: keep-alive * Pragma: no-cache * Cache-Control: no-cache * Accept: text/event-stream * User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 * Referer: http://192.168.1.109:3000/ * Accept-Encoding: gzip, deflate * Accept-Language: zh-CN,zh;q=0.9 * * path: E:\store\live * name: live * info: [Monibuca] * # 是否等待流,如果为true则订阅一个尚未发布的流会进入等待发布的状态,否则返回订阅失败 * EnableWaitStream = true * EnableAudio = true * EnableVideo = true * # 缓冲环大小默认是2的10次方 * RingSize = 10 * # 发布流默认过期时间 1分钟 * PublishTimeout = 60000000000 * [RTSP] * # 端口接收推流 * ListenAddr = ":554" * # 开启自动拉流后,一旦有订阅流,就会从RemoteAddr进行拉流 * AutoPull = false * Reconnect = true * # 远程地址配合AutoPull配置起作用 * RemoteAddr = "rtsp://admin:admin@192.168.1.212:554/cam/realmonitor?channel=1&subtype=1" * # 发布流的名称配合AutoPull配置起作用 远程拉流后在本地起的流名称 * StreamPath = "live/rtsp" * #[[RTSP.AutoPullList]] * #URL = "rtsp://admin:admin@192.168.1.212:554/cam/realmonitor?channel=1&subtype=1" * #StreamPath = "live/rtsp2" * clear: true */ private String info="[Monibuca]\n" + "# 是否等待流,如果为true则订阅一个尚未发布的流会进入等待发布的状态,否则返回订阅失败\n" + "EnableWaitStream = true\n" + "EnableAudio = true\n" + "EnableVideo = true\n" + "# 缓冲环大小默认是2的10次方\n" + "RingSize = 10\n" + "# 发布流默认过期时间 1分钟\n" + "PublishTimeout = 60000000000\n" + "[RTMP]\n" + "ListenAddr = \":1935\"\n" + "[GateWay]\n" + "ListenAddr = \":8081\"\n" + "[Jessica]\n" + "ListenAddr = \":8080\"\n" + "[LogRotate]\n" + "# 日志存储目录相对或绝对\n" + "Path = \"logs\"\n" + "# 日志是否按大小分割,0表示不按大小分割,非零代表按该大小字节进行分割\n" + "Size = 0\n" + "Days = 1\n" + "[Cluster]\n" + "# 监听端口代表该服务器为源服务器\n" + "ListenAddr = \":2019\"\n" + "# 源服务器地址,用于向源服务器进行推或拉流\n" + "OriginServer = \"\"\n" + "# 推送模式,true表示如果此服务器有发布流,就会推送到源服务器,否则表示拉模式,即如果此服务器有订阅流则从源服务器拉流\n" + "Push = true\n" + "[HLS]\n" + "# 是否开启写磁盘,开启后侦测到发布流就会开始写TS文件\n" + "EnableWrite = false\n" + "# 是否打开内存模式,在内存中保留TS数据,方便直接读取\n" + "EnableMemory = false\n" + "# 分片大小 单位秒\n" + "Fragment = 10\n" + "# 窗口数里,代表一个m3u8文件里面有几个ts\n" + "Window = 2\n" + "# ts文件存放目录,m3u8会存放在上一级\n" + "Path = \"resource\"\n" + "[HDL]\n" + "ListenAddr = \":2020\"\n" + "[TS]\n" + "# 是否自动发布,开启后一旦有订阅流就会读取ts文件进行发布,方便测试\n" + "AutoPublish = false\n" + "# ts存放目录\n" + "Path = \"resource\"\n" + "[Record]\n" + "Path = \"resource\"\n" + "# 是否自动发布,开启后一旦有订阅流就会读取flv文件进行发布,方便测试\n" + "AutoPublish = false\n" + "# 自动录制功能\n" + "AutoRecord = false\n" + "[RTSP]\n" + "ListenAddr = \":554\"\n" + "AutoPull = true\n" + "Reconnect = true\n" + "RemoteAddr = \"rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov\"\n" + "StreamPath = \"live/rtsp\"\n" + "#[[RTSP.AutoPullList]]\n" + "#URL = \"rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov\"\n" + "#StreamPath = \"live/rtsp2\"\n" + "[WebRTC]\n" + "# 公网IP地址\n" + "PublicIP = [\"127.0.0.1\"]\n" + "# 端口范围不配置的话是自动分配\n" + "# PortMin = 30000\n" + "# PortMax = 40000\n" + "[GB28181]\n" + "Serial = \"34020000002000000001\"\n" + "Realm = \"3402000000\"\n" + "Expires = 3600\n" + "AutoInvite = false\n" + "ListenAddr = \"192.168.1.120:5060\""; @Test void init() { String result = createInstance(); if (result.contains("success")){ startInstance(); }else { //创建实例失败 System.out.println("创建实例失败"); } } /** * 创建实例 * GET */ private String createInstance(){ String url="http://127.0.0.1:3000/api/instance/create"; Map<String, String> params = Maps.newLinkedHashMap(); params.put("path","/Users/xucheng/Downloads/store/live"); params.put("name","live"); params.put("info",info); params.put("clear","true"); String result = HttpClientUtil.doGet(url, params); return result; } /** * 启动实例 * POST * http://localhost:3000/api/instance/start?name=live */ private void startInstance(){ String url="http://localhost:3000/api/instance/start?name=live"; String result = HttpClientUtil.doPost(url); System.out.println(result); } /** * 放入rtsp链接 * GET * http://localhost:8081/rtsp/pull? * target=rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov& * streamPath=live/test * * * return :http://localhost:2020/live/test1.flv */ private void pullRTSPURL(){ String url="http://localhost:8081/rtsp/pull"; Map<String, String> params = Maps.newLinkedHashMap(); /* params.put("target","rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"); params.put("streamPath","live/test"); HttpClientUtil.doGet(url,params); params.clear(); params.put("target","rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"); params.put("streamPath","live/test1"); */ for (int i = 0; i < 10; i++) { params.put("target","rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"); params.put("streamPath","live/test"+i); String result = HttpClientUtil.doGet(url, params); if (result.contains("0")){ }else { System.out.println("第"+i+"次创建失败"); } params.clear(); } } @Test public void pullRTSP(){ String url="http://localhost:8081/rtsp/pull"; Map<String, String> params = Maps.newLinkedHashMap(); params.put("target","rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"); params.put("streamPath","live/test"); String result = HttpClientUtil.doGet(url, params); System.out.println(result); } }