How to Capture a Video Moment with JavaScript

Question:

There is a rather interesting question: how can I capture a specific moment of the video (using a screenshot of the moment itself, of course) and draw it directly on the <canvas></canvas> without creating a separate image (which contains this screenshot) in the same folder?

Target:

There is a preview window in the .preview-section block, which, when hovered, shows the timecode of the moment I move the mouse over. So I need to capture this moment of the video and draw it on canvas .

PS. Need a solution without jQuery.

Warning: the code is sooo long, so excuse your eyes:

var vid, playPauseButton, seekSlider, currentTime, vidDuration, muteButton, volumeSlider, fullScreenToggler, loop, fullscreenHider;
var mainVideo, timeCodeContainer, frameContainer, previewBox;



function intializePlayer(){
"use strict"; 
	
// Set object references  
vid = document.getElementById("vid");
mainVideo = document.getElementsByClassName('mainVideo')[0];
playPauseButton = document.getElementById("playPauseButton");
seekSlider = document.getElementById("seekSlider");
currentTime = document.getElementById("done");
vidDuration = document.getElementById("duration");
muteButton = document.getElementById("muteUnmute");
volumeSlider = document.getElementById("volumeSlider");
fullScreenToggler = document.getElementById("toggleFullScreen");
loop = document.getElementById("loop");
fullscreenHider = document.getElementById("exitFullScreen");
previewBox = document.getElementsByClassName("preview-section")[0];	
timeCodeContainer = document.getElementById("timeCodeContainer");
frameContainer = document.getElementById("frameContainer");	
// Add event listeners
playPauseButton.addEventListener("click",playPauseVideo,false);
seekSlider.addEventListener("input",timeSlider,false);
seekSlider.addEventListener("mousemove",sliderHoverValue,false);
seekSlider.addEventListener("mouseout",previewBoxFadeOut,false);
vid.addEventListener("timeupdate",videoTimeUpdate,false);
muteButton.addEventListener("click",muteUnmute,false);
volumeSlider.addEventListener("input",volumeChange,false);
volumeSlider.addEventListener("input",toggleIcon,false);
fullScreenToggler.addEventListener("click",enterFullScreen,false);
fullscreenHider.addEventListener("click",exitFullScreen,false);
document.addEventListener('webkitfullscreenchange', exitHandler, false);
document.addEventListener('mozfullscreenchange', exitHandler, false);
document.addEventListener('fullscreenchange', exitHandler, false);
document.addEventListener('MSFullscreenChange', exitHandler, false);  
loop.addEventListener("click",loopVideo,false);
//Add some other settings
vid.controls = false;
vid.oncontextmenu = function(){return false;};
}

window.onload = intializePlayer;

//Secondary functions 
function videoFSstyling(video,videoBox){
  video.style.width = "100%"
  video.style.height = "auto";
  videoBox.style.width = "100%";
  videoBox.style.height = "auto"}
	
function videoNotFSstyling(video,videoBox){
  video.removeAttribute("style");
  videoBox.removeAttribute("style"); 	
}	

function fadeIn(el){
  el.classList.add('show');
  el.classList.remove('hide');  }

function fadeOut(el){
  el.classList.add('hide');
  el.classList.remove('show');}	
	
var valueHover = 0;
function calcSliderPos(e) {
    return (e.offsetX / e.target.clientWidth) *  parseInt(e.target.getAttribute('max'),10);
}	
	
//Video Functions
function exitHandler(){
"use strict";
  if (document.webkitIsFullScreen === false || document.mozFullScreen === false || document.msFullscreenElement === false){
   videoNotFSstyling(vid,mainVideo);	  
   document.getElementsByClassName("videoControls")[0].classList.remove("fullscreen");  
    fullscreenHider.style.display = "none";
  fullScreenToggler.style.display = "inline-block";}
}
	
function playPauseVideo(){
"use strict";
  if(vid.paused){
    document.title = "►" + " " + document.title;
    vid.play();
    playPauseButton.innerHTML = '<i class="fa fa-pause" aria-hidden="true" style="color: #15C936; font-size:1.7em; margin-top: 5px"></i>';}
  else {
    vid.pause();
    playPauseButton.innerHTML = '<i class="fa fa-play" aria-hidden="true" style="color: #15C936; font-size:1.7em; margin-top: 5px"></i>';}
}
	
