海康视频回放,rtsp视频接口转换成.m3u8格式文件

news2025/1/11 0:13:11

 通过海康接口返回的rtsp视频接口,转换成.m3u8格式文件,逻辑如下

1、采用ffmpeg实时转化rtsp链接视频,转化为m3u8,存放服务器固定地址

2、采用nginx代理视频成.m3u8视频

3、采用token+redis方式处理视频播放和删除过程,开启视频录像,并将token或者自定义文件夹存入redis,将用户token解析部分(我解析的是jwt的token最后一个点后面内容,作为当前用户的开始视频存放的文件夹A),视频摄像头唯一编码作为下面一个子文件夹B,A+B作为ffmpeg开启的key

4、停止某个视频,通过A+B停止ffmpeg视频转化,并删除B下面所有资源,包含B所有文件夹

5、退出登录,停止并删除A下面的所有视频资源转化,并删除A文件夹

6、redis中的token过期,回调方法返回过期的key,对key解析,拿到token最后一个点后面内容,也是就是文件A,对第五步进行操作

 1、nginx转码配置及ffmpeg转化,我参考的下面博客

https://www.freesion.com/article/5775913700/

, 注意,java的ffmpeg部分,我自定义了一个文件夹ffmpeg,

 

 1、我的调用ffmpeg的start方法开始转视频流,注意转流的文件路径要先创建,fileExistTWo.mkdirs();

@Autowired
CommandManager manager;
public String toHls(String fileName, String code, String url) {
      //.m3u8文件路径
      String basePath = rootPath + fileName + File.separator + code + File.separator + code + File.separator;
      //文件夹路径
      String basePathTWo = rootPath + fileName + File.separator + code + File.separator;

      File fileExist = new File(basePath);
      File fileExistTWo = new File(basePathTWo);
      // 文件夹不存在,则新建
      if (!fileExistTWo.exists()) {
         fileExistTWo.mkdirs();
      }
      //ffmPeg的唯一编码codeId
      String codeId = fileName + ":" + code;
      // 先停止视频
      manager.stop(codeId);
      // 调用ffmPeg开启新的转流方法
      manager.start(codeId, CommandBuidlerFactory.createBuidler()
         .add("`ffmpeg`")
         .add("-rtsp_transport", "tcp")
         .add("-i", url)              // 取videoUrl
         .add("-c", "copy")
         .add("-f", "hls")
         .add("-hls_time", "2.0")
         .add("-hls_list_size", "2")
         .add("-hls_flags", "2")
         .add(fileExist + ".m3u8"));
      String urlHead = "http://";
//    CommonUtil.getIpv4IP()我自定义的获取公网IP方法
//    返回路径根据ffmpeg存放视频路径+nginx代理灵活配置
      String urlTwo = urlHead + CommonUtil.getIpv4IP().trim() + ":" + 1011 + "/videoCache/" + fileName + "/" + code + "/" + code + ".m3u8";
      return urlTwo;
   }

获取公网IP方法 

