Skip to content

Instantly share code, notes, and snippets.

@andregivenchy
Last active November 19, 2022 10:30
Show Gist options
  • Save andregivenchy/03260ca48baf2ecaca4a317ea8d0f78f to your computer and use it in GitHub Desktop.
Save andregivenchy/03260ca48baf2ecaca4a317ea8d0f78f to your computer and use it in GitHub Desktop.
Segern-Custom-HTML5-VideoPlayer
<body>
<div class="video-container">
<video id="video" disableRemotePlayback poster="https://cdn-std.droplr.net/files/acc_205555/d00I61" src="https://cdn.viqeo.tv/storage/72/59/eb441789490697bc2e574bd8a83e5d04.mp4"></video>
<div class="player-state">
<span class="state-btn state-backward">
<ion-icon name="play-back-outline"></ion-icon>
<span class="backward-duration">5</span>
</span>
<span class="main-state state-btn">
<ion-icon name="play-outline"></ion-icon>
</span>
<span class="custom-loader"></span>
<span class="state-btn state-forward">
<span class="forward-duration">5</span>
<ion-icon name="play-forward-outline"></ion-icon>
</span>
</div>
<div class="controls">
<div class="duration">
<div class="current-time"></div>
<div class="hover-time">
<span class="hover-duration"></span>
</div>
<div class="buffer"></div>
</div>
<div class="btn-controls">
<div class="btn-con">
<span class="play-pause control-btn">
<ion-icon name="play-outline"></ion-icon>
</span>
<span class="volume">
<span class="mute-unmute control-btn">
<ion-icon name="volume-high-outline"></ion-icon>
</span>
<div class="max-vol">
<div class="current-vol"></div>
</div>
</span>
<span class="time-container">
<span class="current-duration">0:00</span>
<span>/</span>
<span class="total-duration">0:00</span>
</span>
</div>
<div class="right-controls">
<span class="backward control-btn" title="5 backward">
<ion-icon name="play-back-outline"></ion-icon>
</span>
<span class="forward control-btn" title="5 forward">
<ion-icon name="play-forward-outline"></ion-icon>
</span>
<span class="mini-player control-btn">
<ion-icon name="albums-outline"></ion-icon>
</span>
<span class="settings control-btn">
<span class="setting-btn">
<ion-icon name="options-outline"></ion-icon>
</span>
<ul class="setting-menu">
<li data-value="0.25">0.25x</li>
<li data-value="0.5">0.5x</li>
<li data-value="0.75">0.75x</li>
<li data-value="1" class="speed-active">1x</li>
<li data-value="1.25">1.25x</li>
<li data-value="1.5">1.5x</li>
<li data-value="1.75">1.75x</li>
<li data-value="2">2x</li>
</ul>
</span>
<span class="theater-btn control-btn">
<span class="theater-default">
<ion-icon name="tablet-landscape-outline"></ion-icon>
</span>
<span class="theater-active">
<ion-icon name="tv-outline"></ion-icon>
</span>
</span>
<span class="fullscreen-btn control-btn" title="fullscreen">
<span class="full">
<ion-icon name="scan-outline"></ion-icon>
</span>
<span class="contract">
<ion-icon name="contract-outline"></ion-icon>
</span>
</span>
</div>
</div>
</div>
</div>
</body>
.video-container {
width: 1024px;
height: calc((9 / 16) * 1024px);
position: relative;
color: #ffffff;
overflow: hidden;
background: rgb(255, 255, 255, 1.0);
border-radius: .5rem;
display: flex;
align-items: center;
justify-content: center;
}
.video-container video {
max-width: 100%;
max-height: 100%;
}
.custom-loader {
position: absolute;
width: 50px;
height: 50px;
border-radius: 50%;
border-top: 5px solid transparent;
border-right: 5px solid #ffffff;
border-left: 5px solid #ffffff;
border-bottom: 5px solid #ffffff;
z-index: 9999;
animation: rotation 2s infinite linear;
-webkit-animation: rotation 2s infinite linear;
display: none;
}
.player-state {
display: flex;
position: absolute;
width: 100%;
justify-content: space-around;
}
.state-btn {
font-size: 2.3rem;
width: 80px;
height: 80px;
cursor: pointer;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
background: rgba(1, 2, 3, 0.25);
backdrop-filter: blur(10px);
z-index: 9999;
-webkit-tap-highlight-color: transparent;
opacity: 0;
user-select: none;
transform: scale(0);
}
.state-forward,
.state-backward {
font-size: 1.5rem;
}
.state-forward .forward-duration {
margin-right: 0.5rem;
}
.state-backward .backward-duration {
margin-left: 0.5rem;
}
.animate-state {
animation: playPause 0.5s forwards;
}
.show-state {
transform: scale(1);
opacity: 1;
}
.show-controls {
opacity: 1 !important;
transform: translateY(0) !important;
visibility: visible !important;
}
.controls {
position: absolute;
bottom: 0;
left: 0;
padding: 0rem 1rem 0.5rem 1rem;
width: 100%;
background: linear-gradient(to top, #000000b8 -100%, transparent);
backdrop-filter: blur(0px);
box-sizing: border-box;
opacity: 0;
transform: translateY(40px);
visibility: hidden;
z-index: 99;
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.duration {
position: relative;
width: 100%;
height: .5rem;
background: rgb(255, 255, 255, 0.0);
backdrop-filter: blur(10px);
cursor: pointer;
transition: all 0.2s;
}
.duration:hover {
height: .5rem;
}
.duration .buffer {
height: 100%;
position: absolute;
inset: 0;
background-color: rgb(255, 255, 255, .05);
backdrop-filter: blur(10px);
z-index: 9;
width: 0;
}
.hover-time {
height: 100%;
position: absolute;
inset: 0;
background: #ffffff9a;
z-index: 99;
display: flex;
align-items: center;
width: 0;
}
.hover-time .hover-duration {
position: absolute;
right: calc((-25px / 2));
top: -25px;
background: #3c3c3ca7;
padding: 0.2rem;
border-radius: 5px;
font-size: 0.7rem;
visibility: hidden;
font-weight: bold;
opacity: 0;
transform: scale(0);
}
.duration:hover .hover-time .hover-duration {
visibility: visible;
opacity: 1;
transition: all 0.2s;
transform: scale(1);
}
.duration .current-time {
height: 100%;
position: absolute;
inset: 0;
background: rgb(0, 0, 0, 1.0);
z-index: 999;
display: flex;
align-items: center;
width: 0;
}
.current-time::before {
content: "";
position: absolute;
right: calc((-15px / 2));
background: #ff1900;
width: .5rem;
height: 1.5rem;
border-radius: 5%;
transition: all 0.2s;
visibility: hidden;
transform: scale(0);
}
.duration:hover .current-time::before {
visibility: visible;
transform: scale(1);
}
.btn-controls {
padding-top: 1rem;
font-size: 1.2rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.btn-con {
display: flex;
align-items: center;
}
.btn-con,
.btn-controls > span {
cursor: pointer;
}
.play-pause {
display: flex;
margin-right: 0.5rem;
}
.control-btn {
width: 2.2rem;
height: 2.2rem;
border-radius: 50%;
background: rgb(1, 2, 3, 0.0);
backdrop-filter: blur(0px);
display: flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
box-sizing: border-box;
position: relative;
margin-right: 0.5rem;
}
.control-btn:last-child {
margin-right: 0;
}
.control-btn:hover {
background: rgb(1, 2, 3, 0.15);
backdrop-filter: blur(10px);
}
.control-btn::before {
content: "";
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background: #2424246a;
position: absolute;
transition: all 0.1s;
transform: scale(0);
}
.control-btn:active::before {
transform: scale(1);
border: 1px solid #3131315c;
}
.time-container {
font-size: 13px;
font-weight: 500;
padding-left: 0.7rem;
}
.volume {
display: flex;
align-items: center;
cursor: default;
margin-right:1rem;
}
.mute-unmute {
display: flex;
cursor: pointer;
}
.max-vol {
height: 3px;
cursor: pointer;
background: #ffffff6e;
transition: all 0.1s;
width: 0;
visibility: hidden;
transform: scaleX(0);
transform-origin: left;
display: flex;
align-items: center;
}
.max-vol.show {
width: 56px;
visibility: visible;
transform: scaleX(1);
}
.current-vol {
position: absolute;
inset: 0;
width: 20%;
height: 100%;
background: #fff;
display: flex;
transition: none;
align-items: center;
}
.current-vol::before {
content: "";
position: absolute;
right: -5px;
width: 12px;
height: 12px;
display: block;
border-radius: 50%;
background: #eee;
}
.setting-menu {
opacity: 0;
visibility: hidden;
list-style: none;
padding-inline-start: 0;
margin-block-start: 0;
margin-block-end: 0;
position: absolute;
bottom: 4.5rem;
transition: all 0.2s;
background: rgba(28, 28, 28, 0.9);
transform: scaleY(0);
transform-origin: bottom;
border-radius: 5px;
}
.setting-menu li {
padding: 0.3rem 2rem;
margin: 0.5rem;
transition: all 0.2s;
border-radius: 5px;
font-size: 0.9rem;
font-weight: 500;
}
.speed-active {
background: rgb(23, 23, 23);
}
.setting-menu li:hover {
background: rgb(31, 31, 31);
}
.setting-btn {
display: flex;
}
.show-setting-menu {
opacity: 1;
transform: scaleY(1);
visibility: visible;
}
.theater {
width: 100% !important;
}
.theater-btn .theater-default,
.theater-btn .theater-active {
display: flex;
}
.video-container.theater .theater-default {
display: none;
}
.video-container:not(.theater) .theater-active {
display: none;
}
.fullscreen {
position: absolute !important;
max-width: 100% !important;
width: 100% !important;
height: 100% !important;
display: flex !important;
background: #000 !important;
align-items: center !important;
}
.right-controls {
display: flex;
align-items: center;
}
.right-controls span {
cursor: pointer;
}
.full,
.contract {
display: none;
}
.video-container:not(.fullscreen) .full {
display: flex;
}
.video-container.fullscreen .contract {
display: flex;
}
@keyframes playPause {
50% {
opacity: 1;
transform: scale(1.1);
}
100% {
opacity: 0;
transform: scale(1);
}
}
@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
const video = document.querySelector("video");
const fullscreen = document.querySelector(".fullscreen-btn");
const playPause = document.querySelector(".play-pause");
const volume = document.querySelector(".volume");
const currentTime = document.querySelector(".current-time");
const duration = document.querySelector(".duration");
const buffer = document.querySelector(".buffer");
const totalDuration = document.querySelector(".total-duration");
const currentDuration = document.querySelector(".current-duration");
const controls = document.querySelector(".controls");
const videoContainer = document.querySelector(".video-container");
const currentVol = document.querySelector(".current-vol");
const totalVol = document.querySelector(".max-vol");
const mainState = document.querySelector(".main-state");
const muteUnmute = document.querySelector(".mute-unmute");
const forward = document.querySelector(".forward");
const backward = document.querySelector(".backward");
const hoverTime = document.querySelector(".hover-time");
const hoverDuration = document.querySelector(".hover-duration");
const miniPlayer = document.querySelector(".mini-player");
const settingsBtn = document.querySelector(".setting-btn");
const settingMenu = document.querySelector(".setting-menu");
const theaterBtn = document.querySelector(".theater-btn");
const speedButtons = document.querySelectorAll(".setting-menu li");
const backwardSate = document.querySelector(".state-backward");
const forwardSate = document.querySelector(".state-forward");
const loading = document.querySelector(".custom-loader");
let isPlaying = false,
mouseDownProgress = false,
mouseDownVol = false,
isCursorOnControls = false,
muted = false,
timeout,
volumeVal = 1,
mouseOverDuration = false,
touchClientX = 0,
touchPastDurationWidth = 0,
touchStartTime = 0;
currentVol.style.width = volumeVal * 100 + "%";
// Video Event Listeners
video.addEventListener("loadedmetadata", canPlayInit);
video.addEventListener("play", play);
video.addEventListener("pause", pause);
video.addEventListener("progress", handleProgress);
videoContainer.addEventListener("click", toggleMainState);
video.onwaiting = function () {
loading.style.display = "block";
};
video.onplaying = function () {
loading.style.display = "none";
};
fullscreen.addEventListener("click", toggleFullscreen);
videoContainer.addEventListener("fullscreenchange", () => {
videoContainer.classList.toggle("fullscreen", document.fullscreenElement);
});
playPause.addEventListener("click", (e) => {
if (!isPlaying) {
play();
} else {
pause();
}
});
duration.addEventListener("click", navigate);
duration.addEventListener("mousedown", (e) => {
mouseDownProgress = true;
navigate(e);
});
totalVol.addEventListener("mousedown", (e) => {
mouseDownVol = true;
handleVolume(e);
});
document.addEventListener("mouseup", (e) => {
mouseDownProgress = false;
mouseDownVol = false;
});
document.addEventListener("mousemove", handleMousemove);
duration.addEventListener("mouseenter", (e) => {
mouseOverDuration = true;
});
duration.addEventListener("mouseleave", (e) => {
mouseOverDuration = false;
hoverTime.style.width = 0;
hoverDuration.innerHTML = "";
});
videoContainer.addEventListener("mouseleave", hideControls);
videoContainer.addEventListener("mousemove", (e) => {
controls.classList.add("show-controls");
hideControls();
});
videoContainer.addEventListener("touchstart", (e) => {
controls.classList.add("show-controls");
touchClientX = e.changedTouches[0].clientX;
const currentTimeRect = currentTime.getBoundingClientRect();
touchPastDurationWidth = currentTimeRect.width;
touchStartTime = e.timeStamp;
});
videoContainer.addEventListener("touchend", () => {
hideControls();
touchClientX = 0;
touchPastDurationWidth = 0;
touchStartTime = 0;
});
videoContainer.addEventListener("touchmove", touchNavigate);
function touchNavigate(e) {
hideControls();
if (e.timeStamp - touchStartTime > 500) {
const durationRect = duration.getBoundingClientRect();
const clientX = e.changedTouches[0].clientX;
const value = Math.min(
Math.max(
0,
touchPastDurationWidth + (clientX - touchClientX) * 0.2
),
durationRect.width
);
currentTime.style.width = value + "px";
video.currentTime = (value / durationRect.width) * video.duration;
currentDuration.innerHTML = showDuration(video.currentTime);
}
}
controls.addEventListener("mouseenter", (e) => {
controls.classList.add("show-controls");
isCursorOnControls = true;
});
controls.addEventListener("mouseleave", (e) => {
isCursorOnControls = false;
});
mainState.addEventListener("click", toggleMainState);
mainState.addEventListener("animationend", handleMainSateAnimationEnd);
muteUnmute.addEventListener("click", toggleMuteUnmute);
muteUnmute.addEventListener("mouseenter", (e) => {
if (!muted) {
totalVol.classList.add("show");
} else {
totalVol.classList.remove("show");
}
});
muteUnmute.addEventListener("mouseleave", (e) => {
if (e.relatedTarget != volume) {
totalVol.classList.remove("show");
}
});
forward.addEventListener("click", handleForward);
forwardSate.addEventListener("animationend", () => {
forwardSate.classList.remove("show-state");
forwardSate.classList.remove("animate-state");
});
backward.addEventListener("click", handleBackward);
backwardSate.addEventListener("animationend", () => {
backwardSate.classList.remove("show-state");
backwardSate.classList.remove("animate-state");
});
miniPlayer.addEventListener("click", toggleMiniPlayer);
theaterBtn.addEventListener("click", toggleTheater);
settingsBtn.addEventListener("click", handleSettingMenu);
speedButtons.forEach((btn) => {
btn.addEventListener("click", handlePlaybackRate);
});
document.addEventListener("keydown", (e) => {
const tagName = document.activeElement.tagName.toLowerCase();
if (tagName === "input") return;
if (e.key.match(/[0-9]/gi)) {
video.currentTime = (video.duration / 100) * (parseInt(e.key) * 10);
currentTime.style.width = parseInt(e.key) * 10 + "%";
}
switch (e.key.toLowerCase()) {
case " ":
if (tagName === "button") return;
if (isPlaying) {
video.pause();
} else {
video.play();
}
break;
case "f":
toggleFullscreen();
break;
case "arrowright":
handleForward();
break;
case "arrowleft":
handleBackward();
break;
case "t":
toggleTheater();
break;
case "i":
toggleMiniPlayer();
break;
case "m":
toggleMuteUnmute();
break;
case "+":
handlePlaybackRateKey("increase");
break;
case "-":
handlePlaybackRateKey();
break;
default:
break;
}
});
function canPlayInit() {
totalDuration.innerHTML = showDuration(video.duration);
video.volume = volumeVal;
muted = video.muted;
if (video.paused) {
controls.classList.add("show-controls");
mainState.classList.add("show-state");
handleMainStateIcon(`<ion-icon name="play-outline"></ion-icon>`);
}
}
function play() {
video.play();
isPlaying = true;
playPause.innerHTML = `<ion-icon name="pause-outline"></ion-icon>`;
mainState.classList.remove("show-state");
handleMainStateIcon(`<ion-icon name="pause-outline"></ion-icon>`);
// watchInterval();
}
function watchInterval() {
if (isPlaying) {
requestAnimationFrame(watchInterval);
handleProgressBar();
}
}
video.ontimeupdate = handleProgressBar;
function handleProgressBar() {
currentTime.style.width = (video.currentTime / video.duration) * 100 + "%";
currentDuration.innerHTML = showDuration(video.currentTime);
}
function pause() {
video.pause();
isPlaying = false;
playPause.innerHTML = `<ion-icon name="play-outline"></ion-icon>`;
controls.classList.add("show-controls");
mainState.classList.add("show-state");
handleMainStateIcon(`<ion-icon name="play-outline"></ion-icon>`);
if (video.ended) {
currentTime.style.width = 100 + "%";
}
}
function navigate(e) {
const totalDurationRect = duration.getBoundingClientRect();
const width = Math.min(
Math.max(0, e.clientX - totalDurationRect.x),
totalDurationRect.width
);
currentTime.style.width = (width / totalDurationRect.width) * 100 + "%";
video.currentTime = (width / totalDurationRect.width) * video.duration;
}
function showDuration(time) {
const hours = Math.floor(time / 60 ** 2);
const min = Math.floor((time / 60) % 60);
const sec = Math.floor(time % 60);
if (hours > 0) {
return `${formatter(hours)}:${formatter(min)}:${formatter(sec)}`;
} else {
return `${formatter(min)}:${formatter(sec)}`;
}
}
function formatter(number) {
return new Intl.NumberFormat({}, { minimumIntegerDigits: 2 }).format(
number
);
}
function toggleMuteUnmute() {
if (!muted) {
video.volume = 0;
muted = true;
muteUnmute.innerHTML = `<ion-icon name="volume-mute-outline"></ion-icon>`;
handleMainStateIcon(`<ion-icon name="volume-mute-outline"></ion-icon>`);
totalVol.classList.remove("show");
} else {
video.volume = volumeVal;
muted = false;
totalVol.classList.add("show");
handleMainStateIcon(`<ion-icon name="volume-high-outline"></ion-icon>`);
muteUnmute.innerHTML = `<ion-icon name="volume-high-outline"></ion-icon>`;
}
}
function hideControls() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
if (isPlaying && !isCursorOnControls) {
controls.classList.remove("show-controls");
settingMenu.classList.remove("show-setting-menu");
}
}, 1000);
}
function toggleMainState(e) {
e.stopPropagation();
if (!e.path.includes(controls)) {
if (!isPlaying) {
play();
} else {
pause();
}
}
}
function handleVolume(e) {
const totalVolRect = totalVol.getBoundingClientRect();
currentVol.style.width =
Math.min(Math.max(0, e.clientX - totalVolRect.x), totalVolRect.width) +
"px";
volumeVal = Math.min(
Math.max(0, (e.clientX - totalVolRect.x) / totalVolRect.width),
1
);
video.volume = volumeVal;
}
function handleProgress() {
if (!video.buffered || !video.buffered.length) {
return;
}
const width = (video.buffered.end(0) / video.duration) * 100 + "%";
buffer.style.width = width;
}
function toggleFullscreen() {
if (!document.fullscreenElement) {
videoContainer.requestFullscreen();
handleMainStateIcon(`<ion-icon name="scan-outline"></ion-icon>`);
} else {
handleMainStateIcon(` <ion-icon name="contract-outline"></ion-icon>`);
document.exitFullscreen();
}
}
function handleMousemove(e) {
if (mouseDownProgress) {
e.preventDefault();
navigate(e);
}
if (mouseDownVol) {
handleVolume(e);
}
if (mouseOverDuration) {
const rect = duration.getBoundingClientRect();
const width = Math.min(Math.max(0, e.clientX - rect.x), rect.width);
const percent = (width / rect.width) * 100;
hoverTime.style.width = width + "px";
hoverDuration.innerHTML = showDuration(
(video.duration / 100) * percent
);
}
}
function handleForward() {
forwardSate.classList.add("show-state");
forwardSate.classList.add("animate-state");
video.currentTime += 5;
handleProgressBar();
}
function handleBackward() {
backwardSate.classList.add("show-state");
backwardSate.classList.add("animate-state");
video.currentTime -= 5;
handleProgressBar();
}
function handleMainStateIcon(icon) {
mainState.classList.add("animate-state");
mainState.innerHTML = icon;
}
function handleMainSateAnimationEnd() {
mainState.classList.remove("animate-state");
if (!isPlaying) {
mainState.innerHTML = `<ion-icon name="play-outline"></ion-icon>`;
}
if (document.pictureInPictureElement) {
mainState.innerHTML = ` <ion-icon name="tv-outline"></ion-icon>`;
}
}
function toggleTheater() {
videoContainer.classList.toggle("theater");
if (videoContainer.classList.contains("theater")) {
handleMainStateIcon(
`<ion-icon name="tablet-landscape-outline"></ion-icon>`
);
} else {
handleMainStateIcon(`<ion-icon name="tv-outline"></ion-icon>`);
}
}
function toggleMiniPlayer() {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
handleMainStateIcon(`<ion-icon name="magnet-outline"></ion-icon>`);
} else {
video.requestPictureInPicture();
handleMainStateIcon(`<ion-icon name="albums-outline"></ion-icon>`);
}
}
function handleSettingMenu() {
settingMenu.classList.toggle("show-setting-menu");
}
function handlePlaybackRate(e) {
video.playbackRate = parseFloat(e.target.dataset.value);
speedButtons.forEach((btn) => {
btn.classList.remove("speed-active");
});
e.target.classList.add("speed-active");
settingMenu.classList.remove("show-setting-menu");
}
function handlePlaybackRateKey(type = "") {
if (type === "increase" && video.playbackRate < 2) {
video.playbackRate += 0.25;
} else if (video.playbackRate > 0.25 && type !== "increase") {
video.playbackRate -= 0.25;
}
handleMainStateIcon(
`<span style="font-size: 1.4rem">${video.playbackRate}x</span>`
);
speedButtons.forEach((btn) => {
btn.classList.remove("speed-active");
if (btn.dataset.value == video.playbackRate) {
btn.classList.add("speed-active");
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment