diff --git a/package.json b/package.json index 03ae8e86c0439d1748ed66115b4289d50ab7d7a2..f9dd871bfc08810ed727d4ccefd7b483ba50f0b3 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "aframe-input-mapping-component": "https://github.com/johnshaughnessy/aframe-input-mapping-component#23e2855", "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin", "aframe-xr": "github:brianpeiris/aframe-xr#3162aed", + "detect-browser": "^2.1.0", "material-design-lite": "^1.3.0", "minijanus": "^0.4.0", "naf-janus-adapter": "^0.3.0", diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index a71e4e886abb9813d0b29cea868887ca60d17474..0e46ace485799f2f602907e17b2f439d57bac2f8 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { getPreEntryMobileVRDeviceCaps } from "../utils/vr-caps-detect.js" class UIRoot extends Component { static propTypes = { @@ -7,8 +8,7 @@ class UIRoot extends Component { }; componentDidMount = () => { - if (navigator.getVRDisplays()) { - } + getPreEntryMobileVRDeviceCaps().then(x => console.log(x)) } render() { diff --git a/src/utils/vr-caps-detect.js b/src/utils/vr-caps-detect.js new file mode 100644 index 0000000000000000000000000000000000000000..4013e1bbc12a82439c6a55a45c12d158f98c4cca --- /dev/null +++ b/src/utils/vr-caps-detect.js @@ -0,0 +1,80 @@ +const { detect } = require("detect-browser"); +const browser = detect(); + +// Precision on device detection is fuzzy -- we can sometimes know if a device is definitely +// available, or definitely *not* available, and assume it may be available otherwise. +const VR_DEVICE_AVAILABILITY = { + yes: "yes", // Implies VR can be launched into on this device immediately, in this browser + no: "no", // Implies this VR device is definitely not supported regardless of browser + maybe: "maybe" // Implies this device may support this VR device, but may not be installed or in the right browser +}; + +function hasPhysicalScreenDimensions(w, h) { + const dpr = window.devicePixelRatio; + const width = screen.width * dpr; + const height = screen.height * dpr; + + // Compensate for rounding error due to fractional pixel densities + return Math.abs(w - width) < 3 && Math.abs(h - height) < 3; +} + +function matchesScreenSizesAndUserAgentRegex(sizes, regex) { + return !!(sizes.find(s => hasPhysicalScreenDimensions(...s)) && navigator.userAgent.match(regex)); +} + +function isGearVRCompatibleDevice() { + // Modern Samsung Galaxy devices have model numbers in the user agent of the form SX-XXXX + return matchesScreenSizesAndUserAgentRegex([[1440, 2560], [1440, 2960]], /\WS.-.....?\W/); +} + +function isGooglePixelPhone() { + return matchesScreenSizesAndUserAgentRegex([[1080, 1920], [1440, 2560]], /\WPixel\W/); +} + +function isKnownDaydreamCompatibleDevice() { + // Samsung S6, S7, and Note 5 do not support Daydream, but other GearVR compatiable devices + // do. Instead of doing fine-grained model detection we will just assume they are all compatible. + if (isGearVRCompatibleDevice()) return true; + if (isGooglePixelPhone()) return true; + + // Note this is non-exhaustive -- this function may return false for compatible devices. + return false; +} + +// Captures the potential ways a user can launch into mobile VR *before* a headset is attached to the phone. +// Tries to determine VR platform compatibility regardless of the current browser. +// +// For each mobile VR platform, returns "yes" if that platform can be launched into directly from this browser +// on this device, returns "no" if that platform cannot be supported on any browser, and "maybe" if the +// device potentially could support that platform if a different browser was running. +export async function getPreEntryMobileVRDeviceCaps() { + const isWebVREnabled = !!navigator.getVRDisplays; + const isWebVREnabledChrome = !!(isWebVREnabled && browser.name === "chrome"); + const isAndroidCompatible = + navigator.userAgent.match(/\Wandroid\W/i) && !navigator.userAgent.match(/\Wwindows phone\W/i); + + // We only consider GearVR hardware support as "maybe" and never return "yes" for GearVR. The only browser + // that will detect GearVR outside of VR is Samsung Internet, and we'd prefer to launch into Oculus + // Browser for now since Samsung Internet requires an additional WebVR installation + flag. + const gearvr = isGearVRCompatibleDevice() ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; + + // For daydream detection, we first check if they are on an Android compatible device, and assume they + // may support daydream *unless* this browser has WebVR capabilities, in which case we can do better. + let daydream = isAndroidCompatible ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; + + if (isWebVREnabled) { + // For daydream detection, if this is a WebVR browser we can increase confidence in daydream compatibility. + const displays = await navigator.getVRDisplays(); + const hasDaydreamWebVRDevice = displays.find(d => d.displayName.match(/\Wdaydream\W/i)); + + if (hasDaydreamWebVRDevice) { + // If we detected daydream via WebVR + daydream = VR_DEVICE_AVAILABILITY.yes; + } else if (isWebVREnabledChrome || !isKnownDaydreamCompatibleDevice()) { + // If we didn't detect daydream in chrome (which is known to detect it) and we are on a known compatible device + daydream = VR_DEVICE_AVAILABILITY.no; + } + } + + return { gearvr, daydream }; +} diff --git a/yarn.lock b/yarn.lock index 6ef0aefe1bdce4a08646a63e22a0957c5040c677..b27d10b90e3d20263df87e3177517fec3730c9b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2136,6 +2136,10 @@ destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" +detect-browser@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-2.1.0.tgz#df35462901dfd92b8f37c2fa457d6e1f57b5e8eb" + detect-conflict@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/detect-conflict/-/detect-conflict-1.0.1.tgz#088657a66a961c05019db7c4230883b1c6b4176e"