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.