dt4320279
dt4320279
2016-06-28 07:21

GoLang http网络服务器提供视频(mp4)

已采纳

I developed a webserver using golang. Pretty plane stuff, it just provides html/js/css and images which works perfectly fine:

func main() {
    http.Handle("/", new(viewHandler))
    http.ListenAndServe(":8080", nil)
}

func (vh *viewHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    path := r.URL.Path[1:]
    log.Println(path)
    data, err := ioutil.ReadFile(string(path))

    if err == nil {

        var contentType string

        if strings.HasSuffix(path, ".html") {
            contentType = "text/html"
        } else if strings.HasSuffix(path, ".css") {
            contentType = "text/css"
        } else if strings.HasSuffix(path, ".js") {
            contentType = "application/javascript"
        } else if strings.HasSuffix(path, ".png") {
            contentType = "image/png"
        } else if strings.HasSuffix(path, ".jpg") {
            contentType = "image/jpeg"
        } 

        w.Header().Add("Content-Type", contentType)
        w.Write(data)
    } else {
        log.Println("ERROR!")
        w.WriteHeader(404)
        w.Write([]byte("404 - " + http.StatusText(404)))
    }
}

I tried to add a video in mp4 format to my website in the same way:

<video width="685" height="525" autoplay loop style="margin-top: 20px">
   <source src="../public/video/marketing.mp4" type="video/mp4">Your browser does not support the video tag.</video>

and enhanced the content type part of my go application with:

else if strings.HasSuffix(path, ".mp4") {
            contentType = "video/mp4"
        }

When I open the html file directly in Chrome the Video is playing correctly, but if I open the website by calling the Web Server at http://localhost... it can not be loaded and therefor not played.

There is no video loaded, if I try to hit it directly the browser just shows some video contols without loading the file:

enter image description here

media loaded twice

media details

Any ideas on this? thanks in advance.

UPDATE:

As suggested I also tried another video which is working perfectly fine! but I have no clue why, I compared both videos:

enter image description here enter image description here

Could it be the size??

Cheers and thanks!

UPDATE2:

icza is right and I marked his post as correct answer to my question. But let me explain what I finally did to get it working:

I tried to solve my problem without using the http.FileServe method. Therefore I implemented like so:

} else if strings.HasSuffix(path, ".mp4") {
            contentType = "video/mp4"
            size := binary.Size(data)
            if size > 0 {
                requestedBytes := r.Header.Get("Range")
                w.Header().Add("Accept-Ranges", "bytes")
                w.Header().Add("Content-Length", strconv.Itoa(size))
                w.Header().Add("Content-Range", "bytes "+requestedBytes[6:len(requestedBytes)]+strconv.Itoa(size-1)+"/"+strconv.Itoa(size))
                w.WriteHeader(206)
            }
        }

        w.Header().Add("Content-Type", contentType)
        w.Write(data)

That worked for the first time I played the video. But since I want it to loop it should directly start from the beginning but it stuck at the very last frame of the video.

So I implemented the ServeFile() method like:

if contentType == "video/mp4" {
            http.ServeFile(w, r, path)
        } else {
            w.Header().Add("Content-Type", contentType)
            w.Write(data)
        }

The video is also playing in a loop correctly now. But I still have no idea why ServeFile() is working and the other method not though the response is exactly the same for both methods. Nevertheless the content is looking like that now:

enter image description here

Cheers and thanks to all of you who helped me.

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