function timeSlider(){
"use strict";
  var slideTo = vid.duration * (seekSlider.value / 100);
  vid.currentTime = slideTo;
}
	
function videoTimeUpdate(){
"use strict";
  var timeInterval = vid.currentTime * (100 / vid.duration);
  seekSlider.value = timeInterval;
  var currentMinutes = Math.floor(vid.currentTime / 60);
  var currentSeconds = Math.floor(vid.currentTime - currentMinutes * 60);
  var durationMinutes = Math.floor(vid.duration / 60);
  var durationSeconds = Math.floor(vid.duration - durationMinutes * 60);
  if(currentSeconds < 10) {currentSeconds = "0"+ currentSeconds;}
  if(durationSeconds < 10) {durationSeconds = "0"+ durationSeconds;}
  if(currentMinutes < 10) {currentMinutes = "0"+ currentMinutes;}
  if(durationMinutes < 10) {durationMinutes = "0"+ durationMinutes;}
  currentTime.innerHTML = currentMinutes + ":" + currentSeconds; 
  vidDuration.innerHTML = durationMinutes + ":" + durationSeconds;
}
	
function sliderHoverValue(e){
"use strict";
	valueHover = Math.round(calcSliderPos(e));
	var currentHoverTime = vid.duration * (valueHover / 100);
	var hoverTimeMinutes, hoverTimeSeconds; 
	currentHoverTime = Math.floor(currentHoverTime);

	hoverTimeMinutes = Math.floor(currentHoverTime / 60);
	hoverTimeSeconds = Math.floor(currentHoverTime - hoverTimeMinutes * 60);
	
	if(hoverTimeMinutes < 10){hoverTimeMinutes = "0" + hoverTimeMinutes;}
	if(hoverTimeSeconds < 10){hoverTimeSeconds = "0" + hoverTimeSeconds}
	
	timeCodeContainer.innerHTML = hoverTimeMinutes + ":" + hoverTimeSeconds;
	if(currentHoverTime > Math.floor(vid.duration)){
		timeCodeContainer.innerHTML = vidDuration.innerHTML;}
	if(currentHoverTime < 0){timeCodeContainer.innerHTML = "00:00";}
	fadeIn(previewBox);
	var translateXpos = parseFloat(calcSliderPos(e).toFixed(4));
	var translateToPosition = ((translateXpos * seekSlider.clientWidth / 100).toFixed(4));
	if(translateXpos < 0){
		previewBox.setAttribute("style","transform: translate(0%, -106%) !important;");
	}
	else if(translateXpos > seekSlider.clientWidth || parseFloat(translateToPosition) > seekSlider.clientWidth){
		previewBox.setAttribute("style","transform: translate(" + seekSlider.clientWidth + "px" + ", -106%) !important;");
	}
	else {
    previewBox.setAttribute("style","transform: translate(" + translateToPosition + "px" + ", -106%) !important;");}
}	
	
function previewBoxFadeOut(){fadeOut(previewBox);}	
	
function volumeChange(){
"use strict";
  vid.volume = volumeSlider.value / 100;
}

function enterFullScreen(){
"use strict";
  
  if(mainVideo.requestFullScreen){
    mainVideo.requestFullScreen();
    videoFSstyling(vid,mainVideo);}
  else if(mainVideo.webkitRequestFullScreen){
    mainVideo.webkitRequestFullScreen();
    videoFSstyling(vid,mainVideo);}
  else if(mainVideo.mozRequestFullScreen){
    mainVideo.mozRequestFullScreen();
    videoFSstyling(vid,mainVideo);}
  else if(mainVideo.oRequestFullScreen){
    mainVideo.oRequestFullScreen();
    videoFSstyling(vid,mainVideo);}
  else if(mainVideo.msRequestFullScreen){
    mainVideo.msRequestFullScreen();
    videoFSstyling(vid,mainVideo);}
  document.getElementsByClassName("videoControls")[0].classList.add("fullscreen");

  fullScreenToggler.style.display = "none";
  fullscreenHider.style.display = "inline-block";
}

