Reputation: 2262
I'm trying to get highest video resolution as possible through JS navigator.getUserMedia
. I know about constraints, but don't know how to choose right in my case.
The problem is looks like there is no way to say "I want a video at maximum resolution". So instead I'm trying to say "I want video not less than very big resolution".
When I'm trying minWidth: 1600
, Chrome returns me 1280×720 video (highest possible for my camera, I think). But what if user has camera with higher resolution? So I'm asking for minWidth: 2048
video, and Chrome returns only 640×480.
var constraints = {
video: {
optional: [
{minWidth: 2048}
]
}
};
This is online example: http://jsbin.com/kibeza/1/watch?js,output
And there is actual problem: Chrome doesn't know math. It think what 1600 is greater than 2048. I can't ask for video "not less than 100500", because in this case I'll get standard low resolution. I cant ask video "not less than some reasonable small resolution", because there can be users with higher resolution and I want to get higher resolution.
Upvotes: 60
Views: 78665
Reputation: 3070
You can do this directly.
You need to get user media first to gain access to the video camera and its stream,
then get a list of devices,
then filter cameras (kind 'videoinput'
),
then find the device with the highest pair of width
and height
capability properties. These are the primary audio or video track of the device's stream you have gained access to previously.
I have found my best camera first by sorting them in descending order by maximum height.max * width.max
value of each. One can have a different criteria.
async function getBestCamera(criteriaFunc) {
return navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(() => {
return navigator.mediaDevices.enumerateDevices().then((devices) => {
const sortedDevices = devices
.filter(device => device.kind == 'videoinput')
.toSorted((device1, device2) => {
const capabilities1 = device1.getCapabilities();
const capabilities2 = device2.getCapabilities();
console.log(device1, device2)
console.log("capabilities1", capabilities1)
console.log("capabilities2", capabilities2)
return criteriaFunc(capabilities1, capabilities2); // return type: truthy
})
console.log("sortedDevices: ", sortedDevices)
return sortedDevices;
})
.then(sortedDevices => sortedDevices[0])
})
}
// Usage:
// return type: truthy
function compareResolutions(capabilities1, capabilities2) {
return (capabilities1.height.max * capabilities1.width.max) > (capabilities2.height.max * capabilities2.width.max)
}
getBestCamera(compareResolutions).then(bestCamera => {
console.log("bestCamera: ", bestCamera)
console.log("capabilities: ", bestCamera.getCapabilities())
}) // just to log stuff returned
Upvotes: 1
Reputation: 1
I was also getting same problem. tried all give solution but not able to resolve, somehow i write a code and its working fine for my project.
<!DOCTYPE html>
<html>
<head>
<title>Colgate Smile Test</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous">
<style>
#canvas {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}
#video {
-webkit-transform: scaleX(-1); /* mirror effect while using front cam */
transform: scaleX(-1); /* mirror effect while using front cam */
}
</style>
</head>
<body>
<div>
<canvas id="canvas" style= "width:100vw;height:100vh"></canvas>
<video id="video" style="display:none" ></video>
</div>
</body>
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.style.width = window.screen.width;
canvas.style.height = window.screen.width;
// Fix for iOS Safari from https://leemartin.dev/hello-webrtc-on-safari-11-e8bcb5335295
video.setAttribute('autoplay', '');
video.setAttribute('muted', '');
video.setAttribute('playsinline', '')
const constraints = {
audio: false,
video: {
facingMode: 'user'
}
}
function getVideo() {
navigator.mediaDevices.getUserMedia(constraints)
.then(localMediaStream => {
console.log(localMediaStream);
// DEPRECIATION :
// The following has been depreceated by major browsers as of Chrome and Firefox.
// video.src = window.URL.createObjectURL(localMediaStream);
// Please refer to these:
// Deprecated - https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
// Newer Syntax - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
console.dir(video);
if ('srcObject' in video) {
video.srcObject = localMediaStream;
} else {
video.src = URL.createObjectURL(localMediaStream);
}
// video.src = window.URL.createObjectURL(localMediaStream);
video.play();
})
.catch(err => {
console.error(`OH NO!!!!`, err);
});
}
function paintToCanvas() {
const width = video.videoWidth;
const height = video.videoHeight;
canvas.width = width;
canvas.height = height;
return setInterval(() => {
ctx.drawImage(video, 0, 0, width, height);
}, 16);
}
var count = 0;
getVideo();
video.addEventListener('canplay', paintToCanvas);
</script>
</html>
Upvotes: 0
Reputation: 3578
APIs and standards have changed since these answers were posted so I will post my current solution to the max-res GUM problem. I've tried the "scanning" solutions, even binary search etc., all have resulted in a crashing webpage on mobile devices (e.g. iPhone iPad) while desktop browsers showed more resilience.
This is what I currently do to get max resolution (use advanced: [
and multiple width: { exact:
s):
const video = $("#video")[0]
const constraints = {
audio: false,
video: {
advanced: [
{ width: { exact: 2560 } },
{ width: { exact: 1920 } },
{ width: { exact: 1280 } },
{ width: { exact: 1024 } },
{ width: { exact: 900 } },
{ width: { exact: 800 } },
{ width: { exact: 640 } },
{ width: { exact: 320 } }
]
}
};
$(document).ready(() => {
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => {
window.stream = mediaStream;
video.srcObject = mediaStream;
})
.then(() => {
return new Promise((resolve) => {
video.onloadedmetadata = resolve;
});
})
.then(async () => {
console.log('Ready to work');
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<video id="video" autoplay muted playsinline />
Upvotes: 7
Reputation: 1847
You can always use applyConstraints()
combined with getCapabilities()
on the track.
Honza Kalfus' answer claims chrome now handles the ideal
prop right for resolution. I didn't test that, but Chrome 83 still screws up ideal value for frameRate and simply wont start the video if outside of the capable range... so I hope this helps someone.
stream = await navigator.mediaDevices.getUserMedia({ video: { width: 100, height: 100, frameRate: { min: 8, max:60 }, facingMode: 'environment' } });
track = stream.getTracks()[0];
var frIdeal = 35;
constrs = track.getConstraints();
frCap = track.getCapabilities().frameRate;
if (frCap && "min" in frCap && "max" in frCap) {
constrs.frameRate = Math.max(frCap.min, Math.min(frCap.max, frIdeal));
track.applyConstraints(constrs);
}
this will give me 30hz if that is the maximum (Chrome83).
see also the MDN docs
Upvotes: 3
Reputation: 783
You can check this utility to check the available resolutions of your devices:
https://webrtchacks.github.io/WebRTC-Camera-Resolution/
It is useful for troubleshooting purposes.
Hope you find it interesting!
Upvotes: 5
Reputation: 5922
Use:
var constraints = {
video: {
width: { ideal: 4096 },
height: { ideal: 2160 }
}
};
This will make the browser use the maximum resolution available, up to 4K. Works in Chrome 63, Edge 41 and Firefox 58.
Citing MDN regarding the use of ideal:
An ideal value, when used, has gravity, which means that the browser will try to find the setting (and camera, if you have more than one), with the smallest fitness distance from the ideal values given.
Upvotes: 61
Reputation: 1352
Still I haven't found any good API to get maximum video resolution with getUserMedia.
But here I am sharing my idea to get maximum supported video resolution of the connected device using Binary Search algorithm and it's working great.
Here are the steps to do this job,
Here I am sharing my implementation:
var ResolutionsToCheck = [
{width: 160, height:120},
{width: 320, height:180},
{width: 320, height:240},
{width: 640, height:360},
{width: 640, height:480},
{width: 768, height:576},
{width: 1024, height:576},
{width: 1280, height:720},
{width: 1280, height:768},
{width: 1280, height:800},
{width: 1280, height:900},
{width: 1280, height:1000},
{width: 1920, height:1080},
{width: 1920, height:1200},
{width: 2560, height:1440},
{width: 3840, height:2160},
{width: 4096, height:2160}
];
var left = 0;
var right = ResolutionsToCheck.length;
var selectedWidth;
var selectedHeight;
var mid;
function FindMaximum_WidthHeight_ForCamera()
{
console.log("left:right = ", left, ":", right);
if(left > right)
{
console.log("Selected Height:Width = ", selectedWidth, ":", selectedHeight);
return;
}
mid = Math.floor((left + right) / 2);
var temporaryConstraints = {
"audio": true,
"video": {
"mandatory": {
"minWidth": ResolutionsToCheck[mid].width,
"minHeight": ResolutionsToCheck[mid].height,
"maxWidth": ResolutionsToCheck[mid].width,
"maxHeight": ResolutionsToCheck[mid].height
},
"optional": []
}
}
navigator.mediaDevices.getUserMedia(temporaryConstraints).then(checkSuccess).catch(checkError);
}
function checkSuccess(stream)
{
console.log("Success for --> " , mid , " ", ResolutionsToCheck[mid]);
selectedWidth = ResolutionsToCheck[mid].width;
selectedHeight = ResolutionsToCheck[mid].height;
left = mid+1;
for (let track of stream.getTracks())
{
track.stop()
}
FindMaximum_WidthHeight_ForCamera();
}
function checkError(error)
{
console.log("Failed for --> " + mid , " ", ResolutionsToCheck[mid], " ", error);
right = mid-1;
FindMaximum_WidthHeight_ForCamera();
}
Just call function FindMaximum_WidthHeight_ForCamera(). When operation will be completed then maximum video resolution will be stored in selectedWidth and selectedHeight variables. Here I am also attaching console output for my device:
//Console Output
left:right = 0 : 17
Success for --> 8 Objectheight: 768width: 1280__proto__: Object
left:right = 9 : 17
Failed for --> 13 Objectheight: 1200width: 1920__proto__: Object NavigatorUserMediaError
left:right = 9 : 12
Success for --> 10 Objectheight: 900width: 1280__proto__: Object
left:right = 11 : 12
Failed for --> 11 Objectheight: 1000width: 1280__proto__: Object NavigatorUserMediaError
left:right = 11 : 10
Selected Height:Width = 1280 : 900
I have tested this implementation using Chrome Version 57.0.2987.110 (64-bit) and Logitech, Inc. Webcam C270. But I think this solution should work in each scenario. Thank You.
Upvotes: 7
Reputation: 25324
I had varied success with defining ideal
dimensions and trying to force the 'back' camera.
$video = document.getElementById('video')
//declare ideal values
var constraints = {
audio: false,
video: {
width: { ideal: 1280 },
height: { ideal: 1024 },
facingMode: "environment"
}
};
// enumerate devices and select the first camera (mostly the back one)
navigator.mediaDevices.enumerateDevices().then(function(devices) {
for (var i = 0; i !== devices.length; ++i) {
if (devices[i].kind === 'videoinput') {
console.log('Camera found: ', devices[i].label || 'label not found', devices[i].deviceId || 'id no found');
videoConstraints.deviceId = { exact: devices[i].deviceId }
}
}
});
//first up the stream
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
$video.srcObject = stream;
// log the real size
console.log($video.videoWidth, $video.videoHeight);
}).catch(function(err) {
console.log(err.name + ': ' + err.message);
});
Upvotes: 12
Reputation: 543
I agree with homm, his approach works fine: https://jsfiddle.net/evpozdniakov/c84ksucw/
var getUserMediaPrefixed,
videoStream,
videoTag;
setGumPrefix();
if (!getUserMediaPrefixed) {
logMessage('Sorry, your browser doesn\'t support getUserMedia interface');
}
else {
runCamera();
}
function dealWithStream(stream) {
videoStream = stream;
if (!videoTag) {
videoTag = document.createElement('video');
videoTag.addEventListener('resize', videoEventListener);
}
videoTag.src = window.URL.createObjectURL(stream);
videoTag.play();
}
function handleError(e) {
if (e.name == 'PermissionDeniedError') {
logMessage('It looks like you\'ve denied access to the camera.');
}
else if (e.name == 'SourceUnavailableError') {
logMessage('It looks like your camera is <b>used</b> by another application.');
}
else {
logMessage('The camera is unavailable. The error message is: ' +e.message);
}
}
function logMessage(msg) {
var p = document.createElement('p');
p.innerHTML = msg;
document.getElementById('output').appendChild(p);
}
function runCamera() {
var constraints = {
audio: false,
video: {
optional: [
{minWidth: 320},
{minWidth: 640},
{minWidth: 800},
{minWidth: 900},
{minWidth: 1024},
{minWidth: 1280},
{minWidth: 1920},
{minWidth: 2560}
]
}
};
navigator[getUserMediaPrefixed](constraints, dealWithStream, handleError);
}
function setGumPrefix() {
if (navigator.getUserMedia) {
getUserMediaPrefixed = 'getUserMedia';
}
else if (navigator.webkitGetUserMedia) {
getUserMediaPrefixed = 'webkitGetUserMedia';
}
else if (navigator.mozGetUserMedia) {
getUserMediaPrefixed = 'mozGetUserMedia';
}
else if (navigator.msGetUserMedia) {
getUserMediaPrefixed = 'msGetUserMedia';
}
}
function videoEventListener() {
if (videoTag.videoWidth) {
logMessage('Best captured video quality in your browser is ' +videoTag.videoWidth+ '×' +videoTag.videoHeight);
// stop stream
videoStream.stop();
videoTag.src = '';
}
}
In my case, Opera and Chrome offer max resolution 1280×720.
Firefox gives 640×480 by default, but you can improve his resolution as well up to 1280×720. Here you go:
Upvotes: 4
Reputation: 2262
I still don't know correct answer, but I do the following:
video: {
optional: [
{minWidth: 320},
{minWidth: 640},
{minWidth: 1024},
{minWidth: 1280},
{minWidth: 1920},
{minWidth: 2560},
]
}
While single minWidth: 2560
expression resets resolution to default, series of minWidth
expression make browser always takes maximum resolution on tested hardware.
Upvotes: 18