sinanisler logo

Custom HTML5 Video Player with Chapters and Tooltips, No Lib 😎

Custom native HTML, CSS and JS video player. Completely native no library is needed. Colors can be changed from CSS easily.

Enjoy using. Alternative ->

See the Pen Untitled by sinanisler (@sinanisler) on CodePen.

  <div class="video-container">
    <video id="myVideo">
      <source src="https://wpaimuse.com/wp-content/uploads/2024/03/2024-03-14-11-59-41-2.mp4" type="video/mp4">
      Your browser does not support the video tag.
    </video>

    <!-- Custom Controls -->
    <div class="controls">
      <button id="playPauseBtn">►</button>
      <div class="progress-container">
        <div id="progressBar" class="progress-bar">
          <div id="progress" class="progress"></div>
          <!-- Progress dots will be added here -->
        </div>
      </div>
      <button id="muteBtn">🔈</button>
      <button id="fullscreenBtn">⛶</button>
    </div>

    <!-- Chapter buttons -->
    <div id="chapterButtons" class="chapter-buttons" style="display:none"></div>
  </div>
    /* Container styling */
    .video-container {
      position: relative;
      width: 640px;
      margin: 0 auto;
      background: #000;
    }

    /* Video styling */
    #myVideo {
      width: 100%;
    }

    /* Custom controls styling */
    .controls {
      display: flex;
      align-items: center;
      justify-content: space-between;
      background: #333;
      padding: 10px;
      color: #fff;
    }

    .controls button {
      background: none;
      border: none;
      color: #fff;
      font-size: 20px;
      cursor: pointer;
    }

    /* Progress bar container */
    .progress-container {
      flex: 1;
      margin: 0 10px;
      position: relative;
      height: 10px;
      background: #555;
      cursor: pointer;
    }

    /* Progress bar */
    .progress-bar {
      position: relative;
      width: 100%;
      height: 100%;
    }

    .progress {
      background: #2196F3;
      height: 100%;
      width: 0%;
    }

    /* Progress dots */
    .progress-dot {
      position: absolute;
      top: 50%;
      width: 8px;
      height: 8px;
      background: #fff;
      border-radius: 50%;
      cursor: pointer;
      transform: translate(-50%, -50%);
    }

    /* Tooltip styling */
    .tooltip {
      position: absolute;
      padding: 5px 10px;
      background: rgba(0, 0, 0, 0.8);
      color: #fff;
      border-radius: 4px;
      font-size: 14px;
      white-space: nowrap;
      pointer-events: none;
      opacity: 0;
      transition: opacity 0.2s;
    }

    /* Chapter buttons styling */
    .chapter-buttons {
      margin-top: 20px;
      text-align: center;
    }

    .chapter-buttons button {
      margin: 5px;
      padding: 10px;
      font-size: 16px;
      cursor: pointer;
    }
    // Get the video element
    const video = document.getElementById('myVideo');

    // Array of chapters with title and time (in minutes:seconds)
    const chapters = [
      { title: "Chapter 1", time: "0:30" },
      { title: "Chapter 2", time: "1:00" },
      { title: "Chapter 3", time: "1:30" },
      { title: "Chapter 4", time: "2:00" }
    ];

    // Convert "min:sec" to total seconds
    function timeToSeconds(time) {
      const [minutes, seconds] = time.split(':').map(Number);
      return minutes * 60 + seconds;
    }

    // Jump to a specific time in the video
    function goToTime(seconds) {
      video.currentTime = seconds;
      video.play();
    }

    // Generate chapter buttons
    const chapterButtonsDiv = document.getElementById('chapterButtons');

    chapters.forEach((chapter) => {
      const button = document.createElement('button');
      button.textContent = `${chapter.title} - ${chapter.time}`;
      button.addEventListener('click', () => {
        const seconds = timeToSeconds(chapter.time);
        goToTime(seconds);
      });
      chapterButtonsDiv.appendChild(button);
    });

    // Custom Controls
    const playPauseBtn = document.getElementById('playPauseBtn');
    const muteBtn = document.getElementById('muteBtn');
    const fullscreenBtn = document.getElementById('fullscreenBtn');
    const progressContainer = document.querySelector('.progress-container');
    const progressBar = document.getElementById('progress');
    const progressBarContainer = document.getElementById('progressBar');

    // Tooltip element
    const tooltip = document.createElement('div');
    tooltip.className = 'tooltip';
    document.body.appendChild(tooltip);

    // Play/Pause functionality
    playPauseBtn.addEventListener('click', () => {
      if (video.paused) {
        video.play();
        playPauseBtn.textContent = '❚❚';
      } else {
        video.pause();
        playPauseBtn.textContent = '►';
      }
    });

    // Mute/Unmute functionality
    function updateMuteButton() {
      muteBtn.textContent = video.muted ? '🔈+' : '🔈-';
    }

    // Initial mute button state
    updateMuteButton();

    muteBtn.addEventListener('click', () => {
      video.muted = !video.muted;
      updateMuteButton();
    });

    // Fullscreen functionality
    fullscreenBtn.addEventListener('click', () => {
      if (video.requestFullscreen) {
        video.requestFullscreen();
      } else if (video.webkitRequestFullscreen) { /* Safari */
        video.webkitRequestFullscreen();
      } else if (video.msRequestFullscreen) { /* IE11 */
        video.msRequestFullscreen();
      }
    });

    // Update progress bar as the video plays
    video.addEventListener('timeupdate', () => {
      const percent = (video.currentTime / video.duration) * 100;
      progressBar.style.width = percent + '%';
    });

    // Seek functionality
    progressContainer.addEventListener('click', (e) => {
      const rect = progressContainer.getBoundingClientRect();
      const offsetX = e.clientX - rect.left;
      const totalWidth = rect.width;
      const percent = offsetX / totalWidth;
      video.currentTime = percent * video.duration;
    });

    // Create progress dots after metadata is loaded
    function createProgressDots() {
      chapters.forEach((chapter) => {
        const dot = document.createElement('div');
        dot.classList.add('progress-dot');
        const seconds = timeToSeconds(chapter.time);
        const percent = (seconds / video.duration) * 100;
        dot.style.left = percent + '%';

        // Store the chapter title for tooltip
        dot.dataset.title = chapter.title;

        // Add event listeners for tooltip
        dot.addEventListener('mouseenter', (e) => {
          tooltip.textContent = chapter.title;
          tooltip.style.opacity = 1;
          const rect = dot.getBoundingClientRect();
          tooltip.style.left = rect.left + 'px';
          tooltip.style.top = rect.top - 30 + 'px'; // Position above the dot
        });

        dot.addEventListener('mouseleave', () => {
          tooltip.style.opacity = 0;
        });

        dot.addEventListener('click', (e) => {
          e.stopPropagation(); // Prevent triggering the parent click event
          goToTime(seconds);
        });

        progressBarContainer.appendChild(dot);
      });
    }

    // Wait for video metadata to be loaded before creating progress dots
    if (video.readyState >= 1) {
      createProgressDots();
    } else {
      video.addEventListener('loadedmetadata', createProgressDots);
    }

    // Update Play/Pause button text based on video state
    video.addEventListener('play', () => {
      playPauseBtn.textContent = '❚❚';
    });

    video.addEventListener('pause', () => {
      playPauseBtn.textContent = '►';
    });

    // Update mute button when video mute state changes
    video.addEventListener('volumechange', updateMuteButton);

Leave the first comment