Копирование с видео на холст в javascript

#javascript #html5-canvas #dom-events #html5-video

Вопрос:

Я пытаюсь загрузить видео и нарисовать из него миниатюру на холсте. Проблема, с которой я сталкиваюсь, заключается в том, что я не могу найти событие, которое срабатывает, когда видеоизображение готово к копированию. Когда срабатывает событие loadeddata, размер задан правильно, но изображение на холсте пустое. Если я раскомментирую таймер на задержку на секунду, то холст заполнится, но без задержки это не так. Есть ли какое-то событие, которое я пропустил, или какой-то другой способ определить, когда элемент видео может быть скопирован? Я пробовал load, canplay, canplaythrough, loadeddata, прогресс, но, похоже, ничего не приходит в нужное время.

 <html>
  <head>
    <script>
      var video, image, width, height;
      function copy(src) {
        video = document.getElementById('video');
        video.addEventListener('loadeddata', x => finishCopy()); 
        video.src = src;
      }
      function finishCopy() {
        width = document.defaultView.getComputedStyle(video).width.replace(/[^.0-]/g, '');
        height = document.defaultView.getComputedStyle(video).height.replace(/[^.0-]/g, '');
        image = document.getElementById('image');
        image.width = width;
        image.height = height;
        //setTimeout(draw, 1000);
        draw();
      }

      function draw() {
        let ctxt = image.getContext('2d');
        ctxt.drawImage(video, 0, 0, width, height);
      }

    </script>
  </head>
  <body >
    <video id="video" autostart="false" ></video>
    <canvas id="image"></canvas>
  </body>
</html>
 

Ответ №1:

Хотя loadeddata событие срабатывает, как только загружаются данные для текущего кадра, оно не дает никакого намека на то, что это на самом деле означает.

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

Решение состоит в том, чтобы искать событие, которое срабатывает позже в процессе загрузки/обработки видео.

Порядок, в котором происходят эти события, выглядит следующим образом:

  1. запуск загрузки
  2. изменение продолжительности
  3. загруженные данные
  4. загруженные данные
  5. прогресс
  6. может играть

Так что , если мы просто возьмем canplay событие вместо loadeddata этого, все должно работать так, как ожидалось.

Вот пример:

 var video, image, width, height;

function copy(src) {
  video = document.getElementById('video');
  video.addEventListener('canplay', x => finishCopy());
  video.src = src;
}

function finishCopy() {
  width = parseInt(document.defaultView.getComputedStyle(video).width);
  height = parseInt(document.defaultView.getComputedStyle(video).height);
  image = document.getElementById('image');
  image.width = width;
  image.height = height;
  draw();
}

function draw() {
  let ctxt = image.getContext('2d');
  ctxt.drawImage(video, 0, 0, width, height);
}

copy("https://www.w3schools.com/html/mov_bbb.mp4") 
 <video id="video" autostart="false"></video>
<canvas id="image"></canvas> 

Редактировать

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

Существует довольно недокументированный параметр, который вы можете добавить к URL-адресу вашего видео, который позволяет вам указать начальную позицию или даже диапазон для вашего видео ( #t= ).

Теперь, если вы установите начальную позицию на что-то низкое, например 0,000001, вы все равно увидите первый кадр своего видео И сможете рисовать на холсте.

Однако мое тестирование также показало, что это работает только в FireFox и Chrome, если вы используете canplaythrough событие вместо canplay или loadeddata .

Вот модифицированный пример:

 var video, image, width, height;

function copy(src) {
  video = document.getElementById('video');
  video.addEventListener('canplaythrough', x => finishCopy());
  video.src = src;
}

function finishCopy() {
  width = parseInt(document.defaultView.getComputedStyle(video).width);
  height = parseInt(document.defaultView.getComputedStyle(video).height);
  image = document.getElementById('image');
  image.width = width;
  image.height = height;
  draw();
}

function draw() {
  let ctxt = image.getContext('2d');
  ctxt.drawImage(video, 0, 0, width, height);
}

copy("https://www.w3schools.com/html/mov_bbb.mp4#t=0.000001") 
 <video id="video" autostart="false"></video>
<canvas id="image"></canvas> 

Комментарии:

1. Спасибо за ваш ответ, но извините, это не работает для меня. Я уже пробовал событие «canplay» раньше, а также только что протестировал ваш пример кода. Я осмотрел холст, а затем добавил сплошную рамку, чтобы убедиться, что смотрю в нужное место. Размер холста правильный, но он пустой. Я также попробовал его в firefox и safari — он работал в Safari, но не в Chrome или Firefox. Я думаю, что либо это зависит от браузера, либо, возможно, проблема со временем. Мой обходной путь kludgy работает на всех 3.

2. Извините Рассела — я просто забыл протестировать его в Chrome. Я нашел другой обходной путь, хотя и обновил свой ответ.

3. Замечательно! Большое вам спасибо за ваши усилия — не только за решение, но и за понимание того, что происходит. Это значительно упростит поддержку других браузеров.

Ответ №2:

Если у кого-нибудь есть лучшее решение, я бы приветствовал его, но я нашел способ сделать то, что хочу, загрузив метаданные, выполнив поиск, а затем скопировав изображение в прослушивателе событий «просмотрено». Я использую Chrome и подозреваю, что из того, что я прочитал, это может быть не общее решение, но поскольку на данный момент я поддерживаю только Chrome, это, похоже, удовлетворяет моим потребностям.

 <html>
  <head>
    <script>
      var video, image, width, height;
      function copy(src) {
        video = document.getElementById('video');
        video.addEventListener('loadedmetadata', x => video.currentTime = 0);
        video.addEventListener('seeked', x => finishCopy());
        video.src = src;
      }

      function finishCopy() {
        width = document.defaultView.getComputedStyle(video).width.replace(/[^.0-9]/g, '');
        height = document.defaultView.getComputedStyle(video).height.replace(/[^.0-9]/g, '');
        image = document.getElementById('image');
        image.width = width;
        image.height = height;
        //setTimeout(draw, 1000);
        draw();
      }

      function draw() {
        let ctxt = image.getContext('2d');
        ctxt.drawImage(video, 0, 0, width, height);
      }
    </script>
  </head>
  <body >
    <button onclick="copy('asd.mp4')">Copy asd.mp4</button>
    <br />
    <video id="video" preload="metadata" autostart="false" ></video>
    <canvas id="image"></canvas>
  </body>
</html>