PHP get size of portion of video - php

How do you get the size of a portion of a video? I want to get the size of one minute of a video. Is this possible?
I am making a php video streaming script and I realized that you cannot skip forward. This is what I plan to do: If a user skips forward one minute, I calculate the size of one minute of the video. Then I echo out the video, but skip the first minute.
This is my current code:
function readfile_chunked($filename, $retbytes = TRUE) {
$buffer = "";
$cnt = 0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, CHUNK_SIZE);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
I also have a content-type header that I didn't include in the code above.
Thanks for all your help!

Its not how "skipping" is done.
What you should be doing is looking for the HTTP_RANGE headers that will be sent by the player.
Within your PHP you need to add and then handle the header('Accept-Ranges: bytes'); header.
So when a user clicks to skip forward in the video the player will send a $_SERVER['HTTP_RANGE'] to the server, you then use this to seek to the part of the file for output.
Here is and example (untested):
<?php
...
...
//check if http_range is sent by browser (or download manager)
if(isset($_SERVER['HTTP_RANGE'])){
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes'){
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}else{
$range = '';
header('HTTP/1.1 416 Requested Range Not Satisfiable');
exit;
}
}else{
$range = '';
}
//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);
//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);
//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($file_size - 1)){
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$file_size);
header('Content-Length: '.($seek_end - $seek_start + 1));
}else{
header("Content-Length: $file_size");
}
header('Accept-Ranges: bytes');
...
...
?>
There is also a previous question here you may find usefull, which will be much easyier to implement.

Related

PHP download resume in Chrome HTTP RANGE not working

I'm using the following code, I adapted: http://ee.php.net/manual/en/function.fread.php#84115 to my code to allow download resume, it works great on firefox but doesn't work at all in chrome. If I try in chrome, the file is downloading, I pause it, wait a few minutes, but then when I resume it the download finishes instantly and the file is corrupted.
Any idea why?
function dl_file_resumable($file, $is_resume=TRUE)
{
//First, see if the file exists
if (!is_file($file))
{
die("<b>404 File not found!</b>");
}
//Gather relevent info about file
$size = filesize($file);
$fileinfo = pathinfo($file);
//workaround for IE filename bug with multiple periods / multiple dots in filename
//that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
$filename = (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ?
preg_replace('/\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1) :
$fileinfo['basename'];
$file_extension = strtolower($path_info['extension']);
$ctype='application/octet-stream';
//check if http_range is sent by browser (or download manager)
if($is_resume && isset($_SERVER['HTTP_RANGE']))
{
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes')
{
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}
else
{
$range = '';
}
}
else
{
$range = '';
}
//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);
//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = (empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)),($size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);
//add headers if resumable
if ($is_resume)
{
//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($size - 1))
{
header('HTTP/1.1 206 Partial Content');
}
header('Accept-Ranges: bytes');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$size);
}
//headers for IE Bugs (is this necessary?)
//header("Cache-Control: cache, must-revalidate");
//header("Pragma: public");
header('Content-Type: ' . $ctype);
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: '.($seek_end - $seek_start + 1));
//open the file
$fp = fopen($file, 'rb');
//seek to start of missing part
fseek($fp, $seek_start);
//start buffered download
while(!feof($fp))
{
//reset time limit for big files
set_time_limit(0);
print(fread($fp, 1024*8));
flush();
ob_flush();
}
fclose($fp);
exit;
}
dl_file_resumable('/home/var/www/app/back/media/ready/5a58hGuRFR.tar');
I did not tried execute your code, but it looks like there is some mess with switch statement:
It does not have any meaning - if $ext is set at all, it will fall into one and only default section
It has wrong syntax - after default should be :
It has variable $new_name which isn't set anywhere
In the end, having these errors in reply, different browsers will handle them differently.

PHP Resumable Download [duplicate]

This question already has answers here:
PHP Remote file streaming with Resume Support
(3 answers)
Resumable downloads when using PHP to send the file?
(14 answers)
Closed 9 years ago.
I found this function on the internet:
function download_file($file,$ctype, $is_resume=TRUE)
{
//First, see if the file exists
if (!is_file($file))
{
die("<b>404 File not found!</b>");
}
//Gather relevent info about file
$size = filesize($file);
$fileinfo = pathinfo($file);
//workaround for IE filename bug with multiple periods / multiple dots in filename
//that adds square brackets to filename - eg. setup.abc.exe becomes setup[1].abc.exe
$filename = (strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')) ?
preg_replace('/\./', '%2e', $fileinfo['basename'], substr_count($fileinfo['basename'], '.') - 1) :
$fileinfo['basename'];
//check if http_range is sent by browser (or download manager)
if($is_resume && isset($_SERVER['HTTP_RANGE']))
{
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes')
{
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
list($range, $extra_ranges) = explode(',', $range_orig, 2);
}
else
{
$range = '';
}
}
else
{
$range = '';
}
//figure out download piece from range (if set)
list($seek_start, $seek_end) = explode('-', $range, 2);
//set start and end based on range (if set), else set defaults
//also check for invalid ranges.
$seek_end = (empty($seek_end)) ? ($size - 1) : min(abs(intval($seek_end)),($size - 1));
$seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);
//add headers if resumable
if ($is_resume)
{
//Only send partial content header if downloading a piece of the file (IE workaround)
if ($seek_start > 0 || $seek_end < ($size - 1))
{
header('HTTP/1.1 206 Partial Content');
}
header('Accept-Ranges: bytes');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$size);
}
//headers for IE Bugs (is this necessary?)
//header("Cache-Control: cache, must-revalidate");
//header("Pragma: public");
header('Content-Type: ' . $ctype);
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: '.($seek_end - $seek_start + 1));
//open the file
$fp = fopen($file, 'rb');
//seek to start of missing part
fseek($fp, $seek_start);
//start buffered download
while(!feof($fp))
{
//reset time limit for big files
set_time_limit(0);
print(fread($fp, 1024*8));
flush();
ob_flush();
}
fclose($fp);
exit;
}
The download is working, but when I use Download Manager (IDM Etc.) and I want to pause the download to continue it later, I can't. This function is supposed to allow that (look at the Content-Range header), but for some reason, it does not work.
Does anyone know why? And how do I fix it?
I've tried a lot of functions that I found on the internet, but nothing works properly.
#see https://github.com/audith/Persephone/blob/master/sources/handlers/static.php - starting from Line 658 for an example.
Suffice to say, resumable downloading requires correct HTTP header handling, specifically 'Connection: close' one wherever necessary.

