The problem
My Hisense remote died last year and I went down a small rabbit hole trying to find a single iOS app that worked across all the Hisense smart-TV variants. Turns out Hisense ships four entirely different smart-TV operating systems depending on the model, each with its own pairing flow and command set:
VIDAA U6/U7/U8 — Hisense's own OS, the most common on the global market
Roku TV Edition — sold mostly in North America
Google TV / Android TV — A-series and some U-series
Fire TV Edition — Amazon-branded models sold in US/UK
Every iOS app I tried covered one or two of these at most. The "universal" ones used hardware-IR blasters which my iPhone obviously doesn't have. So I spent ~3 months implementing local-network clients for each platform and shipped them in a single app called Hiremote.
This post is a sketch of what each platform's protocol looks like and the trickiest parts of getting them under one roof. Useful if you're building anything that talks to consumer TVs over the local network.
**Platform 1: VIDAA — MQTT over local Wi-Fi
**VIDAA is the most Hisense-specific platform and also the one with the least public documentation. The TVs run an MQTT broker locally on port 36669 (TLS) and accept commands over a pinned topic structure.
Pairing flow:
Discover the TV via SSDP (M-SEARCH for urn:schemas-upnp-org:device:MediaRenderer:1) or via Bonjour (_hisense._tcp.local.)
Send a request_pairing MQTT message — TV displays a 4-digit PIN on screen
User enters PIN in app — app sends confirm_pairing with the code
TV returns a long-lived auth token, app stores it in Keychain
Commands sent as JSON on /remoteapp/tv/remote_service//actions/sendkey
Command payload looks like:
{
"msg_type": "remote_control",
"keyName": "KEY_HOME",
"device_type": "iPhone"
}
The TLS handshake is the part most people get wrong — VIDAA uses a self-signed cert with a non-standard root, so you have to pin the cert or set urlSession(_:didReceive:completionHandler:) to trust the specific cert hash. Apple's Network framework handles this cleanly with sec_protocol_options_set_verify_block.
Platform 2: Hisense Roku TV — ECP over HTTP
Hisense Roku TVs implement Roku's External Control Protocol (ECP) — a plain HTTP API on port 8060. No pairing needed (this is both the upside and the downside).
Discovery is via SSDP for roku:ecp service. Once you have the IP, every command is a POST:
POST http://<TV_IP>:8060/keypress/Home
POST http://<TV_IP>:8060/keypress/VolumeUp
POST http://<TV_IP>:8060/launch/12 // launch Netflix
No auth, no encryption. ECP is the easiest of the four to integrate but you also get nothing for security — if a malicious app on the same LAN knows the IP, it can control the TV. That's a Roku architectural choice, not something we can fix from the client side.
One thing I built on top: a web-based version of the Roku remote at hiremote.app/hisense-roku-tv-remote that uses the same ECP calls but runs in any browser. No install required. Works because ECP is plain HTTP and we can proxy through a small Vercel edge function (CORS isn't permissive on the TV itself).
Platform 3: Hisense Google TV — Android TV Remote v2
Google TV-branded Hisense models implement Google's Android TV Remote v2 protocol — the same one the official Google TV app uses. Documentation is sparse, but the open-source androidtv-remote library documents the wire format well.
It's a TLS connection with mutual auth (both sides present a cert), running gRPC-style binary messages on port 6466 (pairing) and 6467 (control). Pairing uses a Diffie-Hellman key exchange with a 4-digit code displayed on screen.
The simplified flow:
TCP connect with TLS on port 6466
Send PairingRequest proto
TV shows code, user enters in app
Send PairingSecret with hash of (DH shared secret + code)
TV verifies, both sides swap certs for future connections
Switch to port 6467 for command messages
Once paired, commands are protobuf-encoded button events. Implementing this in Swift meant writing the protobuf definitions by hand from the open-source captures (Google never published the spec) and using Network framework's NWConnection with manual TLS configuration to handle the mutual auth.
This was the hardest of the four to get right. About 6 weeks of evening work.
Platform 4: Fire TV Edition — ADB over TCP
Fire TV Edition models are Android-based and accept ADB (Android Debug Bridge) commands over TCP port 5555. Same protocol Android developers use over USB, except over Wi-Fi.
Pairing:
User enables "ADB debugging over network" in TV's Developer Options (which they have to unlock first by tapping the build number seven times — same as on a phone)
App connects to :5555 with TLS
ADB key exchange: app sends its RSA public key, TV shows a confirmation dialog, user approves
Keys are stored, future connections auto-authenticate
Commands are issued as ADB shell commands:
input keyevent KEYCODE_HOME
input keyevent KEYCODE_VOLUME_UP
am start -n com.netflix.ninja/.MainActivity // launch Netflix
The protocol overhead (TLS + ADB framing) is real, so command latency on Fire TV Edition is the worst of the four — about 150ms vs ~30ms on ECP/VIDAA. Not bad, but noticeable.
This is also the only platform where the user has to dig into developer settings — friction we couldn't engineer away. Onboarding for Fire TV users is a longer flow.
The hardest part: figuring out which platform a given TV is
Users don't necessarily know which OS their Hisense TV runs. They see "Hisense" on the box. So the app has to detect the platform before showing pairing instructions.
Initially I just probed all four ports in parallel (tryConnect(IP, 8060) for ECP, tryConnect(IP, 36669) for VIDAA, etc.) and used the first one that responded. Worked but felt hacky and produced false negatives when one of the probes timed out.
Better approach: use mDNS service discovery. Each platform advertises a distinct service:
Platform mDNS service
VIDAA _hisense._tcp.local.
Roku TV _googlecast._tcp.local. (Roku also advertises this) + roku-info headers
Google TV _googlecast._tcp.local. + _androidtvremote._tcp.local.
Fire TV _amzn-wplay._tcp.
So a single NetServiceBrowser scan returns a list of TVs with platform labels attached. The remaining edge case is when a user has both a Roku TV and a Chromecast on the network — both advertise _googlecast._tcp.local. — so we also check the TXT record manufacturer field to disambiguate.
This bit took me longer than the four protocol implementations combined.
What I shipped
Hiremote — single iOS app, all four platforms, free download. Pro tier ($9.99/year) adds Apple Watch app + Lock Screen widget + custom button layouts. Built solo, 1.3K App Store ratings, 4.6 average.
Stack:
iOS app: SwiftUI + Network framework + Bonjour for discovery
Marketing site: Next.js 16 App Router
What I learned
A few non-obvious things for anyone building similar local-network app integrations:
mDNS first, port-probing as fallback. Service discovery is faster, gentler on the network, and produces fewer false positives. Pure port-probing also looks like port-scanning to some network security appliances.
TLS pinning matters for consumer TVs. Several platforms use self-signed or non-standard root certs. Network framework's sec_protocol_options_set_verify_block handles this cleanly; URLSession does not.
Pairing UX is most of the UX. Users don't read instructions. The pairing flow has to handle: "I didn't see the PIN," "The PIN went away," "It says pairing failed," and "Will this work again next time?" — each as a first-class state, not an error.
AppKeychain for tokens, not UserDefaults. Obvious in retrospect; I burned a day debugging why tokens disappeared on iOS restore from backup before fixing.
Apple's Network framework is genuinely good for this kind of work. I started with URLSession + raw sockets and migrated halfway through. NWConnection's state machine is cleaner than anything I'd have written by hand.
Links
App Store: Hiremote on App Store
Site + troubleshooting docs: hiremote.app
My other side projects: hisuperdev.github.io
Happy to answer Qs about any of the four protocols, multi-platform iOS app architecture, ASO for niche utility apps, or solo-dev distribution. Reply or DM me.
Top comments (0)