I am using this below code to stream a movie on my localhost. The movie which is lesser then 2GB works fine but when it comes to movie greater than 2gb then the movie doesn't play.
Please help me what should I do to read larger files.
This is the code I am using for streaming movie
<?php
//Determine file path according to extension
if (!isset($_GET['ext']) || $_GET['ext'] == 'mp4') {
$path ='movies/'.$_GET['movie'];
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($path);
header('Content-Type: ' . $mime);
$size = filesize($path);
if (isset($_SERVER['HTTP_RANGE'])) {
list($specifier, $value) = explode('=', $_SERVER['HTTP_RANGE']);
if ($specifier != 'bytes') {
header('HTTP/1.1 400 Bad Request');
return;
}
list($from, $to) = explode('-', $value);
if (!$to) {
$to = $size - 1;
}
$fp = fopen($path, 'rb');
if (!$fp) {
header('HTTP/1.1 500 Internal Server Error');
return;
}
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header('Content-Length: ' . ($to - $from));
header("Content-Range: bytes {$from}-{$to}/{$size}");
fseek($fp, $from);
while(true){
if(ftell($fp) >= $to){
break;
}
echo fread($fp, 8192);
// Flush do buffer
ob_flush();
flush();
}
}
else {
header('Content-Length: ' . $size);
readfile($path);
}
I have hosted the files on localhost using xammpp as a server.
This could have different reasons.
fopen() reads a file into your RAM. So the limitation seems to come from there.
First, make sure you have min 4GB installed in your system.
If you need more also make sure you dont use a Win 32-Bit system since this only supports up to 4GB ram.
Then you can try to increase the memory_limit of PHP. Open the xampp/php/php.ini file and change the memory_limit to what ever you need OR set the ini value directly in your php file like this
ini_set(”memory_limit”,”16M”);
Hope this helps. For a more specific answer it would be helpful if you have any error message.
Related
I want to serve huge files from a folder above the public_html.
Currently I do:
<?php
// Authenticate
if ($_GET['key'] !== "MY-API-KEY") {
header('HTTP/1.0 403 Forbidden');
echo "You are not authorized.";
return;
}
define('CHUNK_SIZE', 1024*1024);
$PATH_ROOT_AUTOPILOT_ACTIVITY_STREAMS = "../../../data/csv/";
// Read a file and display its content chunk by chunk
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;
}
// Get the file parameter
$file = basename(urldecode($_GET['file']));
$fileDir = $PATH_ROOT_AUTOPILOT_ACTIVITY_STREAMS;
$filePath = $fileDir . $file;
if (file_exists($filePath))
{
// Get the file's mime type to send the correct content type header
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $filePath);
// Send the headers
header("Content-Disposition: attachment; filename=$file.csv;");
header("Content-Type: $mime_type");
header('Content-Length: ' . filesize($filePath));
// Stream the file
readfile_chunked($filePath);
exit;
}
?>
This currently fails for some reason I don't understand. curl outputs:
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 457M 0 457M 0 0 1228k 0 --:--:-- 0:06:21 --:--:-- 1762k
curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR (err 2)
Is there a better way to serve big files programatically, via PHP?
Currently about 2/3 of the file is served. The response is not complete because it crashes. There are no logs.
Ok, I give up... Something very strange is going on and, after days of messing with this, I have to ask for help. I have a PHP script that serves an MP4 file from outside of the document root. This script works great, except for one very important (to me at least) detail: it will not give me the option to cast the content. On the same server, when I access an MP4 file that IS inside the document root, I load the page and when I click the three dots in the bottom right corner of the Chrome video player, I have the option to Download or Cast to my Chromecast. Using my script, I only have the option to Download, and I REALLLLY need to CAST! I have tweaked this so much that the headers output from either method are all but identical. Here is my code...
<?php
$file=$_GET['file'];
//validate
if($file=="." || $file==".."){$file="";}
$mediaRoot="../../../hostMedia";
$file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
$file=str_replace('\\',"/",$file);
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
// find the requested range
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;
// output the right headers for partial content
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
header('Content-Type: video/mp4');
header('Content-Length: ' . $filesize);
header('Accept-Ranges: bytes');
header('Cache-Control: max-age=0');
// open file for reading
$file = fopen($file, 'r');
// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);
// populate $data with all except the last byte of the file
$numBytes=($filesize-1);
$dataLen=0;
while($dataLen<$numBytes){
$lenGrab=($numBytes-$dataLen);
if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
$data=fread($file, $lenGrab);
print($data);
$dataLen+=strlen($data);
}
// close file
fclose($file);
?>
A thousand "thank-you"s to whoever solves this one!
UPDATE
Ok, taking #Brian Heward's advice, I have spent countless hours making sure that the headers are ABSOLUTELY IDENTICAL!!! I was so sure it would work, but alas, it still fails to give me the option to cast. Here is my updated PHP...
<?php
session_start();
$accessCode=$_SESSION['accessCode'];
$file=$_GET['file'];
//handle injection
if($file=="." || $file==".."){$file="";}
if($accessCode=="blahblahblah8"){
$mediaRoot="../../../hostMedia";
$file=$mediaRoot . DIRECTORY_SEPARATOR . $file;
$file=str_replace('\\',"/",$file);
$filesize = filesize($file);
$offset = 0;
$length = $filesize;
$lastMod=filemtime($file);
if ( isset($_SERVER['HTTP_RANGE']) ) {
// if the HTTP_RANGE header is set we're dealing with partial content
$partialContent = true;
// find the requested range
preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
$offset = intval($matches[1]);
$length = (($matches[2]) ? intval($matches[2]) : $filesize) - $offset;
} else {
$partialContent = false;
}
if ( $partialContent ) {
// output the right headers for partial content
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $offset . '-' . ($offset + $length-1) . '/' . $filesize);
}else{
header('HTTP/1.1 200 OK');
}
// output the regular HTTP headers
header('Content-Type: video/mp4');
header('Content-Length: ' . $length);
header('Accept-Ranges: bytes');
header('ETag: "3410d79f-576de84c004aa"');
header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', $lastMod));
// don't forget to send the data too
$file = fopen($file, 'r');
// seek to the requested offset, this is 0 if it's not a partial content request
fseek($file, $offset);
//populate $data with all except the last byte of the file
$numBytes=($length);
$dataLen=0;
while($dataLen<$numBytes){
$lenGrab=($numBytes-$dataLen);
if($lenGrab>(1024*2700)){$lenGrab=(1024*2700);}
$data=fread($file, $lenGrab);
print($data);
$dataLen+=strlen($data);
}
fclose($file);
}else{
echo "You are not authorized to view this media.";
}
?>
If someone can get this thing to work, you are seriously a superhero!
FINAL UPDATE (for now...)
Well, after many, many hours of frustration, I had to abandon the approach and try something different. Luckily, there are usually more than one way to accomplish something, and I have found another way. I am hosting the .mp4 files inside the doc root in a folder protected using HTTP Basic Auth. Very similar to what I was trying to achieve and it is working for me. Thanks for your advice and direction!
Your headers are "all but identical" and there is the problem. Make them identical :P
Use the developer tools on your browser, (F12) and check the network headers each request is making. The most likely causes are the following lines I used on a similar project and you seem to be missing:
header('Content-Description: File Transfer');
header('Content-Disposition: inline; filename=' . basename($file));
alternately it might want
header('Content-Disposition: attachment; filename=' . basename($file));
This script might be what you're looking for, it handles video serving via PHP really well, Source
<?php
// disable zlib so that progress bar of player shows up correctly
if(ini_get('zlib.output_compression')) {
ini_set('zlib.output_compression', 'Off');
}
$folder = '.';
$filename = 'video.mp4';
$path = $folder.'/'.$filename;
// from: http://l...content-available-to-author-only...n.net/post/stream-videos-php/
if (file_exists($path)) {
// Clears the cache and prevent unwanted output
ob_clean();
$mime = "video/mp4"; // The MIME type of the file, this should be replaced with your own.
$size = filesize($path); // The size of the file
// Send the content type header
header('Content-type: ' . $mime);
// Check if it's a HTTP range request
if(isset($_SERVER['HTTP_RANGE'])){
// Parse the range header to get the byte offset
$ranges = array_map(
'intval', // Parse the parts into integer
explode(
'-', // The range separator
substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header
)
);
// If the last range param is empty, it means the EOF (End of File)
if(!$ranges[1]){
$ranges[1] = $size - 1;
}
// Send the appropriate headers
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header('Content-Length: ' . ($ranges[1] - $ranges[0])); // The size of the range
// Send the ranges we offered
header(
sprintf(
'Content-Range: bytes %d-%d/%d', // The header format
$ranges[0], // The start range
$ranges[1], // The end range
$size // Total size of the file
)
);
// It's time to output the file
$f = fopen($path, 'rb'); // Open the file in binary mode
$chunkSize = 8192; // The size of each chunk to output
// Seek to the requested start range
fseek($f, $ranges[0]);
// Start outputting the data
while(true){
// Check if we have outputted all the data requested
if(ftell($f) >= $ranges[1]){
break;
}
// Output the data
echo fread($f, $chunkSize);
// Flush the buffer immediately
#ob_flush();
flush();
}
}
else {
// It's not a range request, output the file anyway
header('Content-Length: ' . $size);
// Read the file
#readfile($path);
// and flush the buffer
#ob_flush();
flush();
}
}
die();
?>
I have a php script that allows users to download large files. The script works well except for files of over around 500mb. Everytime I try to download a file that is 664mb the download stops at about 460mb ( around 15-17 mins). There is no error. What can I do? I
suspect the script is timing out but I can't see why. I've spent days trying to get it to work and just can't make any progress. Any thoughts or suggestions would be great. I'm using a hosted server so cannot try modx_sendfile sadly. I'm using php 7.0.25
$db = new mysqli($dbHost, $dbUsername, $dbPassword, $dbName);
ob_start() or die('Cannot start output buffering');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$strDownloadFolder = "files/";
//- turn off compression on the server
//#apache_setenv('no-gzip', 1);
#ini_set('zlib.output_compression', 'Off');
//Download a file more than once
$boolAllowMultipleDownload = 2;
if(!empty($_GET['key'])){
//check the DB for the key
$sql = "SELECT * FROM downloads WHERE downloadkey = '".mysqli_real_escape_string($db,$_GET['key'])."' LIMIT 1";
$resCheck=mysqli_query($db, $sql);
$arrCheck = mysqli_fetch_array($resCheck); //Create array from first result
if(!empty($arrCheck['file'])){
//check that the download time hasnt expired
if($arrCheck['expires']>=time()){
if(!$arrCheck['downloads'] OR $boolAllowMultipleDownload){
//everything is ok -let the user download it
$strDownload = $strDownloadFolder.$arrCheck['file'];
if(file_exists($strDownload)){
$file_path = $arrCheck['file'];
$path_parts = pathinfo($file_path);
$file_name = $path_parts['basename'];
$file_ext = $path_parts['extension'];
$file_path = 'files/' . $file_name;
// allow a file to be streamed instead of sent as an attachment
$is_attachment = isset($_REQUEST['stream']) ? false : true;
// make sure the file exists
if (is_file($file_path))
{
$file_size = filesize($file_path);
$file = #fopen($file_path,"rb");
if ($file)
{
// set the headers, prevent caching
header("Pragma: public");
header("Expires: -1");
header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
header("Content-Disposition: attachment; filename=\"$file_name\"");
// set appropriate headers for attachment or streamed file
if ($is_attachment) {
header("Content-Disposition: attachment; filename=\"$file_name\"");
}
else {
header('Content-Disposition: inline;');
header('Content-Transfer-Encoding: binary');
}
// set the mime type based on extension, add yours if needed.
$ctype_default = "application/octet-stream";
$content_types = array(
"exe" => "application/octet-stream",
"zip" => "application/zip",
"mp3" => "audio/mpeg",
"mpg" => "video/mpeg",
"avi" => "video/x-msvideo",
);
$ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
header("Content-Type: " . $ctype);
//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')
{
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');
set_time_limit(0);
fseek($file, $seek_start);
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
if (connection_status()!=0)
{
#fclose($file);
exit;
}
}
// file save was a success
#fclose($file);
exit;
}
else
{
// file couldn't be opened
header("HTTP/1.0 500 Internal Server Error");
exit;
}
}
else
{
// file does not exist
header("HTTP/1.0 404 Not Found");
exit;
}
}else{
echo "We couldn't find the file to download.";
}
}else{
//this file has already been downloaded and multiple downloads are not allowed
echo "This file has already been downloaded.";
}
}else{
//this download has passed its expiry date
echo "This download has expired.";
}
}else{
//the download key given didnt match anything in the DB
echo "No file was found to download.";
}
}else{
//No download key wa provided to this script
echo "No download key was provided. Please return to the previous page and try again.";
}
A much simpler piece of code that still throws up the same problem (but works with smaller files) mkaes me think it is a hosting / php.ini issue although I don't know why.
<?php
$local_file = 'my_big_file.zip';
$download_file = 'my_big_file.zip';
// set the download rate limit (=> 20,5 kb/s)
if(file_exists($local_file) && is_file($local_file))
{
header('Cache-control: private');
header('Content-Type: application/octet-stream');
header('Content-Length: '.filesize($local_file));
header('Content-Disposition: filename='.$download_file);
flush();
$file = fopen($local_file, "r");
while(!feof($file))
{
// send the current file part to the browser
print fread($file, round(1024 * 1024));
// flush the content to the browser
flush();
// sleep one second
sleep(1);
}
fclose($file);}
else {
die('Error: The file '.$local_file.' does not exist!');
}
?>
This script stops downloading after about 17 minutes.
I didn't know wrote what in Title, sorry!
I have this script that I got from MediaDivision for downloading data with header:
<?php
// hide notices
#ini_set('error_reporting', E_ALL & ~ E_NOTICE);
//- turn off compression on the server
#apache_setenv('no-gzip', 1);
#ini_set('zlib.output_compression', 'Off');
//Getting upload center and append file name
$file_path = $_SERVER['DOCUMENT_ROOT'] . $dir_path;
$file_name = pathinfo($file_path)['basename'];
// allow a file to be streamed instead of sent as an attachment
$is_attachment = isset($_REQUEST['stream']) ? false : true;
// make sure the file exists
if (is_file($file_path))
{
$file_size = filesize($file_path);
$file = #fopen($file_path,"rb");
if ($file)
{
// set the headers, prevent caching
header("Pragma: public");
header("Expires: -1");
header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
header("Content-Disposition: inline; filename=\"$file_name\"");
header("Content-Type: video/x-msvideo");
if(isset($_SERVER['HTTP_RANGE']))
{
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes')
{
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');
set_time_limit(0);
fseek($file, $seek_start);
while(!feof($file))
{
print(#fread($file, 1024*8));
ob_flush();
flush();
if (connection_status()!=0)
{
#fclose($file);
exit;
}
}
// file save was a success
#fclose($file);
exit;
}
else
{
// file couldn't be opened
header("HTTP/1.0 500 Internal Server Error");
exit;
}
}
else
{
// file does not exist
header("HTTP/1.0 404 Not Found");
exit;
}
?>
This script works great in Linux servers that I tested with. I can resume downloads and get 8 connection in IDM and everything, But is IIS I just get 1 connection and after every pause, I have to wait a few seconds or I get Server Closed Connection.
I have exact php version installed in both servers, And did all the configs that I found in tutorials with Windows Server 2012 and IIS 8.
So what's the problem here?
(Although I have to say that direct download from Server 2012 has no problem and works great.)
Edit:
I test the script on windows server with Nginx too, but same as IIS, It doesn't work right.
I've got problem streaming a mp4 file throught php.
The downloading of the files hangs on 1MB. You can see it here:
I've tried many headers and read many threads, but lots of them are unresolved or didnt help me, like this one: MP4 plays when accessed directly, but not when read through PHP, on iOS . Any help please?
There is my code:
<?
$root=getenv("DOCUMENT_ROOT");
$file = "$root/".$_GET['get'];
header("Content-Type: video/mp4");
//header("Content-Type: application/octet-stream"); // downloads the file
$start=0;
$size = filesize($file);
$length = $size;
$end = $size - 1;
//header('HTTP/1.1 206 Partial Content');
header("Accept-Ranges: $start-$end");
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: $length");
$data = fopen("$file", "r");
fseek($data, $start);
$bytesleft = $size-$start + 1;
$buffer = 1024 * 256; // 256kb
while(!feof($data)){
if($bytesleft > $buffer){
echo fread($data, $buffer);
flush();
}else{
echo fread($data, $bytesleft);
flush();
}
//sleep(1); // Speedlimit = one buffer per second
}
fclose($data);
exit;
?>
Thanks in advance
Nevermind. I downloaded the file from the server and compared it with original stored on server in hex.
The downloaded one had one more byte on begining: 0A and that was the problem. Everything works now.