7

I'm trying to listen to click events (To be exact, magnet pull from google cardboard) on Android Chrome, but this seems to not work if the device goes into VR mode. I'm using Samsung Galaxy S7 for testing:

JS:

window.addEventListener('click', function (evt) {
    console.log("test")
});

On Samsung's built in Android browser, the log is printed both in and out of VR mode. In Android Chrome, logging is only shown when the browser is not in VR mode.

HTML:

<a-entity camera="userHeight: 1.6" restrict-position look-controls>
    <a-entity cursor="fuse: true; fuseTimeout: 500"
                position="0 0 -1"
                geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
                material="color: black; shader: flat">
    </a-entity>
</a-entity>

I'm using A-Frame ver 0.7.0, but this issue is reproducible just from using native WebVR APIs as well.

I thought the canvas might be consuming the click events, so I tried to add the eventlistener to Canvas directly. This also did not work.

Is this a bug in Chrome? Is there any workaround? I just need to be able to listen to button presses.

TtT23
  • 6,876
  • 34
  • 103
  • 174

2 Answers2

3

I've finally found an approach that works. We have to use native WebVR APIs:

function addVRClickListener(clickCallback) {
    let lastButtonState = [];
    let presentingDisplay = null;

    // Set up a loop to check gamepad state while any VRDisplay is presenting.
    function onClickListenerFrame() {
      // Only reschedule the loop if a display is still presenting.
      if (presentingDisplay && presentingDisplay.isPresenting) {
        presentingDisplay.requestAnimationFrame(onClickListenerFrame);
      }
      
      let gamepads = navigator.getGamepads();
      for (let i = 0; i < gamepads.length; ++i) {
        let gamepad = gamepads[i];
        // Ensure the gamepad is valid and has buttons.
        if (gamepad &&
            gamepad.buttons.length) {
          let lastState = lastButtonState[i] || false;
          let newState = gamepad.buttons[0].pressed;
          // If the primary button state has changed from not pressed to pressed 
          // over the last frame then fire the callback.
          if (newState && !lastState) {
            clickCallback(gamepad);
          }
          lastButtonState[i] = newState;
        }
      }
    }

    window.addEventListener('vrdisplaypresentchange', (event) => {
      // When using the polyfill, CustomEvents require event properties to
      // be attached to the `detail` property; native implementations
      // are able to attach `display` directly on the event.
      var display = event.detail ? event.detail.display : event.display;
      if (display.isPresenting) {
        let scheduleFrame = !presentingDisplay;
        presentingDisplay = display;
        if (scheduleFrame)
          onClickListenerFrame();
      } else if (presentingDisplay == display) {
        presentingDisplay = null;
      }
    });
  }

  

Then we can register the click:

  addVRClickListener(onClick);

The problem I see in A-frame's code is that it's not accounting for display change (non vr -> vr) for propagating the touch events.

This seems to be a bug in A-Frame.

Kyle Baker
  • 3,424
  • 2
  • 23
  • 33
TtT23
  • 6,876
  • 34
  • 103
  • 174
  • Did this actually work for you? I can't tell if chrome used to support a cardboard button and has removed it, or if it ever did work. My cardboard button works perfectly fine in android apps, but does not register any event (and the gamepad is a list of 4 empty spaces in the function you gave above) in chrome vr mode. in vr mode, touch events are disabled as well. – Kyle Baker Jun 27 '20 at 12:35
  • (just wanted to add, I'm not using the magnetic button, I'm using a touch-based one, which should rule out a lot of complications) – Kyle Baker Jun 28 '20 at 03:29
1

This sounds related to a few issues I've seen with the magnetic button.

First thing to try could be touchstart instead of click:

You can try adding this component to the scene and the see if it works:

<script>
AFRAME.registerComponent("click-handler", {
    init: function() {
        const sceneEl = this.el.sceneEl
        const canvasEl = sceneEl.canvas    
        canvasEl.addEventListener('touchstart', () => {
            // code here
        })
    }
});
</script>

<a-scene click-handler>...</a-scene>

More context in this thread: aframe cardboard button (magnet) click does not trigger

Next if that doesn't work, then perhaps the chrome flag and this thread could help:

Detect Google Cardboard Magnetic Button Click in Javascript

chrome://flags/#enable-generic-sensor

chrome://flags/#enable-generic-sensor-extra-classes

Noam Almosnino
  • 866
  • 1
  • 6
  • 8
  • I've tried all of the suggestions.. and here's the problem I've found: 1. Listening for "click-handler" does not work. No events are emitted. 2. The sensor values read from Magnetosphere is super inaccurate. For instance, just tilting your phone in any X-Y-Z axis produces extraneous values because it's trying to read the magnetic field in all coordinates - there's no real way of distinguishing phone movement vs real button press. And more importantly, I still need the user to be able to manually touch the screen for the event to emit. – TtT23 Mar 09 '18 at 02:16
  • Hm, just curious, does using Don's universal controls work with your Cardboard and Android Chrome: https://github.com/donmccurdy/aframe-extras/tree/master/src/controls if not, then that probably leads it to being more of an issue with Chrome and the magnetic button i suspect. – Noam Almosnino Mar 09 '18 at 20:43
  • Tried Don's universal controls, it also doesn't work. Looking at the source code, what it's doing under the scene is listening to `touchstart` event anyways, which is something I've already tried. – TtT23 Mar 12 '18 at 01:56