tl; dr
См. Вариант B для моего кода.
GIF в разделе Phenomenon показывает проблему.
Только в Safari.
Справочная информация
Я создаю демонстрацию машинного обучения через Интернет.Демонстрация фиксирует локальную прямую трансляцию веб-камеры от пользователя, обрабатывает изображение (придумайте что-то вроде поиска лица, а затем запустите несколько сверточных нейронных сетей на изображении).Затем отображает результаты (например, лицо, выделенное прямоугольником, и некоторый текст) пользователю, например, зеркало.
Чтобы получить представление, представьте что-то вроде этого: https://tastenkunst.github.io/brfv4_javascript_examples/
(Этоэто не мое приложение, просто пример. На данный момент я не могу поделиться своим настоящим приложением.)
Цель
- Захват изображения с веб-камеры.
- Выполните некоторые длительные вычисления для захваченного изображения.
- Отображение изображения (захваченного на шаге 1) и результатов расчета на холсте.
- Повтор.
Контекст
Safari 12.0.2 с macOS Mojave на MBP 2018 (проблема также возникает на нескольких старых MacBook, которые я пробовал).
Инициализация (чтобы дать контекст для примеров):
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: 320, height: 240 }
});
video.srcObject = stream;
video.setAttribute("playsinline", "true");
await video.play();
captureImage()
означает следующее:
tempCanvasCtx.drawImage(video, 0, 0);
const image = tempCanvasCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
longCalculation()
: может варьироваться по продолжительности (на кадр, а также в зависимости от машины) от моментов до 0,5 секунды.Он использует TensorFlow.js, работающий на графическом процессоре, и некоторые другие вычисления шейдеров WebGL (независимо от холстов).
video
- это HTMLVideoElement, а ctx
и tempCanvasCtx
- это двухмерные контексты двух разныххолсты.Код находится внутри асинхронной функции.
Trials
Опция A
const loop = async () => {
const image = captureImage();
const result = await longCalculation(image);
ctx.putImageData(image, 0, 0);
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
Мое первое очевидное решение, которое работает во всех основных браузерах,кроме сафари.В Safari он является неопределенным, иногда работает, а иногда видео останавливается навсегда, возможно, из-за выполнения RAF, которое иногда занимает много времени и глушит RAF.
Опция B
const loop = async () => {
const image = captureImage();
const result = await longCalculation(image);
ctx.putImageData(image, 0, 0);
setTimeout(loop, 0);
};
setTimeout(loop, 0);
В этом случае происходит следующее:
Феномен
Полученное видео, отображаемое на холсте, не сохраняет последовательный порядок кадров.Предыдущие кадры иногда появляются позже и наоборот.Он прыгает вперед и назад на несколько секунд, иногда больше.Это дает действительно жуткий эффект, похожий на кассету в фильме «Кольцо», когда в одном кадре маленькая девочка стоит где-то, а в следующем - где-то совсем другое.Если вы записываете наручные часы, секундная стрелка будет прыгать вперед и назад.
Опция C
while (true) {
const image = captureImage();
const result = await longCalculation(image);
ctx.putImageData(image, 0, 0);
await new Promise(resolve => setTimeout(resolve, 0));
}
Я не уверен, отличаются ли B и C от других, но я включил их для прозрачности.
Я подумал, что то, о чем я прошу, должно быть простым ... Захват кадра и просто ожидание, пока он не будет обработан иотображается перед захватом и обработкой следующего кадра.Я не вижу, где что-то идет не так.
Я уже пробовал:
- Увеличение времени ожидания, добавление дополнительных ожиданий (хотя работа медленнее, чем машина способнане желательное решение).
- Создание и обработка очереди кадров.
- Два уровня вложенности
requestAnimationFrame
с. - Регистрация времени событий (захвачено, нарисованономер кадра № и т. д.), который идет в хорошем порядке в консоли.Никогда не бывает более одного кадра в «очереди».
- Рисование результатов в отдельном RAF.
Обратите внимание, что это может быть ошибкой в самом Safari, так как я 'Я видел это явление на других сайтах (в частности, видеопроигрыватель Coursera), но тем не менее я все еще заинтересован в обходном пути.
Спасибо.