diff --git a/index.html b/index.html
index 54b5fdf07..aad4cd92f 100644
--- a/index.html
+++ b/index.html
@@ -100,6 +100,8 @@
Control camera pan, tilt, and zoom
+
+ Control exposure
Devices:
Query media devices
diff --git a/src/content/getusermedia/exposure/index.html b/src/content/getusermedia/exposure/index.html
new file mode 100644
index 000000000..3a7f61327
--- /dev/null
+++ b/src/content/getusermedia/exposure/index.html
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Control Exposure
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open camera
+
+
+
+
+
+
+
Exposure Compensation:
+
+
+
+
+
+
+
White Balance Mode:
+
+
+
+
+
Refresh Controls
+
+
+
+
Display the video stream from getUserMedia()
in a video
+ element and control exposureMode, exposureTime and exposureCompensation if camera supports it.
+
+
The MediaStreamTrack
object track
is in
+ global scope, so you can inspect it from the console.
+
+
View source on GitHub
+
+
+
+
+
+
+
+
+
diff --git a/src/content/getusermedia/exposure/js/main.js b/src/content/getusermedia/exposure/js/main.js
new file mode 100644
index 000000000..22a01b1bf
--- /dev/null
+++ b/src/content/getusermedia/exposure/js/main.js
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+'use strict';
+
+// Put variables in global scope to make them available to the browser console.
+const constraints = window.constraints = {
+ audio: false,
+ video: true
+};
+
+function handleSuccess(stream) {
+ const video = document.querySelector('video');
+ const videoTracks = stream.getVideoTracks();
+ console.log('Got stream with constraints:', constraints);
+ console.log(`Using video device: ${videoTracks[0].label}`);
+ video.srcObject = stream;
+
+ // make track variable available to browser console.
+ [window.track] = stream.getVideoTracks();
+
+ loadProperties();
+
+ document.querySelector(`button[id=refreshControls]`).disabled = false;
+}
+
+function loadProperties(refreshValuesOnly) {
+ const track = window.track;
+ const capabilities = track.getCapabilities();
+ const settings = track.getSettings();
+ console.log('Capabilities: ', capabilities);
+ console.log('Settings: ', settings);
+
+ for (const property of ['exposureMode', 'exposureTime', 'exposureCompensation', 'brightness', 'whiteBalanceMode']) {
+ // Check whether camera supports exposure.
+ if (!(property in settings)) {
+ errorMsg(`Camera does not support ${property}.`);
+ continue;
+ }
+
+ let element;
+
+ if (Array.isArray(capabilities[property])) {
+ // Map it to a select element.
+ const select = document.querySelector(`select[name=${property}]`);
+ element = select;
+ if (capabilities[property] && !refreshValuesOnly) {
+ for (const mode of capabilities[property]) {
+ select.insertAdjacentHTML('afterbegin', `${mode} `);
+ }
+ }
+ } else {
+ // Map it to a slider element.
+ const input = document.querySelector(`input[name=${property}]`);
+ element = input;
+ input.min = capabilities[property].min;
+ input.max = capabilities[property].max;
+ input.step = capabilities[property].step;
+ }
+
+ element.value = settings[property];
+ element.disabled = false;
+ if (!refreshValuesOnly) {
+ element.oninput = async event => {
+ try {
+ const constraints = {advanced: [{[property]: element.value}]};
+ await track.applyConstraints(constraints);
+ console.log('Did successfully apply new constraints: ', constraints);
+ console.log('New camera settings: ', track.getSettings());
+ } catch (err) {
+ console.error('applyConstraints() failed: ', err);
+ }
+ };
+ }
+ }
+}
+
+function handleError(error) {
+ if (error.name === 'NotAllowedError') {
+ errorMsg('Permissions have not been granted to use your camera, ' +
+ 'you need to allow the page access to your devices in ' +
+ 'order for the demo to work.');
+ }
+ errorMsg(`getUserMedia error: ${error.name}`, error);
+}
+
+function errorMsg(msg, error) {
+ const errorElement = document.querySelector('#errorMsg');
+ errorElement.innerHTML += `${msg}
`;
+ if (typeof error !== 'undefined') {
+ console.error(error);
+ }
+}
+
+async function init(e) {
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia(constraints);
+ handleSuccess(stream);
+ e.target.disabled = true;
+ } catch (e) {
+ handleError(e);
+ }
+}
+
+document.querySelector('#showVideo').addEventListener('click', e => init(e));
diff --git a/src/content/peerconnection/trickle-ice/js/main.js b/src/content/peerconnection/trickle-ice/js/main.js
index bfbe2c727..4f96b1d0a 100644
--- a/src/content/peerconnection/trickle-ice/js/main.js
+++ b/src/content/peerconnection/trickle-ice/js/main.js
@@ -83,7 +83,7 @@ function selectServer(event) {
function addServer() {
const scheme = urlInput.value.split(':')[0];
- if (scheme !== 'stun' && scheme !== 'turn' && scheme !== 'turns') {
+ if (!['stun', 'stuns', 'turn', 'turns'].includes(scheme)) {
alert(`URI scheme ${scheme} is not valid`);
return;
}
@@ -155,32 +155,28 @@ async function start() {
// Whether we only gather a single set of candidates for RTP and RTCP.
console.log(`Creating new PeerConnection with config=${JSON.stringify(config)}`);
- document.getElementById('error').innerText = '';
- pc = new RTCPeerConnection(config);
- pc.onicecandidate = iceCallback;
- pc.onicegatheringstatechange = gatheringStateChange;
- pc.onicecandidateerror = iceCandidateError;
- if (stream) {
- stream.getTracks().forEach(track => pc.addTrack(track, stream));
+ const errDiv = document.getElementById('error');
+ errDiv.innerText = '';
+ let desc;
+ try {
+ pc = new RTCPeerConnection(config);
+ pc.onicecandidate = iceCallback;
+ pc.onicegatheringstatechange = gatheringStateChange;
+ pc.onicecandidateerror = iceCandidateError;
+ if (stream) {
+ stream.getTracks().forEach(track => pc.addTrack(track, stream));
+ }
+ desc = await pc.createOffer(offerOptions);
+ } catch (err) {
+ errDiv.innerText = `Error creating offer: ${err}`;
+ gatherButton.disabled = false;
+ return;
}
- pc.createOffer(
- offerOptions
- ).then(
- gotDescription,
- noDescription
- );
-}
-
-function gotDescription(desc) {
begin = window.performance.now();
candidates = [];
pc.setLocalDescription(desc);
}
-function noDescription(error) {
- console.log('Error creating offer: ', error);
-}
-
// Parse the uint32 PRIORITY field into its constituent parts from RFC 5245,
// type preference, local preference, and (256 - component ID).
// ex: 126 | 32252 | 255 (126 is host preference, 255 is component ID 1)