
Android screen sharing over WebRTC stopped being a weekend hack the day Android 14 shipped. Half the patterns that worked in 2023 now throw a SecurityException and kill your process on the first tap. Get one call in the wrong order and the feature dies in front of your user. We’ve shipped this on production Android apps at Fora Soft since the MediaProjection API landed, so this guide is the version we hand a new engineer: every skeleton compiles, every bitrate is from real traffic, and every gotcha is one we’ve hit and fixed.
Key takeaways
• Stop using google-webrtc. Google’s last official prebuilt, 1.0.32006, shipped in August 2020. In 2026 build on a maintained fork: LiveKit’s webrtc-sdk/android (150-series builds) or io.getstream:stream-webrtc-android (1.3.10).
• Foreground service first, projection second. Android 14 enforces the order. Get it backwards and the OS throws SecurityException.
• 720p at 15 fps, 1.5 Mbps is the honest baseline for shared screens on cellular. Reach for 1080p only after you’ve confirmed Wi-Fi.
• Android 17 is the current target (June 2026, API 37). On large screens apps can no longer opt out of filling the display, so resize handling is mandatory.
• Mobile browsers can’t do it on Android. getDisplayMedia() is not available in Chrome, Firefox or Samsung Internet on Android (MDN data, October 2026), so Android screen share needs native code.
• It’s a lifecycle problem, not a codec problem. Nail the service order, the callbacks, and TURN failover, and the rest is tuning.
Updated October 2026: Android 17 (API 37) rules, current WebRTC library versions, and where browser getDisplayMedia() works today.
Why Fora Soft wrote this guide
We build real-time video for a living: 250+ products since 2005 with a 50+ person in-house team, most of it WebRTC. WebRTC screen sharing is a feature we’ve shipped and re-shipped across Android versions, and the surface bites in predictable places. On BrainCert, a virtual-classroom platform with $3M annual revenue (2024), 100K+ customers and 500M+ classroom minutes, tutors share their Android screens to walk students through a problem. On Sprii, a live-commerce app that’s driven €365M+ in sales, hosts share product close-ups mid-stream.
The honest reason this guide exists: most tutorials online are browser-only (getDisplayMedia in JavaScript) and skip the Android-specific traps entirely. This one is about the native path. If you want the wider Android WebRTC picture first, read our companion piece on WebRTC in Android: SDKs, capture, and Compose UI, or the primer on what WebRTC actually is. If you only need the platform side (MediaProjection, foreground-service types, Android 14–17 rules) without the WebRTC transport, our Android screen sharing guide to MediaProjection goes deeper there. And when the feature has to land inside your own product, our WebRTC development team builds it from capture to TURN, including the device test matrix.
Stuck on an Android screen-share bug you can’t reproduce?
We’ll look at your capture pipeline and tell you what’s wrong in 30 minutes — no slides, just engineers who’ve shipped this.
How WebRTC screen sharing works: browser vs Android
WebRTC screen sharing captures a screen, window or tab as a video track and sends it over an RTCPeerConnection exactly like a camera track. In a desktop browser you get that track from navigator.mediaDevices.getDisplayMedia(); on Android you build it natively with MediaProjection and ScreenCapturerAndroid, because Android’s browsers don’t implement getDisplayMedia(). Everything after capture (encoding, congestion control, SRTP, TURN) is the same WebRTC stack on both.
| Platform | How you capture | Consent | What to watch |
|---|---|---|---|
| Desktop Chrome, Edge | getDisplayMedia() (Chrome 72+, Edge 79+) |
Picker every session, from a user gesture | Tab, window or full screen; tab audio; surfaceSwitching |
| Desktop Firefox, Safari | getDisplayMedia() (Firefox 66+, Safari 13+) |
Picker every session | System and tab audio support varies by browser |
| Android browsers (Chrome, Firefox, Samsung Internet) | Not supported | — | Ship a native app or a native module |
| Android native | MediaProjection + ScreenCapturerAndroid |
System dialog every session | Foreground service and Android 14–17 rules (this guide) |
| iOS native | ReplayKit broadcast upload extension | System broadcast picker | Separate extension process with a tight memory budget |
| iOS Safari | getDisplayMedia() listed from Safari 27 (September 2026) |
Picker every session | New; test on real devices before you rely on it |
Support data: MDN browser-compat data via caniuse, checked October 2026.
The browser side fits in a few lines. The one setting people miss is contentHint, the browser’s equivalent of Android’s isScreencast=true:
const stream = await navigator.mediaDevices.getDisplayMedia({
video: { frameRate: 15 },
audio: true, // tab or system audio, where offered
surfaceSwitching: "include" // let users switch tabs mid-share
});
const [track] = stream.getVideoTracks();
track.contentHint = "detail"; // keep text sharp; "motion" for video
pc.addTrack(track, stream);
track.onended = () => stopSharing(); // user clicked "Stop sharing"
With contentHint = "detail" (or "text") the encoder protects resolution over frame rate, which is what keeps code and slides readable. The call must come from a click or tap, and the browser shows its picker every time. For the Apple side, see our guide to screen sharing on iOS. The rest of this article is the Android native path.
Android screen sharing in 60 seconds
The Android screen-share path has five moving parts, and skipping any one of them breaks the feature. Here they are in the order the frames actually flow.
1. MediaProjection. Android’s system API for capturing screen pixels. It asks the user for consent every single session, and there’s no way to skip the dialog.
2. Foreground service. From Android 14, projection has to run inside a foreground service of type mediaProjection, started before the projection is granted.
3. ScreenCapturerAndroid. The WebRTC library’s VideoCapturer that wraps a MediaProjection into a frame stream.
4. PeerConnection. The WebRTC abstraction that handles ICE, congestion control, and SRTP encryption to the far peer.
5. Signaling. An out-of-band channel (WebSocket is the usual pick) that trades SDP offers, answers, and ICE candidates. WebRTC doesn’t ship signaling; you bring your own.

