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

 通过海康接口返回的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);
      System.err.println(fileExist);
      // 文件夹不存在,则新建
      if (!fileExistTWo.exists()) {
         fileExistTWo.mkdirs();
      }

      // 省略查询路径部分    实体-> result
      String codeId = fileName + ":" + code;
      manager.stop(codeId);                             // 先停止视频
      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"));
      TaskEntity info= (TaskEntity) manager.query(codeId);
      System.out.println(info);
      int suspensionState = info.getSuspensionState();
      System.out.println(suspensionState);
      String urlHead = "http://";
//    返回路径根据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);
      }



   }
}

猜你喜欢

转载自blog.csdn.net/weixin_43288858/article/details/128253490
今日推荐