Skip to main content

Server Mute Notifier

The Server Mute Notifier plugin is used to notify a guest when they are muted by a host on the webapp2 application. This plugin will present a notification at the top of the video informing the user that they are muted and also switch the mute button to grey with no click function.

https://youtu.be/jvT-Dlw_PW8

How it works

Global Variables

To make this plugin work, we need to create some global variables that are scoped for access by the entire plugin. They will sit inside the IIFE so they are scoped to the plugin.

var uuid = '';
var serverMute = false;

The uuid variable will be used in the action observable to track the uuid of the current user that is running the plugin. The serverMute variable is used to track the current server mute state, this will be set by information coming from the server side.

On Load

When the plugin is first loaded it attaches a stylesheet to the webapp2 application. This is invoked and appended when the load method is called.

// Create a locally scoped link element
let styleSheet = document.createElement('link');
// Set the link to a stylesheet relation
styleSheet.rel = 'stylesheet';
// Set the location of the stylesheet
styleSheet.href =
'custom_configuration/plugins/serverMuteNotifier/assets/css/styles.css';
// Append the locally scoped link element to the DOM
document.head.appendChild(styleSheet);

Once the stylesheet has been attached, the plugin subscribes to the action observable. This is used to monitor what is going on within the webapp2 application so that we can act on the actions as they happen.

// Create a subscription to the action observeable
const actionsSubscription = PEX.actions$.subscribe({
// Whenever an action happens
next(action) {
// Check that the action param exists and that it has a type
if (action && action.type) {
// Get the users UUID for use later
if (action.type == '[Conference] Set Self UUID') {
// Store the uuid in the global variable
uuid = action.payload;
// Check if the action type is mute change
} else if (action.type == '[Participant] Muted Changed') {
// If the uuid of the action is the same as the local users uuid
if (action.payload.uuid == uuid) {
// Set the serverMute global variable to the returned mute state
serverMute = action.payload.muted;
// Call the updateMute method
updateMute();
}
// Check if the action type is a disconnection
} else if (action.type == '[Conference] Disconnect Success') {
// Set the serverMute gloval variable to false, we've disconnected so the server no longer has us muted
serverMute = false;
// Call the updateMute method
updateMute();
}
}
},
// If an error occured
error(err) {
// Log the error to the console
console.log('Error Action: ', err);
},
});

From the code above we have subscribed to a few different states, we grab the uuid of the user so that we can ensure that the states that we store are for the local user specifically. We also synchronize the serverMute state with the backend so that we can show the message to the user locally and also disable the mute button. Finally, we make sure that when the call is disconnected we hide the message and revert the mute to being able to be used.

Update Mute Method

The update mute method is used to obtain the elements from the DOM so that we can style them according to the current mute state.

function updateMute() {
// Get the mic on button container
var micOnButtonContainer = document.getElementById(
'toolbar-audio-on__button-container',
);
// Get the mic off button container
var micOffButtonContainer = document.getElementById(
'toolbar-audio-off__button-container',
);

// If the container exists, get the first "button" element in its child elements
let micOnButton = micOnButtonContainer
? micOnButtonContainer.getElementsByTagName('button')[0]
: undefined;
// If the container exists, get the first "button" element in its child elements
let micOffButton = micOffButtonContainer
? micOffButtonContainer.getElementsByTagName('button')[0]
: undefined;

// Call the updateMuteStyle for both mute buttons (on and off)
updateMuteStyle(micOnButtonContainer, micOnButton);
updateMuteStyle(micOffButtonContainer, micOffButton);

// Call the notifyMute method
notifyMute();
}

Update Mute Style Method

This method is used to style the elements that were called. It looks quite complex but once you decipher the code, this the help of the comments below, it's quite simple.

// Pass the mute container and the button element
function updateMuteStyle(container, elem) {
// Check that the element exists and is not undefined
if (elem) {
// If the server side mute is true
if (serverMute) {
// Hide the element, in this case the mic on/off button
elem.style.display = 'none';

// Create a locally scoped button element
let micDisabled = document.createElement('button');
// Set the ID of the button element, for future use
micDisabled.id = 'mic-disabled';
// Add a class to the mic disabled button
micDisabled.classList.add('mic-disabled');
// Set an aria-label to the mic disabled button for hover tooltips
micDisabled.ariaLabel = 'Mic muted by host';

// Create a locally scoped SVG element to show on the mic mute button
let micDisabledSVG = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg',
);
// Add the classes per the current webapp2 buttons in the toolbar
micDisabledSVG.classList.add('pex-stage__toolbar-button-icon');
micDisabledSVG.classList.add('toolbar-buttons');
// Set the icon colour to dark grey
micDisabledSVG.style.color = '#666';

// Create a locally scoped image link for the SVG element
let micDisabledUse = document.createElementNS(
'http://www.w3.org/2000/svg',
'use',
);
// Set the link address of the SVG image, in this case we are using the inbuilt icons.svg
micDisabledUse.setAttributeNS(
'http://www.w3.org/1999/xlink',
'xlink:href',
'icons.svg#mic-off',
);

// Append the image link to the SVG element
micDisabledSVG.appendChild(micDisabledUse);
// Append the SVG element to the button
micDisabled.appendChild(micDisabledSVG);
// Append the button to the mute button container
container.appendChild(micDisabled);
// If the server side mute is false
} else {
// Show the mic on/off button again
elem.style.display = 'inline-block';

// Find all of the buttons with the mic-disabled class
let micDisabledButtons = document.getElementsByClassName('mic-disabled');
// Remove all of the disabled mic buttons from the DOM
while (micDisabledButtons.length > 0) micDisabledButtons[0].remove();
}
}
}

