Using XR Plugin Management and the OpenXR plugin I’m trying to get whether an Oculus Quest 2 headset is mounted or not. I have tried several things:
Use InputDevice.TryGetFeatureValue for feature CommonUsages.userPresence querying for the headset device using InputDevices.GetDeviceAtXRNode(XRNode.Head). This doesn’t give any proper result in the editor nor in the player on the Quest.
Use an OpenXRFeature in which I keep track of the XrSessionState value, and assume XR_SESSION_STATE_FOCUSED is when the user is present. This works with Quest Link in the editor, but in the player on the Quest there is a 15 second delay between the actual unmounting of the headset and the change in session state.
Don’t use the OpenXR plugin loader, but the Oculus plugin loader for Android based devices (probably need another loader for other than Meta devices). This works, using the first method.
My question is: is there a way to retrieve the Oculus userPresence state with the OpenXR plugin, or is using the Oculus XR plugin the only option?
There’s ongoing discussions within the OpenXR working group to get user presence to be part of the spec. Right now it’s not so we infer based on session state I think but it’s not exactly user presence as you’ve found. We’ve seen other developers get close by inferring with more signals such as timing when the headset isn’t moving.
Thanks for the link. I have used most of the suggestions in that thread, and it seems there is currently no viable option other than to use the Oculus XR loader instead of the OpenXR loader. That’s unfortunate, but I can work with that.
Yeah I think the Oculus XR loader uses a private extension that exposes the sensor state which we don’t have access to … I’ll keep pushing the working group on this missing signal and get it in the spec.
Hey there @thep3000 , I was searching for a similar solution and Google sent me to this thread. Appreciate you looking into it! Any update about a standard way in OpenXR to determine whether the headset is currently being worn or not?
I have a similar use-case where I am building a high-throughput experience where the app needs to auto-reset when the headset is removed, and do an auto-recenter once the headset is worn (driven by my own C# logic). Currently, on an older version of Unity, this works great using the native Oculus SDK which allows me to check whether or not the headset is worn. But soon I’ll be upgrading the project it to the newest LTS version of Unity and converting everything to OpenXR, so I need to have feature parity via OpenXR.
The OpenXR working group is currently having a quarterly meetup and we discussed this issue today coincidentally. I reiterated the importance and even linked them to this thread. I think we’ve all reached agreement and should be merging this functionality to the spec very soon and it will make its way out to runtimes and we’ll hook up our Unity implementation of it. When we have our implementation I’ll share details here.
Thank you so much @thep3000 (and anyone else advocating for & contributing to this feature)!
Our team greatly appreciates it, since knowing when the headset is removed and then worn again is utterly essential for high-throughput VR installations, where I need the app to reset itself (when removed from the user’s head) and then begin the experience again as soon as the headset is disinfected and worn by the next person.
It is also a nice feature for other things, such as fading out background music when the headset is removed. The app TRIPP does this, and it really adds a nice touch.
Thank you, I’ve been fighting with this all day today and just came across this thread.
Any idea what a possible time frame might be and if there is a workaround in the meantime?
Can’t use OVRManager since I’m trying to make it multiplatform…
XR_EXT_user_presence was made available as a multi-vendor extension with the latest release of the Open XR SDK. It will be available through the existing CommonUsages.userPresence value. We are targeting the next Unity OpenXR package release which will be version 1.11.0.
We are aware that updating this value will affect existing apps, so we are working on a solution that will not impact existing application functionality.
I made a very crude hack that seems to work for my game; Arthur Owl’s Word Block. It is based on the observation that when I removed my headset, the image on the screen froze. The Update calls didn’t stop however, so I made a test in OnRenderObject that works out the difference between the two and if over a small buffer amount different it fires the relevant events.
I’ll post the code I use below, but it won’t work as I’ve posted it since it depends on a few other things alongside it. You’ll have to adapt it for your own usage as you see fit, but the core detection bits should still work fine.
Notes:
You don’t have com.deeperbeige.lib. That’s a private library I developed myself that contains lots of common useful things.
I use a custom event system from that library. It’s just based on strings, rather than ‘proper’ events. I’ve been using this instead of the proper events for so long I can’t even remember why it’s my preferred system. But you can intuit the meanings easily enough I suspect.
I just put this script on a prefab and include it in every scene. That way I can just listen for the events on anything that needs to know.
Controls is another part of that library. It reads from the various VR plugins and translates all the weirdness into sensible named bools. Controls.inst.headsetWorn is just coming from the XR CommonUsages.userPresence since that actually still works in some rare cases.
You can query the current state via a reference to the HeadsetWorn gameobject and the getter GetHeadsetWorn. Or you can just listen to the events.
The buffer size I found worked well in testing is the default 10 frames. Meaning you’ll wait roughly 10 frames before being notified of the headset worn state changing.
I tested this with an original Vive, an HP1000, a Rift CV1, Quest 3 via Link and Quest 2 via Link. I also tested with Windows MR, Oculus and Steam VR OpenXR runtimes. It’s reasonably complicated so that it works with all the test cases.