Screen Share - Flutter
Screen sharing in a room is the process of sharing your device screen with other participants in the room. It allows everyone in the room to see exactly what you are seeing on your screen, which can be helpful for presentations, demonstrations, or collaborations.
Screen Share feature Compatibility
| Feature | Android app | iOS app |
|---|---|---|
| Screen share | ||
| System audio with screen share |
enableScreenShare()
-
By using
enableScreenShare()function ofRoomobject, local participant can share his/her screen to other participants. -
Create the screen-share track first with
VideoSDK.createScreenShareVideoTrack(), and pass it toenableScreenShare()ascustomTrack. ItsencoderConfigpicks the resolution and frame rate, for exampleCustomScreenShareTrackConfig.h720p_15fps(the default),h480p_15fps,h480p_30fpsorh720p_30fps. See Custom Screen Share Track for every profile. -
On Android,
createScreenShareVideoTrack()asks the user to allow screen capture, and returnsnullif the user declines. On iOS, no prompt opens there:enableScreenShare()opens the system broadcast picker. -
You can also call
enableScreenShare()without a track. It then shares atCustomScreenShareTrackConfig.h720p_15fps, and on Android it asks the user to allow screen capture itself. To use another profile, create a track. -
room.isScreenSharingtells you if the local participant is sharing. -
Screen Share stream of the participant can be accessed from the
streamsproperty ofParticipantobject. -
On Android, the SDK adds the
FOREGROUND_SERVICEandFOREGROUND_SERVICE_MEDIA_PROJECTIONpermissions and its own screen-share foreground service to your app's manifest, so you do not declare them. The SDK shows its own notification while you share, and thenotificationoption ofVideoSDK.createRoom()does not change it.
System Audio with Screen Share (Android Only)
-
System Audio Sharing during screen sharing, allowing users to share both the screen and the audio from their device simultaneously.
-
bool withAudio: In thecreateScreenShareVideoTrack()method,withAudiois a flag that controls whether system audio is shared. Set it totrueto include system audio with the screen share, orfalse(the default) to share only the screen without audio. On iOS, the flag is ignored. -
The device audio is mixed into your microphone track. There is no separate share-audio stream, so muting the microphone mutes both, and nothing is sent while the microphone is off. Apps that block audio capture, such as Chrome, are captured as silence.
-
System audio capture needs the
RECORD_AUDIOpermission, which your app also needs for the microphone. Declare it inAndroidManifest.xmland ask for it withVideoSDK.requestPermissions()before you share. -
Devices running
Android 9(API Level 28) or below will not support the System Audio sharing feature. -
Apps that do not allow their audio to be captured are not heard in the share.
The system audio is mixed into your microphone track. It will only be shared if the microphone is enabled. If the microphone is muted, the system audio will also be muted.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
disableScreenShare()
- By using
disableScreenShare()function ofRoomobject, local participant can stop sharing his/her screen to other participants.
enableScreenShare() and disableScreenShare() return a Future that throws a VideoSDKError when the SDK refuses the call, for example 3054 when a share is already on or off, or 3055 while another screen-share change is still running. await them inside try/catch. On Android, a call made while the room reconnects is reported on Events.error instead of being thrown, so keep an Events.error handler too.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class RoomScreen extends StatefulWidget {
...
}
class _RoomScreenState extends State<RoomScreen> {
late Room _room;
@override
void initState() {
super.initState();
...
}
@override
Widget build(BuildContext context) {
return Column(
children:[
ElevatedButton(
onPressed: () async {
try {
final CustomStreamTrack? shareTrack =
await VideoSDK.createScreenShareVideoTrack(
encoderConfig: CustomScreenShareTrackConfig.h720p_15fps,
withAudio: true, // Android only
);
// On Android, null means the user declined the capture prompt.
if (shareTrack != null) {
await _room.enableScreenShare(customTrack: shareTrack);
}
} on VideoSDKError catch (error) {
print("enableScreenShare failed: ${error.code} ${error.name}");
}
},
child: const Text("Enable Screen Share"),
),
ElevatedButton(
onPressed: () async {
try {
await _room.disableScreenShare(); // Disable screen share
} on VideoSDKError catch (error) {
print("disableScreenShare failed: ${error.code} ${error.name}");
}
},
child: const Text("Disable Screen Share"),
),
]
);
}
}
To keep the microphone working while the app is in the background, follow Background Handling. The camera does not run in the background.
iOS Setup
For Flutter iOS Screen Share feature, you need to follow this guide Flutter iOS Screen Share
- On iOS, one
enableScreenShare()call opens the system broadcast picker, and the share starts when the user taps Start Broadcast. If the user closes the picker, callenableScreenShare()again to open it again. - When the user stops the broadcast from Control Center, the SDK ends the share for you, and
Events.streamDisabledfires.
Rendering Screen Share Stream
- To render the screenshare, you will need the
participantIdwho is presenting the screen, whichroom.getActivePresenterId()returns.
We will listen for the Events.presenterChanged on Room object to check who is presenting, and for Events.streamEnabled and Events.streamDisabled on the Room object to know when a screen-share stream starts or stops. room.isScreenSharing tells you if the local participant is sharing.
import 'dart:async' show unawaited;
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './screen_share_view.dart';
class RoomScreen extends StatefulWidget {
final String roomId;
final String token;
const RoomScreen(
{super.key, required this.roomId, required this.token});
@override
State<RoomScreen> createState() => _RoomScreenState();
}
class _RoomScreenState extends State<RoomScreen> {
late Room _room;
String? _presenterId;
@override
void initState() {
super.initState();
// create room
_room = VideoSDK.createRoom(
roomId: widget.roomId,
token: widget.token,
participantName: "John Doe",
micEnabled: true,
camEnabled: true,
defaultCamera: FacingMode.FRONT, // camera used when the room starts
);
setRoomEventListener();
// Join room
unawaited(joinRoom());
}
Future<void> joinRoom() async {
// On Android, the SDK does not ask for camera and microphone permission.
await VideoSDK.requestPermissions();
try {
await _room.join();
} on VideoSDKError catch (error) {
log("Could not join the room: ${error.code} ${error.name}");
}
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
// listening to room events
void setRoomEventListener() {
_room.on(Events.roomLeft, () {
Navigator.popUntil(context, ModalRoute.withName('/'));
});
//Listening if a participant starts or stops presenting
_room.on(Events.presenterChanged, (String? presenterId) {
setState(() => _presenterId = presenterId);
});
//Listening if a screen-share stream starts or stops
_room.on(Events.streamEnabled, (RTCStream stream) {
if (stream.kind == MediaType.SHARE) {
setState(() {
_presenterId = stream.participantId;
});
}
});
_room.on(Events.streamDisabled, (RTCStream stream) {
if (stream.kind == MediaType.SHARE) {
setState(() {
if (_presenterId == stream.participantId) _presenterId = null;
});
}
});
}
Future<void> toggleScreenShare() async {
try {
if (_room.isScreenSharing) {
await _room.disableScreenShare();
} else {
final CustomStreamTrack? shareTrack =
await VideoSDK.createScreenShareVideoTrack(
withAudio: true, // Android only
);
// On Android, null means the user declined the capture prompt.
if (shareTrack != null) {
await _room.enableScreenShare(customTrack: shareTrack);
}
}
} on VideoSDKError catch (error) {
log("Could not toggle screen share: ${error.code} ${error.name}");
}
}
// back button pressed: leave the room
Future<void> leaveRoom() async {
try {
await _room.leave();
} on VideoSDKError catch (error) {
// 3054: the room has already left.
if (error.code != 3054) {
log("Could not leave the room: ${error.code} ${error.name}");
}
}
}
@override
Widget build(BuildContext context) {
return PopScope(
onPopInvokedWithResult: (didPop, result) {
if (didPop) unawaited(leaveRoom());
},
child: Scaffold(
appBar: AppBar(
title: const Text('VideoSDK QuickStart'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(widget.roomId),
//we will render the screenshare view if the presenterId is not null
if (_presenterId != null)
ScreenShareView(
participant: _room.participants[_presenterId],
),
ElevatedButton(
onPressed: toggleScreenShare,
child: const Text('Toggle Screenshare')),
],
),
),
),
);
}
}
- Now that we know if there is an active presenter, let us get the screen share stream from the
Participantobject and render it.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ScreenShareView extends StatelessWidget {
final Participant? participant;
final RTCStream? shareStream;
ScreenShareView({super.key, required this.participant})
//intialize the shareStream
: shareStream = participant?.streams.values
.where((stream) => stream.kind == MediaType.SHARE)
.firstOrNull;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey.shade800,
height: 300,
width: 200,
//show the screen share stream
child: shareStream != null
? RTCVideoView(
shareStream?.renderer as RTCVideoRenderer,
objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
: null,
);
}
}
Events associated with enableScreenShare
-
Every Participant will receive a callback on
Events.streamEnabledof theRoomobject with the screen-shareRTCStreamobject, whosekindisMediaType.SHARE.stream.participantIdis the id of the participant who started the screen share. -
Every Participant, including the one who shares, will receive
Events.presenterChangedcallback on theRoomobject with the participantId aspresenterIdwho started the screen share.
Events associated with disableScreenShare
-
Every Participant will receive a callback on
Events.streamDisabledof theRoomobject with the screen-shareRTCStreamobject. -
Every Participant, including the one who shared, will receive
Events.presenterChangedcallback on theRoomobject with thepresenterIdasnull.
Listen for these events with room.on(). You can also listen with participant.on(), which delivers only that participant's streams.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class ParticipantTile extends StatefulWidget {
final Room room;
final Participant participant;
...
}
class _ParticipantTileState extends State<ParticipantTile> {
@override
void initState() {
super.initState();
...
_initStreamListeners();
}
//Change state according to the events received
void _initStreamListeners() {
widget.room.on(Events.streamEnabled, _onStreamEnabled);
widget.room.on(Events.streamDisabled, _onStreamDisabled);
}
void _onStreamEnabled(RTCStream stream) {
if (stream.participantId == widget.participant.id && stream.kind == MediaType.SHARE) {
//Screen share Turned On
}
}
void _onStreamDisabled(RTCStream stream) {
if (stream.participantId == widget.participant.id && stream.kind == MediaType.SHARE) {
//Screen Share Turned Off
}
}
@override
void dispose() {
widget.room.off(Events.streamEnabled, _onStreamEnabled);
widget.room.off(Events.streamDisabled, _onStreamDisabled);
super.dispose();
}
@override
Widget build(BuildContext context) {
return YourParticipantWidget();
}
}
class RoomScreen extends StatefulWidget {
...
}
class _RoomScreenState extends State<RoomScreen> {
late Room room;
@override
void initState() {
super.initState();
...
setupRoomEventListener();
}
@override
Widget build(BuildContext context) {
return YourRoomWidget();
}
void setupRoomEventListener() {
room.on(Events.presenterChanged, (String? presenterId) {
//Room screen presenter has changed
//Participant ID of current presenter is presenterId
});
}
}
API Reference
The API references for all the methods and events utilised in this guide are provided below.
Got a Question? Ask us on discord

