Mac 和 IOS 14 上的 Safari 无法播放 HTML 5 MP4 视频

时间:2021-03-08 15:14:13

标签: ios node.js ffmpeg symfony4 mp4

所以我开发了一个使用节点作为后端的聊天应用程序。当用户在他们的 iphone 上选择一个视频时,它通常是 .mov 格式,所以当它被发送到节点服务器时,它会使用 ffmpeg 转换为 mp4。一切正常,然后如果我在 Mac 上的 Chrome 中再次加载我的聊天,视频会像 mp4 一样播放。

enter image description here

此屏幕截图显示视频嵌入在那里,设置为 mp4 但它不会在我的 Mac 或我的手机上的 Safari 中播放,实际上它只是将视频显示为 0 秒长,但我可以在 chrome 中播放它,也可以直接访问嵌入网址下载mp4文件。

有什么想法吗?我已将其转换为 mp4 以防止出现此类情况,但 safari 似乎甚至不喜欢 mp4 文件。

服务私有文件的后端部分在 Symfony 4 (PHP) 中:

/**
     * @Route("/private/files/download/{base64Path}", name="downloadFile")
     * @param string $base64Path
     * @param Request $request
     * @return Response
     */
    public function downloadFile(string $base64Path, Request $request) : Response
    {


        // get token
        if(!$token = $request->query->get('token')){
            return new Response('Access Denied',403);
        }



        /** @var UserRepository $userRepo */
        $userRepo = $this->getDoctrine()->getRepository(User::class);

        /** @var User $user */
        if(!$user = $userRepo->findOneBy(['deleted'=>false,'active'=>true,'systemUser'=>false,'apiKey'=>$token])){
            return new Response('Access Denied',403);
        }



        // get path
        if($path = base64_decode($base64Path)){

            // make sure the folder we need exists
            $fullPath = $this->getParameter('private_upload_folder') . '/' . $path;



            if(!file_exists($fullPath)){
                return new Response('File Not Found',404);
            }

        

            $response = new Response();
            $response->headers->set('Content-Type', mime_content_type($fullPath));
            $response->headers->set('Content-Disposition', 'inline; filename="' . basename($fullPath) . '"');
            $response->headers->set('Content-Length', filesize($fullPath));
            $response->headers->set('Pragma', "no-cache");
            $response->headers->set('Expires', "0");
            $response->headers->set('Content-Transfer-Encoding', "binary");

            $response->sendHeaders();

            $response->setContent(readfile($fullPath));

            return $response;
        }

        return new Response('Invalid Path',404);
    }

在尝试嵌入视频时,除了 safari 之外,这在任何地方都可以正常工作。这样做是因为视频不是公开的,需要访问令牌。

更新:这是一个 mp4 的测试链接,您必须允许不安全的证书,因为它位于快速测试子域中。如果你用 chrome 打开它,你会看到我的 3d 打印机固化站的 3 秒视频,如果你在 safari 中加载相同的链接,你会看到它不起作用

https://tester.nibbrstaging.com/private/files/download/Y2hhdC83Nzk1Y2U2MC04MDFmLTExZWItYjkzYy1lZjI4ZGYwMDhkOTMubXA0?token=6ab1720bfe922d44208c25f655d61032

服务器在带有 Apache 的 cPanel 上运行,我认为这可能与视频需要流式传输有关?

在 SAFARI 中有效但现在在 Chrome 中损坏的更新代码:

Chrome 现在提供 Content-Length: 0 但它在 safari 中运行良好。

public function downloadFile(string $base64Path, Request $request) : ?Response
    {

        ob_clean();

        // get token
        if(!$token = $request->query->get('token')){
            return new Response('Access Denied',403);
        }


        

        /** @var UserRepository $userRepo */
        $userRepo = $this->getDoctrine()->getRepository(User::class);

        /** @var User $user */
        if(!$user = $userRepo->findOneBy(['deleted'=>false,'active'=>true,'systemUser'=>false,'apiKey'=>$token])){
            return new Response('Access Denied',403);
        }



        // get path
        if($path = base64_decode($base64Path)){

            // make sure the folder we need exists
            $fullPath = $this->getParameter('private_upload_folder') . '/' . $path;



            if(!file_exists($fullPath)){
                return new Response('File Not Found',404);
            }


            $filesize = filesize($fullPath);
            $mime = mime_content_type($fullPath);

            header('Content-Type: ' . $mime);

            if(isset($_SERVER['HTTP_RANGE'])){

                // Parse the range header to get the byte offset
                $ranges = array_map(
                    'intval', // Parse the parts into integer
                    explode(
                        '-', // The range separator
                        substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header
                    )
                );



                // If the last range param is empty, it means the EOF (End of File)
                if(!$ranges[1]){
                    $ranges[1] = $filesize - 1;
                }

                header('HTTP/1.1 206 Partial Content');
                header('Accept-Ranges: bytes');
                header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range

                // Send the ranges we offered
                header(
                    sprintf(
                        'Content-Range: bytes %d-%d/%d', // The header format
                        $ranges[0], // The start range
                        $ranges[1], // The end range
                        $filesize // Total size of the file
                    )
                );

                // It's time to output the file
                $f = fopen($fullPath, 'rb'); // Open the file in binary mode
                $chunkSize = 8192; // The size of each chunk to output

                // Seek to the requested start range
                fseek($f, $ranges[0]);

                // Start outputting the data
                while(true){
                    // Check if we have outputted all the data requested
                    if(ftell($f) >= $ranges[1]){
                        break;
                    }

                    // Output the data
                    echo fread($f, $chunkSize);

                    // Flush the buffer immediately
                    @ob_flush();
                    flush();
                }
            }else{

                // It's not a range request, output the file anyway
                header('Content-Length: ' . $filesize);

                // Read the file
                @readfile($filesize);

                // and flush the buffer
                @ob_flush();
                flush();



            }

        }else {

            return new Response('Invalid Path', 404);
        }
    }

