Laravel S3将视频转发到视频流

时间:2017-09-22 11:24:25

标签: laravel laravel-5 amazon-s3

我正在通过我的Laravel应用程序将视频存储到Amazon S3。这很好用。但我不能“流”出来。

这是一个网址,例如:https://website.com/video/342.qt?api_token=a5a18c9f-f5f6-5d66-85e3-aaaaaaa,应该从S3返回这部名为'212.DdsqoK1PlL.qt'的电影

调用URL时返回此输出: Output of Video URL 这是视频,但我希望它直接在浏览器中运行,就像这个视频一样:https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4

路由调用此函数,从S3磁盘检索非公共文件:

public function document(Document $document)
{
    return Storage::disk('s3')->get($document->path);
}

工作的示例网址与我的唯一区别是示例是MP4和我的.QT,但我也尝试了MP4并在浏览器中输出相同的输出;所以没有自动播放视频。

我猜直接播放的电影是直播视频吗?..

我的网站在Ubuntu上运行,并且还安装了sudo apt-get install vlc

2 个答案:

答案 0 :(得分:1)

我个人反对重定向到S3 URL的想法。我通过Laravel php包装器服务器端屏蔽了所有URL。如果有人遇到类似问题,这就是我用来执行此操作的代码。编写此代码是为了使用Laravel 5.6从S3流式传输视频(并包括HTTP_RANGE支持,因此它也适用于iOS)。

我使用下面的类,位于App / Http / Responses。要使用此类,请创建一个执行此操作的方法(这类似于getFile方法):

$filestream = new \App\Http\Responses\S3FileStream('file_path_and_name_within_bucket', 'disk_bucket_name');
return $filestream->output();

如果有运气,您应该立即进行流式传输(不泄露S3 URL)!

S3FileStream.php:     

namespace App\Http\Responses;

use Illuminate\Http\Request;
use Storage;

class S3FileStream
{
    /**
     * @var \League\Flysystem\AwsS3v3\AwsS3Adapter
     */
    private $adapter;

    /**
     * @var \Aws\S3\S3Client
     */
    private $client;

    /**
     * @var file end byte
     */
    private $end;

    /**
     * @var string
     */
    private $filePath;

    /**
     * @var bool storing if request is a range (or a full file)
     */
    private $isRange = false;

    /**
     * @var length of bytes requested
     */
    private $length;

    /**
     * @var
     */
    private $return_headers = [];

    /**
     * @var file size
     */
    private $size;

    /**
     * @var start byte
     */
    private $start;

    /**
     * S3FileStream constructor.
     * @param string $filePath
     * @param string $adapter
     */
    public function __construct(string $filePath, string $adapter = 's3')
    {
        $this->filePath   = $filePath;
        $this->filesystem = Storage::disk($adapter)->getDriver();
        $this->adapter    = Storage::disk($adapter)->getAdapter();
        $this->client     = $this->adapter->getClient();
    }

    /**
     * Output file to client
     */
    public function output()
    {
        return $this->setHeaders()->stream();
    }

    /**
     * Output headers to client
     * @return $this
     */
    protected function setHeaders()
    {
        $object = $this->client->headObject([
            'Bucket' => $this->adapter->getBucket(),
            'Key'    => $this->filePath,
        ]);

        $this->start = 0;
        $this->size  = $object['ContentLength'];
        $this->end   = $this->size - 1;
        //Set headers
        $this->return_headers                        = [];
        $this->return_headers['Last-Modified']       = $object['LastModified'];
        $this->return_headers['Accept-Ranges']       = 'bytes';
        $this->return_headers['Content-Type']        = $object['ContentType'];
        $this->return_headers['Content-Disposition'] = 'attachment; filename=' . basename($this->filePath);

        if (!is_null(request()->server('HTTP_RANGE'))) {
            $c_start = $this->start;
            $c_end   = $this->end;

            [$_, $range] = explode('=', request()->server('HTTP_RANGE'), 2);
            if (strpos($range, ',') !== false) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);
                return response('416 Requested Range Not Satisfiable', 416);
            }
            if ($range == '-') {
                $c_start = $this->size - substr($range, 1);
            } else {
                $range   = explode('-', $range);
                $c_start = $range[0];

                $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
            }
            $c_end = ($c_end > $this->end) ? $this->end : $c_end;
            if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                headers('Content-Range: bytes ' . $this->start . '-' . $this->end . '/' . $this->size);
                return response('416 Requested Range Not Satisfiable', 416);
            }
            $this->start                            = $c_start;
            $this->end                              = $c_end;
            $this->length                           = $this->end - $this->start + 1;
            $this->return_headers['Content-Length'] = $this->length;
            $this->return_headers['Content-Range']  = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
            $this->isRange                          = true;
        } else {
            $this->length                           = $this->size;
            $this->return_headers['Content-Length'] = $this->length;
            unset($this->return_headers['Content-Range']);
            $this->isRange = false;
        }

        return $this;
    }

    /**
     * Stream file to client
     * @throws \Exception
     */
    protected function stream()
    {
        $this->client->registerStreamWrapper();
        // Create a stream context to allow seeking
        $context = stream_context_create([
            's3' => [
                'seekable' => true,
            ],
        ]);
        // Open a stream in read-only mode
        if (!($stream = fopen("s3://{$this->adapter->getBucket()}/{$this->filePath}", 'rb', false, $context))) {
            throw new \Exception('Could not open stream for reading export [' . $this->filePath . ']');
        }
        if (isset($this->start)) {
            fseek($stream, $this->start, SEEK_SET);
        }

        $remaining_bytes = $this->length ?? $this->size;
        $chunk_size      = 1024;

        $video = response()->stream(
            function () use ($stream, $remaining_bytes, $chunk_size) {
                while (!feof($stream) && $remaining_bytes > 0) {
                    echo fread($stream, $chunk_size);
                    $remaining_bytes -= $chunk_size;
                    flush();
                }
                fclose($stream);
            },
            ($this->isRange ? 206 : 200),
            $this->return_headers
        );
        return $video;
    }
}

答案 1 :(得分:0)

就像我在评论部分所说的那样。我认为你应该使用S3网址(临时或公共)。

你有一些选择:

  1. 使用laravel temporary url;
  2. 将您的文件设置为public + get url;
  3. 了解更多信息: https://laravel.com/docs/5.5/filesystem#storing-files

    将文件可见性设置为公开:

    Storage::setVisibility('file.jpg', 'public')

    临时网址:

    $url = Storage::temporaryUrl(
        'file1.jpg', Carbon::now()->addMinutes(5)
    );
    

    如果您的文件是公开的,则可以使用:

    Storage::url('file1.jpg');