Backend

Platforma video z AWS Elastic Transcoder i Amazon S3 – tutorial

Co to jest AWS S3, Elastic Transcoder i AWS IAM? Stwórz platformę video - tutorial

Z roku na rok formaty video generują coraz większy % ruchu internetowego. Według CISCO, w 2020 roku było to aż 82% całego ruchu, a prognozy mówią, że trend ten cały czas będzie się nasilał. W tym artykule zajmiemy się problematyką platform video oraz stworzymy prostą stronę w Laravel 8.

Michał Putkowski. Laravel developer w Polcode. W wolnej chwili angażuje się w projekty open-source oraz rozwój frameworka Laravel. Lubi proste rozwiązania oraz czysty kod. Interesuje się sportem oraz nowinkami technologicznymi.


Na jakie problemy możemy natrafić podczas tworzenia własnej platformy video?

  • Konwertowanie filmów

Bardzo często pliki wgrywane przez użytkowników nie są odpowiednio zoptymalizowane – mają za duży rozmiar lub w przypadku zdjęć i filmów, są w zbyt wysokiej rozdzielczości, której nie zamierzamy obsługiwać. Warto pomyśleć o konwertowaniu filmów na format, który pozwoli nam zaoszczędzić miejsce na dysku. W tym artykule wykorzystamy format M3U8, który jest podstawą protokołu HLS, stworzonego przez firmę Apple. Zaletami tego rozwiązania jest możliwość płynnego przełączania się pomiędzy dostępnymi jakościami filmu (jeśli są dostępne) oraz rozbicie pliku video na kilka lub kilkadziesiąt mniejszych plików (tzw. video chunks), co znacząco wpływa na wydajność i płynność oglądania.

  • Przepustowość serwera

W przypadku platform video, przepustowość odgrywa kluczową rolę w celu zapewnienia płynnego oglądania wielu użytkownikom w tym samym czasie. Dobrym pomysłem może być rozłożenie ruchu na kilka serwerów.

  • Pojemność serwera

Filmy zwykle zajmują dużo miejsca na dysku, dlatego jeśli decydujemy się hostować je na własnym serwerze, musimy dobrać odpowiednią powierzchnię dyskową. Zbyt duża pojemność dysku/dysków może skutkować zbędnymi kosztami, jeśli nie będziemy jej w odpowiedni sposób wykorzystywać. Natomiast zbyt mała pojemność uniemożliwi dodawanie większej ilości filmów.

Co to jest AWS S3, Elastic Trancoder i IAM?

Konfiguracja usług na AWS

Amazon S3 konfiguracja tutorial - pliki do konwersji

Zaczniemy od skonfigurowania wszystkich potrzebnych usług na AWS, krok po kroku.

W przypadku usług S3 oraz Elastic Transcoder, będziemy musieli znaleźć wspólny region dla obydwu usług, żeby nie ponosić dodatkowych kosztów za transfer poza region. Dla nas, najlepszym wyborem będzie eu-west-1 (Irlandia), ponieważ jest to jedyny region w Europie, który posiada wsparcie dla usługi Elastic Transcoder.

Amazon S3

