Skip to content Skip to sidebar Skip to footer

Golang Http Webserver Provide Video (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))

Solution 1:

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:

funcfileh(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "/path/to/a/big/video.mp4")    
}

funcmain() {
    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: bytesContent-Length: 104715956Content-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: bytesContent-Length: 104715956Content-Range: bytes 0-104715955/104715956Content-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).

Solution 2:

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")
    } elseif strings.HasSuffix(path, ".mp4") {
        w.Header().Add("Content-Type", "video/mp4")
    }

    w.Write(data)
}

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

index.html:

<!doctype html><html><body><videoautoplayloop><sourcesrc="big_buck_bunny.mp4"type="video/mp4"></video></body></html>

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

Solution 3:

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

Solution 4:

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.

Post a Comment for "Golang Http Webserver Provide Video (mp4)"