Search Unity

How to request camera access from Safari (Desktop)

Discussion in 'Web' started by Bersaelor, Feb 2, 2021.

  1. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
    Hey there, I created a simple test scene with a screen on a tree that shows the webcamtexture .

    On Chrome I get the "Camera access" popup, so I can give the app access.

    On Safari nothing happens, no popup, no camera showing in my scene.

    The code is real minimal:

    Code (CSharp):
    1.     void Start()
    2.     {
    3.         Debug.Log("WebCamTexture.devices.Length: " + WebCamTexture.devices.Length);
    4.         WebCamTexture webcamTexture = new WebCamTexture();
    5.         Renderer renderer = GetComponent<Renderer>();
    6.         renderer.material.mainTexture = webcamTexture;
    7.         webcamTexture.Play();
    8.     }
    I also published the app, so you can try yourself: https://test.looc.io/forest/index.html

    On Chrome, the log is `WebCamTexture.devices.Length: 1` but on Safari it is `WebCamTexture.devices.Length: 0`.

    What can I do to make Safari ask for the camera access?

    PS: I know when I write plain js code, I have to use `navigate.mediaDevices.getUserMedia`. Is it possible Unity still uses the deprecated `navigator.getUserMedia()`?
    EDIT: Version 2020.2.2f1
     
    Last edited: Feb 2, 2021
  2. jukka_j

    jukka_j

    Unity Technologies

    Joined:
    May 4, 2018
    Posts:
    953
    The new Camera backend has not made its way to 2020.2.2f1 release I believe. Try out Unity 20201.1.0b4 beta and see if that resolves the issue?
     
    Bersaelor likes this.
  3. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
  4. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
    I was looking into the "webgl-1.framework.js" that is created in the `Build` folder.

    And I think I found one culprit, in the below method `enumerateMediaDevices` it will early exit on Safari:

    Code (JavaScript):
    1.  var MediaDevices = [];
    2.     if (typeof ENVIRONMENT_IS_PTHREAD === "undefined" || !ENVIRONMENT_IS_PTHREAD) {
    3.         Module["preRun"].push(function () {
    4.             var enumerateMediaDevices = function () {
    5.                 var getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    6.  
    7.                 console.log("getMedia: " + getMedia); // <-- added by me
    8.  
    9.                 if (!getMedia) return;
    10.                 function addDevice(label) {
    11.                     label = label ? label : "device #" + MediaDevices.length;
    12.                     var device = { deviceName: label, refCount: 0, video: null };
    13.                     MediaDevices.push(device);
    14.                 }
    15.                 if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
    16.                     if (typeof MediaStreamTrack == "undefined" || typeof MediaStreamTrack.getSources == "undefined") {
    17.                         console.log("Media Devices cannot be enumerated on this browser.");
    18.                         return;
    19.                     }
    20.                     function gotSources(sourceInfos) {
    21.                         for (var i = 0; i !== sourceInfos.length; ++i) {
    22.                             var sourceInfo = sourceInfos[i];
    23.                             if (sourceInfo.kind === "video") addDevice(sourceInfo.label);
    24.                         }
    25.                     }
    26.                     MediaStreamTrack.getSources(gotSources);
    27.                 }
    28.                 navigator.mediaDevices
    29.                     .enumerateDevices()
    30.                     .then(function (devices) {
    31.                         devices.forEach(function (device) {
    32.                             if (device.kind == "videoinput") addDevice(device.label);
    33.                         });
    34.                     })
    the line `var getMedia = navigator.getUserMedia` is null/undefined on Safari and will early abort the method `enumerateMediaDevices`.
     

    Attached Files:

  5. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
    Digging deeper into this (after fixing the early exit), there might be another issue related to privacy settings in Safari. The class MediaDeviceInfo has a deviceId and a label variable, yet unless you specifically allow safari access to your camera both of those variables will be empty. Compare this github issue on an unrelated project
    Maybe Unity is in fact trying to enumerate the devices before calling the method that would trigger Safari to ask the user to allow access and since the variables are undefined the method:
    Code (JavaScript):
    1. if (device.kind == "videoinput") addDevice(device.label);
    from webgl-1.framework.js will add undefined.
    Check out the following screenshot, on Chrome the label of my camera is `FaceTime HD Camera (Built-in) (05ac:8511)` on Safari the label gets created to be `device #0`.
    Thing is, on Chrome, Unity seems to call the `_JS_WebCamVideo_GetNumDevices`
    and gets a `1` back. In Safari the js method `_JS_WebCamVideo_GetNumDevices` doesn't seem to be called at all. The c# Start() method above calls "Debug.Log("WebCamTexture.devices.Length: " in safari the log says `0`, which is weird given that MediaDevices clearly now has one entry, after the path to the if-clause from my previous post.
    I'm out of ideas for now how to proceed.
     

    Attached Files:

    Last edited: Feb 2, 2021
  6. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
    PS: The build.framework.js file created by 2020.2.2f1 and the code discussed here look the same, with the same results.
    PPS: I also noticed after `Input Manager initialize...` on Chrome the method "_JS_WebCamVideo_GetNumDevices" is called when the `WebCamTexture.devices.Length` is accessed from inside unity. In Safari, the log in "_JS_WebCamVideo_GetNumDevices" never triggered even though `WebCamTexture.devices.Length` is called.
    The code inside unity that sits between my Monobehaviour component and this js code isn't accessible, but I recon something is happening in there that prevents the methods `_JS_WebCamVideo_GetNumDevices`, `_JS_WebCamVideo_GetDeviceName` and `_JS_WebCamVideo_Start` from being called.

    PPPS:
    If I do drop the code from the `_JS_WebCamVideo_Start` method:
    Code (JavaScript):
    1.     navigator.getMedia = function (constraints, success, error) {
    2.         navigator.mediaDevices.getUserMedia(constraints).then(success).catch(error);
    3.     };
    4.     var video = document.createElement("video");
    5.     navigator.getMedia(
    6.         { video: true, audio: false },
    7.         function (stream) {
    8.             video.srcObject = stream;
    9.             // webcam.canvas.appendChild(video);
    10.             video.play();
    11.             MediaDevices[0].video = video;
    12.             MediaDevices[0].stream = stream;
    13.             MediaDevices[0].refCount++;
    14.         },
    15.         function (err) {
    16.             console.log("An error occurred! " + err);
    17.         }
    18.     );    
    into the preRun - enumerateMediaDevices method, it in fact triggers the Safari-Video-Permission-Popup, so we just need a way of making Unity call _JS_WebCamVideo_Start when running in Safari.
     
    Last edited: Feb 2, 2021
  7. jukka_j

    jukka_j

    Unity Technologies

    Joined:
    May 4, 2018
    Posts:
    953
    Wait, that does not look right at all. In the updated Webcam implementation, the line

    var getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;

    should not appear at all. The function `navigator.getUserMedia` is old and deprecated web API that existed at the time that the initial webcam backend was written, but is no longer there. `navigator.mediaDevices` is the new API. All of that JS code that you have been looking is gone, and been replaced with a new rewritten version.

    Sigh, now looking at our code tree, the webcam rewrite has landed to trunk on 12th of December 2020, but has not made its way into a release yet. It is due to land to 2020.2 and 2019.4 but those have not progressed. My apologies for bad information and causing you to waste time. I'll try to hurry the PRs along.
     
    Bersaelor likes this.
  8. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
    Thank you @jukka_j for clarifying though, at least I know that help is on the way :)
     
  9. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
  10. jukka_j

    jukka_j

    Unity Technologies

    Joined:
    May 4, 2018
    Posts:
    953
    Great, thanks for confirming! I am puzzled by the difference between 2021.1 and 2021.2, they should now be running with the same code. I'll dig through the commit history to see if one of the webcam commits are missing a backport.
     
  11. Bersaelor

    Bersaelor

    Joined:
    Oct 8, 2016
    Posts:
    111
  12. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547