'HTML + JavaScript custom player for multiple videos on a website page

EDIT: I have found the solution, posted and marked as the best answer below.

I'm coding a portfolio website for myself using HTML, CSS and JS and I need to add multiple videos on a lot of pages.

I followed some tutorials to learn how to customize the video player, but it only works for one specific video on the page. If I were to add more videos, I'd need to have one .js custom player file for each video and manually select them on the website.

How can I apply this single .js custom video player to all of my videos using purely javascript?

I have found similar topics about the subject here, but all of them uses jQuery and I'm struggling to make it work with javascript.

My HTML for the video player:

<section class="videoplayer">
<div class="c-video">
    <div id="video_player">
        <video src="./media/portfolio/videos/Show-Reel-2021.mp4" id="main-video"></video>
        <div class="progressAreaTime">00:00</div>
        <div class="controls">
            <div class="progress-area">
                <div class="progress-bar">
                    <span></span>
                </div>
                <div class="buffered-progress-bar"></div>
            </div>
            <div class="controls-list">
                <div class="controls-left">
                    <span class="icon">
                        <i class="material-icons fast-rewind" title="Retroceder 10 segundos">first_page</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons play_pause" title="Reproduzir">play_arrow</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons fast-forward" title="Avançar 10 segundos">last_page</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons volume" title="Sem áudio">volume_up</i>
                        <input type="range" min="0" max="100" class="volume_range">
                    </span>
                    <div class="timer">
                        <span class="current">00:00</span> / <span class="duration">0:00</span>
                    </div>
                </div>
                <div class="controls-right">
                    <span class="icon">
                        <i class="material-icons auto-play" title="A repetição automática está desativada"></i>
                    </span>
                    <span class="icon">
                        <i class="material-icons settingsBtn" title="Detalhes">settings</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons picture_in_picture" title="Miniplayer">picture_in_picture_alt</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons fullscreen" title="Tela inteira">fullscreen</i>
                    </span>
                </div>
            </div>
        </div>
        <div id="settings">
            <div class="playback">
                <span>Velocidade da Reprodução</span>
                <ul>
                    <li data-speed="0.25">0.25</li>
                    <li data-speed="0.5">0.5</li>
                    <li data-speed="0.75">0.75</li>
                    <li data-speed="1" class="active">Normal</li>
                    <li data-speed="1.25">1.25</li>
                    <li data-speed="1.5">1.5</li>
                    <li data-speed="1.75">1.75</li>
                    <li data-speed="2">2</li>
                </ul>
            </div>
        </div>
    </div>
</div>

My JavaScript for the video player:

// Select elements
const vidsrc = document.querySelector('#main-video').src;
const video_player = document.querySelector('#video_player'),
mainVideo = video_player.querySelector('#main-video'),
progressAreaTime = video_player.querySelector('.progressAreaTime'),
controls = video_player.querySelector('.controls'),
progressArea = video_player.querySelector('.progress-area'),
progress_Bar = video_player.querySelector('.progress-bar'),
buffered_Bar = video_player.querySelector('.buffered-progress-bar'),
fast_rewind = video_player.querySelector('.fast-rewind'),
play_pause = video_player.querySelector('.play_pause'),
fast_forward = video_player.querySelector('.fast-forward'),
volume = video_player.querySelector('.volume'),
volume_range = video_player.querySelector('.volume_range'),
current = video_player.querySelector('.current'),
totalDuration = video_player.querySelector('.duration'),
auto_play = video_player.querySelector('.auto-play'),
settingsBtn = video_player.querySelector('.settingsBtn'),
picture_in_picture = video_player.querySelector('.picture_in_picture'),
fullscreen = video_player.querySelector('.fullscreen'),
settings = video_player.querySelector('#settings'),
playback = video_player.querySelectorAll('.playback li');

