WebRTC Streaming¶
Stream the Android device or iOS simulator driven by an AutoMobile daemon to a WHIP server such as MediaMTX, then watch it through WHEP in a browser.
CI worker → WHIP ingest → MediaMTX → WHEP browser viewer
Configure the worker¶
Start the daemon with a booted Android device or iOS simulator, then set a unique WHIP path for each CI job:
export AUTOMOBILE_WEBRTC_WHIP_ENDPOINT="https://mediamtx.example.com:8889/$CI_JOB_ID/whip"
# Optional for authenticated or NAT-restricted deployments:
export AUTOMOBILE_WEBRTC_WHIP_TOKEN="<user>:<pass>"
export AUTOMOBILE_WEBRTC_ICE_SERVERS='[{"urls":"turn:turn.example.com:3478","username":"user","credential":"secret"}]'
MediaMTX must expose its WHIP/WHEP listener and reachable ICE candidates. For containerized or firewalled deployments, expose its WebRTC UDP port too.
Start, watch, and stop¶
The daemon accepts newline-delimited JSON on
~/.auto-mobile/webrtc-stream.sock:
SESSION_UUID="<sessionUuid returned by the MCP getAndroid or getApple tool>"
DEVICE_ID="<device id returned by the same tool>"
PLATFORM="android" # Use "ios" for an iOS Simulator.
STREAM_ID="$CI_JOB_ID"
WHIP="https://mediamtx.example.com:8889/$CI_JOB_ID/whip"
START_RESPONSE="$(
jq -nc \
--arg sessionUuid "$SESSION_UUID" \
--arg deviceId "$DEVICE_ID" \
--arg platform "$PLATFORM" \
--arg streamId "$STREAM_ID" \
--arg whipEndpoint "$WHIP" \
'{action:"start",$sessionUuid,$deviceId,$platform,$streamId,$whipEndpoint}' \
| nc -U ~/.auto-mobile/webrtc-stream.sock
)"
LEASE_ID="$(jq -er '.stream.lease.id' <<<"$START_RESPONSE")"
# Watch: https://mediamtx.example.com:8889/$CI_JOB_ID
# Send this status request at least once every 60 seconds while the job runs;
# carrying leaseId renews the stream lease.
jq -nc \
--arg sessionUuid "$SESSION_UUID" \
--arg streamId "$STREAM_ID" \
--arg leaseId "$LEASE_ID" \
'{action:"status",$sessionUuid,$streamId,$leaseId}' \
| nc -U ~/.auto-mobile/webrtc-stream.sock
jq -nc \
--arg sessionUuid "$SESSION_UUID" \
--arg streamId "$STREAM_ID" \
--arg leaseId "$LEASE_ID" \
'{action:"stop",$sessionUuid,$streamId,$leaseId}' \
| nc -U ~/.auto-mobile/webrtc-stream.sock
Acquire a fresh daemon session with the public MCP getAndroid or getApple
tool before opening the socket; every request must carry that tool’s
sessionUuid. The example always includes deviceId and platform so the iOS
path cannot silently fall back to Android. The stream reconnects after transient
network failures; browser viewers may need to reconnect too.
With AUTOMOBILE_DAEMON_STREAM_AUTH=0, lease ownership enforcement is advisory only and is not enforced.
Viewers and device ownership¶
Watching never requires owning the device. A WebRTC or video-relay stream keeps streaming when another session acquires, releases, or idle-releases the device; an owner whose session loses the device is downgraded to a read-only viewer. A stream ends only when its own session ends, the viewer disconnects, or the device goes away (removed, restored from a snapshot, quarantined, or daemon shutdown), and the end is reported with a typed reason. The capture is shared, so remaining viewers keep receiving frames when other subscribers leave. Changing stream parameters and input still require ownership.
Troubleshooting¶
No WHIP endpoint configured: setAUTOMOBILE_WEBRTC_WHIP_ENDPOINTor passwhipEndpointin the start request.401: configure MediaMTX credentials and setAUTOMOBILE_WEBRTC_WHIP_TOKEN.- Connected but black video: configure a reachable TURN server or MediaMTX ICE host.
iOS Simulator highlights¶
On macOS, highlight draws over the Simulator through screen-capture-helper;
With a helper advertising simulator-highlights, Simulators do not need the
AutoMobile SDK in the target app. Older pinned helpers retain the SDK route until
a capable helper is released; a local helper can be selected using the override below. Physical iOS devices
continue to use the SDK overlay. The host helper needs Screen Recording and
Accessibility access in System Settings → Privacy & Security. The Simulator window
must be visible. For capture of highlights, keep the window fully on one monitor.
Highlights draw a red hand-drawn circle using Android’s irregular arcs, varying
stroke width, and 1.2-second draw/hold/fade animation. Circle bounds use device
coordinates, with bounds.sourceWidth and bounds.sourceHeight describing their
source coordinate space. Selector-based highlights obtain these dimensions from
the hierarchy automatically. The helper reads the Simulator’s accessibility
display bounds to exclude its toolbar and bezel when positioning circles. Box, path, color, and stroke-style options are not supported.
While a highlight is visible, ScreenCaptureKit captures only the selected Simulator and the helper’s overlay windows, cropped to the Simulator window. The same capture path feeds raw frames and H.264 streams. Separate device screenshots do not include the host overlay. Outside highlights, capture uses its independent-window filter. The overlay host stays alive until its parent daemon exits because ScreenCaptureKit retains connections to applications whose windows have appeared in a stream.
For a local development build, build ios/screen-capture with SwiftPM and set
AUTOMOBILE_IOS_SCREEN_CAPTURE_HELPER to the absolute path of its
screen-capture-helper executable when starting AutoMobile. Both capture and
highlighting must use this build; older released helpers do not support the overlay
command. Rebuilt or unsigned executables may require renewed macOS privacy approval.
Desktop subscriptions use the same read-only viewer policy as the video relay.
Each WebRTC lease records its subscription kind when admitted: viewers keep watching
through device ownership changes, while an owner losing ownership downgrades to a
viewer. This downgrade is one-way, even if that session later regains ownership;
renewal preserves the recorded kind. Any kind may release its own lease with stop,
or renew its live lease with a compatible start. A non-owner’s leaseless stop
releases only that session’s leases, preserving anonymous and other sessions’ leases.
Capture stops when no leases remain. Viewers also allow status, list, and await.
viewer_read_only rejects changes to stream parameters or controls acting on others.
The device’s current owner controls its streams: stop, with or without a lease,
stops the stream outright, releases the owner’s leases, and ends every other lease
with stopped_by_owner. Viewers learn this on their next lease-carrying request.
An owner start with different parameters replaces the capture using bounded owner-stop
cleanup, ending other attached leases with stopped_by_owner. Compatible parameters
attach without restarting. Comparisons resolve environment defaults too.
Video relay subscribe and WebRTC start, including renewal, admit any live,
non-releasing device session to any device. The owning session attaches as owner;
other device sessions attach read-only as viewer, including on another session’s
device. Missing, unknown, expired, releasing and registration-only observer sessions
remain rejected. Viewers cannot change an owner’s capture or control: WebRTC joins
keep the existing capture configuration. A fresh WebRTC viewer start ignores all
capture overrides and uses environment defaults if it starts the capture. Viewer
lease renewals must remain compatible. The video relay ignores viewer hints and
size while the device has an owner; a first owner join replaces viewer-created
hints and size with its supplied values or defaults. Owner-less relay hints retain
the existing last-supplied semantics.
Either kind ends when its session ends (session_ended), the device is removed
(device_removed) or quarantined (identity_quarantined), or the daemon shuts down
(daemon_shutdown). VM restore ends the captured incarnation as device_restored.
This socket is request/response only: the next status, await, or stop carrying
an ended lease reports its reason and subscriptionKind. Ended leases are retained
for one lease TTL, up to 256 entries; a later start can re-subscribe with a new lease.
The optional response fields subscriptionKind, reason, and errorCode are additive
and can be ignored by older clients. Auth disabled and legacy authenticators without
an ownership resolver retain the previous stop and attach semantics.
Observation-stream, telemetry-push, failures-push, and performance-push subscribe commands require a valid sessionUuid when stream authentication is enabled. Register one with daemon/registerSession (or acquire a device session) before subscribing. Older desktop apps are rejected at subscribe until they register and send their session UUID. AUTOMOBILE_DAEMON_STREAM_AUTH=0 retains the existing opt-out behavior. Registration-only sessions are admitted only on observation and push paths; device-scoped observation requests retain their existing ownership checks.