From 1d9ae8230ea710eacf22a8cece92969a17ea6b7c Mon Sep 17 00:00:00 2001 From: "mark a. foltz" Date: Wed, 11 Dec 2024 15:35:54 -0800 Subject: [PATCH 1/5] Add ondevicechange event support to demo --- src/content/devices/input-output/js/main.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/content/devices/input-output/js/main.js b/src/content/devices/input-output/js/main.js index 7e3eca8152..f908a841a0 100644 --- a/src/content/devices/input-output/js/main.js +++ b/src/content/devices/input-output/js/main.js @@ -48,7 +48,9 @@ function gotDevices(deviceInfos) { }); } -navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); +function getDevices() { + navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); +} // Attach audio output device to video element using device/sink ID. function attachSinkId(element, sinkId) { @@ -79,8 +81,7 @@ function changeAudioDestination() { function gotStream(stream) { window.stream = stream; // make stream available to console videoElement.srcObject = stream; - // Refresh button list in case labels have become available - return navigator.mediaDevices.enumerateDevices(); + getDevices(); } function handleError(error) { @@ -99,12 +100,12 @@ function start() { audio: {deviceId: audioSource ? {exact: audioSource} : undefined}, video: {deviceId: videoSource ? {exact: videoSource} : undefined} }; - navigator.mediaDevices.getUserMedia(constraints).then(gotStream).then(gotDevices).catch(handleError); + navigator.mediaDevices.getUserMedia(constraints).then(gotStream); } audioInputSelect.onchange = start; audioOutputSelect.onchange = changeAudioDestination; - videoSelect.onchange = start; +navigator.mediaDevices.ondevicechange = getDevices; start(); From d7a734761284c08c63a2eb71da4d7ce3d76001dd Mon Sep 17 00:00:00 2001 From: "mark a. foltz" Date: Mon, 16 Dec 2024 12:07:23 -0800 Subject: [PATCH 2/5] Fix re-entrancy --- src/content/devices/input-output/js/main.js | 55 +++++++++++++++++---- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/content/devices/input-output/js/main.js b/src/content/devices/input-output/js/main.js index f908a841a0..8955874223 100644 --- a/src/content/devices/input-output/js/main.js +++ b/src/content/devices/input-output/js/main.js @@ -13,10 +13,22 @@ const audioInputSelect = document.querySelector('select#audioSource'); const audioOutputSelect = document.querySelector('select#audioOutput'); const videoSelect = document.querySelector('select#videoSource'); const selectors = [audioInputSelect, audioOutputSelect, videoSelect]; +var hasMic = false; +var hasCamera = false; +var openMic = undefined; +var openCamera = undefined; audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype); +function getDevices() { + console.log('getDevices'); + navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); +} + function gotDevices(deviceInfos) { + console.log('gotDevices', deviceInfos); + hasMic = false; + hasCamera = false; // Handles being called several times to update labels. Preserve values. const values = selectors.map(select => select.value); selectors.forEach(select => { @@ -26,15 +38,18 @@ function gotDevices(deviceInfos) { }); for (let i = 0; i !== deviceInfos.length; ++i) { const deviceInfo = deviceInfos[i]; + console.log(deviceInfo); const option = document.createElement('option'); option.value = deviceInfo.deviceId; if (deviceInfo.kind === 'audioinput') { + hasMic = true; option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`; audioInputSelect.appendChild(option); } else if (deviceInfo.kind === 'audiooutput') { option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`; audioOutputSelect.appendChild(option); } else if (deviceInfo.kind === 'videoinput') { + hasCamera = true; option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`; videoSelect.appendChild(option); } else { @@ -46,9 +61,11 @@ function gotDevices(deviceInfos) { select.value = values[selectorIndex]; } }); + start(); } function getDevices() { + console.log('getDevices'); navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); } @@ -81,7 +98,15 @@ function changeAudioDestination() { function gotStream(stream) { window.stream = stream; // make stream available to console videoElement.srcObject = stream; - getDevices(); + if (stream.getVideoTracks()[0]) { + openCamera = stream.getVideoTracks()[0].getSettings().deviceId; + } + if (stream.getAudioTracks()[0]) { + openMic = stream.getAudioTracks()[0].getSettings().deviceId; + } + console.log('openCamera', openCamera, 'openMic', openMic); + // Refresh list in case labels have become available + return getDevices(); } function handleError(error) { @@ -89,18 +114,30 @@ function handleError(error) { } function start() { + const audioSource = audioInputSelect.value || undefined; + const videoSource = videoSelect.value || undefined; + console.log('audio', audioSource, 'video', videoSource); + if (openMic == audioSource && openCamera == videoSource) { + return; + } if (window.stream) { window.stream.getTracks().forEach(track => { track.stop(); }); + openCamera = undefined; + openMic = undefined; + } + let constraints = {}; + if (hasMic) { + constraints['audio'] = {deviceId: audioSource ? {exact: audioSource} : undefined}; + } + if (hasCamera) { + constraints['video'] = {deviceId: videoSource ? {exact: videoSource} : undefined}; + } + console.log('start', constraints); + if (hasCamera || hasMic) { + navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError); } - const audioSource = audioInputSelect.value; - const videoSource = videoSelect.value; - const constraints = { - audio: {deviceId: audioSource ? {exact: audioSource} : undefined}, - video: {deviceId: videoSource ? {exact: videoSource} : undefined} - }; - navigator.mediaDevices.getUserMedia(constraints).then(gotStream); } audioInputSelect.onchange = start; @@ -108,4 +145,4 @@ audioOutputSelect.onchange = changeAudioDestination; videoSelect.onchange = start; navigator.mediaDevices.ondevicechange = getDevices; -start(); +getDevices(); From e7004b441bf05fefe2bd4b4c497737d2071bef2c Mon Sep 17 00:00:00 2001 From: "mark a. foltz" Date: Mon, 16 Dec 2024 12:09:45 -0800 Subject: [PATCH 3/5] Remove redundant function --- src/content/devices/input-output/js/main.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/content/devices/input-output/js/main.js b/src/content/devices/input-output/js/main.js index 8955874223..c20d0aacf5 100644 --- a/src/content/devices/input-output/js/main.js +++ b/src/content/devices/input-output/js/main.js @@ -64,11 +64,6 @@ function gotDevices(deviceInfos) { start(); } -function getDevices() { - console.log('getDevices'); - navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); -} - // Attach audio output device to video element using device/sink ID. function attachSinkId(element, sinkId) { if (typeof element.sinkId !== 'undefined') { From 8b96f7238b33eda10875f66cc925a66e0d02ecd0 Mon Sep 17 00:00:00 2001 From: "mark a. foltz" Date: Tue, 17 Dec 2024 11:22:07 -0800 Subject: [PATCH 4/5] Fix bugs and lint issues. --- src/content/devices/input-output/js/main.js | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/content/devices/input-output/js/main.js b/src/content/devices/input-output/js/main.js index c20d0aacf5..c270a2c29e 100644 --- a/src/content/devices/input-output/js/main.js +++ b/src/content/devices/input-output/js/main.js @@ -13,10 +13,11 @@ const audioInputSelect = document.querySelector('select#audioSource'); const audioOutputSelect = document.querySelector('select#audioOutput'); const videoSelect = document.querySelector('select#videoSource'); const selectors = [audioInputSelect, audioOutputSelect, videoSelect]; -var hasMic = false; -var hasCamera = false; -var openMic = undefined; -var openCamera = undefined; +let hasMic = false; +let hasCamera = false; +let openMic = undefined; +let openCamera = undefined; +let hasPermission = false; audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype); @@ -29,6 +30,7 @@ function gotDevices(deviceInfos) { console.log('gotDevices', deviceInfos); hasMic = false; hasCamera = false; + hasPermission = false; // Handles being called several times to update labels. Preserve values. const values = selectors.map(select => select.value); selectors.forEach(select => { @@ -39,6 +41,12 @@ function gotDevices(deviceInfos) { for (let i = 0; i !== deviceInfos.length; ++i) { const deviceInfo = deviceInfos[i]; console.log(deviceInfo); + if (deviceInfo.deviceId == '') { + continue; + } + // If we get at least one deviceId, that means user has granted user + // media permissions. + hasPermission = true; const option = document.createElement('option'); option.value = deviceInfo.deviceId; if (deviceInfo.kind === 'audioinput') { @@ -112,9 +120,12 @@ function start() { const audioSource = audioInputSelect.value || undefined; const videoSource = videoSelect.value || undefined; console.log('audio', audioSource, 'video', videoSource); - if (openMic == audioSource && openCamera == videoSource) { + console.log('openMic', openMic, 'openCamera', openCamera); + // Don't open the same devices again. + if (hasPermission && openMic == audioSource && openCamera == videoSource) { return; } + // Close existng streams. if (window.stream) { window.stream.getTracks().forEach(track => { track.stop(); @@ -122,15 +133,18 @@ function start() { openCamera = undefined; openMic = undefined; } - let constraints = {}; + const constraints = { + audio: true, + video: true + }; if (hasMic) { constraints['audio'] = {deviceId: audioSource ? {exact: audioSource} : undefined}; } if (hasCamera) { - constraints['video'] = {deviceId: videoSource ? {exact: videoSource} : undefined}; + constraints['video'] = {deviceId: videoSource ? {exact: videoSource} : undefined}; } console.log('start', constraints); - if (hasCamera || hasMic) { + if (!hasPermission || hasCamera || hasMic) { navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError); } } From a5a78cbed9ea33aee7b74167fc9af77fddc46591 Mon Sep 17 00:00:00 2001 From: "mark a. foltz" Date: Tue, 17 Dec 2024 11:24:46 -0800 Subject: [PATCH 5/5] Prune back console.logs --- src/content/devices/input-output/js/main.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/content/devices/input-output/js/main.js b/src/content/devices/input-output/js/main.js index c270a2c29e..bca9b9078f 100644 --- a/src/content/devices/input-output/js/main.js +++ b/src/content/devices/input-output/js/main.js @@ -22,7 +22,6 @@ let hasPermission = false; audioOutputSelect.disabled = !('sinkId' in HTMLMediaElement.prototype); function getDevices() { - console.log('getDevices'); navigator.mediaDevices.enumerateDevices().then(gotDevices).catch(handleError); } @@ -40,7 +39,6 @@ function gotDevices(deviceInfos) { }); for (let i = 0; i !== deviceInfos.length; ++i) { const deviceInfo = deviceInfos[i]; - console.log(deviceInfo); if (deviceInfo.deviceId == '') { continue; } @@ -107,7 +105,6 @@ function gotStream(stream) { if (stream.getAudioTracks()[0]) { openMic = stream.getAudioTracks()[0].getSettings().deviceId; } - console.log('openCamera', openCamera, 'openMic', openMic); // Refresh list in case labels have become available return getDevices(); } @@ -119,8 +116,6 @@ function handleError(error) { function start() { const audioSource = audioInputSelect.value || undefined; const videoSource = videoSelect.value || undefined; - console.log('audio', audioSource, 'video', videoSource); - console.log('openMic', openMic, 'openCamera', openCamera); // Don't open the same devices again. if (hasPermission && openMic == audioSource && openCamera == videoSource) { return;