Screen Share - Flutter
This feature enables hosts to start or stop sharing their screen with other hosts and audience members during the live stream. Only hosts (in SEND_AND_RECV mode) can broadcast their screen, while audience members (in RECV_ONLY mode) can view it in real time.
enableScreenShare()
-
By using
enableScreenShare()function ofRoomobject, host can share his/her screen to other hosts and audience members. -
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). 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. -
To share the device audio as well, create the track with
withAudio: true. This works on Android 10 and later only. See System Audio with Screen Share. -
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.
disableScreenShare()
- By using
disableScreenShare()function ofRoomobject, host can stop sharing his/her screen to other hosts and audience members.
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 livestream reconnects is reported on Events.error instead of being thrown, so keep an Events.error handler too.
Events associated with enableScreenShare
-
Every Participant will receive a callback on
Events.streamEnabledof theRoomobject with the screen-shareRTCStreamobject, whosekindisshare.stream.participantIdis the id of the host who started the screen share. -
Every Participant, including the host 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 host who shared, will receive
Events.presenterChangedcallback on theRoomobject with thepresenterIdasnull.
streamEnabled and streamDisabled also reach handlers added with participant.on() on the participant who owns the stream.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
class LiveStreamScreen extends StatefulWidget {
...
}
class _LiveStreamScreenState extends State<LiveStreamScreen> {
late Room _room;
@override
void initState() {
super.initState();
...
_initStreamListeners();
}
//Change state according to the events received
void _initStreamListeners() {
_room.on(Events.streamEnabled, (RTCStream stream) {
if (stream.kind == MediaType.SHARE) {
//Screen share Turned On
//stream.participantId is the id of the host who shares
}
});
_room.on(Events.streamDisabled, (RTCStream stream) {
if (stream.kind == MediaType.SHARE) {
//Screen Share Turned Off
}
});
_room.on(Events.presenterChanged, (String? presenterId) {
//Room screen presenter has changed
//Participant ID of current presenter is presenterId
});
}
Future<void> enableScreenShare() async {
try {
final CustomStreamTrack? shareTrack =
await VideoSDK.createScreenShareVideoTrack(
encoderConfig: CustomScreenShareTrackConfig.h720p_15fps,
);
// 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}");
}
}
Future<void> disableScreenShare() async {
try {
await _room.disableScreenShare();
} on VideoSDKError catch (error) {
print("disableScreenShare failed: ${error.code} ${error.name}");
}
}
@override
Widget build(BuildContext context) {
return Column(
children:[
ElevatedButton(
onPressed: enableScreenShare,
child: const Text("Enable Screen Share"),
),
ElevatedButton(
onPressed: disableScreenShare,
child: const Text("Disable Screen Share"),
),
]
);
}
}
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, which you can read with thegetActivePresenterId()method of theRoomobject.
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. stream.isLocal tells you if the stream is the local participant's own share.
import 'dart:async' show unawaited;
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';
import './screen_share_view.dart';
class LiveStreamScreen extends StatefulWidget {
final String liveStreamId;
final String token;
const LiveStreamScreen(
{super.key, required this.liveStreamId, required this.token});
@override
State<LiveStreamScreen> createState() => _LiveStreamScreenState();
}
class _LiveStreamScreenState extends State<LiveStreamScreen> {
late Room _room;
String? _presenterId;
bool _isSharing = false;
@override
void initState() {
super.initState();
// create room
_room = VideoSDK.createRoom(
roomId: widget.liveStreamId,
token: widget.token,
participantName: "John Doe",
micEnabled: true,
camEnabled: true,
defaultCamera: FacingMode.FRONT, // camera used when the livestream starts
);
setLivestreamEventListener();
// 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 livestream: ${error.code} ${error.name}");
}
}
@override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
// listening to livestream events
void setLivestreamEventListener() {
_room.on(Events.roomLeft, () {
Navigator.popUntil(context, ModalRoute.withName('/'));
});
//Listening if a host 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;
if (stream.isLocal) _isSharing = true;
});
}
});
_room.on(Events.streamDisabled, (RTCStream stream) {
if (stream.kind == MediaType.SHARE) {
setState(() {
if (stream.isLocal) _isSharing = false;
if (_presenterId == stream.participantId) _presenterId = null;
});
}
});
}
Future<void> toggleScreenShare() async {
try {
if (_isSharing) {
await _room.disableScreenShare();
} else {
final CustomStreamTrack? shareTrack =
await VideoSDK.createScreenShareVideoTrack();
// 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 livestream
Future<void> leaveRoom() async {
try {
await _room.leave();
} on VideoSDKError catch (error) {
// 3054: the room has already left the livestream.
if (error.code != 3054) {
log("Could not leave the livestream: ${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.liveStreamId),
//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,
);
}
}
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

