Search code examples
javascripthtmlcsshtml5-videovideo-player

Highlight player seekbar in HTML5 video


I have a customized seekbar for my HTML5 video player. But I need to highlight some predefined portions of the seekbar, say seconds 2-5 and 7-8. How can I do that?

Basically, I need it to be something like this:

enter image description here

Here is my simple code so far:

<!DOCTYPE html> 
<html> 
<head>

<style>
.body{
background-color:black;
}
.video-player {
  position: relative;
  width: 66%;
  height: 66%;
}
.video-player img {
  width: 100%;
  height: 100%;
}
.video-player video {
  position: fixed;
  top: 0;
  left: 0;
  min-width: 66%;
  min-height: 66%;
  width: auto;
  height: auto;
  z-index: -100;
  background-repeat: no-repeat;
}
.video-player .controls {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
.video-player .controls .progress-bar {
  position: absolute;
  margin-left: 28%;
  bottom: 10%;
  color: orange;
  font-size: 12px;
  width: 40%;
  height: 8%;
  border: none;
  background: #434343;
  border-radius: 9px;
  vertical-align: middle;
  cursor: pointer;
}
.video-player .controls progress::-moz-progress-bar {
  color: orange;
  background: #434343;
}
.video-player .controls progress[value]::-webkit-progress-bar {
  background-color: #434343;
  border-radius: 2px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
}
.video-player .controls progress[value]::-webkit-progress-value {
  background-color: orange;
}

video#backgroundvid {
  position: absolute;
  right: 0;
  bottom: 0;
  min-width: 100%;
  min-height: 100%;
  width: auto;
  height: auto;
  z-index: -100;
  background-repeat: no-repeat;

}
</style>
</head>

<body>
<div class="video-player">
  <video preload="auto" autoplay loop id="backgroundvid">
    <source src="mov_bbb.mp4" type="video/mp4">
    Your browser does not support HTML5 video.
  </video>
  <img src="top2.png" style="object-fit:cover" alt="" id="backgroundvid">
  <div class="controls">
    <progress class="progress-bar" style="object-fit:cover; z-index=10000" min="0" max="100" value="0">0% played</progress>
  </div>
</div>

<script>
const player = document.querySelector('.video-player');
const video = player.querySelector('video');
const progressBar = player.querySelector('.progress-bar');

video.addEventListener('timeupdate', updateProgressBar, false);
progressBar.addEventListener('click', seek);

function updateProgressBar() {
  var percentage = Math.floor((100 / video.duration) * video.currentTime);
  progressBar.value = percentage;
  progressBar.innerHTML = percentage + '% played';
}

function seek(e) {
  let percent = e.offsetX / this.offsetWidth;
  video.currentTime = percent * video.duration;
  e.target.value = Math.floor(percent / 100);
  e.target.innerHTML = progressBar.value + '% played';
}
</script>
</body> 
</html>

