Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Support HTTP Header Range, mp4, php.php/mp4.mp4
<?php
# Nginx don't have PATH_INFO
if (!isset($_SERVER['PATH_INFO'])) {
$_SERVER['PATH_INFO'] = substr($_SERVER["ORIG_SCRIPT_FILENAME"], strlen($_SERVER["SCRIPT_FILENAME"]));
}
$request = substr($_SERVER['PATH_INFO'], 1);
$file = $request;
$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();
?>
@Wundark

This comment has been minimized.

Copy link

commented Aug 9, 2013

Epic, this is the only thing I have found that works!

Props to you!

@lvitti

This comment has been minimized.

Copy link

commented Jun 20, 2014

Thanks for the code is very useful.
Works with small videos, but when I play a 300mb video doesn't work.

@Rob--W

This comment has been minimized.

Copy link

commented Jun 23, 2014

Change Accept-Ranges: 0-$length to Accept-Ranges: bytes. Otherwise some clients may request the all data at once instead of some chunks.

@lvitti

This comment has been minimized.

Copy link

commented Jun 24, 2014

Thanks, I don't know if it was that or video problem. I think the large file was corrupted.

@fadiabualnaser

This comment has been minimized.

Copy link

commented Jun 25, 2014

Hello,
really thank you for this code and try this on Firefox and chrome but i think the HTTP_RANGE doesn't work i stream the video from amazon s3 any idea ?

@srggroup

This comment has been minimized.

Copy link

commented Apr 1, 2015

You saved my day! Thanks!

@q2apro

This comment has been minimized.

Copy link

commented Nov 3, 2015

Just want to note that there seems to be a problem when using webm video files instead of mp4. Many network requests appear but the video does not continue to play. Unfortunately I could not find a solution for this issue.

@Globulopolis

This comment has been minimized.

Copy link

commented Nov 20, 2015

@q2apro see https://github.com/diversen/http-send-file I use this to send video(mp4/webm/ogv)

@johnobono

This comment has been minimized.

Copy link

commented Sep 14, 2016

thank you very much! this the only code I found that works!

@geibi

This comment has been minimized.

Copy link

commented Dec 27, 2016

absolutely awesome! thanks for that!

@tejastank

This comment has been minimized.

Copy link

commented Jun 9, 2017

Python code, to download based on range header.

url = "http://changeyour-range-based-URL.snippetbucket.com/5MB.zip"
headers = {"Range": "bytes=0-100"} # first 100 bytes
r = get(url, headers=headers)

@hassansheikh

This comment has been minimized.

Copy link

commented Jan 9, 2018

That is really good script. What if we've hosted the video on s3/oss(amazon/alicloud) bucket instead of our server. For that case this script will work?

@naveenkonduru

This comment has been minimized.

Copy link

commented Feb 20, 2018

Works well in Chrome and firefox, but not working in Safari!

@musikdoktor

This comment has been minimized.

Copy link

commented Mar 3, 2018

Hi, i'm having a problem with this script (And many others i found) it works excellent for most of my videos, but i found a problem with large files.. For example if the file is about 300mb everything is ok.. but i tried a HD mp4 h264 video that is > 1h and 2.5 gigs and there's no way to make it play.. Is not a coding problem ..

First thing this code fix the filesize 32bit limitation for larger files:

if(substr(PHP_OS, 0, 3) == "WIN"){ exec('for %I in ("'.$file.'") do @echo %~zI', $output); $size = $output[0]; }else{ $size = filesize($file); }

But then don't know if it's a fopen and must use CURL, not sure if is the fseek handling large files.. someone with more experience than me can check and test this code for larger files? would be great!

Happy Coding!

@andrerumjanek

This comment has been minimized.

Copy link

commented Apr 15, 2018

Some many years later...thank you so much, this is awesome!

@inkquery

This comment has been minimized.

Copy link

commented Oct 16, 2018

Everything works fine but for some reason I don't know, when I start playing a video and then click on a link on my page or use the browser's back button, the browser freezes and waits a while ( variable ) before following the link.

The profiler indicates that the requested page change is in pending status.
If I position the video cursor further away, the browser unlocks instantly and follows the link I clicked.

And finally if I reload that page, the issue doesn't appears anymore. Seems that chrome have cached the video.

Any idea ?

EDIT
it seems that problem occurs only with chrome..
While debugging script, I saw that value in "Content-range" header always empty.
That's involve that chrome tries to fully load video and not partially. I think this is that causing the issue.

Any idea ?

EDIT2
And of course I changed Accept-Ranges: 0-$length to Accept-Ranges: bytes

@maxpelic

This comment has been minimized.

Copy link

commented Apr 1, 2019

Change Accept-Ranges: 0-$length to Accept-Ranges: bytes. Otherwise some clients may request the all data at once instead of some chunks.

Thanks @Rob--W, this saved me a bunch of time

@karimalaa22

This comment has been minimized.

Copy link

commented Jun 26, 2019

Great work, thank you very much

@tedchou12

This comment has been minimized.

Copy link

commented Jul 11, 2019

@fadiabualnaser
I think the AWS works, but the AWS sdk package causes memory leak issue when file seek:

function stream($file_path='', $file_name='', $file_size=0, $file_type='')
    {
      header('Content-Type: '. $file_type);
      header('Accept-Ranges: bytes');

      $headers = $this->s3->headObject(array(
        'Bucket'               => $this->bucket,
        'Key'                  => $this->folder.'/'.$file_path,
      ));

      $size   = $file_size;     // File size
      $size   = $headers['ContentLength'];
      $length = $size;           // Content length
      $start  = 0;               // Start byte
      $end    = $size - 1;       // End byte
      // header('X-Custom:'.$_SERVER['HTTP_RANGE']);
      // header('Content-Disposition: inline; filename="' . rawurlencode($file_name) . '"');

      $this->s3->registerStreamWrapper();
      $file = "s3://{$this->bucket}/{$this->folder}/{$file_path}";
      $context = stream_context_create(array('s3' => array('seekable' => true)));

      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;
        header('HTTP/1.1 206 Partial Content');
      }

      header("Content-Range: bytes $start-$end/$size");
      header('Content-Length: ' . $length);

      $stream = fopen($file, 'r', false, $context);
      if (isset($_SERVER['HTTP_RANGE'])) {
        fseek($stream, $start); // this causes error.. I think is the AWS package problem. It will try to cache the entire video into the ram.
      }

      $buffer = 1024 * 4;
      while (!feof($stream) && ($p = ftell($stream)) <= $end) {
        if ($p + $buffer > $end) {
          $buffer = $end - $p + 1;
        }
        set_time_limit(0);
        echo fread($stream, $buffer);
        flush();
      }

      fclose($stream);

      exit();
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.