Capture the device's own screen as an H264 stream, from inside a mob app — the
in-app replacement for host-side adb screenrecord, so a NAT'd phone can publish its
screen with no adb and no host on the same network.
The native side captures the display (Android MediaProjection, iOS ReplayKit /
ScreenCaptureKit) and hardware-encodes it to H264 on-device (MediaCodec /
VideoToolbox), so the BEAM receives ready-to-send Annex-B NAL units rather than raw
frames. That output drops straight into a WebRTC RTP payloader on the
receiving side.
Streaming
MobScreencast.start_stream(socket, bitrate: 2_000_000, max_size: 1280)delivers each encoded access unit as a message to the calling process:
handle_info({:screencast, :frame, %{bytes: nal_units, format: :h264,
timestamp_ms: t, keyframe: kf?}}, socket)bytes— one access unit of Annex-B H264 (00 00 00 01start codes); a keyframe frame is prefixed with SPS/PPS.keyframe— true for an IDR (a freshly-joined decoder needs one; request the next viarequest_keyframe/0).
Stop with stop_stream/1.
Permission
Screen capture needs explicit, per-session user consent (Android's
MediaProjection system dialog; iOS ReplayKit's broadcast prompt). start_stream
triggers it; {:screencast, :permission, :granted | :denied} reports the outcome.
Summary
Functions
Ask the encoder to emit a keyframe (IDR) on the next frame — call this when a new viewer joins so its decoder can start without waiting for the periodic keyframe.
Start capturing + encoding the screen. Frames arrive as {:screencast, :frame, map}
messages to the calling process (see the module doc).
Stop the active screen-capture session.
Build the config map passed to screencast_start_stream/1. Pure function
exposed so tests can pin defaults + serialisation without going through the
NIF. Note :max_size is currently honored on Android only — the iOS encoder
captures at native resolution (TODO in the iOS NIF).
Types
@type frame() :: %{ bytes: binary(), format: :h264, width: non_neg_integer(), height: non_neg_integer(), timestamp_ms: non_neg_integer(), keyframe: boolean() }
Functions
@spec request_keyframe() :: :ok
Ask the encoder to emit a keyframe (IDR) on the next frame — call this when a new viewer joins so its decoder can start without waiting for the periodic keyframe.
@spec start_stream( Mob.Socket.t(), keyword() ) :: Mob.Socket.t()
Start capturing + encoding the screen. Frames arrive as {:screencast, :frame, map}
messages to the calling process (see the module doc).
Options:
:bitrate— target encoder bitrate in bits/sec (default2_000_000).:max_size— cap the longer screen edge to this many px, preserving aspect (default: native resolution). Lower = less bandwidth/CPU. Currently honored on Android only; the iOS encoder captures at native resolution.:fps— target frame rate (default30).:keyframe_interval_ms— force an IDR at least this often (default2000).
@spec stop_stream(Mob.Socket.t()) :: Mob.Socket.t()
Stop the active screen-capture session.
Build the config map passed to screencast_start_stream/1. Pure function
exposed so tests can pin defaults + serialisation without going through the
NIF. Note :max_size is currently honored on Android only — the iOS encoder
captures at native resolution (TODO in the iOS NIF).