Manage video with PHP

Asked

Viewed 684 times

2

I’m setting up a file management system, but I’m having problems with displaying the videos.

It has an interpreter and in a certain part I have a "ignorer".

$mime = mime_content_type($path);

if(in_array($mime, self::$ignore)){
    $len = filesize($path);

    header("Content-type: $mime");
    header("Content-Length: {$len}");
    readfile($path);
    exit();
}

The problem is with the videos, which due to being dynamic do not play properly.

Problem

  • When rendering the video this way, I can’t advance in time unless the buffer has already been loaded.
  • If the video ends I can’t come back to watch again.

Research

Doubt

  • How to manage videos by PHP?

Note

We are using equipment with different configurations, some run on Nginx and others with Apache.

In such a way that to make as compatible as possible with both, all requests are centralized and handled via PHP.

.htaccess

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

Nginx

location / {
    try_files ~ ~ /index.php?url=$uri&$args;
}

Thus it is not possible to create rules of .htaccess or ngnix as :

RewriteCond %{REQUEST_URI} \.(webm|mp4)$
RewriteRule ^(.*)$ - [L]
  • Guilherme found strange that this https://gist.github.com/h2ero/ccf90f3592b06d9510655902b274b61d does not work, but I looked at the code better, this script probably requires an adjustment in . htaccess, due to $_SERVER['PATH_INFO'] which is only generated by rewriting, apart from the fact that with the experiment that has the line 4 will not work (SCRIPT_FILENAME). Aside from that the author put a set_time_limit(0); in the middle of the whille, which I think is redundancy. I’ll refactor that code and send it to you if the tests go well, you use Nginx?

  • @Guilhermenascimento because it’s actually going to have to adjust the question for that reason, you reminded me, I use both. We have some equipment that roram Nginx and other apache. but I will edit explaining.

  • But if you are going to pass the video address/name by "ID" of the bank or GET you will not need adjustments, in fact it would remove a lot there :), I think you do not need to quote in the question, unless it is a requirement "create routes".

  • @Guilhermenascimento added. Actually in this system the ideal would be not to create routes.

1 answer

1

Making a brief remark on the script https://gist.github.com/h2ero/ccf90f3592b06d9510655902b274b61d there were some "problems", this script was probably set to a .htaccess specific, that is it would have to be adjusted to both need, the same goes for Ngnix.

I assume this is due to $_SERVER['PATH_INFO'] which is only generated by rewriting, apart from the fact that with the experiment that has the line 4 will not work (SCRIPT_FILENAME). Aside from that the author put a set_time_limit(0); in the middle of the whille, which I think is redundancy. I can not say that this compromises the functioning and I am not wanting to criticize anyone, I’m just stating that the script is made to work in a specific situation, I remade the script to work with a simple GET (querystring):

<?php
set_time_limit(0); //Remove o limite

//Pega o mimetype
function mimeType($file)
{
    $mimetype = false;

    if (class_exists('finfo')) {//PHP5.4+
        $finfo     = finfo_open(FILEINFO_MIME_TYPE);
        $mimetype  = finfo_file($finfo, $file);
        finfo_close($finfo);
    } else if (function_exists('mime_content_type')) {//php5.3 ou inferiror
        $mimetype = mime_content_type($file);
    }

    return $mimetype;
}

if (empty($_GET['video'])) {
    die('Video não definido');
}

//Pega a URL do video
$file = 'videos/' . str_replace('../', '', $_GET['video']);

$mime = mimeType($file);

if (!$mime || strpos($mime, 'video/') !== 0) {
    die('Erro ao ler o video ou mime-type inválido');
}

$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: ' . $mime);
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;
    }

    echo fread($fp, $buffer);
    flush();
}

fclose($fp);

If the video address is based on a bank ID simply adjust:

//Exemplo teórico, pode adaptar pro seu ORM ou mysqli, é apenas para esclarecer a lógica
$consulta = $pdo->prepare('SELECT filename FROM videos WHERE id = :id');

//Pega o ID pela QUERYSTRING
$consulta->bindValue(':id', $_GET['videoid'], PDO::PARAM_INT);

$consulta->execute();
$linha = $consulta->fetch(PDO::FETCH_ASSOC);

//Pega a URL do video
$file = 'videos/' . $linha['filename'];

Heed: I am looking at the Docs and some things about HTTP to better understand the range, I will fix possible flaws in the script soon


Testing

No Chrome (opera) limitei para 3G Fast (simulation), the HTML looked like this:

<video src="range.php?video=1.mp4" controls preload="none"></video><hr>
<video src="range.php?video=2.mp4" controls preload="none"></video><hr>
<video src="range.php?video=3.mp4" controls preload="none"></video><hr>

And on the console:

console

I gave the play in the first video and then dragged the control forward and resulted in:

range

I’m just not sure why it generated multiple requests, but I think it may be that the script is not yet perfect or the download of the video after "Seek" depends on the Codecs of the machine and/or browser (and perhaps the speed of the internet).

Image of the "seekbar":

seekbar

  • So I looked this up correctly, it’s due to protocol 206. How to use PHP to output an mp4 video ?

  • @Guilhermelautert I just think (I’m not sure) that the calculation of Content-Range: bytes .... may be wrong at some point, I am reading and trying to adjust, as soon as possible I send you the tests or adjusted script :) ... PS: Are your videos passed through the URL, routes or ID in queystring? Although I think for you this is no mystery

  • My videos are passed via url, would be direct http://domain/video.mp4, at the moment I’m not worried about the player (<video>), so I’m simulating to open the url of the source.

  • Just remember that in theory if you want to simulate my situation everything ends up turning queystring in ^(.*)$ index.php?url=$1

  • @Guilhermelautert understood, only one more doubt, your videos should stay in a folder (route) specific? Type http://site/video/1.webm or it would be any video on any route?

  • All of them are also in a folder, APP_ROOT/Filmes/*.mp4, but the user doesn’t even know it because even if he type http://site/video1 I can send him to APP_ROOT/Filmes/filme7.mp4'.

  • @Guilhermelautert blz, I will create an example for htaccess and ngnix and I will send you ) (it may take a while, I will do some tests to be sure, apache2.4 and ngnix 1.6.3)

  • 1
Show 3 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.