muteUserAudioLocally alternative in iOS

Description
As I saw in another post it’s currently not possible to use muteUserAudioLocally in Safari on a iOS device. IS there any alternative? We have implemented a custom solution using the video-sdk. There are specific users which should only be heard by the moderator and know one else. Currently we are setting this in the active-speaker event, but it’s useless as long as there is no solution for this in iOS

Which Web Video SDK version?
1.11.10

Video SDK Code Snippets

export async function useActiveSpeaker(payload: ActiveSpeaker[]) {
    const zoomStore = useZoomStore()
    const { room } = useRoomStore()
    if (room.key_role === 'user') {
        for (const user of payload) {
            if (user.displayName?.includes('coach|')) {
                console.log('mute user audio locally', user.userId, user.displayName)
                await zoomStore.client!.getMediaStream().muteUserAudioLocally(user.userId)
            }
        }
    }

    highlightActiveSpeaker(payload.map(user => user.userId).filter(userId => userId !== zoomStore.localUserId))
}

Hey @thomas1

Thanks for your feedback.

It’s the browser’s limitation that the stream. muteUserAudioLocally doesn’t work on iOS Safari.

Thanks
Vic

Hello @vic.yang ,
yes I saw that already - but my questions is if there is any other way to prevent a audio output on an iPhone for specific users.

Any news @vic.yang ?

Hey @thomas1

Sorry for the late reply.

If you just want to prevent iPhone users from hearing any sound, you can simply call stream.stopAudio. However, if you’re aiming to mute a specific user’s audio, that’s currently not possible.

Thanks
Vic

Thanks for the reply. @vic.yang

Would it be possible to stop the user Audio OR muteAll users locally in safari dynamically when the active speaker changes? So something like this:

export async function useActiveSpeaker(payload: ActiveSpeaker[]) {
    const zoomStore = useZoomStore()
    const currentUser = zoomStore.client?.getCurrentUserInfo()
    const isSafari = window.safari !== undefined;

    if (currentUser?.displayName.includes('user|') ?? true) {
        for (const user of payload) {
            if (user.displayName?.includes('coach|')) {
                await zoomStore.client!.getMediaStream().muteUserAudioLocally(user.userId)
                
                // fix for safari?
                if (isSafari) {
                    await zoomStore.client!.getMediaStream().muteAllUserAudioLocally() // or
                    await zoomStore.client!.getMediaStream().stopAudio()
                }
            }
        }

        if (isSafari && !payload.find(user => user.displayName?.includes('coach|'))) {
            await zoomStore.startAudio(false) // or
            await zoomStore.client?.getMediaStream().unmuteAllUserAudioLocally()
        }
    }

    highlightActiveSpeaker(payload.map(user => user.userId).filter(userId => userId !== zoomStore.localUserId))
}

muteAllUserAudioLocally works in Safari right? So what would be the better option here and can you say if this is reliable enough? As far as I remember there are sometimes issues with safari when starting the user audio again right?

Hey @thomas1

Yes. muteAllUserAudioLocally is available on all platforms. Under the hood, we use the muted attribute to control the local audio, which is widely supported across browsers.

However, please note the difference between muteUserAudioLocally and muteAllUserAudioLocally. The latter will mute the audio for all users.

Thanks
Vic

Ok thanks for the clarification. Yes, that’s why the function above will mute all users as soon as the coach speaks and unmute them when any other user starts speaking. My question is whether the active-speaker-change event is reliable enough to manage this, or if you have a better suggestion for addressing this issue. @vic.yang

Hey @thomas1

In Video SDK Web, we provide two types of events for the active user. The active-speaker event will populate the three loudest users based on volume, including the current user, while the video-active-change event will provide the only one active user (based on audio volume) excluding the current user.

Based on your requirement, it’s better to use active-speaker event to handle the situation.

Thanks
Vic

Hey @thomas1

Another approach is to use the command channel to send control commands to precisely manage when to mute local audio, especially when there is a need to whisper to the moderator.

// someone want to send whisper
commandChannel.send("mute local audio start");

client.on("command-channel-message", async (value) => {
  const { text, senderId } = value;
  if (text === "mute local audio start") {
    await zoomStore.client
      ?.getMediaStream()
      .muteUserAudioLocally(Number(senderId));
    if (isIOSSafari) {
      await zoomStore.client?.getMediaStream().muteAllUserAudioLocally(); 
    }
  } else if (text === "unmute local audio end") {
    await zoomStore.client
      ?.getMediaStream()
      .unmuteUserAudioLocally(Number(senderId));
    if (isIOSSafari) {
      await zoomStore.client?.getMediaStream().unmuteAllUserAudioLocally(); // or
    }
  }
});

Thanks
Vic