Screen sharing
Screen sharing is MediaProjection → ScreenCapturerAndroid → the outgoing
video track, plus device-playback audio mixed into the mic stream, plus
orientation follow-through.
Starting a share
The resolution presets map to concrete capture requests and bitrate caps (see
ScreenShareResolution). “Native”
shares the full panel at 60 fps.
The capture pipeline
- Dedicated
SurfaceTextureHelper("ScreenCaptureThread"). Sharing the camera capturer’s helper leaves the MediaProjection virtual display with an invalid producer surface (SurfaceFlinger: dequeueBuffer failed … error: -19) and zero frames are ever delivered. - The camera capturer is stopped first; the screen capturer feeds the same
VideoSourcevia itscapturerObserver. degradationPreference = MAINTAIN_FRAMERATE.MAINTAIN_RESOLUTIONpinned the resolution and let the frame rate collapse to ~2 fps on weaker H.265 encoders. Screen content stays legible when WebRTC sheds resolution under pressure and recovers when it eases; a 2 fps slideshow does not.applyScreenShareBitrate()- the camera preset’s bitrate cap is far too low for 1080p/native screen content, so the cap is raised to the preset’s value andscaleResolutionDownBycleared.
Screencast source - pending
Ideally the screen share would run on its own createVideoSource(isScreencast = true) rather than reusing the camera source. That source pins resolution
and lets FPS give - the correct trade-off for a screen, and the opposite of
the camera source. That
change plus a native-resolution bitrate bump is a pending improvement.
Device audio
ScreenAudioCapturer reuses the same MediaProjection to capture device
playback (AudioPlaybackCaptureConfiguration) and mixes it into the outgoing mic
buffer as the last stage of CompositeAudioProcessing - after RNNoise and
gain, so the shared media audio isn’t denoised or re-gained. Stops with the
share.
Orientation follow-through
ScreenCapturerAndroid never resizes its virtual display on its own, so rotating
the sharer’s phone just letterboxes the new orientation into the old buffer.
MainActivity watches for rotation (a DisplayListener +
onConfigurationChanged) and calls WebRtcClient.resizeScreenShare(w, h), which
changeCaptureFormats the capturer to the swapped dimensions. The new rotation
is also sent to the peer on the screen-share frame (rotation field) so the
viewer’s PiP window can match aspect ratio, and - if
screenShareAutoRotate is on - the viewer’s whole screen rotates to the sharer’s
orientation.
The follow-through also handles 180° flips within one orientation (phone upside-down).
On the sharer’s own screen
While you share, CallScreen shows a ScreenShareCover instead of the call -
a card explaining the screen is being shared, with a warning if your camera is
also off (the peer would see nothing but your screen). This keeps your call UI
out of the shared frame.
PiP is suppressed while sharing
MainActivity.enterPipMode() bails out when isScreenSharingState is set:
fun enterPipMode(): Boolean {
if (SDK_INT >= O && inCallState && !isScreenSharingState) { … }
return false
}The PiP window would float on top of whatever you’re presenting and get
captured back into the share (the peer sees their own video echoed in your
screen). The foreground service keeps the call alive
in the background regardless, and the CallStyle notification carries the
controls. When you stop sharing, PiP works normally again.
Stopping
stopScreenShare() stops the audio capturer, stops+disposes the screen
capturer, disposes the dedicated helper, and calls ensureCapturing() to bring
the camera back. It runs on an explicit tap, on the system’s
MediaProjection.Callback.onStop, and on call teardown.