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 напрямую работает и воспроизводит файл!

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

  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) – Исходный код
  3. видеофайла, загруженного PHP с помощью Byte Ranges: https://github.com/q2apro/videotest-ipad/blob/master/test-byterange.php (не воспроизводится на iPad) – Исходный код
  4. видеофайла, загруженного PHP с помощью Byte Ranges (еще один скрипт): https://github.com/q2apro/videotest-ipad/blob/master/test-byterange-2.php (не играя на iPad, предупреждение «Операция может не будет завершено ") – Исходный код

Заметки:

  • все вышеперечисленные ссылки имеют прямой доступ / воспроизведение видеофайла без встроенного тега
  • видео работает во всех браузерах в 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

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

прямой вызов mp4

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

введите описание изображения здесь

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

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

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

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

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

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

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

 Failed to load resource: Plug-in handled load 

Вау, это было сложно!

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

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

Сначала я обнаружил это в беседе здесь . После преобразования видео я всегда использовал инструмент 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

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

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(); ?> в <?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 для iPad , это исправлено.

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