Skip to main content
Version: 2.x.x

Change Mode - Flutter

In a live stream, audience members usually join in RECV_ONLY mode, meaning they can only view and listen to the hosts. However, if a host invites an audience member to actively participate (e.g., to speak or present), the audience member can switch their mode to SEND_AND_RECV using the changeMode() method.

This guide explains how to use the changeMode() method and walks through a sample implementation where a host invites an audience member to become a host using PubSub.

changeMode()

  • The changeMode() method from the Room class allows a participant to switch between modes during a live stream—for example, from audience to host.
import 'package:flutter/material.dart';
import 'package:videosdk/videosdk.dart';

class LivestreamScreen extends StatefulWidget {
const LivestreamScreen({Key? key}) : super(key: key);

@override
_LiveStreamScreenState createState() => _LiveStreamScreenState();
}

class _LiveStreamScreenState extends State<LiveStreamScreen> {
late Room _room;

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

@override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
child: const Text('Change Mode'),
onPressed: () async {
if (_room.localParticipant.mode == Mode.SEND_AND_RECV) {
_room.changeMode(Mode.RECV_ONLY);// change mode to RECV_ONLY
} else if (_room.localParticipant.mode == Mode.RECV_ONLY) {
_room.changeMode(Mode.SEND_AND_RECV); // change Mode to SEND_AND_RECV
}
Navigator.pop(context);
},
),
]);
}
}

Implementation Guide

Step 1 : Create a Pubsub Topic

  • Set up a PubSub topic to send a mode change request from the host to a specific audience member.
void sendInvite(Room livestream, String participantId) {
livestream.pubSub.publish(
"REQUEST_TO_JOIN_AS_HOST_$participantId", // PubSub topic specific to participant
"SEND_AND_RECV", // message
);
}

Step 2 : Create an Invite Button

  • Add an "Invite" button for each audience member. When clicked, it publishes a PubSub message with the mode "SEND_AND_RECV" to that participant.
Widget buildInviteButton(Room livestream, Participant participant) {
return Expanded(
child: ListView.builder(
itemCount: _participants.values.length,
itemBuilder: (context, index) => ParticipantListItem(
participant: _participants.values.elementAt(index),
isRaisedHand: raisedHandParticipant
.contains(_participants.values.elementAt(index).id),
onMoreOptionsSelected: (value) {
if (value == "Remove from Co-host" ||
value == "Add as a Co-host") {
sendInvite(widget.livestream.id,_participants.values.elementAt(index).id)

}
},
),
),
),
}

class ParticipantListItem extends StatefulWidget {
...
}

class _ParticipantListItemState extends State<ParticipantListItem> {
...
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
// padding: const EdgeInsets.all(12),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (!widget.participant.isLocal)
PopupMenuButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
color: black700,
icon: const Icon(
Icons.more_vert,
color: Colors.white,
),
onSelected: (value) {
widget.onMoreOptionsSelected(value);
},
itemBuilder: (context) {
return {
(widget.participant.mode == Mode.RECV_ONLY
? "Add as a Co-host"
: "Remove from Co-host"),
'Remove Participant'
}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
}),
],
),
),
],
);
}

void addParticipantListener(Participant participant) {
participant.on(Events.streamEnabled, (Stream _stream) {
if (mounted) {
setState(() {
if (_stream.kind == "video") {
videoStream = _stream;
} else if (_stream.kind == 'audio') {
audioStream = _stream;
}
});
}
});

participant.on(Events.streamDisabled, (Stream _stream) {
if (mounted) {
setState(() {
if (_stream.kind == "video") {
videoStream = null;
} else if (_stream.kind == 'audio') {
audioStream = null;
}
});
}
});
}
}

Step 3 : Create a Listener to Change the Mode

  • On the audience side, subscribe to the specific PubSub topic. When a mode request is received, update the participant’s mode using changeMode().
void registerModeListener(Room livestream, BuildContext context) async {
await livestream.pubSub.subscribe(
"CHANGE_MODE_${livestream.localParticipant.id}",
(PubSubMessage pubSubMessage) {
final message = pubSubMessage.message;

if (message == "SEND_AND_RECV") {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(
"${pubSubMessage.senderName} requested you to join as a speaker",
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Decline"),
),
TextButton(
onPressed: () {
livestream.changeMode(Mode.SEND_AND_RECV);
Navigator.pop(context);
},
child: const Text("Accept"),
),
],
),
);
}
},
);
}
note

Calling changeMode() changes only the caller's mode. Every other client must rebuild its host and audience lists from the participant-mode-changed event, as described in Updating the UI on mode change.

Updating the UI on mode change

changeMode() updates the mode of the participant who calls it. The room then broadcasts a participant-mode-changed event to every participant, including clients that did not initiate the change. Each client should use that event to update its UI.

In the event handler, rebuild the lists that drive your UI by filtering participants by their current mode, then refresh the view. Simply pinning, unpinning, or displaying a message is not enough: when a participant is promoted, their mode changes, but other clients will not automatically move them from the audience to the stage.

room.on(Events.participantModeChanged, (Map<String, dynamic> data) {
setState(() {
// Re-derive both lists from room.participants on every mode change.
speakers = participantsWithMode("SEND_AND_RECV");
viewers = participantsWithMode("SIGNALLING_ONLY");
});
});
caution

Rebuild both lists — the speakers and the viewers. Removing the participant from the audience list alone is the common half-fix: they disappear from the audience and never appear on stage.

API Reference

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

Got a Question? Ask us on discord