I have a 200MB file that I want to give to a user via download. However, since we want the user to only download this file once, we are doing this:
echo file_get_contents('http://some.secret.location.com/secretfolder/the_file.tar.gz');
to force a download. However, this means that the whole file has to be loaded in memory, which usually doesn't work. How can we stream this file to them, at some kb per chunk?
Try something like this (source http://teddy.fr/2007/11/28/how-serve-big-files-through-php/):
<?php
define('CHUNK_SIZE', 1024*1024); // Size (in bytes) of tiles chunk
// 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;
}
// Here goes your code for checking that the user is logged in
// ...
// ...
if ($logged_in) {
$filename = 'path/to/your/file';
$mimetype = 'mime/type';
header('Content-Type: '.$mimetype );
readfile_chunked($filename);
} else {
echo 'Tabatha says you haven\'t paid.';
}
?>
Use fpassthru(). As the name suggests, it doesn't read the entire file into memory prior to sending it, rather it outputs it straight to the client.
Modified from the example in the manual:
<?php
// the file you want to send
$path = "path/to/file";
// the file name of the download, change this if needed
$public_name = basename($path);
// get the file's mime type to send the correct content type header
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $path);
// send the headers
header("Content-Disposition: attachment; filename=$public_name;");
header("Content-Type: $mime_type");
header('Content-Length: ' . filesize($path));
// stream the file
$fp = fopen($path, 'rb');
fpassthru($fp);
exit;
If you would rather stream the content directly to the browser rather than a download (and if the content type is supported by the browser, such as video, audio, pdf etc) then remove the Content-Disposition header.
Take a look at the example from the manual page of fsockopen():
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
This will connect to www.example.com, send a request then get and echo the response in 128 byte chunks. You may want to make it more than 128 bytes.
I found this method in http://codesamplez.com/programming/php-html5-video-streaming-tutorial
And it works very well for me
<?php
class VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath)
{
$this->path = $filePath;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: video/mp4");
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
header("Last-Modified: ".gmdate('D, d M Y H:i:s', #filemtime($this->path)) . ' GMT' );
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
To use this class, you will have to write simple code like as below:
$stream = new VideoStream($filePath);
$stream->start();
I ran into this problem as well using readfile() to force a download. The memory problem lies not within readfile, rather with ouput buffering.
Just make sure you switch off output buffering before readfile, and the problem should be fixed.
if (ob_get_level()) ob_end_clean();
readfile($yourfile);
Works for files with a size much larger than the allocated memory limit.
Related
I'm using a class for video stream:
<?php
/**
* Description of VideoStream
*
* #author Rana
* #link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
*/
class VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath)
{
$this->path = $filePath;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: video/mp4");
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
header("Last-Modified: ".gmdate('D, d M Y H:i:s', #filemtime($this->path)) . ' GMT' );
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
I can call this class by doing:
include_once 'videostream.php';
$stream = new VideoStream("video.mp4");
$stream->start();
This code works with local videos, but the problem is: When I try to open a external video like this:
$stream = new VideoStream("https://www.w3schools.com/html/mov_bbb.ogg");
The vídeo don't open and I don't receive any error messages? Is a fopen() problem? Is a server problem? I'm using xampp v3.3.0.
When I see Network tab in Google Chrome, I receive two headers and one of them is pending.
UPDATE 1 (Partial Solution)
After performing some tests I made some modifications to the code and I was able to open the video using an external URL, follow the corrections below:
1.The video that I wanna play has HTTPS (https://www.w3schools.com/html/mov_bbb.ogg) and not HTTP format, so you must change all
headers that was HTTP/1.1 to HTTPS/1.1:
header('HTTPS/1.1 416 Requested Range Not Satisfiable');
header('HTTPS/1.1 206 Partial Content');
2.The command filesize does not work when it comes to an external URL, so we need to create a function capable of getting the size of the file we want:
function getContentLengthFromURL($url){
$arrayHeaders = get_headers($url,true);
if(isset($arrayHeaders['Content-Length'])){
if(is_numeric($arrayHeaders['Content-Length']) && $arrayHeaders['Content-Length'] > 0){
return $arrayHeaders['Content-Length'];
}
}
return null;
}
After that we need to update setHeader() function:
//$this->size = filesize($this->path);
$this->size = $this->getContentLengthFromURL($this->path);
3.We need to comment out all the lines that specify the Content-Range: and Fseek()
//header("Content-Range: bytes $this->start-$this->end/$this->size");
//fseek($this->stream, $this->start);
Amazing, now the HTML5 player is able to play the video, the only problem is that you are still unable to change the time of the video.
In this case I can't jump forward or back my video.
UPDATE 2 (THE SOLUTION)
After several attempts I finally got a solution to the code problem.
The problem was in the content-ranges and in the positioning of the headers.
By the way, I created a videoStream package using the new code, if you want an automated videoStream, just access this link.
I'm fairly new to PHP and definitely new to streaming video.
I have mp4 videos stored in a filestream column of my SQL SERVER, and I'd like to stream them to HTML5 video.
I've managed to use the following stream class ( http://codesamplez.com/programming/php-html5-video-streaming-tutorial ) by providing it a temporary file via 'file_put_contents($filePath, $queryresult),' but having to depend on writing and then cleaning up an intermediate file in the filesystem is problematic in the end.
Am I missing a key concept that would allow me to stream a file directly from the DB to the video "src" without having to write to a file in between?
Thanks in advance!
If you were creating temp files yourself and then deleting it. Then you can improve the process by using PHP's inbuilt
tempfile() which
Creates a temporary file with a unique name in read-write (w+) mode and returns a file handle .The file is automatically removed when closed (for example, by calling fclose(), or when there are no remaining references to the file handle returned by tmpfile()), or when the script ends.
private function open()
{
if ( !($this->stream = tmpfile()) ) {
fwrite($this->stream, $videodata);
rewind($this->stream);
die('Could not open stream for reading');
}
// now your temp file is ready to be read.
}
So now you have no responsibility of temp. deletion PHP will handle that for you.If you need to customize the temp. file name or get details you may use this http://php.net/manual/en/function.sys-get-temp-dir.php
If you don't want to use files at all and rather want to use purely in memory solution then you can try using memory streams
private function open()
{
if ( !($this->stream = fopen('php://memory', 'wb+') ) ) {
fwrite($this->stream, $videodata);
rewind($this->stream);
die('Could not open stream for reading');
}
// now your in-memory file is ready to be read.
}
You can fread, fwrite, file_get_contents on the memory stream or throw it over the network using the tcp streams. But I must say that the second solution is a bit memory intensive so not suitable for streaming huge files.
Writing a temporary file seems like a temporary workaround for me. As you are getting a stream from the database and you need to serve a stream to the client, there is no need to store data on the harddisk at all.
All you need to do is to replace the part of your linked example that reads from file (and echo's the binary content to the client) and make it read from sql instead.
In your linked example function stream(), replace this part
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
Replace by somthing like this:
/* Execute the query. */
$stmt = sqlsrv_query($conn, $tsql, $params);
if( $stmt === false )
{
echo "Error in statement execution.</br>";
die( print_r( sqlsrv_errors(), true));
}
/* Retrieve and display the data.
The return data is retrieved as a binary stream. */
if ( sqlsrv_fetch( $stmt ) )
{
$videostream = sqlsrv_get_field( $stmt, 0,
SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
//header("Content-Type: image/jpg");
fpassthru($videostream );
}
else
{
echo "Error in retrieving data.</br>";
die(print_r( sqlsrv_errors(), true));
}
For posterity, here's my final result, which seems to work well without needing temp files.
Not certain if it would make more sense to include the query into the class and just pass the $MediaFileID to the class, but this works as is, so for now I've left it.
The entire modified class is included with its original credit info:
function ExecutereadMediaSP($MediaFileID){
try{
$connection = ConnectToDB();
ini_set('memory_limit', '-1');
// logs basic info about media viewer. Mainly for a basic hit counter.
LogMediaRequest($connection, $MediaFileID);
//Selects binary data from SQL Server based on MediaFileID. Passes this to Stream Class. May be able to make further improvements later.
$sql = "SELECT ... data from FILESTREAM column based on file ID ...";
$rst = $connection->prepare($sql);
$rst->execute();
$rst->bindColumn(1, $filecontent, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $rst->fetch(PDO::FETCH_ASSOC);//sql can only return one row due to unique identifier
//Stream file
$stream = new VideoStream($filecontent);
$stream->start();
//Clean up.
$rst->closeCursor();
unset($rst);
$connection = null;
} catch (Exception $e) {
error_log("Error in getting video\n".$e->getMessage(),0);
}
}
/**
* VideoStream - PHP class that supports (adaptive) streaming of files
*
* #author Rana
* modified by HazCod to use stream_get_contents and correct session shutoff
* https://github.com/HazCod
* #link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
*/
class VideoStream
{
private $path = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filecontent)
{
try{
// Opens file handle resource to replace use of actual file in the file system.
$file_handle = fopen('php://memory', 'r+', false, stream_context_create());
// Writes data from SQL Query to file wrapper.
fwrite($file_handle, $filecontent);
// Moves pointer to beginning of file.
fseek($file_handle, 0);
// Gets info on the "file." Required to get filesize.
$fstat = array();
// gather statistics
$fstat = fstat($file_handle);
//Set File Size for Stream Class.
$this->size = $fstat['size'];
// Define Stream as "File."
$this->stream = $file_handle;
} catch (PDOException $e) {
error_log("Error in getting video\n".$e->getMessage(),0);
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: video/mp4");
header("Cache-Control: max-age=2592000, public");
header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
// header("Last-Modified: ".gmdate('D, d M Y H:i:s', #filemtime($this->path)) . ' GMT' );
$this->start = 0;
$this->end = $this->size - 1;
// header("Accept-Ranges: 0-".$this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
if ($range == '-') {
$c_start = $this->size - substr($range, 1);
}else{
$range = explode('-', $range);
$c_start = $range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
}
$c_end = ($c_end > $this->end) ? $this->end : $c_end;
if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: ".$length);
header("Content-Range: bytes $this->start-$this->end/".$this->size);
}
else
{
header("Content-Length: ".$this->size);
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end && connection_aborted() == 0) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = stream_get_contents($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
session_write_close(); //ensure our session is written away before streaming, else we cannot use it elsewhere
$this->setHeader();
$this->stream();
$this->end();
}
}
I'm implementing a HTML5 video player that serves mp4-files.
When I use a regular mp4 file as a source, the video starts pretty soon and I can also seek to parts that aren't downloaded yet.
Now, I want to secure my video files, i.e. they are stored below the web root and the video src is a php-file, that checks, if the user is allowed to view the video.
So my HTML looks like this:
...
<video src="http://videoserver.com/stream.php?file=videoxy.mp4">
...
And the stream.php:
...
if ($access_granted) {
if ($fd = fopen($path, "rb")) {
$fsize = filesize($path);
header("Content-Type: video/mp4");
header("Content-Disposition: inline; filename=\"".$filename."\"");
header('Content-Transfer-Encoding: binary');
header("Content-Length: $fsize");
fpassthru($fd);
} else {
die('file not found');
}
} else {
die('forbidden');
}
...
Now, the video will still be played, but only after the video is completely downloaded.
How can I make the video start before everything is downloaded? Also I'd like seeking to not yet downloaded parts to work as before.
I found the solution from this gist to be working:
...
if ($access_granted) {
if ($fp = fopen($path, "rb")) {
$size = filesize($path);
$length = $size;
$start = 0;
$end = $size - 1;
header('Content-type: video/mp4');
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;
$c_end = $end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
if ($range == '-') {
$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;
}
$c_end = ($c_end > $end) ? $end : $c_end;
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");
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
}
set_time_limit(0);
echo fread($fp, $buffer);
flush();
}
fclose($fp);
exit();
} else {
die('file not found');
}
} else {
die('forbidden');
}
...
I am trying to stream a simple mp4 video to a video.js player using php.
I found here a simple function to use and modified a bit but the video player is issuing an error even when i go directly to the link i get an error that media is not ok or plugin failed to load (when using the direct link on safari)
Here is the code snippet:
$token = $_GET['token'];
if ($token == 1 )
{
$path = '../videos/vid1.mp4';
$size=filesize($path);
$fm=#fopen($path,'rb');
if(!$fm) {
// You can also redirect here
header ("HTTP/1.0 404 Not Found");
die();
}
$begin=0;
$end=$size;
if(isset($_SERVER['HTTP_RANGE'])) {
if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin=intval($matches[0]);
if(!empty($matches[1])) {
$end=intval($matches[1]);
}
}
}
if($begin>0||$end<$size)
header('HTTP/1.0 206 Partial Content');
else
header('HTTP/1.0 200 OK');
header("Content-Type: video/mp4");
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Disposition: inline;");
header("Content-Range: bytes $begin-$end/$size");
header("Content-Transfer-Encoding: binary\n");
header('Connection: close');
$cur=$begin;
fseek($fm,$begin,0);
while(!feof($fm)&&$cur<$end&&(connection_status()==0))
{ print fread($fm,min(1024*16,$end-$cur));
$cur+=1024*16;
usleep(1000);
}
die();
}
Ignore token as that is for testing purposes only. Is there something wrong with my code and is there any other practical way to stream mp4 or other media types to the browser?
Regards
Solved using this also found here... Sorry for posting but to who will be redirected here can have options.
Working code snippet:
$file = '../videos/vid1.mp4';
$fp = #fopen($file, 'rb');
$size = filesize($file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
header('Content-type: video/mp4');
//header("Accept-Ranges: 0-$length");
header("Accept-Ranges: bytes");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;
$c_end = $end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
if ($range == '-') {
$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;
}
$c_end = ($c_end > $end) ? $end : $c_end;
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");
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: " . $length);
$buffer = 1024 * 8;
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
}
set_time_limit(0);
echo fread($fp, $buffer);
flush();
}
fclose($fp);
exit();
here's a simple solution:
if (access_token_valid()) {
$file = $_GET['file'];
if (file_exists($file)) {
header('Location:
'.$file);
} else {
header('Location:
404.mp4');
}
} else {
header('Location:
403.mp4');
}
Ok basically I have a project that requires that videos are hidden from the users while still able to see them (by using php). here's what i got so far:
The video.php file has this:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'path/to/movie.mp4');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$out = curl_exec($ch);
curl_close($ch);
header('Content-type: video/mp4');
header('Content-type: video/mpeg');
header('Content-disposition: inline');
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($out));
echo $out;
exit();
?>
and the html file that is supposed to display this is using html5 as it would expect. now here's the thing.. when I straight embed this (not ) it works. but it doesn't work on my iPhone and doesn't work in the tag... if I use the direct file instead of the php wrapper, everything works fine, on my iPhone too...
so I guess my question for this one is this: what are the proper header() information to perfectly replicate an mp4 that can be streamed via iPhone and HMTL5?
Solution derived from: http://mobiforge.com/developing/story/content-delivery-mobile-devices
video.php file:
<?php
$file = 'path/to/videofile.mp4';
$fp = #fopen($file, 'rb');
$size = filesize($file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
header('Content-type: video/mp4');
header("Accept-Ranges: 0-$length");
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $start;
$c_end = $end;
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
exit;
}
if ($range == '-') {
$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;
}
$c_end = ($c_end > $end) ? $end : $c_end;
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");
exit;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
fseek($fp, $start);
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: ".$length);
$buffer = 1024 * 8;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
}
set_time_limit(0);
echo fread($fp, $buffer);
flush();
}
fclose($fp);
exit();
?>
Iphones use something called byte-ranges for audio and video requests. See this link for a solution. It's in Appendix A.
http://mobiforge.com/developing/story/content-delivery-mobile-devices
Here is a code snippet that will do what you want (from this question). The PHP solution seems more elegant, and it adds a more efficient solution that might work that uses the web server to serve the content.
<?php
$path = 'file.mp4';
$size=filesize($path);
$fm=#fopen($path,'rb');
if(!$fm) {
// You can also redirect here
header ("HTTP/1.0 404 Not Found");
die();
}
$begin=0;
$end=$size;
if(isset($_SERVER['HTTP_RANGE'])) {
if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
$begin=intval($matches[0]);
if(!empty($matches[1])) {
$end=intval($matches[1]);
}
}
}
if($begin>0||$end<$size)
header('HTTP/1.0 206 Partial Content');
else
header('HTTP/1.0 200 OK');
header("Content-Type: video/mp4");
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Disposition: inline;");
header("Content-Range: bytes $begin-$end/$size");
header("Content-Transfer-Encoding: binary\n");
header('Connection: close');
$cur=$begin;
fseek($fm,$begin,0);
while(!feof($fm)&&$cur<$end&&(connection_status()==0))
{ print fread($fm,min(1024*16,$end-$cur));
$cur+=1024*16;
usleep(1000);
}
die();
More Performance
Note that this is not the most efficient way to do it, because the whole file needs to go through PHP, so you will just need to try how it goes for you.
Assuming the reason you want to do this is to restrict access, and you need more efficiency later, you can use a flag for the web server.
Apache with X-Sendfile module or lightty (nginx info here)
$path = 'file.mp4';
header("X-Sendfile: $path");
die();
This is a bit more advanced and you should only use it if you need it, but it is relaxing to know you have an upgrade option when you start out with something that is rather easy but has mediocre performance.
This code was very handy for me, but I ran into trouble because I am using session vars, and PHP queues access to sessions. If a video was loading, all AJAX requests were impossible, etc. So make sure to call session_write_close() before you start output.
Yes, its easy to do. No need to set those headers manually. Let the server do it automatically.
Heres a working script -
ob_start();
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);