public static String getIpv4IP() {
      StringBuilder result = new StringBuilder();
      BufferedReader in = null;
      try {
         URL realUrl = new URL("https://www.taobao.com/help/getip.php");
         // 打开和URL之间的连接
         URLConnection connection = realUrl.openConnection();
         // 设置通用的请求属性
         connection.setRequestProperty("accept", "*/*");
         connection.setRequestProperty("connection", "Keep-Alive");
         connection.setRequestProperty("user-agent",
            "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
         // 建立实际的连接
         connection.connect();
         // 获取所有响应头字段
         Map<String, List<String>> map = connection.getHeaderFields();
         // 定义 BufferedReader输入流来读取URL的响应
         in = new BufferedReader(new InputStreamReader(
            connection.getInputStream()));
         String line;
         while ((line = in.readLine()) != null) {
            result.append(line);
         }
      } catch (Exception e) {
//            log.error("获取ipv4公网地址异常");
         e.printStackTrace();
      } finally {
         try {
            if (in != null) {
               in.close();
            }
         } catch (Exception e2) {
            e2.printStackTrace();
         }
      }
      String str = result.toString().replace("ipCallback({ip:", "");
      String ipStr = str.replace("})", "");

      return ipStr.replace('"', ' ');
   }

 2、停止视频方法

token值如下

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJpc3N1c2VyIiwiYXVkIjoiYXVkaWVuY2UiLCJ0ZW5hbnRfaWQiOiIwMDAwMDAiLCJyb2xlX25hbWUiOiJhZG1pbmlzdHJhdG9yIiwicG9zdF9pZCI6IjExMjM1OTg4MTc3Mzg2NzUyMDEiLCJ1c2VyX2lkIjoiMTEyMzU5ODgyMTczODY3NTIwMSIsInJvbGVfaWQiOiIxMTIzNTk4ODE2NzM4Njc1MjAxIiwidXNlcl9uYW1lIjoiYWRtaW4iLCJuaWNrX25hbWUiOiLnrqHnkIblkZgiLCJkZXRhaWwiOnsidHlwZSI6IndlYiIsInByb2plY3RJZCI6MTU4MjAwNTQ3Mzk1NTEzMTM5MywiVXNlckNhdGVnb3J5IjoiMCIsIldzSWQiOjE0MzgzMzIwNjQ1NjExNDM4MDl9LCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwiZGVwdF9pZCI6IjExMjM1OTg4MTM3Mzg2NzUyMDEiLCJhY2NvdW50IjoiYWRtaW4iLCJjbGllbnRfaWQiOiJzYWJlciIsImV4cCI6MTY3MDU2OTAyMSwibmJmIjoxNjcwNTY1NDIxfQ.NHZiaWqrCIRukfvAqChkDNAAH34Pffm_PvQIEfqAU0SdKkS9ZNhxnB354demmkAJ2l8m3OXWIkeSkeHHGNuzEg

停止的方法如下 

public R stopBackVideo(String code, String token) throws IOException {

   //解析jwt的token值,拿到最后面一截,这个也是不会重复
   String fileName = StringUtils.split(token, ".")[2];
   String basePath = rootPath + fileName + File.separator + code + File.separator;

   String basePathAll = rootPath + fileName + File.separator;
   List<String> fileNamesList = getFileNamesList(basePathAll);
   fileNamesList.forEach(x -> {
      System.err.println("文件名称:" + x);
   });
   System.err.println(basePathAll);
   File fileExist = new File(basePath);
   String codeId = fileName + ":" + code;
   //停止ffmpeg转码
   manager.stop(codeId);
   manager.start(codeId, CommandBuidlerFactory.createBuidler()
      .add("rm -rf", fileExist.toString()));
   //对文件夹进行删除操作
   if (fileExist.exists()) {
      deleteDir(basePath);
   }
   return R.data("删除成功");
}

删除文件夹下面所有文件 

public void deleteDir(String basePath) throws IOException {
   Path path = Paths.get(basePath);
   Files.walkFileTree(path,
      new SimpleFileVisitor<Path>() {
         // 先去遍历删除文件
         @Override
         public FileVisitResult visitFile(Path file,
                                  BasicFileAttributes attrs) throws IOException {
            Files.delete(file);
            System.out.printf("文件被删除 : %s%n", file);
            return FileVisitResult.CONTINUE;
         }

         // 再去遍历删除目录
         @Override
         public FileVisitResult postVisitDirectory(Path dir,
                                         IOException exc) throws IOException {
            Files.delete(dir);
            System.out.printf("文件夹被删除: %s%n", dir);
            return FileVisitResult.CONTINUE;
         }

      }
   );
}

3、停止并删除当前用户的所有视频,及记录 

public void removeAllVideoByToken(String token1) throws IOException {

   String fileName = StringUtils.split(token1, ".")[2];
   String basePathAll = rootPath + fileName + File.separator;
   File fileExist = new File(basePathAll);
   // 文件夹文件夹存在,则停止后删除
   if (fileExist.exists()) {
      List<String> fileNamesList = getFileNamesList(basePathAll);
      if (CollectionUtils.isNotEmpty(fileNamesList)) {
         fileNamesList.forEach(x -> {
            try {
               stopBackVideo(x, token1);
            } catch (IOException e) {
               e.printStackTrace();
            }
         });
         if (fileExist.exists()) {
            deleteDir(basePathAll);
         }
      }
   }
}

拿取到所有该文件目录,下面所有的文件夹名称 集合

private List<String> getFileNamesList(String path) {
      File file = new File(path);
      if (!file.exists()) {
         return null;
      }
      List<String> fileNames = new ArrayList<>();
      return getFileNames(file, fileNames);
   }

   /**
    * 得到文件名称
    *
    * @param file      文件
    * @param fileNames 文件名
    * @return {@link List}<{@link String}>
    */
   private List<String> getFileNames(File file, List<String> fileNames) {
      File[] files = file.listFiles();
      for (File f : files) {
         if (f.isDirectory()) {
            fileNames.add(f.getName());
         }
      }
      //所有文件
//       if (f.isDirectory()) {
//          getFileNames(f, fileNames);
//       } else {
//          fileNames.add(f.getName());
//       }
//    }
      return fileNames;
   }

4、监听redis中token失效回调方法,并停止用户的没有关闭的视频流,删除文件,减少资源占用

redis.conf将 notify-keyspace-events修改 成Ex

 notify-keyspace-events Ex

RedisMessageListenerContainer 加入容器 

@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
   RedisMessageListenerContainer container = new RedisMessageListenerContainer();
   container.setConnectionFactory(connectionFactory);
   return container;
}

 我是放在application下面

