当前位置: 首页 > article >正文

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);
            }
        }
    }


http://www.kler.cn/a/147529.html

相关文章:

  • MongoDB自定义顺序排序
  • 大模型基础BERT——Transformers的双向编码器表示
  • C语言之MakeFile
  • 供应链管理、一件代发系统功能及源码分享 PHP+Mysql
  • 等保测评怎么做?具体流程是什么?
  • linux,一、部署LNMP环境二、配置动静分离三、地址重写四、编写systemd Unit文件
  • 超全超实用行业解决方案合集,覆盖十大行业数据应用需求
  • 基于Webserver的工业数据采集控制
  • 查swap内存使用
  • windows dockerdesktop 安装sqlserver2022
  • vue3中的customRef创建一个自定义的 ref对象
  • CCC联盟数字钥匙(一)——UWB MAC概述
  • barcode.js+elementUi——实现二维码的展示——基础积累
  • 21款奔驰GLE350升级迈巴赫踏板 老人小孩 上下车更加简单
  • Figma 插件学习(二)- 常用属性和方法
  • 基于vue的全民生鲜网上商城
  • 前端 HTML 和 JavaScript 的基础知识有哪些?
  • 纯新手发布鸿蒙的第一个java应用
  • Linux telnet命令详解:通过TCP/IP网络连接与管理远程机器(附实例教程和注意事项)
  • Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业
  • 【JAVA】SpringBoot + mongodb 分页、排序、动态多条件查询及事务处理
  • 开源四轴协作机械臂ultraArm激光雕刻技术案例!
  • 【开源】基于JAVA的衣物搭配系统
  • jmeter使用beanshell
  • AMP State Evolution的计算:以伯努利先验为例
  • Python自动化办公:PDF文件的分割与合并