Figure 1. The pipeline from screen pixels to the remote viewer. The foreground service (stage 3 in the diagram) is where Android 14 breaks naive code.
Picking a WebRTC library in 2026
Start here, because most teams get this decision wrong. Google stopped publishing official prebuilt WebRTC binaries in 2020: the last org.webrtc:google-webrtc artifact is 1.0.32006 from August 2020, and JCenter, where it lived, went read-only in 2021, so nothing new will ever land there. If a Stack Overflow answer tells you to depend on it, that answer is stale.
| Library | Status (2026) | Best for | License |
|---|---|---|---|
| getstream/webrtc-android | Maintained (1.3.10, Sep 2025; m125-based core) | Kotlin and Compose helpers | BSD (libwebrtc) |
| webrtc-sdk/android (LiveKit) | Active (150.7871.01, 2026) | Newest libwebrtc, LiveKit apps, namespace isolation | BSD (libwebrtc) |
| org.webrtc:google-webrtc | Dead since 2020 (last: 1.0.32006) | Nothing — do not depend on it | BSD |
| LiveKit Android SDK | Active | High-level API plus managed infrastructure | Apache 2.0 |
| Daily Android SDK | Active | Daily.co customers, rooms out of the box | Proprietary |
Our pick for new builds: LiveKit’s io.github.webrtc-sdk:android when you want the newest libwebrtc. Its 150-series builds follow current Chromium milestones, and a prefixed variant moves the classes to livekit.org.webrtc so they don’t collide with another WebRTC copy in your app. io.getstream:stream-webrtc-android is still a fine choice for its Kotlin and Compose helpers, but its last release was 1.3.10 in September 2025 on an m125-based core, which is about 25 milestones of upstream security fixes behind the 150-series builds, so weigh that before you pin it. Both give you direct control of the PeerConnection, which the managed SDKs hide.
// build.gradle.kts (Module: app): pick one core
dependencies {
implementation("io.github.webrtc-sdk:android:150.7871.01")
// or, with Kotlin and Compose helpers:
// implementation("io.getstream:stream-webrtc-android:1.3.10")
// implementation("io.getstream:stream-webrtc-android-ui-compose:1.3.10")
}
Always check the webrtc-sdk/android repository and the GetStream/webrtc-android releases for the current version before you pin it.
Reach for a managed SDK (LiveKit, Daily, 100ms) when: you need screen share live in under a month and don’t yet have an Android engineer who’s fought foreground services. Reach for a raw fork when you need per-frame control or you’re optimizing cost at scale.
Manifest permissions and the service type
Get the manifest right first, because a missing permission or the wrong service type fails silently or with a confusing exception. Two lines catch people out: FOREGROUND_SERVICE_MEDIA_PROJECTION is required from Android 14, and POST_NOTIFICATIONS is a runtime permission from Android 13 that you also have to request at runtime.
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application ...>
<service
android:name=".ScreenShareService"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
</application>
</manifest>
The foreground-service order that breaks Android 14
Here’s the single bug that generates the most Android 14 crash reports: from API 34 you must call startForeground before getMediaProjection. Do it the other way and the OS throws SecurityException and tears down the process. Same four calls, two orders, one ships.

