Реалізація завантаження відео та транскодування для сайту

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація завантаження відео та транскодування для сайту
Складна
від 1 тижня до 3 місяців
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація завантаження та трансходування відео

Завантаження відео потребує окремого конвеєру: файл приймається на сервер або безпосередньо в хмару, потім трансходується на кілька якостей (360p, 720p, 1080p) та форматів (MP4/H.264, WebM/VP9). Трансходування — це операція, яка вимагає значних обчислень, виконується у фоні.

Архітектура конвеєру

[Client] → [Presigned S3 Upload] → [S3: original/]
         ↓
[S3 Event → SQS/Lambda] → [Transcoding Worker (FFmpeg)]
         ↓
[S3: processed/{quality}/] → [CDN CloudFront]
         ↓
[Update DB: video.status = ready, paths = {...}]
         ↓
[WebSocket/Webhook → Client notification]

Крок 1: Presigned Upload в S3

class VideoController extends Controller
{
    public function initiateUpload(Request $request): JsonResponse
    {
        $request->validate([
            'filename'     => 'required|string|max:255',
            'content_type' => 'required|in:video/mp4,video/webm,video/quicktime,video/x-msvideo',
            'size'         => 'required|integer|max:5368709120',  // 5 ГБ
        ]);

        $key = sprintf(
            'original/%d/%s/%s',
            auth()->id(),
            now()->format('Y/m'),
            Str::uuid() . '.' . pathinfo($request->filename, PATHINFO_EXTENSION)
        );

        $s3 = app('aws')->createClient('s3');
        $command = $s3->getCommand('PutObject', [
            'Bucket'      => config('filesystems.disks.s3.bucket'),
            'Key'         => $key,
            'ContentType' => $request->content_type,
        ]);

        $presigned = $s3->createPresignedRequest($command, '+2 hours');

        $video = Video::create([
            'user_id'       => auth()->id(),
            'original_key'  => $key,
            'original_name' => $request->filename,
            'status'        => 'pending',
            'size'          => $request->size,
        ]);

        return response()->json([
            'video_id'   => $video->id,
            'upload_url' => (string) $presigned->getUri(),
            'key'        => $key,
        ]);
    }

    public function confirmUpload(Request $request, Video $video): JsonResponse
    {
        $this->authorize('update', $video);

        $video->update(['status' => 'uploaded']);
        TranscodeVideoJob::dispatch($video);

        return response()->json(['status' => 'processing']);
    }
}

Крок 2: Трансходування з FFmpeg

class TranscodeVideoJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public int $timeout = 7200;  // 2 години
    public int $tries = 2;

    const QUALITIES = [
        '360p'  => ['width' => 640,  'height' => 360,  'bitrate' => '800k',  'audiorate' => '96k'],
        '720p'  => ['width' => 1280, 'height' => 720,  'bitrate' => '2500k', 'audiorate' => '128k'],
        '1080p' => ['width' => 1920, 'height' => 1080, 'bitrate' => '5000k', 'audiorate' => '192k'],
    ];

    public function __construct(private Video $video) {}

    public function handle(): void
    {
        $this->video->update(['status' => 'transcoding']);
        $inputUrl = Storage::disk('s3')->temporaryUrl($this->video->original_key, now()->addHour());
        $paths = [];

        foreach (self::QUALITIES as $quality => $params) {
            $outputKey = sprintf('processed/%d/%s/%s.mp4', $this->video->user_id, $this->video->id, $quality);
            $outputPath = sys_get_temp_dir() . "/{$this->video->id}_{$quality}.mp4";
            $scale = "scale={$params['width']}:{$params['height']}:force_original_aspect_ratio=decrease,pad={$params['width']}:{$params['height']}:(ow-iw)/2:(oh-ih)/2";

            $command = [
                'ffmpeg', '-y', '-i', $inputUrl,
                '-vf', $scale,
                '-c:v', 'libx264',
                '-preset', 'medium',
                '-crf', '23',
                '-maxrate', $params['bitrate'],
                '-bufsize', (int)($params['bitrate']) * 2 . 'k',
                '-c:a', 'aac',
                '-b:a', $params['audiorate'],
                '-movflags', '+faststart',
                $outputPath,
            ];

            $process = new \Symfony\Component\Process\Process($command);
            $process->setTimeout(3600);
            $process->run();

            if (!$process->isSuccessful()) {
                throw new \RuntimeException("FFmpeg failed for {$quality}");
            }

            Storage::disk('s3')->putFileAs(dirname($outputKey), new \Illuminate\Http\File($outputPath), basename($outputKey));
            $paths[$quality] = $outputKey;
            unlink($outputPath);
        }

        $this->video->update([
            'status'        => 'ready',
            'paths'         => $paths,
        ]);

        $this->video->user->notify(new VideoReadyNotification($this->video));
    }

    public function failed(\Throwable $e): void
    {
        $this->video->update(['status' => 'failed', 'error' => $e->getMessage()]);
    }
}

AWS MediaConvert для великих обсягів

Для великих обсягів відео використовуйте керовану службу:

import boto3

def transcode_with_mediaconvert(input_key: str, output_prefix: str) -> str:
    client = boto3.client('mediaconvert', region_name='eu-west-1')
    job = client.create_job(
        Role='arn:aws:iam::123456789:role/MediaConvertRole',
        Settings={...}
    )
    return job['Job']['Id']

Прогрес трансходування

Route::get('/videos/{video}/status', function (Video $video) {
    return response()->json([
        'status'   => $video->status,
        'progress' => $video->transcoding_progress,
        'paths'    => $video->status === 'ready' ? $video->paths : null,
    ]);
});

Терміни реалізації

Завдання Термін
Presigned upload + FFmpeg черга 4–5 днів
Генерація мініатюр + метаданих +1–2 дні
Інтеграція AWS MediaConvert 2–3 дні
HLS адаптивне потокування +3–4 дні