function exitFullScreen(){
"use strict"; 
  if(document.cancelFullScreen){
    document.cancelFullScreen();
    videoNotFSstyling(vid,mainVideo);}
  else if(document.webkitCancelFullScreen){
    document.webkitCancelFullScreen();
    videoNotFSstyling(vid,mainVideo);}
  else if(document.mozCancelFullScreen){
    document.mozCancelFullScreen();
    videoNotFSstyling(vid,mainVideo);}
  else if(document.oCancelFullScreen){
    document.oCancelFullScreen();
    videoNotFSstyling(vid,mainVideo);}
  else if(document.msCancelFullScreenn){
    document.msCancelFullScreen();
    videoNotFSstyling(vid,mainVideo);}
    document.getElementsByClassName("videoControls")[0].classList.remove("fullscreen");
	
   fullscreenHider.style.display = "none";
   fullScreenToggler.style.display = "inline-block";
}

function loopVideo(){
"use strict"; 
  if(!loop.hasAttribute("style")){
    loop.setAttribute("style","opacity: 1; color: rgba(22,206,170,1.00);");
    vid.setAttribute("loop","");  
  }
  else {
    loop.removeAttribute("style");
    vid.removeAttribute("loop");
  }
}
	
function toggleIcon(){
"use strict"; 
if(vid.volume <= 0.01){
    muteButton.innerHTML = '<i class="fa fa-volume-off" aria-hidden="true" style="font-size: 2.4em; color: #C57A0C; margin-top: 5px;"></i>';}
else if(vid.volume <= 0.42){
  muteButton.innerHTML = '<i class="fa fa-volume-down" aria-hidden="true" style="font-size: 2.4em; color: #C57A0C; margin-top: 5px;"></i>';}  
else {
  muteButton.innerHTML = '<i class="fa fa-volume-up" aria-hidden="true" style="font-size: 2.4em; color: #C57A0C; margin-top: 5px;"></i>';
}
}
	
var prev_level;

function muteUnmute(){
"use strict";
  if(vid.volume >= 0.03){
    prev_level = volumeSlider.value;
    volumeSlider.value = 0;
    vid.volume = volumeSlider.value;
    toggleIcon();
  }
    else if(vid.volume <= 0.05){
    volumeSlider.value= prev_level;
    vid.volume = volumeSlider.value / 100;
    toggleIcon();
  }
}

//KeyPress Fuctions

function pressSpaceToStart(e){
 "use strict"; 
  if(e.keyCode === 32){
     e.preventDefault();
       playPauseVideo();}
}
window.onkeypress = function(o){"use strict"; pressSpaceToStart(o);};
window.onkeydown = function(o){"use strict"; pressSpaceToStart(o);};
button:focus {outline: none !important;}	
input:focus	 {outline: none !important;} 
	  
