Remote Mute/Unmute
You're running a video call room and decide that someone who's currently talking shouldn't be talking.
You'd prefer they'd stay mute. Or perhaps you want their video turned off as well as their audio. You're looking for a remote mute.
Muting can apply to both audio and video.
Let's understand the steps:
Permissions
Can't let just anyone mute others. First, you need to create a role with the permissions to mute others and also to ask them to Unmute.
The permission to mute others is within PermissionsParams
mute
and you should check for that within the HMSRole
of the peer to see if they have it.
Similarly, the permission to Unmute other peers is within PermissionsParams
unmute
.
Here's how to check whether the local peer has permission to mute or Unmute others. You can do it like this:
Future<bool> isAllowedToMuteOthers() async{ return (await hmsSDK.getLocalPeer()).role.permission!.mute; } Future<bool> isAllowedToUnMuteOthers(){ return (await hmsSDK.getLocalPeer()).role.permission!.unMute; }
hmsSDK.getLocalPeer()
will not return null as long as you're in a preview or in a meeting room. Since you likely won't need to check for permissions if you're not in one So, it would be ok.
Mute/Unmute other peers
HMSSDK
provides dedicated methods to mute/unmute:
- Individual peer
- Specific role
These methods will only work if the peer has permission to mute/unmute another peer's audio/video. The permission can be checked as the steps mentioned above.
Let's look at each of them:
Individual peer
We can use the changeTrackState
method to mute/unmute remote peer's audio/video
class Meeting implements HMSUpdateListener, HMSActionResultListener{ ... void changeTrackState(HMSTrack forRemoteTrack, bool mute, HMSActionResultListener hmsActionResultListener) { /// [forRemoteTrack] : track whose state needs to be changed(mute -> unmute or unmute -> mute) /// Set [mute] to true if the track needs to be muted, false otherwise. ///[hmsActionResultListener]: an instance of a class that implements HMSActionResultListener //Here this is an instance of a class that implements HMSActionResultListener, that is, Meeting if( //peer has permission to change track state localPeer?.role.permissions.mute ){ hmsSDK.changeTrackState( forRemoteTrack: forRemoteTrack, mute: mute, hmsActionResultListener: this); } else{ //Peer doesn't have permission to mute/unmute other peers } } void onSuccess( {HMSActionResultListenerMethod methodType = HMSActionResultListenerMethod.unknown, Map<String, dynamic>? arguments}) { switch (methodType) { ... case HMSActionResultListenerMethod.changeTrackState: //Track state successfully changed break; } } void onException( {HMSActionResultListenerMethod methodType = HMSActionResultListenerMethod.unknown, Map<String, dynamic>? arguments, required HMSException hmsException}) { switch (methodType) { ... case HMSActionResultListenerMethod.changeTrackState: // Check the HMSException object for details about the error break; } } }
If the changeTrackState
method is successful we will get the onSuccess
callback on HMSActionResultListener attached and track update in onTrackUpdate
.
Specific role
We can use the changeTrackStateForRole
method to mute/unmute peers under specific roles.
class Meeting implements HMSUpdateListener, HMSActionResultListener{ ... void changeTrackStateForRole(bool mute, HMSTrackKind? kind, String? source, List<HMSRole>? roles, HMSActionResultListener? hmsActionResultListener) { if(//peer has permission to change track state localPeer?.role.permissions.mute) { /// Set [mute] true if the track needs to be muted, false otherwise /// [kind] is the HMSTrackType that should be affected. If this and the source are specified, it is considered an AND operation. If not specified, all track sources are affected. /// [source] is the HMSTrackSource that should be affected. If this and type are specified, it is considered an AND operation. If not specified, all track types are affected. /// [roles] is a list of roles, that may have a single item in a list, whose tracks should be affected. //If not specified, all roles are affected. ///[hmsActionResultListener]: an instance of a class that implements HMSActionResultListener //Here this is an instance of a class that implements HMSActionResultListener, that is, Meeting HMSSDK.changeTrackStateForRole( mute: mute, kind: kind, source: source, roles: roles, hmsActionResultListener: this); } else{ //Peer doesn't have permission to mute/unmute other peers } } void onSuccess( {HMSActionResultListenerMethod methodType = HMSActionResultListenerMethod.unknown, Map<String, dynamic>? arguments}) { switch (methodType) { ... case HMSActionResultListenerMethod.changeTrackState: break; case HMSActionResultListenerMethod.changeTrackStateForRole: //Track state successfully changed break; } } void onException( {HMSActionResultListenerMethod methodType = HMSActionResultListenerMethod.unknown, Map<String, dynamic>? arguments, required HMSException hmsException}) { switch (methodType) { ... case HMSActionResultListenerMethod.changeTrackState: break; case HMSActionResultListenerMethod.changeTrackStateForRole: // Check the HMSException object for details about the error break; } } }
If the changeTrackStateForRole
method is successful we will get the onSuccess
callback and track update in onTrackUpdate
similar to changeTrackState
.
If roles is passed as an empty list then all the roles will get affected.
Handling a mute callback
Mute callbacks are automatically applied to the receiver. No action is required.
Handling an unmute callback
Let's turn the table now to what happens if a remote peer wishes to mute/unmute our audio/video.
- In case when remote peer mutes our audio/video
HMSSDK
performs it automatically without asking permission In another case, we get theonChangeTrackStateRequest
if we accept the request we need to calltoggleCameraMuteState
ortoggleMicMuteState
according to the request.
Let's understand this with a diagram:
Now let's do it step-by-step:
PeerA calls changeTrackState on PeerB's track
hmsSDK.changeTrackState( forRemoteTrack: "PeerB's track", mute: mute, hmsActionResultListener: this);
PeerB receives onChangeTrackStateRequest
void onChangeTrackStateRequest( {required HMSTrackChangeRequest hmsTrackChangeRequest}){}
How this is implemented in the example app can be found here
Let's look at the HMSTrackChangeRequest
structure :
class HMSTrackChangeRequest { bool mute; HMSPeer requestBy; HMSTrack track; }
This contains information on which track is requested for unmuting. Check the track type and call the unmute methods accordingly.
public void checkTrack(HMSTrack track) { if(track.kind == HMSTrackType.kHMSTrackKindAudio) { //Requested track is an audio track } else if (track.kind == HMSTrackType.kHMSTrackKindVideo) { //Requested track is a video track } }
So final implementation looks like this:
class Meeting implements HMSUpdateListener, HMSActionResultListener{ ... public void checkTrack(HMSTrack track) { if(track.kind == HMSTrackType.kHMSTrackKindAudio) { hmsSDK.toggleMicMuteState(); } else if (track.kind == HMSTrackType.kHMSTrackKindVideo) { hmsSDK.toggleCameraMuteState(); } } void onChangeTrackStateRequest( {required HMSTrackChangeRequest hmsTrackChangeRequest}){ checkTrack(hmsTrackChangeRequest.track) } }
PeerB calls toggleMicMuteState/toggleCameraMuteState depending on the track kind
If the track is an audio track then PeerB calls toggleMicMuteState
hmsSDK.toggleMicMuteState();
If the track is a video track then PeerB calls toggleCameraMuteState
hmsSDK.toggleCameraMuteState();
Now all the peers will receive an onTrackUpdate
with the type as trackUnmuted