Skip to main content
Version: 7.1

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" />
Tip

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 const val EXTRA_REASON = "EXTRA_REASON"
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, reason: Int? = null) {
val intent = Intent(context, MyScreenCaptureService::class.java).apply {
action = ACTION_STOP_SCREEN_CAPTURE
reason?.let { putExtra(EXTRA_REASON, it) }
}
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 -> {
val reason = if (intent.hasExtra(EXTRA_REASON)) {
intent.getIntExtra(EXTRA_REASON, -1)
} else {
null
}
onScreenShareStopped(reason)
}
}
return START_NOT_STICKY
}

override fun onBind(intent: Intent?): IBinder? = null

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(reason: Int? = null) {
PlanetKit.getCall(callInstanceId)?.let {
if (reason != null) {
it.stopMyScreenShare(reason)
} else {
it.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())
}

private fun stopMyScreenCapture(context: Context, reason: Int?) {
MyScreenCaptureService.stopScreenCapturing(context, reason)
}

override fun onCreate(savedInstanceState: Bundle?) {
...
binding.screenShareRequestBtn.setOnClickListener {
requestMyScreenCapture(call)
}

binding.screenShareStopBtn.setOnClickListener {
stopMyScreenCapture(this, null)
}
}
}

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, disabledReason: 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