在 iOS 上直接访问 MP4 时播放,但通过 PHP 读取时不播放
Posted
技术标签:
【中文标题】在 iOS 上直接访问 MP4 时播放,但通过 PHP 读取时不播放【英文标题】:MP4 plays when accessed directly, but not when read through PHP, on iOS 【发布时间】:2011-03-08 22:03:48 【问题描述】:我使用 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 手册页上的 cmets 中找到:http://php.net/manual/en/function.readfile.php。无论如何,test-video.mp4
仅约为 5 MB,小于内存限制——在这种情况下,我实际上可以用普通的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
我尝试了许多不同的标头排列,甚至将它们与直接请求中的标头完全匹配,但无济于事。
有没有人在 iOS 上通过 PHP 成功地提供 html5 视频?
[注意:我会尝试使用 X-Sendfile,但该站点位于共享主机上,访问权限非常有限。]
编辑:我读到 iOS 可能对文件扩展名很敏感,所以我尝试设置一个 RewriteRule 将 MP4 请求重写回我的原始 PHP 脚本,但这也没有帮助。
【问题讨论】:
知道这是个老问题,但是 Chrome 存在问题,请参阅 Chrome issue - 206 Partial content & Content range Headers 【参考方案1】:试试:
$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 ($range0 == '-')
// 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
【讨论】:
哇,非常感谢你们!这修复了 iPhone 4S (iOS6) 上的视频问题,也适用于 android 4.1.1 和我测试过的所有桌面浏览器 谢谢!这个问题已经困扰我好几个星期了。实际上,我不久前尝试了您的解决方案,但一定是错误地实施了...今天再次尝试,MP4 现在在 Safari(iOS 和桌面)中通过 PHP,没有问题。对于其他人来说,正在发生的事情(或多或少)是 Safari 不只是请求 mp4。相反,它会像您期望的那样发出初始请求,但随后会使用 Range 标头发出许多后续块请求。 太棒了!该脚本适用于 IOS 和 Chrome 视频。我找到了另一个脚本,***.com/questions/157318/…,这个对我不起作用。 这个答案基本上是侵犯版权。从这里复制:mobiforge.com/design-development/… 我知道这是一个老话题,但我对使用此代码的 chrome 有疑问,请参阅Chrome issue - 206 Partial content & Content range Headers【参考方案2】:如果您自己这样处理,那么您也需要自己处理字节范围请求。
【讨论】:
完全正确 — 我最终只是从以下文章的附录 A 中复制了rangeDownload()
函数:mobiforge.com/developing/story/content-delivery-mobile-devices(这个函数的酷之处在于它还允许用户从任意点开始播放视频,只需单击时间线。)我想我对这里的问题特别感到困惑,因为它在桌面上完美运行,而 iOS 有这个字节范围要求。【参考方案3】:
请注意,这是代码 (https://mobiforge.com/design-development/content-delivery-mobile-devices) 是救命稻草。但是要注意这条线
"if ($range0 == '-')" 或 "if ($range0 == '-')"
应该是
如果 ($range[0] == '-')
这个错字导致很长时间才弄清楚为什么它不起作用。
【讨论】:
【参考方案4】:如上所述,使用 PHP 流式传输或播放 MP4 视频时,如果您想在 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
这样的库
我是根据在我的音乐视频网站上提供数千个视频的经验编写的。虽然可能并非所有人都这样,但我发现这种跨浏览器和跨设备设置按预期工作。
【讨论】:
【参考方案5】:如果你从 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;
【讨论】:
【参考方案6】:我的代码有问题。
修复:
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.
【讨论】:
以上是关于在 iOS 上直接访问 MP4 时播放,但通过 PHP 读取时不播放的主要内容,如果未能解决你的问题,请参考以下文章
Swift AVPlayer 不会播放 MP4:播放按钮通过它