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

1.说明

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

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

2.代码

java 复制代码
    @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);
            }
        }
    }
相关推荐
苹果醋3几秒前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花5 分钟前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端8 分钟前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan15 分钟前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
可峰科技17 分钟前
斗破QT编程入门系列之二:认识Qt:编写一个HelloWorld程序(四星斗师)
开发语言·qt
customer0819 分钟前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
全栈开发圈21 分钟前
新书速览|Java网络爬虫精解与实践
java·开发语言·爬虫
WaaTong24 分钟前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
面试鸭26 分钟前
离谱!买个人信息买到网安公司头上???
java·开发语言·职场和发展