I'm a beginner with javascript and I managed to merge two functions controlling video play, however, they interfere with each other a bit.
This function is supposed to provide custom pause and play video controls
// VIDEO CONTROLS START
const video = document.getElementById("heroVideo"),
pauseControl = document.getElementById("pauseControl"),
playControl = document.getElementById("playControl"),
playPauseButton = document.getElementById("playPauseButton");
playPauseButton.addEventListener("click", function () {
if (video.paused) {
video.play();
playPauseButton.classList.remove("play");
playPauseButton.classList.add("pause");
pauseControl.style.display = "unset";
playControl.style.display = "none";
} else {
video.pause();
playPauseButton.classList.remove("pause");
playPauseButton.classList.add("play");
pauseControl.style.display = "none";
playControl.style.display = "unset";
video.removeAttribute("controls");
}
});
// VIDEO CONTROLS END
And this one pauses and plays video automaticaly when out of user's view field (so it does not play on the background)
// VIDEO OFFLOAD START
function videoOffload() {
// Get all video elements with the "video-offload" class
const videos = document.querySelectorAll(".video-offload");
// Function to handle the Intersection Observer for a single video
function handleVideoIntersection(video) {
// Define the options for the Intersection Observer
const options = {
root: null, // Use the viewport as the root
rootMargin: "0px", // No margin
threshold: 0.1, // 10% of the target element must be visible to trigger
};
// Callback function when the video enters or exits the viewport
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// The video is in the viewport, so play it and show controls
video.play();
video.removeAttribute("controls");
} else {
// The video is out of the viewport, so pause it and hide controls
video.pause();
}
});
};
// Create an Intersection Observer with the specified options and callback for the current video
const observer = new IntersectionObserver(callback, options);
// Start observing the current video element
observer.observe(video);
}
// Iterate over all video elements and apply the Intersection Observer
videos.forEach((video) => {
video.setAttribute("autoplay", "false"); // Disable autoplay initially
// Apply Intersection Observer to the current video
handleVideoIntersection(video);
});
}
// Call the videoOffload function to initialize
videoOffload();
// VIDEO OFFLOAD END
Video control button
<button id="playPauseButton" class="play">
<i id="playControl" class="fa-regular fa-circle-play fa-xl"></i>
<i id="pauseControl" class="fa-regular fa-circle-pause fa-xl"></i>
</button>
The issue is, as soon as I pause the video manually (utilising the first function) scroll out of the field of view of the video and then scroll back to the video again it resumes playing (the second function overcalls the first one). That is fine, however, the video control icons are reversed and you need to click twice - to pause and then play according to the pause & play function logic (because they change on click and the video was triggered by another function).
They are both in the same js file. I tried to keep them separately but the issue persisted.
I also tried to inject the icons to the HTML according to the IF statements - playPauseButton.innerHTML = "<i id='pauseControl' class='fa-regular fa-circle-pause fa-xl'></i>";
that did not work however.
The solution crossing my mind is to have them as indicators of the current state of the video - playing or paused, so no matter how the video was triggered, it is going to display the right icon OR make those two functions not interfere with each other.
What do you think is the best solution and code?
Consider refactoring the controls management to an event listener on the video
element. This will be the most accurate source-of-truth to whether the video is playing or not:
function updateControls() {
if (video.paused) {
playPauseButton.classList.remove("pause");
playPauseButton.classList.add("play");
pauseControl.style.display = "none";
playControl.style.display = "unset";
video.removeAttribute("controls");
} else {
playPauseButton.classList.remove("play");
playPauseButton.classList.add("pause");
pauseControl.style.display = "unset";
playControl.style.display = "none";
}
}
video.addEventListener('pause', updateControls);
video.addEventListener('play', updateControls);
playPauseButton.addEventListener("click", function() {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
// VIDEO CONTROLS START
const video = document.getElementById("heroVideo"),
pauseControl = document.getElementById("pauseControl"),
playControl = document.getElementById("playControl"),
playPauseButton = document.getElementById("playPauseButton");
function updateControls() {
if (video.paused) {
playPauseButton.classList.remove("pause");
playPauseButton.classList.add("play");
pauseControl.style.display = "none";
playControl.style.display = "unset";
video.removeAttribute("controls");
} else {
playPauseButton.classList.remove("play");
playPauseButton.classList.add("pause");
pauseControl.style.display = "unset";
playControl.style.display = "none";
}
}
video.addEventListener('pause', updateControls);
video.addEventListener('play', updateControls);
playPauseButton.addEventListener("click", function() {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
// VIDEO CONTROLS END
// VIDEO OFFLOAD START
function videoOffload() {
// Get all video elements with the "video-offload" class
const videos = document.querySelectorAll(".video-offload");
// Function to handle the Intersection Observer for a single video
function handleVideoIntersection(video) {
// Define the options for the Intersection Observer
const options = {
root: null, // Use the viewport as the root
rootMargin: "0px", // No margin
threshold: 0.1, // 10% of the target element must be visible to trigger
};
// Callback function when the video enters or exits the viewport
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// The video is in the viewport, so play it and show controls
video.play();
video.removeAttribute("controls");
} else {
// The video is out of the viewport, so pause it and hide controls
video.pause();
}
});
};
// Create an Intersection Observer with the specified options and callback for the current video
const observer = new IntersectionObserver(callback, options);
// Start observing the current video element
observer.observe(video);
}
// Iterate over all video elements and apply the Intersection Observer
videos.forEach((video) => {
video.setAttribute("autoplay", "false"); // Disable autoplay initially
// Apply Intersection Observer to the current video
handleVideoIntersection(video);
});
}
// Call the videoOffload function to initialize
videoOffload();
// VIDEO OFFLOAD END
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<video id="heroVideo" class="video-offload" loop controls>
<source src="https://v3.cdnpk.net/videvo_files/video/free/2013-08/large_preview/hd0992.mp4" type="video/mp4">
<source src="https://v3.cdnpk.net/videvo_files/video/free/2013-08/large_watermarked/hd0992_preview.mp4" type="video/mp4">
</video>
<button id="playPauseButton" class="play">
<i id="playControl" class="fa-regular fa-circle-play fa-xl"></i>
<i id="pauseControl" class="fa-regular fa-circle-pause fa-xl"></i>
</button>
<div style="height: 150vh"></div>