Figure 2. The correct order on the left; the SecurityException path on the right.
class ScreenShareService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, id: Int): Int {
startForeground(
NOTIF_ID, buildNotification(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
)
// Only NOW is it safe to grab the projection
val code = intent!!.getIntExtra(EXTRA_RESULT_CODE, -1)
val data = intent.getParcelableExtra<Intent>(EXTRA_DATA)!!
val mp = mediaProjectionManager.getMediaProjection(code, data)
startCapture(mp)
return START_STICKY
}
}
One non-obvious detail: the projection’s resultCode and data Intent are single-use. Grab them in the Activity, pass them to the service via putExtra, and don’t try to reuse them.
Requesting the projection consent
The user approves every projection session, and you can’t suppress the dialog. The flow is: launch the capture intent, receive the result, hand it to the foreground service.
private val launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK && result.data != null) {
val svc = Intent(this, ScreenShareService::class.java).apply {
putExtra(EXTRA_RESULT_CODE, result.resultCode)
putExtra(EXTRA_DATA, result.data!!)
}
ContextCompat.startForegroundService(this, svc)
}
}
fun requestScreenShare() {
val mpm = getSystemService(MediaProjectionManager::class.java)
launcher.launch(mpm.createScreenCaptureIntent())
}
Wiring the capturer to a PeerConnection
Inside the service, hand the projection to ScreenCapturerAndroid, wrap it in a VideoSource, and add the track to your PeerConnection. The one flag that decides whether your share looks sharp or smeared is isScreencast=true.
private fun startCapture(projection: MediaProjection) {
val egl = EglBase.create()
val factory = PeerConnectionFactory.builder()
.setVideoEncoderFactory(DefaultVideoEncoderFactory(egl.eglBaseContext, true, true))
.setVideoDecoderFactory(DefaultVideoDecoderFactory(egl.eglBaseContext))
.createPeerConnectionFactory()
val capturer = ScreenCapturerAndroid(projectionData(projection),
object : MediaProjection.Callback() {
override fun onStop() { stopSelf() }
})
val helper = SurfaceTextureHelper.create("CaptureThread", egl.eglBaseContext)
val source = factory.createVideoSource(true) // isScreencast = true
capturer.initialize(helper, this, source.capturerObserver)
capturer.startCapture(1280, 720, 15) // width, height, fps
val track = factory.createVideoTrack("screen0", source)
peerConnection.addTrack(track, listOf("stream0"))
}
Why the flag matters: createVideoSource(true) selects WebRTC’s screen-optimized encoder profile — lower keyframe cadence, content-adaptive bitrate. Forgetting it is the most common reason a share looks terrible on the receiving end.
The bitrate budget that actually works
The realistic default is 1280×720 at 15 fps, targeting 1.5 Mbps with a 2.5 Mbps ceiling. It survives virtually every 4G connection, reads well for about 95% of screen content, and doesn’t melt a mid-range CPU. Teams that ship “1080p, 30 fps” off the marketing page drop frames the moment a user walks outside. The numbers below are from production traffic on our Android apps.

