Budowa platformy video za pomocą FFMPEG - Laravel
Zawsze zastanawiałem się jak ładować wideo asynchronicznie w mniejszych kawałkach. Kilka razy chciałem nawet dodać taki materiał do strony internetowej, jednak zawsze kończyło się to tak samo (wideo ładowało się zaskakująco długo).
Po zgłębieniu tematu dowiedziałem się o istnieniu oprogramowania jakim jest FFMPEG, oraz o paczce stworzonej przez protonemedia w celu integracji z Laravelem
Rozpocznijmy więc
Stworzenie tej prostej aplikacji można podzielić na 3 podpunkty
- Instalacja zależności
- Stworzenie modelów oraz migracji
- Stworzenie kolejki Zainstaluj paczkę:
composer require pbmedia/laravel-ffmpeg
Stwórz model:
php artisan make:model Video -m
W pliku migracji:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('videos', function (Blueprint $table) {
$table->id();
// file info
$table->string('name');
$table->string('path')->nullable();
$table->string('progress')->nullable();
// video info
$table->string('title')->nullable();
$table->text('description')->nullable();
$table->string('tags')->nullable();
$table->boolean('public');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('videos');
}
};
Stwórz controller:
php artisan make:controller VideoController --api
Następnie musimy napisać funkcję która potnie nasze video i zachowa je w dwóch formatach: stantadrowy, oraz playlista ffmpeg.
public function store (Request $request) {
$name = Str::random(16);
$path = $name . '.' . $request->video->getClientOriginalExtension();
$request->video->storeAs('public', $path);
$video = new Video;
$video->name = $name;
$video->path = asset('/storage/' . $name . '.m3u8');
$video->progress = 0;
$video->save();
VideoProcess::dispatch($path, $name, $video);
return redirect()->route('video-processing', ['id' => $video->id]);
}
Z powodu długiego czasu przetwarzania controller odnosi się do kolejki, zatem po standardowej konfiguracji możemy dodać odpowiednie pliki:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use FFMpeg\Format\Video\X264;
use App\Models\Video;
use FFMpeg;
use FFMpeg\Format\ProgressListener\AbstractProgressListener;
use ProtoneMedia\LaravelFFMpeg\FFMpeg\ProgressListenerDecorator;
class VideoProcess implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $path;
public $name;
public $video;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($path, $name, Video $video)
{
$this->path = $path;
$this->name = $name;
$this->video = $video;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$format = new \FFMpeg\Format\Video\X264;
$decoratedFormat = ProgressListenerDecorator::decorate($format);
$lowBitrate = (new X264)->setKiloBitrate(250);
$midBitrate = (new X264)->setKiloBitrate(500);
$highBitrate = (new X264)->setKiloBitrate(1000);
FFMpeg::fromDisk('public')
->open($this->path)
->exportForHLS()
->onProgress(function ($percentage) {
$this->video->progress = $percentage;
$this->video->save();
})
->inFormat($decoratedFormat)
->setSegmentLength(10) // optional
->setKeyFrameInterval(48) // optional
->addFormat($lowBitrate)
->addFormat($midBitrate)
->addFormat($highBitrate)
->save( $this->name . '.m3u8');
$this->video->progress = 100;
$this->video->save();
}
}
frontend
W tym artykule nie poruszyłem kwestji frontendu, jednak należy pamiętać, że zwykły element video nie jest kompatybilny z playlistami FFMPEG. Jesteśmy zatem zmuszeni używać javascriptowych bibliotek. Video.js jest tu najbardziej popularnym wyborem.