mainVideo.addEventListener('loadeddata',()=>{
    setInterval(() => {
        let bufferedTime = mainVideo.buffered.end(0);
        let duration = mainVideo.duration;
        let width = (bufferedTime / duration) * 100;
        buffered_Bar.style.width = `${width}%`
    }, 500);
})

// Play
function playVideo() {
    play_pause.innerHTML = "pause";
    play_pause.title = "Pausar";
    video_player.classList.add('paused')
    mainVideo.play();
}

// Pause
function pauseVideo() {
    play_pause.innerHTML = "play_arrow";
    play_pause.title = "Reproduzir";
    video_player.classList.remove('paused')
    mainVideo.pause();
}

play_pause.addEventListener('click',()=>{
    const isVideoPaused = video_player.classList.contains('paused');
    isVideoPaused ? pauseVideo() : playVideo();
})

mainVideo.addEventListener('play',()=>{
    playVideo();
})

mainVideo.addEventListener('pause',()=>{
    pauseVideo();
})

// Rewind
fast_rewind.addEventListener('click',()=>{
    mainVideo.currentTime -= 10;
})

// Forward
fast_forward.addEventListener('click',()=>{
    mainVideo.currentTime += 10;
})

// Total duration
mainVideo.addEventListener("loadeddata",(e)=>{
    let videoDuration = e.target.duration;
    let totalMin = Math.floor(videoDuration / 60);
    let totalSec = Math.floor(videoDuration % 60);

    // Complete with zero at beggining
    totalSec < 10 ? totalSec = "0"+totalSec:totalSec;
    totalMin < 10 ? totalMin = "0"+totalMin:totalMin;
    totalDuration.innerHTML = `${totalMin}:${totalSec}`;
})

// Current duration
mainVideo.addEventListener('timeupdate',(e)=>{
    let currentVideoTime = e.target.currentTime;
    let currentMin = Math.floor(currentVideoTime / 60);
    let currentSec = Math.floor(currentVideoTime % 60);
   // Complete with zero at beggining
    currentSec < 10 ? currentSec = "0"+currentSec:currentSec; 
    currentMin < 10 ? currentMin = "0"+currentMin:currentMin; 
    current.innerHTML = `${currentMin}:${currentSec}`;

    let videoDuration = e.target.duration
    // Seek bar
    let progressWidth = (currentVideoTime / videoDuration) * 100;
    progress_Bar.style.width = `${progressWidth}%`;
})

// Update current duration
progressArea.addEventListener('click',(e)=>{
    let videoDuration = mainVideo.duration;
    let progressWidthval = progressArea.clientWidth;
    let ClickOffsetX = e.offsetX;
    mainVideo.currentTime = (ClickOffsetX / progressWidthval) * videoDuration;
})

// Volume
function changeVolume() {
    mainVideo.volume = volume_range.value / 100;
    if (volume_range.value == 0) {
        volume.innerHTML = "volume_off";
    }else if(volume_range.value < 40){
        volume.innerHTML = "volume_down";
    }else{
        volume.innerHTML = "volume_up";
    }
}

function muteVolume() {
    if (volume_range.value == 0) {
        volume_range.value = 80;
        mainVideo.volume = 0.8;
        volume.innerHTML = "volume_up";
        volume.title = "Sem áudio";
    }else{
        volume_range.value = 0;
        mainVideo.volume = 0;
        volume.innerHTML = "volume_off";
        volume.title = "Reativar o som";
    }
}

volume_range.addEventListener('change',()=>{
    changeVolume();
})

volume.addEventListener('click',()=>{
    muteVolume();
})

// Update on mouse move
progressArea.addEventListener('mousemove',(e)=>{
    let progressWidthval = progressArea.clientWidth;
    let x = e.offsetX;
    progressAreaTime.style.setProperty('--x',`${x}px`);
    progressAreaTime.style.display = "block";
    let videoDuration = mainVideo.duration;
    let progressTime = Math.floor((x/progressWidthval)*videoDuration);
    let currentMin = Math.floor(progressTime / 60);
    let currentSec = Math.floor(progressTime % 60);
   // Complete with zero at beggining
    currentSec < 10 ? currentSec = "0"+currentSec:currentSec; 
    currentMin < 10 ? currentMin = "0"+currentMin:currentMin; 
    progressAreaTime.innerHTML = `${currentMin}:${currentSec}`;
})