S3 jest to usługa typu cloud storage (przechowywanie danych w chmurze). Skalowalność tej usługi daje ogromne możliwości przechowywania danych, jest to praktycznie nielimitowana przestrzeń dyskowa w chmurze. Dodatkowo użytkownik płaci wyłącznie za przestrzeń, którą wykorzystuje (per GB) oraz za transfer według cennika (https://aws.amazon.com/s3/pricing/).

Tworzymy dwa buckety, pierwszy będzie odpowiadał za przechowywanie plików wgranych przez użytkowników (pliki do konwersji), a na drugim będą znajdować się przetworzone filmy. S3 posiada wsparcie dla protokołu HLS, więc bez problemu będziemy mogli wczytywać filmy bezpośrednio z S3 do naszego playera.

Amazon S3 konfiguracja tutorial - przetworzone filmy

Amazon S3 - zakładka permissions

W ustawieniach bucketa justjoinit-video-output (zakładka permissions), odznaczamy “Block all public access”, ponieważ nasze pliki będą dostępne publicznie.

Amazon S3 - pliki publiczne

I następnie edytujemy bucket policy, aby pliki, które są w nim umieszczone, były publicznie dostępne.

Amazon S3 - ustawienia CORS

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::justjoinit-video-output/*"
        }
    ]
}

Ostatnią rzeczą jest ustawienie CORS, tak aby przeglądarka nie zablokowała zapytania.

AWS Elastic Transcoder - konwersja do formatu m3u8

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "HEAD"
        ],
        "AllowedOrigins": [
            "http://localhost"
        ],
        "ExposeHeaders": []
    },
    {
        "AllowedHeaders": [],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]

Konfiguracja AWS Elastic Transcoder

Elastic Transcoder jest usługą do konwersji video – to rozwiązanie korzystne cenowo (jeśli nie konwertujemy bardzo dużej ilości filmów) oraz szybkie w obsłudze. W naszym przypadku użyjemy go do przetwarzania filmów na format M3U8.

AWS Elastic Transcoder - konfiguracja tutorial

AWS Elastic Transcoder - pipelines

AWS Elastic Transcoder - pipelines

Wartości .env:

  • Pipeline ID – AWS_PIPELINE_ID

Elastic Transcoder Preset

Użyjemy stworzony już Preset pod HLS “System preset: HLS 2M” ze zmienioną rozdzielczością oraz liczbą klatek na sekundę. W większości zastosowań, takie ustawienia powinny wystarczyć.

Możliwości ustawień są duże, więc każdy jest w stanie dostosować je do swoich wymagań.

AWS Elastic Transcoder - preset

Co to jest Identity and Access Management (IAM)?

Identity and Access Management (IAM) - konfiguracja tutorial

Identity and Access Management (IAM) - wybór uprawnień

IAM - konfiguracja

Wartości .env:

  • ID – AWS_PRESET_ID

Konfiguracja Identity and Access Management (IAM)

IAM czyli Identity Access Management – służy do zarządzania dostępem do usług na koncie AWS, umożliwia kontrolę nad użytkownikami oraz ich uprawnieniami.

Konfiguracja Identity and Access Management

Tworzymy nowego użytkownika “app” z dostępem “programmatic access”.

Identity and Access Management (IAM)

Następnie wybieramy odpowiednie uprawnienia dla naszego użytkownika. W tym przypadku będzie to: AmazonS3FullAccess oraz AmazonElasticTranscoder_JobsSubmitter.

IAM

Sprawdzamy czy wszystko się zgadza i zatwierdzamy.

Identity and Access Management

Wartości .env:

Platforma video – Laravel 8

Cały projekt aplikacji znajduje się w repozytorium na Githubie, więc zajmiemy się wyłącznie kluczowymi fragmentami projektu. Podczas pisania kodu skupiłem się wyłącznie na samej funkcjonalności video, więc nie ma tam m.in. logowania/rejestracji, dokładnej walidacji czy złożonego designu.

Composer

Zaczniemy od instalacji potrzebnych bibliotek:

composer require league/flysystem-aws-s3-v3

composer require aws/aws-sdk-php

Konfiguracja zmiennych

Zanim zaczniemy pracę z AWS SDK, musimy ustawić zmienne (klucze dostępu).

.env

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=eu-west-1
AWS_BUCKET=justjoinit-video-input

AWS_URL=https://justjoinit-video-output.s3-eu-west-1.amazonaws.com
AWS_PIPELINE_ID=
AWS_PRESET_ID=

Link do S3 jesteśmy w stanie wygenerować sami według wzoru:

https://{BUCKET_NAME}.s3-{REGION}.amazonaws.com

config/services.php

<?php

return [
    //....
    'ses' => [
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    ],

    'transcoder' => [
        'pipeline_id' => env('AWS_PIPELINE_ID'),
        'preset_id' => env('AWS_PRESET_ID'),
        'url' => env('AWS_URL'),
    ],
];

Model oraz migracja

php artisan make:model Video --migration

database/migrations/create_videos_table.php

<?php

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

class CreateVideosTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('videos', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->string('title');
            $table->string('extension');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('videos');
    }
}

Jako primary key użyłem losowego ciągu znaków, ponieważ auto increment jest zbyt łatwy do odgadnięcia, więc ewentualne filmy niepubliczne (dostęp wyłącznie przez link) nie miałyby sensu.

Controller

W kontrolerze walidujemy request używając Form Requests, tworzymy model oraz zapisujemy wgrany plik.

app/Http/Controllers/VideoController.php

<?php

namespace AppHttpControllers;

use AppModelsVideo;
use IlluminateSupportStr;
use AppJobsProcessVideoJob;
use AppHttpRequestsStoreVideo;

class VideoController extends Controller
{
    //...
    public function store(StoreVideo $request)
    {
        $id = Str::random(5);
        $file = $request->file('video');
        $extension = $file->getClientOriginalExtension();

        $video = Video::create([
            'id' => $id,
            'title' => $request->input('title'),
            'extension' => $extension,
        ]);

        $file->storeAs('tmp-video', $id . '.' . $extension);

        ProcessVideoJob::dispatch($video);

        return redirect()->route('index');
    }
    //...
}

Job

Natychmiast po wgraniu filmu, ProcessVideoJob wysyłany jest do kolejki. Po uruchomieniu następuje przesłanie pliku video na S3.

<?php

namespace AppJobs;

use AppModelsVideo;
use AwsElasticTranscoderElasticTranscoderClient;
use IlluminateSupportFacadesStorage;
use IlluminateBusQueueable;
use IlluminateContractsQueueShouldBeUnique;
use IlluminateContractsQueueShouldQueue;
use IlluminateFoundationBusDispatchable;
use IlluminateQueueInteractsWithQueue;
use IlluminateQueueSerializesModels;

class ProcessVideoJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * @var AppModelsVideo
     */
    protected $video;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Video $video)
    {
        $this->video = $video;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $id = $this->video->id;
        $path = 'tmp-video/' . $id . '.' . $this->video->extension;

        $s3Path = $id . '.' . $this->video->extension;

        Storage::disk('s3')->writeStream($s3Path, Storage::disk('local')->readStream($path));

        $client = new ElasticTranscoderClient([
            'credentials' => [
                'key' => config('services.ses.key'),
                'secret' => config('services.ses.secret'),
            ],
            'region' => config('services.ses.region'),
            'version' => '2012-09-25'
        ]);

        $client->createJob([
            'PipelineId' => config('services.transcoder.pipeline_id'),
            'Input' => [
                'Key' => $s3Path,
            ],
            'Output' => [
                'Key' => $id,
                'SegmentDuration' => '10',
                'PresetId' => config('services.transcoder.preset_id'),
                'ThumbnailPattern' => $id . '-{count}',
            ],
        ]);
    }
}

