Skip to content

1. Discovery & Connection

Your phone and your TV are two separate computers, sitting on the same Wi-Fi network, that have never talked to each other before. Before your phone can say “play this video,” it needs to answer two questions:

  1. Which device on this network is a Chromecast, and what’s its name? (“Living Room TV” vs “Bedroom TV”)
  2. How do I open a connection to it? (an IP address and a port aren’t enough on their own — you need a way to find that IP address first)

This is true of casting in general, not just this app: AirPlay, DLNA, and Chromecast all solve the same “find a device on my LAN” problem, each with its own discovery mechanism. This app uses Chromecast’s, which is built on a general-purpose protocol called mDNS (multicast DNS), also known by the marketing name Bonjour (Apple) or DNS-SD (the underlying standard, “DNS Service Discovery”).

Normal DNS requires a central server that knows “google.com → 142.250.x.x.” There’s no such central server for “what’s on my home Wi-Fi right now” — devices join and leave constantly, and nobody runs a DNS server for a home network. mDNS solves this by broadcasting the question to everyone on the local network at once, over a reserved multicast IP address (224.0.0.251), and letting any device that knows the answer reply.

Every service type has a name. Chromecasts (and Chromecast-compatible devices, Android TVs, etc.) advertise themselves under:

_googlecast._tcp.local
"Who is _googlecast._tcp?"
┌───────────┐ ───────────────────────────► ┌───────────┐
│ │ (multicast broadcast, │ │
│ Phone │ everyone on the LAN │ TV │
│ (sender) │ can hear this) │(receiver) │
│ │ ◄─────────────────────────── │ │
└───────────┘ "It's me — 192.168.1.42:8009, └───────────┘
name=Living Room TV"
(any other device on the LAN that isn't
a Chromecast simply stays silent)

The reply isn’t just an IP address — it comes with a small bundle of key/value metadata called a TXT record. For Chromecast this typically includes the device’s friendly name (fn), model (md), and a unique id. That’s how your phone can show “Living Room TV” in a picker instead of a raw IP address.

Devices get new IP addresses (DHCP leases expire, routers reboot), Chromecasts get unplugged and moved, and your phone doesn’t keep a permanent registry. So “discovery” is really “ask again, right now, and collect replies for a few seconds.” Give it a window (this app uses 5 seconds), then stop listening and show whatever answered.

t=0s t=5s
│ send the mDNS query, start listening │ stop listening,
│ │ show the picker
▼ ▼
├──────────────────────────────────────────────────┤
"Living Room TV" replies at t=0.3s
"Bedroom TV" replies at t=1.1s

Once you’ve found a device: opening a connection

Section titled “Once you’ve found a device: opening a connection”

Discovery only gets you a name, an IP, and a port (Chromecast always listens on TCP port 8009). The actual conversation happens over a TLS-encrypted TCP socket to that IP:port — TLS because Google designed the protocol to be encrypted in transit, even on a trusted home network. One quirk worth knowing: Chromecasts use a self-signed certificate (there’s no public certificate authority for “every Chromecast ever sold”), so the client has to explicitly accept that instead of validating it the normal way a browser would validate https://google.com.

Phone TV (Chromecast)
│ │
│ TCP connect to 192.168.1.42:8009 │
├────────────────────────────────────────────────────►
│ │
│ TLS handshake (self-signed cert, accepted anyway) │
◄────────────────────────────────────────────────────┤
│ │
│ ✅ encrypted socket now open │
│ (this is the pipe everything else flows over — │
│ see doc 2 for what actually gets sent on it) │
  • local_lib/flutter_cast/lib/discovery_service.dart — CastDiscoveryService wraps the bonsoir package (a Dart mDNS library) to search for _googlecast._tcp, run the timed window, and resolve each reply’s TXT record into a friendly name.
  • local_lib/flutter_cast/lib/device.dart — CastDevice, the plain value object discovery produces: serviceName, name, host, port. Nothing here knows about media or protocols yet — it’s purely “an address and a label.”
  • local_lib/flutter_cast/lib/socket.dart — CastSocket.connect(host, port) is the TLS socket step: it opens the connection and accepts the self-signed certificate.
  • lib/presentation/widgets/player/cast_device_sheet.dart — the UI layer: shows the “pick a device” sheet, runs discovery when it opens, and calls into the connection step when the user taps a device.

Discovery and connection are two genuinely separate steps, and it’s worth keeping them mentally separate: discovery is “who’s out there” (a broadcast question, answered over UDP multicast, producing a list of names+addresses), and connection is “let’s talk” (a normal, one-to-one TCP+TLS socket to one specific address you already have). Everything from here on — the actual “play this video” conversation — happens over that one socket, which is the subject of the next doc.