Skip to content

Commit 9725a36

Browse files
authored
Add screen sharing to SDK (#808)
1 parent 80da737 commit 9725a36

File tree

21 files changed

+734
-82
lines changed

21 files changed

+734
-82
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro
115115

116116
### 0.4.0 milestone
117117

118+
- [X] Screensharing from mobile
118119
- [ ] Complete Livestreaming APIs and Tutorials for hosting & watching
119120
- [ ] Android SDK development.md cleanup (Daniel)
120121
- [ ] Upgrade to more recent versions of webrtc (Kanat)
@@ -131,7 +132,6 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro
131132

132133
- [ ] Testing on more devices
133134
- [ ] Enable SFU switching
134-
- [ ] Screensharing from mobile
135135
- [ ] Camera controls
136136
- [ ] Tap to focus
137137
- [ ] H264 workaround on Samsung 23 (see https://github.com/livekit/client-sdk-android/blob/main/livekit-android-sdk/src/main/java/io/livekit/android/webrtc/SimulcastVideoEncoderFactoryWrapper.kt#L34 and
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
title: Screen sharing
3+
description: Setup for screen sharing
4+
---
5+
6+
## Introduction
7+
8+
The Stream Video Android SDK has support for screen sharing from an Android device. The SDK is using the [Android Media Projection API](https://developer.android.com/guide/topics/large-screens/media-projection) for the capture.
9+
10+
In order for a user to be able to share their screen, they must have the `screenshare` capability configured for the call they are in.
11+
12+
## How to start sharing your screen
13+
14+
You need to be in an active call (have a `Call` instance in Active call state) to start screen sharing.
15+
16+
You must ask the user for screen sharing permission before you can start sharing the screen. The permission is requested by using the [Media Projection API](https://developer.android.com/guide/topics/large-screens/media-projection). And then use the returned intent data from the permission result and call `Call.startScreenSharing(intentData)`.
17+
18+
An example implementation:
19+
20+
```kotlin
21+
val startMediaProjection = registerForActivityResult(StartActivityForResult()) { result ->
22+
if (it.resultCode == Activity.RESULT_OK && it.data != null) {
23+
call.startScreenSharing(it.data!!)
24+
}
25+
}
26+
27+
val mediaProjectionManager = context.getSystemService(MediaProjectionManager::class.java)
28+
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())
29+
```
30+
31+
You can check if screen sharing is currently active by observing `call.screenShare.isEnabled`.
32+
33+
## Stopping screen sharing
34+
35+
Screen sharing can be stopped wit `Call.stopScreenSharing()`. It is automatically stopped if the call state goes into Inactive state.
36+
37+
The user can also disable screen sharing directly in the system settings (depending on the OEM there is usually a button in the notification bar for disabling screen sharing).
38+
39+
And the screen sharing can also be disabled through the screen sharing notification action button (described in next section).
40+
41+
## Screen sharing notification
42+
43+
A notification is always displayed to the user when the screen sharing is active. The notification itself can't be hidden and is required by the Android OS. The notification title and description can be customised.
44+
45+
Override string `stream_video_screen_sharing_notification_title` and `stream_video_screen_sharing_notification_description` to customise the notification text.
46+
47+
There is also a "Stop screen sharing" action button on the notification, the text of the button can be modified by overriding `stream_video_screen_sharing_notification_action_stop`.
48+
49+
All notifications in Android need to have a notification channel. The Stream Video Android SDK will automatically create a new channel for the screen sharing notification. You can customise the channel title and description (this is visible to the user in the system application settings). Override `stream_video_screen_sharing_notification_channel_title` and `stream_video_screen_sharing_notification_channel_description`.
50+
51+
```xml
52+
<string name="stream_video_screen_sharing_notification_title">You are screen sharing</string>
53+
<string name="stream_video_screen_sharing_notification_description"></string>
54+
<string name="stream_video_screen_sharing_notification_action_stop">Stop screen sharing</string>
55+
<string name="stream_video_screen_sharing_notification_channel_title">Screen-sharing</string>
56+
<string name="stream_video_screen_sharing_notification_channel_description">Required to be enabled for screen sharing</string>
57+
```

docusaurus/docs/Android/06-advanced/04-chat-with-video.mdx renamed to docusaurus/docs/Android/06-advanced/05-chat-with-video.mdx

File renamed without changes.
File renamed without changes.
File renamed without changes.

dogfooding/src/main/kotlin/io/getstream/video/android/ui/call/SettingsMenu.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package io.getstream.video.android.ui.call
1818

19+
import android.app.Activity
20+
import android.media.projection.MediaProjectionManager
1921
import android.widget.Toast
22+
import androidx.activity.compose.rememberLauncherForActivityResult
23+
import androidx.activity.result.contract.ActivityResultContracts
2024
import androidx.compose.foundation.background
2125
import androidx.compose.foundation.clickable
2226
import androidx.compose.foundation.layout.Column
@@ -29,6 +33,8 @@ import androidx.compose.material.Card
2933
import androidx.compose.material.Icon
3034
import androidx.compose.material.Text
3135
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.collectAsState
37+
import androidx.compose.runtime.getValue
3238
import androidx.compose.runtime.rememberCoroutineScope
3339
import androidx.compose.ui.Alignment
3440
import androidx.compose.ui.Modifier
@@ -54,6 +60,21 @@ internal fun SettingsMenu(
5460
val reactions =
5561
listOf(":fireworks:", ":hello:", ":raise-hand:", ":like:", ":hate:", ":smile:", ":heart:")
5662

63+
val screenSharePermissionResult = rememberLauncherForActivityResult(
64+
contract = ActivityResultContracts.StartActivityForResult(),
65+
onResult = {
66+
if (it.resultCode == Activity.RESULT_OK && it.data != null) {
67+
call.startScreenSharing(it.data!!)
68+
}
69+
onDismissed.invoke()
70+
},
71+
)
72+
73+
val isScreenSharing by call.screenShare.isEnabled.collectAsState()
74+
val screenShareButtonText = if (isScreenSharing) {
75+
"Stop screen-sharing"
76+
} else { "Start screen-sharing" }
77+
5778
Popup(
5879
alignment = Alignment.BottomStart,
5980
offset = IntOffset(30, -200),
@@ -92,6 +113,37 @@ internal fun SettingsMenu(
92113

93114
Spacer(modifier = Modifier.height(12.dp))
94115

116+
Row(
117+
modifier = Modifier.clickable {
118+
if (!isScreenSharing) {
119+
scope.launch {
120+
val mediaProjectionManager = context.getSystemService(
121+
MediaProjectionManager::class.java,
122+
)
123+
screenSharePermissionResult.launch(
124+
mediaProjectionManager.createScreenCaptureIntent(),
125+
)
126+
}
127+
} else {
128+
call.stopScreenSharing()
129+
}
130+
},
131+
) {
132+
Icon(
133+
painter = painterResource(id = R.drawable.stream_video_ic_screensharing),
134+
tint = VideoTheme.colors.textHighEmphasis,
135+
contentDescription = null,
136+
)
137+
138+
Text(
139+
modifier = Modifier.padding(start = 20.dp),
140+
text = screenShareButtonText,
141+
color = VideoTheme.colors.textHighEmphasis,
142+
)
143+
}
144+
145+
Spacer(modifier = Modifier.height(12.dp))
146+
95147
if (showDebugOptions) {
96148
Row(
97149
modifier = Modifier.clickable {

stream-video-android-compose/src/main/kotlin/io/getstream/video/android/compose/pip/PictureInPicture.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal fun enterPictureInPicture(context: Context, call: Call) {
3434
val screenSharing = call.state.screenSharingSession.value
3535

3636
val aspect =
37-
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && screenSharing == null) {
37+
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && (screenSharing == null || screenSharing.participant.isLocal)) {
3838
Rational(9, 16)
3939
} else {
4040
Rational(16, 9)

stream-video-android-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/activecall/CallContent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ internal fun DefaultPictureInPictureContent(call: Call) {
212212
val video = session?.participant?.video?.collectAsStateWithLifecycle()
213213
val pictureInPictureAspectRatio: Float = 16f / 9f
214214

215-
if (session != null) {
215+
if (session != null && !session.participant.isLocal) {
216216
VideoRenderer(
217217
modifier = Modifier.aspectRatio(pictureInPictureAspectRatio, false),
218218
call = call,

stream-video-android-compose/src/main/kotlin/io/getstream/video/android/compose/ui/components/call/renderer/ParticipantsGrid.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public fun ParticipantsGrid(
6969
val screenSharingSession = call.state.screenSharingSession.collectAsStateWithLifecycle()
7070
val screenSharing = screenSharingSession.value
7171

72-
if (screenSharing == null) {
72+
// We do not display our own screen-sharing session
73+
if (screenSharing == null || screenSharing.participant.isLocal) {
7374
ParticipantsRegularGrid(
7475
call = call,
7576
modifier = modifier,

stream-video-android-core/api/stream-video-android-core.api

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public final class io/getstream/video/android/core/Call {
1515
public final fun getLocalMicrophoneAudioLevel ()Lkotlinx/coroutines/flow/StateFlow;
1616
public final fun getMicrophone ()Lio/getstream/video/android/core/MicrophoneManager;
1717
public final fun getMonitor ()Lio/getstream/video/android/core/CallHealthMonitor;
18+
public final fun getScreenShare ()Lio/getstream/video/android/core/ScreenShareManager;
1819
public final fun getSessionId ()Ljava/lang/String;
1920
public final fun getSpeaker ()Lio/getstream/video/android/core/SpeakerManager;
2021
public final fun getState ()Lio/getstream/video/android/core/CallState;
@@ -51,9 +52,11 @@ public final class io/getstream/video/android/core/Call {
5152
public final fun setVisibility (Ljava/lang/String;Lstream/video/sfu/models/TrackType;Z)V
5253
public final fun startHLS (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5354
public final fun startRecording (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
55+
public final fun startScreenSharing (Landroid/content/Intent;)V
5456
public final fun stopHLS (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5557
public final fun stopLive (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5658
public final fun stopRecording (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
59+
public final fun stopScreenSharing ()V
5760
public final fun subscribe (Lio/getstream/video/android/core/events/VideoEventListener;)Lio/getstream/video/android/core/EventSubscription;
5861
public final fun subscribeFor ([Ljava/lang/Class;Lio/getstream/video/android/core/events/VideoEventListener;)Lio/getstream/video/android/core/EventSubscription;
5962
public final fun switchSfu (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -392,6 +395,8 @@ public final class io/getstream/video/android/core/MediaManagerImpl {
392395
public final fun getContext ()Landroid/content/Context;
393396
public final fun getEglBaseContext ()Lorg/webrtc/EglBase$Context;
394397
public final fun getScope ()Lkotlinx/coroutines/CoroutineScope;
398+
public final fun getScreenShareTrack ()Lorg/webrtc/VideoTrack;
399+
public final fun getScreenShareVideoSource ()Lorg/webrtc/VideoSource;
395400
public final fun getVideoSource ()Lorg/webrtc/VideoSource;
396401
public final fun getVideoTrack ()Lorg/webrtc/VideoTrack;
397402
}
@@ -663,6 +668,22 @@ public final class io/getstream/video/android/core/RingingState$TimeoutNoAnswer
663668
public fun toString ()Ljava/lang/String;
664669
}
665670

671+
public final class io/getstream/video/android/core/ScreenShareManager {
672+
public static final field Companion Lio/getstream/video/android/core/ScreenShareManager$Companion;
673+
public fun <init> (Lio/getstream/video/android/core/MediaManagerImpl;Lorg/webrtc/EglBase$Context;)V
674+
public final fun disable (Z)V
675+
public static synthetic fun disable$default (Lio/getstream/video/android/core/ScreenShareManager;ZILjava/lang/Object;)V
676+
public final fun enable (Landroid/content/Intent;Z)V
677+
public static synthetic fun enable$default (Lio/getstream/video/android/core/ScreenShareManager;Landroid/content/Intent;ZILjava/lang/Object;)V
678+
public final fun getEglBaseContext ()Lorg/webrtc/EglBase$Context;
679+
public final fun getMediaManager ()Lio/getstream/video/android/core/MediaManagerImpl;
680+
public final fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
681+
public final fun isEnabled ()Lkotlinx/coroutines/flow/StateFlow;
682+
}
683+
684+
public final class io/getstream/video/android/core/ScreenShareManager$Companion {
685+
}
686+
666687
public final class io/getstream/video/android/core/SpeakerManager {
667688
public fun <init> (Lio/getstream/video/android/core/MediaManagerImpl;Lio/getstream/video/android/core/MicrophoneManager;Ljava/lang/Integer;)V
668689
public synthetic fun <init> (Lio/getstream/video/android/core/MediaManagerImpl;Lio/getstream/video/android/core/MicrophoneManager;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -874,6 +895,7 @@ public final class io/getstream/video/android/core/call/RtcSession {
874895
public final fun reconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
875896
public final fun setLocalTrack (Lstream/video/sfu/models/TrackType;Lio/getstream/video/android/core/model/MediaTrack;)V
876897
public final fun setMuteState (ZLstream/video/sfu/models/TrackType;)V
898+
public final fun setScreenShareTrack ()V
877899
public final fun setSubscriber (Lio/getstream/video/android/core/call/connection/StreamPeerConnection;)V
878900
public final fun setTrack (Ljava/lang/String;Lstream/video/sfu/models/TrackType;Lio/getstream/video/android/core/model/MediaTrack;)V
879901
public final fun setTracks (Ljava/util/Map;)V
@@ -902,7 +924,7 @@ public final class io/getstream/video/android/core/call/connection/StreamPeerCon
902924
public fun <init> (Lkotlinx/coroutines/CoroutineScope;Lio/getstream/video/android/core/model/StreamPeerType;Lorg/webrtc/MediaConstraints;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;I)V
903925
public final fun addAudioTransceiver (Lorg/webrtc/MediaStreamTrack;Ljava/util/List;)V
904926
public final fun addIceCandidate (Lio/getstream/video/android/core/model/IceCandidate;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
905-
public final fun addVideoTransceiver (Lorg/webrtc/MediaStreamTrack;Ljava/util/List;)V
927+
public final fun addVideoTransceiver (Lorg/webrtc/MediaStreamTrack;Ljava/util/List;Z)V
906928
public final fun createAnswer (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
907929
public final fun createOffer (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
908930
public final fun getAudioTransceiver ()Lorg/webrtc/RtpTransceiver;

0 commit comments

Comments
 (0)