Symfony2 视频流 [英] Symfony2 video streaming

查看:25
本文介绍了Symfony2 视频流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在用 symfony2 编写应用程序,但视频流出现问题.

I'm writing app in symfony2 and I have a problem with video streaming.

如果一个操作需要很长时间来执行 - 例如 1 分钟,整个应用程序被冻结(在第二个选项卡中打开时)并且必须等待该执行结束.

If one action takes a long time to execute - for example 1 minute, the whole app is frozen (when opened in second tab) and must wait for end of that execution.

问题出在哪里?

//编辑

    public function streamAction($fileName) {

    $user = $this->get('security.context')->getToken()->getUser();
    $request = $this->getRequest();

    $uid = $request->get('uid') != 'null' ? $user->getId() : $request->get('uid');

    $libPath = $this->_libPath('Users', 'uid' . str_pad($uid, 6, "0", STR_PAD_LEFT));

    $file = pathinfo($fileName);
    $fileName = $file['basename'];
    $fileExt = $file['extension'];
    $filePath = realpath($libPath . $fileName);

    if (in_array($fileExt, $this->formats['video'])) {
        $mime = 'video';
    }

    if (in_array($fileExt, $this->formats['audio'])) {
        $mime = 'audio';
    }

    $mime .= '/' . $fileExt;

    header("Accept-Ranges: bytes");

    if (is_file($filePath)) {
        header("Content-type: $mime");
        if (isset($_SERVER['HTTP_RANGE'])) {

            $fp = fopen($filePath, 'rb');
            $size = filesize($filePath);
            $length = $size;
            $start = 0;
            $end = $size - 1;

            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('ETag: "' . md5(microtime()) . '"');
            }

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

            $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);
        } else {
            header("Content-Length: " . filesize($filePath));
            readfile($filePath);
        }
    } else {
        echo "error";
    }
    die();
}

问题不在于代码,因为 symfony2 不仅会阻止流式传输,还会阻止其他长时间操作,例如下载文件.

Problem is not code because symfony2 block not only with streaming but with other long time actions like downloading files.

推荐答案

如果你使用像 Symfony 这样的 OOP 框架,为什么还要使用过程方法而不是 Symfony 提供的方法?例如 StreamedResponse 类.

If you use an OOP framework such as Symfony, why do still use procedural methods instead of what Symfony has to offer ? For instance the StreamedResponse class.

我在下面为您提供了更多面向 OOP/Symfony 的内容,但是,如果提供的范围只是 -,您当前将范围开始重置为...文件大小?我很确定这不是你应该做的.我认为你应该仔细检查一下!

I provided you a more OOP / Symfony oriented below, However in case the provided Range is just -, you currently reset the range start to... The filesize? I am pretty sure this is not what you should do. I think you should double-check it!

这是 streamAction() 的审查版本:

Here is a reviewed version of streamAction() :

use SplFileInfo;
use RuntimeException;

// Symfony >= 2.1
use Symfony\Component\HttpFoundation\StreamedResponse;

public function streamAction($fileName) {
    $user = $this->getUser();
    $request = $this->getRequest();

    // Create the StreamedResponse object
    $response = new StreamedResponse();

    $uid = $request->get('uid') != 'null' ? $user->getId() : $request->get('uid');
    $libPath = $this->_libPath('Users', 'uid' . str_pad($uid, 6, "0", STR_PAD_LEFT));

    try {
        $file = new SplFileObject($libPath . $fileName);
    }
    catch (RuntimeException $runtimeException) {
        // The file cannot be opened (permissions?)
        // throw new AnyCustomFileErrorException() maybe?
    }

    // Check file existence
    if (!($file->isFile())) {
        // The file does not exists, or is not a file.
        throw $this->createNotFoundException('This file does not exists, or is not a valid file.');
    }

    // Retrieve file informations
    $fileName = $file->getBasename();
    $fileExt  = $file->getExtension();
    $filePath = $file->getRealPath();
    $fileSize = $file->getSize();

    // Guess MIME Type from file extension
    if (in_array($fileExt, $this->formats['video'])) {
        $mime = 'video';
    } elseif (in_array($fileExt, $this->formats['audio'])) {
        $mime = 'audio';
    }

    $mime .= '/' . $fileExt;

    $response->headers->set('Accept-Ranges', 'bytes');
    $response->headers->set('Content-Type', $mime);

    // Prepare File Range to read [default to the whole file content]
    $rangeMin = 0;
    $rangeMax = $fileSize - 1;
    $rangeStart = $rangeMin;
    $rangeEnd = $rangeMax;

    $httpRange = $request->server->get('HTTP_RANGE');

    // If a Range is provided, check its validity
    if ($httpRange) {
        $isRangeSatisfiable = true;

        if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $httpRange, $matches)) {
            $rangeStart  = intval($matches[1]);

            if (!empty($matches[2])) {
                $rangeEnd  = intval($matches[2]);
            }
        } else {
            // Requested HTTP-Range seems invalid.
            $isRangeSatisfiable = false;
        }

        if ($rangeStart <= $rangeEnd) {
            $length = $rangeEnd - $rangeStart + 1;
        } else {
            // Requested HTTP-Range seems invalid.
            $isRangeSatisfiable = false;
        }

        if ($file->fseek($rangeStart) !== 0) {
            // Could not seek the file to the requested range: it might be out-of-bound, or the file is corrupted?
            // Assume the range is not satisfiable.
            $isRangeSatisfiable = false;

            // NB : You might also wish to throw an Exception here...
            // Depending the server behaviour you want to set-up.
            // throw new AnyCustomFileErrorException();
        }

        if ($isRangeSatisfiable) {
            // Now the file is ready to be read...
            // Set additional headers and status code.
            // Symfony < 2.4
            // $response->setStatusCode(206);
            // Or using Symfony >= 2.4 constants
            $response->setStatusCode(StreamedResponse::HTTP_PARTIAL_CONTENT);

            $response->headers->set('Content-Range', sprintf('bytes %d/%d', $rangeStart - $rangeEnd, $fileSize));
            $response->headers->set('Content-Length', $length);
            $response->headers->set('Connection', 'Close');
        } else {
            $response = new Response();

            // Symfony < 2.4
            // $response->setStatusCode(416);
            // Or using Symfony >= 2.4 constants
            $response->setStatusCode(StreamedResponse::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
            $response->headers->set('Content-Range', sprintf('bytes */%d', $fileSize));

            return $response;
        }
    } else {
        // No range has been provided: the whole file content can be sent
        $response->headers->set('Content-Length', $fileSize);
    }

    // At this step, the request headers are ready to be sent.
    $response->prepare($request);
    $response->sendHeaders();

    // Prepare the StreamCallback
    $response->setCallback(function () use ($file, $rangeEnd) {
        $buffer = 1024 * 8;

        while (!($file->eof()) && (($offset = $file->ftell()) < $rangeEnd)) {
            set_time_limit(0);

            if ($offset + $buffer > $rangeEnd) {
                $buffer = $rangeEnd + 1 - $offset;
            }

            echo $file->fread($buffer);
        }

        // Close the file handler
        $file = null;
    });

    // Then everything should be ready, we can send the Response content.
    $response->sendContent();

    // DO NOT RETURN $response !
    // It has already been sent, both headers and body.
}

这篇关于Symfony2 视频流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