Screen share in a 1-to-1 call
This page provides a code example for implementing screen share in a 1-to-1 call.
This document demonstrates how to implement screen share functionality in a service using the MediaProjection API. The implementation supports maintaining screen share even when the app is in the background.
Prerequisites
Before you begin, you must do the following.
Add required permissions
In your AndroidManifest.xml
, include the following permissions:
<!-- For Foreground Service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- For Screen Capture on Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
Register the foreground service
Declare the service by specifying mediaProjection
foreground service type so that the app can maintain screen share even when it is in the background.
<service
android:name=".MyScreenCaptureService"
android:foregroundServiceType="mediaProjection" />
For more information on foreground services, refer to Android documentation (Foreground services, Foreground service types are required).
Sender - Implement the screen capture service
This service handles the start and stop operations for screen capture using MediaProjection, integrating with the 1-to-1 call.
class MyScreenCaptureService : Service() {
companion object {
private const val SCREEN_CAPTURE_ID = "Screen capture"
// This can be any dummy number
private const val NOTIFICATION_ID = 315
private const val ACTION_START_SCREEN_CAPTURE = "ACTION_START_SCREEN_CAPTURE"
private const val ACTION_STOP_SCREEN_CAPTURE = "ACTION_STOP_SCREEN_CAPTURE"
private var callInstanceId:Int = 0
private lateinit var mediaProjectionResult:ActivityResult
@Synchronized
@JvmStatic
fun startScreenCapturing(context: Context, instanceId:Int, projectionPermissionResult: ActivityResult) {
val intent = Intent(context, MyScreenCaptureService::class.java).apply {
action = ACTION_START_SCREEN_CAPTURE
this@Companion.callInstanceId = instanceId
mediaProjectionResult = projectionPermissionResult
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
@Synchronized
@JvmStatic
fun stopScreenCapturing(context: Context) {
val intent = Intent(context, MyScreenCaptureService::class.java).apply {
action = ACTION_STOP_SCREEN_CAPTURE
}
context.startService(intent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action = intent?.action ?: return START_NOT_STICKY
when (action) {
ACTION_START_SCREEN_CAPTURE -> {
onScreenShareStarted()
}
ACTION_STOP_SCREEN_CAPTURE -> {
onScreenShareStopped()
}
}
return START_NOT_STICKY
}
private fun onScreenShareStarted() {
val notification = createNotification()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
} else {
startForeground(NOTIFICATION_ID, notification)
}
mediaProjectionResult.data?.let { data ->
val screenSource = ScreenCapturerVideoSource.getInstance(mediaProjectionResult.resultCode, data)
screenSource.setOnErrorListener { onScreenShareStopped() }
PlanetKit.getCall(callInstanceId)?.let {
it.startMyScreenShare(screenSource) { response ->
if (!response.isSuccessful) {
onScreenShareStopped()
}
}
}
}
}
private fun onScreenShareStopped() {
PlanetKit.getCall(callInstanceId)?.stopMyScreenShare()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
}
stopSelf()
}
private fun createNotification(): Notification {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
SCREEN_CAPTURE_ID,
SCREEN_CAPTURE_ID,
NotificationManager.IMPORTANCE_NONE
)
// Register the channel with the system
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
return NotificationCompat.Builder(
this,
SCREEN_CAPTURE_ID
)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle("Screen share in progress")
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
}
}
Sender - Manage screen capture in an activity
This activity demonstrates how to request screen capture permissions and start or stop the service.
class MyCallActivity : AppCompatActivity() {
private var callInstanceId: Int = 0
private val requestMediaProjectionActivityResultLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { projectionPermissionResult: ActivityResult ->
val data = projectionPermissionResult.data
if (projectionPermissionResult.resultCode != RESULT_OK || data == null) {
return@registerForActivityResult
}
MyScreenCaptureService.startScreenCapturing(this, callInstanceId, projectionPermissionResult)
}
private fun requestMyScreenCapture(call: PlanetKitCall) {
callInstanceId = call.instanceId
val mediaProjectionManager =
getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
// This initiates a prompt dialog for the user to confirm screen projection.
requestMediaProjectionActivityResultLauncher.launch(mediaProjectionManager.createScreenCaptureIntent())
}
fun stopMyScreenCapture(context: Context) {
MyScreenCaptureService.stopScreenCapturing(context)
}
}
Receiver - Receive screen share update events
First of all, check whether the peer's screen share has started or stopped through the onPeerScreenShareStarted
or onPeerScreenShareStopped
event of CallListener
.
private val makeAcceptCallListener = object : MakeCallListener, AcceptCallListener {
override fun onPeerScreenShareStarted(call: PlanetKitCall) {
// Add your own implementation.
...
}
override fun onPeerScreenShareStopped(call: PlanetKitCall, hasReason: Boolean, reason: Int) {
// Add your own implementation.
...
}
...
}
Receiver - Render a peer's screen share video
You can display the shared screen by adding a PlanetKitVideoView
after the onPeerScreenShareStarted
event occurs.
var videoView: PlanetKitVideoView
call.addPeerScreenShareView(videoView)
Receiver - Remove a peer's screen share video or hide the view
If you do not wish to render the peer's shared screen, you can either remove the PlanetKitVideoView
from PlanetKitCall
or hide the view to make it invisible.
-
Remove the
PlanetKitVideoView
var videoView: PlanetKitVideoView
call.removePeerScreenShareView(videoView) -
Hide the
PlanetKitVideoView
var videoView: PlanetKitVideoView
videoView.isVisible = false