Figure 3. Bitrate by use case. Columns are anchored to zero; the solid block is the safe floor, the lighter block is Wi-Fi headroom.
| Use case | Resolution | FPS | Bitrate | Network |
|---|---|---|---|---|
| Slides / static | 1280×720 | 5–10 | 0.5–0.8 Mbps | 3G+ |
| App walkthrough | 1280×720 | 15 | 1.0–1.5 Mbps | 4G / Wi-Fi |
| Code walkthrough | 1920×1080 | 10–15 | 1.5–2.5 Mbps | Wi-Fi |
| Video playback | 1280×720 | 24–30 | 2–3 Mbps | Wi-Fi |
| High-quality 1080p | 1920×1080 | 30 | 3–5 Mbps | Wi-Fi only |
Set the ceiling explicitly through RtpSender.parameters.encodings[i].maxBitrateBps. Skip it and WebRTC’s congestion control will happily starve your audio and other tracks on the same connection.
Codecs to ship in 2026
Ship H.264 with a VP8 fallback and let the far end pick. That’s the boring, correct answer for screen share on Android in 2026, and here’s the reasoning behind each option.
H.264. Universal hardware encode support on Android, which is why it’s the safe default. It compresses text-heavy screens a little worse than VP9, and that’s a trade we take for reliability.
VP8. The historical WebRTC default. Software support is everywhere; hardware support varies. Slightly nicer than H.264 on screen content at the same bitrate.
VP9. Better compression on dense text, but hardware support is patchy on mid-range Android. Fine as an opt-in upgrade with a software fallback.
AV1. Best compression, with real screen-content tools (palette mode and intra block copy), but software encoding costs far more CPU than VP9. Hardware AV1 encode on phones started with Google’s Tensor G3 in the Pixel 8 (2023) and is still a minority of Android devices in 2026. Decode is broader: Android delivers a software AV1 decoder to most devices through Google Play system updates. That makes AV1 a reasonable receive-side bet and a risky default for Android senders. We haven’t shipped it as the sender default yet, and we’d tell you not to either.
Reach for VP9 or AV1 when: the content is mostly static text (spreadsheets, code, documents), both peers have confirmed hardware support, and bandwidth is the binding constraint. Otherwise H.264 with VP8 fallback wins on reliability. If codecs are new to you, our primer on digital video covers the fundamentals.
Capturing system audio with the screen
If you need the audio a shared app is playing, Android 10 and up gives you AudioPlaybackCaptureConfiguration, which captures system playback during a projection session. It’s handy for sharing a screen with video or in-app sound.
val config = AudioPlaybackCaptureConfiguration.Builder(projection) .addMatchingUsage(AudioAttributes.USAGE_MEDIA) .addMatchingUsage(AudioAttributes.USAGE_GAME) .build() val record = AudioRecord.Builder() .setAudioPlaybackCaptureConfig(config) .setAudioFormat(format) .setBufferSizeInBytes(bufferSize) .build()
The catch: apps that set android:allowAudioPlaybackCapture="false" opt out entirely — banking and DRM apps usually do. Microphone input stays a separate AudioRecord; mix the two streams client-side or server-side depending on your use case.
Reach for system-audio capture when: you’re sharing a video, a game, or an app with sound cues. Skip it for plain UI walkthroughs — it’s one more permission and one more thing to fail, for audio nobody needs.
Android 14 to 17: what changed
Android 17 is the current release (it shipped on June 16, 2026, as API level 37), and each version since Android 13 has tightened screen capture. You target the newest one but keep every older guard in place, because your users span all of them.

