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, но сайт находится на общем хосте с очень ограниченным доступом.]

EDIT: я читал, что iOS может быть чувствительным к расширению файлов, поэтому я попытался настроить RewriteRule, который перезаписывает запросы MP4 обратно на мой оригинальный PHP-скрипт, но это тоже не помогло.

Если вы справляетесь с этим самостоятельно, тогда вам также нужно будет обрабатывать байтовые запросы.

Пытаться:

 $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 

Если вы читаете файл с http url, то вместо функции 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; } 

Обратите внимание, что это код ( 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. 

Как было сказано выше, для воспроизведения или воспроизведения видеороликов MP4 с использованием PHP вам необходимо обрабатывать диапазоны байтов, если вы хотите правильно воспроизводить на Safari и iOS.

rangeDownload() упомянутая в предыдущих ответах, хорошо работает.

Я хочу упомянуть еще одну часть этой головоломки – убедитесь, что источник в видео заканчивается на .mp4 как в <video source="url/yourfile.php/referenceForFile.mp4"> . Это делает браузером то, что это видеофайл, и начинает рассматривать его как одно.

Внутри yourfile.php вы можете захватить входящую ссылку для вашего файла, используя $_SERVER['PATH_INFO'] или в REQUEST_URI . Не нужно передавать его как ?id=someId.mp4 , подход с прямым ?id=someId.mp4 больше похож на реальный файл.

Подводя итог, по моему опыту, чтобы правильно загрузить видеофайл с PHP, вам понадобятся:

  • Поддержка диапазона байтов. Браузер сообщает серверу, какая часть файла ему нужна, и сервер должен отвечать этим содержимым диапазона байтов.
  • Имейте ваш moov atom в начале файла (вы можете использовать ffmpeg -movflags +faststart или MP4Box )
  • <video source="...file.mp4"> Исходный атрибут тега видео должен выглядеть как файл .mp4 . Без этого мои видео показывались только в Chrome, а не в Safari / iOS.
  • Прямой проигрыватель HTML5, или вы можете использовать библиотеку, такую ​​как videojs

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