Springboot + Minio résout le problème que H5 ne peut pas lire de vidéo sous IOS via un téléchargement fragmenté

1. Description environnementale

  • JDK 1.8
  • Springboot 2.7.5
  • Minion 8.4.5
  • Page Web officielle du compte WeChat mise en œuvre par Vue3

2. Description du problème

Le projet actuel est basé sur l'architecture de séparation front-end et back-end de springboot et vue 3. Le front-end est actuellement principalement affiché sur la page Web du compte officiel WeChat basé sur H5. J'ai rencontré un problème lors de la mise en œuvre du téléchargement de la vidéo et de la lecture en ligne : mes collègues frontaux ont déclaré que la vidéo ne pouvait pas être lue sur l'iPhone. Au début, la balise vidéo était utilisée de manière uniforme. Android peut la lire normalement, mais "la lecture de la vidéo a échoué " est apparu sur l'iPhone. Les collègues du front-end ont essayé de changer video.js, vue3-play, html5 api, avplay, mui-player, mais ils n'ont pas pu résoudre le problème, alors ils ont commencé à essayer le back-end pour trouver une solution.

3. Solutions back-end

Pour la première fois, essayez de modifier le Content-Disposition de la requête vidéo de attachment;filename=** à inline;filename=**, afin que la requête vidéo puisse être lue directement dans le navigateur au lieu d'être téléchargée. Cependant, le problème de l'échec de la lecture vidéo sur les téléphones mobiles Apple n'est toujours pas résolu. Et essayez d' ajouter "add_header Accept-Ranges bytes ;" à nginx dans la " Solution pour iOS ne peut pas lire les fichiers vidéo MP4" Que faire si la vidéo mp4 ne peut pas être lue sur l'iphone ", mais le problème ne peut pas être résolu.

La deuxième fois, j'ai analysé la vidéo trouvée sur Internet et j'ai constaté que la vidéo intégrée à la vidéo pouvait être lue sur le téléphone mobile Apple. En observant la requête, il a été constaté qu'il y avait un code de réponse de 206, et a commencé à étudier les téléchargements de points d'arrêt, et finalement inspiré par les deux articles "05.springboot utilise minio pour réaliser des téléchargements segmentés" et "H5 Video playback video iOS breakpoint traitement du téléchargement" , a résolu le problème par téléchargement segmenté minio.

4. Connaissances clés

Les réponses aux demandes de téléchargement doivent contenir les attributs spéciaux suivants :

Accept-Ranges : octets Requête reçue octets
Content-Length : 2 Longueur correspondante
Content-Range : octets 0-1/18494715 L'espace derrière les octets ne doit pas être inférieur, 0 position de début, 1 position de fin. "/" est suivi de la longueur totale du fichier

Content-Disposition inline signifie l'utilisation directe du navigateur, la pièce jointe signifie le téléchargement, fileName signifie le nom du fichier téléchargé

HttpResponse

Code d'état : 206 Contenu partiel indique que la demande a réussi et que le corps contient la plage de données demandée

5. Extrait de code de la méthode de téléchargement backend

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

Je suppose que tu aimes

Origine blog.csdn.net/yangfande362/article/details/129416339
conseillé
Classement