NotReadableError: Concurrent mic process limit

I am trying to implement a microphone switch feature where, whilst being on a call, i am able to replace my current in-use microphone with a new one.

After selecting the new microphone I want to change to, I receive a NotReadableError: Concurrent mic process limit. error message.

This is because the previous device isnt being removed/deactivated before adding the new device, this is visible from the permission icons here:

The old microphone is still active and thus when permitting the new device i receive the concurrent mic process limit error.

I am using replaceTrack() to swap to the new selected device.

async onMicrophoneSelected(event: any) {
		const selectedDeviceId = event?.value;

		var newAudioTrack;
		var constraints;
		var mediaStream: MediaStream;
		var audioTrack: MediaStreamTrack;

		await navigator.mediaDevices.enumerateDevices().then((res) => {
			res.forEach((device) => {
				if (device.kind === 'audioinput' && device.deviceId === selectedDeviceId) {
					newAudioTrack = device;

                    // constraints specify new deviceId 
					constraints = {
						video: { facingMode: 'user' },
						audio: { deviceId: { exact: newAudioTrack['deviceId'] } },
					};

				}
			});
		});		

		mediaStream = await navigator.mediaDevices.getUserMedia(constraints);
		audioTrack = mediaStream.getVideoTracks()[0];

		this.localUsersService
			.getWebcamPublisher()
			.replaceTrack(audioTrack)
			.then(() => {
				this.publishAudio(true);
				this.hasAudioDevices = true;
			});
	}

Please let me know how I can get around this issue

@CSantosM do you have any idea on how to solve the issue?

1 Like

The below code is what i have so far, and it works ! I successfully switch from one microphone to another without corrupting my video or audio input, and the change is reflected ofcourse for both users.

There are two issues here however:

  1. track.stop() function, which im using to deactivate old device permissions, proves to be very unstable in general, and literally just doesnt work everytime.

  2. track.stop() for some reason doesnt work outside of ngOninit().

So yes, the below code is in ngOninit(), where it should have been in the onMicrophoneSelected() function I showed in the first message.

What is not in the below code is that I attempted to use onended event as a checker for tracker.stop() completion for a bit more stability, but has not worked so far for me.

  • read //comments in code, screenshot of code below also for clarity
		var currentMediaStream: MediaStream = this.localUsersService.getWebcamPublisher().stream.getMediaStream();
		var currentAudioTrack: MediaStreamTrack;
		var currentVideoTrack: MediaStreamTrack;

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

		//Specifying current video & audio track being used on call, then stopping both tracks to deactivate devices
		currentMediaStream.getTracks().forEach((track) => {
			if (track.kind === 'video') currentAudioTrack = track;
			if (track.kind === 'audio') currentVideoTrack = track;
			track.stop(); // this is unstable & only works in ngOninit() for some reason i.e doesnt work in custom functions()
		});

		//At this point in the code the issue i mentioned first of deactivating previous device permissions on the broswer is resolved, and the call-app has no video or audio input i.e ready for new media

		//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 !== currentAudioTrack.id) {
					newAudioTrack = device;

					//Passing constraints that contain new deviceId for audio, then using replaceTrack() to replace audio & video

					var t = navigator.mediaDevices.getUserMedia({ video: true, audio: { deviceId: { exact: newAudioTrack.deviceId } } }).then((stream) => {   //this also promps user for new device permissions

						currentMediaStream.addTrack(currentVideoTrack); // adding back same videotrack to my active stream
						currentMediaStream.addTrack(stream.getAudioTracks()[0]); // adding back new audiotrack to my active stream

						//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(stream.getAudioTracks()[0])
							.then(() => {
								this.publishAudio(true);
								this.hasAudioDevices = true;
							});

						this.localUsersService
							.getWebcamPublisher()
							.replaceTrack(stream.getVideoTracks()[0])
							.then(() => {
								this.publishAudio(true);
								this.hasAudioDevices = true;
							});
					});
				}
			});
		});


replaceTrack() may not have been the right solution, and any criticism or advise here is welcome.

The goal being either to get track.stop() working everytime, or replacing it with a better solution, and getting this code working in its correct function() onMicrophoneSelected()

@CSantosM @micael.gallego

Few updates since my last message:

  1. The issue with track.stop() not working consistently is only relevant on Firefox. The reason being that currently Firefox has a limit of having 1 active microphone at a time in a tab, and thus if the previous microphone track is not stopped successfully, we get the browser’s NotReadableError. This is something Firefox is planning on changing 1238038 - Allow capture from more than one mic at a time in getUserMedia.

  2. If you use Google Chrome browser for example. The track.stop() part of the code isnt as important to work because chromium browsers dont have a microphone limit in this way. And thus we can continue with replacing the track.

After testing this. The microphone successfully switches, however the issue is that just as the other user can hear me through my new microphone audio, so can I. There is a really loud echo of my own audio coming from the new microphone, and setting echoCancellation to true does not resolve this.

Any ideas on how to potentially resolve this would be really appreciated, thanks for reading.