Skip to main content
Version: 5.x.x

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 of Room object, host can share his/her screen to other hosts and audience members.

  • Create the screen-share track first with VideoSDK.createScreenShareVideoTrack(), and pass it to enableScreenShare() as customTrack. Its encoderConfig picks the resolution and frame rate, for example CustomScreenShareTrackConfig.h720p_15fps (the default). See Custom Screen Share Track for every profile.

  • On Android, createScreenShareVideoTrack() asks the user to allow screen capture, and returns null if 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 at CustomScreenShareTrackConfig.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 streams property of Participant object.

  • On Android, the SDK adds the FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PROJECTION permissions and its own screen-share foreground service to your app's manifest, so you do not declare them.

disableScreenShare()​

  • By using disableScreenShare() function of Room object, 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.streamEnabled of the Room object with the screen-share RTCStream object, whose kind is share. stream.participantId is the id of the host who started the screen share.

  • Every Participant, including the host who shares, will receive Events.presenterChanged callback on the Room object with the participantId as presenterId who started the screen share.

Events associated with disableScreenShare​

  • Every Participant will receive a callback on Events.streamDisabled of the Room object with the screen-share RTCStream object.

  • Every Participant, including the host who shared, will receive Events.presenterChanged callback on the Room object with the presenterId as null.

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​

note

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, call enableScreenShare() again to open it again.
  • When the user stops the broadcast from Control Center, the SDK ends the share for you, and Events.streamDisabled fires.

Rendering Screen Share Stream​

  1. To render the screenshare, you will need the participantId who is presenting the screen, which you can read with the getActivePresenterId() method of the Room object.

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.

LiveStreamScreen.dart
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')),
],
),
),
),
);
}
}

  1. Now that we know if there is an active presenter, let us get the screen share stream from the Participant object and render it.
screen_share_view.dart
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