java大视频在线预览(支持断点下载)

1.说明

        大视频的在线预览,如果不支持断点下载,将无法在苹果手机上播放,同时不支持进度条拖动.

        之所以这样,是因为视频文件太大了,通过二进制流向浏览器传输时,整个文件尚未传输完成时,会被浏览器强制关闭流,不再接收,等缓存播放到一定程度时,浏览器会再次向后端请求视频文件,同时附带range参数,指定获取数据范围,后端需支持对range参数的处理.

2.代码

    @ResponseBody
    @GetMapping(value = "/preview")
    @Operation(summary = "在线预览", description = "文件在浏览器中预览")
    public void Preview(@RequestParam String fileInfoId, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取存储文件
        var fileInfo = fileInfoService.Get(fileInfoId);
        if (fileInfo == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
            return;
        }

        //设置页面缓存
        if (MisFileType.PICTURE.equals(fileInfo.getFile_type()) || MisFileType.TEXT.equals(fileInfo.getFile_type())) {
            response.setHeader("Cache-Control", "max-age=60");//页面缓存时间60秒
        }

        //断点下载
        ResumeDownload(request, response, fileInfo, "inline");
    }


    /**
     * 支持断点重新下载文件
     *
     * @param fileInfo    文件
     * @param disposition 下载方式 inline:内嵌 attachment:附件
     */
    private static void ResumeDownload(HttpServletRequest request, HttpServletResponse response,
                                       FileInfoOutput fileInfo, String disposition) throws IOException {
        //获取存储文件
        String storageFilePath = FileConfig.Path + File.separator + fileInfo.getFile_path();
        File storageFile = new File(storageFilePath);
        if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {//相对路径未找到文件
            storageFilePath = fileInfo.getFile_absolute_path();//根据绝对路径寻找文件
            storageFile = new File(storageFilePath);
            if (!StringUtils.hasLength(storageFilePath) || !storageFile.exists()) {
                response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found!");
                return;
            }
        }

        //推断类型
        String mimeType = Files.probeContentType(storageFile.toPath());
        if (!StringUtils.hasLength(mimeType)) {
            URL url = new URL("file:///" + storageFilePath);
            mimeType = url.openConnection().getContentType();
        }
        //下载开始位置
        long startByte = 0;
        //下载结束位置
        long endByte = storageFile.length() - 1;
        //获取下载范围
        String range = request.getHeader("range");
        if (range != null && range.contains("bytes=") && range.contains("-")) {
            range = range.substring(range.lastIndexOf("=") + 1).trim();
            String[] rangeArray = range.split("-");
            if (rangeArray.length == 1) {
                //Example: bytes=1024-
                if (range.endsWith("-")) {
                    startByte = Long.parseLong(rangeArray[0]);
                } else { //Example: bytes=-1024
                    endByte = Long.parseLong(rangeArray[0]);
                }
            }
            //Example: bytes=2048-4096
            else if (rangeArray.length == 2) {
                startByte = Long.parseLong(rangeArray[0]);
                endByte = Long.parseLong(rangeArray[1]);
            }
        }
        long contentLength = endByte - startByte + 1;

        //HTTP 响应头设置
        //断点续传,HTTP 状态码必须为 206,否则不设置,如果非断点续传设置 206 状态码,则浏览器无法下载
        if (range != null) {
            log.trace("断点下载range:{},总大小:{},{}({})", range, storageFile.length(), fileInfo.getFile_name(), fileInfo.getId());
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        }

        if (StringUtils.hasLength(mimeType)) {
            response.setContentType(mimeType);
            response.setHeader("Content-Type", mimeType);
        }
        response.setHeader("Content-Length", String.valueOf(contentLength));
        response.setHeader("Accept-Ranges", "bytes");
        //Content-Range: 下载开始位置-下载结束位置/文件大小
        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + storageFile.length());
        //Content-disposition: inline; filename=xxx.xxx 表示浏览器内嵌显示该文件
        response.setHeader("Content-Disposition", disposition + "; filename=" + URLEncoder.encode(fileInfo.getFile_name(), StandardCharsets.UTF_8));

        //传输文件流
        BufferedOutputStream outputStream = null;
        RandomAccessFile randomAccessFile = null;
        //已传送数据大小
        long transmittedLength = 0;
        try {
            //以只读模式设置文件指针偏移量
            randomAccessFile = new RandomAccessFile(storageFile, "r");
            randomAccessFile.seek(startByte);

            outputStream = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[4096];
            int len;
            while (transmittedLength < contentLength && (len = randomAccessFile.read(buff)) != -1) {
                outputStream.write(buff, 0, len);
                transmittedLength += len;
            }

            outputStream.flush();
            response.flushBuffer();
            log.trace("下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength,
                    storageFile.length(), fileInfo.getFile_name(), fileInfo.getId());
        } catch (IOException e) {
            if (StringUtils.hasLength(range)) {
                response.setHeader("Content-Range", "bytes " + startByte + "-" + (startByte + transmittedLength) + "/" + storageFile.length());
                log.trace("断点下载完毕:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(),
                        fileInfo.getFile_name(), fileInfo.getId());
            } else {
                log.info("下载停止:{}-{},下载量:{},总大小:{},{}({})", startByte, endByte, transmittedLength, storageFile.length(),
                        fileInfo.getFile_name(), fileInfo.getId());
            }
        } finally {
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                log.error("下载异常," + fileInfo.getId(), e);
            }
        }
    }

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kenick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值