A question that I get a lot is: “How do I mute all participants of a conference remotely?” Let’s take the scenario where you are hosting a presentation and of course there is always that one person who forgets to mute the microphone while doing something else (that’s usually me…) and creates unwanted noise in the conference. How do you take action to handle this situation?
It is possible to mute a remote participant (this capability for Dolby Voice came with our SDK 3.2) release. However, the effect is only on yourself, meaning that if you mute one particular participant, everybody but you will still be able to hear that person, so this is not what we want to do here.
We recommend leveraging the CommandService (available on all platforms) to send messages to the participants in a conference. These messages can be a simple string as well as a JSON payload (stringified).
Standard message
The broadcasted messages can have multiple receivers in your application. You can send messages to use for chat communication, you can trigger commands to manipulate the user interface for the participants, or you can spread the local status of the participant to others (audio and video state…). We can create a standard format for all the messages that will be sent and received through the CommandService
. We will use a JSON message that contains a property called command
as the name of the action we want to transmit with this message, as well as a content
property that will contain all the information necessary for the action to be performed.
Message examples
As an example, this could be the payload to use to send a chat message to the other participants of the conference:
{
"command": "ChatMessage",
"content": {
"text": "Hello world!"
}
}
What is interesting to us in this blog post is for the host to be able to mute remote participants. So, to mute all of them at once, we can use this payload:
{
"command": "Mute",
"content": {
"participants": []
}
}
In the event that we want to mute only a selected few participants, because the CommandService
will broadcast the message to all the participants, we need to specify the list of participants we want to mute and filter the message if needed:
{
"command": "Mute",
"content": {
"participants": [
"externalId01",
"externalId02",
"externalId03"
]
}
}
The same applies to unmute some participants:
{
"command": "Unmute",
"content": {
"participants": [
"externalId04",
"externalId05"
]
}
}
Send a message
We will take the example of muting some participants in the conference. First, we need to build the JSON payload and then use the CommandService
to send these messages across the conference.
Web SDK
In JavaScript, we will use the CommandService to send the message:
// Build your list of participants you want to mute
const participants = [
"externalId01",
"externalId02",
"externalId03"
]
// Build the JSON payload to send to the participants
const payload = {
"command": "Mute",
"content": {
"participants": participants
}
}
// The content of the message must be a string
const json = JSON.stringify(payload);
// Send the message to the other participants
VoxeetSDK
.command
.send(json)
.then(() => console.log("Message sent successfully."))
.catch((error) => console.error("There was an error sending the message.", error));
Swift for iOS
In Swift for iOS with the CommandService:
// Build your list of participants you want to mute
let participants = [
"externalId01",
"externalId02",
"externalId03"
]
// Build the JSON payload to send to the participants
let payload = [
"command": "Mute",
"content": [
"participants": participants
]
] as [String: Any]
// The content of the message must be a string
if let jsonData = try? JSONSerialization.data(withJSONObject: payload, options: .prettyPrinted),
let jsonString = String(data: jsonData, encoding: .utf8) {
// Send the message to the other participants
VoxeetSDK.shared.command.send(message: jsonString, completion: { error in
if error == nil {
// The message was properly sent to the conference
}
})
}
Java for Android
In Java for Android with the CommandService:
try {
// Build your list of participants you want to mute
JSONArray participants = new JSONArray();
participants.put("externalId01");
participants.put("externalId02");
participants.put("externalId03");
JSONObject jObjectContent = new JSONObject();
jObjectContent.put("participants", participants);
// Build the JSON payload to send to the participants
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", "Mute");
jsonObject.put("content", jObjectContent);
String jsonStr = jsonObject.toString(4);
String conferenceId = VoxeetSDK.conference().getCurrentConference().getConference().getId();
// Send the message to the other participants
VoxeetSDK.command().send(conferenceId, jsonStr)
.then((r) -> { /* Success */ })
.error((error) -> { /* Handle error */ });
} catch (JSONException e) {
// Handle exception
}
The message is now broadcasted to all the participants, let’s now work on processing the message on the receiver side.
Process a message received
In your application, we need to subscribe to the received
event of the CommandService
, then process the incoming messages and trigger actions if needed.
Web SDK
// Subscribe to the received event
VoxeetSDK.command.on("received", (participant, message) => {
console.info("Message/Command received from", participant);
const json = JSON.parse(message);
if (json.command === "Mute") {
if (Voxeet.conference.isMuted || Voxeet.session.participant.type !== "user") {
// The local participant is already muted or is a listener
return;
}
if (!json.content || json.content.participants // If the participants property is missing
|| json.content.participants.length <= 0 // or the list is empty (we would consider we want everybody to be muted)
|| json.content.participants.indexOf(Voxeet.session.participant.id) >= 0) { // the participant needs to be muted
// Mute the local participant
Voxeet.conference.mute(Voxeet.session.participant, true);
}
} else if (json.command === "Unmute") {
if (!Voxeet.conference.isMuted || Voxeet.session.participant.type !== "user") {
// The local participant is already unmuted or is a listener
return;
}
if (!json.content || json.content.participants // If the participants property is missing
|| json.content.participants.length <= 0 // or the list is empty (we would consider we want everybody to be unmuted)
|| json.content.participants.indexOf(Voxeet.session.participant.id) >= 0) { // the participant needs to be unmuted
// Unmute the local participant
Voxeet.conference.mute(Voxeet.session.participant, false);
}
}
});
Swift for iOS
We are going to use the VTCommandDelegate in Swift.
extension ViewController: VTCommandDelegate {
func received(participant: VTParticipant, message: String) {
if let participantId = participant.id {
print("Message/Command received from " + participantId)
}
if VoxeetSDK.shared.session.participant?.type != VTParticipantType.user {
// The local participant must be a user to trigger actions
return
}
guard let externalId = VoxeetSDK.shared.session.participant?.info.externalID else {
// We need an externalId for the local participant
return
}
if let jsonData = message.data(using: .utf8, allowLossyConversion: false),
let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []),
let dico = jsonObject as? NSDictionary,
let actionName = dico.value(forKey: "command") as? String,
let content = dico.value(forKey: "content") as? NSDictionary {
if actionName == "Mute" {
if (VoxeetSDK.shared.conference.isMuted()) {
// The local participant is already muted
return
}
if let participants = content.value(forKey: "participants") as? Array<String> {
if participants.isEmpty // if the list is empty (we would consider we want everybody to be muted)
|| participants.contains(externalId) { // the participant needs to be muted
// Mute the local participant
VoxeetSDK.shared.conference.mute(true)
}
}
} else if actionName == "Unmute" {
if (!VoxeetSDK.shared.conference.isMuted()) {
// The local participant is already unmuted
return
}
if let participants = content.value(forKey: "participants") as? Array<String> {
if participants.isEmpty // if the list is empty (we would consider we want everybody to be muted)
|| participants.contains(externalId) { // the participant needs to be muted
// Unmute the local participant
VoxeetSDK.shared.conference.mute(false)
}
}
}
}
}
}
Now assign the command delegate to the current ViewController:
VoxeetSDK.shared.command.delegate = self
Java for Android
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
private static boolean contains(JSONArray jArray, String value) throws JSONException {
for (int i = 0; i < jArray.length(); i++) {
if (jArray.getString(i).equalsIgnoreCase(value)) {
return true;
}
}
return false;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageReceived event) {
Log.d(TAG, "Message/Command received from " + event.participantId);
try {
JSONObject jsonObject = new JSONObject(event.message);
String command = jsonObject.getString("command");
if ("Mute".equalsIgnoreCase(command)) {
if (VoxeetSDK.conference().isMuted() || VoxeetSDK.session().getParticipant().participantType() != ParticipantType.USER) {
// The local participant is already muted or is a listener
return;
}
JSONObject jContent = jsonObject.getJSONObject("content");
if (jContent == null) {
// Mute the local participant
VoxeetSDK.conference().mute(true);
} else {
JSONArray jParticipants = jContent.getJSONArray("participants");
if (jParticipants == null // If the participants property is missing
|| jParticipants.length() <= 0 // or the list is empty (we would consider we want everybody to be muted)
|| contains(jParticipants, VoxeetSDK.session().getParticipantInfo().getExternalId())) { // the participant needs to be muted
// Mute the local participant
VoxeetSDK.conference().mute(true);
}
}
} else if ("Unmute".equalsIgnoreCase(command)) {
if (!VoxeetSDK.conference().isMuted() || VoxeetSDK.session().getParticipant().participantType() != ParticipantType.USER) {
// The local participant is already unmuted or is a listener
return;
}
JSONObject jContent = jsonObject.getJSONObject("content");
if (jContent == null) {
// Unmute the local participant
VoxeetSDK.conference().mute(false);
} else {
JSONArray jParticipants = jContent.getJSONArray("participants");
if (jParticipants == null // If the participants property is missing
|| jParticipants.length() <= 0 // or the list is empty (we would consider we want everybody to be unmuted)
|| contains(jParticipants, VoxeetSDK.session().getParticipantInfo().getExternalId())) { // the participant needs to be unmuted
// Unmute the local participant
VoxeetSDK.conference().mute(false);
}
}
}
} catch (JSONException e) {
// Handle exception
}
}
Bonus question
To conclude this post I also want to answer the following question, for free! What is the difference between stop/start audio and mute/unmute?
In a Dolby.io conference, when you mute yourself, the audio stream between your device and the media server is kept alive, but the SDK will send an empty audio track. When you unmute yourself, then the SDK will resume sending the audio to conference right away without lag. Now, if you stop the audio, the SDK will close the connection to the media server completely and when you start the audio again, a WebRTC re-negotiation will happen before the SDK is able to send the audio to the media server again. This can take a few hundred milliseconds.