0% found this document useful (0 votes)
6 views21 pages

Vhs

The document outlines the structure and styling of an enhanced video player that supports HLS streaming. It includes HTML, CSS, and JavaScript components for video controls, playback speed, quality settings, and volume control. The design emphasizes a user-friendly interface with responsive elements and customizable settings.

Uploaded by

mayankk6582
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views21 pages

Vhs

The document outlines the structure and styling of an enhanced video player that supports HLS streaming. It includes HTML, CSS, and JavaScript components for video controls, playback speed, quality settings, and volume control. The design emphasizes a user-friendly interface with responsive elements and customizable settings.

Uploaded by

mayankk6582
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 21

<!

DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Video Player with HLS Support</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/
all.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.5.2/video-
js.min.css" rel="stylesheet">

<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-http-streaming/3.3.0/
videojs-http-streaming.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/video.js/8.5.2/video.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-quality-
levels/4.0.0/videojs-contrib-quality-levels.min.js"></script>

<style>
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: #000;
color: #fff;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

.video-container {
position: relative;
width: 80%;
max-width: 800px;
background-color: transparent;
aspect-ratio: 16/9;
}

/* Fullscreen styles */
.video-container:fullscreen {
width: 100%;
height: 100%;
max-width: none;
display: flex;
justify-content: center;
align-items: center;
background: black;
aspect-ratio: auto;
}

.video-container:-webkit-full-screen {
width: 100%;
height: 100%;
max-width: none;
display: flex;
justify-content: center;
align-items: center;
background: black;
aspect-ratio: auto;
}

.video-container:-moz-full-screen {
width: 100%;
height: 100%;
max-width: none;
display: flex;
justify-content: center;
align-items: center;
background: black;
aspect-ratio: auto;
}

.video-js {
width: 100%;
height: 100%;
background-color: transparent;
}

.vjs-quality-selector {
display: none !important;
}

.controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
box-sizing: border-box;
z-index: 2;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.8) 0%,
rgba(0, 0, 0, 0.6) 0%,
rgba(0, 0, 0, 0) 100%
);
height: 100px;
padding-top: 60px;
}

.progress-container {
position: absolute;
bottom: 50px;
left: 15px;
right: 15px;
height: 20px;
cursor: pointer;
display: flex;
align-items: center;
z-index: 3;
}

.progress-bar {
position: relative;
width: 100%;
height: 3px;
background: #333;
border-radius: 2px;
}

.progress {
position: absolute;
top: 0.15px;
left: 0;
width: 0;
height: 3px;
background: #5a4bda;
border-radius: 2px;
transition: width 0.05s linear;
box-shadow:
0 0 6px 3px rgba(90, 75, 218, 0.7),
0 0 4px 1px rgba(90, 75, 218, 0.9);
}

.control-button {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 5px;
margin: 0 5px;
font-size: 1.2em;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}

.left-controls, .right-controls {
display: flex;
align-items: center;
gap: 10px;
}

.time-info {
position: absolute;
bottom: 70px;
left: 15px;
display: flex;
align-items: center;
gap: 10px;
font-size: 16px;
font-weight: bold;
z-index: 3;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}

.end-time {
position: absolute;
bottom: 70px;
right: 15px;
font-size: 16px;
color: white;
font-weight: bold;
z-index: 3;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}

.playback-speed {
background: white;
color: black;
padding: 1.75px 5px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
}

.volume-control {
position: relative;
display: flex;
align-items: center;
}

.volume-slider {
position: absolute;
left: 40px;
width: 0;
height: 40px;
transition: width 0.3s ease, opacity 0.3s ease;
opacity: 0;
border-radius: 8px;
display: flex;
align-items: center;
padding: 0 10px;
pointer-events: none;
background: rgba(0, 0, 0, 0.8);
}

.volume-slider.active {
width: 120px;
opacity: 1;
pointer-events: all;
}

