跳转至内容
  • 社区首页
  • 版块
  • 最新
  • 标签
  • 热门
折叠

GitHub中文论坛

  1. 主页
  2. 版块
  3. 技术交流
  4. Spring 大文件下载(前端视频播放)优化

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

已定时 已固定 已锁定 已移动 技术交流
3 帖子 1 发布者 5.6k 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • loveme199L 离线
    loveme199L 离线
    loveme199
    写于 最后由 loveme199 编辑
    #1

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

    1 条回复 最后回复
    0
    • loveme199L 离线
      loveme199L 离线
      loveme199
      写于 最后由 loveme199 编辑
      #2

      优化前代码

      @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
      • loveme199L 离线
        loveme199L 离线
        loveme199
        写于 最后由 编辑
        #3

        优化后。我们解析请求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
        回复
        • 在新帖中回复
        登录后回复
        • 从旧到新
        • 从新到旧
        • 最多赞同


        • 登录

        • 第一个帖子
          最后一个帖子
        0
        • 社区首页
        • 版块
        • 最新
        • 标签
        • 热门