Add Muting and Unmuting to Your Video Chat App in 30 Seconds

January 14, 2021
Written by
Reviewed by
Diane Phan
Twilion

muteunmute.png

This article is for reference only. We're not onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2024.


We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide to assist you in minimizing any service disruption.

Part of developing a high quality video call app is enabling participants to mute and unmute themselves as needed on the call.

This article is going to show you the way to make this happen in your Twilio Programmable Video apps that are built with the JavaScript SDK. You won’t be learning how to build a video chat app from scratch, but you will learn how to implement muting and unmuting quickly in your own app.

The basics of Tracks

Every participant in a video room can publish an audio and/or video track that captures the stream from their local media device (like a webcam or microphone) and sends it to all other participants on the call that are subscribed to those tracks. Don’t worry - these subscriptions happen automatically, unless otherwise specified. Tracks are how participants can see and hear each other on a call.

Access and attach Tracks

When you were building your Programmable Video app in JavaScript, at some point you should have obtained the local participant’s tracks and attached them to the DOM.

For the local participant first connecting a room, that might have looked like this:

room.on('participantConnected', participant => {
  participant.tracks.forEach(publication => {
    if (publication.isSubscribed) {
      const track = publication.track;
      document.getElementById('remote-media-div').appendChild(track.attach());
    }
  });
});

The code above runs when the participantConnected event is emitted on the room. The newly connected participant is available in the callback function. From there, the code loops over any tracks belonging to the participant and attaches them to the DOM using the track.attach() method.

Mute and unmute the video and audio tracks

With the local participant’s tracks attached, you can now give them the power to mute and unmute themselves, and to turn their video on and off.

Mute

Typically, you’d provide each participant with a clickable icon or button, a toggle, or other interactive element that allows them to individually turn off their audio and video tracks.

Once the user interacts with this element, you can respond by disabling the associated track. For audio tracks:

const muteAudio = document.getElementById('muteAudio');

muteAudio.on('click', () => {
  room.localParticipant.audioTracks.forEach(track => {
    track.disable();
  });
});

For video tracks:

const stopVideo = document.getElementById('stopVideo');

stopVideo.on('click', () => {
  room.localParticipant.videoTracks.forEach(track => {
    track.disable();
  });
});

When a user disables their video track, the video stops streaming, but their camera is still engaged. Their device will continue to indicate its active use, usually via a light. If this isn’t the behavior you want, then you could instead stop the track and then unpublish it. Twilio, however, doesn’t recommend this approach.

Respond to muted tracks on the UI

The .disable() method emits an event that the track can listen for. This is handy if you want to use a change on the UI to signify to other participants that someone in the room has turned off their audio or video. Maybe you want to display a still image where a user’s video stream once was, or show a muted symbol on participants whose mics are turned off.

A good time to add this listener is when you attach the remote participant’s tracks.

For example, after a new user connects, you likely want to access and attach the tracks  belonging to all remote participants so that the new user can see everyone present in the room:

room.participants.forEach(participant => {
  participant.tracks.forEach(publication => {
    if (publication.track) {
      document.getElementById('remote-media-div').appendChild(publication.track.attach());
    }
  });

  participant.on('trackSubscribed', track => {
    document.getElementById('remote-media-div').appendChild(track.attach());
  });
});

This code loops through all existing participants in the room, attaches their tracks, and then also listens for new automatic subscriptions that will occur whenever someone else joins the room. This way, whenever someone else joins the room, their tracks will automatically be shared with every remote participant.

Add the listener to the code as seen below:


room.participants.forEach(participant => {
  participant.tracks.forEach(publication => {
    if (publication.track) {
      document.getElementById('remote-media-div').appendChild(publication.track.attach());

      publication.track.on('disabled', () => {
        // do something with the UI here
      });
    }
  });

  participant.on('trackSubscribed', track => {
    document.getElementById('remote-media-div').appendChild(track.attach());

    track.on('disabled', () => {
      // do something with the UI here
    });
  });
});

Unmute

To allow the user to turn their audio tracks back on:

const unmuteAudio = document.getElementById('unmuteAudio');

unmuteAudio.on('click', () => {
  room.localParticipant.audioTracks.forEach(track => {
    track.enable();
  });
});

And this following code to allow users to turn their video tracks back on:

const startVideo = document.getElementById('startVideo');

startVideo.on('click', () => {
  room.localParticipant.videoTracks.forEach(track => {
    track.enable();
  });
});

Like the .disable() method, the enable() method emits an event that can be listened for on all tracks.


room.participants.forEach(participant => {
  participant.tracks.forEach(publication => {
    if (publication.track) {
      document.getElementById('remote-media-div').appendChild(publication.track.attach());

      publication.track.on('enabled', () => {
        // do something with the UI here
      });
    }
  });

  participant.on('trackSubscribed', track => {
    document.getElementById('remote-media-div').appendChild(track.attach());

    track.on('enabled', () => {
      // do something with the UI here
    });
  });
});

Conclusion

In this article you learned about the code that allows users to enable and disable audio and video tracks in your JavaScript video calling app powered by Twilio Programmable Video. Before you go, try using the DataTrack API to add shareable CSS filters to your video app or learn how to generate an access token for your video chat app backend.

For questions, hit me up on Twitter. I’d love to know what you’re building!

Ashley is a JavaScript Editor for the Twilio blog. To work with her and bring your technical stories to Twilio, find her at @ahl389 on Twitter. If you can’t find her there, she’s probably on a patio somewhere having a cup of coffee (or glass of wine, depending on the time).