.volume-percentage {
position: absolute;
background: white;
color: black;
padding: 3px 5px;
border-radius: 5px;
font-size: 12px;
top: -15px;
transform: translateX(-50%);
pointer-events: none;
transition: left 0.1s ease;
}

.volume-percentage::after {
content: '';
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid white;
}

input[type="range"] {
-webkit-appearance: none;
width: 100px;
height: 4px;
background: rgba(255, 255, 255, 0.4);
border-radius: 2px;
position: relative;
}

input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
cursor: pointer;
position: relative;
z-index: 2;
}

.volume-track {
position: absolute;
left: 10px;
height: 4px;
background: white;
border-radius: 2px;
pointer-events: none;
}

.settings-menu {
position: absolute;
bottom: 50px;
right: 80px;
width: 330px;
display: none;
z-index: 3;
}

.menu-container {
background: #17171C;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
position: absolute;
bottom: 0;
left: 0;
right: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 142px;
}

.menu-container.hidden {
pointer-events: none;
visibility: hidden;
}
.menu-content {
opacity: 1;
transition: opacity 0.2s ease;
padding: 16px 0;
height: 100%;
}

.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
}

.label {
color: #d9d9da;
font-size: 16px;
font-weight: 500;
width: 136px;
}

.value-selector {
background: #23232a;
padding: 10px 12px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}

.value-selector:hover {
background: #1E1E24;
}

.selected-value {
color: #d9d9da;
font-size: 14px;
font-weight: 500;
width: 70px;
}

.dropdown {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #17171C;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
opacity: 0;
pointer-events: none;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 0;
z-index: 3;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dropdown.active {
opacity: 1;
pointer-events: auto;
height: 300px;
}

.dropdown-content {
opacity: 0;
transition: opacity 0.2s ease;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}

.dropdown.active .dropdown-content {
opacity: 1;
}

.dropdown-header {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #17171C;
z-index: 2;
flex-shrink: 0;
position: relative;
}

.dropdown-header::after {
content: '';
position: absolute;
bottom: 0;
left: 16px;
right: 16px;
height: 1px;
background: #3A3A46;
}

.back-button {
padding: 4px;
border-radius: 4px;
cursor: pointer;
}

.back-button:hover {
background: #23232A;
}

.dropdown-title {
color: #d9d9da;
font-size: 18px;
font-weight: 600;
}

#speed-options, #quality-options {
overflow-y: auto;
padding: 4px 16px 16px;
flex-grow: 1;
}

.option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
margin-bottom: 4px;
cursor: pointer;
border-radius: 6px;
}

.option:hover {
background: #23232A;
}

.option.selected {
background: #332E56;
}

.option-label {
color: #d9d9da;
font-size: 16px;
font-weight: 500;
}

