Screen sharing is almost the basic function of video call platforms. Skype, WhatsApp, Telegram, Teams, Google Meet. All these systems have this feature.
We have already explained how to implement screen sharing into a mobile iOS application. In this article we will explain how to do it on Android. With code examples.
Also in other articles within our Android series you will learn how to make custom call notification and how we develop WebRTC systems in general.
Screen sharing implementation
You can enable screen sharing immediately when you create a new video call, in advance, before it actually starts.
However, we will take a glance at the most common case, when screen sharing starts after the call itself started.
To simplify the description of the screen sharing implementation, let’s say that we already have a ready-made application with WebRTC calls. Read more about the implementation of the WebRTC video call.
Steps for implementation will be the following:
Accessing screen content
Creating a video track with a screen image
Replacing the camera video track to the the screen video track
Displaying a notification of an ongoing screen sharing
Now each one in detail:
Accessing screen content
First we get access to capturing the screen content and device sound with Media Projection API:
val screenSharingPermissionLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
// Handle request result
val screenSharingIntent = result.data
if (screenSharingIntent != null) {
// Success request
}
}
val mediaProjectionManager = getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
val intent = mediaProjectionManager.createScreenCaptureIntent()
screenSharingPermissionLauncher.launch(intent)
When calling for screenSharingPermissionLauncher.launch(intent), a dialog window will appear. It will tell the user that media projection will access all the information displayed on the screen.
As a result of successful access to the screen content we get screenSharingIntent
Creating a video track with a screen image
Create videoCapturer, which will capture the image from the screen:
val mediaProjectionCallback = object : MediaProjection.Callback() {
override fun onStop() {
// screen capture stopped
}
}
val videoCapturer = ScreenCapturerAndroid(screenSharingIntent, mediaProjectionCallback)
Then create localVideoTrack:
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBase.eglBaseContext)
val videoSource = peerConnectionFactory.createVideoSource(/* isScreencast = */ true)
videoCapturer.initialize(surfaceTextureHelper,context, videoSource.capturerObserver)
videoCapturer.startCapture(displayWidth, displayHeight, fps)
val localVideoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource)
Replacing the camera video track to the the screen video track
To replace the video track correctly, implement the renegotiation logic for both call participants. When changing local media tracks, WebRTC calls onRenegotiationNeeded. It repeats the sdp exchange process:
val peerConnectionObserver = object : PeerConnection.Observer {
...
override fun onRenegotiationNeeded() {
// Launch sdp exchange
peerConnection.createOffer(...)
}
}
val peerConnection = peerConnectionFactory.createPeerConnection(iceServers, peerConnectionObserver)
Now to the video track replacing. Delete the camera video track from the local media:
localMediaStream.removeTrack(cameraVideoTrack)
Stop capturing the camera video:
cameraVideoCapturer.stopCapture()
Add screen sharing video track:
localMediaStream.addTrack(screenVideoTrack)
Displaying a notification about an ongoing screenshot
At the start of the screen sharing, it’s necessary to run the Foreground Service with the notification that the demonstration has started.
Create a ScreencastService and add it to AndroidManifest.xml. Also specify the foregroundServiceType parameter:
<service
android:name=".ScreencastService"
android:foregroundServiceType="mediaProjection"
android:stopWithTask="true" />
Before replacing the video trach from the camera with the screen sharing video track, launch ScreencastService:
val intent = Intent(this, ScreencastService::class.java)
ContextCompat.startForegroundService(this, intent)
Then, in ScreencastService (e.g. in onStartCommand()), call the startForeground method:
startForeground(NOTIFICATION_ID, notification)
Common issues with implementation
The app crashes on Android 10+ devices with the “Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION” error
Foreground Service ensures that the system will not “kill” the app during screen sharing. The Foreground Service notification will inform the user about the running screen sharing and will allow to quickly return to the application.
How to solve: do not forget to display the notification about the started screen sharing 🙂
There is no replacement for the camera video track to the screen one
This might occur if the recognition logic is not implement (correctly) on one or both callers sides.
How to solve: override onRenegotiationNeeded method in PeerConnection.Observer (method name on other platforms may differ). When calling onRenegotiationNeeded, the sdp exchange process must be started.
Conclusion
In this article we covered the implementation of screen sharing in video call and how you can:
Access screen content with MediaProjection API
Capture screen content with ScreenCapturerAndroid
Create a local video track with screen image
Replace the camera video track with the screen video track
Implement Foreground Service for displaying screenshot notification
See the final result of screen sharing implementation in our secure video calling app.