Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stream video, with no errors, when outside the html folder?

On ubuntu 16 This is in the var/www/uploads outside the /html folder, chmod 777 for now (testing). It will play then have an error if you try to pause the video when it is downloading:

image.php

<?php

    $filename = $_GET['filename'];
    header('Content-Type: video/mp4');
    readfile("../uploads/" . $filename);
?>

html

<video id="my_video_1" class="video-js vjs-default-skin" width="100%" height="100%"
      controls preload="none" poster='img.png'
      data-setup='{ "playbackRates": [1, 1.5, 2] }'>
    <source src="image.php?filename=myfile.mp4" type='video/mp4' />
</video>

Works but within the www/html/uploads, chmod 777. No errors at all. This is bad practice though:

<video id="my_video_1" class="video-js vjs-default-skin" width="100%" height="100%"
      controls preload="none" poster='img.png'
      data-setup='{ "playbackRates": [1, 1.5, 2] }'>
    <source src="uploads/myfile.mp4" type='video/mp4' />
</video>

What else are you suppose to do with mp4 to stop this from happening?

like image 261
DDJ Avatar asked Nov 22 '25 08:11

DDJ


1 Answers

The best approach for this is to use "byte range" headers - this returns only the chunk of the file you need. Wikipedia has a very brief intro (https://en.wikipedia.org/wiki/Byte_serving) but you can google for more.

This is a function I wrote for my projects - you may need to tweak to get it to suit your exact needs, but it's pretty generic and might work out of the box.

function serve_file_resumable ($file, $contenttype = 'application/octet-stream') {

    // Avoid sending unexpected errors to the client - we should be serving a file,
    // we don't want to corrupt the data we send
    @error_reporting(0);

    // Make sure the files exists, otherwise we are wasting our time
    if (!file_exists($file)) {
        header("HTTP/1.1 404 Not Found");
        exit;
    }

    // Get the 'Range' header if one was sent
    if (isset($_SERVER['HTTP_RANGE'])) {
        $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions
    } else if ($apache = apache_request_headers()) { // Try Apache again
        $headers = array();
        foreach ($apache as $header => $val) {
            $headers[strtolower($header)] = $val;
        }
        if (isset($headers['range'])) {
            $range = $headers['range'];
        } else {
            $range = false; // We can't get the header/there isn't one set
        }
    } else {
        $range = false; // We can't get the header/there isn't one set
    }

    // Get the data range requested (if any)
    $filesize = filesize($file);
    if ($range) {
        $partial = true;
        list($param,$range) = explode('=',$range);
        if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes'
            header("HTTP/1.1 400 Invalid Request");
            exit;
        }
        $range = explode(',',$range);
        $range = explode('-',$range[0]); // We only deal with the first requested range
        if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid
            header("HTTP/1.1 400 Invalid Request");
            exit;
        }
        if ($range[0] === '') { // First number missing, return last $range[1] bytes
            $end = $filesize - 1;
            $start = $end - intval($range[1]);
        } else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end
            $start = intval($range[0]);
            $end = $filesize - 1;
        } else { // Both numbers present, return specific range
            $start = intval($range[0]);
            $end = intval($range[1]);
            if ($end >= $filesize || (!$start && (!$end || $end == ($filesize - 1)))) {
                $partial = false; // Invalid range/whole file specified, return whole file
            }
        }
        $length = $end - $start + 1;
    } else {
        $partial = false; // No range requested
        $length = $filesize;
    }



    // Send standard headers
    header("Content-Type: $contenttype");
    header("Content-Length: $length");  // was $filesize
    header('Content-Disposition: attachment; filename="'.basename($file).'"');
    header('Accept-Ranges: bytes');

    // if requested, send extra headers and part of file...
    if ($partial) {
        header('HTTP/1.1 206 Partial Content');
        header("Content-Range: bytes $start-$end/$filesize");
        if (!$fp = fopen($file, 'r')) { // Error out if we can't read the file
            header("HTTP/1.1 500 Internal Server Error");
            exit;
        }
        if ($start) {
            fseek($fp,$start);
        }
        while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server
            $read = ($length > 8192) ? 8192 : $length;
            $length -= $read;
            print(fread($fp,$read));
        }
        fclose($fp);
    } else {
        readfile($file); // ...otherwise just send the whole file
    }

    // Exit here to avoid accidentally sending extra content on the end of the file
    exit;

  }

  serve_file_resumable ("../uploads/" . $filename, 'video/mp4');
like image 104
Robbie Avatar answered Nov 25 '25 00:11

Robbie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!