PHP:iPad不播放由PHP提供的MP4视频,但如果直接访问则可以

时间:2014-06-04 16:35:38

标签: php html5 ipad video

我试图找到这个问题的解决方案已经有几天了,我尝试了所有可以在stackoverflow和其他平台上找到的建议。但仍然没有解决方案。

我正在通过HTML5视频代码嵌入视频:

 <video poster="thumb.png" controls="controls" preload="none" width="640" height="480">
    <source src="provider.php?secure=12345" type="video/mp4">
 </video>

我尝试通过PHP传送MP4视频文件,而不是直接链接它。链接mp4文件直接工作并播放文件!

测试:

  1. 视频文件:https://github.com/q2apro/videotest-ipad/raw/master/video.mp4(在iPad上播放)
  2. 由PHP加载的视频文件,标题相同:https://github.com/q2apro/videotest-ipad/blob/master/test-headers.php(不在iPad上播放) - Sourcecode
  3. PHP加载字节范围的视频文件:https://github.com/q2apro/videotest-ipad/blob/master/test-byterange.php(不在iPad上播放) - Sourcecode
  4. PHP使用字节范围加载的视频文件(另一个脚本):https://github.com/q2apro/videotest-ipad/blob/master/test-byterange-2.php(不在iPad上播放,警告&#34;操作无法完成&#34;) - Sourcecode < / LI>

    注意:

    • 以上所有链接都是直接访问/播放视频文件而不使用嵌入标记
    • 视频适用于Windows中的所有浏览器(但不适用于iPad上的Safari / Chrome,也可能不适用于iPhone)

    我的设置:

    • 测试设备:iPad iOS 6(我没有Mac,cannot debug
    • 使用Safari和Chrome的iPad(尝试过两种浏览器)
    • 我的服务器是domainfactory
    • 的共享托管服务器
    • 调试工具:Firefox 29 Web Developer Console / WIN7

    test文件夹中的.htaccess设置MIME类型和Accept-Ranges:

    AddType video/mp4 .mp4 
    
    <IfModule mod_headers.c>
       Header set Accept-Ranges "bytes"
    </IfModule>
    


    即使我创建了相同标题(比较测试网址1.2.),iPad也没有通过PHP请求播放该文件。

    相反,我总是得到这个严格的播放按钮:

    ipad strikedthrough play button

    1. (direct mp4 call)的标题:

    mp4 direct call

    2.的标题与上面相同的标题,但由PHP设置(由PHP提供的mp4):

    enter image description here

    -

    我还尝试阅读整个视频文件并使用PHP fread()fpassthru()file_get_contents()将其发送到浏览器,但iPad始终显示无效 - 播放图标。

    -

    我的托管服务器不提供Connection keep-alive,这可能是个问题吗? iPad解释.php与.mp4不同吗?

    有人可以帮我摆脱痛苦吗?我完全陷入困境。



    PS:我试图考虑的事情:

    • 字节范围请求(206个部分内容)01 02 03
    • 正确的视频编码04
    • 在测试时使用其他编码视频
    • 在php脚本中禁用zlib.output_compression



    更新:调试控制台

    我终于得到了朋友的MAC,连接了iPad,在Mac上的Safari中打开了调试控制台,在iPad上加载了页面并检查了Mac上出现的错误信息(顺便说一下,苹果强大了多么复杂?我们要发展......)。对于所有测试脚本,将出现此错误:

    Failed to load resource: Plug-in handled load
    

3 个答案:

答案 0 :(得分:14)

哇,这太难了!


1。第一个主要问题

事实证明,没有编码问题,但在视频转换过程中设置的mp4容器标题存在问题 - iPad显然存在为渐进式流式传输准备的MP4视频的问题。

首先,我在对话here中发现了这一点。转换视频后,我总是使用工具MP4 Fast Start为渐进流准备视频文件。这对于将视频文件分段(逐步)流式传输到Flash Player是必要的,因此它不会加载整个文件(并且用户必须等待)。

Handbrake有一个类似的设置,称为Web Optimized。它也是这样:

Web Optimized
Also known as "Fast Start"
This places the container header at the start of the file, optimizing it for streaming across the web.

如果启用此功能并转换视频,iPad将无法播放视频文件!相反,您会收到错误“操作无法完成”。

ipad strikedthrough play button

自行检查并测试:video test resources


2。第二个问题

在生产环境中,我总是使用PHP来检查引用者。我发现,iPad不会发送引用信息。这也会阻止流式传输,您还会看到无法播放的符号(通过播放图标)。


第3。第三个问题

我找不到原因,但iPad只接受来自此脚本的视频流http://ideone.com/NPSlw5

<?php
// disable zlib so that progress bar of player shows up correctly
if(ini_get('zlib.output_compression')) {
    ini_set('zlib.output_compression', 'Off'); 
}

$folder = '.'; 
$filename = 'video.mp4';
$path = $folder.'/'.$filename;

// from: http://licson.net/post/stream-videos-php/
if (file_exists($path)) {
    // Clears the cache and prevent unwanted output
    ob_clean();

    $mime = "video/mp4"; // The MIME type of the file, this should be replaced with your own.
    $size = filesize($path); // The size of the file

    // Send the content type header
    header('Content-type: ' . $mime);

    // Check if it's a HTTP range request
    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] = $size - 1;
        }

        // Send the appropriate headers
        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
                $size // Total size of the file
            )
        );

        // It's time to output the file
        $f = fopen($path, '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: ' . $size);

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

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

}
die();

