This system is closer to a B2B institutional device-fleet scenario than a consumer calling app. In healthcare, eldercare, and similar facilities, many shared Android devices may need to stay online for long periods, recover without local troubleshooting, and keep calls usable in noisy rooms where pickup distance and speech quality both matter.
The real test is recovery inside a constrained LAN: whether an Android call system can return to a trustworthy state after network changes, device restarts, signaling disconnects, app upgrades, or audio environment changes.
The boundary that worked best was clear: Go gRPC handles signaling and control, WebRTC handles media, RTC Gateway owns device state and client callbacks, and the server-side state machine resolves abnormal paths into explicit outcomes.
Constraints
The abstracted scenario is a group of shared Android terminals in a constrained LAN. The network may include isolated segments, NAT, temporary route changes, multiple switches, and device restarts. The system requirements are practical:
- Calls should connect quickly, and failures should have clear reasons.
- Online state and callable state should match.
- Network and process failures should recover automatically.
- Audio issues should be diagnosable instead of being blamed on the network.
- Engineering teams should be able to reconstruct a call from logs and state.
A simple WebRTC demo can often work on the same subnet. Real deployments fail in less obvious ways: signaling, media, UI state, and device state drift apart.
Architecture Boundary
I split the system into four responsibilities.
| Component | Owns | Does not own |
|---|---|---|
| Android Client | Camera, microphone, speaker, UI state, permissions, local PeerConnection | Server session state, global device routing |
| RTC Gateway | Device registration, call control, callbacks, SDP/ICE forwarding, local recovery hooks | Media forwarding |
| Go gRPC Signaling | Device discovery, presence, session routing, server state machine, audit logs | Capture, playback, media codecs |
| STUN/TURN | NAT traversal and relay when needed | Business state decisions |
The boundary is fixed first: gRPC does not carry media. Audio and video stay on the WebRTC media path. The server controls state, routing, and observability fields. This avoids pushing real-time media load into the business service while keeping the call lifecycle observable.

Why gRPC for Signaling
WebRTC does not define a signaling protocol. WebSocket, HTTP polling, MQTT, and gRPC can all work. For this device-oriented system, gRPC was useful because the interface boundary was stable:
- Registration, call, answer, hangup, SDP, and ICE candidate messages can be described with protobuf.
- Bidirectional streams are a good fit for call events, device state changes, and error notifications.
- Go services make connection management, timeouts, interceptors, logs, and metrics easier to unify.
- Android clients, Go tools, and test programs can share the same protocol definition.
gRPC is not magic. In networks with proxies, old gateways, or aggressive long-connection cleanup, a stream may look alive while it is no longer useful. The application layer still needs heartbeat and state confirmation.
Signaling Events
I separate signaling into three event classes instead of treating everything as one vague call event.
Device State
After startup, a device registers and then keeps reporting heartbeat state. Registration should include more than a peer id:
- peer id.
- client type.
- app version and gateway version.
- registration time and latest heartbeat time.
- current call state.
- latest error code.
These fields answer a common production question: why does the page show the device as online while the call cannot be placed?
Call Control
Call control owns the business lifecycle:
- call started.
- callee ringing.
- answered or rejected.
- hold.
- hangup.
- timeout cancel.
- abnormal end.
These states need a server-side state machine. Android UI state is not enough. Page close, rotation, permission dialogs, and background switching can interrupt UI state, but the server must still move the session into one explicit result.
WebRTC Negotiation
WebRTC negotiation includes offer, answer, and ICE candidates. The signaling layer should forward them reliably. It should not parse or rewrite SDP inside the business service unless there is a specific compatibility reason.
The more useful fields to record are process states:
- offer creation and send time.
- answer creation and set result.
- candidate send and receive count.
- ICE gathering state.
- ICE connection state.
- selected candidate pair.
- whether relay was used.
Those fields help separate signaling failures, NAT traversal failures, and media path failures.
Media Path Debugging
A common mistake in constrained networks is assuming that if signaling works, media should also work. They are different paths.
I usually isolate media issues in this order:
- Same-subnet direct path.
- Cross-subnet path.
- Forced TURN relay.
- Network switch, device restart, and service restart.
Do not mix everything in the first test. When host, srflx, and relay candidates are mixed, it is hard to know whether the failure comes from code, network, NAT, TURN configuration, or an OS version. Forcing relay reduces variables. Once every device uses the same relay path, intermittent failures often become reproducible.
Recovery Design
Field systems need to accept reality: connections drop, devices restart, and routes change. The architecture should recover to a knowable state.
I split recovery into layers:
- reconnect when the gRPC stream drops.
- refresh registration after application heartbeat timeout.
- explicitly end or rebuild the session when WebRTC ICE enters
FAILED. - re-register after device restart and clear old sessions.
- report app version after upgrades for compatibility tracking.
- let the server clean peers without heartbeat for too long.
The rule is to avoid half-connected state. If the client thinks the call is still active while the server thinks it ended, the user experience becomes unpredictable. Every abnormal path should end in a clear state.
Audio Quality
Many audio/video systems start by focusing on video. In practice, audio often decides whether the experience feels usable. Common field problems include:
- speaker volume causing device resonance.
- microphone distance raising background noise.
- unstable hardware AEC/ANS/AGC behavior.
- WebRTC 3A parameters not matching device characteristics.
- nonlinear echo being misread as network jitter.
Audio should be made observable early. Subjective listening is not enough. AEC dump, sample rate checks, input/output waveforms, and device structure all matter. Some problems cannot be solved by software alone; enclosure design, speaker volume, and microphone placement also affect the result.
Observable Fields
If I could prepare only one thing early, I would prepare logs and state records. WebRTC issues are hard to debug from “the call was bad”. At minimum, keep:
- session id.
- caller and callee peer id.
- gRPC stream state.
- registration state and latest heartbeat time.
- SDP offer/answer creation and set results.
- ICE gathering and connection state.
- selected candidate pair type.
- whether TURN relay was used.
- call start, answer, hangup, and abnormal end time.
- Android OS version, app version, and WebRTC SDK version.
These fields do not need to be exposed to users, but engineers need them after a failure.
Takeaway
The hard part of this system is less about forwarding offer and answer, and more about making state trustworthy, media observable, and abnormal paths recoverable.
My practical conclusions:
- gRPC is a good control plane for device signaling, but it does not replace WebRTC media.
- Server-side state is more trustworthy than Android page state.
- ICE state and selected candidate pair matter more than scattered timeout logs.
- Audio quality needs to be analyzed across device, environment, and WebRTC 3A.
- Recovery should be an architecture capability, not an afterthought.
