Synchronizing 2 HTML5 videos
I have seen many platforms playing multiple videos where the presenter and their screen are separate streams but they are kept in sync. Native HTML5 video support was relatively new when I last worked on web development so I decided to experiment with multiple videos in an HTML5 document. Here is the basic HTML page:
<!DOCTYPE html>
<html lang="en">
<title>Two Video Synchronization Demo</title>
<body>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video -->
<video controls id="video1" src="flower.mp4"></video>
<video controls id="video2" src="nature.mp4"></video>
</body>
</html>
I ran this through the W3C Markup Validation Service and this snippet passed the check. My initial attempt closed the video tag using the <video ... />
style. The validator complained that “Self-closing syntax (/>) used on a non-void HTML element.” A search engine led me to Mozilla’s Void element page.
A void element is an element in HTML that cannot have any child nodes (i.e., nested elements or text nodes). Void elements only have a start tag; end tags must not be specified for void elements.
This means that non-void elements should have a closing tag. It’s also strange to me that the controls attribute is required to show controls but I guess it makes sense to let that be, well, controllable.
To synchronize the videos, we need to ensure that playing one video results in the other playing as well. Likewise for pausing, seeking, and changing the playback speed. My first attempt at this was to add JavaScript to the head of the HTML document. I’m not using jQuery or any other libraries. Therefore, I ran into exceptions because the DOM wasn’t ready when my script was running. Vanilla JavaScript equivalent of jQuery’s $.ready() – how to call a function when the page/DOM is ready for it [duplicate] suggested putting the script after all the HTML elements. All this was once second nature to me back in the IE6 days but this suggestion is good enough for my experiment. The final page now looks like this (with links to the relevant events, properties, and methods):
<!DOCTYPE html>
<html lang="en">
<title>Two Video Synchronization Demo</title>
<body>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video -->
<video controls id="video1" src="flower.mp4"></video>
<video controls id="video2" src="nature.mp4"></video>
<script>
const video1 = document.getElementById("video1");
const video2 = document.getElementById("video2");
// Handle the play event on each video to ensure that
// when one video is played the other plays as well.
video1.addEventListener("play", (event) => {
video2.play();
});
video2.addEventListener("play", (event) => {
video1.play();
});
// Handle the pause event on each video to ensure that
// when one video is paused the other is paused as well.
video1.addEventListener("pause", (event) => {
video2.pause();
});
video2.addEventListener("pause", (event) => {
video1.pause();
});
// Handle the ratechange event on each video to ensure that
// when the playback rate of one video is changed,
// the other is set to use the same rate.
video1.addEventListener("ratechange", (event) => {
video2.playbackRate = video1.playbackRate;
});
video2.addEventListener("ratechange", (event) => {
video1.playbackRate = video2.playbackRate;
});
// Handle the seek event on each video to ensure that
// when one video is seeked the other seeks to the same location.
video1.addEventListener("seeked", (event) => {
// Do not use fastSeek since we need precision.
if (video1.paused && video2.paused) {
video2.currentTime = video1.currentTime;
}
});
video2.addEventListener("seeked", (event) => {
if (video1.paused && video2.paused) {
video1.currentTime = video2.currentTime;
}
});
</script>
</body>
</html>
Notice that the last requirement on seeking is implemented slightly differently: I synchronized the current time in the videos only if they were both paused. This prevents weird behavior where the videos keep syncing to each other interrupting playback.
The last thing I wanted to do was lay out the videos so that one overlaps the other (in the top left or bottom right). I needed to add a style tag to the head of the document. I searched for how to put a div in the bottom right and the StackOverflow question How can I position my div at the bottom of its container? suggests absolute positioning in a container div. See the CSS in the final page below.
<!DOCTYPE html>
<html lang="en">
<title>Two Video Synchronization Demo</title>
<style>
#video-container {
position: relative;
}
#video1 {
width: 20%;
position:absolute;
top: 0px;
left: 0px;
}
#video2 {
width: 100%;
}
</style>
<body>
<div id="video-container">
<video controls id="video1" src="flower.mp4"></video>
<video controls id="video2" src="nature.mp4"></video>
</div>
<script>
const video1 = document.getElementById("video1");
const video2 = document.getElementById("video2");
// Handle the play event on each video to ensure that
// when one video is played the other plays as well.
video1.addEventListener("play", (event) => {
video2.play();
});
video2.addEventListener("play", (event) => {
video1.play();
});
// Handle the pause event on each video to ensure that
// when one video is paused the other is paused as well.
video1.addEventListener("pause", (event) => {
video2.pause();
});
video2.addEventListener("pause", (event) => {
video1.pause();
});
// Handle the ratechange event on each video to ensure that
// when the playback rate of one video is changed,
// the other is set to use the same rate.
video1.addEventListener("ratechange", (event) => {
video2.playbackRate = video1.playbackRate;
});
video2.addEventListener("ratechange", (event) => {
video1.playbackRate = video2.playbackRate;
});
// Handle the seek event on each video to ensure that
// when one video is seeked the other seeks to the same location.
video1.addEventListener("seeked", (event) => {
// Do not use fastSeek since we need precision.
if (video1.paused && video2.paused) {
video2.currentTime = video1.currentTime;
}
});
video2.addEventListener("seeked", (event) => {
if (video1.paused && video2.paused) {
video1.currentTime = video2.currentTime;
}
});
</script>
</body>
</html>
This was a useful HTML, JavaScript, and CSS refresher!
Leave a Reply