我在 chrome 中注意到它正在发送这样的范围标头:

范围:字节=611609-

Safari 发送到哪里

范围:字节=611609-61160

因此,出于某种原因,chrome 缺少第二个范围数量,这显然意味着我的代码无法找到第二个的范围编号。

不管我做什么,我都无法在 chrome 和 safari 中使用它。 Safari 需要字节范围部分,chrome 似乎请求它然后发送对完整文件的新请求,但即使代码的完整文件部分也会出现 500 错误。如果我取出字节范围位,那么它在 chrome 中可以正常工作,但在 safari 中不能正常工作。

更新:

Chrome 中发生了一些奇怪的事情:

对于我正在测试的视频,它发出了 3 个范围请求:

REQUEST 1 HEADERS - 请求字节 0-(到文件末尾)

GET /private/files/download/Y2hhdC83Nzk1Y2U2MC04MDFmLTExZWItYjkzYy1lZjI4ZGYwMDhkOTMubXA0?token=6ab1720bfe922d44208c25f655d61032 HTTP/1.1

Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36
Accept-Encoding: identity;q=1, *;q=0
Accept: */*
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: video
Referer: https://gofollow.vip/
Accept-Language: en-US,en;q=0.9
Range: bytes=0-

响应会返回文件中的所有字节,这正是 Chrome 所要求的:

HTTP/1.1 206 Partial Content
Date: Wed, 10 Mar 2021 12:35:54 GMT
Server: Apache
Accept-Ranges: bytes
Content-Length: 611609
Content-Range: bytes 0-611609/611610
Vary: User-Agent
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: video/mp4

第二个请求头:现在要求 589824 到文件末尾:

Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36
Accept-Encoding: identity;q=1, *;q=0
Accept: */*
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: video
Referer: https://gofollow.vip/
Accept-Language: en-US,en;q=0.9
Range: bytes=589824-

响应义务:

HTTP/1.1 206 Partial Content
Date: Wed, 10 Mar 2021 12:35:55 GMT
Server: Apache
Accept-Ranges: bytes
Content-Length: 21785
Content-Range: bytes 589824-611609/611610
Vary: User-Agent
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: video/mp4

然后它发出的第 3 个请求会导致内部服务器出错,这一次实际上是在请求最后一个字节:

GET /private/files/download/Y2hhdC83Nzk1Y2U2MC04MDFmLTExZWItYjkzYy1lZjI4ZGYwMDhkOTMubXA0?token=6ab1720bfe922d44208c25f655d61032 HTTP/1.1

Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36
Accept-Encoding: identity;q=1, *;q=0
Accept: */*
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: video
Referer: https://gofollow.vip/
Accept-Language: en-US,en;q=0.9
Range: bytes=611609-

响应 - 内容长度为 0,因为请求的字节数和返回的字节数没有区别:

HTTP/1.1 500 Internal Server Error
Date: Wed, 10 Mar 2021 12:35:56 GMT
Server: Apache
Accept-Ranges: bytes
Cache-Control: max-age=0, must-revalidate, private
X-Frame-Options: DENY
X-XSS-Protection: 1
X-Content-Type-Options: nosniff
Referrer-Policy: origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
Expires: Wed, 10 Mar 2021 12:35:56 GMT
Content-Length: 0
Content-Range: bytes 611609-611609/611610
Vary: User-Agent
Connection: close
Content-Type: text/html; charset=UTF-8

1 个答案:

答案 0 :(得分:0)

毕竟我终于找到了问题所在。它要求第三个请求中的最后一个字节和以下代码来计算发回内容的大小:

header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range

生成的长度为 0 作为最后一个字节,直到文件末尾变为 0。所以我将其更改为加 1 到末尾:

header('Content-Length: ' . (($ranges[1] - $ranges[0])+1)); // The size of the range

我以为我可能会遇到这个问题,但事实证明它适用于两种浏览器