From 4daa7b8c8ce5dba7ec1dee08114ded6015ec979a Mon Sep 17 00:00:00 2001 From: Greg Fodor <gfodor@gmail.com> Date: Mon, 2 Apr 2018 19:06:46 -0700 Subject: [PATCH] Sticky mic selection and fix denial case --- src/react-components/ui-root.js | 152 +++++++++++++++++++++----------- src/storage/store.js | 3 +- 2 files changed, 104 insertions(+), 51 deletions(-) diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index ce8c569c1..fd0bf9b17 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -225,8 +225,6 @@ class UIRoot extends Component { }; performDirectEntryFlow = async enterInVR => { - this.startTestTone(); - this.setState({ enterInVR }); const hasGrantedMic = await this.hasGrantedMicPermissions(); @@ -235,7 +233,6 @@ class UIRoot extends Component { await this.setMediaStreamToDefault(); await this.beginAudioSetup(); } else { - this.stopTestTone(); this.setState({ entryStep: ENTRY_STEPS.mic_grant }); } }; @@ -290,8 +287,24 @@ class UIRoot extends Component { }; setMediaStreamToDefault = async () => { - await this.fetchAudioTrack({ audio: true }); + let hasAudio = false; + console.log(this.props.store.state); + const { lastUsedMicDeviceId } = this.props.store.state; + + // Try to fetch last used mic, and if we don't get it then fall back to default. + if (lastUsedMicDeviceId) { + console.log("Check for " + lastUsedMicDeviceId); + hasAudio = await this.fetchAudioTrack({ audio: { deviceId: { exact: lastUsedMicDeviceId } } }); + } + + if (!hasAudio) { + console.log("Use defualt"); + hasAudio = await this.fetchAudioTrack({ audio: true }); + } + await this.setupNewMediaStream(); + + return { hasAudio }; }; setStateAndRequestScreen = async e => { @@ -322,51 +335,79 @@ class UIRoot extends Component { if (this.state.audioTrack) { this.state.audioTrack.stop(); } - const mediaStream = await navigator.mediaDevices.getUserMedia(constraints); - this.setState({ audioTrack: mediaStream.getAudioTracks()[0] }); + + try { + const mediaStream = await navigator.mediaDevices.getUserMedia(constraints); + this.setState({ audioTrack: mediaStream.getAudioTracks()[0] }); + return true; + } catch (e) { + // Error fetching audio track, most likely a permission denial. + this.setState({ audioTrack: null }); + return false; + } }; setupNewMediaStream = async () => { const mediaStream = new MediaStream(); - // we should definitely have an audioTrack at this point. - mediaStream.addTrack(this.state.audioTrack); - if (this.state.videoTrack) { mediaStream.addTrack(this.state.videoTrack); } - const AudioContext = window.AudioContext || window.webkitAudioContext; - const audioContext = new AudioContext(); - const source = audioContext.createMediaStreamSource(mediaStream); - const analyzer = audioContext.createAnalyser(); - const levels = new Uint8Array(analyzer.fftSize); + // we should definitely have an audioTrack at this point unless they denied mic access + if (this.state.audioTrack) { + mediaStream.addTrack(this.state.audioTrack); - source.connect(analyzer); + const AudioContext = window.AudioContext || window.webkitAudioContext; + const audioContext = new AudioContext(); + const source = audioContext.createMediaStreamSource(mediaStream); + const analyzer = audioContext.createAnalyser(); + const levels = new Uint8Array(analyzer.fftSize); - const micUpdateInterval = setInterval(() => { - analyzer.getByteTimeDomainData(levels); + source.connect(analyzer); - let v = 0; + const micUpdateInterval = setInterval(() => { + analyzer.getByteTimeDomainData(levels); - for (let x = 0; x < levels.length; x++) { - v = Math.max(levels[x] - 127, v); - } + let v = 0; + + for (let x = 0; x < levels.length; x++) { + v = Math.max(levels[x] - 127, v); + } - const level = v / 128.0; - this.micLevelMovingAverage.push(Date.now(), level); - this.setState({ micLevel: this.micLevelMovingAverage.movingAverage() }); - }, 50); + const level = v / 128.0; + this.micLevelMovingAverage.push(Date.now(), level); + this.setState({ micLevel: this.micLevelMovingAverage.movingAverage() }); + }, 50); - this.setState({ mediaStream, micUpdateInterval }); + setTimeout(() => { + // TODO DEAL WITH BY REFACTORING THESE METHODS TO PASS IN mediaStream; + const selectedMicDeviceId = this.selectedMicDeviceId(); + + console.log(this.selectedMicLabel()); + console.log("Save " + selectedMicDeviceId); + + if (selectedMicDeviceId) { + this.props.store.update({ lastUsedMicDeviceId: this.selectedMicDeviceId() }); + } + }, 100); + + this.setState({ micUpdateInterval }); + } + + this.setState({ mediaStream }); }; onMicGrantButton = async () => { if (this.state.entryStep == ENTRY_STEPS.mic_grant) { - await this.setMediaStreamToDefault(); - this.setState({ entryStep: ENTRY_STEPS.mic_granted }); + const { hasAudio } = await this.setMediaStreamToDefault(); + + if (hasAudio) { + this.setState({ entryStep: ENTRY_STEPS.mic_granted }); + } else { + await this.beginAudioSetup(); + } } else { - this.startTestTone(); await this.beginAudioSetup(); } }; @@ -376,6 +417,7 @@ class UIRoot extends Component { }; beginAudioSetup = async () => { + this.startTestTone(); await this.fetchMicDevices(); this.setState({ entryStep: ENTRY_STEPS.audio_setup }); }; @@ -409,7 +451,7 @@ class UIRoot extends Component { }; selectedMicDeviceId = () => { - return this.state.micDevices.filter(d => d.label === this.selectedMicLabel).map(d => d.deviceId)[0]; + return this.state.micDevices.filter(d => d.label === this.selectedMicLabel()).map(d => d.deviceId)[0]; }; onAudioReadyButton = () => { @@ -566,11 +608,19 @@ class UIRoot extends Component { </div> <div className="audio-setup-panel__levels"> <div className="audio-setup-panel__levels__mic"> - <img - src="../assets/images/mic_level.png" - srcSet="../assets/images/mic_level@2x.png 2x" - className="audio-setup-panel__levels__mic_icon" - /> + {this.state.audioTrack ? ( + <img + src="../assets/images/mic_level.png" + srcSet="../assets/images/mic_level@2x.png 2x" + className="audio-setup-panel__levels__mic_icon" + /> + ) : ( + <img + src="../assets/images/mic_denied.png" + srcSet="../assets/images/mic_denied@2x.png 2x" + className="audio-setup-panel__levels__mic_icon" + /> + )} <img src="../assets/images/level_fill.png" srcSet="../assets/images/level_fill@2x.png 2x" @@ -592,22 +642,24 @@ class UIRoot extends Component { /> </div> </div> - <div className="audio-setup-panel__device-chooser"> - <select - className="audio-setup-panel__device-chooser__dropdown" - value={this.selectedMicDeviceId()} - onChange={this.micDeviceChanged} - > - {this.state.micDevices.map(d => ( - <option key={d.deviceId} value={d.deviceId}> - {d.label} - </option> - ))} - </select> - <div className="audio-setup-panel__device-chooser__mic-icon"> - <img src="../assets/images/mic_small.png" srcSet="../assets/images/mic_small@2x.png 2x" /> + {this.state.audioTrack && ( + <div className="audio-setup-panel__device-chooser"> + <select + className="audio-setup-panel__device-chooser__dropdown" + value={this.selectedMicDeviceId()} + onChange={this.micDeviceChanged} + > + {this.state.micDevices.map(d => ( + <option key={d.deviceId} value={d.deviceId}> + {d.label} + </option> + ))} + </select> + <div className="audio-setup-panel__device-chooser__mic-icon"> + <img src="../assets/images/mic_small.png" srcSet="../assets/images/mic_small@2x.png 2x" /> + </div> </div> - </div> + )} {this.shouldShowHmdMicWarning() && ( <div className="audio-setup-panel__hmd-mic-warning"> <img diff --git a/src/storage/store.js b/src/storage/store.js index a517bfc57..6f480aa76 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -26,7 +26,8 @@ export const SCHEMA = { properties: { id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, - profile: { $ref: "#/definitions/profile" } + profile: { $ref: "#/definitions/profile" }, + lastUsedMicDeviceId: { type: "string" } }, additionalProperties: false -- GitLab