Vjerci.com

A personal blog by software engineer

February, 3-rd, 2024

Hacking chromecast -> Casting server


Hacking with Chromecast
1. Basics 2. Protocol 3. Casting server 4. Gotchas

Casting server

Casting server is quite possibly the simplest part of the whole chromecast thing. It follows standard convetions and it is a matter of simple function call.

Behind the scene it needs to support “Content-Range” header returning only partial bytes instead of the whole file at once.

Lucky for us both the standard library as well as echo support it. Here is echo code to serve static files:

	import "github.com/labstack/echo/v4"

	router := echo.New()
	router.Static("/files", "/files")

Besides “Content-Range” headers you server also needs to support CORS header. The safe bet is to just allow everything. After all it is your local home network. This is CORS that is working for me:


	router.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if c.Request().Method == http.MethodOptions {
				c.Response().Header().Add("Access-Control-Allow-Origin", "*")
				c.Response().Header().Add("Access-Control-Allow-Methods", "GET, DELETE, POST, PUT, OPTIONS, HEAD")
				c.Response().Header().Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type, Accept, ngrok-skip-browser-warning")
				c.NoContent(http.StatusOK)
			} else {
				c.Response().Header().Add("Access-Control-Allow-Origin", "*")
				c.Response().Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS, HEAD")
				c.Response().Header().Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, Content-Type, Accept, ngrok-skip-browser-warning")
			}

			return next(c)
		}
	})

Getting local ip

Besides serving files we will need to be able to pass our local ip, in order for our chromecast device to know from where to fetch the media. In order to do that we can use this snippet. Basically conn is aware of local address so we can get it from there.

func GetIP() (string) {
	conn, _ := net.Dial("udp", "8.8.8.8:80") # googles dns resolving ip, we need some valid address
	defer conn.Close()

	localAddr := conn.LocalAddr().(*net.UDPAddr)

	localIP := localAddr.IP.String()

	return localIP
}

Tying it together

So in order to cast our media to chromecast we can start the ffmpeg conversion. That will produce our m3u8 playlist and as it progresses, keep adding chunks to it. We can add that folder to serveStatic, since it serves from disk, it will make the new chunks imediately available. While our conversion is happening we can start watching our media, by instructing chromecast using cromecast protocol to start playing our m3u8 playlist.

In most cases if we haven’t selected some insanely good quality in ffmpeg, our machine will be able to keep up with playing and converting the media before playing finishes, allowing us to instantly start watching media as it downloads from web 😊

Summary

We’ve learned a bit about static files hosting and what are headers it uses behind the scene. We’ve also learned how to get local ip. We’ve gotten some idea how a robust media server should work. Next we will dive into some of the chromecast and media servers gotchas.