In this method, we have created a disabled button with a dark grey icon and added it to the currently shown mic on/off container. When the server mute is turned off we delete that button, with a fallback to delete any instances of it (just in case we ever end up with multiple by some coincidence).

The output looks like below.

Disabled mute button

Notify Mute Method

This method is used to display a message to the user when they are muted on the server-side.

Notification that the host has muted the guest

function notifyMute() {
// Check if the toast element already exists
let toast = document.getElementById('toast');

// If the server mute is true
if (serverMute) {
// If the toast does not exist
if (!toast) {
// Create a new toast div
toast = document.createElement('div');
// Set the ID of the div to toast
toast.id = 'toast';
// Set the class to toast, this is styled in the stylesheet we added at load
toast.className = 'toast';
// Set the text of the toast div
toast.innerHTML = `You have been muted by the host`;
// Append the toast to the document
document.body.appendChild(toast);
}
// If the server mute is false
} else {
// Check if the toast div exists
if (toast) {
// Remove the toast div from the DOM
toast.remove();
}
}
}

Putting it all together

Using all of the above code, and the information from the Getting Started section we end up with a complete plugin.js file that shows a user when they are server-side muted.

(function () {
var uuid = '';
var serverMute = false;

function load(participants$, conferenceDetails$) {
console.log('load server-mute-notifier-plugin', window.location.hostname);
let styleSheet = document.createElement('link');
styleSheet.rel = 'stylesheet';
styleSheet.href =
'custom_configuration/plugins/serverMuteNotifier/assets/css/styles.css';
document.head.appendChild(styleSheet);

const actionsSubscription = PEX.actions$.subscribe({
next(action) {
console.log('DEBUG:', action);
if (action && action.type) {
if (action.type == '[Conference] Set Self UUID') {
uuid = action.payload;
console.log('DEBUG: Set UUID:', uuid);
} else if (action.type == '[Participant] Muted Changed') {
if (action.payload.uuid == uuid) {
serverMute = action.payload.muted;
console.log('DEBUG:', 'Update my mute to', serverMute);
updateMute();
}
} else if (action.type == '[Conference] Disconnect Success') {
serverMute = false;
updateMute();
}
}
},
error(err) {
console.log('Error Action: ', err);
},
});
}

function updateMute() {
var micOnButtonContainer = document.getElementById(
'toolbar-audio-on__button-container',
);
var micOffButtonContainer = document.getElementById(
'toolbar-audio-off__button-container',
);

let micOnButton = micOnButtonContainer
? micOnButtonContainer.getElementsByTagName('button')[0]
: undefined;
let micOffButton = micOffButtonContainer
? micOffButtonContainer.getElementsByTagName('button')[0]
: undefined;

updateMuteStyle(micOnButtonContainer, micOnButton);
updateMuteStyle(micOffButtonContainer, micOffButton);

notifyMute();
}

function updateMuteStyle(container, elem) {
if (elem) {
if (serverMute) {
elem.style.display = 'none';

let micDisabled = document.createElement('button');
micDisabled.id = 'mic-disabled';
micDisabled.classList.add('mic-disabled');
micDisabled.ariaLabel = 'Mic muted by host';

let micDisabledSVG = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg',
);
micDisabledSVG.classList.add('pex-stage__toolbar-button-icon');
micDisabledSVG.classList.add('toolbar-buttons');
micDisabledSVG.style.color = '#666';

let micDisabledUse = document.createElementNS(
'http://www.w3.org/2000/svg',
'use',
);
micDisabledUse.setAttributeNS(
'http://www.w3.org/1999/xlink',
'xlink:href',
'icons.svg#mic-off',
);

micDisabledSVG.appendChild(micDisabledUse);
micDisabled.appendChild(micDisabledSVG);
container.appendChild(micDisabled);
} else {
elem.style.display = 'inline-block';

let micDisabledButtons =
document.getElementsByClassName('mic-disabled');
while (micDisabledButtons.length > 0) micDisabledButtons[0].remove();
}
}
}

function notifyMute() {
let toast = document.getElementById('toast');

if (serverMute) {
if (!toast) {
toast = document.createElement('div');
toast.id = 'toast';
toast.className = 'toast';
toast.innerHTML = `You have been muted by the host`;
document.body.appendChild(toast);
}
} else {
if (toast) {
toast.remove();
}
}
}

function unload() {
console.log('unload server-mute-notifier-plugin');
}

PEX.pluginAPI.registerPlugin({
id: 'server-mute-notifier-plugin-1.0',
load: load,
unload: unload,
});
})();

Once the above is saved to a new file named serverMuteNotifier.plugin.js we need to create the package file, create a new file named serverMuteNotifier.plugin.package.json and add the following code.

{
"id": "server-mute-notifier-plugin-1.0",
"name": "serverMuteNotifier",
"description": "Show a notification when a user is server side muted",
"version": 1.0,
"srcURL": "serverMuteNotifier.plugin.js",
"allowUnload": false,
"platforms": ["web", "electron", "ios", "android"],
"participantRoles": ["chair", "guest"],
"menuItems": {
"toolbar": []
}
}

To activate the plugin, you need to create a folder in webapp2/plugins named serverMuteNotifier, move the above files into that folder and edit the settings.json file in the webapp2 folder. Add the following code to the plugins section.

{
"id": "server-mute-notifier-plugin-1.0",
"srcURL": "plugins/serverMuteNotifier/serverMuteNotifier.plugin.package.json",
"enabled": true
}

Once all of this is done, upload the zip package of the theme to the conference node/reverse proxy and the new plugin will be activated.

If you have any doubt, visit the sections Create a branding package and Upload to Infinity.