pom.xml
@@ -145,16 +145,19 @@ </dependency> <dependency> <groupId>org.mitre.dsmiley.httpproxy</groupId> <artifactId>smiley-http-proxy-servlet</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency> <!-- okhttp --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.9.0</version> </dependency> </dependencies> <build> src/main/java/com/genersoft/iot/vmp/gb28181/bean/DeviceChannel.java
@@ -143,6 +143,11 @@ */ private String ssrc; /** * 是否含有音频 */ private boolean hasAudio; public String getChannelId() { return channelId; } @@ -371,4 +376,16 @@ public void setSubCount(int subCount) { this.subCount = subCount; } public void setPTZTypeText(String PTZTypeText) { this.PTZTypeText = PTZTypeText; } public boolean isHasAudio() { return hasAudio; } public void setHasAudio(boolean hasAudio) { this.hasAudio = hasAudio; } } src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -241,13 +241,6 @@ StreamInfo streamInfo = new StreamInfo(); streamInfo.setSsrc(ssrc); // String streamId = Integer.toHexString(Integer.parseInt(streamInfo.getSsrc())); // String streamId = String.format("%08x", Integer.parseInt(streamInfo.getSsrc())).toUpperCase(); // ZLM 要求大写且首位补零 // streamInfo.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId)); // streamInfo.setWS_FLV(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId)); // streamInfo.setRTMP(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtmpPort(), streamId)); // streamInfo.setHLS(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getLocalIP(), mediaInfo.getHttpPort(), streamId)); // streamInfo.setRTSP(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getLocalIP(), mediaInfo.getRtspPort(), streamId)); streamInfo.setCahnnelId(channelId); streamInfo.setDeviceID(device.getDeviceId()); storager.startPlay(streamInfo); src/main/java/com/genersoft/iot/vmp/gb28181/transmit/request/impl/MessageRequestProcessor.java
@@ -200,6 +200,7 @@ deviceChannel.setLongitude(itemDevice.element("Longitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Longitude"))); deviceChannel.setLatitude(itemDevice.element("Latitude") == null? 0.00:Double.parseDouble(XmlUtil.getText(itemDevice,"Latitude"))); deviceChannel.setPTZType(itemDevice.element("PTZType") == null? 0:Integer.parseInt(XmlUtil.getText(itemDevice,"PTZType"))); deviceChannel.setHasAudio(false); // 默认含有音频为false storager.updateChannel(device.getDeviceId(), deviceChannel); } // 更新 src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHTTPProxyController.java
@@ -2,7 +2,6 @@ import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import org.apache.http.HttpResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -14,6 +14,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -44,7 +45,13 @@ @Autowired private IVideoManagerStorager storager; @Value("${media.ip}") private String mediaIp; @Value("${media.port}") private int mediaPort; /** * 流量统计事件,播放器或推流器断开时并且耗用流量超过特定阈值时会触发此事件,阈值通过配置文件general.flowThreshold配置;此事件对回复不敏感。 * @@ -308,6 +315,7 @@ // List<MediaServerConfig> mediaServerConfigs = JSON.parseArray(JSON.toJSONString(json), MediaServerConfig.class); // MediaServerConfig mediaServerConfig = mediaServerConfigs.get(0); MediaServerConfig mediaServerConfig = JSON.toJavaObject(json, MediaServerConfig.class); mediaServerConfig.setLocalIP(mediaIp); storager.updateMediaInfo(mediaServerConfig); // TODO Auto-generated method stub src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java
New file @@ -0,0 +1,152 @@ package com.genersoft.iot.vmp.media.zlm; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @Component @Order(value=1) public class ZLMRunner implements CommandLineRunner { private final static Logger logger = LoggerFactory.getLogger(ZLMRunner.class); @Autowired private IVideoManagerStorager storager; @Value("${media.ip}") private String mediaIp; @Value("${media.port}") private int mediaPort; @Value("${media.secret}") private String mediaSecret; @Value("${sip.ip}") private String sipIP; @Value("${server.port}") private String serverPort; @Override public void run(String... strings) throws Exception { // 获取zlm信息 logger.info("等待zlm接入..."); MediaServerConfig mediaServerConfig = getMediaServerConfig(); if (mediaServerConfig != null) { logger.info("zlm接入成功..."); storager.updateMediaInfo(mediaServerConfig); logger.info("设置zlm..."); saveZLMConfig(); } } public MediaServerConfig getMediaServerConfig() { MediaServerConfig mediaServerConfig = null; OkHttpClient client = new OkHttpClient(); String url = String.format("http://%s:%s/index/api/getServerConfig?secret=%s", mediaIp, mediaPort, mediaSecret); //创建一个Request Request request = new Request.Builder() .get() .url(url) .build(); //通过client发起请求 final Call call = client.newCall(request); //执行同步请求,获取Response对象 Response response = null; try { response = call.execute(); if (response.isSuccessful()) { String responseStr = response.body().string(); if (responseStr != null) { JSONObject responseJSON = JSON.parseObject(responseStr); JSONArray data = responseJSON.getJSONArray("data"); if (data != null && data.size() > 0) { mediaServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), MediaServerConfig.class); mediaServerConfig.setLocalIP(mediaIp); } } }else { logger.error("getMediaServerConfig失败, 1s后重试"); Thread.sleep(1000); getMediaServerConfig(); } } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } return mediaServerConfig; } private void saveZLMConfig() { String hookIP = sipIP; if (mediaIp.equals(sipIP)) { hookIP = "127.0.0.1"; } OkHttpClient client = new OkHttpClient(); String url = String.format("http://%s:%s/index/api/setServerConfig", mediaIp, mediaPort); String hookPrex = String.format("http://%s:%s/index/hook", hookIP, serverPort); RequestBody body = new FormBody.Builder() .add("secret",mediaSecret) .add("hook.enable","1") .add("hook.on_flow_report","") .add("hook.on_http_access","") .add("hook.on_publish",String.format("%s/on_publish", hookPrex)) .add("hook.on_record_mp4","") .add("hook.on_record_ts","") .add("hook.on_rtsp_auth","") .add("hook.on_rtsp_realm","") .add("hook.on_server_started",String.format("%s/on_server_started", hookPrex)) .add("hook.on_shell_login",String.format("%s/on_shell_login", hookPrex)) .add("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrex)) .add("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrex)) .add("hook.timeoutSec","20") .build(); Request request = new Request.Builder() .post(body) .url(url) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { logger.error("saveZLMConfig ",e); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String responseStr = response.body().string(); if (responseStr != null) { JSONObject responseJSON = JSON.parseObject(responseStr); if (responseJSON.getInteger("code") == 0) { logger.info("设置zlm成功"); }else { logger.info("设置zlm失败: " + responseJSON.getString("msg")); } } } } }); } } src/main/java/com/genersoft/iot/vmp/storager/redis/VideoManagerRedisStoragerImpl.java
@@ -344,6 +344,7 @@ */ @Override public boolean stopPlay(StreamInfo streamInfo) { if (streamInfo == null) return false; return redis.del(String.format("%S_%s_%s_%s", VideoManagerConstants.PLAYER_PREFIX, streamInfo.getSsrc(), streamInfo.getDeviceID(), src/main/java/com/genersoft/iot/vmp/vmanager/device/DeviceController.java
@@ -144,4 +144,10 @@ PageResult pageResult = storager.querySubChannels(deviceId, channelId, query, channelType, online, page, count); return new ResponseEntity<>(pageResult,HttpStatus.OK); } @PostMapping("channel/update/{deviceId}") public ResponseEntity<PageResult> updateChannel(@PathVariable String deviceId,DeviceChannel channel){ storager.updateChannel(deviceId, channel); return new ResponseEntity<>(null,HttpStatus.OK); } } src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java
@@ -67,6 +67,7 @@ cmder.streamByeCmd(ssrc); StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); if (streamInfo == null) return new ResponseEntity<String>(HttpStatus.PAYMENT_REQUIRED); storager.stopPlay(streamInfo); if (logger.isDebugEnabled()) { logger.debug(String.format("设备预览停止API调用,ssrc:%s", ssrc)); src/main/resources/application.yml
@@ -25,7 +25,6 @@ server: port: 18080 sip: # ip: 10.200.64.63 ip: 192.168.1.20 port: 5060 # 根据国标6.1.2中规定,domain宜采用ID统一编码的前十位编码。国标附录D中定义前8位为中心编码(由省级、市级、区级、基层编号组成,参照GB/T 2260-2007) @@ -38,4 +37,10 @@ auth: #32位小写md5加密(默认密码为admin) username: admin password: 21232f297a57a5a743894a0e4a801fc3 password: 21232f297a57a5a743894a0e4a801fc3 media: #zlm服务器的ip与http端口, 重点: 这是http端口 ip: 192.168.1.20 port: 9080 secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc web_src/src/components/channelList.vue
@@ -31,10 +31,19 @@ <el-table ref="channelListTable" :data="deviceChannelList" :height="winHeight" border style="width: 100%"> <el-table-column prop="channelId" label="通道编号" width="210"> </el-table-column> <el-table-column prop="name" label="通道名称" width="500"> <el-table-column prop="name" label="通道名称"> </el-table-column> <el-table-column prop="subCount" label="子节点数"> </el-table-column> <el-table-column label="开启音频" align="center"> <template slot-scope="scope"> <el-switch @change="updateChannel(scope.row)" v-model="scope.row.hasAudio" active-color="#409EFF"> </el-switch> </template> </el-table-column> <el-table-column label="状态" width="180" align="center"> <template slot-scope="scope"> <div slot="reference" class="name-wrapper"> @@ -193,6 +202,7 @@ }, //通知设备上传媒体流 sendDevicePush: function(itemData) { console.log(itemData) let deviceId = this.deviceId; this.isLoging = true; let channelId = itemData.channelId; @@ -204,7 +214,7 @@ }).then(function(res) { let ssrc = res.data.ssrc; that.isLoging = false that.$refs.devicePlayer.play(res.data,deviceId,channelId); that.$refs.devicePlayer.play(res.data,deviceId,channelId,itemData.hasAudio); }).catch(function(e) { }); }, @@ -256,6 +266,16 @@ this.currentPage = 1; this.total = 0; this.initData(); }, updateChannel: function(row) { console.log(row) this.$axios({ method: 'post', url: `/api/channel/update/${this.deviceId}`, params: row }).then(function(res) { console.log(JSON.stringify(res)); }); } } web_src/src/components/gb28181/devicePlayer.vue
@@ -1,7 +1,7 @@ <template> <div id="devicePlayer"> <el-dialog title="视频播放" top="0" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="stop()"> <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" fluent autoplay live stretch></LivePlayer> <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live ></LivePlayer> <div id="shared" style="text-align: right; margin-top: 1rem;"> <el-tabs v-model="tabActiveName"> <el-tab-pane label="媒体流信息" name="media"> @@ -114,17 +114,22 @@ ssrc: '', deviceId: '', channelId: '', tabActiveName: 'media' tabActiveName: 'media', hasaudio: false }; }, methods: { play: function(streamInfo, deviceId, channelId) { play: function(streamInfo, deviceId, channelId, hasAudio) { console.log(hasAudio); this.hasaudio = hasAudio; this.ssrc = streamInfo.ssrc; this.deviceId = deviceId; this.channelId = channelId; this.videoUrl = streamInfo.flv + "?" + new Date().getTime(); // this.$refs.videoPlayer.hasaudio = hasAudio; // this.videoUrl = streamInfo.flv + "?" + new Date().getTime(); this.videoUrl = streamInfo.ws_flv; this.showVideoDialog = true; console.log(this.ssrc); },