2
\$\begingroup\$

I've written a simple application that captures frames from my webcam and streams the frames via a websocket.

The stream implementation I've come up with feels weird. Networking is not my strong suit. I am wondering if the communication between the server and app can be written better.

type Stream struct {
    Frame []byte
}

func (s *Stream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
            log.Fatal(err)
    }

    //This feels weird
    //I use a ticker that ticks with the fps of the camera
    //to send the individual frames to the browser

    ticker := time.NewTicker(time.Millisecond * 33)
    for _ = range ticker.C {        
            if err = conn.WriteMessage(websocket.TextMessage, s.Frame); err != nil {
                    log.Fatal(err)                  
            }
    }
}


s := server.Stream{}
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
        http.Handle("/", &server.TemplateHandler{Filename: "index.html"})
        http.Handle("/ws", &s)
        http.ListenAndServe(":8080", nil)
}()

go func() {
        err = camera.StartStreaming()
        checkErr(err)

        for {
                frame, err := camera.ReadFrame()
                checkErr(err)

                //Also weird, but hey it works
                s.Frame = []byte(base64.StdEncoding.EncodeToString(frame))
            }
}()

wg.Wait()
\$\endgroup\$
2
\$\begingroup\$

You've reviewed your own code, and done a good job, actually:

//This feels weird
//I use a ticker that ticks with the fps of the camera
//to send the individual frames to the browser

ticker := time.NewTicker(time.Millisecond * 33)
for _ = range ticker.C {        
        if err = conn.WriteMessage(websocket.TextMessage, s.Frame); err != nil {
                log.Fatal(err)                  
        }
}

Fundamentally the issue is that you've joined two separate problems in to one function, and the result is a poorly defined operation that's a bit brittle.

What you want to do is expose a channel that sends Frame instances each time one is available. This will result in code that looks more like:

for f = range s.Frames() {        
        if err = conn.WriteMessage(websocket.TextMessage, f); err != nil {
                log.Fatal(err)                  
        }
}

Loop/range on a channel (assume s.Frames() returns a channel of Frame instances), and then send each Frame down the websocket.

This sets the channel as an "api" to the camera, and the logic is encapsulated in there.

Now, the challenge is to set up a source for that frame channel....

A separate goroutine that polls the camera for new frames, listens to a stream and breaks on each frame, or loops on a timer (like you have now), or whatever mechanism best suits managing your camera, would feed Frames in to the channel, and you've thus separated the two concerns.

You can also now have different mechanisms for sourcing frames, and still feed them in to the same method for delivering them to the websocket.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.