Skip to content

Instantly share code, notes, and snippets.

@codler
Created October 17, 2012 17:18
Show Gist options
  • Star 63 You must be signed in to star a gist
  • Fork 19 You must be signed in to fork a gist
  • Save codler/3906826 to your computer and use it in GitHub Desktop.
Save codler/3906826 to your computer and use it in GitHub Desktop.
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
Copy link

Wundark commented Aug 9, 2013

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

Props to you!

@lvitti
Copy link

lvitti 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
Copy link

Rob--W 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
Copy link

lvitti commented Jun 24, 2014

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

@fadiabualnaser
Copy link

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 ?

@adambalint-srg
Copy link

You saved my day! Thanks!

@q2apro
Copy link

q2apro 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
Copy link

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

@johnobono
Copy link

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

@geibi
Copy link

geibi commented Dec 27, 2016

absolutely awesome! thanks for that!

@tejastank
Copy link

tejastank 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
Copy link

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
Copy link

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

@musikdoktor
Copy link

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
Copy link

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

@inkquery
Copy link

inkquery 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
Copy link

maxpelic 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

@karim-alaa
Copy link

Great work, thank you very much

@tedchou12
Copy link

@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();
    }

@Serabass
Copy link

Great! You saved my day too!

@pakeil
Copy link

pakeil commented Feb 24, 2021

Thank you so much! Works!

@TatiSamuel
Copy link

Han Lin Yap great work. Thank you for sharing.

@sidelux
Copy link

sidelux commented Apr 14, 2021

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

Same here... some ideas?

@pawtwa
Copy link

pawtwa commented May 23, 2021

Thanks dude!
I had to implement ranging for video downloading for iOS video player purposes (Flutter) and it works!
I adopted it for Slim framework of course :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment