在 iOS 上直接访问 MP4 时播放,但通过 PHP 读取时不播放
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3128906/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
MP4 plays when accessed directly, but not when read through PHP, on iOS
提问by JKS
I use a PHP script to validate video requests before serving them. This script works as expected on the desktop, with Safari and Chrome. But on iOS, I get a broken play button.
在提供视频请求之前,我使用 PHP 脚本来验证它们。此脚本在桌面上按预期工作,使用 Safari 和 Chrome。但是在 iOS 上,我的播放按钮坏了。
I'm sure the video is properly encoded for iPhone/iPad, because when I access it directly, it works as expected.
我确定视频已针对 iPhone/iPad 正确编码,因为当我直接访问它时,它会按预期工作。
The relevant PHP code:
相关的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()is similar to readfile()but for very large files, found in the comments on the PHP manual page: http://php.net/manual/en/function.readfile.php. In any event, test-video.mp4is only ~5 MB, which is less than the memory limit — and in this case I actually can substitute in the normal readfile()and produce the exact same behavior.)
(readfile_chunked()类似于readfile()但对于非常大的文件,在 PHP 手册页的注释中找到:http: //php.net/manual/en/function.readfile.php。无论如何,test-video.mp4只有 ~5 MB,这是小于内存限制——在这种情况下,我实际上可以替代正常readfile()并产生完全相同的行为。)
The headers I get when I access test-video.mp4directly are:
我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
The headers from the PHP script are:
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
I've tried many different permutations of headers, even matching them exactly to those from a direct request, to no avail.
我尝试了许多不同的标头排列,甚至将它们与来自直接请求的那些完全匹配,但无济于事。
Has anyone had success serving HTML5 video through PHP, on iOS?
有没有人在 iOS 上通过 PHP 成功提供 HTML5 视频?
[Note: I would try using X-Sendfile, but the site is on a shared host with very limited access.]
[注意:我会尝试使用 X-Sendfile,但该站点位于共享主机上,访问权限非常有限。]
EDIT:I was reading that iOS can be sensitive about file extensions, so I tried setting up a RewriteRule that rewrites MP4 requests back to my original PHP script, but that didn't help either.
编辑:我读到 iOS 可能对文件扩展名很敏感,所以我尝试设置一个 RewriteRule,将 MP4 请求重写回我原来的 PHP 脚本,但这也无济于事。
采纳答案by sroussey
If you are handling it yourself like that, then you will need to handle byte-range requests yourself as well.
如果您像这样自己处理它,那么您也需要自己处理字节范围请求。
回答by Luis Felipe Tomazini
Try:
尝试:
$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
回答by lopezjorgel
Please note that this is code (https://mobiforge.com/design-development/content-delivery-mobile-devices) is a lifesaver. However be on the lookout for the line
请注意,这是代码(https://mobiforge.com/design-development/content-delivery-mobile-devices)是救命稻草。但是要注意线路
"if ($range{0} == '-'){" or "if ($range0 == '-'){"
"if ($range{0} == '-'){" 或 "if ($range0 == '-'){"
it should be
它应该是
if ($range[0] == '-'){
如果 ($range[0] == '-'){
This typo resulted in a very long time figuring out why it did not work.
这个错字导致了很长时间才弄清楚为什么它不起作用。
回答by dyd
I had problem with that code.
我对那个代码有问题。
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.
回答by Muhammed M.
As was stated above to stream or playback MP4 videos using PHP, you will need to handle byte ranges if you want proper playback on Safari and iOS.
如上所述,使用 PHP 流式传输或播放 MP4 视频,如果您想在 Safari 和 iOS 上正确播放,则需要处理字节范围。
rangeDownload()function mentioned in the previous answers does the job pretty well.
rangeDownload()前面答案中提到的函数可以很好地完成这项工作。
I want to mention another piece of this puzzle - make sure the source in the video ends with .mp4as in <video source="url/yourfile.php/referenceForFile.mp4">. This makes browser thing that it is a video file, and it starts treating it as one.
我想提一下这个难题的另一部分 - 确保视频中的源.mp4以<video source="url/yourfile.php/referenceForFile.mp4">. 这使浏览器认为它是一个视频文件,并开始将其视为一个文件。
Inside yourfile.php, you could grab the incoming reference for your file using $_SERVER['PATH_INFO']or within REQUEST_URI. No need to pass it as a ?id=someId.mp4, direct slash approach looks more like a real file.
在里面yourfile.php,您可以使用$_SERVER['PATH_INFO']或inside获取文件的传入引用REQUEST_URI。无需将其作为?id=someId.mp4. 直接斜线方法传递,看起来更像一个真实的文件。
To sum up, from my experience to serve a video file from PHP correctly you will need:
总而言之,根据我的经验,要正确地从 PHP 提供视频文件,您将需要:
- Byte range support. Browser tells server which part of the file it needs, and the server needs to respond with that byte range content.
- Have your
moov atomat the beginning of the file (you could use ffmpeg's-movflags +faststartorMP4Box) <video source="...file.mp4">Source attribute of video tag needs to look like an.mp4file. Without this my videos were only playing in Chrome and not in Safari/iOS.- Direct HTML5 player, or you can use a library like
videojs
- 字节范围支持。浏览器告诉服务器它需要文件的哪一部分,服务器需要用那个字节范围的内容来响应。
- 将您的
moov atom放在文件的开头(您可以使用 ffmpeg-movflags +faststart或MP4Box) <video source="...file.mp4">视频标签的源属性需要看起来像一个.mp4文件。没有这个,我的视频只能在 Chrome 中播放,而不能在 Safari/iOS 中播放。- 直接 HTML5 播放器,或者您可以使用类似的库
videojs
I wrote this based on my experience with serving thousands of videos on my music video website. While this might not be the case for all, but I found this cross-browser and cross-device setup work as expected.
我根据我在音乐视频网站上提供数千个视频的经验写了这篇文章。虽然这可能不是所有情况,但我发现这个跨浏览器和跨设备设置按预期工作。
回答by Mehul Dudhat
If you read file from http url then instead of filsize() function you user below code for get file size
如果您从 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;
}

