MobScreencast (mob_screencast v0.1.0)

Copy Markdown View Source

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 01 start codes); a keyframe frame is prefixed with SPS/PPS.
  • keyframe — true for an IDR (a freshly-joined decoder needs one; request the next via request_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

frame()

@type frame() :: %{
  bytes: binary(),
  format: :h264,
  width: non_neg_integer(),
  height: non_neg_integer(),
  timestamp_ms: non_neg_integer(),
  keyframe: boolean()
}

Functions

request_keyframe()

@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.

start_stream(socket, opts \\ [])

@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 (default 2_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 (default 30).
  • :keyframe_interval_ms — force an IDR at least this often (default 2000).

stop_stream(socket)

@spec stop_stream(Mob.Socket.t()) :: Mob.Socket.t()

Stop the active screen-capture session.

stream_opts(opts)

@spec stream_opts(keyword()) :: map()

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).