Search code examples
iphoneajaxwebrtcmobile-safarigetusermedia

WebRTC snapshot from webcam and save it to server in PHP : iPhone Safari crashes when resolution higher than 2096


I have a WORKING code to take a snapshot from the webcam and save it in PHP via ajax... but when I try with a resolution higher than 2000, safari mobile on iPhone crashes, why and how to fix this ?

here , If I do getUserMedia({ video:{width: { ideal: 2096 => it's all ok

But if I fo getUserMedia({ video:{width: { ideal: 3096 => it crashes :(

HTML

<video id="precam" playsinline="true" muted autoplay style="max-width:90%;" ></video>
<canvas id="canvax" ></canvas>
<div onclick="sendToServer('ajaxanswer');">SAVE</div>
<div onclick="webcamFRONT();">SWITCH TO FRONT CAM</div>
<div id="ajaxanswer" ></div>

JAVASCRIPT

<script>
const videoPlayer = document.querySelector("#precam"); 
const canvas = document.querySelector("#canvax");

function webcamREAR () {
    navigator.mediaDevices.getUserMedia({ video:{width: { ideal: 2096 },facingMode: "environment"}, audio:false })
        .then(stream => videoPlayer.srcObject = stream)
        .catch(error => {console.error(error);});
}

function webcamFRONT () {
    navigator.mediaDevices.getUserMedia({ video:{width: { ideal: 2096 },facingMode: "user"}, audio:false })
        .then(stream => videoPlayer.srcObject = stream)
        .catch(error => {console.error(error);});
}
        
function wcanvasim () { 
    canvas.width = videoPlayer.videoWidth; canvas.height = videoPlayer.videoHeight;
    canvas.getContext("2d").drawImage(videoPlayer, 0, 0, canvas.width, canvas.height);
}

webcamREAR();

function sendToServer (divID) {
    var pcache = (Math.floor(Math.random() * 100000000) + 1);
    var params = "divID="+encodeURIComponent(divID)+"&canvablob="+encodeURIComponent(canvas.toDataURL());
    var xhr = new XMLHttpRequest(); xhr.open("POST", "/file.php?pcache="+pcache, true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function(e) { if (xhr.readyState == 4) { $("#"+divID).html(e.currentTarget.responseText) ; } }
    xhr.send(params);
}
</script>

PHP

<?php

$whereTOdeBlob = '/path/to/server/'.mt_rand().'.png';
$canvablob = ( $_REQUEST['canvablob'] ?? '' ) ;
$canva64 = explode('base64,',$canvablob)[1] ?? '';
file_put_contents(INCLUDE_PATH_ROOT.$whereTOdeBlob,base64_decode($canva64));

// CONVERT TO JPG
// ADD TO DATABASE
// ANY STUFF YOU WANT ;)

echo '<img src="'.$canvablob.'" />';

?>

******* The question is : why large resolution makes safari mobile crash?


Solution

  • You use .toDataURL() to encode that huge image as a lossless .png file. That probably generates a multimegabyte string that you try to POST. Somewhere along the way Safari probably runs out of contiguous RAM and gacks, either on allocation or garbage collection. It's also likely your POST's payload size exceeds php's maximum.

    Try .toDataURL('image/jpeg', 0.3) to get a low-quality JPEG image, which will generate a much smaller string. If that works, crank up the quality (0.3 to 0.4, 0.5, etc).

    Or, better, convert your canvas to a binary Blob, then post that. It will take less space than a data URL. Something like this. Not, repeat not, debugged.

    function sendToServer (divID) {
        var pcache = (Math.floor(Math.random() * 100000000) + 1)
        canvas.toBlob (function (blob) {
          var xhr = new XMLHttpRequest()
          xhr.open("POST", "/file.php?pcache="+pcache, true);
          xhr.onreadystatechange = function (e) { 
            if (xhr.readyState == 4) 
               $("#"+divID).html(e.currentTarget.responseText) 
          }
    
          var fd = new FormData();
          fd.append('divID', divID)
          fd.append('canvaBlob', blob)
          xhr.send(fd)
        }, 'image/jpeg', 0.8)
    }
    

    Also, try constraining the height of the image as well as the width. If your camera is in portrait mode and you ask for a 2K width you may get a 4K height if you let .getUserMedia() choose whatever height it wants; it obeys a default aspect ratio.

    Finally, in a practical sense images at so high a resolution usually have to be downsampled before you use them. Lower-resolution JPEG images usually meet practical requirements. So simply capture lower-resolution images. (If you're a photographer making photos to print in glossy magazines, you already know about the limitations of iPhone cameras.)