WebRTC how to set constraints to use audio when available - javascript

Typically you would set constraints as follow
{ audio: true, video: true }
but I'd like to be more permissive then that. I'd like to only get the audio, if it is able to get it i.e. if a microphone is available, otherwise it will throw me a NotFoundError error.
Any ideas if that's possible?

Yes.
You can use the MediaDevices.enumerateDevices() method to get the list of all audio and video devices.
This will return an Array of MediaDeviceInfo objects which will have a kind property letting you know which constraint you can use.
So you could do something like
navigator.mediaDevices.enumerateDevices()
.then(getDevicesTypes)
.then(getUserMedia)
.then(console.log)
.catch(console.error);
function getDevicesTypes(list){
return new Set(list
.filter(device => device.kind.indexOf('input') > -1)
.map(device => device.kind.replace('input', ''))
);
}
function getUserMedia(deviceKinds){
const constraint = {};
deviceKinds.forEach(kind => constraint[kind] = true);
console.log(constraint);
return navigator.mediaDevices.getUserMedia(constraint);
}

Related

Web Audio API Constraints and AudioContext Graph

I am doing some audio processing with web audio api using JavaScript and I need an advice.
I am trying to do something like this.
const stream = await navigator.mediaDevices.getUserMedia
{
audio: true,
}
// Adding some constraints to the stream
for await(const track of stream.getAudioTracks()){
await track.applyConstraints({echoCancellation:true, noiseSuppresion:false,....});
}
// Creating source and destination
const source = context.createMediaStreamSource(stream);
const destination = context.createMediaStreamDestination();
// Filtering some audio
source.connect(filter);
filter.connect(destination);
// Applying new constraints after filtering
for await(const destTrack of destination.stream.getAudioTracks()){
await destTrack.applyConstraints({autoGainControl:true...});
}
But after trying to apply new constarints to the destination I get error OverconstrainedErrorĀ {name: 'OverconstrainedError', message: 'Cannot satisfy constraints', constraint: ''}. Why is error.constraint === '' ? How resolve this issue?

JavaScript programatically check if camera is being used

I'm writing functional tests for a video chat app.
I want to make sure that when the user leaves the meeting the camera turns off. So, I'm trying to check if the camera is being used or not.
Is there a way to do that programatically? I couldn't find any methods on navigator.MediaDevices that say "hey your camera is being used".
Here is how I solved it in TestCafe by "spying" on getUserMedia:
const overWriteGetUserMedia = ClientFunction(() => {
const realGetUserMedia = navigator.mediaDevices.getUserMedia;
const allRequestedTracks = [];
navigator.mediaDevices.getUserMedia = constraints =>
realGetUserMedia(constraints).then(stream => {
stream.getTracks().forEach(track => {
allRequestedTracks.push(track);
});
return stream;
});
return allRequestedTracks;
});
test('leaving a meeting should end streams', async t => {
const allRequestedTracks = await overWriteGetUserMedia();
await t.wait(5000); // wait for streams to start;
await t.click(screen.getByLabelText(/leave/i));
await t.click(screen.getByLabelText(/yes, leave the meeting/i));
await t.wait(1000); // wait for navigation;
const actual = !allRequestedTracks.some(track => !track.ended);
const expected = true;
await t.expect(actual).eql(expected);
});
You can use navigator.mediaDevices.getUserMedia method to get access of user camera, and user active value to check is camera already activated.
If user block the permission to the camera you will receive an error.
Hope this will be work for you.

How to find out which microphone device user gave permission to?

