СоХабр закрыт.
С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.
ffmpeg -i input.mp4 -movflags faststart -acodec copy -vcodec copy output.mp4
$ curl -i -X HEAD --header "Range: bytes=50-100" http://вашСайт/вашРесурс.mp4
пример правильного ответа
HTTP/1.1 206 Partial Content
Date: Wed, 08 Nov 2017 13:58:06 GMT
Server: Apache
Last-Modified: Thu, 03 Nov 2016 15:14:44 GMT
ETag: "a002df-160c-54067aa74702f"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Range: bytes 50-100/5644
Content-Type: video/mp4
В Laravel есть возможность использоваться disk driver что бы получать, загружать ресурсы на свой сервер или на S3 облако и другие облака.
Например:
Storage::disk('local')->get('video.mp4');
или
Storage::disk('s3')->get('video.mp4');
Моя проблема заключалась в том что мне надо производить потоковую передачу видео файла используя Storage::disk('local').
Пошел самым простом способом, воспользовался PHP VideoStream class for HTML5 video streaming который написал то ли Vagner Luz do Carmo то ли Rana Md Ali Ahsan, спасибо им. Но внес некоторые изменения для того что бы передавать конкретный тип ресурса и что бы video stream работал в IE11.
Реализация:
В нужном мне методе проверяю наличие файла и потом передаю путь к файлу и тип файла:
if (\Storage::disk(config('be_storage.default_disk'))->exists($path)) {
$videoPath=$filesystem->getAdapter()->getPathPrefix().$path;
$stream = new VideoStream($videoPath,$type);
return response()->stream(function() use ($stream) {
$stream->start();
});
}
return response("File doesn't exists", 404);
Теперь мною усовершенствованный VideoStream.class
<?php
namespace BE\Storage\Server;
/**
* Description of VideoStream
*
* @author Rana
* @link http://codesamplez.com/programming/php-html5-video-streaming-tutorial
*/
class VideoStream
{
private $path = "";
private $type = "";
private $stream = "";
private $buffer = 102400;
private $start = -1;
private $end = -1;
private $size = 0;
function __construct($filePath, $type)
{
$this->path = $filePath;
$this->type = $type;
}
/**
* Open stream
*/
private function open()
{
if (!($this->stream = fopen($this->path, 'rb'))) {
die('Could not open stream for reading');
}
}
/**
* Set proper header to serve the video content
*/
private function setHeader()
{
ob_get_clean();
header("Content-Type: " . $this->type);
// header("Cache-Control: max-age=2592000, public");
header("Expires: " . gmdate('D, d M Y H:i:s', time() + 2592000) . ' GMT');
header("Last-Modified: " . gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT');
$this->start = 0;
$this->size = filesize($this->path);
$this->end = $this->size - 1;
header("Accept-Ranges: 0-" . $this->end);
if (isset($_SERVER['HTTP_RANGE'])) {
$c_start = $this->start;
$c_end = $this->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 $this->start-$this->end/$this->size");
exit;
}
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) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $this->start-$this->end/$this->size");
exit;
}
$this->start = $c_start;
$this->end = $c_end;
$length = $this->end - $this->start + 1;
fseek($this->stream, $this->start);
header('HTTP/1.1 206 Partial Content');
header("Content-Length: " . $length);
header("Content-Range: bytes $this->start-$this->end/" . $this->size);
} else {
header("Content-Length: " . $this->size);
header("Accept-Ranges: bytes");
}
}
/**
* close curretly opened stream
*/
private function end()
{
fclose($this->stream);
exit;
}
/**
* perform the streaming of calculated range
*/
private function stream()
{
$i = $this->start;
set_time_limit(0);
while (!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if (($i + $bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
/**
* Start streaming video content
*/
function start()
{
$this->open();
$this->setHeader();
$this->stream();
$this->end();
}
}
Самое важное изменения, которое позволило работать video stream в IE11 - это добавление header("Accept-Ranges: bytes"); без диапазона при первом Response когда не существует $_SERVER['HTTP_RANGE'].
Что и как должно работать
Первый Response должен иметь такие заголовки:
Accept-Ranges:bytes
Connection:Keep-Alive
Content-Length:1342588187
Content-Type:video/mp4
В втором запросе в Request Headers Вы увидите Range:bytes=0- , это означает что Мы требуем начало файла и отравляем Response:
Accept-Ranges:0-2127552
Connection:Keep-Alive
Content-Length:1342588187
Content-Range:bytes 0-1342588186/1342588187
Content-Type:video/mp4
Если Мы переключаем на средину видео - на сервер отправляется Request :
Range:bytes=325550080-1342588186
а Response должен быть таким:
Accept-Ranges:0-1342588186
Connection:Keep-Alive
Content-Length:1017038107
Content-Range:bytes 325550080-1342588186/1342588187
и моментально происходит воспроизведение видео с нужной нам точки.
Спасибо за внимание!
комментарии (0)