ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/CarController.java
@@ -52,14 +52,23 @@ @RequestMapping("/car") public class CarController { @Value("${live.output.path}") private String outputPath; @Value("${live.hls.output-path}") private String hlsOutputPath; @Value("${live.ip}") private String liveIp; @Value("${live.hls.ip}") private String hlsIp; @Value("${live.port}") private Integer livePort; @Value("${live.hls.port}") private Integer hlsPort; @Value("${live.flv.ip}") private String flvIp; @Value("${live.flv.rtmp-port}") private Integer flvRtmpPort; @Value("${live.flv.http-port}") private Integer flvHttpPort; @Resource private ICarService carService; @Resource @@ -122,32 +131,17 @@ Enterprise enterprise = enterpriseService.getById(car.getEnterpriseId()); R<UPRealvideoMsgStartupAckVo> msgStartupAckVoR = realVideoMsgClient.startupRealVideo(Integer.valueOf(enterprise.getCode()), car.getVehicleNumber()); if (200 == msgStartupAckVoR.getCode()) { String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8"; String folderPath = outputPath + "hls\\" + car.getVehicleNumber(); FileUtil.mkParentDirs(path); File file = new File(path); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } } UPRealvideoMsgStartupAckVo data = msgStartupAckVoR.getData(); RealVideoResp resp = new RealVideoResp(); //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_hls(data.getUrl(), path, id, folderPath); } }); resp.setServerIp(liveIp); resp.setServerPort(livePort); // live_hls(data.getUrl(), car); // resp.setServerIp(hlsIp); // resp.setServerPort(hlsPort); live_flv(data.getUrl(), car.getId()); resp.setServerIp(flvIp); resp.setServerPort(flvHttpPort); return R.ok(resp); } @@ -165,7 +159,7 @@ if (null == car) { return R.fail("失败"); } String folderPath = outputPath + "hls\\" + car.getVehicleNumber(); String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber(); JavaCVStreamUtil.close(id, folderPath); return R.ok(); } @@ -197,30 +191,14 @@ if (200 == startupAckVoR.getCode()) { UPPlaybackMsgStartupAckVo data = startupAckVoR.getData(); RealVideoResp resp = new RealVideoResp(); String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8"; String folderPath = outputPath + "hls\\" + car.getVehicleNumber(); FileUtil.mkParentDirs(path); File file = new File(path); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } } //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_hls(data.getUrl(), path, req.getId(), folderPath); } }); resp.setServerIp(liveIp); resp.setServerPort(livePort); // live_hls(data.getUrl(), car); // resp.setServerIp(hlsIp); // resp.setServerPort(hlsPort); live_flv(data.getUrl(), car.getId()); resp.setServerIp(flvIp); resp.setServerPort(flvHttpPort); return R.ok(resp); } return R.fail(startupAckVoR.getMsg()); @@ -313,4 +291,46 @@ } return R.ok(list); } public void live_hls(String input, Car car){ String path = hlsOutputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8"; String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber(); FileUtil.mkParentDirs(path); File file = new File(path); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } } //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_hls(input, path, car.getId(), folderPath); } }); carService.taskPlayDetection(car.getId()); } public void live_flv(String input, Integer id){ String url = "rtmp://" + flvIp + ":" + flvRtmpPort + "/flv/" + id; //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_flv(input, url, id); } }); // carService.taskPlayDetection(id); } } ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/OrderController.java
@@ -75,14 +75,23 @@ @Resource private RedisTemplate redisTemplate; @Value("${live.output.path}") private String outputPath; @Value("${live.hls.output-path}") private String hlsOutputPath; @Value("${live.ip}") private String liveIp; @Value("${live.hls.ip}") private String hlsIp; @Value("${live.port}") private Integer livePort; @Value("${live.hls.port}") private Integer hlsPort; @Value("${live.flv.ip}") private String flvIp; @Value("${live.flv.rtmp-port}") private Integer flvRtmpPort; @Value("${live.flv.http-port}") private Integer flvHttpPort; @GetMapping("/getOrderList") @@ -187,32 +196,58 @@ if (200 == startupAckVoR.getCode()) { UPPlaybackMsgStartupAckVo data = startupAckVoR.getData(); RealVideoResp resp = new RealVideoResp(); String path = outputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8"; String folderPath = outputPath + "hls\\" + car.getVehicleNumber(); FileUtil.mkParentDirs(path); File file = new File(path); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } } //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_hls(data.getUrl(), path, id, folderPath); } }); resp.setServerIp(liveIp); resp.setServerPort(livePort); // live_hls(data.getUrl(), car); // resp.setServerIp(hlsIp); // resp.setServerPort(hlsPort); live_flv(data.getUrl(), car.getId()); resp.setServerIp(flvIp); resp.setServerPort(flvHttpPort); return R.ok(resp); } return R.fail(startupAckVoR.getMsg()); } public void live_hls(String input, Car car){ String path = hlsOutputPath + "hls\\" + car.getVehicleNumber() + "\\live.m3u8"; String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber(); FileUtil.mkParentDirs(path); File file = new File(path); if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { throw new RuntimeException(e); } } //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_hls(input, path, car.getId(), folderPath); } }); carService.taskPlayDetection(car.getId()); } public void live_flv(String input, Integer id){ String url = "rtsp://" + flvIp + ":" + flvRtmpPort + "/flv/" + id; //执行拉流和推流 ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); executorService.execute(new Runnable() { @Override public void run() { JavaCVStreamUtil.push_flv(input, url, id); } }); carService.taskPlayDetection(id); } } ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ICarService.java
@@ -37,5 +37,5 @@ * 检测视频播放,清除没有播放的视频流 * @return */ void taskPlayDetection(); void taskPlayDetection(Integer deviceNumber); } ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CarServiceImpl.java
@@ -31,6 +31,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @author zhibing.pu @@ -39,8 +42,8 @@ @Service public class CarServiceImpl extends ServiceImpl<CarMapper, Car> implements ICarService { @Value("${live.output.path}") private String outputPath; @Value("${live.hls.output-path}") private String hlsOutputPath; @Resource private UPExgMsgRegisterClient upExgMsgRegisterClient; @@ -162,13 +165,20 @@ * 检测视频播放,清除没有播放的视频流 */ @Override public void taskPlayDetection() { List<Car> list = this.list(); for (Car car : list) { if(!redisTemplate.hasKey("live:" + car.getId())){ String folderPath = outputPath + "hls\\" + car.getVehicleNumber(); JavaCVStreamUtil.close(car.getId(), folderPath); } public void taskPlayDetection(Integer deviceNumber) { Car car = this.getById(deviceNumber); try { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); executorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { if(!redisTemplate.hasKey("live:" + car.getId())){ String folderPath = hlsOutputPath + "hls\\" + car.getVehicleNumber(); JavaCVStreamUtil.close(car.getId(), folderPath); } }}, 1, 1, TimeUnit.MINUTES); }catch (Exception e){ e.printStackTrace(); } } ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/JavaCVStreamUtil.java
@@ -2,19 +2,25 @@ import cn.hutool.core.io.FileUtil; import com.ruoyi.common.core.utils.StringUtils; import com.ruoyi.system.service.ICarService; import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.ffmpeg.global.avutil; import org.bytedeco.javacv.*; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 音视频推流拉流工具类 * 参考资料:https://juejin.cn/post/7311230172237561866 * * @author zhibing.pu * @Date 2025/4/8 16:03 */ @Component public class JavaCVStreamUtil { private static Map<Integer, FFmpegFrameGrabber> grabberMap = new ConcurrentHashMap<>(); @@ -25,27 +31,33 @@ private static Map<Integer, Boolean> statusMap = new ConcurrentHashMap<>(); @Resource private RedisTemplate redisTemplate; @Resource private ICarService carService; /** * 视频拉流和推流 */ public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber){ public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber) { OpenCVFrameGrabber grabber = null; // FFmpegFrameGrabber grabber = null; FFmpegFrameRecorder recorder = null; try { //关闭上一个没有正确关闭的流 FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber); if(null != fFmpegFrameGrabber){ if (null != fFmpegFrameGrabber) { fFmpegFrameGrabber.close(); } OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber); if(null != openCVFrameGrabber){ openCVFrameGrabber.close();; if (null != openCVFrameGrabber) { openCVFrameGrabber.close(); ; } FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber); if(null != fFmpegFrameRecorder){ if (null != fFmpegFrameRecorder) { fFmpegFrameRecorder.close(); } @@ -95,13 +107,13 @@ while ((frame = grabber.grab()) != null) { recorder.record(frame); //判断状态为停止,则结束此线程任务 if(!statusMap.get(deviceNumber)){ if (!statusMap.get(deviceNumber)) { break; } } } catch (FrameGrabber.Exception | FrameRecorder.Exception e) { e.printStackTrace(); }finally { } finally { close(deviceNumber, null); } } @@ -110,7 +122,7 @@ /** * 视频拉流和推流 */ public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath){ public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath) { OpenCVFrameGrabber grabber = null; // FFmpegFrameGrabber grabber = null; FFmpegFrameRecorder recorder = null; @@ -118,15 +130,16 @@ try { //关闭上一个没有正确关闭的流 FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber); if(null != fFmpegFrameGrabber){ if (null != fFmpegFrameGrabber) { fFmpegFrameGrabber.close(); } OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber); if(null != openCVFrameGrabber){ openCVFrameGrabber.close();; if (null != openCVFrameGrabber) { openCVFrameGrabber.close(); ; } FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber); if(null != fFmpegFrameRecorder){ if (null != fFmpegFrameRecorder) { fFmpegFrameRecorder.close(); } @@ -194,13 +207,13 @@ while ((frame = grabber.grab()) != null) { recorder.record(frame); //判断状态为停止,则结束此线程任务 if(!statusMap.get(deviceNumber)){ if (!statusMap.get(deviceNumber)) { break; } } } catch (FrameGrabber.Exception | FrameRecorder.Exception e) { e.printStackTrace(); }finally { } finally { close(deviceNumber, folderPath); } } @@ -208,12 +221,13 @@ /** * 关闭推流和拉流进程 * * @param deviceNumber */ public static void close(Integer deviceNumber, String folderPath){ public static void close(Integer deviceNumber, String folderPath) { //设置状态为停止 Boolean status = statusMap.get(deviceNumber); if(null == status || !status){ if (null == status || !status) { return; } statusMap.put(deviceNumber, false); @@ -222,26 +236,25 @@ FFmpegFrameRecorder fFmpegFrameRecorder = null; try { fFmpegFrameGrabber = grabberMap.get(deviceNumber); if(null != fFmpegFrameGrabber){ fFmpegFrameGrabber.flush(); if (null != fFmpegFrameGrabber) { fFmpegFrameGrabber.close(); } openCVFrameGrabber = grabberMap1.get(deviceNumber); if(null != openCVFrameGrabber){ openCVFrameGrabber.flush(); if (null != openCVFrameGrabber) { openCVFrameGrabber.close(); } fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber); if(null != fFmpegFrameRecorder){ fFmpegFrameRecorder.flush(); if (null != fFmpegFrameRecorder) { fFmpegFrameRecorder.close(); } }catch (Exception e){ } catch (Exception e) { e.printStackTrace(); } //开始清除视频文件 if(StringUtils.isNotEmpty(folderPath)){ if (StringUtils.isNotEmpty(folderPath)) { FileUtil.del(folderPath); } } } ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/TaskUtil.java
@@ -81,14 +81,4 @@ public void taskUpdateCarStatus() { carService.taskUpdateCarStatus(); } /** * 视频播放检测 */ @Scheduled(fixedRate = 1000 * 300) public void taskPlayDetection() { carService.taskPlayDetection(); } }