I ask user the permission to use Camera and Microphone:
await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
And in Firefox, I get the following prompt:
Once the user gave the permission, how can I tell which Camera and Microphone was selected? The return value of getUserMedia doesn't provide much info.
Once gUM has given you a stream object do something like this:
async function getAudioDeviceLabel(stream) {
let audioDeviceLabel = 'unknown'
const tracks = stream.getAudioTracks()
if( tracks && tracks.length >= 1 && tracks[0] ) {
const settings = tracks[0].getSettings()
const chosenDeviceId = settings.deviceId
if (chosenDeviceId) {
let deviceList = await navigator.mediaDevices.enumerateDevices()
deviceList = deviceList.filter(device => device.deviceId === chosenDeviceId)
if (deviceList && deviceList.length >= 1) audioDeviceLabel = deviceList[0].label
}
}
return audioDeviceLabel
}
This gets the deviceId of the audio track of your stream, from its settings. It then looks at the list of enumerated devices to retrieve the label associated with the deviceId.
It is kind of a pain in the xxx neck to get this information.

Check if webcam is already activated

I have this piece of code, that activates my webcam:
var video = document.getElementById('video');
// Get access to the camera!
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
// Not adding `{ audio: true }` since we only want video now
navigator.mediaDevices.getUserMedia({ video: true }).then(function (stream) {
video.srcObject = stream;
video.play();
});
}
When running the code above, the browser asks for permission to use the webcam. Let's assume that I allow it. The webcam is active now.
What I want now, is to write some code that checks if the webcam is active/being used. So I want to do something like this:
if(navigator.mediaDevices.getUserMedia == true) {
alert("The camera is active");
}
I found a similar post which has a solution, but I guess I am doing something wrong, even though I tried to follow the same solution. The post is here: How to check with JavaScript that webcam is being used in Chrome
Here is what I tried:
function isCamAlreadyActive() {
if (navigator.getUserMedia) {
navigator.getUserMedia({
video: true
}), function (stream) {
// returns true if any tracks have active state of true
var result = stream.getVideoTracks().some(function (track) {
return track.enabled && track.readyState === 'live';
});
}
if (result) {
alert("Already active");
return true;
}
}
alert("Not active");
return false;
}
My solution always returns false, even when the webcam is active
I am not too sure as to what you are trying to detect exactly.
If you want to know if the webcam is already used by some other program or some other pages, or some other scripts on the same page, then there is in my knowledge no bullet proof solution: different devices and different OSes will have different abilities with regard to request the same device simultaneously. So yes, requesting the device and keeping the stream alive, you could do function isActive() { return true; } but...
But if I read correctly between the lines of your question, I feel that what you want to know is if you have been granted the authorization from the user, and thus if they will see the prompt or not.
In this case, you can make use of the MediaDevices.enumerateDevices method, which will (IMM unfortunately) not request for user permission, while it needs it to get the full informations about the user's devices.
Indeed, the label property of the MediaDeviceInfo object should remain anonymised to avoid finger-printing. So if when you do request for this information, the MediaDeviceInfo all return an empty string (""), it means that you don't have user authorization, and thus, that they will get a prompt.
function checkIsApproved(){
return navigator.mediaDevices.enumerateDevices()
.then(infos =>
// if any of the MediaDeviceInfo has a label, we're good to go
[...infos].some(info=>info.label!=="")
);
}
check.onclick = e => {
checkIsApproved().then(console.log);
};
req.onclick = e => {
navigator.mediaDevices.getUserMedia({video:true}).catch(e=>console.error('you might be better using the jsfiddle'));
}
<button id="check">check</button>
<button id="req">request approval</button>
But since StackSnippets don't work well with getUserMedia, here is a jsfiddle demonstrating this.
In the code which you have shared, there is some problem with the syntax of calling navigator.getMediaUserMedia(). navigator.getMediaUserMedia() expects 3 parameters. You can check here for more details.
You can modify your function to:
function isCamAlreadyActive() {
if (navigator.getUserMedia) {
navigator.getUserMedia({
video: true
}, function (stream) {
// returns true if any tracks have active state of true
var result = stream.getVideoTracks().some(function (track) {
return track.enabled && track.readyState === 'live';
});
if (result) {
alert("Already active");
}else{
alert("No")
}
},
function(e) {
alert("Error: " + e.name);
});
}
}
PS : Since navigator.getMediaUserMedia() is deprecated, you can use navigator.mediaDevices.getUserMedia instead. You can checkout here for more details.
You can use navigator.mediaDevices.getUserMedia as :
function isCamAlreadyActive(){
navigator.mediaDevices.getUserMedia({video: true}).then(function(stream){
result = stream.getVideoTracks().some(function (track) {
return track.enabled && track.readyState === 'live';
});
if(result){
alert("On");
}else{
alert("Off");
}
}).catch(function(err) { console.log(err.name + ": " + err.message); });
}
Hope it helps.

