Back to projects

Project detail

Pixel Sentinel — Repurposed Android Security Camera

2026 (inferred)

Old phones have good cameras, microphones, and speakers, which is everything a security camera needs. But the apps that turn a phone into a camera assume you're on your home Wi-Fi and willing to fight a clunky built-in web page. I wanted to send my family one link and have it just work, anywhere.

Pixel Sentinel does that. The phone sits where it's pointed, and a small server I wrote on my home Mac handles everything between it and the outside world: it answers the link, handles sign-in invisibly, and carries the live video and two-way audio. I also replaced the camera app's built-in web page with my own. The stock one lagged about ten seconds behind reality, which makes conversation impossible; mine plays sound essentially in real time and adds a hold-to-talk button.

In practice: anyone I send the link to can watch and listen in any browser, with nothing to install and no account to create. A guard mode lets the system watch on its own and announce itself out loud through the phone's speaker. I can grab a snapshot or record a clip from the command line without interrupting the live view. And a watchdog notices when the stream dies and brings it back, so the whole thing runs unattended.

What started as recycling an old phone ended up being a small, self-hosted piece of infrastructure that just stays up.

Under the hood

The phone (a Pixel 3a XL) runs the IP Webcam app; a Python proxy on my Mac sits between it and the internet, exposed as a public HTTPS endpoint through Tailscale Serve. The proxy injects Basic Auth server-side so visitors never see a login, and speaks HTTP/1.0 close-per-request upstream because the app's responses hang browsers over HTTP/1.1 keep-alive. The talk-back channel is a WebSocket endpoint, which a plain HTTP relay breaks; the proxy detects the upgrade and relays it as a raw TCP tunnel with auth injected into the handshake.

The custom web UI replaced the stock page because its live audio buffered about ten seconds behind reality. Mine fetches the raw PCM stream, parses the WAV header for the sample rate, and feeds Int16-to-Float32 chunks into the Web Audio API behind a ~50ms jitter buffer, dropping backlog if playback falls over a second behind. Push-to-talk runs over its own WebSocket because the app's legacy getUserMedia path doesn't exist on iOS Safari, where the AudioContext also has to be resumed inside the tap gesture, and where the silent switch mutes Web Audio entirely.

The app itself was installed as a full multi-split APK bundle over adb (single APKs crash with missing native libraries and density resources). The streaming server dies with the screen off, so the phone runs display-stay-on while powered; a launchd watchdog on the Mac revives the stream when it dies.

Python
adb
Tailscale
Web Audio API
WebSockets
launchd
Android
DADaksh Adhikari