论坛

    • 登录
    • 版块
    • 最新
    • 标签
    • 热门

    Spring 大文件下载(前端视频播放)优化

    技术交流
    1
    3
    312
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • loveme199
      loveme199 最后由 loveme199 编辑

      文件是存在对象存储 MinIO 上面的,这里是视频文件,前端页面需要播放。
      Java后端开发一个文件下载接口,这里没有直接提供minio的地址。问题是前端播放器播放视频的时候,会把整个视频下载完毕,播放也不能拖拽播放,只能从头到尾顺序播放。
      用户体验很差

      1 条回复 最后回复 回复 引用 0
      • loveme199
        loveme199 最后由 loveme199 编辑

        优化前代码

        @GetMapping("/download")
        public void download(HttpServletResponse res, @RequestParam("fileName") String fileName) {
                GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(BUCKETNAME)
                        .object(fileName).build();
                try (GetObjectResponse response = minioClient.getObject(objectArgs)){
                    byte[] buf = new byte[1024];
                    int len;
                    try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
                        while ((len=response.read(buf))!=-1){
                            os.write(buf,0,len);
                        }
                        os.flush();
                        byte[] bytes = os.toByteArray();
                        res.setCharacterEncoding("utf-8");
                        res.setContentType("application/octet-stream");
                        res.setContentLength(bytes.length);
                        res.addHeader("Content-Disposition", "attachment;filename=" + FileUtil.getName(fileName));
                        try (ServletOutputStream stream = res.getOutputStream()){
                            stream.write(bytes);
                            stream.flush();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        
        1 条回复 最后回复 回复 引用 0
        • loveme199
          loveme199 最后由 编辑

          优化后。我们解析请求header 中 Range实现分片下载。这样就能实现拖拽播放,不用将文件一次性全部下载完毕。

          @GetMapping("/download")
              public void download(HttpServletResponse res, HttpServletRequest request, @RequestParam("fileName") String fileName) {
                  GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(BUCKETNAME)
                          .object(fileName).build();
                  StatObjectArgs statObjectArgs = StatObjectArgs.builder().bucket(BUCKETNAME).object(fileName).build();
          
                  long fileSize;
                  try {
                      fileSize = minioClient.statObject(statObjectArgs).size();
                  } catch (Exception e) {
                      log.error("文件下载失败:{}", fileName, e);
                      return;
                  }
          
                  String range = request.getHeader("Range");
                  if(range != null && range.startsWith("bytes=")) {
                      try {
                          int idx = range.indexOf("-");
                          long start = Long.parseLong(range.substring("bytes=".length(), idx));
                          long end = minioClient.statObject(statObjectArgs).size() - 1;
                          if (idx + 1 < range.length()) {
                              end = Long.parseLong(range.substring(idx + 1));
                          }
                          long length = end - start + 1;
                          InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(BUCKETNAME).object(fileName).offset(start).length(length).build());
                          res.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                          res.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileSize);
                          res.setContentType("application/octet-stream");
                          res.setHeader("Content-Disposition", "attachment; filename=" + FileUtil.getName(fileName));
                          res.setContentLength((int)length);
                          try {
                              IOUtils.copy(inputStream, res.getOutputStream());
                              inputStream.close();
                              res.flushBuffer();
                          } catch (ClientAbortException e) {
                              // pass
                          }
                      } catch (Exception e) {
                          log.error("断点下载失败:{}", fileName, e);
                      }
                  } else {
                      try (InputStream is = minioClient.getObject(objectArgs)) {
                          res.setContentType("application/octet-stream");
                          res.addHeader("Content-Disposition", "attachment;filename=" + FileUtil.getName(fileName));
                          res.setContentLength((int)fileSize);
                          IOUtils.copy(is, res.getOutputStream());
                          res.flushBuffer();
                      } catch (Exception e) {
                          log.error("文件下载失败:{}", fileName, e);
                      }
                  }
              }
          
          1 条回复 最后回复 回复 引用 0
          • First post
            Last post