контекст drawImage / canvas toDataURL значительно отстает в Opera Mobile

У меня есть немного Javascript, который работает на планшете Android под управлением Opera Mobile 12. Планшет прикреплен к стене в офисе, поэтому он используется всеми, кто работает в этом офисе, в качестве системы хронометража (т. Е. Они используют его для того, чтобы приходить / приходить на работу). Этот javascript делает фотографию пользователя при возникновении определенного события, а затем преобразует эту фотографию в URL-адрес данных, чтобы ее можно было отправить на сервер. Однако все это выполняется в фоновом режиме - для элементов video и canvas установлено значение display:none;.

Вот код, который обрабатывает взаимодействие с веб-камерой:

var Webcam = function() {
  var video = document.getElementById('video');
  var stream_webcam = function(stream) {
    video.addEventListener('loadedmetadata', function(){
      video.play();
    }, false);
    video.src = window.URL.createObjectURL(stream);
  }

  // start recording
  if (navigator.getUserMedia)
    navigator.getUserMedia({video: true}, stream_webcam);
  else
    _error("No navigator.getUserMedia support detected!");

  var w = {};

  w.snap = function(callback) {
    var canvas = document.getElementById('canvas');
    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
    // delay is incurred in writing to canvas on opera mobile
    setTimeout(function() {
      callback(canvas.toDataURL());
    }, 1);
  };

  return w;
}

w.snap(callback) вызывается, когда пользователь нажимает определенную кнопку. Проблема в том, что существует значительная задержка между вызовом drawImage и изображением, фактически нарисованным на холсте. Возникает следующая проблема:

  • Пользователь 1 пользуется планшетом, нажимает кнопку, его фото "сфотографировано" и отправлено на сервер. Сервер получает пустое прозрачное изображение. (Это не то же самое, что холст html5 toDataURL возвращает пустое изображение - данные отправка на сервер существенно меньше для первого изображения, потому что оно пустое)
  • Через несколько минут пользователь 2 использует планшет и нажимает кнопку, и его фотография «сфотографирована». На сервер отправляется фотография пользователя 1.
  • Позже пользователь 3 делает то же самое, и сервер получает фотографию пользователя 2.

Итак, я предполагаю, что вызов toDataURL() возвращает старые данные, потому что drawImage() - нет. (Я где-то читал, что это асинхронно, но я не могу подтвердить и не могу найти способ прикрепить событие к моменту завершения рисования.)

Я пробовал:

  • изменение тайм-аута в snap() на различные значения. Самый высокий, который я пробовал, был 2000, но возникла та же проблема. (Если кто-то предлагает более высокие числа, я рад попробовать их, но я не хочу подниматься намного выше, потому что, если я перейду слишком высоко, у пользователя 3 есть возможность сфотографироваться до того, как я обработал фотографии пользователя 2 фото, а значит, я могу его полностью потерять!)
  • когда холст рисовал что-то при первой загрузке страницы, но это не исправляло - когда пользователь 1 был сфотографирован, сервер получал любую фотографию, сделанную при загрузке страницы, вместо фотографии пользователя 1.
  • "получение" элемента холста снова, используя getElementById, перед вызовом toDataURL.

Еще несколько примечаний:

  • Это отлично работает на моем компьютере (macbook air) в Chrome и Opera.
  • Не могу надежно реплицировать с помощью отладчика (привязка к планшету с помощью Opera Dragonfly).
  • AFAIK Opera Mobile - единственный браузер Android, который поддерживает getUserMedia и, таким образом, может делать фотографии.
  • Если убрать display:none; из video и canvas, работает корректно (на планшете). Проблема возникает только в том случае, если для видео и холста установлено display:none;.

Поскольку страница представляет собой одностраничное приложение, не требующее прокрутки или масштабирования, возможный обходной путь - переместить видео и холст ниже складки страницы, а затем отключить прокрутку с помощью javascript (т. Е. Прокручивать вверх всякий раз, когда пользователь прокручивает).

Но я бы предпочел правильное исправление, чем обходной путь, если это возможно!


person Alex Ghiculescu    schedule 13.02.2013    source источник


Ответы (2)


У меня нет очень хорошего решения, но я бы посоветовал не обрабатывать невидимый холст через дом, установив его невидимым.

Вместо того

 var canvas = document.getElementById('canvas');

Пытаться

var canvas = document.createElement('canvas');
 canvas.width = video.width;
 canvas.height = video.height;
 canvas.getContext('2d').drawImage(video, 0, 0)

Таким образом, он отделен от DOM.

Также почему бы не использовать

canvas.toDataURL("image/jpg")?

РЕДАКТИРОВАТЬ: Кроме того, если вы его разрабатываете, пробовали ли вы другие браузеры? Что ограничивает вас в Opera по сравнению с другими доступными браузерами или при использовании телефонного разговора?

EDIT2: подумав об этом, у Canvas также есть два других варианта размещения этой фотографии, которые вы хотели бы рассмотреть пополам. Эти двое:

var imgData = canvas.getContext('2d').getImageData(0,0,canvas.width,canvas.height);

-or-

canvas.getContext('2d').putImageData(imgData,0,0);

Эти двое игнорируют любое масштабирование, которое вы сделали для контекста, но я обнаружил, что они гораздо более прямые и часто быстрее. Они являются надежной альтернативой toDataURL и drawImage, но данные изображения, которые вы помещаете и получаете из них, кодируются в виде массива в форме:

[r1, b1, g1, a1, r2, b2, g2, a2....]

вы можете найти для них документацию здесь:

http://www.w3schools.com/tags/canvas_putimagedata.asp http://www.w3schools.com/tags/canvas_getimagedata.asp

person Jack Franzen    schedule 23.02.2013
comment
Opera - единственный браузер на Android, который поддерживает getUserMedia, поэтому он и используется. Невозможно использовать Phonegap в деловых целях. - person Alex Ghiculescu; 25.02.2013
comment
Спасибо за предложения - попробую. - person Alex Ghiculescu; 25.02.2013
comment
К сожалению нет. Я решил (с помощью stackoverflow.com/users/154112/simon-sarris), что проблема в том, что Opera избегает рисования на холсте до тех пор, пока в этом нет крайней необходимости. Я нашел только один способ заставить его рисовать на невидимом холсте, и он заключался в установке фиксированной высоты тела, overflow: hidden и отображении холста внизу, где его не было бы видно. - person Alex Ghiculescu; 04.03.2013

У некоторых версий Android есть проблемы с toDataUrl(); Может это решение?

http://code.google.com/p/todataurl-png-js/wiki/FirstSteps

Скрипт: http://todataurl-png-js.googlecode.com/svn/trunk/todataurl.js

Вставьте это в свой head:

<script src="todataurl.js"></script>
person Joep    schedule 09.05.2013
comment
Спасибо за это, я разберусь. Кажется, он больше ориентирован на браузер Android по умолчанию; Я использую Opera. - person Alex Ghiculescu; 10.05.2013
comment
Кажется, это хорошо. Если вы немного измените код, вы можете сделать dataurl в Web Worker без зависания основного потока пользовательского интерфейса. Переносимые объекты позволяют очень быстро передавать данные изображения холста работнику. Внутри worker вы можете более эффективно применять сжатие данных к IDAT-фрагменту PNG, например, используя. быстрый порт zlib github.com/nodeca/pako. Создание crc32 можно эффективно выполнить с помощью github.com/SheetJS/js-crc32. РЕДАКТИРОВАТЬ: однако я не знаю, как рабочие и передаваемые объекты поддерживаются в целевых мобильных браузерах. - person Timo Kähkönen; 07.09.2015