监听key变化,key过期则对视频流进行清理操作 

@Component

@Slf4j

public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {


   public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {

      super(listenerContainer);
   }


   @Autowired
   CameraSecretInfoService cameraSecretInfoService;

   /**
    * key过期触发的事件
    */
   @SneakyThrows
   @Override
   public void onMessage(Message message, byte[] pattern) {
      String channel = new String(message.getChannel(), StandardCharsets.UTF_8);
      String key = new String(message.getBody(), StandardCharsets.UTF_8);
      boolean contains = key.contains("bladeFile:fileName:");
      if (contains) {
         log.info("redis key 过期:pattern={},channel={},key={}", new String(pattern), channel, key);
         String token = key.substring(19);
         cameraSecretInfoService.removeAllVideoByToken(token);
      }



   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

太好玩了,我用 Python 做了一个 ChatGPT 机器人

大家好&#xff0c;我是早起。 毫无疑问&#xff0c;ChatGPT 已经是当下编程圈最火的话题之一&#xff0c;它不仅能够回答各类问题&#xff0c;甚至还能执行代码&#xff01; 或者是变成一只猫 因为它实在是太好玩&#xff0c;我使用Python将ChatGPT改造&#xff0c;可以实现在…

亚马逊黑五哑火,中国跨境电商高歌猛进!

黑五作为一个类似于中国双11的全球性购物狂欢节&#xff0c;在11月的最后一周拉开帷幕&#xff0c;据有关业内人士称&#xff0c;作为拥有众多全球站的亚马逊电子商务平台在此次黑色星期五的促销狂欢节中表现似乎稍显停滞&#xff0c;其作为电商领域的龙头企业&#xff0c;没能…

数据结构基础--图

一、图的基本概念 1.图的定义 图是由顶点集合V和边集合E组成的&#xff0c;记为G(G,V)。图可以只有点没有边&#xff0c;但不能只有边没有点。边&#xff1a;用&#xff08;x&#xff0c;y&#xff09;表示为xy之间的一条无向边&#xff1b;用<x,y>表示xy之间的一条有向…

大数据开发适合哪类人群?

有不少应届大学毕业生和0基础人群选择学大数据&#xff0c;但是要选择零基础的大数据培训班&#xff0c;从Java基础开始学习&#xff0c;由浅入深掌握离线数据分析、实时数据分析和内存数据计算等重要内容。 应届大学生缺乏工作经验和技能&#xff0c;对未来没有明确的规划&am…

VTK- PointLocator

欢迎大家加入VTK社区雪易VTK社区-CSDN社区云 小结&#xff1a;本博文主要针对VTK中的PointLocator的分类及各接口的用途进行讲解&#xff0c;PointLocator主要用途为点的位置计算&#xff0c;希望能为各位小伙伴有所帮助。 vtk中关于Locator的关系图 目录 vtkLocator vtkAbs…

软件测试基础理论体系学习4-单元测试的目的?概念是什么?过程是什么?

4-单元测试的目的&#xff1f;概念是什么&#xff1f;过程是什么&#xff1f;1 单元测试目的1.1 单元测试的错误认识1.2 单元测试的重要性1.2.1 时间方面1.2.2 测试效果1.2.3 测试成本1.2.4 产品质量1.3 单元测试的优点1.3.1 它是一种验证行为1.3.2 它是一种设计行为1.3.3 它是…

CPU是什么

CPU&#xff08;Central Processing Unit&#xff09;是计算机系统的运算和控制核心&#xff0c;是信息处理、程序运行的最终执行单元&#xff0c;相当于系统的“大脑”。当 CPU 过于繁忙&#xff0c;就像“人脑”并发处理过多的事情&#xff0c;会降低做事的效率&#xff0c;严…

Postman安装和运行

下载安装 Postman是一个方便用于构造请求的软件.可以以简单的方式来构造请求. 要下载软件,还是同样的话,要去官网下载.这里我们直接将官网地址放在这里. https://www.postman.com/downloads/ 进入官网以后,点击windows 64-bit(图中圈起来的部分)即可下载. 下载好以后双击安…

java小技能:JWT认证实现

文章目录 引言I. 预备知识1.1 关键字去空格处理II token组成2.1 头部(Header)2.2 有效载荷(Playload)2.3 签名(Signature)2.4 代码实现:生成tokenIII 验证token3.1 网关验证token3.2 使用拦截器验证token引言 认证流程 I. 预备知识 1.1 关键字去空格处理

前端复制粘贴方式上传图片

最近在做一个论坛的项目&#xff0c;发布评论的时候&#xff0c;很多时候会用到截图上传的功能&#xff0c;通过微信截图&#xff0c;QQ截图&#xff0c;直接将截取的图片通过Ctrlv 复制到输入框里&#xff0c;自动上传将图片渲染到页面上&#xff0c;今天就来实现一个这样的功…

BOS金蝶云星空:表单插件设置单据体背景色

一.效果图&#xff1a; 备注&#xff1a;只适用于只读列 二.代码案例&#xff1a; 自定义单据提附加背景色方法&#xff1a; /// /// 设置单据体背景颜色 /// /// 实体 /// 行 /// 字段 /// 颜色代码 private void SetEntityBackgoundColor(string entityKey, int row, st…

【面试题】大厂面试题分享:如何让(a===1a===2a===3)的值为true?

大厂面试题分享 面试题库 前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 当我第一次看到这一题目的时候&#xff0c;我是比较震惊的&#xff0c;分析了下很不合我们编程的常理&#xff0c;并认为不大可能&#…

面试官:断网了,还能 ping 通 127.0.0.1 吗?

你女神爱不爱你&#xff0c;你问她&#xff0c;她可能不会告诉你。 ‍ 但网通不通&#xff0c;你 ping 一下就知道了。 可能看到标题&#xff0c;你就知道答案了&#xff0c;但是你了解背后的原因吗&#xff1f; 那如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样呢&…

Win10用命令行编译带有cuda的opencv

0. 环境 笔记本win10 NVIDIA GeForce GTX 1660 Ti 1. 准备x64 Native Tools Command Prompt 1.1 准备Visual Studio Installer 需要安装visual studio 2019 1.2 安装工作负荷 为了安装x64 Native Tools Command Prompt&#xff0c;勾上使用C的桌面开发 安装完毕后&#xf…

Latent Class Modeling lca

潜类别模型&#xff08;Latent Class Modeling&#xff09; 潜在类别分析&#xff08;LCA&#xff09;数据分析流程&#xff08;详细版&#xff09; - 简书 (jianshu.com) R数据分析&#xff1a;用R语言做潜类别分析LCA - 知乎 (zhihu.com) About Latent Class Modeling -…

Postman(六): postman定义公共函数

Postman(11): postman定义公共函数 postman定义公共函数 在postman中&#xff0c;如下面的代码&#xff1a; 1、返回元素是否与预期值一致 var assertEqual(name,actual,expected)>{tests[${name}&#xff1a;实际结果&#xff1a; ${actual} &#xff0c; 期望结果&…

PDF转Excel怎么转?这些方法值得收藏

在我们的工作生活中&#xff0c;避免不了Excel表格的使用&#xff0c;当我们遇到想要将PDF文件中的信息转换制作成表格的时候&#xff0c;要怎么做呢&#xff1f;毕竟&#xff0c;PDF文件是一个不易编辑的格式&#xff0c;我们想复制其中的内容就较为的麻烦。一般这种时候&…

何止一个惨字形容,水滴 Java 面试一轮游,壮烈了,问啥啥不会,数据库血崩

static 关键字是用来干什么的&#xff0c;static 修饰的方法里面可以使用非静态的成员变量吗&#xff0c;为什么呢 private 修饰的方法是否可以被子类覆盖 覆盖和重载有什么区别 进程跟线程的区别 Java 中创建线程有几种方式 a. 反思&#xff1a;讲完三种方式之后&#xff…

Python——文件

文件 概念 我们常见的txt,jpg,mp4等等都是文件&#xff0c;存储在硬盘中的内容&#xff0c;就是文件&#xff0c;而文件夹是一种特殊的文件——目录文件 路径 一层一层文件夹组成的字符串就是路径&#xff0c;每一个文件的路径都是唯一的&#xff0c;相当于身份证号&#x…

卷积、自相关函数、功率谱密度

文章目录1、自相关函数和卷积2、自相关函数的傅里叶变换最近我在思考为什么&#xff1a; 为什么随机过程的自相关函数和其功率谱密度是一对傅里叶变换&#xff1f;1、自相关函数和卷积 这俩跟孪生兄弟似的&#xff0c;经常一起出现&#xff0c;我们先来看看自相关函数和卷积的…