From f6e0cd9a03914546dc3abe130c359a14cbb55d65 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 18:54:15 +0200 Subject: [PATCH] don't show error dialog when user has no webcam instead, retry with just audio. Also when mounted, check if the user has given enough permissions to return non-empty labels for the devices, something both ff & chrome do if you haven't going through the permissions popup yet. If not, show the permissions button. --- src/CallMediaHandler.js | 5 ++ .../tabs/user/VoiceUserSettingsTab.js | 77 +++++++++++-------- src/i18n/strings/en_EN.json | 3 - 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js index 55b8764a9e..a0364f798a 100644 --- a/src/CallMediaHandler.js +++ b/src/CallMediaHandler.js @@ -18,6 +18,11 @@ import * as Matrix from 'matrix-js-sdk'; import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; export default { + hasAnyLabeledDevices: async function() { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.some(d => !!d.label); + }, + getDevices: function() { // Only needed for Electron atm, though should work in modern browsers // once permission has been granted to the webapp diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index 31a11b13ea..0cafbacf8d 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -29,48 +29,64 @@ export default class VoiceUserSettingsTab extends React.Component { super(); this.state = { - mediaDevices: null, + mediaDevices: false, activeAudioOutput: null, activeAudioInput: null, activeVideoInput: null, }; } - componentWillMount(): void { - this._refreshMediaDevices(); + async componentDidMount() { + const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices(); + if (canSeeDeviceLabels) { + this._refreshMediaDevices(); + } } _refreshMediaDevices = async (stream) => { - if (stream) { - // kill stream so that we don't leave it lingering around with webcam enabled etc - // as here we called gUM to ask user for permission to their device names only - stream.getTracks().forEach((track) => track.stop()); - } - this.setState({ mediaDevices: await CallMediaHandler.getDevices(), activeAudioOutput: CallMediaHandler.getAudioOutput(), activeAudioInput: CallMediaHandler.getAudioInput(), activeVideoInput: CallMediaHandler.getVideoInput(), }); + if (stream) { + // kill stream (after we've enumerated the devices, otherwise we'd get empty labels again) + // so that we don't leave it lingering around with webcam enabled etc + // as here we called gUM to ask user for permission to their device names only + stream.getTracks().forEach((track) => track.stop()); + } }; - _requestMediaPermissions = () => { - const getUserMedia = ( - window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia - ); - if (getUserMedia) { - return getUserMedia.apply(window.navigator, [ - { video: true, audio: true }, - this._refreshMediaDevices, - function() { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createTrackedDialog('No media permissions', '', ErrorDialog, { - title: _t('No media permissions'), - description: _t('You may need to manually permit Riot to access your microphone/webcam'), - }); - }, - ]); + _requestMediaPermissions = async () => { + let constraints; + let stream; + let error; + try { + constraints = {video: true, audio: true}; + stream = await navigator.mediaDevices.getUserMedia(constraints); + } catch (err) { + // user likely doesn't have a webcam, + // we should still allow to select a microphone + if (err.name === "NotFoundError") { + constraints = { audio: true }; + try { + stream = await navigator.mediaDevices.getUserMedia(constraints); + } catch (err) { + error = err; + } + } else { + error = err; + } + } + if (error) { + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog('No media permissions', '', ErrorDialog, { + title: _t('No media permissions'), + description: _t('You may need to manually permit Riot to access your microphone/webcam'), + }); + } else { + this._refreshMediaDevices(stream); } }; @@ -101,15 +117,7 @@ export default class VoiceUserSettingsTab extends React.Component { _renderDeviceOptions(devices, category) { return devices.map((d) => { - let label = d.label; - if (!label) { - switch (d.kind) { - case "audioinput": label = _t("Unnamed microphone"); break; - case "audiooutput": label = _t("Unnamed audio output"); break; - case "videoinput": label = _t("Unnamed camera"); break; - } - } - return (); + return (); }); } @@ -120,6 +128,7 @@ export default class VoiceUserSettingsTab extends React.Component { let speakerDropdown = null; let microphoneDropdown = null; let webcamDropdown = null; + console.log({mediaDevices: this.state.mediaDevices}); if (this.state.mediaDevices === false) { requestButton = (
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 010ad29da0..d3d32a6e89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -615,9 +615,6 @@ "Learn more about how we use analytics.": "Learn more about how we use analytics.", "No media permissions": "No media permissions", "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Unnamed microphone": "Unnamed microphone", - "Unnamed audio output": "Unnamed audio output", - "Unnamed camera": "Unnamed camera", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", "Request media permissions": "Request media permissions", "No Audio Outputs detected": "No Audio Outputs detected",