Search code examples
angulartypescriptwebrtcopenvidu

Switching microphone device on MediaStream causes echo of own voice


Im building an Angular app which allows 2 users to video call each other using Openvidu calling solution.

Within this app I developed a feature of changing the camera or microphone which you are actively using on the call.

Once selecting the new microphone, the previous microphone track is stopped and removed from the stream, before adding the new one. This process is encapsulated in the below code:

async onMicrophoneSelected(event: any) {
        var currentMediaStream: MediaStream = this.localUsersService.getWebcamPublisher().stream.getMediaStream();
        var currentAudioTrack: MediaStreamTrack;
        var currentVideoTrack: MediaStreamTrack;

        var newAudioInfo: MediaDeviceInfo; // type here is MediaDeviceInfo as it will be specified from enumerateDevices()
        var newAudioTrack: MediaStreamTrack;
        var newVideoTrack: MediaStreamTrack;

        // Specifying current video & audio track being used on call
        currentMediaStream.getTracks().forEach((track) => {
            if (track.kind === 'audio') {
                currentAudioTrack = track;
                currentAudioTrack.stop();  // stopping old audio track here 
            }

            if (track.kind === 'video') {
                currentVideoTrack = track;
            }
        });

        // Looping through available devices
        await navigator.mediaDevices.enumerateDevices().then((res) => {
            res.forEach((device) => {
                // Checking for: the current inactive device
                if (device.kind === 'audioinput' && device.deviceId === event.value) {
                    newAudioInfo = device;
                }
            });
        });

        // Passing constraints that contain new deviceId for audio, then using replaceTrack() to replace audio  // this also promps user for new device permissions
        await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: newAudioInfo.deviceId } } }).then((stream) => {
            newAudioTrack = stream.getAudioTracks()[0];
        });

        // replaceTrack() used here to notify OpenVidu of new devices, where they will then be published and thus changes also seen by the other-end-user
        this.localUsersService
            .getWebcamPublisher()
            .replaceTrack(newAudioTrack)
            .then(() => {
                console.log(currentMediaStream.getTracks(), '<<<-- checking stream after changes');
            });
    }

After the above code successfully runs through, the end result should be that the microphone I am actively using on the call should have changed to the one which I selected.

This is the case, however the issue im facing is that the change also comes with a very loud echo of myself, meaning once I switch microphones, the active microphone changes and I can also hear myself through that microphone.

Any ideas on this would really be appreciated, thanks for reading.

Note: echoCancellation did not solve this issue.


Solution

  • So I have this working on Firefox, Safari and Google browser so feel free to use this code.

    The reason I was experiencing the echo issue is because I was running the initwebcamPublisher() in ngOnInit() of the Settings component... This funtion already runs once on initiation of the app, and doing it once more causes some sort of duplicate in the webcam publisher.

    In removing that I am left with the working onMicrophoneSelect():

    async onMicrophoneSelected(event: any) {
            const audioSource = event?.value;
    
            if (!!audioSource) {
                // Is New deviceId different than older?
                if (this.oVDevicesService.needUpdateAudioTrack(audioSource)) {
                    const mirror = this.oVDevicesService.cameraNeedsMirror(this.camSelected.device);
                    this.openViduWebRTCService.unpublishWebcamPublisher();
                    this.openViduWebRTCService.replaceTrack(null, audioSource, mirror);
                    this.openViduWebRTCService.publishWebcamPublisher();
                    this.oVDevicesService.setMicSelected(audioSource);
                    this.micSelected = this.oVDevicesService.getMicSelected();
                }
                // Publish microphone
                this.publishAudio(true);
                this.isAudioActive = true;
    
                return;
            }
    
            // Unpublish microhpone
            this.publishAudio(false);
            this.isAudioActive = false;
        }
    

    So once the user selects the new microphone, their current cam and mic are unpublished, causing them to appear blank for the other user for about 5ms, and then reappear with both same video and audio from the new selected microphone

    The 3 most important parts here are:

    1. Unpublishing old publisher using unpublishWebcamPublisher()
    2. Using the replaceTrack() method to change microphone
    3. Republish the new publisher this.openViduWebRTCService.publishWebcamPublisher();

    Nevertheless a great way to improve this to remove the split-second blank-screen that happens while the camera and mic are unpublished due to this.openViduWebRTCService.publishWebcamPublisher();