Я использую php-скрипт, чтобы проверить, вошел ли пользователь перед тем, как показывать изображения или видео. Фактические файлы хранятся в папке, к которой нет прямого доступа. Если аутентификация прошла успешно, скрипт php отправит / выведет файл. Моя цель состоит в том, чтобы файл, обслуживаемый через скрипт php, вел себя как можно точнее, чем прямая ссылка на фактический файл.
Итак, вот сделка. Изображения работают нормально. Видео (mp4) работают с несколькими оговорками. Я не могу создать псевдопоток с помощью модуля потоковой передачи h264.code-shop.com, и видео успешно воспроизводится только один раз на iphone. Как только видео заканчивается, я не могу воспроизвести видео без обновления страницы, и я получаю сообщение об ошибке «видео не может быть загружено» (проигрыватель JW). Если я обошел скрипт php и напрямую связался с видеофайлом, все работает правильно. Поэтому очевидно, что между выводом, сгенерированным из моего php-скрипта, и выводом, который вы обычно получаете от прямого доступа к файлу, есть нечто иное. Итак, всем вам, экспертам, чего я могу упустить? Правильные заголовки http? Что я могу сделать, чтобы мой скрипт выводил файл точно так же, как файл будет отправлен при прямом доступе?
Вот скрипт, который я использую:
<?php
if (!isset($_GET['f'])){die(header('location:../login.php'));}
if (!isset($_GET['onlyHappensFromHTACCESS'])) {
$_GET['f'] = "../protectedFolder/".$_GET['f'];
$file = realpath($_GET['f']);
$type = getFileType($file);
if (acceptableType($type))
{
if (goodTiming())
{
//this function used to allow navigation away from the page while video has not completely loaded
session_write_close();
$fs = stat($file);
header("Content-Type: $type");
header("Etag: ".sprintf('"%x-%x-%s"', $fs['ino'], $fs['size'],base_convert(str_pad($fs['mtime'],16,"0"),10,16)));
if (isset($_SERVER['HTTP_RANGE']))
{ // do it for any device that supports byte-ranges not only iPhone
rangeDownload($file);
}
else
{
$size = filesize($file); // File size
header("Content-Length: $size");
header("Last-Modified: " .gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
header("Pragma: no-cache");
header("Keep-Alive: timeout=5, max=100");
header("Connection: Keep-Alive");
$fh = fopen($file, "rb");
while ( ($buf=fread( $fh, 1024 * 8 )) != '' )
{
set_time_limit(0); // Reset time limit for big files
echo $buf;
flush();
}
fclose($fh);
}
}
die();
}
header('HTTP/1.1 403 Forbidden');
die(header('location:../login.php'));
}
function getFileType($file) {
if (function_exists("finfo_open")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($file==false){$file=realpath("../authorization_failure.html");}
$type = finfo_file($finfo, $file);
finfo_close($finfo);
return $type;
}
else {
$types = array(
'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'pjpeg' => 'image/jpeg', 'png' => 'image/png',
'gif' => 'image/gif', 'bmp' => 'image/bmp', 'flv' => 'video/x-flv', 'mp4' => 'video/mp4'
);
$ext = substr($file, strrpos($file, '.') + 1);
if (key_exists($ext, $types)) return $types[$ext];
return "unknown";
}
}
function acceptableType($type) {
$array = array("image/jpeg", "image/jpg", "image/png", "image/png", "video/x-flv", "video/mp4");
if (in_array($type, $array))
return true;
return false;
}
function goodTiming() {
$n = time();
session_start();
if ($n - $_SESSION['lastcheck'] > 15 )
return false;
return true;
}
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;
}
// 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== '-') {
// 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;
}
/* Check the range and make sure it's treated according to the specs.
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
*/
// End bytes cannot 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;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1; // Calculate new content length
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
// 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;
}
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.
}
fclose($fp);
}
header('location:../login.php');
?>