Skip to main content
Version: 5.x.x

Invite Guest on Stage - Flutter

In this guide, we will see how you can request a viewer to join your livestream by using the changeMode().

note

Before going forward in this guide, do make sure all the attendees join the room with mode as SIGNALLING_ONLY and the host joins with mode as SEND_AND_RECV

info

Important Changes Flutter SDK in Version v1.3.0

  • The following modes have been deprecated:
    • CONFERENCE has been replaced by SEND_AND_RECV
    • VIEWER has been replaced by SIGNALLING_ONLY

Please update your implementation to use the new modes.

⚠️ Compatibility Notice:
To ensure a seamless meeting experience, all participants must use the same SDK version.
Do not mix version v1.3.0 + with older versions, as it may cause significant conflicts.

Let's first have a look at how we will be using the PubSub mechanism to acheive the requesting and switching of the participant's mode.

invite-guest-pubsub

Step 1: Loading Viewer List​

The host will see a list of participants who have joined as SIGNALLING_ONLY along with a button that, when selected, will invite that user to join the livestream.

room.participants holds the remote participants only, so the host is never in the viewer list. participant.mode is already updated when participantModeChanged fires, so the example rebuilds the list from that event.

import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class SpeakerScreen extends StatefulWidget {
...
}

class _SpeakerScreenState extends State<SpeakerScreen> {
late Room room;

Map<String, Participant> _attendees = {};

@override
void initState() {
...

updateAttendees();

setupRoomEventListener();
}

@override
Widget build(BuildContext context) {
return Column(
children:[
//button which will show viewers list
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ListView.builder(
itemCount: _attendees.values.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Text(_attendees.values
.elementAt(index)
.displayName)),
ElevatedButton(
onPressed: () {

},
child: Text("Invite on Stage")),
],
),
);
}),
));
},
child: const Text("Invite Viewer on Stage"),
),
...
]
);
}

void updateAttendees(){
Map<String, Participant> attendees = {};
room.participants.values.forEach((participant) {
if (participant.mode == Mode.SIGNALLING_ONLY) {
attendees.putIfAbsent(participant.id, () => participant);
}
});
setState(()=>_attendees = attendees);
}

void setupRoomEventListener() {
room.on(Events.roomJoined, () {
updateAttendees();
});

room.on(Events.participantModeChanged, (Map<String, dynamic> data){
updateAttendees();
});

room.on(Events.participantJoined, (Participant participant) {
updateAttendees();
});


room.on(Events.participantLeft, (Participant participant, LeaveReason reason) {
updateAttendees();
});
}
}

Step 2: Requesting a Viewer to Join Livestream​

We have a Viewer list ready. Now, let us handle the click event for the Join Livestream button.

We will be using CHANGE_MODE_$participantId as the topic for PubSub.

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class SpeakerScreen extends StatefulWidget {
...
}

class _SpeakerScreenState extends State<SpeakerScreen> {
late Room room;

Map<String, Participant> _attendees = {};

@override
void initState() {
...
}

@override
Widget build(BuildContext context) {
return Column(
children:[
ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ListView.builder(
itemCount: _attendees.values.length,
itemBuilder: (builderContext, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: Text(_attendees.values
.elementAt(index)
.displayName)),
ElevatedButton(
onPressed: () async {
//Publish the message on CHANGE_MODE_$participantId to join
try {
await room.pubSub.publish(
"CHANGE_MODE_${_attendees.values.elementAt(index).id}",
"SEND_AND_RECV");
} on VideoSDKError catch (error) {
log("Could not send the request: ${error.code} ${error.name}");
}
},
child: Text("Invite on Stage")),
],
),
);
}),
));
},
child: const Text("Invite Viewer on Stage"),
),
...
]
);
}

void updateAttendees(){
...
}

void setupRoomEventListener() {
...
}
}

Step 3: Showing Viewer Request Dialog​

After implementing the Host requesting viewer to join the livestream, let's display the viewer's request dialogue and switch the SIGNALLING_ONLY mode to SEND_AND_RECV.

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:videosdk/videosdk.dart';
import './livestream_player.dart';

class ILSViewerView extends StatefulWidget {
final Room room;
...
}

class _ILSViewerViewState extends State<ILSViewerView> {
// Keep the listener: unsubscribe() takes the same object
late final PubSubMessageListener changeModeListener =
PubSubMessageListener(onMessageReceived: changeModeRequestHandler);

@override
void initState() {
super.initState();

//subscribe to the change mode request topic
subscribeToChangeModeRequests();
}

Future<void> subscribeToChangeModeRequests() async {
try {
await widget.room.pubSub.subscribe(
"CHANGE_MODE_${widget.room.localParticipant.id}",
changeModeListener);
} on VideoSDKError catch (error) {
log("Could not subscribe: ${error.code} ${error.name}");
}
}

//show dialog to accept or reject when change mode requested by speaker
void changeModeRequestHandler(PubSubMessage message) {
if (mounted) {
showDialog(
context: this.context,
builder: (context) {
return AlertDialog(
actions: [
ElevatedButton(
onPressed: () async {
Navigator.of(context).pop();
try {
await widget.room.changeMode(Mode.SEND_AND_RECV);
} on VideoSDKError catch (error) {
log("Could not change mode: ${error.code} ${error.name}");
}
},
child: const Text("Accept"),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text("Reject"),
)
],
content:
Text("${message.senderName} requested you to join livestream"),
);
});
}
}

@override
Widget build(BuildContext context) {
return YourViewerWidget;
}

@override
void dispose() {
// The listener stops at once, even when this call fails
// because the room has already left.
widget.room.pubSub
.unsubscribe("CHANGE_MODE_${widget.room.localParticipant.id}",
changeModeListener)
.catchError((Object error) {});
super.dispose();
}
}

Step 4: Pin the participant​

We need to pin the participant so that he/she can appears on the livestream. To achieve this, we will listen to the callback on the participantModeChanged of Room object. Here, we are already calling the updateAttendees() which we will update to pin only the SEND_AND_RECV mode participants.

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class SpeakerScreen extends StatefulWidget {
...
}

class _SpeakerScreenState extends State<SpeakerScreen> {
late Room room;
bool _joined = false; // set to true in the roomJoined handler

Map<String, Participant> _attendees = {};

@override
void initState() {
...
}

@override
Widget build(BuildContext context) {
return Column(
children:[
...
]
);
}

void updateAttendees(){
Map<String, Participant> attendees = {};
room.participants.values.forEach((participant) {
if (participant.mode == Mode.SIGNALLING_ONLY) {
attendees.putIfAbsent(participant.id, () => participant);
setPinned(participant, false);
}
});
// room.participants holds remote participants only, so pin yourself here
// once the room is joined and your mode is SEND_AND_RECV.
if (_joined && room.localParticipant.mode == Mode.SEND_AND_RECV) {
setPinned(room.localParticipant, true);
}
setState(()=>_attendees = attendees);
}

Future<void> setPinned(Participant participant, bool pinned) async {
try {
if (pinned) {
await participant.pin();
} else {
await participant.unpin();
}
} on VideoSDKError catch (error) {
log("Could not update the pin: ${error.code} ${error.name}");
}
}

}

API Reference​

The API references for all the methods utilized in this guide are provided below.

Got a Question? Ask us on discord