Streaming a video with PHP

I currently have this code:
function Stream($file)
{
header_remove();
$arr = get_headers($file);
foreach ($arr as &$value) {if((strpos($value,'Content-Type')!== false)){header($value);}}
if (isset($_SERVER['HTTP_RANGE'])) {
rangeDownload($file);
}
else {
header('HTTP/1.1 206 Partial Content');
header("Content-Length:1");
//foreach ($arr as &$value) {if((strpos($value,'Content-Length')!== false)){header($value);}}
//header("Content-Range:bytes 21056-21056/243957100");
readfile($file);
}
}
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 ($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;
}
/* 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;
}
$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);
}
I can see that all 3 requests are made and the file download begins yet instead of the video screen appearing the stream cancels. I have tried mimics standards apache headers without success, I am trying to have it serve video content with the widest possible support (e.g. content type detection).
I just had to go through the same thing for a job I was asked to do. I had no issues making it work for chrome/firefox but I had issues with Safari on the Mac/iPad. I had referenced multiple sources and tried many things, in the end I threw everything I had to get a clean slate, and decided to try fix the issue using the code that was in this issue since it was very similar to what I already had, but different enough to give me more clarity.
Here is the final working version using the code from the original question as a base but with the necessary fixes that worked for me. Hopefully this helps someone else.
if (isset($_SERVER['HTTP_RANGE'])) {
header('Content-Type: video/'.$ext); // reference your own mime variable here
$fp = #fopen($filepath, 'rb');
$size = filesize($filepath); // 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");
// multipart/byteranges
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
$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 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;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1; // Calculate new content length
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;
fseek($fp,$start);
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);
}
This is the ENTIRE code that I use, with the exception of a few variables before it to build the filepath and video extension/mime type. This has been tested on Chrome(latest), Firefox(latest), Safari 7.0.6 on macbook pro, Safari on iOS 7
The issue is in the range download function.In this function you are headers are correct.but you are printing the complete file as the response.
These changes should fix your problem.
// Notify the client the byte range we'll be outputting
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
Here you need to seek the file to start specified in the content-range headers.
fseek($fp,$start) /* MISSING CODE */
Inserting the above line should fix your problem.
// 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);

VideoJS Flash Version doesn't Play Files (PHP Passthrough Stream)