progressArea.addEventListener('mouseleave',()=>{
    progressAreaTime.style.display = "none";
})

// Loop
auto_play.addEventListener('click',()=>{
    auto_play.classList.toggle('active')
    if(auto_play.classList.contains('active')){
        auto_play.title = "A repetição automática está ativada";
    }else{
        auto_play.title = "A repetição automática está desativada";
    }
});

mainVideo.addEventListener("ended",()=>{
    if (auto_play.classList.contains('active')) {
        playVideo();
    }else{
        play_pause.innerHTML = "replay";
        play_pause.title = "Reproduzir novamente";
    }
});

// Picture in picture
picture_in_picture.addEventListener('click',()=>{
    mainVideo.requestPictureInPicture();
})

// Full screen
fullscreen.addEventListener('click',()=>{
    if (!video_player.classList.contains('openFullScreen')) {
        video_player.classList.add('openFullScreen');
        fullscreen.innerHTML = "fullscreen_exit";
        fullscreen.title = "Sair da tela inteira";
        video_player.requestFullscreen();
    }else{
        video_player.classList.remove('openFullScreen');
        fullscreen.innerHTML = "fullscreen";
        fullscreen.title = "Tela inteira";
        document.exitFullscreen();
    }
});

// Settings
settingsBtn.addEventListener('click',()=>{
    settings.classList.toggle('active');
    settingsBtn.classList.toggle('active');
})

// Speed
playback.forEach((event)=>{
    event.addEventListener('click',()=>{
        removeActiveClasses();
        event.classList.add('active');
        let speed = event.getAttribute('data-speed');
        mainVideo.playbackRate = speed;
    })
})

function removeActiveClasses() {
    playback.forEach(event => {
        event.classList.remove('active')
    });
}

// Get URL
let xhr = new XMLHttpRequest();
xhr.open("GET",vidsrc);
xhr.responseType = "arraybuffer";
xhr.onload = (e)=>{
    let blob = new Blob([xhr.response]);
    let url = URL.createObjectURL(blob);
    mainVideo.src = url;
}
xhr.send();

// Store duration
window.addEventListener('unload',()=>{
    let setDuration = localStorage.setItem('duration',`${mainVideo.currentTime}`);
    let setSrc = localStorage.setItem('src',`${mainVideo.getAttribute('src')}`);
})

window.addEventListener('load',()=>{
    let getDuration = localStorage.getItem('duration');
    let getSrc = localStorage.getItem('src');
    if (getSrc) {
        mainVideo.src = getSrc;
        mainVideo.currentTime = getDuration;
    }
})

mainVideo.addEventListener('contextmenu',(e)=>{
    e.preventDefault();
})

// Hide and show controls (mouse)
video_player.addEventListener('mouseover',()=>{
    controls.classList.add('active');
})

video_player.addEventListener('mouseleave',()=>{
    if (video_player.classList.contains('paused')) {
        if (settingsBtn.classList.contains('active')) {
            controls.classList.add('active');
        }else{
            controls.classList.remove('active')
        }
    }else{
        controls.classList.add('active')
    }
})

if (video_player.classList.contains('paused')) {
    if (settingsBtn.classList.contains('active')) {
        controls.classList.add('active');
    }else{
        controls.classList.remove('active')
    }
}else{
    controls.classList.add('active')
}

// Hide and show controls (mobile)
video_player.addEventListener('touchstart',()=>{
    controls.classList.add('active');
    setTimeout(() => {
        controls.classList.remove('active')
    }, 8000);
})