Kluczowym fragmentem jest sam sposób przesyłania.

Metody write oraz read wczytują plik do pamięci co może być problematyczne w przypadku dużych plików, ogranicza nas maksymalny rozmiar zmiennej.

Z tego powodu użyłem writeStream i readStream, które używają strumienia do przesyłu danych.

Helper

Do generowania linków do S3 przyda się prosty helper.

app/helpers.php

<?php

if (! function_exists('s3_url')) {
    /**
     * Generate asset link for S3
     *
     * @param string $file
     * @return string
     */
    function s3_url($file) {
        $baseUrl = config('services.transcoder.url');

        return $baseUrl . '/' . $file;
    }
}

Player

Jako player użyłem video.js, który według mnie jest najlepszą opcją dla tego typu stron. Dla ułatwienia zakładamy, że każdy zasób na S3 jest dostępny (przetworzony), więc będziemy w “ciemno” wyświetlać linki na widokach. W celu otrzymywania powiadomień odnośnie statusu przetwarzania można skorzystać z usługi Amazon Simple Notification.

<video id="video-player" class="video-js vjs-16-9" controls preload="auto" poster="{{ s3_url($video->id . '-00001.png') }}" data-setup="{}">
    <source src="{{ s3_url($video->id . '.m3u8') }}" type="application/x-mpegURL" />
</video>

Platforma video z AWS Elastic Transcoder i AWS S3 gotowa!

Według mnie jest to najlepsze rozwiązanie dla osób, które chcą stworzyć skalowalną oraz wydajną platformę video. Oczywiście skonfigurowanie wszystkich usług AWS może być problematyczne dla osób niewtajemniczonych w rozwiązania firmy Amazon, lecz praktyka czyni mistrza. Zachęcam wszystkich zainteresowanych do rozwoju mojego kodu we własnym zakresie.


Zdjęcie główne artykułu pochodzi z unsplash.com.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://geek.justjoin.it/platforma-video-z-aws-elastic-transcoder-i-amazon-s3-tutorial/" order_type="social" width="100%" count_of_comments="8" ]