4条回答

  • dounanyin3179 dounanyin3179 5年前

    Your "issue" is related to the video length / size. I don't know the default buffer Chrome uses (it may depend on multiple things like free memory, free disk space etc.), but you shouldn't rely on it for your video to play!

    One of your video is a few seconds shorter and almost 3rd the size of the other. Chrome buffers completely the smaller video (likely in memory), while does not do the same with the other. And since it's bigger than the buffer, it needs the server to support Range requests (partial content serving). The way you serve the content of your video, you are not supporting partial content serving (which Chrome expects in case of "large" videos), so Chrome simply denies to handle / play the video.

    You should serve your files using http.ServeFile() which supports serving Range requests (required for videos that are bigger than a certain size). See related question: How to serve http partial content with Go?

    Once you do this, both your video files will play fine.

    Going forward, there is no practical reason not to use http.ServeFile(). It won't load the complete file into memory as you did which is a really bad idea in case of big (video) files. http.ServeFile() handles Range requests if asked so, and also properly sets response headers including the Content-Type (and knows that .mp4 files require video/mp4 MIME type).

    Analyzing requests of a big video file

    Testing this simple application:

    func fileh(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "/path/to/a/big/video.mp4")    
    }
    
    func main() {
        http.HandleFunc("/", fileh)
        panic(http.ListenAndServe("localhost:8080", nil))
    }
    

    And opening http://localhost:8080 from Chrome, the browser makes 2 requests (I intentionally filtered out unimportant / unrelated request-response headers):

    Request #1

    GET / HTTP/1.1
    

    To get the requested (root) path.

    Response #1

    HTTP/1.1 200 OK
    Accept-Ranges: bytes
    Content-Length: 104715956
    Content-Type: video/mp4
    

    As you can see, the Go app reports the video size is around 100 MB. http.ServeFile() also includes the response header Accept-Ranges: bytes which lets clients know prior that we support Range requests.

    How does Chrome respond to this? Closes the connection after receiving 32 KB (32.2 KB including headers; which is enough to look into the stream and tell what it can do with it), then fires another request (which also happens even if the Accept-Ranges response header is not sent - your example code does not send it):

    Request #2

    GET / HTTP/1.1
    Referer: http://localhost:8080/
    Range: bytes=0-
    

    Chrome fires a Range request (asking everything from the first byte), so if your Go app does not support this, you're in trouble. See response #2.

    Response #2

    HTTP/1.1 206 Partial Content
    Accept-Ranges: bytes
    Content-Length: 104715956
    Content-Range: bytes 0-104715955/104715956
    Content-Type: video/mp4
    

    http.ServeFile() behaves well, and responds properly to the Range request (HTTP 206 Partial Content status code and Content-Range header), and sends the range that was requested (everything in this case).

    Your example code does not respond with an HTTP 206 Partial Content but with a simple HTTP 200 OK. When Chrome receives the unexpected HTTP 200 OK, that's the point when it gives up on playing your video. If the video file is small, Chrome will not make a big deal out of it as it will just accept the HTTP 200 OK response too (and likely just store the small video in memory or in a cache file).

    点赞 评论 复制链接分享
  • douyou2732 douyou2732 5年前

    Your code seems to work fine for me. Here's the complete working example I tested, using Chrome on OS X.

    main.go:

    package main
    
    import "io/ioutil"
    import "log"
    import "net/http"
    import "strings"
    
    type viewHandler struct{}
    
    func (vh *viewHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        path := r.URL.Path[1:]
    
        data, err := ioutil.ReadFile(string(path))
    
        if err != nil {
            log.Printf("Error with path %s: %v", path, err)
            w.WriteHeader(404)
            w.Write([]byte("404"))
        }
    
        if strings.HasSuffix(path, ".html") {
            w.Header().Add("Content-Type", "text/html")
        } else if strings.HasSuffix(path, ".mp4") {
            w.Header().Add("Content-Type", "video/mp4")
        }
    
        w.Write(data)
    }
    
    func main() {
        http.Handle("/", new(viewHandler))
        http.ListenAndServe(":8080", nil)
    }
    

    index.html:

    <!doctype html>
    <html>
    <body>
        <video autoplay loop>
            <source src="big_buck_bunny.mp4" type="video/mp4">
        </video>
    </body>
    </html>
    

    Video downloaded from http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4.

    点赞 评论 复制链接分享
  • dtukyb8095 dtukyb8095 5年前

    Well, since it's not a live stream (in terms that the size and duration are known since the source is an mp4 file) I think your server should honor the Range header in the request and respond with proper Content-Length, Content-Range and Accept-Range values (otherwise seeking would be impossible and I guess you don't want that). That should make Chrome happy. Take care when implementing the handling of the Range header value in the request - if it is not 0- then you must actually seek to the requested point and return the data from there. Also I don't think videos should be transferred with Transfer-Encoding set to chunked.

    点赞 评论 复制链接分享
  • droos02800 droos02800 3年前

    This is an example that supports all file types. contentType := http.DetectContentType(buffer) https://play.golang.org/p/XuVgmsCHUZB

    点赞 评论 复制链接分享

相关推荐