PHP: iPad не воспроизводит MP4-видео, доставленные PHP, но при прямом доступе
Я пытаюсь найти решение для этой проблемы уже несколько дней, я попробовал все советы, которые я мог найти здесь, на stackru и других платформах. И все же, нет решения.
Я встраиваю видео через тег 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 напрямую работает и воспроизводит файл!
Тестирование:
- видеофайл: https://github.com/q2apro/videotest-ipad/raw/master/video.mp4 (проигрывается на iPad)
- видеофайл, загруженный PHP с такими же заголовками: https://github.com/q2apro/videotest-ipad/blob/master/test-headers.php (не воспроизводится на iPad) - исходный код
- видеофайл, загруженный PHP с диапазоном байтов: https://github.com/q2apro/videotest-ipad/blob/master/test-byterange.php (не воспроизводится на iPad) - исходный код
- видеофайл, загруженный 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 (пробовал оба браузера)
- мой сервер является shared-хостингом от domainfactory
- инструмент для отладки: Консоль для разработчиков Firefox 29 / WIN7
.htaccess
в тестовой папке задает MIME-тип и Accept-Ranges:
AddType video/mp4 .mp4
<IfModule mod_headers.c>
Header set Accept-Ranges "bytes"
</IfModule>
Несмотря на то, что я создал один и тот же заголовок (сравните тестовые URL 1. и 2.), iPad не воспроизводит файл через запрос PHP.
Вместо этого я всегда получаю эту поразительную кнопку воспроизведения:
Заголовки 1. (прямой вызов mp4):
Заголовки 2. Те же заголовки, что и выше, но настроены PHP (mp4 поставляется PHP):
-
Я также попытался прочитать весь видеофайл и отправить его в браузер, используя PHP- функции fread (), fpassthru () и file_get_contents(), но iPad всегда показывает значок "невозможно воспроизвести".
-
Мой размещенный сервер не поддерживает соединения, может ли это быть проблемой? IPad интерпретирует.php отличается от.mp4?
Может ли кто-нибудь помочь мне от боли? Я полностью застрял.
PS: что я пытался рассмотреть:
- Запросы диапазона байтов (206 частичное содержание) 01 02 03
- правильное кодирование видео 04
- использовали другие закодированные видео при тестировании
- отключено zlib.output_compression в PHP-скриптах
ОБНОВЛЕНИЕ: Консоль отладки
Наконец-то я получил MAC своего друга, подключил iPad, открыл Debug Console в Safari на Mac, загрузил страницу на iPad и проверил сообщения об ошибках, появляющиеся на Mac (кстати, насколько сложнее Apple может заставить нас развиваться)...). Для всех тестовых скриптов эта ошибка появляется:
Failed to load resource: Plug-in handled load
1 ответ
Вау, это было тяжело!
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 не будет воспроизводить видеофайл! Вместо этого вы получаете ошибку "Операция не может быть завершена".
Проверьте и протестируйте сами: видео ресурсы тестирования.
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 для iPad, это исправлено.
PS: пару дней исследований и хлопот только для воспроизведения видеофайла, который, кстати, работает на всех других устройствах. Это еще раз доказывает мне, что Apple добилась успеха только благодаря отличному маркетингу, а не благодаря отличному программному обеспечению.
Спасибо за ваш вклад, очень важно... Но даже ваш код не подошел для моего 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;
(к сожалению, я не могу комментировать, так как я новичок на форуме, так что это новый пост:)
Что касается сценария №3 (Третья проблема): он работал у меня во всех браузерах, включая более старые версии Safari на iOS, но не на текущих версиях Safari, ни на iOS, ни на MacOS.
Уменьшение конца каждого диапазона ($range[1]) на 1 в строке 37:
else {
$ranges[1]--;
}
... и расширив Content-Range на 1 (как предложил Конрад) в строке 41:
header('Content-Length: ' . (($ranges[1] - $ranges[0]) + 1));
... работал у меня и решил проблему для текущих версий Safari, хотя более старые версии (например, на iOS 9.3.5) теперь, похоже, больше не работают.