Stream video với ffmpeg và laravel

Stream video với ffmpeg và laravel

Mình xử dụng https://github.com/PHP-FFMpeg/PHP-FFMpeg để tạo file hls video nhằm mục đích stream

<?php

namespace App\Services;

use FFMpeg\Format\Video\X264;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;

class FfmpegService
{
    protected $ffmpeg;

    // lỗi Unable to load FFProbe
        /*
            sudo apt update
            sudo apt install ffmpeg -y
            Kiểm tra:
            which ffmpeg
            which ffprobe cho vào env
        */
    public function __construct()
    {
        // Cấu hình FFmpeg và FFProbe
        $this->ffmpeg = \FFMpeg\FFMpeg::create([
            'ffmpeg.binaries'  => env('FFMPEG_PATH', '/usr/bin/ffmpeg'),
            'ffprobe.binaries' => env('FFPROBE_PATH', '/usr/bin/ffprobe'),
            'timeout' => 0, // giây
        ]);
        // $this->ffmpeg = \FFMpeg\FFMpeg::create([
        //     'ffmpeg.binaries'  => env('FFMPEG_PATH', 'C:\\ffmpeg\\bin\\ffmpeg.exe'),
        //     'ffprobe.binaries' => env('FFPROBE_PATH', 'C:\\ffmpeg\\bin\\ffprobe.exe'),
        //     'timeout' => 0, // giây
        // ]);
    }

    /**
     * Chuyển video sang HLS
     */
    public function convertToHLS(string $diskInput, string $inputPath, string $diskOutput, string $outputDir, bool $useGpu = false): array
    {
        $filename = basename($inputPath);
        $tempFile = storage_path('app/temp_video_' . $filename);
        $hlsDir = storage_path('app/hls_' .Str::slug( pathinfo($inputPath, PATHINFO_FILENAME)));

        try {


            // 1️⃣ Copy file từ disk input sang local
            $stream = Storage::disk($diskInput)->readStream($inputPath);
            if (!$stream) {
                throw new \Exception("Không thể đọc file nguồn");
            }

            $out = fopen($tempFile, 'w');
            while (!feof($stream)) {
                fwrite($out, fread($stream, 8 * 1024 * 1024)); // đọc 8MB/lần
            }
            fclose($out);
            fclose($stream);

            // // 2️⃣ Chuẩn bị thư mục output HLS local
            // if (!is_dir($hlsDir)) {
            //     mkdir($hlsDir, 0755, true);
            // }


            // Nếu folder tồn tại, xóa hết
            if (is_dir($hlsDir)) {
                File::deleteDirectory($hlsDir);
            }

            // Tạo lại folder mới
            mkdir($hlsDir, 0755, true);


            // 3️⃣ Chạy FFmpeg tạo HLS
            $video = $this->ffmpeg->open($tempFile);
            /*
            $format = new X264('aac', 'libx264');

            $format->setAdditionalParameters([
                '-profile:v', 'baseline',
                '-level', '3.0',
                '-start_number', '0',
                '-hls_time', '10',
                '-hls_list_size', '0',
                '-hls_segment_filename', $hlsDir . '/segment_%03d.ts',
            ]);
			*/
            if ($useGpu) {
                // GPU NVIDIA NVENC
                $format = new X264('aac', 'h264_nvenc');
                $format->setAdditionalParameters([
                    '-preset', 'fast',
                    '-crf', '23',
                    '-b:v', '5M',
                    '-maxrate', '5M',
                    '-bufsize', '10M',
                    '-hls_time', '10',
                    '-hls_list_size', '0',
                    '-hls_segment_filename', $hlsDir . '/segment_%03d.ts',
                ]);
            } else {
                // CPU
                $format = new X264('aac', 'libx264');
                $format->setAdditionalParameters([
                    '-preset', 'fast',
                    '-crf', '23',
                    '-hls_time', '10',
                    '-hls_list_size', '0',
                    '-hls_segment_filename', $hlsDir . '/segment_%03d.ts',
                ]);
            }
            $outputPath = $hlsDir . '/index.m3u8';
            $video->save($format, $outputPath);

            // 4️⃣ Upload tất cả file HLS sang disk output
            $uploaded = [];
            foreach (scandir($hlsDir) as $file) {
                if ($file === '.' || $file === '..') continue;

                $localPath = $hlsDir . '/' . $file;
                $remotePath = trim($outputDir, '/') . '/' . $file;

                Storage::disk($diskOutput)->put($remotePath, fopen($localPath, 'r'));
                $uploaded[] = $remotePath;
            }

            // 5️⃣ Dọn dẹp file tạm
            @unlink($tempFile);
            File::deleteDirectory($hlsDir);
            Log::info($hlsDir);
            return [
                'status' => 1,
                'url' => $outputDir . '/index.m3u8',
                'files' => $uploaded,
            ];

        } catch (\FFMpeg\Exception\RuntimeException $e) {
            dd($e->getMessage(), $e);
            // Dọn dẹp tất cả file tạm nếu lỗi
            @unlink($tempFile);
            if (is_dir($hlsDir)) {
                \File::deleteDirectory($hlsDir);
            }

            return [
                'status' => 0,
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Chuyển video sang HLS và tạo thư mục output tự động
     */
    public function convertToHLSWithAutoDir(string $diskInput, string $inputPath, string $diskOutput): array
    {
        $filename = pathinfo($inputPath, PATHINFO_FILENAME);
        $slug = Str::slug($filename);
        $outputDir = "hlsdata/" . $slug;
        set_time_limit(400);
        return $this->convertToHLS($diskInput, $inputPath, $diskOutput, $outputDir , false);
    }
}