DEV Community

Perm Chao
Perm Chao

Posted on

Try to stream video part 1

For path 1, I want to try basic thing to streaming video on internet. Let begin with using ffmpeg + video.js

Backend service

main.go

func main() {
    fileServer := http.FileServer(http.Dir("./videos"))


    http.Handle("/upload", http.HandlerFunc(uploadHandler))
    http.Handle("/stream/", http.StripPrefix("/stream/", fileServer))



    err := http.ListenAndServe(":8080", func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
            if r.Method == "OPTIONS" {
                w.WriteHeader(http.StatusOK)
                return
            }
            next.ServeHTTP(w, r)
        })
    }(http.DefaultServeMux))
    if err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    r.Body = http.MaxBytesReader(w, r.Body, 2<<30)

    file, header, err := r.FormFile("video")
    if err != nil {
        http.Error(w, fmt.Sprintf("Error retrieving file: %v", err), http.StatusBadRequest)
        return
    }
    defer file.Close()

    log.Printf("📥 กำลังรับไฟล์: %s (ขนาด: %d bytes)", header.Filename, header.Size)

    uploadDir := "./raw_videos"
    if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
        http.Error(w, "Unable to create directory", http.StatusInternalServerError)
        return
    }

    destPath := filepath.Join(uploadDir, header.Filename)
    destFile, err := os.Create(destPath)
    if err != nil {
        http.Error(w, "Unable to create file on server", http.StatusInternalServerError)
        return
    }
    defer destFile.Close()

    _, err = io.Copy(destFile, file)
    if err != nil {
        http.Error(w, fmt.Sprintf("Error saving file: %v", err), http.StatusInternalServerError)
        return
    }

    log.Printf("✅ อัปโหลดสำเร็จ: %s ถูกเซฟไว้ที่ %s", header.Filename, destPath)

    seq.Add(1)
    id := seq.Load()

    // ** --------------------- Focus here --------------------- **
    go func(id int64, path string) {
        if e := tsToHLS(destPath, "./videos"); e != nil {
            log.Panicln(e)
        }
    }(id, destPath)

    w.WriteHeader(http.StatusOK)
    w.Write(fmt.Appendf(nil, "Upload successful: %s", header.Filename))
}
Enter fullscreen mode Exit fullscreen mode
func tsToHLS(i, o string) error {
    plPath := filepath.Join(o, "playlist.m3u8")
    sPath := filepath.Join(o, "video_%03d.ts")
    cmd := exec.Command("ffmpeg",
        "-i", i,
        "-vf", "scale=854:-2",
        "-crf", "26",
        "-b:v", "1000k",
        "-maxrate", "1000k",
        "-bufsize", "2000k",
        "-codec:v", "libx264",
        "-codec:a", "aac",
        "-f", "hls",
        "-hls_time", "10",
        "-hls_list_size", "0",
        "-hls_segment_filename", sPath,
        plPath,
    )

    log.Printf("⏳ กำลังเริ่มแปลงไฟล์: %s ...", i)
    output, err := cmd.CombinedOutput()
    if err != nil {
        return fmt.Errorf("FFmpeg error: %v, output: %s", err, string(output))
    }

    log.Println("✅ แปลงไฟล์เป็น HLS สำเร็จแล้ว!")
    return nil
}
Enter fullscreen mode Exit fullscreen mode

ตารางสรุปพารามิเตอร์ FFmpeg สำหรับระบบ Streaming