Select rear camera on Android device - jsartoolkit5

I am building a project similar to this example with jsartoolkit5, and I would like to be able to select the back camera of my device instead of letting Chrome on Android select the front one as default.
According to the example in this demo, I have added the code below to switch camera automatically if the device has a back camera.
var videoElement = document.querySelector('canvas');
function successCallback(stream) {
window.stream = stream; // make stream available to console
videoElement.src = window.URL.createObjectURL(stream);
videoElement.play();
}
function errorCallback(error) {
console.log('navigator.getUserMedia error: ', error);
}
navigator.mediaDevices.enumerateDevices().then(
function(devices) {
for (var i = 0; i < devices.length; i++) {
if (devices[i].kind == 'videoinput' && devices[i].label.indexOf('back') !== -1) {
if (window.stream) {
videoElement.src = null;
window.stream.stop();
}
var constraints = {
video: {
optional: [{
sourceId: devices[i].deviceId
}]
}
};
navigator.getUserMedia(constraints, successCallback, errorCallback);
}
}
}
);
The issue is that it works perfectly for a <video> tag, but unluckily jsartoolkit renders the content inside a canvas and it consequently throws an error.
I have also tried to follow the instructions in this closed issue in the Github repository, but this time I get the following error: DOMException: play() can only be initiated by a user gesture.
Do you know or have any suggestion on how to solve this issue?
Thanks in advance for your replies!
Main problem :
You are mixing old and new getUserMedia syntax.
navigator.getUserMedia is deprecated, and navigator.mediaDevices.getUserMedia should be preferred.
Also, I think that optional is not part of the constraints dictionary anymore.
Default Solution
This part is almost a duplicate of this answer : https://stackoverflow.com/a/32364912/3702797
You should be able to call directly
navigator.mediaDevices.getUserMedia({
video: {
facingMode: {
exact: 'environment'
}
}
})
But chrome still has this bug, and even if #jib's answer states that it should work with adpater.js polyfill, I myself were unable to make it work on my chrome for Android.
So previous syntax will currently work only on Firefox for Android.
For chrome, you'll indeed need to use enumerateDevices, along with adapter.js to make it work, but don't mix up the syntax, and everything should be fine :
let handleStream = s => {
document.body.append(
Object.assign(document.createElement('video'), {
autoplay: true,
srcObject: s
})
);
}
navigator.mediaDevices.enumerateDevices().then(devices => {
let sourceId = null;
// enumerate all devices
for (var device of devices) {
// if there is still no video input, or if this is the rear camera
if (device.kind == 'videoinput' &&
(!sourceId || device.label.indexOf('back') !== -1)) {
sourceId = device.deviceId;
}
}
// we didn't find any video input
if (!sourceId) {
throw 'no video input';
}
let constraints = {
video: {
sourceId: sourceId
}
};
navigator.mediaDevices.getUserMedia(constraints)
.then(handleStream);
});
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
Fiddle for chrome which need https.
Make it work with jsartoolkit
You'll have to fork jsartoolkit project and edit artoolkit.api.js.
The main project currently disables mediaDevices.getUserMedia(), so you'll need to enable it again, and you'll also have to add a check for an sourceId option, that we'll add later in the ARController.getUserMediaThreeScene() call.
You can find a rough and ugly implementation of these edits in this fork.
So once it is done, you'll have to rebuild the js files, and then remember to include adapter.js polyfill in your code.
Here is a working fiddle that uses one of the project's demo.

Categories

Resources