#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
событие срабатывает, как только загружаются данные для текущего кадра, оно не дает никакого намека на то, что это на самом деле означает.
На практике, однако, это означает, что в некоторых случаях или в некоторых браузерах он может отображать первый кадр, хотя вы не можете зависеть от этого.
Решение состоит в том, чтобы искать событие, которое срабатывает позже в процессе загрузки/обработки видео.
Порядок, в котором происходят эти события, выглядит следующим образом:
- запуск загрузки
- изменение продолжительности
- загруженные данные
- загруженные данные
- прогресс
- может играть
Так что , если мы просто возьмем 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>