I am Using the newest Version of VideoJS and Put my Videos Through PHP in the Following way:
Called like: $this->streaming->handleDownload($videoPath, "video/mp4");
The Function is defined like:
public function handleDownload($file, $mime = "") {
if (is_file($file)) {
header("Content-Type: $mime");
if (isset($_SERVER['HTTP_RANGE'])) { // do it for any device that supports byte-ranges not only iPhone
header("X-Info: Starting Ranged Download of $file");
$this->rangeDownload($file);
} else {
header("Content-Length: ".filesize($file));
header("X-Info: Starting Straight Download of $file");
readfile($file);
}
} else {
header("HTTP/1.1 500 Internal Server Error");
$msg = "The requested path was no file!";
}
}
The rangeDownload() is defined:
private function rangeDownload($file) {
ob_end_clean();
$fp = #fopen($file, 'rb');
$size = filesize($file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
header("Accept-Ranges: 0-$length");
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) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
header("X-Got-Range: $range, $range0");
// 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 == '-') {
$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;
}
// 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;
}
$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);
}
I am Passing the Video through PHP, because the Files should be only Visible when Logged In.
I do the things with the Partial Content/Ranges because I want to be able to Click in the Middle of The Position bar and The Video should start to play from there.
That all works well with Chrome, Newer Safari, iOS, but (Because of the Flash Fallback) doesn't work in IE or Firefox (Video is mp4) (Perhaps it only takes TOO long to load)
The Range Works in Chrome, but well, the Flash Version of VideoJS doesn't even Ask for the Range-Download, so it gets the other Version.
If I play the Video directly (With PHP-streaming, but Without VideoJS) in Firefox (Just call the Video-URL in the URL Bar) it Starts directly and Loads while it Plays; VideoJS doesn't
What do I need to change that the Videos starts to Play directly in VideoJS Flash-Version?
I have found the Solution (by accident ;-) )
The Problem was, that The Flash-Player asks for The Video as a Complete File. As Long as The Server doesn't answer, that there are Streaming-Versions Available (Accept-Ranges: 0-$size) the Player doesn't Ask for them.
So I set the Header and it Worked.
So All a small Tutorial for all that have Problems with VideoJS Flash Fallback:
Videos need to be encoded via MP4Box (See MP4 in Video.js not playing until fully loaded)
The PHP Passthrough Script needs to send following Headers:
header("Content-Type: $videoMimeType");
header("Content-Length: $videoByteCount");
header("Accept-Ranges: 0-$videoByteCount");
If You want to handle the Range (Identified by isset($_SERVER["HTTP_RANGE"]) ) you need also to specify those headers:
header("Content-Range: bytes $startByte-$endByte/$videoByteCount");
This works for me (See Code in my Question). It works with Chrome, FF, iOS, Safari (IE not tested yet) with Flash and HTML5 with mp4-Files

How can i serve a file through a php script and make it behave exactly the same as a direct link to the file?

I use a php script to check if a user is logged in before serving images or videos. The actual files are stored in a folder that is not able to be accessed directly. If the authentication is successful, the php script will relay/output the file. My goal is to have the file served through the php script behave as closely as possible as a direct link to the actual file.
So, here's the deal. Images work fine. Videos (mp4) work with a few caveats. I'm not able to pseudo stream using the h264.code-shop.com streaming module and the video only successfully plays through once on an iphone. Once the video reaches the end i cannot replay the video without refreshing the page and i receive a "video could not be loaded" error (JW player). If i bypass the php script and directly link to the video file, everything works properly. Therefore it is apparent there is something different between the output generated from my php script and the output you would normally get from directly accessing the file. So, to all you experts out there, what could i possibly be missing? The correct http headers? What can i do to make my script output a file the exact same way the file would be sent if accessed directly?
Here's the script i'm using:
<?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');
?>
I use mod_xsendfile for this
https://tn123.org/mod_xsendfile/
Let Apache deal with serving the file, rather than trying to replicate it all in PHP :)
Yes, its easy to do. No need to set those headers manually. Let the server do it automatically.
Heres a working script which I wrote for a video streaming proxy -
ini_set('memory_limit','1024M');
set_time_limit(3600);
ob_start();
**// do any user checks here - authentication / ip restriction / max downloads / whatever**
**// if check fails, return back error message**
**// if check succeeds, proceed with code below**
if( isset($_SERVER['HTTP_RANGE']) )
$opts['http']['header']="Range: ".$_SERVER['HTTP_RANGE'];
$opts['http']['method']= "HEAD";
$conh=stream_context_create($opts);
$opts['http']['method']= "GET";
$cong= stream_context_create($opts);
$out[]= file_get_contents($real_file_location_path_or_url,false,$conh);
$out[]= $http_response_header;
ob_end_clean();
array_map("header",$http_response_header);
readfile($real_file_location_path_or_url,false,$cong);

Categories