Laravel 9: AWS S3检索视频流



在PHP 8.1.4升级到Laravel 9.14之后,流媒体已经中断了,这是基于这个答案https://stackoverflow.com/a/52598361/6825499。

我可以看出这是因为

Call to undefined method LeagueFlysystemAwsS3V3AwsS3V3Adapter::getClient()

所以它似乎在league/flysystem- laws -s3-v3(3.0.13)的最新版本中被删除了

我确实找到了这篇文章的参考,它试图解释现在有一个解决方案:从Laravel 9的存储facade中获取S3Client

这对我来说太复杂了,我无法理解。

有谁知道我们能做什么吗?

您需要将服务器上的PHP版本更新到最新版本。听起来服务器还在PHP 7上。X版本,这就是问题所在。

经过一番研究后,我意识到问题归结为Laravel文档页面中关于升级的更新描述。

我最终对代码做了一点修改,在这里我使用环境变量来填充代码中适配器之前提供的内容。

在最新版本的flysystem中,不再通过适配器访问客户端。由于上述原因,服务已经中断。

对于一个完整的工作类,你可以使用下面的代码在laravel版本9

<?php
namespace AppHttp;

use Exception;
use IlluminateFilesystemFilesystemAdapter;
use IlluminateHttpResponse;
use IlluminateSupportArr;
use IlluminateSupportStr;
use LeagueFlysystemFilesystem;
use Storage;
use SymfonyComponentHttpFoundationStreamedResponse;

class S3FileStream
{
/**
* Name of adapter
*
* @var string
*/
private $adapterName;

/**
* Storage disk
*
* @var FilesystemAdapter
*/
private $disk;

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

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

/**
* Human-known filename
*
* @var string|null
*/
private $humanName;

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

/**
* @var int|null length of bytes requested
*/
private $length = null;

/**
* @var array
*/
private $returnHeaders = [];

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

/**
* @var string bucket name
*/
private $bucket;

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

/**
* S3FileStream constructor.
* @param string $filePath
* @param string $adapter
* @param string $humanName
*/
public function __construct(string $filePath, string $adapter = 's3', ?string $humanName = null)
{
$options = [
'region'            => env("AWS_DEFAULT_REGION"),
'version'           => 'latest'
];
$this->filePath    = $filePath;
$this->adapterName = $adapter;
$this->disk        = Storage::disk($this->adapterName);
$this->client     = new AwsS3S3Client($options);
$this->humanName   = $humanName;
$this->bucket      = env("AWS_BUCKET");
//Set to zero until setHeadersAndStream is called
$this->start = 0;
$this->size  = 0;
$this->end   = 0;
}

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

/**
* Output headers to client.
* @return Response|StreamedResponse
*/
protected function setHeadersAndStream()
{
if (!$this->disk->exists($this->filePath)) {
report(new Exception('S3 File Not Found in S3FileStream - ' . $this->adapterName . ' - ' . $this->disk->path($this->filePath)));
return response('File Not Found', 404);
}

$this->start   = 0;
$this->size    = $this->disk->size($this->filePath);
$this->end     = $this->size - 1;
$this->length  = $this->size;
$this->isRange = false;

//Set headers
$this->returnHeaders = [
'Last-Modified'       => $this->disk->lastModified($this->filePath),
'Accept-Ranges'       => 'bytes',
'Content-Type'        => $this->disk->mimeType($this->filePath),
'Content-Disposition' => 'inline; filename=' . ($this->humanName ?? basename($this->filePath) . '.' . Arr::last(explode('.', $this->filePath))),
'Content-Length'      => $this->length,
];

//Handle ranges here
if (!is_null(request()->server('HTTP_RANGE'))) {
$cStart = $this->start;
$cEnd   = $this->end;

$range = Str::after(request()->server('HTTP_RANGE'), '=');
if (strpos($range, ',') !== false) {
return response('416 Requested Range Not Satisfiable', 416, [
'Content-Range' => 'bytes */' . $this->size,
]);
}
if (substr($range, 0, 1) == '-') {
$cStart = $this->size - intval(substr($range, 1)) - 1;
} else {
$range  = explode('-', $range);
$cStart = intval($range[0]);

$cEnd = (isset($range[1]) && is_numeric($range[1])) ? intval($range[1]) : $cEnd;
}

$cEnd = min($cEnd, $this->size - 1);
if ($cStart > $cEnd || $cStart > $this->size - 1) {
return response('416 Requested Range Not Satisfiable', 416, [
'Content-Range' => 'bytes */' . $this->size,
]);
}

$this->start                           = intval($cStart);
$this->end                             = intval($cEnd);
$this->length                          = min($this->end - $this->start + 1, $this->size);
$this->returnHeaders['Content-Length'] = $this->length;
$this->returnHeaders['Content-Range']  = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
$this->isRange                         = true;
}

return $this->stream();
}

/**
* Stream file to client.
* @throws Exception
* @return StreamedResponse
*/
protected function stream(): StreamedResponse
{
$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->bucket}/{$this->filePath}", 'rb', false, $context))) {
throw new Exception('Could not open stream for reading export [' . $this->filePath . ']');
}
if (isset($this->start) && $this->start > 0) {
fseek($stream, $this->start, SEEK_SET);
}

$remainingBytes = $this->length ?? $this->size;
$chunkSize      = 100;

$video = response()->stream(
function () use ($stream, $remainingBytes, $chunkSize) {
while (!feof($stream) && $remainingBytes > 0) {
$toGrab = min($chunkSize, $remainingBytes);
echo fread($stream, $toGrab);
$remainingBytes -= $toGrab;
flush();
}
fclose($stream);
},
($this->isRange ? 206 : 200),
$this->returnHeaders
);

return $video;
}
}

最新更新