Refactor video overlay implementation to improve control handling and visibility

This commit is contained in:
Sebastiaan de Schaetzen 2025-06-17 10:05:39 +02:00
parent c8b6798f9b
commit e3424ad77b

View File

@ -86,7 +86,7 @@
height: 100%; height: 100%;
background-color: black; background-color: black;
z-index: 10000; /* Above other page content */ z-index: 10000; /* Above other page content */
display: flex; display: none; /* Initially hidden, JS will change to flex */
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
@ -159,175 +159,180 @@
} }
let currentVideo = null; let currentVideo = null;
let videoElement = null; let videoElement = null;
let overlayContainer = null; let overlayContainer = null;
let inactivityTimer = null; let inactivityTimer = null;
let backButton = null; let backButton = null;
let customControls = null; let customControls = null;
let playPauseButton = null; let playPauseButton = null;
let seekBar = null; let seekBar = null;
function exitVideoPlayback() { function exitVideoPlayback() {
if (videoElement && videoElement.pause) { if (videoElement && videoElement.pause) {
videoElement.pause(); videoElement.pause();
} }
clearTimeout(inactivityTimer); clearTimeout(inactivityTimer);
if (overlayContainer) { if (overlayContainer) {
overlayContainer.css('cursor', 'default'); overlayContainer.css('cursor', 'default');
overlayContainer.off('mousemove.inactivityControls'); overlayContainer.off('mousemove.inactivityControls'); // Remove session-specific listener
// Detach document-level fullscreen listener specific to this playback overlayContainer.hide(); // Hide instead of remove
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange); }
overlayContainer.remove();
}
// Attempt to exit fullscreen if the document is still in fullscreen mode
// and our container was likely the one in fullscreen.
if (document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement) {
if (document.exitFullscreen) {
document.exitFullscreen().catch(() => {});
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
function showControlsAndResetTimer() { $(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
backButton.removeClass('button-hidden');
customControls.removeClass('controls-hidden');
overlayContainer.css('cursor', 'default'); // Show cursor
clearTimeout(inactivityTimer);
inactivityTimer = setTimeout(function() {
backButton.addClass('button-hidden');
customControls.addClass('controls-hidden');
overlayContainer.css('cursor', 'none'); // Hide cursor
}, 3000); // 3 seconds
}
// --- Moved Helper Function Definitions --- if (videoElement) {
function handlePlayPauseClick() { $(videoElement).off('.currentVideo'); // Remove all namespaced video events
if (videoElement.paused) { videoElement.src = ""; // Clear the source
videoElement.play(); videoElement.load(); // Important to ensure the video unloads
} else { }
videoElement.pause(); if (seekBar) {
} seekBar.val(0);
} seekBar.css('background', ''); // Reset seek bar style
}
updatePlayPauseButtonState(); // Update button to show play icon usually
function updatePlayPauseButtonState() { // Attempt to exit fullscreen
if (!playPauseButton) return; // Ensure button exists if (document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement) {
if (videoElement.paused) { if (document.exitFullscreen) {
playPauseButton.text('►'); // Play icon document.exitFullscreen().catch(() => {});
} else { } else if (document.mozCancelFullScreen) {
playPauseButton.text('❚❚'); // Pause icon document.mozCancelFullScreen();
} } else if (document.webkitExitFullscreen) {
} document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
}
function handleVideoLoadedMetadata() { function showControlsAndResetTimer() {
if (!seekBar) return; // Ensure seekbar exists if (backButton) backButton.removeClass('button-hidden');
seekBar.attr('max', videoElement.duration); if (customControls) customControls.removeClass('controls-hidden');
} if (overlayContainer) overlayContainer.css('cursor', 'default');
function handleVideoTimeUpdate() { clearTimeout(inactivityTimer);
if (!seekBar) return; // Ensure seekbar exists inactivityTimer = setTimeout(function() {
seekBar.val(videoElement.currentTime); if (backButton) backButton.addClass('button-hidden');
const percentage = (videoElement.currentTime / videoElement.duration) * 100; if (customControls) customControls.addClass('controls-hidden');
seekBar.css('background', `linear-gradient(to right, #3498db ${percentage}%, #555 ${percentage}%)`); if (overlayContainer) overlayContainer.css('cursor', 'none');
} }, 3000);
}
function handleSeekBarInput() { function handlePlayPauseClick() {
if (!seekBar) return; // Ensure seekbar exists if (videoElement) {
videoElement.currentTime = seekBar.val(); if (videoElement.paused) {
} videoElement.play();
} else {
videoElement.pause();
}
}
}
function handleFullscreenChange() { function updatePlayPauseButtonState() {
const isActuallyFullscreen = document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement; if (!playPauseButton || !videoElement) return;
if (!isActuallyFullscreen && overlayContainer && overlayContainer.length && !overlayContainer.is(document.fullscreenElement)) { if (videoElement.paused) {
if (overlayContainer.is(':visible')) { playPauseButton.text('►');
exitVideoPlayback(); } else {
} playPauseButton.text('❚❚');
} }
} }
function handleVideoLoadedMetadata() {
if (seekBar && videoElement) {
seekBar.attr('max', videoElement.duration);
}
}
function handleVideoTimeUpdate() {
if (seekBar && videoElement && videoElement.duration) { // Check videoElement.duration to prevent NaN
seekBar.val(videoElement.currentTime);
const percentage = (videoElement.currentTime / videoElement.duration) * 100;
seekBar.css('background', `linear-gradient(to right, #3498db ${percentage}%, #555 ${percentage}%)`);
} else if (seekBar) {
seekBar.val(0); // Reset if duration is not available
}
}
function handleSeekBarInput() {
if (videoElement && seekBar) {
videoElement.currentTime = seekBar.val();
}
}
function handleFullscreenChange() {
const isActuallyFullscreen = document.fullscreenElement || document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement;
if (!isActuallyFullscreen && overlayContainer && overlayContainer.is(':visible')) {
// If fullscreen exited and overlay was visible, treat as exiting video playback
// This helps if user presses Esc.
exitVideoPlayback();
}
}
$(document).ready(function() { $(document).ready(function() {
// Initialize global selectors for static elements
overlayContainer = $("#video-overlay-container");
videoElement = overlayContainer.find(".video-player")[0];
backButton = overlayContainer.find("#back-button");
customControls = overlayContainer.find("#custom-video-controls");
playPauseButton = customControls.find("#custom-play-pause-button");
seekBar = customControls.find("#custom-seek-bar");
// Pre-set all thumbnails to a placeholder state // Pre-set all thumbnails to a placeholder state
$(".video-thumbnail").attr("src", "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 16 9'%3E%3C/svg%3E"); $(".video-thumbnail").attr("src", "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 16 9'%3E%3C/svg%3E");
$.getJSON("/api/homepage", function(data) { $.getJSON("/api/homepage", function(data) {
currentVideo = data.currentVideo; currentVideo = data.currentVideo;
// previousVideo = data.previousVideo; $("#current-thumb").attr("src", currentVideo.thumbnail);
// nextVideo = data.nextVideo; hideLoader();
//
// // Set thumbnails
$("#current-thumb").attr("src", currentVideo.thumbnail);
// $("#prev-thumb").attr("src", previousVideo.thumbnail);
// $("#next-thumb").attr("src", nextVideo.thumbnail);
// Hide loader and show videos
hideLoader();
}) })
$("#play-button").click(function() { // Attach event listeners that can be set up once for static controls
if (currentVideo && currentVideo.url) { if (playPauseButton) playPauseButton.click(handlePlayPauseClick);
// Create the overlay container if (seekBar) seekBar.on('input', handleSeekBarInput);
overlayContainer = $("<div id='video-overlay-container'></div>").appendTo("body"); if (backButton) backButton.click(exitVideoPlayback);
// Create video element and append to container
videoElement = $("<video autoplay class='video-player'></video>") // Removed 'controls' attribute
.attr("src", currentVideo.url)
.appendTo(overlayContainer)[0];
// Create back button and append to container $("#play-button").click(function() {
backButton = $("<button id='back-button'>Back</button>") if (currentVideo && currentVideo.url && videoElement && overlayContainer) {
.appendTo(overlayContainer); // Set video source and load
videoElement.src = currentVideo.url;
videoElement.load();
// Create custom video controls container // --- Setup Inactivity Controls (session-specific) ---
customControls = $("<div id='custom-video-controls'></div>").appendTo(overlayContainer); showControlsAndResetTimer(); // Initial call
playPauseButton = $("<button id='custom-play-pause-button'>❚❚</button>").appendTo(customControls); // Assign to global var
seekBar = $("<input type='range' id='custom-seek-bar' value='0' />").appendTo(customControls); // Assign to global var
// Remove local inactivityTimer declaration, it's global now updatePlayPauseButtonState(); // Initial state for the button
// let inactivityTimer;
// --- Setup Inactivity Controls ---
overlayContainer.on('mousemove.inactivityControls', showControlsAndResetTimer);
showControlsAndResetTimer(); // Initial call
// --- Setup Custom Video Controls --- // Show overlay and request fullscreen
playPauseButton.click(handlePlayPauseClick); overlayContainer.css('display', 'flex');
$(videoElement).on('play', updatePlayPauseButtonState); const containerEl = overlayContainer[0];
$(videoElement).on('pause', updatePlayPauseButtonState); if (containerEl.requestFullscreen) {
$(videoElement).on('loadedmetadata', handleVideoLoadedMetadata); containerEl.requestFullscreen().catch(err => console.error("Error attempting to enable full-screen mode:", err));
$(videoElement).on('timeupdate', handleVideoTimeUpdate); } else if (containerEl.mozRequestFullScreen) {
seekBar.on('input', handleSeekBarInput); containerEl.mozRequestFullScreen();
// Initial call to set play/pause button state, especially if autoplay is effective } else if (containerEl.webkitRequestFullscreen) {
updatePlayPauseButtonState(); containerEl.webkitRequestFullscreen();
} else if (containerEl.msRequestFullscreen) {
containerEl.msRequestFullscreen();
}
// --- Setup Video End and Back Button --- videoElement.play().catch(err => console.error("Error attempting to play video:", err));
$(videoElement).on('ended', exitVideoPlayback); }
backButton.click(exitVideoPlayback); });
// --- Setup Fullscreen Change Listener --- // --- Setup Custom Video Controls Events (session-specific for video element) ---
// Detach any previous listeners first (using the specific named handler for accuracy if needed, but general works too) $(videoElement).on('play.currentVideo', updatePlayPauseButtonState);
$(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback'); $(videoElement).on('pause.currentVideo', updatePlayPauseButtonState);
$(document).on('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange); $(videoElement).on('loadedmetadata.currentVideo', handleVideoLoadedMetadata);
$(videoElement).on('timeupdate.currentVideo', handleVideoTimeUpdate);
$(videoElement).on('ended.currentVideo', exitVideoPlayback);
// --- Request Fullscreen --- // --- Setup Fullscreen Change Listener (session-specific) ---
const containerEl = overlayContainer[0]; $(document).off('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange); // Ensure clean state
if (containerEl.requestFullscreen) { $(document).on('fullscreenchange.videoPlayback webkitfullscreenchange.videoPlayback mozfullscreenchange.videoPlayback MSFullscreenChange.videoPlayback', handleFullscreenChange);
containerEl.requestFullscreen();
} else if (containerEl.mozRequestFullScreen) { /* Firefox */ overlayContainer.on('mousemove.inactivityControls', showControlsAndResetTimer);
containerEl.mozRequestFullScreen();
} else if (containerEl.webkitRequestFullscreen) { /* Chrome, Safari & Opera */
containerEl.webkitRequestFullscreen();
} else if (containerEl.msRequestFullscreen) { /* IE/Edge */
containerEl.msRequestFullscreen();
}
// NOTE: exitVideoPlayback was defined above.
// The original snippet showed its definition here, it's now grouped with other helpers.
}
});
}) })
</script> </script>
</head> </head>
@ -339,5 +344,15 @@
<img id="next-thumb" class="video-thumbnail side-video" alt="Next Video"> <img id="next-thumb" class="video-thumbnail side-video" alt="Next Video">
<div class="play-button" id="play-button"></div> <div class="play-button" id="play-button"></div>
</div> </div>
<!-- STATIC VIDEO OVERLAY STRUCTURE -->
<div id="video-overlay-container">
<video class="video-player"></video> <!-- no src, no autoplay, no controls initially -->
<button id="back-button">Back</button>
<div id="custom-video-controls">
<button id="custom-play-pause-button"></button> <!-- Default to Play icon -->
<input type="range" id="custom-seek-bar" value="0" />
</div>
</div>
</body> </body>
</html> </html>