• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Springboot+Minio通过分片下载解决IOS下H5没办法播放视频问题

武飞扬头像
yangfande362
帮助3

一、环境说明

  • JDK 1.8
  • Springboot 2.7.5
  • Minio 8.4.5
  • Vue3实现的微信公众号网页

二、问题描述

当前项目是基于springboot和vue3的前后端分离架构,前端目前主要是基于H5展示在微信公众号的网页中。在实现视频上传、在线播放时遇到问题:前端同事说苹果手机播放不了视频,刚开始是统一用的video标签,安卓可以正常播放,但是苹果手机就出现“视频播放失败”。前端同事尝试换过video.js、vue3-play、html5 api、avplay、mui-player,都无法解决该问题,于是开始尝试后端寻找解决方案。

三、后端解决思路

第一次,是尝试将视频请求的Content-Disposition由attachment;filename=**改成inline;filename=**,这样视频请求可以直接在浏览器播放,而不是下载。但是依旧没有解决苹果手机视频播放失败的问题。并尝试《iOS无法播放MP4视频文件的解决方案 mp4视频iphone播放不了怎么办》在nginx中加入“add_header Accept-Ranges bytes;”,未能解决该问题。

第二次,分析网上找的视频,将该视频嵌入video是可以在苹果手机播放的。通过观察该请求,发现是有206的响应码,开始研究断点下载,最终在《05.springboot使用minio实现分段下载》《H5 Video播放视频iOS 断点下载处理》两篇文章的启发下,通过minio分段下载解决了该问题。

四、关键知识点

对于下载请求的响应需要包含以下几个特殊属性:

Accept-Ranges: bytes          接收字节请求
Content-Length: 2                 相应长度
Content-Range: bytes 0-1/18494715    bytes后面的空格不能少,0开始位置,1结束位置。“/”后面的是文件总大小长度

Content-Disposition     inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名

HttpResponse

Status Code: 206 Partial Content      指示请求已成功并且主体包含所请求的数据范围

五、后端下载方法代码片段

  1.  
    public void downloadSlice(String bucketName, String filename, HttpServletResponse response,
  2.  
    HttpServletRequest request) throws Exception{
  3.  
    if (StringUtils.isNotBlank(filename)) {
  4.  
    String range = request.getHeader("Range");
  5.  
    //获取文件信息
  6.  
    StatObjectResponse statObjectResponse = minioClient.statObject(
  7.  
    StatObjectArgs.builder().bucket(bucketName).object(filename).build());
  8.  
    //开始下载位置
  9.  
    long startByte = 0;
  10.  
    //结束下载位置
  11.  
    long endByte = statObjectResponse.size() - 1;
  12.  
     
  13.  
    //有range的话
  14.  
    if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-")) {
  15.  
    range = range.substring(range.lastIndexOf("=") 1).trim();
  16.  
    String[] ranges = range.split("-");
  17.  
    try {
  18.  
    //判断range的类型
  19.  
    if (ranges.length == 1) {
  20.  
    //类型一:bytes=-2343
  21.  
    if (range.startsWith("-")) {
  22.  
    endByte = Long.parseLong(ranges[0]);
  23.  
    }
  24.  
    //类型二:bytes=2343-
  25.  
    else if (range.endsWith("-")) {
  26.  
    startByte = Long.parseLong(ranges[0]);
  27.  
    }
  28.  
    }
  29.  
    //类型三:bytes=22-2343
  30.  
    else if (ranges.length == 2) {
  31.  
    startByte = Long.parseLong(ranges[0]);
  32.  
    endByte = Long.parseLong(ranges[1]);
  33.  
    }
  34.  
     
  35.  
    } catch (NumberFormatException e) {
  36.  
    startByte = 0;
  37.  
    endByte = statObjectResponse.size() - 1;
  38.  
    }
  39.  
    }
  40.  
     
  41.  
    //要下载的长度
  42.  
    long contentLength = endByte - startByte 1;
  43.  
    //文件类型
  44.  
    String contentType = request.getServletContext().getMimeType(filename);
  45.  
     
  46.  
    //解决下载文件时文件名乱码问题
  47.  
    byte[] fileNameBytes = filename.getBytes(StandardCharsets.UTF_8);
  48.  
    filename = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
  49.  
     
  50.  
    //各种响应头设置
  51.  
    //支持断点续传,获取部分字节内容:
  52.  
    response.setHeader("Accept-Ranges", "bytes");
  53.  
    //http状态码要为206:表示获取部分内容
  54.  
    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
  55.  
    response.setContentType(contentType);
  56.  
    response.setHeader("Last-Modified", statObjectResponse.lastModified().toString());
  57.  
    //inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
  58.  
    response.setHeader("Content-Disposition", "inline;filename=" filename);
  59.  
    response.setHeader("Content-Length", String.valueOf(contentLength));
  60.  
    //Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
  61.  
    response.setHeader("Content-Range", "bytes " startByte "-" endByte "/" statObjectResponse.size());
  62.  
    response.setHeader("ETag", "\"".concat(statObjectResponse.etag()).concat("\""));
  63.  
     
  64.  
    try {
  65.  
    GetObjectResponse stream = minioClient.getObject(
  66.  
    GetObjectArgs.builder()
  67.  
    .bucket(statObjectResponse.bucket())
  68.  
    .object(statObjectResponse.object())
  69.  
    .offset(startByte)
  70.  
    .length(contentLength)
  71.  
    .build());
  72.  
    BufferedOutputStream os = new BufferedOutputStream(response.getOutputStream());
  73.  
    byte[] buffer = new byte[1024];
  74.  
    int len;
  75.  
    while ((len = stream.read(buffer)) != -1) {
  76.  
    os.write(buffer, 0, len);
  77.  
    }
  78.  
    os.flush();
  79.  
    os.close();
  80.  
    response.flushBuffer();
  81.  
    } catch (IOException e) {
  82.  
    e.printStackTrace();
  83.  
    }
  84.  
    }
  85.  
    }
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfeijii
系列文章
更多 icon
同类精品
更多 icon
继续加载