video::-webkit-media-controls, video::-webkit-media-controls-enclosure {display:none !important;}
section.videoSection {width: 100%; margin: auto; margin-top: 30px;}
div.mainVideo {text-align: center; width: 454px; margin: auto} 
div.mainVideo:-webkit-full-screen {background-color: transparent !important;}	  
div.mainVideo video {width: 450px; border: 2px solid black; border-bottom: 0}
div.videoControls {width: 450px; margin: -5px auto 0px; background-color: rgba(67,41,82,0.97); padding: 10px 0px 8px 0px; border: 2px solid black; color: snow; border-bottom-left-radius: 12px; border-bottom-right-radius: 12px;}
div.videoControls button {background-color: transparent; border: 0; opacity: 0.8;}
div.videoControls span {position: relative;}
div.videoControls button:hover {opacity: 1;}
div.preview-section {position: absolute; width: 100px; height: 70px; transform: translate(0%, -106%); margin-left: -44.5px; opacity: 0;}
div.preview-section * {cursor: default;}	  
div.preview-box {position: relative; width: 100px; height: 70px; background-image: linear-gradient(to top, #252323, rgba(0, 0, 0, 0.86));
-webkit-background-image: linear-gradient(to top, #252323, rgba(0, 0, 0, 0.86)); 
-moz-background-image: linear-gradient(to top, #252323, rgba(0, 0, 0, 0.86)); 
-o-background-image: linear-gradient(to top, #252323, rgba(0, 0, 0, 0.86));	
-ms-background-image: linear-gradient(to top, #252323, rgba(0, 0, 0, 0.86));	
border: 1px solid rgba(0,0,0,0.84); box-sizing: border-box; border-bottom: 0; box-shadow: inset 0 0 0 2px #363131;
-webkit-box-shadow: inset 0 0 0 2px #363131; -moz-box-shadow: inset 0 0 0 2px #363131; -ms-box-shadow: inset 0 0 0 2px #363131;
-o-box-shadow: inset 0 0 0 2px #363131;}
div.preview-box #frameContainer {width: 96%; height: 58px; background: #E0E0E0; margin: 1px auto 0px;}
div.preview-box #timeCodeContainer {font-size: 0.70em; background-color: rgba(23,22,22,.76); text-align: center; position: absolute; color: white; left: 50%; top: 86%; transform: translate(-50%, -100%); overflow: hidden; display: inline-block; padding: 0px 3px}	  
	  
/* Slider Styling */  
input[type=range] {-webkit-appearance: none; margin: 5.8px 0; background-color: transparent !important;}

input[type=range]:focus {outline: none;}

input[type=range]::-webkit-slider-runnable-track {cursor: pointer; box-shadow: 2.4px 2.4px 6.2px rgba(7, 7, 163, 0.72), 0px 0px 2.4px rgba(8, 8, 187, 0.72); background: #ac62ff; border-radius: 21.6px; border: 1px solid rgba(163, 0, 255, 0.79);}

input[type=range]::-webkit-slider-thumb {box-shadow: 2.4px 2.4px 9.5px rgba(4, 16, 14, 0.78), 0px 0px 2.4px rgba(9, 36, 32, 0.78); border: 1.8px solid rgba(0, 0, 6, 0.77); border-radius: 28px; background: #ffff29; cursor: pointer; -webkit-appearance: none; margin-top: -7.8px;}

input[type=range]:focus::-webkit-slider-runnable-track {background: #b16cff;}

input[type=range]::-moz-range-track {width: 100%; cursor: pointer; box-shadow: 2.4px 2.4px 6.2px rgba(7, 7, 163, 0.72), 0px 0px 2.4px rgba(8, 8, 187, 0.72); background: #ac62ff; border-radius: 21.6px; border: 1px solid rgba(163, 0, 255, 0.79);}

input[type=range]::-moz-range-thumb {box-shadow: 2.4px 2.4px 9.5px rgba(4, 16, 14, 0.78), 0px 0px 2.4px rgba(9, 36, 32, 0.78); border: 1.8px solid rgba(0, 0, 6, 0.77); border-radius: 28px; background: #ffff29; cursor: pointer;}

input[type=range]::-ms-track {width: 100%; cursor: pointer; background: transparent; border-color: transparent; color: transparent;}

input[type=range]::-ms-fill-lower {background: #a758ff; border: 1px solid rgba(163, 0, 255, 0.79); border-radius: 43.2px; box-shadow: 2.4px 2.4px 6.2px rgba(7, 7, 163, 0.72), 0px 0px 2.4px rgba(8, 8, 187, 0.72);}

input[type=range]::-ms-fill-upper {background: #ac62ff; border: 1px solid rgba(163, 0, 255, 0.79); border-radius: 43.2px; box-shadow: 2.4px 2.4px 6.2px rgba(7, 7, 163, 0.72), 0px 0px 2.4px rgba(8, 8, 187, 0.72);}

input[type=range]::-ms-thumb {box-shadow: 2.4px 2.4px 9.5px rgba(4, 16, 14, 0.78), 0px 0px 2.4px rgba(9, 36, 32, 0.78); border: 1.8px solid rgba(0, 0, 6, 0.77); border-radius: 28px; background: #ffff29; cursor: pointer;}

input[type=range]:focus::-ms-fill-lower {background: #ac62ff;}

input[type=range]:focus::-ms-fill-upper {background: #b16cff;}

/* Animationg fadeIn and fadeOut styles */	  
.show {opacity: 1 !important; transition: opacity 400ms;}

.hide {opacity: 0 !important; transition: opacity 400ms;}	 
	  
/* Non fullscreen track and thumb width and height */

/* Track */
.videoControls input[type=range]::-webkit-slider-runnable-track {height: 6.4px;}
.videoControls input[type=range]::-moz-range-track {height: 6.4px;}
.videoControls input[type=range]::-ms-track {height: 6.4px;}

/* Thumb */
.videoControls input[type=range]::-webkit-slider-thumb {height: 20px; width: 8px;}
.videoControls input[type=range]::-moz-range-thumb {height: 20px; width: 8px;}
.videoControls input[type=range]::-ms-thumb {height: 20px; width: 8px;}

/* Video Controls Buttons Styling */
#playPauseButton {float: left; margin: -3px 1px 0px 3px;}
#loop {float: left; margin: 4px 5px 25px 3px;}
#seekContainer {width: 150px; float: left;}	  
#seekSlider {width: 100%; margin: 10px 5px 0px -1px;}
div.mainVideo span {color: snow; font-size: 0.97em; display: inline-block; float: left; margin: 2.5px 0px 0px 1px;}
#muteUnmute {margin-top: -5px; float: left;}
#volumeSlider {width: 64px; margin: 10px 0px 0px 0px; float: left;}
#toggleFullScreen {margin: 0px 0px 0px 4px;}

/* Fullscreen settings -START-*/

.fullscreen {z-index: 2789034264 !important; position: absolute !important; width: 80% !important; bottom: 5.7% !important; left: 10% !important; right: 10% !important; height: 35px !important;}
.fullscreen button i {font-size: 2.5em !important;}
.fullscreen button#playPauseButton {margin-left: 0.2% !important; float: left !important;}
.fullscreen #seekContainer {width: 48% !important; margin-left: 2px !important;} 
.fullscreen button#muteUnmute {margin-left: 0.5%; font-size: 0.98em; margin-top: -10px;}
.fullscreen span {font-size: 1.2em !important;}
.fullscreen input#volumeSlider {width: 12%; margin-left: 1%;}
#exitFullScreen {margin-left: 0.5%; margin-top: -3px;}

/* Fullscreen track and thumb width and height */

/* Track */
.fullscreen input[type=range]::-webkit-slider-runnable-track {height: 12.8px;}
.fullscreen input[type=range]::-moz-range-track {height: 12.8px;}
.fullscreen input[type=range]::-ms-track {height: 12.8px;}

/* Thumb */
.fullscreen input[type=range]::-webkit-slider-thumb {height: 32px; width: 12px; margin-top: -10px;}
.fullscreen input[type=range]::-moz-range-thumb {height: 32px; width: 12px; margin-top: -10px;}
.fullscreen input[type=range]::-ms-thumb {height: 32px; width: 12px; margin-top: -10px;}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"/>


<section class="videoSection">
  <div class="mainVideo">
    <video preload="auto" id="vid" onContextMenu="return false;">
      <source type="video/mp4" src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"/>
      <source type="video/webm" src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.webm"/>
      <img src="media/images/videoErr.png" alt="Your browser doesn't support HTML5 video." title="Your browser doesn't support HTML5 video."/>
    </video>  
    <div class="videoControls">
      <button id="playPauseButton" title="Play/Pause">
      <i class="fa fa-play" aria-hidden="true" style="color: #15C936; font-size:1.7em; margin-top: 5px"></i>
      </button>
      <!-- Video current time and duration iteration -->
      <span id="timer">
      <span id="done" title="Time couter from the start of the video">00:00</span>
      <span>/</span>
      <span id="duration" title="Video duration">00:00</span>
      </span>
      <button id="loop" title="Loop">
      <i class="fa fa-repeat" aria-hidden="true" style="color: #09BF99; font-size: 1.5em"></i>
      </button>
      <div style="display: inline-block" id="seekContainer">
      <!-- Video preview on hover -->
      <div class="preview-section">
      	<div class="preview-box">
      		<div id="frameContainer"></div>
      		<div id="timeCodeContainer">00:00</div>
      	</div>
      </div>
      <input type="range" step="1" min="0" max="100" value="0" id="seekSlider"/>
      </div>
      <button id="muteUnmute" title="Mute/Unmute">
      <i class="fa fa-volume-up" aria-hidden="true" style="font-size: 2.4em; color: #C57A0C; margin-top: 5px;"></i>
      </button>
      <input type="range" step="1" min="0" max="100" value="100" id="volumeSlider" title="Volume level"/>
      <button id="exitFullScreen" style="display: none; font-size: 0.95em" title="Exit fullscreen">
      <i class="fa fa-sign-out" aria-hidden="true" style="color: whitesmoke;"></i>
      </button>
      <button id="toggleFullScreen" title="Enter fullscreen">
      <i class="fa fa-arrows-alt" aria-hidden="true" style="color: whitesmoke; font-size: 2em;"></i>
      </button>
    </div>
  </div>  
  </section>

Answer:

the canvas has a drawImage method. Google it. Here is an example from the first link

function grabScreenshot() {
  ctx.drawImage(video, 0, 0, videoWidth, videoHeight);
  var img = new Image();
  img.src = canvas.toDataURL("image/png");
  img.width = 120;
  ssContainer.appendChild(img);
}
Scroll to Top