PHP: iPad не воспроизводит видеоролики MP4, поставляемые PHP, но при непосредственном доступе он делает

Я пытаюсь найти решение этой проблемы уже несколько дней, я пробовал все советы, которые я мог найти здесь, в stackoverflow и других платформах. И все же решения нет.

Я вставляю видео через тег HTML5:

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

Я пытаюсь доставить видеофайл MP4 через PHP, а не напрямую связывать его. Связывание файла mp4 напрямую работает и воспроизводит файл!

Тестирование:

Примечания:

  • все ссылки, приведенные выше, имеют прямой доступ/воспроизведение видеофайла без встроенного тега
  • видео работает во всех браузерах в Windows (но не в Safari/Chrome на iPad, возможно, не в iPhone).

Моя настройка:

  • тестирующее устройство: iPad iOS 6 (у меня нет mac, не удается отладить)
  • iPad с Safari и Chrome (попробовал оба браузера)
  • мой сервер является общедоступным с domainfactory
  • инструмент для отладки: Firefox 29 Web Developer Console/WIN7

.htaccess в тестовой папке задает тип MIME и Accept-Ranges:

AddType video/mp4 .mp4 

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


Даже если я создал тот же заголовок (сравните тестовые URL 1. и 2.), iPad не воспроизводит файл через запрос PHP.

Вместо этого я всегда получаю эту кнопку прокрутки:

ipad strikedthrough play button

Заголовки 1. (прямой вызов mp4):

mp4 direct call

Заголовки 2. Те же заголовки, что и выше, но настроенные PHP (mp4, поставляемые PHP):

enter image description here

-

Я также пробовал прочитать весь видеофайл и отправить его в браузер с помощью PHP fread(), fpassthru() и file_get_contents(), но iPad всегда показывает cannot- играть-значок.

-

Мой размещенный сервер не поддерживает Connection keep-alive, может ли это быть проблемой? Является ли iPad интерпретацией .php отличным от .mp4?

Может ли кто-нибудь помочь мне избавиться от боли? Я полностью застрял.



PS: Я пытался рассмотреть:

  • Запросы диапазона байтов (206 частичный контент) 01 02 03
  • правильное кодирование видео 04
  • использовали другие кодированные видео при тестировании
  • отключено zlib.output_compression в php-скриптах



UPDATE: консоль отладки

Наконец-то я получил MAC-адрес друга, связал iPad, открыл консоль Debug в Safari на Mac, загрузил страницу на iPad и проверил сообщения об ошибках, появляющиеся на Mac (кстати, насколько сложнее могла сила яблока нас развивать...). Для всех тестовых скриптов эта ошибка появляется:

Failed to load resource: Plug-in handled load

Ответ 1

Ничего себе, это было сложно!


1. Первая серьезная проблема

Оказалось, что это не проблема с кодировкой, а проблема с заголовком контейнера mp4, установленным во время процесса преобразования видео. У iPad, очевидно, проблема с видеороликами MP4, которые подготовлены для прогрессивной потоковой передачи.

Сначала я обнаружил, что в беседе здесь. После преобразования видео я всегда использовал инструмент Быстрый запуск MP4, чтобы подготовить видеофайл для прогрессивного потока. Это было необходимо для потоковой передачи видеофайла на 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

Проверьте и протестируйте его самостоятельно: ресурсы для тестирования видео.


2. Вторая проблема

В рабочей среде я всегда использовал PHP для проверки реферирования. Как я узнал, iPad не отправляет информацию о реферере. Это также предотвращает потоковое воспроизведение, и вы также увидите символ "не может играть" (значок с пропущенным воспроизведением).


3. Третья проблема

Я не мог понять, почему, но iPad принимает только потоковое видео из этого script 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 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 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 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 для iPad, это исправило его.



PS: Пару дней исследований и хлопот только для воспроизведения видеофайла, который, кстати, работает на всех других устройствах. Это снова доказывает мне, что Apple добилась успеха только из-за большого маркетинга, а не из-за большого программного обеспечения.

Ответ 2

Спасибо за ваш вклад, очень важно... Но даже ваш код не смог сделать это для моего 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;

Надеюсь, я помог кому-то! Приветствует всех