i trying to write an script for transferring data with limit with restrictions for premium or free or low loading fast loading and etc
i found this code in internet
date_default_timezone_set('GMT');
//1- file we want to serve :
$data_file = "movie.mp4";
$data_size = filesize($data_file); //Size is not zero base
$mime = 'application/otect-stream'; //Mime type of file. to begin download its better to use this.
$filename = basename($data_file); //Name of file, no path included
//2- Check for request, is the client support this method?
if (isset($_SERVER['HTTP_RANGE']) || isset($HTTP_SERVER_VARS['HTTP_RANGE'])) {
$ranges_str = (isset($_SERVER['HTTP_RANGE']))?
$_SERVER['HTTP_RANGE']:$HTTP_SERVER_VARS['HTTP_RANGE'];
$ranges_arr = explode('-', substr($ranges_str, strlen('bytes=')));
//Now its time to check the ranges
if ((intval($ranges_arr[0]) >= intval($ranges_arr[1]) && $ranges_arr[1] != "" &&
$ranges_arr[0] != "" )
|| ($ranges_arr[1] == "" && $ranges_arr[0] == "")
) {
//Just serve the file normally request is not valid :(
$ranges_arr[0] = 0;
$ranges_arr[1] = $data_size - 1;
}
} else { //The client dose not request HTTP_RANGE so just use the entire file
$ranges_arr[0] = 0;
$ranges_arr[1] = $data_size - 1;
}
//Now its time to serve file
$file = fopen($data_file, 'rb');
$start = $stop = 0;
if ($ranges_arr[0] === "") { //No first range in array
//Last n1 byte
$stop = $data_size - 1;
$start = $data_size - intval($ranges_arr[1]);
} elseif ($ranges_arr[1] === "") { //No last
//first n0 byte
$start = intval($ranges_arr[0]);
$stop = $data_size - 1;
} else {
// n0 to n1
$stop = intval($ranges_arr[1]);
$start = intval($ranges_arr[0]);
}
//Make sure the range is correct by checking the file
fseek($file, $start, SEEK_SET);
$start = ftell($file);
fseek($file, $stop, SEEK_SET);
$stop = ftell($file);
$data_len = $stop - $start;
//Lets send headers
if (isset($_SERVER['HTTP_RANGE']) || isset($HTTP_SERVER_VARS['HTTP_RANGE'])) {
header('HTTP/1.0 206 Partial Content');
header('Status: 206 Partial Content');
}
header('Accept-Ranges: bytes');
header('Content-type: ' . $mime);
header('Content-Disposition: attachment; filename="' . $filename . '"');
header("Content-Range: bytes $start-$stop/" . $data_size );
header("Content-Length: " . ($data_len + 1));
//Finally serve data and done ~!
fseek($file, $start, SEEK_SET);
$bufsize = 50;
ignore_user_abort(true);
#set_time_limit(0);
while (!(connection_aborted() || connection_status() == 1) && $data_len > 0) {
echo fread($file, $bufsize);
$data_len -= $bufsize;
usleep(1000);
flush();
}
fclose($file);
i have a problem with script execution
t want to create connection limit when i download file with idm
i tried to count execution script run with fopen in text file and stop script run with exit function and also try class property but doesn't work
i realized everytime idm send request for another connection whole script execute from the begin
this will work but only for the first time, when i click stop and resume again a problem arises
$file=fopen('text.txt','wr');
//..... code
if ($count < 6){
exit();
}
does anyone have a solution for this problem
Related
I spent a week to find right answer on this question. 'Right' I mean absolutely conform to existing web-standards, reliable and performance effective. Finally, I've found the solution.
All what I've found on StackOverflow (Downloading large files reliably in PHP, How to download large files through PHP script) is not works for me:
Both solutions are not support of range requests. It makes them not working for video and audio streaming and download resuming;
All examples have nothing about caching and performance;
PHP 7.0 code tested with desktop versions of Chrome, Safari, Opera and Firefox. Vivaldi test was not successful.
const STDOUT_CHUNK_SIZE = 128 * 1024; // Buffer size to send data to browser. MUST be less then 1/3 of PHP memory size
const CACHE_EXP_SEC = 1800; // Cache expire time is 30 min.
$fileName = "large_video.mp4";
$contentSize = filesize($fileName);
$isAttachment = false; // false allows to use a file as inline element of web page
// Parse range request. Browser asks for part of file
if (isset($_SERVER["HTTP_RANGE"])) {
list($units, $range) = explode("=", $_SERVER["HTTP_RANGE"], 2);
if ($units !== "bytes") {
http_response_code(416); // Requested Range Not Satisfiable
exit;
}
$range = explode(",", $range, 2)[0]; // Get only first range. You can improve this ;)
list($from, $to) = explode("-", $range, 2);
$to = empty($to) ? ($contentSize - 1) : min(abs((int)$to), ($contentSize - 1));
$from = (empty($from) || $to < abs((int)$from)) ? 0 : max(abs((int)$from), 0);
}
else {
// Request for whole content
$from = 0;
$to = $contentSize - 1;
}
// Set response headers
if ($from > 0 || $to < ($contentSize - 1))
{
http_response_code(206); // Partial Content
header("Content-Type: video/mp4"));
header("Content-Range: bytes $from-$to/$contentSize");
header("Content-Length: " . ($from - $to + 1));
}
else {
$etag = md5($file); // Content is immutable but file name can be changed
if (isset($_SERVER["HTTP_IF_NONE_MATCH"]) && trim($_SERVER["HTTP_IF_NONE_MATCH"]) === $etag) {
http_response_code(304); // Not Modified
setCacheHeaders($etag);
exit;
}
http_response_code(200); // Ok
header("Content-Type: video/mp4"));
header("Content-Length: $contentSize");
if ($isAttachment) header("Content-Disposition: attachment; filename=\"$fileName\"");
else header("Content-Disposition: inline");
header("Accept-Ranges: bytes");
setCacheHeaders($etag);
}
// Send response to client
if ($file = fopen($fileName, "rb")) {
fseek($file, $from);
$counter = $from;
set_time_limit(0);
while (!feof($file) && $counter <= $to) {
$bytesToRead = STDOUT_CHUNK_SIZE;
if ($counter + $bytesToRead > $to) $bytesToRead = $to - $counter + 1;
$data = fread($file, $bytesToRead);
$counter += $bytesToRead;
echo $data;
flush();
}
fclose($file);
function setCacheHeaders(string $etag, bool $cacheEnabled = true, bool $public = true)
{
if ($cacheEnabled) {
header("ETag: $etag");
$scope = $public ? "public" : "private";
$sec = CACHE_EXP_SEC;
$age = ($sec >= 0) ? ", max-age=$sec, s-maxage=$sec" : "";
header("Cache-Control: $scope$age, no-transform");
}
else header("Cache-Control: no-cache, no-store, must-revalidate");
}
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.
I want to stream remote file to user via php script.
Now I have 2 function.
function dl_file_resumable($file, $is_resume=TRUE, $type, $name, $length, $header)
{
//Gather relevent info about file
$size = remotefilesize($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: ' . $type);
header('Content-Disposition: attachment; filename="' . $name . '"');
header('Content-Length: '.($seek_end - $seek_start + 1));
//header('Content-Length: '.$length);
//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;
}
and
function remotefilesize($url, $user = "", $pw = "")
{
ob_start();
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_NOBODY, 1);
if(!empty($user) && !empty($pw))
{
$headers = array('Authorization: Basic ' . base64_encode("$user:$pw"));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$ok = curl_exec($ch);
curl_close($ch);
$head = ob_get_contents();
ob_end_clean();
$regex = '/Content-Length:\s([0-9].+?)\s/';
$count = preg_match($regex, $head, $matches);
return isset($matches[1]) ? $matches[1] : "unknown";
}
I got this code by googling and modified it a little bit.
the problem is, I tried this script with remote rar file. But when the rar file downloaded, it shows error when Im tring to open it. "Unexpected end of archive".
Whats wrong with this script??
I am not expert in php, so I need clear answer from you. All help will be appreciate. Thanks :)
Problem solved!!
actually, in my real code, there is an echo in dl_file_resumable function..
this will result the echo being attach in downloaded file and change the size and the code of file.. I detect this problem using hex editor and saw the echo text exists in my rar source code..
I have a php script that streams an mp3 file to the browser. I was wondering if there was a way for the browser to cache the song once played and then if not, then stream the file. I have included the function that I use to stream the mp3 files. As well as the HTML in how I implement the php script. I use a .htaccess file to get the ID of the song and then stream that based one what song it is in a database. I was imaging something to do with 304 headers correct? If anyone could possibly guide me? Thanks.
PHP:
private function send_file($file, $name, $contenttype = "application/octet-stream") {
// Make sure the files exists, otherwise we are wasting our time
if (!file_exists($file)) {
error_log('file not found');
header("HTTP/1.1 404 Not Found");
exit;
}
// Get the 'Range' header if one was sent
if (isset($_SERVER['HTTP_RANGE'])) $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions
else if ($apache = apache_request_headers()) { // Try Apache again
$headers = array();
foreach ($apache as $header => $val) $headers[strtolower($header)] = $val;
if (isset($headers['range'])) $range = $headers['range'];
else $range = FALSE; // We can't get the header/there isn't one set
} else $range = FALSE; // We can't get the header/there isn't one set
// Get the data range requested (if any)
$filesize = filesize($file);
if ($range) {
$partial = true;
list($param,$range) = explode('=',$range);
if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes'
error_log('range unit not in bytes');
header("HTTP/1.1 400 Invalid Request");
exit;
}
$range = explode(',',$range);
$range = explode('-',$range[0]); // We only deal with the first requested range
if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid
error_log('bytes range parameter is not valid');
header("HTTP/1.1 400 Invalid Request");
exit;
}
if ($range[0] === '') { // First number missing, return last $range[1] bytes
$end = $filesize - 1;
$start = $end - intval($range[0]);
} else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end
$start = intval($range[0]);
$end = $filesize - 1;
} else { // Both numbers present, return specific range
$start = intval($range[0]);
$end = intval($range[1]);
if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) $partial = false; // Invalid range/whole file specified, return whole file
}
$length = $end - $start + 1;
} else $partial = false; // No range requested
// Send standard headers
header("Content-Type: $contenttype");
header("Content-Length: $filesize");
header('Content-Disposition: attachment; filename="'.$name.'.mp3"');
header('Accept-Ranges: bytes');
// if requested, send extra headers and part of file...
if ($partial) {
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/$filesize");
if (!$fp = fopen($file, 'r')) { // Error out if we can't read the file
error_log('can\'t read the file');
header("HTTP/1.1 500 Internal Server Error");
exit;
}
if ($start) fseek($fp,$start);
while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server
$read = ($length > 8192) ? 8192 : $length;
$length -= $read;
print(fread($fp,$read));
}
fclose($fp);
} else readfile($file); // ...otherwise just send the whole file
// Exit here to avoid accidentally sending extra content on the end of the file
exit;
}
HTML:
<audio src="/media/play/12345" loop />
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);