1、摄像头功能修改

2、配置文件增加摄像头相关配置
This commit is contained in:
LiuY 2023-12-28 19:19:08 +08:00
parent 7d7f942ee3
commit 537aae0af7
11 changed files with 10826 additions and 222 deletions

View File

@ -43,8 +43,8 @@ public class CameraController extends AbstractController {
})
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
@GetMapping("camera-pullflow/{cityCameraId}")
public SuccessResultData<String> cameraPullFlow(@PathVariable("cityCameraId") String cityCameraId) throws SearchException {
return new SuccessResultData(cameraService.cameraPullFlow(cityCameraId));
public CameraDTO cameraPullFlow(@PathVariable("cityCameraId") String cityCameraId) throws SearchException {
return cameraService.cameraPullFlow(cityCameraId);
}

View File

@ -0,0 +1,106 @@
package com.cm.population.pojo.dtos.camera;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 摄像头配置
* @author : LY
* @date :2023-12-28 16:37
* @description :
* @modyified By:
*/
@Component
@ConfigurationProperties(prefix = "camera")
public class CameraConfig {
private String ffmpegPath;
private rtmpPlayConfig rtmpPlay;
private HttpPlayConfig httpPlay;
public String getFfmpegPath() {
return ffmpegPath;
}
public void setFfmpegPath(String ffmpegPath) {
this.ffmpegPath = ffmpegPath;
}
public rtmpPlayConfig getRtmpPlay() {
return rtmpPlay;
}
public void setRtmpPlay(rtmpPlayConfig rtmpPlay) {
this.rtmpPlay = rtmpPlay;
}
public HttpPlayConfig getHttpPlay() {
return httpPlay;
}
public void setHttpPlay(HttpPlayConfig httpPlay) {
this.httpPlay = httpPlay;
}
public static class HttpPlayConfig {
private String name;
private String ip;
private String port;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}
public static class rtmpPlayConfig {
private String name;
private String ip;
private String port;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}
}

View File

@ -27,6 +27,8 @@ public class CameraDTO {
private String cameraRtspLink;
private String remark;
private String playAddress;
public String getCityCameraId() {
return cityCameraId;
@ -131,4 +133,12 @@ public class CameraDTO {
public void setRemark(String remark) {
this.remark = remark;
}
public String getPlayAddress() {
return playAddress;
}
public void setPlayAddress(String playAddress) {
this.playAddress = playAddress;
}
}

View File

@ -1,125 +0,0 @@
package com.cm.population.pojo.dtos.camera.media;
import com.cm.common.annotation.CheckEmptyAnnotation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
/**
* @ClassName: MediaConfigVO
* @Description:
* @Author: wanggeng
* @Date: 2023/9/2 20:55
* @Version: 1.0
*/
@ApiModel
public class MediaConfig {
@ApiModelProperty(name = "ffmpegPath", value = "FFMPEG路径")
@CheckEmptyAnnotation(name = "FFMPEG路径")
private String ffmpegPath;
@ApiModelProperty(name = "nginxPath", value = "NGINX路径")
@CheckEmptyAnnotation(name = "NGINX路径")
private String nginxPath;
@ApiModelProperty(name = "nginxConfPath", value = "NGINX CONF文件路径")
@CheckEmptyAnnotation(name = "NGINX CONF文件路径")
private String nginxConfPath;
@ApiModelProperty(name = "nginxRtmpPlayUrl", value = "NGINX RTMP直播地址")
@CheckEmptyAnnotation(name = "NGINX RTMP直播地址")
private String nginxRtmpPlayUrl;
@ApiModelProperty(name = "nginxRtmpRelayUrl", value = "NGINX RTMP转播地址")
@CheckEmptyAnnotation(name = "NGINX RTMP转播地址")
private String nginxRtmpRelayUrl;
@ApiModelProperty(name = "nginxRtmpTranscodeUrl", value = "NGINX RTMP转码地址")
@CheckEmptyAnnotation(name = "NGINX RTMP转码地址")
private String nginxRtmpTranscodeUrl;
@ApiModelProperty(name = "nginxRtmpChannelCount", value = "NGINX RTMP频道数")
@CheckEmptyAnnotation(name = "NGINX RTMP频道数")
private Integer nginxRtmpChannelCount;
@ApiModelProperty(name = "nginxHttpPlayUrl", value = "NGINX HTTP播放地址")
@CheckEmptyAnnotation(name = "NGINX HTTP播放地址")
private String nginxHttpPlayUrl;
@ApiModelProperty(name = "nginxHttpControlUrl", value = "NGINX HTTP控制URL")
@CheckEmptyAnnotation(name = "NGINX HTTP控制URL")
private String nginxHttpControlUrl;
public String getFfmpegPath() {
return ffmpegPath == null ? "" : ffmpegPath.trim();
}
public void setFfmpegPath(String ffmpegPath) {
this.ffmpegPath = ffmpegPath;
}
public String getNginxPath() {
return nginxPath == null ? "" : nginxPath.trim();
}
public void setNginxPath(String nginxPath) {
this.nginxPath = nginxPath;
}
public String getNginxConfPath() {
return nginxConfPath == null ? "" : nginxConfPath.trim();
}
public void setNginxConfPath(String nginxConfPath) {
this.nginxConfPath = nginxConfPath;
}
public String getNginxRtmpPlayUrl() {
return nginxRtmpPlayUrl == null ? "" : nginxRtmpPlayUrl.trim();
}
public void setNginxRtmpPlayUrl(String nginxRtmpPlayUrl) {
this.nginxRtmpPlayUrl = nginxRtmpPlayUrl;
}
public String getNginxRtmpRelayUrl() {
return nginxRtmpRelayUrl == null ? "" : nginxRtmpRelayUrl.trim();
}
public void setNginxRtmpRelayUrl(String nginxRtmpRelayUrl) {
this.nginxRtmpRelayUrl = nginxRtmpRelayUrl;
}
public String getNginxRtmpTranscodeUrl() {
return nginxRtmpTranscodeUrl == null ? "" : nginxRtmpTranscodeUrl.trim();
}
public void setNginxRtmpTranscodeUrl(String nginxRtmpTranscodeUrl) {
this.nginxRtmpTranscodeUrl = nginxRtmpTranscodeUrl;
}
public Integer getNginxRtmpChannelCount() {
return nginxRtmpChannelCount == null ? 0 : nginxRtmpChannelCount;
}
public void setNginxRtmpChannelCount(Integer nginxRtmpChannelCount) {
this.nginxRtmpChannelCount = nginxRtmpChannelCount;
}
public String getNginxHttpPlayUrl() {
return nginxHttpPlayUrl == null ? "" : nginxHttpPlayUrl.trim();
}
public void setNginxHttpPlayUrl(String nginxHttpPlayUrl) {
this.nginxHttpPlayUrl = nginxHttpPlayUrl;
}
public String getNginxHttpControlUrl() {
return nginxHttpControlUrl == null ? "" : nginxHttpControlUrl.trim();
}
public void setNginxHttpControlUrl(String nginxHttpControlUrl) {
this.nginxHttpControlUrl = nginxHttpControlUrl;
}
}

View File

@ -29,7 +29,7 @@ public interface ICameraService {
* @param cameraId
* @return
*/
SuccessResultData<String> cameraPullFlow(String cameraId);
CameraDTO cameraPullFlow(String cameraId);

View File

@ -39,7 +39,7 @@ public class CameraServiceImpl extends AbstractService implements ICameraService
@Autowired
private IRtmpService rtmpService;
public SuccessResultData<String> cameraPullFlow(String cameraId){
public CameraDTO cameraPullFlow(String cameraId){
CameraDTO cameraDTO = this.get(cameraId);
if(cameraDTO == null){
throw new SearchException("未获取到摄像头信息");
@ -47,8 +47,10 @@ public class CameraServiceImpl extends AbstractService implements ICameraService
if(StringUtils.isEmpty(cameraDTO.getCameraRtspLink())){
throw new SearchException("摄像头RTSP地址为空");
}
int code = new Random().nextInt(99999) + 10000;
return new SuccessResultData(rtmpService.pullFlow(cameraDTO.getCameraRtspLink(), Integer.toString(code)));
/*int code = new Random().nextInt(99999) + 10000;*/
// 转码拉流
cameraDTO.setPlayAddress(rtmpService.pullFlow(cameraDTO.getCameraRtspLink(), cameraId));
return cameraDTO;
}

View File

@ -6,8 +6,8 @@ public interface IRtmpService {
/**
* 拉流转码
* @param rtmpUrl
* @param ident
* @param cameraId
* @return
*/
String pullFlow(String rtmpUrl, String ident);
String pullFlow(String rtmpUrl, String cameraId);
}

View File

@ -1,9 +1,13 @@
package com.cm.population.service.camera.rtmp.impl;
import com.cm.common.base.AbstractService;
import com.cm.common.exception.ParamsException;
import com.cm.population.pojo.dtos.camera.CameraConfig;
import com.cm.population.service.camera.command.AbstractCommandStream;
import com.cm.population.service.camera.command.CommandUtil;
import com.cm.population.service.camera.rtmp.IRtmpService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -19,21 +23,34 @@ import java.util.List;
@Service
public class RtmpServiceImpl extends AbstractService implements IRtmpService {
@Autowired
private CameraConfig cameraConfig;
//private static final Logger LOG = LoggerFactory.getLogger(RtmpServiceImpl.class);
//String rtmpUrl = "rtmp://ns8.indexforce.com/home/mystream";
/**
* 拉流转码
* @param rtmpUrl 推流地址
* @param cameraId 摄像头标识
* @return
*/
public String pullFlow(String rtmpUrl, String ident){
public String pullFlow(String rtmpUrl, String cameraId){
String ffmpegPath = cameraConfig.getFfmpegPath();
String rtmpIp = cameraConfig.getRtmpPlay().getIp();
String rtmpPort = cameraConfig.getRtmpPlay().getPort();
String rtmpName = cameraConfig.getRtmpPlay().getName();
String httpIp = cameraConfig.getHttpPlay().getIp();
String httpPort = cameraConfig.getHttpPlay().getPort();
String httpName = cameraConfig.getHttpPlay().getName();
if(StringUtils.isEmpty(ffmpegPath)){
throw new ParamsException("未配置摄像头查看参数-请检查配置文件");
}
List<String> commands = new ArrayList<>();
commands.add("D:\\CF_work\\ffmpeg\\ffmpeg-2023-11-28-git-47e214245b-essentials_build\\bin\\ffmpeg.exe");
commands.add(ffmpegPath); // D:\CF_work\ffmpeg\ffmpeg-2023-11-28-git-47e214245b-essentials_build\bin\ffmpeg.exe
commands.add("-i");
commands.add(rtmpUrl);
commands.add("-vcodec");
@ -42,7 +59,7 @@ public class RtmpServiceImpl extends AbstractService implements IRtmpService {
commands.add("aac");
commands.add("-f");
commands.add("flv");
commands.add("rtmp://127.0.0.1:8081/live/" + ident);
commands.add(String.format("rtmp://%s:%s/%s/%s", rtmpIp, rtmpPort, rtmpName, cameraId));
CommandUtil.execute(commands, new AbstractCommandStream(){
@Override
protected void input(String inputLine) throws Exception {
@ -53,7 +70,15 @@ public class RtmpServiceImpl extends AbstractService implements IRtmpService {
}
});
return "http://127.0.0.1:8080/live?port=8081&app=live&stream=" + ident;
return String.format(
"http://%s:%s/%s?port=%s&app=%s&stream=%s",
httpIp,
httpPort,
httpName,
rtmpPort,
rtmpName,
cameraId
);
}

View File

@ -1,10 +1,9 @@
server:
port: 7023
url: http://192.168.0.115:7023/population
port: 9999
url: http://192.168.0.120:9999/population
title: population
servlet:
context-path: /population
spring:
thymeleaf:
prefix: classpath:/templates/
@ -21,10 +20,12 @@ spring:
datasource:
druid:
url: jdbc:mysql://192.168.0.151:3306/db_baotou_city?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false
#url: jdbc:mysql://127.0.0.1:3306/db_baotou_city?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
username: root
#password: 123456
initial-size: 2
min-idle: 2
max-active: 5
@ -61,7 +62,7 @@ swagger:
# 文件
file:
uploadPath: C:\Users\TS-QD1\Desktop\UploadFiles\
uploadPath: D:\CF_work\ideaWorkSpace\uploadFiles\
imageTypes: png,jpg,jpeg,gif,blob
videoTypes: mp4
audioTypes: mp3,wav,amr
@ -72,10 +73,15 @@ file:
security:
oauth2:
oauth-server: http://192.168.0.15:7021/usercenter
# #oauth-server: http://121.36.71.250:58038/usercenter
#oauth-server: http://219.147.99.164:8082/usercenter
oauth-logout: ${security.oauth2.oauth-server}/logout?redirect_uri=${server.url}
client:
client-id: 00b49298399641448405170d55a3614b
client-secret: Z0RQUmZTdENUckprWVRtdGQ3Sm5ldmlCZFJKT1NBNHJZWnNrM1RSSWU3NG1ac2wwZTJHWk5NbXh3L3h3U2c4Rg==
client-id: e6cd01761ed14679952397c0f73db881
client-secret: SDZnNVV6WUtkdXZTQmtMV1J3ZVcyQzhBTnNzOEEyZE5jc1lveGdUZlJOY21ac2wwZTJHWk5NbXh3L3h3U2c4Rg==
# client-id: 084d50a02c2b4f92984684ff50661690
# client-secret: ZWQyMjBtN2hvWEZ1OW9pNUU2aU1HSFowdEllZ3NZNVYrSndzLzRWbVJqQW1ac2wwZTJHWk5NbXh3L3h3U2c4Rg==
user-authorization-uri: ${security.oauth2.oauth-server}/oauth_client/authorize
access-token-uri: ${security.oauth2.oauth-server}/oauth_client/token
grant-type: authorization_code
@ -120,8 +126,22 @@ logging:
root: error
com.cm: debug
server-other:
area-url: http://192.168.0.15:7022/servicecity
population-update-log: true
population-insert-log: true
population-delete-log: false
camera:
ffmpeg-path: D:\CF_work\ffmpeg\ffmpeg-2023-11-28-git-47e214245b-essentials_build\bin\ffmpeg.exe
rtmp-play:
ip: 127.0.0.1
port: 8081
name: live
http-play:
ip: 127.0.0.1
port: 8080
name: live

File diff suppressed because it is too large Load Diff

View File

@ -70,9 +70,11 @@
</head>
<body>
<div class="layui-fluid layui-anim layui-anim-fadein" id="LAY-component-grid-mobile">
<div class="layui-row layui-col-space12" style="padding: 6px">
注:如提示 “所有播放窗口已占用”,请先暂停一个窗口的视频,在点击要查看的摄像头
</div>
<div class="layui-row layui-col-space12">
<div class="layui-col-md3 camera-list">
<div class="layui-card">
<div class="layui-card-header container">
<div class="layui-inline layui-form search-item" id="streetSelectTemplateBox" lay-filter="streetSelectTemplateBox"></div>
@ -90,25 +92,25 @@
</div>
</div>
</div>
<div class="layui-col-md9 camera-feed">
<div class="layui-col-md9 camera-feed" id="videoDiv">
<div class="layui-row layui-col-space10">
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
摄像头
暂无摄像头
</div>
<div class="layui-card-body">
<video id="video1" class="centeredVideo" controls width="1024" height="576"></video>
<video class="centeredVideo" controls width="1024" height="576"></video>
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
摄像头
暂无摄像头
</div>
<div class="layui-card-body">
<video id="video2" class="centeredVideo" controls width="1024" height="576"></video>
<video class="centeredVideo" controls width="1024" height="576"></video>
</div>
</div>
</div>
@ -117,16 +119,21 @@
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
摄像头
暂无摄像头
</div>
<div class="layui-card-body">
<video id="video3" class="centeredVideo" controls width="1024" height="576"></video>
<video class="centeredVideo" controls width="1024" height="576"></video>
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-card">
<div class="layui-card-header">
暂无摄像头
</div>
<div class="layui-card-body">
<video class="centeredVideo" controls width="1024" height="576"></video>
</div>
</div>
</div>
</div>
@ -278,82 +285,56 @@
$(document).on('click', '.camera', function() {
var id = $(this).attr('id');
var name = $(this).find('.camera-name').text();
var address = $(this).find('.camera-location').text();
if(video_play_list.includes(id)){
top.dialog.msg('该摄像头已播放')
return;
var videos = document.getElementById("videoDiv").getElementsByTagName('video');
for (var i = 0; i < videos.length; i++) {
var v = videos[i];
if(v.getAttribute('id') == id){
top.dialog.msg('该摄像头已播放')
break;
}
if (v.paused && v.currentTime == 0) {
v.setAttribute('id',id);
initVideo(videos[i], id)
break;
}
if(i == videos.length - 1){
top.dialog.msg('所有播放窗口已占用');
}
}
//video_play(id);
});
// 播放
var video_play = function (id) {
// 初始化video
var initVideo = function(vElement, id){
top.restAjax.get(top.restAjax.path('api/camera/camera-pullflow/{cityCameraId}', [id]), {}, null, function(code, data) {
var play_url = data.data.data;
video_play_list.push(id);
switch (video_play_list.length) {
case 1:
initVideo(document.getElementById('video1'), play_url);
break;
case 2:
initVideo(document.getElementById('video2'), play_url);
break;
case 3:
initVideo(document.getElementById('video3'), play_url);
break;
case 4:
initVideo(document.getElementById('video4'), play_url);
break;
default:
console.log(video_play_list)
video_play_list.shift();
console.log(video_play_list)
initVideo(document.getElementById('video1'), play_url);;
var card = vElement.parentNode.parentNode;
var cardHeader = card.getElementsByClassName("layui-card-header")[0];
cardHeader.innerText = data.cameraName;
if (flvjs.isSupported()) {
var flvPlayer = flvjs.createPlayer({
type: 'flv',
enableWorker: true, //浏览器端开启flv.js的worker,多进程运行flv.js
isLive: true, //直播模式
autoplay: true, // 设置自动播放
hasAudio: true, //关闭音频
hasVideo: true,
stashInitialSize: 128,
enableStashBuffer: true, //播放flv时设置是否启用播放缓存只在直播起作用。
url: data.playAddress
});
flvPlayer.attachMediaElement(vElement)
flvPlayer.load();
}
}, function(code, data) {
top.dialog.msg(data.msg);
});
};
// 初始化video
var initVideo = function(vElement, url){
if (flvjs.isSupported()) {
/* var vElement = document.getElementById('videoElement')*/
var flvPlayer = flvjs.createPlayer({
type: 'flv',
enableWorker: true, //浏览器端开启flv.js的worker,多进程运行flv.js
isLive: true, //直播模式
hasAudio: true, //关闭音频
hasVideo: true,
stashInitialSize: 128,
enableStashBuffer: false, //播放flv时设置是否启用播放缓存只在直播起作用。
url: url
});
flvPlayer.attachMediaElement(vElement)
flvPlayer.load();//加载
}
};
// 关闭流
var closeStream = function(flvPlayer) {
flvPlayer.pause();
flvPlayer.unload();
flvPlayer.detachMediaElement();
flvPlayer.destroy();
};
$(function(){
// 样式
var windowHeight = $(window).height() - 50;
var windowHeight = $(window).height() - 75;
$('.camera-list .layui-card-body').css({
'height': (windowHeight - 50) + 'px',
'overflow' : 'auto'