Skip to main content

Send presentation

In the previous section we saw how to receive a presentation and how to display it in the interface. In this section we will learn how to do it the other way around. We will capture our device screen and share it with the other conference participants.

Something that we must have in mind is that we must create a foreground service that will display a notification. This notification will indicate the user that the screen is being recorded. If we don't do this, the application will throw an exception when it tries to capture the screen.

Send Presentation Button

For implementing this feature we have to follow these steps:

  • Modify the Android manifest with a new permission and service information.

  • Create the service that display the notification.

  • Modify the ViewModel with a new LiveData variable to inform the rest of the application that the screen sharing is active. We also obtain here the presentationVideoTrack that we will display in the layout.

  • Modify the fragment that will observe the LiveData variable. In case it detects that the screen sharing is active, it will launch the service to display the notification and start capturing the screen.

  • Create the screen sharing button in the conference layout.

As ever, you can download the starting code from Step.06-Exercise-Send-presentation.

Add a permission in Android manifest

The first step is to define the permissions for the service that will display the notification. We need to define the attribute android:foregroundService as mediaProjection . This will give our application permission for capturing the screen always that this service is running. We don't have to do this modification, since it was already added to the template project.

Create the screen sharing service

This service will be very simple. It will display a notification when the user starts sharing his screen and will remove the notification when he stops sharing his screen.

The main logic will be in the onCreate() method. It will perform the following actions:

  • Create a notification channel.

  • Create a notification and assign it to the notification channel.

  • Run the service in foreground.

In onDestroy() we must call stopForeground(true) to remove the screen sharing notification.

class ScreenSharingService : Service() {

override fun onCreate() {
super.onCreate()
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Capturing Screen" ,
NotificationManager.IMPORTANCE_DEFAULT
)
notificationChannel.lightColor = R.color.teal
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setContentTitle(getString(R.string.screen_capturing_title))
.setContentText(getString(R.string.screen_capturing_text))
.setSmallIcon(R.drawable.ic_launcher_small)
.build()
startForeground(NOTIFICATION_ID, notification)
}

override fun onDestroy() {
super.onDestroy()
stopForeground(true)
}

override fun onBind(intent: Intent): IBinder {
return Binder()
}

companion object {
const val NOTIFICATION_CHANNEL_ID = "NOTIFICATION_CHANNEL_SCREEN_SHARING"
const val NOTIFICATION_ID = 1
}
}

Modify the conference ViewModel

Now it is time to define the LiveData that indicates whether the share screen is active or not. We call this variable isSharingScreen and it will be observed by the fragment and layout.

// Notify whether is sharing or not
private val _isSharingScreen = MutableLiveData<Boolean>()
val isSharingScreen: LiveData<Boolean>
get() = _isSharingScreen

We need to define also onToggleShareScreen() . This method will be called each time the user push the share screen button.

// Method that the layout will call when pushing the button
fun onToggleShareScreen() {
_isSharingScreen.value = _isSharingScreen.value != true
}

At this point, we will define the startScreenShare(intent: Intent) method. This method is called by the fragment once our app has access to the screen.


// The fragment will call this method for obtaining the video track
fun startScreenShare(intent: Intent) {
val callback = object : MediaProjection.Callback() {
override fun onStop() { }
}
_isPresentationInMain.value = false
val presentationVideoTrack = webRtcMediaConnectionFactory.createMediaProjectionVideoTrack(intent, callback)
presentationVideoTrack.startCapture(QualityProfile.High)
mediaConnection.setPresentationVideoTrack(presentationVideoTrack)
_presentationVideoTrack.value = presentationVideoTrack
}

And now we have to define the method to stop sharing the screen.

// The fragment will call this method to stop the screen sharing
fun stopScreenShare() {
mediaConnection.setPresentationVideoTrack(null)
_presentationVideoTrack.value = null
_isPresentationInMain.value = false
}

Modify the conference layout

The next step is to add the button to the toolbar. This button is similar to others that we have already created. When this button is pushed, it will call to the viewModel.onToggleShareScreen() method. This method will decide if it has to enable or disable the screen sharing.

<com.google.android.material.button.MaterialButton
android:id="@+id/share_screen_button"
android:layout_width="60dp"
android:layout_height="72dp"
android:layout_margin="4dp"
android:backgroundTint="@color/teal"
android:onClick="@{() -> viewModel.onToggleShareScreen()}"
app:cornerRadius="100dp"
app:enabled="@{viewModel.isSharingScreen}"
app:icon="@drawable/present_all"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="30dp" />

Modify the conference fragment

The last step is to modify the ConferenceFragment to register the activity and bind the service when we push the "share screen" button.

private fun setPresentationObservers() {
...
// Register the activity with the callback and obtain the launcher
val screenSharingLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == RESULT_OK) {
viewModel.startScreenShare(it.data!!)
}
}

// Define the connection object that will manage the service state
val screenSharingServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val mediaProjectionManager =
getSystemService(requireContext(), MediaProjectionManager::class.java)!!
screenSharingLauncher.launch(mediaProjectionManager.createScreenCaptureIntent())
}
override fun onServiceDisconnected(name: ComponentName?) {}
}

// Observe the isSharingScreen LiveData and bind/unbind the service base on the value
viewModel.isSharingScreen.observe(viewLifecycleOwner, Observer {
val intent = Intent(context, ScreenSharingService::class.java)
if (it == true) {
context?.bindService(intent, screenSharingServiceConnection, Context.BIND_AUTO_CREATE)
} else {
viewModel.stopScreenShare()
context?.unbindService(screenSharingServiceConnection)
}
})
}

Run the app

After following all these step, we will have our Android app with the possibility to share the screen to any other device. It doesn't matter if the participants are using a SIP endpoint, WebRTC app or mobile app. The screen sharing will work flawless in any case.

You can compare your code with the solution in Step.06-Solution-Send-presentation. You can also check the differences with the previous tutorial in the git diff.

Send Presentation