3

I've got a test page with the following line of HTML5 Media Capture code (and nothing else other than a form submit button):

<input type=file accept=image/* id=capture capture=camera>

On an iPhone 4s with ios 8.1.2 the code only works sometimes. It launches the Take Photo/Photo Library dialog successfully, but it does not reliably accept a new photo image (from the phone camera) for upload. More often than not Safari displays an error message 'something has gone wrong with this page and it has been re-loaded'. Generally, if I clear the cache, close Safari and re-launch, it works again, once or twice, and then fails. Once it has failed, it doesn't seem ever to succeed again without re-launching.

It's not clear if this is a buffer issue, or is even related to the file size of the new photo, but given that it does work sometimes, it doesn't appear to be an error in the code or an incompatibility with the OS/browser.

Anyone experience anything similar? Any suggestions how to make this work?

Thanks

sideroxylon
  • 4,338
  • 1
  • 22
  • 40

2 Answers2

5

The Problem:

I found out the reason for this happening in Safari/iOS is that the main page seems to be "throttled" somehow which means that if the page is a bit heavy CPU/GPU and/or (?) memory wise the <input capture="camera" ...> fails randomly most of the times.

A solution:

My solution on this was to place the <input capture="camera" ...> inside of an <iframe>, sized to fit the input seamlessly. This works because each frame is running in its own process, yet on lower priority than main frame but enough to not be a problem here. It works 100% of the time for me now even in a pretty heavy UI app using a lot of GPU.

index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        #camera-button {
          display: inline-block;
          width: 100px;
          height: 100px;
          background: red;
          border: 5px solid #eee;
          padding: 10px;
          z-index: 2147483647;
        }

        #camera-frame {
          width: 100px;
          height: 100px;
          background-color: transparent;
          opacity: 0;
        }
    </style>
</head>
<body>
    <span id="camera">
      <iframe id="camera-frame" src="camera.html" scrolling="no" frameBorder="0" allowTransparency="true" seamless>
    </span>

    <script>
      (function() {
          window.onCameraChanged = function(event) {
              var files;

              console.log("[index.html]: snap!", arguments);

              if (event.target) {
                  files = event.target.files;

                  console.log("[index.html]: files", files);

                  alert("[index.html]:", files.length, "files from camera.");
              }
          };
      }).call(this);
    </script>
</body>
</html>

camera.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        body,
        html {
          width: 100%;
          height: 100%;
          margin: 0;
          padding: 0;
        }

        #camera-input {
          display: block;
          width: 100%;
          height: 100%;
          position: absolute;
          outline: none;
          box-shadow: none;
          border: none;
        }
    </style>
</head>
<body>
    <input id="camera-input" type="file" capture="camera" accept="image/*" onchange="window.onCameraChanged">

    <script>
      (function() {
          var el;

          window.onCameraChanged = function(event) {
              var files;

              console.log("[camera.html]: snap!", arguments);

              if (event.target) {
                  files = event.target.files;

                  if (window.parent) {
                    if (typeof window.parent.onCameraChanged === 'function') {
                      window.parent.onCameraChanged(event);
                    }

                    return window.parent.cameraFiles = files;
                  }
              }


          if (el = document.querySelector('#camera')) {
            el.onchange = window.onCameraChanged; // chrome
          }

      }).call(this);
    </script>
</body>
</html>

Something like that.

grimen
  • 101
  • 4
  • That looks plausible. In my case, the capture code is added dynamically to a div (with positive z-index) which is faded in over the main page. Same effect as your iframe, but no CPU benefits. There is nothing much else in the div initially - but obviously the main page is still open, so you may well be right. The solution below still works, but obviously only for buffer issues. If the CPU is involved as well or instead, then your code looks like a winner. Thanks – sideroxylon Feb 09 '15 at 22:50
  • We tried a lot of solutions that didn't work, but this worked perfect. Thanks! – pseudosavant Feb 24 '16 at 00:16
1

I will offer this as a tentative solution insomuch as it has not yet failed, but I'm not sure why it's required. When clicking the button to capture an image, the first thing I do now is this:

$('#capture').val('');

That clears the form before adding a new image. I tried reset(), but that didn't work. It would appear therefore that there is an issue with changing the image in the form once it has been added - it needs to be removed first, not simply overwritten.

sideroxylon
  • 4,338
  • 1
  • 22
  • 40