{"id":1922,"date":"2018-02-22T19:14:57","date_gmt":"2018-02-22T19:14:57","guid":{"rendered":"http:\/\/websterart.com\/wordpress\/?p=1922"},"modified":"2018-02-22T19:17:36","modified_gmt":"2018-02-22T19:17:36","slug":"custom-html5-video-player-controls","status":"publish","type":"post","link":"https:\/\/websterart.com\/wordpress\/2018\/02\/custom-html5-video-player-controls\/","title":{"rendered":"Custom HTML5 video player controls"},"content":{"rendered":"<p>We had an inch of snow today. I took a brisk walk around the block on the sunny, but slippery sidewalks before getting back to work programming.<\/p>\n<p>Yesterday our <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Learn\/JavaScript\/Client-side_web_APIs\/Video_and_audio_APIs\" target=\"_blank\" rel=\"noopener\">exercise was to remove the default html5 video player controls<\/a>, then substitute a handbuilt controller. As an added challenge, they asked if we could program in a clickable playhead. They gave us a couple clues, but thinking it through was a \u00a0brain squeezer. There was a 190 pixel wide area where the progress bar walked across as the video played.<\/p>\n<p>Somehow that area needed to become clickable. When clicked, the video should play at that percentage. Meaning, if you click half way across the progress bar area, the video should jump to the halfway point.<\/p>\n<p>I can&#8217;t post the video here in WordPress without a lot of trouble from width properties. So instead <a href=\"http:\/\/websterart.com\/html\/study-js\/video3\/index4.html\" target=\"_blank\" rel=\"noopener\">I&#8217;m linking to it here as an external page<\/a>. But the code is shown below. The magic in the \u00a0clickable progress bar is on line 157:\u00a0<strong>function jumpTo(e){&#8230;}<\/strong><\/p>\n<p>My solution isn&#8217;t a true draggable playhead, but it does at least navigate, and displays a nice red bar when clicked.<\/p>\n<pre class=\"lang:default decode:true\">var media = document.querySelector('video');\r\nvar controls = document.querySelector('.controls');\r\n\r\nvar play = document.querySelector('.play');\r\nvar stop = document.querySelector('.stop');\r\nvar rwd = document.querySelector('.rwd');\r\nvar fwd = document.querySelector('.fwd');\r\n\r\nvar timerWrapper = document.querySelector('.timer');\r\nvar timer = document.querySelector('.timer span');\r\nvar timerBar = document.querySelector('.timer div');\r\nvar intervalRwd;\r\nvar intervalFwd;\r\n\/\/removes the default html5 controls\r\nmedia.removeAttribute('controls');\r\ncontrols.style.visibility = 'visible';\r\n\r\nplay.addEventListener('click', playPauseMedia);\r\n\r\nfunction playPauseMedia(){\r\n\r\n  \/*below fixes a problem that occurs if they try to stop\r\n  while fast forward or fast reverse is running*\/\r\n  rwd.classList.remove('active');\r\n  fwd.classList.remove('active');\r\n  clearInterval(intervalRwd);\r\n  clearInterval(intervalFwd);\r\n\r\n  if(media.paused) {\r\n    play.setAttribute('data-icon', 'u');\r\n    media.play();\r\n  } else {\r\n    play.setAttribute('data-icon','P');\r\n    media.pause();\r\n  }\r\n}\/\/end function playPauseMedia\r\n\r\nstop.addEventListener('click', stopMedia);\r\nmedia.addEventListener('ended', stopMedia);\r\n\r\nfunction stopMedia() {\r\n  media.pause();\r\n  media.currentTime = 0;\r\n  play.setAttribute('data-icon','P');\r\n\r\n  \/*below fixes a problem that occurs if they try to stop\r\n  while fast forward or fast reverse is running*\/\r\n  rwd.classList.remove('active');\r\n  fwd.classList.remove('active');\r\n  clearInterval(intervalRwd);\r\n  clearInterval(intervalFwd);\r\n}\r\n\r\nrwd.addEventListener('click', mediaBackward);\r\nfwd.addEventListener('click', mediaForward);\r\n\r\nfunction mediaBackward(){\r\n  clearInterval(intervalFwd);\r\n  fwd.classList.remove('active');\r\n\r\n  if(rwd.classList.contains('active')){\r\n    rwd.classList.remove('active');\r\n    clearInterval(intervalRwd);\r\n    media.play();\r\n  } else {\r\n    rwd.classList.add('active');\r\n    media.pause();\r\n\r\n    \/*\r\n    If it hasn't yet been set, we add the active\r\n    class to the rwd button using classList.add(),\r\n    pause the video using HTMLMediaElement.pause(),\r\n    then set the intervalRwd variable to equal\r\n    a setInterval() call. When invoked, setInterval()\r\n    creates an active interval, meaning that\r\n    it runs the function given as the first\r\n    parameter every x milliseconds,\r\n    where x is the value of the 2nd parameter.\r\n    So here we are running the windBackward()\r\n    function every 200 milliseconds \u2014 we'll\r\n    use this function to wind the video backwards\r\n    constantly. To stop a setInterval() running,\r\n    you have to call clearInterval(),\r\n    giving it the identifying name of the\r\n    interval to clear, which in this case\r\n    is the variable name intervalRwd\r\n    *\/\r\n    intervalRwd = setInterval(windBackward, 200);\r\n  }\r\n}\/\/end function mediaBackward\r\n\r\nfunction mediaForward(){\r\n  clearInterval(intervalRwd);\r\n  rwd.classList.remove('active');\r\n\r\n  if(fwd.classList.contains('active')){\r\n    fwd.classList.remove('active');\r\n    clearInterval(intervalFwd);\r\n    media.play();\r\n  } else {\r\n    fwd.classList.add('active');\r\n    media.pause();\r\n    intervalFwd = setInterval(windForward, 200);\/\/setInterval() takes two parameters: a function &amp; milliseconds\r\n  }\r\n}\/\/end function mediaForward\r\n\r\nfunction windBackward(){\r\n  if(media.currentTime &lt;= 3){\r\n    \/\/rwd.classList.remove('active');\r\n    \/\/clearInterval(intervalRwd);\r\n    stopMedia();\r\n  } else {\r\n    media.currentTime -= 3;\r\n  }\r\n}\/\/end function windBackward\r\n\r\nfunction windForward(){\r\n  if(media.currentTime &gt;= media.duration - 3){\r\n    \/\/fwd.classList.remove('active');\r\n    \/\/clearInterval(intervalFwd);\r\n    stopMedia();\r\n  } else {\r\n    media.currentTime += 3;\r\n  }\r\n}\/\/end function windBackward\r\n\r\nmedia.addEventListener('timeupdate', setTime);\r\n\r\nfunction setTime(){\r\n  var minutes = Math.floor(media.currentTime \/ 60);\/\/floor rounds down to nearest integer, if 90 seconds, returns: 1\r\n  var seconds = Math.floor(media.currentTime - minutes * 60);\/\/if 90 seconds, returns: 30\r\n  var minuteValue;\r\n  var secondValue;\r\n\r\n  if(minutes &lt; 10){\r\n    minuteValue = '0' + minutes;\r\n  } else {\r\n    minuteValue = minutes;\r\n  }\r\n\r\n  if (seconds &lt; 10){\r\n    secondValue = '0' + seconds;\r\n  } else {\r\n    secondValue = seconds;\r\n  }\r\n\r\n  var mediaTime = minuteValue + ':' + secondValue;\r\n  timer.textContent = mediaTime;\r\n\r\n  \/\/any elements width is found via: element.clientWidth\r\n  var barLength = timerWrapper.clientWidth * (media.currentTime\/media.duration);\r\n  timerBar.style.width = barLength + 'px';\r\n}\/\/end function setTime()\r\n\r\ntimerWrapper.addEventListener('click', jumpTo)\r\nvar durationPercent;\r\nfunction jumpTo(e){\r\n  durationPercent = Math.floor((e.x - 273) \/ 2);\r\n  media.currentTime = durationPercent * (media.duration \/ 100);\r\n  timerBar.classList.add('playHead');\r\n  console.log('xMouse: ' + e.x) + ', ' +  console.log('yMouse: ' + e.y);\r\n}\r\n\r\n\/\/ timerWrapper.onclick = function(e){\r\n\/\/   console.log('xMouse: ' + e.x) + ', ' +  console.log('yMouse: ' + e.y);\r\n\/\/ }\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We had an inch of snow today. I took a brisk walk around the block on the sunny, but slippery sidewalks before getting back to work programming. Yesterday our exercise was to remove the default html5 video player controls, then substitute a handbuilt controller. As an added challenge, they asked if we could program in [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,179],"tags":[],"class_list":["post-1922","post","type-post","status-publish","format-standard","hentry","category-blog","category-programming"],"_links":{"self":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1922","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1922"}],"version-history":[{"count":3,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1922\/revisions"}],"predecessor-version":[{"id":1925,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/posts\/1922\/revisions\/1925"}],"wp:attachment":[{"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1922"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1922"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/websterart.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1922"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}