MP4 проигрывается при прямом доступе, но не при чтении через PHP на iOS
Я использую PHP-скрипт для проверки видео запросов перед их обслуживанием. Этот сценарий работает как положено на рабочем столе, с Safari и Chrome. Но на iOS я получаю сломанную кнопку воспроизведения.
Я уверен, что видео правильно закодировано для iPhone/iPad, потому что, когда я обращаюсь к нему напрямую, оно работает как положено.
Соответствующий код PHP:
$file_name = 'test-video.mp4';
$file_size = (string)(filesize($file_name));
header('Content-Type: video/mp4');
header('Content-Length: '.$file_size);
readfile_chunked($file_name);
exit;
(readfile_chunked()
похож на readfile()
но для очень больших файлов, которые можно найти в комментариях на странице руководства PHP: http://php.net/manual/en/function.readfile.php. В любом случае, test-video.mp4
составляет всего ~5 МБ, что меньше предела памяти - и в этом случае я действительно могу заменить в обычном readfile()
и производить точно такое же поведение.)
Заголовки, которые я получаю при доступе test-video.mp4
непосредственно являются:
Accept-Ranges:bytes
Connection:Keep-Alive
Content-Length:5558749
Content-Type:video/mp4
Date:Sun, 27 Jun 2010 21:02:09 GMT
Etag:"1c04757-54d1dd-489944c5a6400"
Keep-Alive:timeout=10, max=30
Last-Modified:Tue, 22 Jun 2010 01:25:36 GMT
Server:Apache/2.2.15 (CentOS) mod_ssl/2.2.15 0.9.8l DAV/2 mod_auth_passthrough/2.1 FrontPage/5.0.2.2635
Заголовки из скрипта PHP:
Connection:Keep-Alive
Content-Disposition:inline; filename="test-video.mp4"
Content-Length:5558749
Content-Type:video/mp4
Date:Sun, 27 Jun 2010 21:03:32 GMT
Keep-Alive:timeout=10, max=15
Server:Apache/2.2.15 (CentOS) mod_ssl/2.2.15 0.9.8l DAV/2 mod_auth_passthrough/2.1 FrontPage/5.0.2.2635
X-Powered-By:PHP/5.2.13
Я пробовал много разных комбинаций заголовков, даже сопоставляя их точно с заголовками из прямого запроса, но безрезультатно.
У кого-нибудь был успех, обслуживающий видео HTML5 через PHP на iOS?
[Примечание: я бы попробовал использовать X-Sendfile, но сайт находится на общем хосте с очень ограниченным доступом.]
РЕДАКТИРОВАТЬ: Я читал, что iOS может быть чувствительным к расширениям файлов, поэтому я попытался настроить RewriteRule, который переписывает запросы MP4 обратно в мой оригинальный скрипт PHP, но это тоже не помогло.
6 ответов
Если вы сами обрабатываете это так, то вам также придется обрабатывать запросы в диапазоне байтов.
Пытаться:
$arquivo_caminho = 'path\file'
if (is_file($arquivo_caminho)){
header("Content-type: video/mp4"); // change mimetype
if (isset($_SERVER['HTTP_RANGE'])){ // do it for any device that supports byte-ranges not only iPhone
rangeDownload($arquivo_caminho);
} else {
header("Content-length: " . filesize($arquivo_caminho));
readfile($arquivo_caminho);
} // fim do if
} // fim do if
function rangeDownload($file){
$fp = @fopen($file, 'rb');
$size = filesize($file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
// Now that we've gotten so far without errors we send the accept range header
/* At the moment we only support single ranges.
* Multiple ranges requires some more work to ensure it works correctly
* and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
*
* Multirange support annouces itself with:
* header('Accept-Ranges: bytes');
*
* Multirange content must be sent with multipart/byteranges mediatype,
* (mediatype = mimetype)
* as well as a boundry header to indicate the various chunks of data.
*/
header("Accept-Ranges: 0-$length");
// header('Accept-Ranges: bytes');
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
if (isset($_SERVER['HTTP_RANGE'])){
$c_start = $start;
$c_end = $end;
// Extract the range string
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
// Make sure the client hasn't sent us a multibyte range
if (strpos($range, ',') !== false){
// (?) Shoud this be issued here, or should the first
// range be used? Or should the header be ignored and
// we output the whole content?
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
// (?) Echo some info to the client?
exit;
} // fim do if
// If the range starts with an '-' we start from the beginning
// If not, we forward the file pointer
// And make sure to get the end byte if spesified
if ($range{0} == '-'){
// The n-number of the last bytes is requested
$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;
} // fim do if
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes can not be larger than $end.
$c_end = ($c_end > $end) ? $end : $c_end;
// Validate the requested range and return an error if it's not correct.
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");
// (?) Echo some info to the client?
exit;
} // fim do if
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1; // Calculate new content length
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
} // fim do if
// Notify the client the byte range we'll be outputting
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
// Start buffered download
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end){
if ($p + $buffer > $end){
// In case we're only outputtin a chunk, make sure we don't
// read past the length
$buffer = $end - $p + 1;
} // fim do if
set_time_limit(0); // Reset time limit for big files
echo fread($fp, $buffer);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
} // fim do while
fclose($fp);
} // fim do function
Как было сказано выше для потоковой передачи или воспроизведения видео MP4 с использованием PHP, вам нужно будет обрабатывать байтовые диапазоны, если вы хотите правильное воспроизведение в Safari и iOS.
rangeDownload()
Функция, упомянутая в предыдущих ответах, делает работу довольно хорошо.
Я хочу упомянуть еще одну часть этой головоломки - убедитесь, что источник в видео заканчивается .mp4
как в <video source="url/yourfile.php/referenceForFile.mp4">
, Это делает браузер тем, что это видеофайл, и он начинает воспринимать его как единое целое.
внутри yourfile.php
вы можете получить входящую ссылку для вашего файла, используя $_SERVER['PATH_INFO']
или в пределах REQUEST_URI
, Не нужно передавать это как ?id=someId.mp4
прямой косой черты больше похож на настоящий файл.
Подводя итог, из моего опыта, чтобы правильно обслуживать видеофайл из PHP вам понадобится:
- Поддержка байтового диапазона. Браузер сообщает серверу, какая часть файла ему нужна, и сервер должен ответить содержимым этого диапазона байтов.
- Иметь свой
moov atom
в начале файла (вы можете использовать ffmpeg-movflags +faststart
или жеMP4Box
) <video source="...file.mp4">
Атрибут источника тега video должен выглядеть как.mp4
файл. Без этого мои видео воспроизводились только в Chrome, а не в Safari/iOS.- Прямой проигрыватель HTML5, или вы можете использовать библиотеку, как
videojs
Я написал это, основываясь на моем опыте работы с тысячами видео на моем музыкальном веб-сайте. Хотя это может быть не для всех, но я обнаружил, что настройка кросс-браузера и кросс-устройства работает, как и ожидалось.
Обратите внимание, что этот код ( https://mobiforge.com/design-development/content-delivery-mobile-devices) является спасением жизни. Однако быть в поисках линии
"if ($range{0} == '-'){" или "if ($range0 == '-'){"
так должно быть
if ($range[0] == '-'){
В результате этой опечатки очень долго выяснялось, почему она не работает.
У меня были проблемы с этим кодом.
Fix:
set_time_limit(0); // Reset time limit for big files
ob_clean(); //added
echo fread($fp, $buffer);
flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
Если вы читаете файл с http-адреса, то вместо функции filsize() вы используете код ниже для получения размера файла.
function getFileSize($file) {
$ch = curl_init($file);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$data = curl_exec($ch);
curl_close($ch);
$contentLength=0;
if (preg_match('/Content-Length: (\d+)/', $data, $matches)) {
// Contains file size in bytes
$contentLength = (int)$matches[1];
}
return $contentLength;
}