Springboot+Minio solves the problem that H5 cannot play video under IOS through fragmented download

1. Environmental Description

  • JDK 1.8
  • Springboot 2.7.5
  • Minion 8.4.5
  • WeChat official account web page implemented by Vue3

2. Problem description

The current project is based on the front-end and back-end separation architecture of springboot and vue3. The front-end is currently mainly based on H5 and displayed on the webpage of the WeChat official account. I encountered a problem when implementing video upload and online playback: my front-end colleagues said that the video cannot be played on the iPhone. At the beginning, the video tag was used uniformly. Android can play it normally, but "video playback failed" appeared on the iPhone. The front-end colleagues tried to change video.js, vue3-play, html5 api, avplay, mui-player, but they couldn't solve the problem, so they started to try the back-end to find a solution.

3. Back-end solutions

For the first time, try to change the Content-Disposition of the video request from attachment;filename=** to inline;filename=**, so that the video request can be played directly in the browser instead of being downloaded. However, the problem of video playback failure on Apple mobile phones is still not solved. And try to add "add_header Accept-Ranges bytes;" to nginx in the " Solution for iOS Cannot Play MP4 Video Files" What to do if mp4 video cannot be played on iphone ", but the problem cannot be solved.

The second time, I analyzed the video found on the Internet and found that the video embedded in the video can be played on the Apple mobile phone. By observing the request, it was found that there was a response code of 206, and began to study breakpoint downloads, and finally inspired by the two articles "05.springboot uses minio to realize segmented downloads" and "H5 Video playback video iOS breakpoint download processing" , solved the problem by minio segmented download.

4. Key knowledge points

Responses to download requests need to contain the following special attributes:

Accept-Ranges: bytes Request received bytes
Content-Length: 2 Corresponding length
Content-Range: bytes 0-1/18494715 The space behind the bytes must not be less, 0 start position, 1 end position. "/" is followed by the total file size length

Content-Disposition inline means browser direct use, attachment means download, fileName means downloaded file name

HttpResponse

Status Code: 206 Partial Content indicates that the request was successful and the body contains the requested range of data

5. Code snippet of backend download method

public void downloadSlice(String bucketName, String filename, HttpServletResponse response,
                              HttpServletRequest request) throws Exception{
        if (StringUtils.isNotBlank(filename)) {
            String range = request.getHeader("Range");
            //获取文件信息
            StatObjectResponse statObjectResponse = minioClient.statObject(
                StatObjectArgs.builder().bucket(bucketName).object(filename).build());
            //开始下载位置
            long startByte = 0;
            //结束下载位置
            long endByte = statObjectResponse.size() - 1;

            //有range的话
            if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-")) {
                range = range.substring(range.lastIndexOf("=") + 1).trim();
                String[] ranges = range.split("-");
                try {
                    //判断range的类型
                    if (ranges.length == 1) {
                        //类型一:bytes=-2343
                        if (range.startsWith("-")) {
                            endByte = Long.parseLong(ranges[0]);
                        }
                        //类型二:bytes=2343-
                        else if (range.endsWith("-")) {
                            startByte = Long.parseLong(ranges[0]);
                        }
                    }
                    //类型三:bytes=22-2343
                    else if (ranges.length == 2) {
                        startByte = Long.parseLong(ranges[0]);
                        endByte = Long.parseLong(ranges[1]);
                    }

                } catch (NumberFormatException e) {
                    startByte = 0;
                    endByte = statObjectResponse.size() - 1;
                }
            }

            //要下载的长度
            long contentLength = endByte - startByte + 1;
            //文件类型
            String contentType = request.getServletContext().getMimeType(filename);

            //解决下载文件时文件名乱码问题
            byte[] fileNameBytes = filename.getBytes(StandardCharsets.UTF_8);
            filename = new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);

            //各种响应头设置
            //支持断点续传,获取部分字节内容:
            response.setHeader("Accept-Ranges", "bytes");
            //http状态码要为206:表示获取部分内容
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            response.setContentType(contentType);
            response.setHeader("Last-Modified", statObjectResponse.lastModified().toString());
            //inline表示浏览器直接使用,attachment表示下载,fileName表示下载的文件名
            response.setHeader("Content-Disposition", "inline;filename=" + filename);
            response.setHeader("Content-Length", String.valueOf(contentLength));
            //Content-Range,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
            response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + statObjectResponse.size());
            response.setHeader("ETag", "\"".concat(statObjectResponse.etag()).concat("\""));

            try {
                GetObjectResponse stream = minioClient.getObject(
                    GetObjectArgs.builder()
                        .bucket(statObjectResponse.bucket())
                        .object(statObjectResponse.object())
                        .offset(startByte)
                        .length(contentLength)
                        .build());
                BufferedOutputStream os = new BufferedOutputStream(response.getOutputStream());
                byte[] buffer = new byte[1024];
                int len;
                while ((len = stream.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
                os.flush();
                os.close();
                response.flushBuffer();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

Guess you like

Origin blog.csdn.net/yangfande362/article/details/129416339