123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- package transcoder
- /*
- Transcoder.go
- This module handle real-time transcoding of media files
- that is not supported by playing on web.
- */
- import (
- "io"
- "log"
- "net/http"
- "os/exec"
- "time"
- )
- type TranscodeOutputResolution string
- const (
- TranscodeResolution_360p TranscodeOutputResolution = "360p"
- TranscodeResolution_720p TranscodeOutputResolution = "720p"
- TranscodeResolution_1080p TranscodeOutputResolution = "1280p"
- TranscodeResolution_original TranscodeOutputResolution = ""
- )
- // Transcode and stream the given file. Make sure ffmpeg is installed before calling to transcoder.
- func TranscodeAndStream(w http.ResponseWriter, r *http.Request, inputFile string, resolution TranscodeOutputResolution) {
- // Build the FFmpeg command based on the resolution parameter
- var cmd *exec.Cmd
- transcodeFormatArgs := []string{"-f", "mp4", "-vcodec", "libx264", "-preset", "superfast", "-g", "60", "-movflags", "frag_keyframe+empty_moov+faststart", "pipe:1"}
- var args []string
- switch resolution {
- case "360p":
- args = append([]string{"-i", inputFile, "-vf", "scale=-1:360"}, transcodeFormatArgs...)
- case "720p":
- args = append([]string{"-i", inputFile, "-vf", "scale=-1:720"}, transcodeFormatArgs...)
- case "1080p":
- args = append([]string{"-i", inputFile, "-vf", "scale=-1:1080"}, transcodeFormatArgs...)
- case "":
- // Original resolution
- args = append([]string{"-i", inputFile}, transcodeFormatArgs...)
- default:
- http.Error(w, "Invalid resolution parameter", http.StatusBadRequest)
- return
- }
- cmd = exec.Command("ffmpeg", args...)
- // Set response headers for streaming MP4 video
- w.Header().Set("Content-Type", "video/mp4")
- w.Header().Set("Transfer-Encoding", "chunked")
- w.Header().Set("Cache-Control", "public, max-age=3600, s-maxage=3600, must-revalidate")
- w.Header().Set("Accept-Ranges", "bytes")
- // Get the command output pipe
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- http.Error(w, "Failed to create output pipe", http.StatusInternalServerError)
- return
- }
- // Get the command error pipe to capture standard error
- stderr, err := cmd.StderrPipe()
- if err != nil {
- http.Error(w, "Failed to create error pipe", http.StatusInternalServerError)
- log.Printf("Failed to create error pipe: %v", err)
- return
- }
- // Start the command
- if err := cmd.Start(); err != nil {
- http.Error(w, "Failed to start FFmpeg", http.StatusInternalServerError)
- return
- }
- // Create a channel to signal when the client disconnects
- done := make(chan struct{})
- // Monitor client connection close
- go func() {
- <-r.Context().Done()
- time.Sleep(300 * time.Millisecond)
- cmd.Process.Kill() // Kill the FFmpeg process when client disconnects
- done <- struct{}{}
- //close(done)
- }()
- // Copy the command output to the HTTP response in a separate goroutine
- go func() {
- if _, err := io.Copy(w, stdout); err != nil {
- // End of video or client disconnected
- cmd.Process.Kill()
- return
- }
- }()
- // Read and log the command standard error
- go func() {
- errOutput, _ := io.ReadAll(stderr)
- if len(errOutput) > 0 {
- log.Printf("FFmpeg error output: %s", string(errOutput))
- }
- }()
- go func() {
- if err := cmd.Wait(); err != nil {
- log.Printf("FFmpeg process exited: %v", err)
- return
- }
- }()
- // Wait for the command to finish or client disconnect
- <-done
- log.Println("[Media Server] Transcode client disconnected")
- }
|