พารามิเตอร์ (Args) หน้าที่และความหมาย ตัวอย่างการใช้งาน ประโยชน์ที่มีต่อระบบ Local Netflix
-i Input: ระบุพาธหรือชื่อไฟล์วิดีโอต้นฉบับที่ต้องการนำมาประมวลผล -i input.mp4 บอกให้ FFmpeg รู้ว่าจะเอาหนังเรื่องไหนมาจัดการ
-vf Video Filter: ใช้จัดการตัวเนื้อภาพ ในที่นี้เราใช้ฟิลเตอร์ scale เพื่อย่อ/ขยายขนาดภาพ -vf "scale=1280:-2" ลดความละเอียดภาพ (เช่น เหลือ 720p) ทำให้ไฟล์เล็กลงอย่างเห็นได้ชัด
-crf Constant Rate Factor: คุมคุณภาพของภาพภาพรวม (ค่าเลขยิ่งเยอะ ไฟล์ยิ่งเล็ก แต่ภาพจะแตก) -crf 26 ช่วยบีบอัดไฟล์ให้เล็กลงโดยรักษาความชัดให้อยู่ในเกณฑ์ที่สายตาคนมองว่าสวย
-b:v Video Bitrate: กำหนดอัตราการส่งข้อมูลของภาพวิดีโอต่อวินาที -b:v 2000k ควบคุมไม่ให้บิตเรตพุ่งสูงเกินไป ป้องกันการกระตุกของ WiFi ในบ้าน
-maxrate Maximum Bitrate: กำหนดเพดานบิตเรตสูงสุดไม่ให้เกินค่านี้เด็ดขาดในฉากที่เคลื่อนไหวเร็ว -maxrate 2000k ป้องกันไม่ให้ฉากแอคชั่นกินแบนด์วิดท์เน็ตเวิร์กสูงเกินไป
-bufsize Buffer Size: กำหนดขนาดของถังพักข้อมูลที่ FFmpeg จะใช้วิเคราะห์เพื่อคุมบิตเรต -bufsize 4000k ช่วยให้การคำนวณบิตเรตไหลลื่นและคงที่นิ่งขึ้น
-codec:v (หรือ -c:v) Video Codec: เลือกตัวถอดรหัส/เข้ารหัสภาพวิดีโอ -codec:v libx264 แปลงให้เป็นฟอร์แมต H.264 ซึ่งเป็นมาตรฐานที่ Browser ทุกตัวเปิดดูได้ชัวร์
-codec:a (หรือ -c:a) Audio Codec: เลือกตัวถอดรหัส/เข้ารหัสระบบเสียง -codec:a aac แปลงระบบเสียงเป็น AAC ซึ่งเป็นที่นิยมและรองรับบนหน้าเว็บอย่างสมบูรณ์
-b:a Audio Bitrate: กำหนดอัตราการส่งข้อมูลของเสียงต่อวินาที -b:a 128k บีบอัดไฟล์เสียงให้เล็กลง แต่ยังคงความชัดเจนของเสียงพูดและเพลง
-f Format: ระบุรูปแบบปลายทางของไฟล์ผลลัพธ์ -f hls สั่งให้ส่งเอาต์พุตออกมาเป็นระบบ HLS (HTTP Live Streaming)
-hls_time HLS Segment Duration: กำหนดความยาว (วินาที) ของไฟล์วิดีโอย่อยแต่ละชิ้น -hls_time 10 สั่งให้หั่นวิดีโอซอยออกมาเป็นชิ้นละ 10 วินาที (ไฟล์ตระกูล .ts)
-hls_list_size Playlist Window Size: กำหนดจำนวนไฟล์ย่อยที่จะแสดงในสารบัญ (0 หมายถึงให้ใส่ทั้งหมด) -hls_list_size 0 บังคับให้ไฟล์ .m3u8 เก็บรายชื่อวิดีโอตั้งแต่ต้นจนจบเรื่อง (ถ้าตั้งเป็น 5 จะดูได้แค่ 5 ชิ้นล่าสุดเหมือนไลฟ์สด)
-hls_segment_filename Segment Naming: กำหนดรูปแบบการตั้งชื่อและพาธของไฟล์วิดีโอย่อยที่ถูกหั่น -hls_segment_filename "video_%03d.ts" ได้ไฟล์ชื่อเรียงตามลำดับ เช่น video_001.ts, video_002.ts เพื่อให้ไล่ลำดับได้ถูกต้อง

Top comments (0)