Streaming HTML5 large (>2GB) videos with PHP
Asked Answered
J

0

6

Ok, so basically I'm working on a project where I need to stream MP4 videos from a hidden source.

As most of people in this forum with this problem, I've used a solution derived from: http://mobiforge.com/developing/story/content-delivery-mobile-devices

function get_video($file){
    $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);
}

The problem appears when you are trying to process a video file bigger than 2GB (in a x86 machine).

The first problem I found was with the php filesize() function, which gives a negative value when the file is bigger than 2GB. For this issue I'm using a work around which is working correctly:

 function showsize($file) {
    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
      if (class_exists("COM")) {
        $fsobj = new COM('Scripting.FileSystemObject');
        $f = $fsobj->GetFile(realpath($file));
        $file = $f->Size;
      } else {
        $file = trim(exec("for %F in (\"" . $file . "\") do @echo %~zF"));
      }
    } elseif (PHP_OS == 'Darwin') {
      $file = trim(shell_exec("stat -f %z " . escapeshellarg($file)));
    } elseif ((PHP_OS == 'Linux') || (PHP_OS == 'FreeBSD') || (PHP_OS == 'Unix') || (PHP_OS == 'SunOS')) {
      $file = trim(shell_exec("stat -c%s " . escapeshellarg($file)));
    } else {
      $file = filesize($file);
    }
    return($file)
 }

The second one was that some parameters were being initialized like integers, and at some point, they failed... I worked around that by using floats instead.

The third one, was the php ftell() function (it is being called in the loop), which also returns an integer... so I have the same problem than with the filesize() function. As the value that ftell() would return when you enter to the loop would be the same value than the parameter $start has, I decided to play with a new parameter (let's call it $mm), which would enter to the loop with the same value than the parameter $start has, and then I would just add the value of the $buffer in every loop, so I wouldn't need to use the ftell() function.

It looks like this (clarify that $start is a float):

$mm = $start;
while(!feof($fp) && $p <= $end) {

    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    $mm = $mm + $buffer;
    $p = $mm;
    flush();
}

$$mm and ftell() have the same value during all the loop, but for some reason, with this change it doesn't work even with <2GB files. As I thougth that maybe at some point they could have a different value, I placed a breakpoint inside the next condition:

if ($mm != $p){
  $dummy = 1; 
}

in:

$mm = $start;
while(!feof($fp) && ($p = ftell($fp)) <= $end) {
    if ($mm != $p){
      $dummy = 1; 
    }
    if ($p + $buffer > $end) {
        $buffer = $end - $p + 1;
    }
    set_time_limit(0);
    echo fread($fp, $buffer);
    $mm = $mm + $buffer;
    flush();
}

and it never stopped... So I'm a bit lost, do you know what can be wrong in the worked around loop? do you know any other work around that could work to avoid the ftell() function?

Jezabel answered 2/4, 2014 at 9:22 Comment(2)
Were you ever able to find a solution for this issue? I'm having a similar problem with this function.Praedial
I am using your first code example, too, and upgraded my hosting environment to a 64 bit system. Now it works with files > 2 GB.Gattis

© 2022 - 2024 — McMap. All rights reserved.