Social Links
HTML
<!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Custom Video Player JavaScript | CodingAyush</title> <link rel="stylesheet" href="style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- These 3 links are only for icons --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> </head> <body> <div class="container show-controls"> <div class="wrapper"> <div class="video-timeline"> <div class="progress-area"> <span>00:00</span> <div class="progress-bar"></div> </div> </div> <ul class="video-controls"> <li class="options left"> <button class="volume"><i class="fa-solid fa-volume-high"></i></button> <input type="range" min="0" max="1" step="any"> <div class="video-timer"> <p class="current-time">00:00</p> <p class="separator"> / </p> <p class="video-duration">00:00</p> </div> </li> <li class="options center"> <button class="skip-backward"><i class="fas fa-backward"></i></button> <button class="play-pause"><i class="fas fa-play"></i></button> <button class="skip-forward"><i class="fas fa-forward"></i></button> </li> <li class="options right"> <div class="playback-content"> <button class="playback-speed"><span class="material-symbols-rounded">slow_motion_video</span></button> <ul class="speed-options"> <li data-speed="2">2x</li> <li data-speed="1.5">1.5x</li> <li data-speed="1" class="active">Normal</li> <li data-speed="0.75">0.75x</li> <li data-speed="0.5">0.5x</li> </ul> </div> <button class="pic-in-pic"><span class="material-icons">picture_in_picture_alt</span></button> <button class="fullscreen"><i class="fa-solid fa-expand"></i></button> </li> </ul> </div> <video src="demo-video.mp4"></video> </div> <script src="script.js"></script> </body> </html>
CSS
/* Import Google font - Poppins */ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap'); *{ margin: 0; padding: 0; box-sizing: border-box; font-family: 'Poppins', sans-serif; } body{ min-height: 100vh; background: #11131e; } body, .container, .video-controls, .video-timer, .options{ display: flex; align-items: center; justify-content: center; } .container{ width: 98%; user-select: none; overflow: hidden; max-width: 900px; border-radius: 5px; background: #000; aspect-ratio: 16 / 9; position: relative; box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); } .container.fullscreen{ max-width: 100%; width: 100%; height: 100vh; border-radius: 0px; } .wrapper{ position: absolute; left: 0; right: 0; z-index: 1; opacity: 0; bottom: -15px; transition: all 0.08s ease; } .container.show-controls .wrapper{ opacity: 1; bottom: 0; transition: all 0.13s ease; } .wrapper::before{ content: ""; bottom: 0; width: 100%; z-index: -1; position: absolute; height: calc(100% + 35px); pointer-events: none; background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); } .video-timeline{ height: 7px; width: 100%; cursor: pointer; } .video-timeline .progress-area{ height: 3px; position: relative; background: rgba(255, 255, 255, 0.6); } .progress-area span{ position: absolute; left: 50%; top: -25px; font-size: 13px; color: #fff; pointer-events: none; transform: translateX(-50%); } .progress-area .progress-bar{ width: 0%; height: 100%; position: relative; background: #2289ff; } .progress-bar::before{ content: ""; right: 0; top: 50%; height: 13px; width: 13px; position: absolute; border-radius: 50%; background: #2289ff; transform: translateY(-50%); } .progress-bar::before, .progress-area span{ display: none; } .video-timeline:hover .progress-bar::before, .video-timeline:hover .progress-area span{ display: block; } .wrapper .video-controls{ padding: 5px 20px 10px; } .video-controls .options{ width: 100%; } .video-controls .options:first-child{ justify-content: flex-start; } .video-controls .options:last-child{ justify-content: flex-end; } .options button{ height: 40px; width: 40px; font-size: 19px; border: none; cursor: pointer; background: none; color: #efefef; border-radius: 3px; transition: all 0.3s ease; } .options button :where(i, span) { height: 100%; width: 100%; line-height: 40px; } .options button:hover :where(i, span){ color: #fff; } .options button:active :where(i, span){ transform: scale(0.9); } .options button span{ font-size: 23px; } .options input{ height: 4px; margin-left: 3px; max-width: 75px; accent-color: #0078FF; } .options .video-timer{ color: #efefef; margin-left: 15px; font-size: 14px; } .video-timer .separator{ margin: 0 5px; font-size: 16px; font-family: "Open sans"; } .playback-content{ display: flex; position: relative; } .playback-content .speed-options{ position: absolute; list-style: none; left: -40px; bottom: 40px; width: 95px; overflow: hidden; opacity: 0; border-radius: 4px; pointer-events: none; background: rgba(255, 255, 255, 0.9); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); transition: opacity 0.13s ease; } .playback-content .speed-options.show{ opacity: 1; pointer-events: auto; } .speed-options li{ cursor: pointer; color: #000; font-size: 14px; margin: 2px 0; padding: 5px 0 5px 15px; transition: all 0.1s ease; } .speed-options li:where(:first-child, :last-child){ margin: 0px; } .speed-options li:hover{ background: #dfdfdf; } .speed-options li.active{ color: #fff; background: #3e97fd; } .container video{ width: 100%; } @media screen and (max-width: 540px) { .wrapper .video-controls{ padding: 3px 10px 7px; } .options input, .progress-area span{ display: none!important; } .options button{ height: 30px; width: 30px; font-size: 17px; } .options .video-timer{ margin-left: 5px; } .video-timer .separator{ font-size: 14px; margin: 0 2px; } .options button :where(i, span) { line-height: 30px; } .options button span{ font-size: 21px; } .options .video-timer, .progress-area span, .speed-options li{ font-size: 12px; } .playback-content .speed-options{ width: 75px; left: -30px; bottom: 30px; } .speed-options li{ margin: 1px 0; padding: 3px 0 3px 10px; } .right .pic-in-pic{ display: none; } }
JS
const container = document.querySelector(".container"), mainVideo = container.querySelector("video"), videoTimeline = container.querySelector(".video-timeline"), progressBar = container.querySelector(".progress-bar"), volumeBtn = container.querySelector(".volume i"), volumeSlider = container.querySelector(".left input"); currentVidTime = container.querySelector(".current-time"), videoDuration = container.querySelector(".video-duration"), skipBackward = container.querySelector(".skip-backward i"), skipForward = container.querySelector(".skip-forward i"), playPauseBtn = container.querySelector(".play-pause i"), speedBtn = container.querySelector(".playback-speed span"), speedOptions = container.querySelector(".speed-options"), pipBtn = container.querySelector(".pic-in-pic span"), fullScreenBtn = container.querySelector(".fullscreen i"); let timer; const hideControls = () => { if(mainVideo.paused) return; timer = setTimeout(() => { container.classList.remove("show-controls"); }, 3000); } hideControls(); container.addEventListener("mousemove", () => { container.classList.add("show-controls"); clearTimeout(timer); hideControls(); }); const formatTime = time => { let seconds = Math.floor(time % 60), minutes = Math.floor(time / 60) % 60, hours = Math.floor(time / 3600); seconds = seconds < 10 ? `0${seconds}` : seconds; minutes = minutes < 10 ? `0${minutes}` : minutes; hours = hours < 10 ? `0${hours}` : hours; if(hours == 0) { return `${minutes}:${seconds}` } return `${hours}:${minutes}:${seconds}`; } videoTimeline.addEventListener("mousemove", e => { let timelineWidth = videoTimeline.clientWidth; let offsetX = e.offsetX; let percent = Math.floor((offsetX / timelineWidth) * mainVideo.duration); const progressTime = videoTimeline.querySelector("span"); offsetX = offsetX < 20 ? 20 : (offsetX > timelineWidth - 20) ? timelineWidth - 20 : offsetX; progressTime.style.left = `${offsetX}px`; progressTime.innerText = formatTime(percent); }); videoTimeline.addEventListener("click", e => { let timelineWidth = videoTimeline.clientWidth; mainVideo.currentTime = (e.offsetX / timelineWidth) * mainVideo.duration; }); mainVideo.addEventListener("timeupdate", e => { let {currentTime, duration} = e.target; let percent = (currentTime / duration) * 100; progressBar.style.width = `${percent}%`; currentVidTime.innerText = formatTime(currentTime); }); mainVideo.addEventListener("loadeddata", () => { videoDuration.innerText = formatTime(mainVideo.duration); }); const draggableProgressBar = e => { let timelineWidth = videoTimeline.clientWidth; progressBar.style.width = `${e.offsetX}px`; mainVideo.currentTime = (e.offsetX / timelineWidth) * mainVideo.duration; currentVidTime.innerText = formatTime(mainVideo.currentTime); } volumeBtn.addEventListener("click", () => { if(!volumeBtn.classList.contains("fa-volume-high")) { mainVideo.volume = 0.5; volumeBtn.classList.replace("fa-volume-xmark", "fa-volume-high"); } else { mainVideo.volume = 0.0; volumeBtn.classList.replace("fa-volume-high", "fa-volume-xmark"); } volumeSlider.value = mainVideo.volume; }); volumeSlider.addEventListener("input", e => { mainVideo.volume = e.target.value; if(e.target.value == 0) { return volumeBtn.classList.replace("fa-volume-high", "fa-volume-xmark"); } volumeBtn.classList.replace("fa-volume-xmark", "fa-volume-high"); }); speedOptions.querySelectorAll("li").forEach(option => { option.addEventListener("click", () => { mainVideo.playbackRate = option.dataset.speed; speedOptions.querySelector(".active").classList.remove("active"); option.classList.add("active"); }); }); document.addEventListener("click", e => { if(e.target.tagName !== "SPAN" || e.target.className !== "material-symbols-rounded") { speedOptions.classList.remove("show"); } }); fullScreenBtn.addEventListener("click", () => { container.classList.toggle("fullscreen"); if(document.fullscreenElement) { fullScreenBtn.classList.replace("fa-compress", "fa-expand"); return document.exitFullscreen(); } fullScreenBtn.classList.replace("fa-expand", "fa-compress"); container.requestFullscreen(); }); speedBtn.addEventListener("click", () => speedOptions.classList.toggle("show")); pipBtn.addEventListener("click", () => mainVideo.requestPictureInPicture()); skipBackward.addEventListener("click", () => mainVideo.currentTime -= 5); skipForward.addEventListener("click", () => mainVideo.currentTime += 5); mainVideo.addEventListener("play", () => playPauseBtn.classList.replace("fa-play", "fa-pause")); mainVideo.addEventListener("pause", () => playPauseBtn.classList.replace("fa-pause", "fa-play")); playPauseBtn.addEventListener("click", () => mainVideo.paused ? mainVideo.play() : mainVideo.pause()); videoTimeline.addEventListener("mousedown", () => videoTimeline.addEventListener("mousemove", draggableProgressBar)); document.addEventListener("mouseup", () => videoTimeline.removeEventListener("mousemove", draggableProgressBar));