diff --git a/scripts/default.env b/scripts/default.env index 6a39a2127153c467bb2dac5e90b6cf485101d0e0..82a69026a22a8eb2ad6c1da4c048095d8e3e3624 100644 --- a/scripts/default.env +++ b/scripts/default.env @@ -1,5 +1,5 @@ # This origin trial token is used to enable WebVR and Gamepad Extensions on Chrome 62+ # You can find more information about getting your own origin trial token here: https://github.com/GoogleChrome/OriginTrials/blob/gh-pages/developer-guide.md ORIGIN_TRIAL_TOKEN="AvIMoF4hyRZQVfSfksoqP+7qzwa4FSBzHRHvUyzC8rMATJVRbcOiLewBxbXtJVyV3N62gsZv7PoSNtDqqtjzYAcAAABkeyJvcmlnaW4iOiJodHRwczovL3JldGljdWx1bS5pbzo0NDMiLCJmZWF0dXJlIjoiV2ViVlIxLjFNNjIiLCJleHBpcnkiOjE1MTYxNDYyMDQsImlzU3ViZG9tYWluIjp0cnVlfQ==", -ORIGIN_TRIAL_EXPIRES="2018-01-16", -JANUS_SERVER="wss://dev-janus.reticulum.io" \ No newline at end of file +ORIGIN_TRIAL_EXPIRES="2018-05-15", +JANUS_SERVER="wss://dev-janus.reticulum.io" diff --git a/src/assets/environments/cliff_meeting_space/bundle.json.tpl b/src/assets/environments/cliff_meeting_space/bundle.json.tpl index cb321614109330cfb9d010a899c215bd740c27b2..6c54054858cec5ac982debae6351bfd6fa1664da 100644 --- a/src/assets/environments/cliff_meeting_space/bundle.json.tpl +++ b/src/assets/environments/cliff_meeting_space/bundle.json.tpl @@ -18,9 +18,6 @@ { "name": "outdoor-geometry", "src": "<%= require("./OutdoorFacade_mesh.glb") %>" }, - { - "name": "collision", "src": "<%= require("./FloorNav_mesh.glb") %>" - }, { "name": "cliff-geometry", "src": "<%= require("./CliffVista_mesh.glb") %>" } diff --git a/src/components/in-world-hud.js b/src/components/in-world-hud.js index 2c10aad240d6ee2d21ea8d536751b0fc3785a463..aa7e8aae756207dc05a640f7d02f6c0f728b41ca 100644 --- a/src/components/in-world-hud.js +++ b/src/components/in-world-hud.js @@ -7,8 +7,18 @@ AFRAME.registerComponent("in-world-hud", { this.bg = this.el.querySelector(".bg"); this.mic = this.el.querySelector(".mic"); this.nametag = this.el.querySelector(".username"); + this.avatar = this.el.querySelector(".avatar"); this.nametag.object3DMap.text.material.depthTest = false; - this.data.raycaster.components.line.material.depthTest = false; + this.nametag.object3DMap.text.renderOrder = 1; + this.mic.object3DMap.mesh.renderOrder = 1; + this.avatar.object3DMap.mesh.renderOrder = 1; + + const line = this.data.raycaster.object3DMap.line; + line.renderOrder = 2; + line.material.depthTest = false; + // Set opacity to 0.99 to force the line to render in the + // transparent pass, so that it renders on top of the HUD + this.data.raycaster.setAttribute("line", "opacity", 0.99); const muted = this.el.sceneEl.is("muted"); this.mic.setAttribute("src", muted ? "#muted" : "#unmuted"); diff --git a/src/hub.html b/src/hub.html index 4bdffba3ba10d3a0f24572970553393a0d178712..a18d5159b09230df5eca4a3a42df5fabac8c74c1 100644 --- a/src/hub.html +++ b/src/hub.html @@ -262,7 +262,7 @@ tracked-controls teleport-controls="cameraRig: #player-rig; teleportOrigin: #player-camera; button: action_teleport_" haptic-feedback - raycaster="objects:.hud; showLine: true;" + raycaster="objects:.hud; showLine: true; far: 2;" cursor="fuse: false; downEvents: action_ui_select_down; upEvents: action_ui_select_up;" app-mode-toggle-playing__teleport-controls="mode: hud; invert: true;" diff --git a/src/input-mappings.js b/src/input-mappings.js index a5338250079c08c94d79edb84457ff4e3b4e7b65..035110dd677efa748c280ae0d6b8f14fde1ae6f4 100644 --- a/src/input-mappings.js +++ b/src/input-mappings.js @@ -31,14 +31,19 @@ const config = { mappings: { default: { "vive-controls": { - menudown: "action_mute", + menudown: ["action_mute", "thumb_down"], + menuup: "thumb_up", "trackpad.pressedmove": { left: "move" }, trackpad_dpad4_pressed_west_down: { right: "snap_rotate_left" }, trackpad_dpad4_pressed_east_down: { right: "snap_rotate_right" }, trackpad_dpad4_pressed_center_down: { right: "action_teleport_down" }, trackpadup: { right: "action_teleport_up" }, - gripdown: "action_grab", - gripup: "action_release" + gripdown: ["action_grab", "middle_ring_pinky_down", "index_down"], + gripup: ["action_release", "middle_ring_pinky_up", "index_up"], + trackpadtouchstart: "thumb_down", + trackpadtouchend: "thumb_up", + triggerdown: "index_down", + triggerup: "index_up" }, "oculus-touch-controls": { joystick_dpad4_west: { diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index ce8c569c13742f2ca18877914e186cb1c6a160b1..83ddd1177f5be71efe796f02043bdceedb2cc6cd 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,19 @@ class UIRoot extends Component { }; setMediaStreamToDefault = async () => { - await this.fetchAudioTrack({ audio: true }); + let hasAudio = false; + const { lastUsedMicDeviceId } = this.props.store.state; + + // Try to fetch last used mic, if there was one. + if (lastUsedMicDeviceId) { + hasAudio = await this.fetchAudioTrack({ audio: { deviceId: { ideal: lastUsedMicDeviceId } } }); + } else { + hasAudio = await this.fetchAudioTrack({ audio: true }); + } + await this.setupNewMediaStream(); + + return { hasAudio }; }; setStateAndRequestScreen = async e => { @@ -322,51 +330,75 @@ 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); + await this.fetchMicDevices(); 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 micDeviceId = this.micDeviceIdForMicLabel(this.micLabelForMediaStream(mediaStream)); + + if (micDeviceId) { + this.props.store.update({ lastUsedMicDeviceId: micDeviceId }); } - const level = v / 128.0; - this.micLevelMovingAverage.push(Date.now(), level); - this.setState({ micLevel: this.micLevelMovingAverage.movingAverage() }); - }, 50); + this.setState({ micUpdateInterval }); + } - this.setState({ mediaStream, 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,14 +408,22 @@ class UIRoot extends Component { }; beginAudioSetup = async () => { - await this.fetchMicDevices(); + this.startTestTone(); this.setState({ entryStep: ENTRY_STEPS.audio_setup }); }; - fetchMicDevices = async () => { - const mediaDevices = await navigator.mediaDevices.enumerateDevices(); - this.setState({ - micDevices: mediaDevices.filter(d => d.kind === "audioinput").map(d => ({ deviceId: d.deviceId, label: d.label })) + fetchMicDevices = () => { + return new Promise(resolve => { + navigator.mediaDevices.enumerateDevices().then(mediaDevices => { + this.setState( + { + micDevices: mediaDevices + .filter(d => d.kind === "audioinput") + .map(d => ({ deviceId: d.deviceId, label: d.label })) + }, + resolve + ); + }); }); }; @@ -399,17 +439,20 @@ class UIRoot extends Component { return !!this.state.micDevices.find(d => HMD_MIC_REGEXES.find(r => d.label.match(r))); }; + micLabelForMediaStream = mediaStream => { + return (mediaStream && mediaStream.getAudioTracks().length > 0 && mediaStream.getAudioTracks()[0].label) || ""; + }; + selectedMicLabel = () => { - return ( - (this.state.mediaStream && - this.state.mediaStream.getAudioTracks().length > 0 && - this.state.mediaStream.getAudioTracks()[0].label) || - "" - ); + return this.micLabelForMediaStream(this.state.mediaStream); + }; + + micDeviceIdForMicLabel = label => { + return this.state.micDevices.filter(d => d.label === label).map(d => d.deviceId)[0]; }; selectedMicDeviceId = () => { - return this.state.micDevices.filter(d => d.label === this.selectedMicLabel).map(d => d.deviceId)[0]; + return this.micDeviceIdForMicLabel(this.selectedMicLabel()); }; onAudioReadyButton = () => { @@ -566,11 +609,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 +643,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 a517bfc5749c53eb9a193298194f7ed7fa0ee56f..6f480aa76db6f14904b611cecf5b0b6b84b1f41f 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 diff --git a/src/telemetry.js b/src/telemetry.js index 2a738593e98541d97e236a0a70d28619b4b10925..6eed4d002b96c507dcdaea78b6a5ab58f9791441 100644 --- a/src/telemetry.js +++ b/src/telemetry.js @@ -1,5 +1,7 @@ import Raven from "raven-js"; export default function registerTelemetry() { - Raven.config("https://f571beaf5cee4e3085e0bf436f3eb158@sentry.io/256771").install(); + if (process.env.NODE_ENV === "production") { + Raven.config("https://f571beaf5cee4e3085e0bf436f3eb158@sentry.io/256771").install(); + } }