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:
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:
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:
Cheers and thanks to all of you who helped me.
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).
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.
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
.
This is an example that supports all file types.
contentType := http.DetectContentType(buffer)
https://play.golang.org/p/XuVgmsCHUZB