.radio {
width: 14px;
height: 14px;
border: 1px solid #5A5A6C;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}

.option.selected .radio {
width: 20px;
height: 20px;
border-color: #B2A9FF;
}

.radio-inner {
display: none;
width: 15px;
height: 15px;
background: #B2A9FF;
border: 1px solid #17171C;
border-radius: 50%;
}

.option.selected .radio-inner {
display: block;
}
</style>
</head>
<body>
<div class="video-container">
<video id="video" class="video-js vjs-default-skin" controls
preload="auto"></video>

<div class="time-info">
<span id="startTime">0:00</span>
<div class="playback-speed" id="playbackSpeed">1x</div>
</div>
<div class="end-time" id="endTime">0:00</div>

<div class="progress-container" id="progress-container">


<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
</div>

<div class="controls">
<div class="left-controls">
<button class="control-button" id="playPauseBtn">
<i class="fas fa-play"></i>
</button>
<button class="control-button" id="rewindBtn">
<i class="fas fa-backward"></i>
</button>
<button class="control-button" id="forwardBtn">
<i class="fas fa-forward"></i>
</button>
<div class="volume-control">
<button class="control-button" id="muteBtn">
<i class="fas fa-volume-up"></i>
</button>
<div class="volume-slider">
<div class="volume-percentage">100%</div>
<div class="volume-track"></div>
<input type="range" id="volumeSlider" min="0" max="1"
step="0.01" value="1">
</div>
</div>
</div>

<div class="right-controls">
<button class="control-button" id="settingsBtn">
<i class="fas fa-cog"></i>
</button>
<button class="control-button" id="fullscreenBtn">
<i class="fas fa-expand"></i>
</button>
</div>
</div>

<div class="settings-menu">
<div class="container" id="menu-container">
<div class="menu-container" id="main-menu">
<div class="menu-content">
<div class="menu-item">
<span class="label">Speed</span>
<div class="value-selector"
onclick="toggleDropdown('speed', this)">
<span class="selected-value" id="speed-value">1</span>
<svg width="20" height="20" viewBox="0 0 20 20"
fill="none">
<path d="M8 14L12 10L8 6L8 14Z" fill="#B3B3BC"/>
</svg>
</div>
</div>

<div class="menu-item">
<span class="label">Quality</span>
<div class="value-selector"
onclick="toggleDropdown('quality', this)">
<span class="selected-value"
id="quality-value">Auto</span>
<svg width="20" height="20" viewBox="0 0 20 20"
fill="none">
<path d="M8 14L12 10L8 6L8 14Z" fill="#B3B3BC"/>
</svg>
</div>
</div>
</div>
</div>

<div id="speed-dropdown" class="dropdown">


<div class="dropdown-content">
<div class="dropdown-header">
<div class="back-button" onclick="closeDropdown('speed')">
<svg width="32" height="32" viewBox="0 0 32 32"
fill="none">
<path d="M18 21L13 16L18 11" stroke="#D9D9DA"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="dropdown-title">Speed</span>
</div>
<div id="speed-options"></div>
</div>
</div>

<div id="quality-dropdown" class="dropdown">


<div class="dropdown-content">
<div class="dropdown-header">
<div class="back-button"
onclick="closeDropdown('quality')">
<svg width="32" height="32" viewBox="0 0 32 32"
fill="none">
<path d="M18 21L13 16L18 11" stroke="#D9D9DA"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="dropdown-title">Quality</span>
</div>
<div id="quality-options"></div>
</div>
</div>
</div>
</div>
</div>
<script>
const player = videojs('video', {
controls: false,
fluid: true,
html5: {
vhs: {
overrideNative: true,
fastQualityChange: true,
useBandwidthFromLocalStorage: true
},
nativeAudioTracks: false,
nativeVideoTracks: false
}
});

// Function to auto-detect format and play video


function setVideoSource(url) {
let type = '';

if (url.endsWith('.mp4')) {
type = 'video/mp4';
} else if (url.endsWith('.webm')) {
type = 'video/webm';
} else if (url.endsWith('.ogv')) {
type = 'video/ogg';
} else if (url.includes('.m3u8')) {
type = 'application/x-mpegURL'; // HLS
} else if (url.includes('.mpd')) {
type = 'application/dash+xml'; // DASH
} else if (url.startsWith('rtmp://')) {
type = 'rtmp/mp4'; // RTMP
} else {
alert('Unsupported format. Please enter a valid video URL.');
return;
}

// Load new source into Video.js


player.src({ src: url, type: type });
player.load();
player.play();

// Auto-fetch quality levels


setTimeout(updateQualityOptions, 1000);
}

// Function to load video from input field


function loadVideo() {
const url = document.getElementById('videoURL').value.trim();
if (url) {
setVideoSource(url);
}
}

// Automatically load a default video (Replace with your video link)


setVideoSource('https://cdn.bitmovin.com/content/assets/art-of-motion-dash-hls-
progressive/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd');
const video = document.getElementById('video');
const progress = document.getElementById('progress');
const progressContainer = document.getElementById('progress-container');
const progressBar = document.querySelector('.progress-bar');
const playPauseBtn = document.getElementById('playPauseBtn');
const rewindBtn = document.getElementById('rewindBtn');
const forwardBtn = document.getElementById('forwardBtn');
const muteBtn = document.getElementById('muteBtn');
const volumeSlider = document.getElementById('volumeSlider');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const settingsBtn = document.getElementById('settingsBtn');
const settingsMenu = document.querySelector('.settings-menu');
const startTime = document.getElementById('startTime');
const endTime = document.getElementById('endTime');
const playbackSpeedDisplay = document.getElementById('playbackSpeed');
const videoContainer = document.querySelector('.video-container');
let isDragging = false;
let isDvrMode = false;
let endTimeUpdateInterval = null;
let timeUpdateInterval = null;
// Enhanced Volume Controls
const volumeControl = document.querySelector('.volume-control');
const volumeTrack = document.querySelector('.volume-track');
const volumePercentage = document.querySelector('.volume-percentage');
const volumeSliderContainer = document.querySelector('.volume-slider');
let isVolumeSliderVisible = false;

// Settings Menu Variables


const speedOptions = ['0.25', '0.5', '0.75', '1', '1.25', '1.5', '1.75', '2'];
let selectedSpeed = '1';
let selectedQuality = 'Auto';
let activeDropdown = null;
const mainMenu = document.getElementById('main-menu');

// Function to safely get video duration


function getVideoDuration() {
const duration = player.duration();
return !isNaN(duration) && isFinite(duration) ? duration : 0;
}

function handleSeek(clientX) {
const rect = progressBar.getBoundingClientRect();
const offsetX = clientX - rect.left;
const clampedOffsetX = Math.max(0, Math.min(offsetX, rect.width));
const clickPercent = clampedOffsetX / rect.width;

if (isDvrMode) {
const seekableStart = player.liveTracker.seekableStart();
const seekableEnd = player.liveTracker.seekableEnd();
const seekableRange = seekableEnd - seekableStart;

if (seekableRange > 0) {
const newTime = seekableStart + (clickPercent * seekableRange);
player.currentTime(newTime);
progress.style.width = `${clickPercent * 100}%`; // Update UI
immediately
startTime.textContent = formatTime(newTime); // Update time display
}
} else {
const duration = getVideoDuration();
if (duration > 0) {
const newTime = clickPercent * duration;
player.currentTime(newTime);
progress.style.width = `${clickPercent * 100}%`; // Update UI
immediately
startTime.textContent = formatTime(newTime); // Update time display
}
}
}

// Touch event handlers for progress bar


progressContainer.addEventListener('touchstart', (e) => {
isDragging = true;
handleSeek(e.touches[0].clientX);
e.preventDefault();
}, { passive: false });

progressContainer.addEventListener('touchmove', (e) => {


if (isDragging) {
handleSeek(e.touches[0].clientX);
e.preventDefault();
}
}, { passive: false });

progressContainer.addEventListener('touchend', () => {
isDragging = false;
});

// Mouse event handlers for progress bar


progressContainer.addEventListener('mousedown', (e) => {
isDragging = true;
handleSeek(e.clientX);
});

document.addEventListener('mousemove', (e) => {


if (isDragging) {
handleSeek(e.clientX);
}
});

document.addEventListener('mouseup', () => {
isDragging = false;
});

// Close menus when clicking/touching outside


function handleOutsideClick(e) {
if (!videoContainer.contains(e.target)) {
settingsMenu.style.display = 'none';
volumeSliderContainer.classList.remove('active');
if (activeDropdown) {
closeDropdown(activeDropdown);
}
return;
}
if (!volumeControl.contains(e.target)) {
volumeSliderContainer.classList.remove('active');
}

if (!settingsMenu.contains(e.target) && !settingsBtn.contains(e.target)) {


settingsMenu.style.display = 'none';
if (activeDropdown) {
closeDropdown(activeDropdown);
}
}
}

// Add both mouse and touch event listeners for outside clicks
document.addEventListener('mousedown', handleOutsideClick);
document.addEventListener('touchstart', (e) => {
handleOutsideClick(e.touches[0]);
});

function updateQualityOptions() {
const qualityLevels = player.qualityLevels();
const qualityOptions = document.getElementById('quality-options');
qualityOptions.innerHTML = '';

// Always add Auto option


qualityOptions.appendChild(createOption('quality', 'Auto'));

if (qualityLevels && qualityLevels.length > 0) {


const uniqueQualities = new Set();

for (let i = 0; i < qualityLevels.length; i++) {


const level = qualityLevels[i];
const height = level.height;
if (height && !uniqueQualities.has(height)) {
uniqueQualities.add(height);
const quality = Math.round(height) + 'p';
qualityOptions.appendChild(createOption('quality', quality));
}
}
}
}

function initializeDropdowns() {
const speedContainer = document.getElementById('speed-options');
speedContainer.innerHTML = '';

speedOptions.forEach(speed => {
speedContainer.appendChild(createOption('speed', speed));
});

updateQualityOptions();
}

function createOption(type, value) {


const div = document.createElement('div');
div.className = `option ${
(type === 'speed' && value === selectedSpeed) ||
(type === 'quality' && value === selectedQuality) ? 'selected' : ''
}`;
div.innerHTML = `
<span class="option-label">${value}</span>
<div class="radio">
<div class="radio-inner"></div>
</div>
`;
div.onclick = () => selectOption(type, value);
return div;
}

function toggleDropdown(type, element) {


const dropdown = document.getElementById(`${type}-dropdown`);

if (activeDropdown) {
document.getElementById(`${activeDropdown}-
dropdown`).classList.remove('active');
}

if (activeDropdown !== type) {


requestAnimationFrame(() => {
dropdown.classList.add('active');
activeDropdown = type;
mainMenu.classList.add('hidden');
});
} else {
activeDropdown = null;
mainMenu.classList.remove('hidden');
}
}

function closeDropdown(type) {
const dropdown = document.getElementById(`${type}-dropdown`);
dropdown.classList.remove('active');

requestAnimationFrame(() => {
mainMenu.classList.remove('hidden');
});

activeDropdown = null;
}

function selectOption(type, value) {


if (type === 'speed') {
selectedSpeed = value;
document.getElementById('speed-value').textContent = value;
player.playbackRate(parseFloat(value));
playbackSpeedDisplay.textContent = `${value}x`;
} else if (type === 'quality') {
selectedQuality = value;
document.getElementById('quality-value').textContent = value;

const qualityLevels = player.qualityLevels();

if (value === 'Auto') {


for (let i = 0; i < qualityLevels.length; i++) {
qualityLevels[i].enabled = true;
}
} else {
const targetHeight = parseInt(value);
for (let i = 0; i < qualityLevels.length; i++) {
const level = qualityLevels[i];
level.enabled = Math.round(level.height) === targetHeight;
}
}
}

const container = document.getElementById(`${type}-options`);


container.querySelectorAll('.option').forEach(option => {
option.classList.remove('selected');
if (option.querySelector('.option-label').textContent === value) {
option.classList.add('selected');
}
});

closeDropdown(type);
}

function formatTime(seconds) {
if (isNaN(seconds) || seconds === null || seconds === undefined) {
return "0:00";
}

const hours = Math.floor(seconds / 3600);


const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = Math.floor(seconds % 60);

if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:$
{remainingSeconds.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
}
function updateVolumeDisplay() {
const value = player.muted() ? 0 : player.volume();
const percentage = Math.round(value * 100);
volumePercentage.textContent = `${percentage}%`;
volumeTrack.style.width = `${percentage}px`;
volumePercentage.style.left = `${percentage + 10}px`;
}

function updateVolumeIcon() {
const value = player.volume();
if (player.muted() || value === 0) {
muteBtn.innerHTML = '<i class="fas fa-volume-mute"></i>';
} else if (value < 0.5) {
muteBtn.innerHTML = '<i class="fas fa-volume-down"></i>';
} else {
muteBtn.innerHTML = '<i class="fas fa-volume-up"></i>';
}
}

// Player Event Listeners


player.ready(() => {
updateVolumeDisplay();
updateVolumeIcon();
initializeDropdowns();
// Enhanced keyboard controls
document.addEventListener('keydown', (e) => {
// Only handle shortcuts if the video container is in focus or in
fullscreen
if (videoContainer.contains(document.activeElement) ||
document.fullscreenElement) {
switch(e.code) {
case 'Space':
e.preventDefault();
if (player.paused()) {
player.play();
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
} else {
player.pause();
playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
}
break;

case 'ArrowLeft':
e.preventDefault();
const newTimeBack = Math.max(0, player.currentTime() - 10);
player.currentTime(newTimeBack);
startTime.textContent = formatTime(newTimeBack);
break;

case 'ArrowRight':
e.preventDefault();
const duration = getVideoDuration();
const newTimeForward = Math.min(duration, player.currentTime()
+ 10);
player.currentTime(newTimeForward);
startTime.textContent = formatTime(newTimeForward);
break;

case 'ArrowUp':
e.preventDefault();
const newVolUp = Math.min(1, player.volume() + 0.1);
player.volume(newVolUp);
player.muted(false);
volumeSlider.value = newVolUp;
updateVolumeDisplay();
updateVolumeIcon();
break;

case 'ArrowDown':
e.preventDefault();
const newVolDown = Math.max(0, player.volume() - 0.1);
player.volume(newVolDown);
volumeSlider.value = newVolDown;
updateVolumeDisplay();
updateVolumeIcon();
break;

case 'KeyM':
e.preventDefault();
player.muted(!player.muted());
updateVolumeDisplay();
updateVolumeIcon();
break;
case 'KeyF':
e.preventDefault();
if (!document.fullscreenElement) {
videoContainer.requestFullscreen();
fullscreenBtn.innerHTML = '<i class="fas
fa-compress"></i>';
} else {
document.exitFullscreen();
fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>';
}
break;

case 'Comma':
if (e.shiftKey) { // < key
e.preventDefault();
const currentSpeed = player.playbackRate();
const speedIndex =
speedOptions.indexOf(currentSpeed.toString());
if (speedIndex > 0) {
const newSpeed = speedOptions[speedIndex - 1];
selectOption('speed', newSpeed);
}
}
break;

case 'Period':
if (e.shiftKey) { // > key
e.preventDefault();
const currentSpeed = player.playbackRate();
const speedIndex =
speedOptions.indexOf(currentSpeed.toString());
if (speedIndex < speedOptions.length - 1) {
const newSpeed = speedOptions[speedIndex + 1];
selectOption('speed', newSpeed);
}
}
break;
}
}
});
});
player.on('dispose', () => {
if (timeUpdateInterval) {
clearInterval(timeUpdateInterval);
timeUpdateInterval = null;
}
});

player.on('loadedmetadata', () => {
isDvrMode = player.liveTracker && player.liveTracker.isLive() &&
player.liveTracker.seekableEnd() > 0;

if (isDvrMode) {
console.log("DVR mode detected");
// Start the end time update interval for DVR mode
startEndTimeUpdates();
}
});

player.on('loadedmetadata', () => {
const duration = getVideoDuration();
endTime.textContent = formatTime(duration);
setTimeout(updateQualityOptions, 1000);
});

player.on('durationchange', () => {
const duration = getVideoDuration();
endTime.textContent = formatTime(duration);
});

player.on('qualitylevels', () => {
updateQualityOptions();
});

function startEndTimeUpdates() {
// Clear any existing interval
if (endTimeUpdateInterval) {
clearInterval(endTimeUpdateInterval);
}

// Update the end time every second, even when paused


endTimeUpdateInterval = setInterval(() => {
if (player.liveTracker) {
const liveEdge = player.liveTracker.liveCurrentTime();
const seekableEnd = player.liveTracker.seekableEnd();

// Update end time with the latest live edge time


endTime.textContent = formatTime(seekableEnd);
}
}, 1000);
}

player.on('timeupdate', () => {
if (!isDragging) {
const currentTime = player.currentTime();

if (isDvrMode) {
// In DVR mode, calculate progress based on seekable range
const seekableStart = player.liveTracker.seekableStart();
const seekableEnd = player.liveTracker.seekableEnd();
const seekableRange = seekableEnd - seekableStart;

if (seekableRange > 0) {
const progressPercent = ((currentTime - seekableStart) /
seekableRange) * 100;
progress.style.width = `${Math.min(progressPercent, 100)}%`; //
Prevent overflow
}

startTime.textContent = formatTime(currentTime);
endTime.textContent = formatTime(seekableEnd);
} else {
// Normal VOD behavior
const duration = getVideoDuration();
if (duration > 0) {
const progressPercent = (currentTime / duration) * 100;
progress.style.width = `${Math.min(progressPercent, 100)}%`; //
Prevent overflow
startTime.textContent = formatTime(currentTime);
}
}
}
});

player.on('durationchange', () => {
if (isDvrMode) {
if (player.liveTracker) {
const seekableEnd = player.liveTracker.seekableEnd();
endTime.textContent = formatTime(seekableEnd);
}
} else {
const duration = getVideoDuration();
endTime.textContent = formatTime(duration);
}
});

// Add this event listener for live status changes


player.on('liveupdatetimeout', () => {
if (isDvrMode && player.liveTracker) {
const seekableEnd = player.liveTracker.seekableEnd();
endTime.textContent = formatTime(seekableEnd);
}
});

// Control Event Listeners


playPauseBtn.addEventListener('click', () => {
if (player.paused()) {
player.play();
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
} else {
player.pause();
playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
}
});

rewindBtn.addEventListener('click', () => {
if (isDvrMode) {
const seekableStart = player.liveTracker.seekableStart();
const newTime = Math.max(seekableStart, player.currentTime() - 10);
player.currentTime(newTime);
} else {
const newTime = Math.max(0, player.currentTime() - 10);
player.currentTime(newTime);
}
});

forwardBtn.addEventListener('click', () => {
if (isDvrMode) {
const seekableEnd = player.liveTracker.seekableEnd();
const newTime = Math.min(seekableEnd, player.currentTime() + 10);
player.currentTime(newTime);
} else {
const duration = getVideoDuration();
const newTime = Math.min(duration, player.currentTime() + 10);
player.currentTime(newTime);
}
});

volumeSlider.addEventListener('input', (e) => {


player.volume(e.target.value);
player.muted(player.volume() === 0);
updateVolumeDisplay();
updateVolumeIcon();
});

muteBtn.addEventListener('click', (e) => {


e.stopPropagation();
isVolumeSliderVisible = !isVolumeSliderVisible;
volumeSliderContainer.classList.toggle('active');
});

fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
videoContainer.requestFullscreen();
fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>';
} else {
document.exitFullscreen();
fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>';
}
});

settingsBtn.addEventListener('click', () => {
settingsMenu.style.display = settingsMenu.style.display === 'block' ? 'none' :
'block';
});

// Handle play/pause state changes


player.on('play', () => {
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
});

player.on('pause', () => {
playPauseBtn.innerHTML = '<i class="fas fa-play"></i>';
});

// Handle fullscreen changes


document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
fullscreenBtn.innerHTML = '<i class="fas fa-compress"></i>';
} else {
fullscreenBtn.innerHTML = '<i class="fas fa-expand"></i>';
}
});

</script>
</body>
</html>

You might also like