Capture frames from video with HTML5 and JavaScript

JavascriptHtmlVideoCanvasHtml5 Canvas

Javascript Problem Overview


I want to capture a frame from video every 5 seconds.

This is my JavaScript code:

video.addEventListener('loadeddata', function() {
	var duration = video.duration;
	var i = 0;
		
	var interval = setInterval(function() {
		video.currentTime = i;
		generateThumbnail(i);
		i = i+5;
		if (i > duration) clearInterval(interval);
	}, 300);
});

function generateThumbnail(i) {		
    //generate thumbnail URL data
	var context = thecanvas.getContext('2d');
	context.drawImage(video, 0, 0, 220, 150);
	var dataURL = thecanvas.toDataURL();

	//create img
	var img = document.createElement('img');
	img.setAttribute('src', dataURL);
		
	//append img in container div
	document.getElementById('thumbnailContainer').appendChild(img);
}

The problem I have is the 1st two images generated are the same and the duration-5 second image is not generated. I found out that the thumbnail is generated before the video frame of the specific time is displayed in < video> tag.

For example, when video.currentTime = 5, image of frame 0s is generated. Then the video frame jump to time 5s. So when video.currentTime = 10, image of frame 5s is generated.

Javascript Solutions


Solution 1 - Javascript

Cause

The problem is that seeking video (by setting it's currentTime) is asynchronous.

You need to listen to the seeked event or else it will risk take the actual current frame which is likely your old value.

As it is asynchronous you must not use the setInterval() as it is asynchronous too and you will not be able to properly synchronize when the next frame is seeked to. There is no need to use setInterval() as we will utilize the seeked event instead which will keep everything is sync.

Solution

By re-writing the code a little you can use the seeked event to go through the video to capture the correct frame as this event ensures us that we are actually at the frame we requested by setting the currentTime property.

Example

// global or parent scope of handlers
var video = document.getElementById("video"); // added for clarity: this is needed
var i = 0;

video.addEventListener('loadeddata', function() {
    this.currentTime = i;
});

Add this event handler to the party:

video.addEventListener('seeked', function() {

  // now video has seeked and current frames will show
  // at the time as we expect
  generateThumbnail(i);

  // when frame is captured, increase here by 5 seconds
  i += 5;

  // if we are not past end, seek to next interval
  if (i <= this.duration) {
    // this will trigger another seeked event
    this.currentTime = i;
  }
  else {
    // Done!, next action
  }
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLinView Question on Stackoverflow
Solution 1 - Javascriptuser1693593View Answer on Stackoverflow