Solution

  • You can use a canvas that you will superimpose on top of your progress-bar,

    Then you will have just to draw the markers in this canvas.

    Just making slight changes in the html (adding an id to the progress-bar id="progress-bar"):

    <progress id="progress-bar" class="progress-bar" style="object-fit:cover; z-index=10000" min="0" max="100" value="0">0% played</progress>
    

    Adding the CSS to style place the canvas (same CSS property than your progress-bar)

    #markers{
        position: absolute;
        bottom: 10%;
        margin-left: 28%;
        border-radius: 9px;
        pointer-events: none;
    }
    

    Note the pointer-events: none; If you don't put it, you can't have access to the control of your progress-bar.

    And so, the javascript to create & insert the canvas, and then place the markers on it.

    // We need the metadata 'duration', so we wrap the code in an event listener to be sure we execute our code when the metadata is loaded
    video.addEventListener('loadedmetadata', function () {
        // Get the dimension of the progress-bar
        const progressbar = document.getElementById('progress-bar');
        const widthProgressBar = window.getComputedStyle(progressbar, null).getPropertyValue("width");
        const heightProgressBar = window.getComputedStyle(progressbar, null).getPropertyValue("height");
        // Create the canvas
        const canvas = document.createElement('canvas');
        const w = canvas.width = parseFloat(widthProgressBar);
        const h = canvas.height = parseFloat(heightProgressBar);
        canvas.id = 'markers';
        const progressBar = document.getElementById("progress-bar");
        // Insert the canvas in the DOM
        progressBar.parentNode.insertBefore(canvas, progressBar.nextSibling)
        // Define the context
        const ctx = canvas.getContext('2d');
        // Calcul how many px will represent 1s
        const videoDuration = video.duration;
        const ratioPxBySeconds = parseFloat(w) / videoDuration;
        // Define the markers
        const markers = {
            'marker1': [2, 5],
            'marker2': [7, 8]
        };
    
        // Function to draw the markers
        function setMarkers(markers, ratioPxSec, height) {
            for (marker in markers) {
                let x = markers[marker][0] * ratioPxSec; // Start x position of the marker
                let y = 0; // Start y position of the marker
                let w = (markers[marker][1] - markers[marker][0]) * ratioPxSec; // Width of the marker
                let h = parseFloat(height); // Height of the marker
                ctx.fillStyle = "#7f3302"; // Set the color of the marker
                ctx.fillRect(x, y, w, h); // Draw a rectangle
            }
        }
    
        setMarkers(markers, ratioPxBySeconds, h); // Call the function
    });
    

    const player = document.querySelector('.video-player');
    const video = player.querySelector('video');
    const progressBar = player.querySelector('.progress-bar');
    
    video.addEventListener('timeupdate', updateProgressBar, false);
    progressBar.addEventListener('click', seek);
    
    function updateProgressBar() {
      var percentage = Math.floor((100 / video.duration) * video.currentTime);
      progressBar.value = percentage;
      progressBar.innerHTML = percentage + '% played';
    }
    
    function seek(e) {
      let percent = e.offsetX / this.offsetWidth;
      video.currentTime = percent * video.duration;
      e.target.value = Math.floor(percent / 100);
      e.target.innerHTML = progressBar.value + '% played';
    }
    
    // We need the metadata 'duration', so we wrap the code in an event listener to be sure we execute our code when the metadata is loaded
    video.addEventListener('loadedmetadata', function() {
      // Get the dimension of the progress-bar
      const progressbar = document.getElementById('progress-bar');
      const widthProgressBar = window.getComputedStyle(progressbar, null).getPropertyValue("width");
      const heightProgressBar = window.getComputedStyle(progressbar, null).getPropertyValue("height");
      // Create the canvas
      const canvas = document.createElement('canvas');
      const w = canvas.width = parseFloat(widthProgressBar);
      const h = canvas.height = parseFloat(heightProgressBar);
      canvas.id = 'markers';
      const progressBar = document.getElementById("progress-bar");
      // Insert the canvas in the DOM
      progressBar.parentNode.insertBefore(canvas, progressBar.nextSibling)
      // Define the context
      const ctx = canvas.getContext('2d');
      // Calcul how many px will represent 1s
      const videoDuration = video.duration;
      const ratioPxBySeconds = parseFloat(w) / videoDuration;
      // Define the markers
      const markers = {
        'marker1': [2, 5],
        'marker2': [7, 8]
      };
    
      // Function to draw the markers
      function setMarkers(markers, ratioPxSec, height) {
        for (marker in markers) {
          let x = markers[marker][0] * ratioPxSec; // Start x position of the marker
          let y = 0; // Start y position of the marker
          let w = (markers[marker][1] - markers[marker][0]) * ratioPxSec; // Width of the marker
          let h = parseFloat(height); // Height of the marker
          ctx.fillStyle = "rgb(127, 51, 2, 0.9)"; // Set the color of the marker
          ctx.fillRect(x, y, w, h); // Draw a rectangle
        }
      }
    
      setMarkers(markers, ratioPxBySeconds, h); // Call the function
      
      // Calculate the new dimensions & redraw
      function resize(){
        const progressBar = document.getElementById('progress-bar');
        const w = canvas.width = progressBar.clientWidth;
        const h = canvas.height = progressBar.clientHeight;
        const ratioPxBySeconds = parseFloat(w) / videoDuration;
        setMarkers(markers, ratioPxBySeconds, h);
      }
      // On page resize, call the resize() function
      window.addEventListener("resize", resize, false);
      
    });
    body {
      background-color: black;
    }
    
    .video-player {
      position: relative;
      width: 66%;
      height: 66%;
    }
    
    .video-player img {
      width: 100%;
      height: 100%;
    }
    
    .video-player video {
      position: fixed;
      top: 0;
      left: 0;
      min-width: 66%;
      min-height: 66%;
      width: auto;
      height: auto;
      z-index: -100;
      background-repeat: no-repeat;
    }
    
    .video-player .controls {
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
    }
    
    .video-player .controls .progress-bar {
      position: absolute;
      margin-left: 28%;
      bottom: 10%;
      color: orange;
      font-size: 12px;
      width: 40%;
      height: 8%;
      border: none;
      background: #434343;
      border-radius: 9px;
      vertical-align: middle;
      cursor: pointer;
    }
    
    #markers {
      position: absolute;
      bottom: 10%;
      margin-left: 28%;
      border-radius: 9px;
      pointer-events: none;
    }
    
    .video-player .controls progress::-moz-progress-bar {
      color: orange;
      background: #434343;
    }
    
    .video-player .controls progress[value]::-webkit-progress-bar {
      background-color: #434343;
      border-radius: 2px;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
    }
    
    .video-player .controls progress[value]::-webkit-progress-value {
      background-color: orange;
    }
    
    video#backgroundvid {
      position: absolute;
      right: 0;
      bottom: 0;
      min-width: 100%;
      min-height: 100%;
      width: auto;
      height: auto;
      z-index: -100;
      background-repeat: no-repeat;
    }
    <div class="video-player">
      <video preload="auto" autoplay loop id="backgroundvid">
        <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
        Your browser does not support HTML5 video.
      </video>
      <img src="https://i.sstatic.net/gmK7P.png" style="object-fit:cover" alt="" id="backgroundvid">
      <div class="controls">
        <progress id="progress-bar" class="progress-bar" style="object-fit:cover; z-index=10000" min="0" max="100" value="0">0% played</progress>
      </div>
    </div>

    Edit:

    Added a resize() function to update the markers when the screen resize

    (typically, it will happen when you put the video in full screen)

    // Calculate the new dimensions & redraw
    function resize(){
      const progressBar = document.getElementById('progress-bar');
      const w = canvas.width = progressBar.clientWidth;
      const h = canvas.height = progressBar.clientHeight;
      const ratioPxBySeconds = parseFloat(w) / videoDuration;
      setMarkers(markers, ratioPxBySeconds, h);
    }
    // On page resize, call the resize() function
    window.addEventListener("resize", resize, false);