Почему могут быть ошибки при потоковой выгрузке видео на Safari?
Всем привет, вывожу видео потоково используя PHP и React. Проблем с выводом видео в браузере Google Chrome не имею. Единственное, что я понял, что Chrome по другому запрашивает Range (0=) видео через стандартный проигрыватель. А в Safari по другому, 0=1.
Из за этого возникает проблема при просмотре большого видео, хоть они и воспроизводятся:
Вот мой код
public function currentFileUnloading(string $range, object $storage_disk): ?array
{
try {
$chunk_size = InfoEnum::CHUNK_SIZE->value;
$total_size = $storage_disk->size($this->url);
$mime_type = $storage_disk->mimeType($this->url);
// Проверяем, что файл существует в S3 перед продолжением
abort_unless($storage_disk->exists($this->url), Response::HTTP_NOT_FOUND);
$range = ltrim(strstr($range, '='), '=');
[$start, $end] = array_map('intval', explode('-', $range));
// Проверяем диапазон и корректируем end для Safari и других браузеров
if ($end - $start > $chunk_size || $end === 0) {
$end = (($start + $chunk_size) > $total_size) ? $total_size - 1 : $start + $chunk_size;
}
if (app()->hasDebugModeEnabled()) {
// Локальная среда - используем локальный stream
$stream = $storage_disk->readStream($this->url);
} else {
// Запрашиваем объект с нужным диапазоном
$stream = $this->getAwsObjectByRange($start, $end);
}
// Отправляем заголовки и запускаем callback для потоковой передачи
return [
'headers' => $this->getHeaders($mime_type, $start ?? 0, $end ?? ($total_size - 1), $total_size),
'callback' => $this->getCallBack($stream, $start ?? 0, $end ?? ($total_size - 1))
];
} catch (Throwable $e) {
report($e);
}
return null;
}
protected function getHeaders(mixed $mime_type, int $start, int $end, mixed $total_size): array
{
return [
'Content-Type' => $mime_type,
'Accept-Ranges' => 'bytes',
'Content-Length' => $this->getLength($start, $end),
'Content-Range' => sprintf("bytes %s-%s/%s", $start, $end, $total_size),
'Cache-Control' => 'max-age=31536000'
];
}
private function getAwsObjectByRange(int $start, int $end): mixed
{
// Используем AWS S3 SDK для получения объекта с диапазоном байтов
$s3_client = AWS::getInstance()->getClient();
$result = $s3_client->getObject(
[
'Bucket' => config('filesystems.disks.s3.bucket'),
'Key' => $this->url,
'Range' => sprintf("bytes=%s-%s", $start, $end),
]
);
return data_get($result, 'Body');
}
protected function getCallBack(mixed $stream, int $start, int $end): Closure
{
$length = $this->getLength($start, $end);
if (app()->hasDebugModeEnabled()) {
// Перемещаем указатель на начало нужной части файла
fseek($stream, $start);
return static function () use ($stream, $length) {
echo fread($stream, $length);
if (is_resource($stream)) {
fclose($stream);
}
};
}
return static function () use ($stream, $length) {
$read_bytes = 0;
// Закрываем поток, если это ресурс
if (is_resource($stream)) {
fclose($stream);
}
// Читаем и выводим блоки данных, пока не прочитаем все необходимые байты
while ($read_bytes < $length && !$stream->eof()) {
// Читаем блоки данных (например, по InfoEnum::CHUNK_SIZE байт)
$chunk = $stream->read(InfoEnum::CHUNK_SIZE->value);
// Если нет данных для чтения, прерываем
if ($chunk === false || $chunk === '') {
break;
}
// Отправляем данные пользователю
echo $chunk;
flush();
// Увеличиваем счётчик прочитанных байт
$read_bytes += strlen($chunk);
}
};
}
protected function getLength(int $start, int $end): int
{
return $end - $start + 1;
}
return (
<SliderImage key={el.id} style={{translate: `${-100 * currentImage}%`}}>
<video
preload="auto"
ref={currentImage === index ? ref : null}
style={{maxHeight: visualViewport ? visualViewport?.height * 8 / 12 : "600px"}}
className={"w-full h-full"}
aria-hidden={currentImage !== index}
controls
key={index}
>
<source src={link(el.file_id)}
type={el.extension === "webm" ? "video/webm" : "video/mp4"}/>
</video>
</SliderImage>
)