Skip to main content

Mute audio and video

In the previous lesson we have learnt how to connect to a conference, even if it's protected by a PIN. Now we will add some new features. We will start by allowing the user to protect their own privacy - and for that we will set two new buttons in our interface to allow the user to mute their microphone and camera.

During this lesson we will complete the following tasks:

  • Hide the local video in case we don't have a local stream active.
  • Define the function that will be triggered when we push the mute microphone button.
  • Define the function that will be triggered when we push the mute camera button.
  • Modify the toolbar to include both buttons.

You can download the starter code from Step.03-Exercise-Mute-audio-and-video.

App component

We will start by defining the functions that will be triggered when the user pushes any of the mute buttons in src/App.tsx.

We will define these functions here instead of doing it in the Toolbar, since we will need access to the infinityClient and localStream variables.

We define the function that will be triggered when the user click on the audio mute button:

const handleAudioMute = (mute: boolean) => {
infinityClient.mute({mute});
};

And now we do the same for the video mute:

const handleVideoMute = async (mute: boolean) => {
infinityClient.muteVideo({muteVideo: mute});
if (mute) {
localStream?.getTracks().forEach(track => track.stop());
setLocalStream(null);
} else {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
setLocalStream(stream);
}
};

As you can see, to mute and unmute in Pexip Infinity we have only to call to infinityClient.mute() and infinityClient.muteVideo(). However, in order to mute video, we need to perform two additional tasks. The first task is to stop all the tracks from the localStream when the user mutes the camera. This will release the access to the camera and turn off its led. The second task is to do the opposite. We should request access again once the user clicks on unmute camera. We do this through getUserMedia().

The next step is to modify how we create the conference component and assign the new callbacks to onAudioMute and onVideoMute.

<Conference
localStream={localStream}
remoteStream={remoteStream}
onAudioMute={handleAudioMute}
onVideoMute={handleVideoMute}
onDisconnect={handleDisconnect}
/>

Conference component

First of all, we will make small changes in the src/components/Conference/Conference.tsx file.

We will start by defining the properties onAudioMute and onVideoMute in the interface:

interface ConferenceProps {
localStream: MediaStream | null;
remoteStream: MediaStream | null;
onAudioMute: (mute: boolean) => void;
onVideoMute: (mute: boolean) => void;
onDisconnect: () => void;
}

One important improvement is that we would like to hide the local video element when the user mutes their camera. This is a small change, but important for the user experience.

For implementing this feature, we only have to modify one line of code. We will add a condition for rendering the localStream and this condition is that props.localStream should exist:

{
props.localStream && (
<Video className="local-video" mediaStream={props.localStream} />
);
}

The next change is to pass the props to the Toolbar:

<Toolbar
className="toolbar"
onAudioMute={props.onAudioMute}
onVideoMute={props.onVideoMute}
onDisconnect={props.onDisconnect}
/>

Toolbar component

Now we will implement the main part of this feature by modifying src/components/Conference/Toolbar/Toolbar.tsx file and adding the buttons to support this functionality.

We will start by defining onAudioMute and onVideoMute in the list of props for the Toolbar:

interface ToolbarProps {
className: string;
onAudioMute: (mute: boolean) => void;
onVideoMute: (mute: boolean) => void;
onDisconnect: () => void;
}

Now we define the state variables that will be used to store the state. Thanks to these variables we will update the interface accordingly:

const [audioMuted, setAudioMuted] = useState(false);
const [videoMuted, setVideoMuted] = useState(false);

Now we will add the function that will be triggered each time the user pushes the mute audio button. This function will call the function defined in the Conference component and update the state:

const handleAudioMute = () => {
props.onAudioMute(!audioMuted);
setAudioMuted(!audioMuted);
};

Now we do the same for the mute video button:

const handleVideoMute = async () => {
props.onVideoMute(!videoMuted);
setVideoMuted(!videoMuted);
};

The final step is to create the buttons themselves:

<Button
onClick={handleAudioMute}
selected={audioMuted}
icon={audioMuted ? <MicOffIcon /> : <MicIcon />}
/>
<Button
onClick={handleVideoMute}
selected={videoMuted}
icon={videoMuted ? <VideocamOffIcon /> : <VideocamIcon />}
/>

One detail that you should notice is that the icon changes depending on the mute state of the variables.

Run the app

When you run the app, you will see your two new buttons in the interface. If you click on them, you will see that the audio/video is muted and the styles change. In the case of the mute video button, the local video will also be hidden when it's active.

You can compare your code with the solution in Step.03-Solution-Mute-audio-and-video. You can also check the differences with the previous lesson in the git diff.

Mute