?>


我希望这些信息可以帮助其他人解决问题。


更新:三个月后,在生产环境中,我的一些用户仍然报告了播放问题。 Safari似乎还有另一个问题。我建议他们使用Chrome for iPad,修复它。



PS:几天的研究和麻烦只是播放一个视频文件,顺便说一句,它运行在所有其他设备上。这再一次向我证明,苹果公司因为出色的营销而获得了成功,而不是因为有很好的软件。

答案 1 :(得分:0)

感谢您的贡献,非常重要... 但是,即使您的代码也无法在我的iPhone上使用。

即使我仍然不知道为什么,下面的代码也对我有用。 可能是这样:

header('HTTP/1.1 416 Requested Range Not Satisfiable');

我从这里得到的: https://github.com/tikiatua/internal-assets-plugin/issues/9

$fp = fopen($filepath, "rb");
    $size = filesize($filepath);
    $length = $size;
    $start = 0;
    $end = $size - 1;
    header('Content-type: video/mp4');
    header("Accept-Ranges: 0-$length");
    if (isset($_SERVER['HTTP_RANGE'])) {
        $c_start = $start;
        $c_end = $end;
        list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);

        if (strpos($range, ',') !== false) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }

        if ($range == '-') {
            $c_start = $size - substr($range, 1);
        } else {
            $range = explode('-', $range);
            $c_start = $range[0];
            $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
        }

        $c_end = ($c_end > $end) ? $end : $c_end;

        if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header("Content-Range: bytes $start-$end/$size");
            exit;
        }

        $start = $c_start;
        $end = $c_end;
        $length = $end - $start + 1;
        fseek($fp, $start);
        header('HTTP/1.1 206 Partial Content');
    }

    header("Content-Range: bytes $start-$end/$size");
    header("Content-Length: ".$length);

    $buffer = 1024 * 8;

    while(!feof($fp) && ($p = ftell($fp)) <= $end) {
        if ($p + $buffer > $end) {
            $buffer = $end - $p + 1;
        }
        set_time_limit(0);
        echo fread($fp, $buffer);
        flush();
    }

    fclose($fp);
    exit;

希望我帮助了某人! 欢呼大家

答案 2 :(得分:0)

(不幸的是,由于我是该论坛的新手,所以我无法发表评论,所以这是新帖子:)

关于#3脚本(第三个问题):它适用于所有浏览器,包括iOS上的Safari的较早版本,但不适用于iOS和MacOS的当前版本的Safari。

在第37行中将每个范围($ ranges [1])的末尾减少1:

else {
    $ranges[1]--; 
}

...并在第41行中将内容范围扩展1(如Konrad建议的那样):

header('Content-Length: ' . (($ranges[1] - $ranges[0]) + 1));

...为我工作,并解决了当前Safari版本的问题,尽管旧版本(例如,在iOS 9.3.5上)现在似乎不再起作用。