video_player.addEventListener('touchmove',()=>{
    if (video_player.classList.contains('paused')) {
        controls.classList.remove('active')
    }else{
        controls.classList.add('active')
    }
})


Solution 1:[1]

I HAVE FOUND THE SOLUTION

For anyone in the future seeking for the same solution, I was able to make it work by first selecting every videoplayer div:

const videoContainers = document.querySelectorAll(".video-container")

Then using the .forEach() function so that the code will generate a button for every individual videoplayer:

videoContainers.forEach((container) => {
  let playPauseBtn = container.querySelector(".play-pause-btn");
  let theaterBtn = container.querySelector(".theater-btn");
  let fullScreenBtn = container.querySelector(".full-screen-btn");
...

// The entire code for one videoplayer
}

You can reference "container" for the individual videoplayer or "document" for every videoplayer at once. It just works like magic!

Solution 2:[2]

Helper function which you can use to write the HTML skeleton once and then have it dynamically created for multiple instances

function renderVideoPlayers() {

  const sources = [
    '../my-video-src-1',
    '../my-video-src-2',
    '../my-video-src-3',
    '../my-video-src-4',
  ]

  const videoPlayers = sources.map((video) => `
  <section class="videoplayer">
  <div class="c-video">
    <div id="video_player">
        <video src="${video}" class="video"></video>
        <div class="progressAreaTime">00:00</div>
        <div class="controls">
            <div class="progress-area">
                <div class="progress-bar">
                    <span></span>
                </div>
                <div class="buffered-progress-bar"></div>
            </div>
            <div class="controls-list">
                <div class="controls-left">
                    <span class="icon">
                        <i class="material-icons fast-rewind" title="Retroceder 10 segundos">first_page</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons play_pause" title="Reproduzir">play_arrow</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons fast-forward" title="Avançar 10 segundos">last_page</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons volume" title="Sem áudio">volume_up</i>
                        <input type="range" min="0" max="100" class="volume_range">
                    </span>
                    <div class="timer">
                        <span class="current">00:00</span> / <span class="duration">0:00</span>
                    </div>
                </div>
                <div class="controls-right">
                    <span class="icon">
                        <i class="material-icons auto-play" title="A repetição automática está desativada"></i>
                    </span>
                    <span class="icon">
                        <i class="material-icons settingsBtn" title="Detalhes">settings</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons picture_in_picture" title="Miniplayer">picture_in_picture_alt</i>
                    </span>
                    <span class="icon">
                        <i class="material-icons fullscreen" title="Tela inteira">fullscreen</i>
                    </span>
                </div>
            </div>
        </div>
        <div id="settings">
            <div class="playback">
                <span>Velocidade da Reprodução</span>
                <ul>
                    <li data-speed="0.25">0.25</li>
                    <li data-speed="0.5">0.5</li>
                    <li data-speed="0.75">0.75</li>
                    <li data-speed="1" class="active">Normal</li>
                    <li data-speed="1.25">1.25</li>
                    <li data-speed="1.5">1.5</li>
                    <li data-speed="1.75">1.75</li>
                    <li data-speed="2">2</li>
                </ul>
            </div>
        </div>
    </div>
</div>
`)

  // get an element already on the page add the rendered
  // html for each video into that section.
  const container = document.getElementById('container')
  container.innerHTML = videoPlayers.join("")
}

// call this function when you want it to be displayed
renderVideoPlayers()
<html>
<head>
</head>
<body>
<div id="container">
</div>
</body>
</html>

You will also need to do the same with Javascript as well as ensure that there aren't multiple instance of the same id. This shouldn't be too hard as you can switch similiar styles to class and make the id's something like video-1, video-2, video-3, etc.

A couple notes

  • sources: this is an array of all video urls you wish to display on the page.
  • sources.map: will iterate through the array and interpolate a string with the video player HTML and video source
  • videoPlayers: is now an array of HTML markup
  • container: will be a parent element on the page where you wish to append all the HTML markup

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 lucasrvimieiro
Solution 2