Capture the device's own screen as an H264 stream, from inside an app built with Mob — 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) and hardware-encodes it on-device (MediaCodec / VideoToolbox), so the BEAM receives ready-to-send Annex-B H264 NAL units rather than raw frames — they drop straight into a WebRTC RTP payloader.

Installation

# mix.exs
{:mob_screencast, "~> 0.1"}

# mob.exs
config :mob, :plugins, [:mob_screencast]

Screen capture needs explicit per-session user consent (Android's MediaProjection system dialog, iOS ReplayKit's prompt) — start_stream/2 triggers it; there's no manifest permission to request. The manifest merges the Android foreground-service permissions the capture runs under.

Usage

socket = MobScreencast.start_stream(socket, bitrate: 2_000_000, max_size: 1280)

def handle_info({:screencast, :permission, granted_or_denied}, socket), do: ...

def handle_info({:screencast, :frame, %{bytes: nal_units, format: :h264,
                                        timestamp_ms: t, keyframe: kf?}}, socket) do
  # bytes: one Annex-B access unit (00 00 00 01 start codes);
  # keyframe frames are prefixed with SPS/PPS
end

socket = MobScreencast.stop_stream(socket)
MobScreencast.request_keyframe()   # force an IDR when a new viewer joins

Options: :bitrate (bits/sec, default 2_000_000), :max_size (cap the longer screen edge in px, default native resolution), :fps (default 30), :keyframe_interval_ms (default 2000).

Host app requirements

AndroidManifest.xml must declare the capture service inside <application>:

<service android:name="io.mob.screencast.ScreencastService"
         android:exported="false"
         android:foregroundServiceType="mediaProjection" />

MediaProjection capture must run in a typed foreground service; without the declaration the app builds and boots fine, then throws a SecurityException at first capture.

Limits

  • :max_size is honored on Android only — the iOS encoder currently captures at native resolution.

Development

Clone, then run once:

mix setup

That fetches deps and activates the repo's git hooks (.githooks/pre-push): mix format --check, mix credo --strict (incl. ExSlop), and mix compile --warnings-as-errors run on every push, plus the full test suite when mix.exs changes — the same gate CI enforces before publishing.

License

MIT