Figure 4. Each release added a constraint. Android 17 is current as of October 2026.
Android 14 (2023). Foreground service of type mediaProjection became mandatory (see the order bug above). It also added a single-app capture picker, plus onCapturedContentResize() and onCapturedContentVisibilityChanged(). The UX assumption breaks here: users think they’re sharing slides, you think they’re sharing the home screen. Tokens are single-use too: passing the same consent Intent to getMediaProjection() twice, or calling createVirtualDisplay() twice on one projection, throws SecurityException. Per the Android media projection guide, skipping registerCallback() before createVirtualDisplay() throws IllegalStateException.
Android 15 (2024; QPR1, December 2024). From QPR1, a large status-bar chip shows any active projection and stops it with one tap, and projection stops automatically when the screen locks, so resuming needs fresh consent. Android 15 also hides notification content and one-time passwords from shared screens, and it added a screen-recording callback (WindowManager.addScreenRecordingCallback) for your own sensitive views. You can’t suppress any of it, so design the re-share prompt.
Android 16 (June 2025). For apps targeting API 36, orientation and aspect-ratio restrictions stop applying on large screens (smallest width 600dp and up), with a temporary opt-out.
Android 17 (June 2026, current). Apps targeting API 37 lose that opt-out: on large screens the system ignores screenOrientation, resizeableActivity=false and aspect-ratio limits (games excepted), so captured dimensions change more often and your onCapturedContentResize handling earns its keep. Android 17 also enforces the ACCESS_LOCAL_NETWORK runtime permission for apps targeting API 37, which matters if you discover peers or signal over the LAN, and it limits background audio playback and focus requests to apps with a while-in-use foreground service, so test any audio cues your share plays while minimized (Android 17 behavior changes). Register the callbacks once and react to them:
projection.registerCallback(object : MediaProjection.Callback() {
override fun onStop() { stopCapture() }
override fun onCapturedContentResize(w: Int, h: Int) {
capturer.changeCaptureFormat(w, h, 15)
}
override fun onCapturedContentVisibilityChanged(visible: Boolean) {
if (!visible) showResumePrompt()
}
}, handler)
Content protection with FLAG_SECURE and DRM
If you handle sensitive data — banking, healthcare, paid streaming — there are two directions to think about: what other apps’ FLAG_SECURE does to your capture, and what your own FLAG_SECURE does when someone captures you.
Capturing protected content. Windows that set FLAG_SECURE render as black in your frames. Hardware DRM (Widevine L1) is enforced at the chipset, so there’s no software workaround. Don’t promise users they can share Netflix or their banking app.
Protecting your own content. For screens that show PINs, OTPs, or PII, set window.setFlags(FLAG_SECURE, FLAG_SECURE) on those Activities. On Android 15 and later, also register a screen-recording callback so a sensitive view can refuse to render while it’s being captured — defense in depth for compliance. Android 17 removes the old setContentCaptureEnabled(false) escape hatch for apps targeting API 37; Google now points to FLAG_SECURE for keeping screen contents out of capture.
Signaling, ICE, and TURN
WebRTC needs a signaling channel it doesn’t provide, and it needs relay servers to survive mobile networks. On cellular, TURN isn’t optional: carrier-grade NAT blocks direct peering, so without a relay the connection simply never forms. Per the W3C WebRTC spec, signaling is your responsibility — WebSocket is the common choice.
ICE servers. Configure both STUN and TURN. STUN gets you direct connectivity in friendly NATs; TURN covers the cellular case. Real deployments run two or three STUN and one or two TURN servers across regions.
TURN providers. Twilio’s Network Traversal Service, Cloudflare’s TURN, Xirsys, or self-hosted coturn. Relayed traffic is billed by the gigabyte and it can dwarf your signaling and SFU costs at scale, which is the whole point of the cost model below.
Reconnect strategy. Screen-share sessions run long (10–30 minutes is normal), so the signaling socket will drop. Use exponential-backoff reconnect with heartbeats and re-negotiate SDP if the new path has a different IP topology. For the topology behind all of this — mesh, SFU, MCU — see our WebRTC architecture guide.
Not sure your TURN setup will hold at scale?
We’ll size your relay footprint and model the egress cost before you commit to a provider.
Build vs buy: the real cost
The dominant variable at runtime is relayed egress, so let’s do the arithmetic out loud. Take a 20-minute share at our default 720p / 15 fps / 1.5 Mbps, relayed through TURN in one direction:
Step 1 — data per session. 1.5 Mbit/s × 1,200 s = 1,800 Mbit = 225 MB, call it 0.22 GB.
Step 2 — egress cost. At Twilio’s Network Traversal rate of $0.40/GB (US and Germany; $0.60–$0.80/GB in Asia-Pacific and São Paulo, October 2026), that’s 0.22 × $0.40 ≈ $0.09 per 20-minute relayed session.
Step 3 — the decision. At a few thousand sessions a month the relay bill is real but modest; self-hosting coturn on a bandwidth-included box pushes the marginal cost toward zero and you pay in ops instead. The build cost that actually dominates is engineering time on the testing matrix, not servers. For a fuller picture, our WebRTC development cost breakdown walks through real budgets, our TURN bandwidth calculator runs the same math for other bitrates, and if you’re weighing a managed SFU instead, compare the Agora alternatives.
Reach for self-hosted coturn when: you’re past roughly 50,000 relayed session-minutes a month and have someone to run it. Below that, a managed relay is cheaper once you price in the ops time you’d spend babysitting a coturn cluster.
Mini case: screen share on BrainCert
The situation. BrainCert’s tutors work across India and South-East Asia, where a lot of sessions run on mid-range Androids over shaky 4G. Their screen share — the feature tutors use to walk a student through a worked problem — looked fine on office Wi-Fi in the demo and fell apart in the field: choppy frames, occasional 30-second cut-offs.
The plan. We moved capture inside a proper foreground service (killing the cut-offs), pinned the encoder to 1280×720 / 15 fps with a 1.2 Mbps target and an explicit ceiling, switched the source to isScreencast=true, and put TURN failover in front of the flaky direct paths. We tested on Pixel, Samsung A-series, and Xiaomi Redmi under shaped-network conditions rather than office Wi-Fi.
The result. Sessions stopped dropping, the picture held together on sub-par 4G, and screen share went from a support-ticket generator to a feature tutors trust. Want a similar assessment of your capture stack? Book a 30-minute call and we’ll take a look.
Troubleshooting matrix
When something’s on fire in production, start here. These are the symptoms we hit most often and what actually fixes them.
| Symptom | Likely cause | Fix |
|---|---|---|
| SecurityException on getMediaProjection | Foreground service not started first (Android 14+) | Call startForeground before getMediaProjection |
| Black frames received | Captured app uses FLAG_SECURE or DRM | Can’t fix — document it for users |
| Choppy frames on cellular | Bitrate too high; congestion control starving | Cap at 1.5–2 Mbps; consider VP8 |
| Capture stops after 30 seconds | No foreground service; OS killed projection | Run capture inside a foreground service |
| Stretched image after rotation | No rotation handler | changeCaptureFormat on configuration change |
| No connection on cellular | No TURN; CGNAT blocking direct peer | Configure TURN over udp, tcp, and tls |
| Crash on Android 15+ after lock | Capture terminated; no resume handling | Listen for onStop and prompt to re-share |
One habit that pays for itself: log the key WebRTC stats (bitrate, packets lost, frame rate) per session to your backend. Without them you’re debugging user reports blind. And test on shaped networks — adb shell tc or a network conditioner — because that’s where field bugs reproduce.
Build it yourself in five questions
Answer these five and the build-vs-buy call usually makes itself.
1. Do you have an Android engineer who’s shipped a foreground service before? If not, the learning curve lands on your users. Partner or hire first.
2. What’s your timeline? Under a month means a managed SDK; the native testing matrix alone eats weeks.
3. How many products and regions? One app in one country rarely justifies the fixed build cost over per-minute managed pricing.
4. Do you need per-frame control? On-device redaction, custom encoders, or unusual layouts push you toward the raw library.
5. Is relay cost a scaling worry? Above ~50K relayed session-minutes a month, owning the stack (and coturn) starts to pay off. If two or more answers point at “build,” that’s usually the right call — and it’s where we can help.
When not to build this yourself
Straight talk: WebRTC screen sharing is one of the harder Android features to ship correctly, and there are three cases where you should buy or partner instead of building from scratch.
1. You need it in four weeks. A team new to WebRTC underestimates the device matrix. Ship on LiveKit Cloud, Daily, or 100ms in two weeks and replace it later if you must. Our take on building on LiveKit is a fair starting point.
2. You ship one product in one country. The fixed cost of building competes badly with paying a managed SFU per participant-minute.
3. You have no experienced Android engineer. Hire or partner — don’t learn foreground services on your customers. Our video chat app development team ships 1-on-1 and group calls with screen share for web and mobile. Screen share is also the layer where AI agents plug into WebRTC next, so building on a shaky base gets expensive fast.
FAQ
Does getDisplayMedia work in Chrome on Android?
No. As of October 2026, MDN’s compatibility data lists getDisplayMedia() as unsupported in Chrome, Firefox and Samsung Internet on Android, so screen sharing from an Android phone needs a native app or SDK built on MediaProjection.
How is WebRTC screen sharing different from sending a camera?
Only the source changes: the screen arrives as a video track from getDisplayMedia() or MediaProjection and goes through the same RTCPeerConnection. Mark it as screen content (contentHint = "detail" in browsers, isScreencast=true on Android) so the encoder keeps text sharp instead of chasing frame rate.
Can I capture the screen without showing the user a permission dialog?
No. The MediaProjection consent dialog is enforced by the OS on production devices and can’t be suppressed. Only privileged system apps preinstalled by the OEM with CAPTURE_VIDEO_OUTPUT can bypass it; consumer apps can’t.
Why do my captured frames show up as black?
The captured app has FLAG_SECURE set or is rendering DRM-protected content (Netflix, Disney+, banking apps). It’s enforced at the OS and hardware level, so there’s no workaround. Document the limitation rather than promising users they can share those apps.
Do I need a foreground service even if my app is already in the foreground?
From Android 14 on, yes. Even if your app is visible, projection must run inside a foreground service of type mediaProjection, started before you call getMediaProjection, or the OS throws SecurityException.
What frame rate should you pick for screen sharing?
15 fps is a solid default. Use 5–10 fps for static slides and documents, 24–30 fps for video playback. Higher frame rates burn bandwidth and CPU and rarely improve how static content reads.
Can I share the screen without WebRTC?
Yes. You can capture with MediaProjection and send over RTMP, HLS, or RTSP. WebRTC wins for low-latency, peer-to-peer or SFU scenarios; RTMP suits one-to-many broadcast where 3–6 seconds of latency is acceptable.
How do I capture audio along with the screen?
Use AudioPlaybackCaptureConfiguration (Android 10+) with the projection for system audio, and mix in the microphone via a separate AudioRecord if you need both. Apps that set allowAudioPlaybackCapture="false" can’t be captured; that’s the source app’s call.
What’s the maximum resolution I can capture?
It’s limited only by the display, but you should downscale to 1280×720 or 1920×1080 for transmission. Sending native resolution (say 3200×1440) wastes bandwidth, and the far end downscales it anyway.
Which Android versions should I support in 2026?
MediaProjection has existed since Android 5.0 (API 21), but the foreground-service rules changed sharply across Android 8, 10, 13, and 14. Google’s December 2025 distribution data puts 83% of active devices on Android 11 or newer and about 69% on Android 12 or newer, so minSdk 30 or 31 is a sensible floor in 2026. Target Android 17 (API 37) and keep the older guards.
Can I do this without writing native code?
Yes. LiveKit, Daily, and 100ms provide higher-level Android SDKs that wrap MediaProjection and WebRTC behind a simpler API. If you don’t need fine-grained control, that’s the fastest route to a working share.
What to read next
Android
WebRTC in Android: SDKs, capture, Compose
The wider Android WebRTC picture this guide sits inside.
Architecture
WebRTC architecture for business 2026
Mesh vs SFU vs MCU, and what to pick for your scale.
Cost
WebRTC development cost
What teams actually pay to ship WebRTC features.
Vendor
Agora.io alternatives compared
If you’re choosing a managed SFU instead of building.
Case study
BrainCert virtual classroom
Live screen sharing in production at $3M ARR.
Ready to ship Android screen sharing in 2026?
Pick a maintained fork (webrtc-sdk/android or getstream/webrtc-android), declare a mediaProjection foreground service, start it before you ask for consent, hand the projection to ScreenCapturerAndroid, and add the track with isScreencast=true. Default to 720p / 15 fps / 1.5 Mbps, wire the Android 14+ resize and visibility callbacks, plan for lock-screen re-consent, and put TURN in front of cellular. Test on Pixel, Samsung, and Xiaomi under shaped networks, not office Wi-Fi.
We’ve shipped this across production Android apps — classrooms, live commerce, telehealth. If you’re starting fresh or unstalling a WebRTC project, the next step is a short scoping call where we’ll tell you the honest path.
The book · Volume 3 of 7
Video Streaming: A Complete Guide to Video Delivery: From ABR and CDNs to Players, WebRTC, DRM, and the Economics of Streaming
Once screen capture runs and the bitrate budget holds, the remaining work is in the WebRTC delivery around it. Volume 3 of our handbook covers that in depth: WebRTC and real-time media, adaptive bitrate decisions, DRM for protected content, and QoE monitoring that tells you when a shared screen turns into a slideshow. Written by Nikolay Sapunov, CEO at Fora Soft.
Read it on Amazon →Kindle & paperback · Fora Soft Video Engineering Handbook
Building WebRTC screen sharing on Android?
We’ll scope the feature, recommend the right stack, and give you a fixed-price estimate — from a team that’s done it before.
Prefer to plan the wider build first? Our video conferencing development service covers the full real-time stack around screen share, from SFU choice to recording.