Optimised Javascript for HTML5 Video player - javascript

I have a HTML5 video player on my site with three videos. The code I found only supported one video per webpage but I managed to do a hack to make it work with multiple videos per page. The hack is pretty inefficient and I am sure there is a more elegant way to implement this. Here is how my code looks:
// Video
var video = document.getElementById("video");
var video2 = document.getElementById("video2");
var video3 = document.getElementById("video3");
// Buttons
var playButton = document.getElementById("play-pause");
var playButton2 = document.getElementById("play-pause2");
var playButton3 = document.getElementById("play-pause3");
// Event listener for the play/pause button 1
playButton.addEventListener("click", function () {
if (video.paused == true) {
// Play the video
video.play();
// Update the button text to 'Pause'
document.getElementById("play-pause").className = "pause";
} else {
// Pause the video
video.pause();
// Update the button text to 'Play'
document.getElementById("play-pause").className = "play";
}
});
// Event listener for the play/pause button 2
playButton2.addEventListener("click", function () {
if (video2.paused == true) {
// Play the video
video2.play();
// Update the button text to 'Pause'
document.getElementById("play-pause2").className = "pause";
} else {
// Pause the video
video2.pause();
// Update the button text to 'Play'
document.getElementById("play-pause2").className = "play";
}
});
// Event listener for the play/pause button 3
playButton3.addEventListener("click", function () {
if (video3.paused == true) {
// Play the video
video3.play();
// Update the button text to 'Pause'
document.getElementById("play-pause3").className = "pause";
} else {
// Pause the video
video3.pause();
// Update the button text to 'Play'
document.getElementById("play-pause3").className = "play";
}
});
}
As you can see I went down the route of simply duplicating the event listener and creating new variables. There must be a way to select the target based on the specific Div selected, maybe through specifying the path of the class? I.e. .container .video1 .play?
The second problem I am having is reverting the pause button and poster image back to the original state after the video has finished playing.
Here is the site where the code and content is placed:
http://www.glowdigital.net/index.php?page=snap-inspire
Any help would be much appreciated!
Thank you

There must be a way to select the target based on the specific Div selected, maybe through specifying the path of the class?
Yes there are better ways of event handling a group of elements.
Event delegation is when the event listener is registered on an ancestor element that the target elements have in common.
Arrays can be used by keeping track of an index number.
The following demo will address the latter.
The second problem I am having is reverting the pause button and poster image back to the original state after the video has finished playing.
Many ways to handle that. The demo demonstrates the use of the CSS ::after pseudo-element and add/removeClass() methods.
I also added exclusive playback capabilities as well. If a player is playing and
another player starts to play, the player that was playing will stop playing.
Details are commented in demo
Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title>HTML5 Video Player Group - Exclusive Playback</title>
<style>
button {
color: rgba(66, 200, 150, 1);
background: none;
border: 0;
font: 400 24px/1 Verdana;
outline: 0;
cursor: pointer;
}
button:hover,
button:active,
button:focus {
color: #0F3
}
.play.toPause::after {
content: '⏸';
font: inherit;
}
.play.toPlay::after {
content: '▶';
font: inherit;
}
.stop::after {
content: '⏹';
font: inherit;
}
</style>
</head>
<body>
<header> </header>
<main id="media">
<figure class="vSection">
<video id="v0" width="320" height="auto" poster="http://www.glowdigital.net/images/projects/snap-inspire-1.jpg">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-1.webm" type="video/webm">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-1.mp4" type="video/mp4">
</video>
<figcaption class="controls">
<button type="button" class="play toPlay"></button>
<button type="button" class="stop"></button>
</figcaption>
</figure>
<figure class="vSection">
<video id="v1" width="320" height="auto" poster="http://www.glowdigital.net/images/projects/snap-inspire-2.jpg">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-2.webm" type="video/webm">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-2.mp4" type="video/mp4">
</video>
<figcaption class="controls">
<button type="button" class="play toPlay"></button>
<button type="button" class="stop"></button>
</figcaption>
</figure>
<figure class="vSection">
<video id="v2" width="320" height="auto" poster="http://www.glowdigital.net/images/projects/snap-inspire-3.jpg">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-3.webm" type="video/webm">
<source src="http://www.glowdigital.net/images/projects/snap-inspire-3.mp4" type="video/mp4">
</video>
<figcaption class="controls">
<button type="button" class="play toPlay"></button>
<button type="button" class="stop"></button>
</figcaption>
</figure>
</main>
<footer> </footer>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
// Gather all <video> into a NodeList then convert it into an array
var videos = Array.from(document.querySelectorAll('video'));
/* map() through array assigning an id to each <video>
|| vArray is returned; an array of <video id='..'>
*/
var vArray = videos.map(function(vid, idx) {
var player = document.getElementById(vid.id);
return player;
});
// When a button.play is clicked...
$('.play').on('click', function(e) {
// Get it's index number
var idx = $('.play').index(this);
/* Invoke functiom excPlay passing the vArray and idx
|| It stops any player if it's playing and the prepares
|| the specified player to play. See bottom of source
|| for details
*/
var player = excPlay(vArray, idx);
// If paused or ended...
if (player.paused || player.ended) {
// Play video
player.play();
// Switch the classes for the all buttons to the paused state
$('.play').removeClass('toPause').addClass('toPlay');
// Switch this button to the playing state
$(e.target).addClass('toPause').removeClass('toPlay');
}
//...Otherwise...
else {
// Pause the video
player.pause();
// Switch all buttons to the paused state
$('.play').removeClass('toPause').addClass('toPlay');
}
// Click thebutton.stop...
$('.stop').on('click', function(e) {
// Get index number
var index = $('.stop').index(this);
// See line 73
var player = excPlay(vArray, index);
// Pause the video
player.pause();
// Set video's time back to 0
player.currentTime = 0;
});
// If a video ends...
$('video').on('ended', function() {
// Reset the time
this.currentTime = 0;
// Get its poster value...
var image = this.poster;
// ,,,then set it
this.poster = image;
// Set all buttons to pause state
$('.play').removeClass('toPause').addClass('toPlay');
});
/* Pass in an array of video objects and the index number of
|| thevideo you want to play.
*/
function excPlay(array, exclude) {
// map() the array of videos; on each loop...
array.map(function(video, index) {
// If the video isn't the video you want to play...
if (index != exclude) {
// Get the video's poster
var image = video.poster;
// Set the time back to the beginning
video.currentTime = 0;
// Pauase video
video.pause();
// Reset the poster image
video.poster = image;
}
});
// Toggle the classes on the play button
$('.play').removeClass('toPause').addClass('toPlay');
// Return the selected player or nothing
return array[exclude] || null;
}
});
</script>
</body>
</html>

Related

how do I loop video until user clicks. then play secondary video and revert to first video once secondary has finished, javascript, HTML

Trying to get a trailer looped until the user clicks somewhere on the screen after which the lecture video is supposed to play once and then revert to the trailer.
Managed to find a similar question here on StackOverflow, unfortunately, I'm unable to get it to fully work.
The trailer loops and when I click on the button the lecture video plays but does not start the trailer video and instead just loops the lecture video.
What am I doing wrong here?
<video id="mp4Source" autoplay loop controls width="100%" src="/assets/trailer.mp4" >
</video>
<button type="button" name="button" onclick="playLectureVid()"> Play Lecture Vid</button>
function playLectureVid(){
var video = document.getElementById("mp4Source");
video.src = '/assets/lecture.mp4';
video.load();
video.play();
video.addEventListener("ended", function(e) {
e.currentTarget.removeEventListener(e.type, handler);
video.src = '/assets/trailer.mp4';
video.load();
video.play();
});
}
Just toggle between two videos.
var trailer = document.getElementById("trailer");
var lecture = document.getElementById("lecture");
function playLectureVid() {
lecture.style.display = 'block';
trailer.style.display = 'none';
trailer.pause();
lecture.play();
}
lecture.addEventListener("ended", function(e) {
trailer.style.display = 'block';
lecture.style.display = 'none';
lecture.pause();
trailer.play();
});
video {
width: 400px;
}
#lecture {
display: none;
}
<center>
<button type="button" name="button" onclick="playLectureVid()"> Play Lecture Vid</button>
<br>
<video id="trailer" autoplay muted loop controls width="100%" src="https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/Sample-MP4-Video-File-for-Testing.mp4"></video>
<video id="lecture" controls width="100%" src="https://samplelib.com/lib/preview/mp4/sample-5s.mp4"></video>
</center>
Instead of using video.addEventListener("ended",, you can use video.onend = () => {code here}. Then, because this is called every time the video ends, insert these lines to the top of your function:
if (trailerLoaded) return
trailerLoaded = true
and add this at the top of playLectureVid:
var trailerLoaded = false
This will make it so that the video will not be reloaded every time it ends.

Get event when <video> is playing and when is paused when the video is loaded from a javascript file

I have a html page that contains a javascript file. This .js file insert into the html the video player. I need to detect when the video is playing and when the video is paused, is possible to have an alert? For example: alert('Video is playing'); alert('Video is paused');
Unfortunately the video tag doesn't have any class or ID, for this the most of codes I've tried from stackoverflow.com doesn't work for me as I don't have any selector.
<video width="600px" height="600px" preload="metadata"><source src="video.mp4" type="video/mp4"></video>
This code is not in the html file but is loaded into by an javascript file.
No jQuery is needed.
You add Event Listeners to every video element. If media events would bubble, then you only needed to listen to those event types on the documentElement and switch regarding the event's target node.
Since this is not possible you need to add the listeners to all existing video elements by walking the HTMLCollection ([...document.getElementsByTagName('video')].forEach(/* ... */)) and you need to add those listeners to every future video element. For this you need a MutationObserver
In this example I have two existing videos and two are added by JS (one with autoplay and one without respectively). I react on play and pause event and change the class of the video accordingly (play: green, paused: pink)
"use strict";
console.clear();
{
const events = ['play', 'pause']
// Called upon play or pause event of video element
function listener(e) {
e.target.classList.remove('playing');
e.target.classList.remove('paused');
switch(e.type) {
case 'play':
e.target.classList.add('playing');
break;
case 'pause':
e.target.classList.add('paused');
break;
}
}
// Add Event Listeners to existing video elements
[...document.getElementsByTagName('video')].forEach(v => {
events.forEach(ev => v.addEventListener(ev, listener))
})
// called by MutationObserver
// Adds Eventlisteners to newly inserted video elements
function react(mutationList, observer) {
[...mutationList].forEach(mr => {
mr.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.tagName.toLowerCase() === 'video') {
events.forEach(ev => node.addEventListener(ev, listener))
}
})
})
}
const observer = new MutationObserver(react);
const config = { childList: true, subtree: true };
observer.observe(document.documentElement, config);
}
document.body.insertAdjacentHTML('beforeend', "<video controls src=\"https://s3-us-west-2.amazonaws.com/s.cdpn.io/96344/SampleVideo_360x240_1mb.mp4\" width=\"360\" height=\"240\" autoplay muted playsinline></video>\n")
document.body.insertAdjacentHTML('beforeend', "<video controls src=\"https://s3-us-west-2.amazonaws.com/s.cdpn.io/96344/SampleVideo_360x240_1mb.mp4\" width=\"360\" height=\"240\"></video>\n")
video {
background-color: gray;
border: 10px solid black;
}
video.playing {
background-color: lightgreen;
border-color: green;
}
video.paused {
background-color: pink;
border-color: red;
}
<video controls src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/96344/SampleVideo_360x240_1mb.mp4" width="360" height="240" autoplay muted playsinline></video>
<video controls src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/96344/SampleVideo_360x240_1mb.mp4" width="360" height="240"></video>
you can refer to W3C HTML DOM Video Object
if you want to see if video is playng a solution is set a function in setInterval
var currTimeChk = 0;
setInterval(function(){
var currTime = document.getElementById("vid").currentTime;
if(currTime != currTimeChk ){
currTimeChk = currTime;
/* video is playing */
alert('Video is playing');
}else{
/* video is paused*/
alert('Video is paused');
}
},100)

Custom HTML5 video controls not working when mp4 is the only source

Problem: I only have a single video source (mp4) as I am trying to add custom controls to a tumblr video.
If there is only mp4 as the source video.duration is returned as NaN.
As a test when using 3 sources (mp4/webm/ogg) then it works, so video.duration must only be returned from wemb or ogg.
JSFIDDLE 1 with only a single mp4 as the source (so not working).
JSFIDDLE 2 with 3 source files which is working.
HTML
<div id="video-container">
<!-- Video -->
<video id="video" width="640" height="365" poster="https://68.media.tumblr.com/tumblr_oj7dx3dAJL1vf3pu7_smart1.jpg">
<source src="https://www.tumblr.com/video_file/t:kuWOGAOEWjotDIJeZpO4yw/155341819278/tumblr_oj7dx3dAJL1vf3pu7/480" type="video/mp4">
</video>
<!-- Video Controls -->
<div id="video-controls">
<button type="button" id="play-pause" class="play">Play</button>
<input type="range" id="seek-bar" value="0">
<button type="button" id="mute">Mute</button>
<input type="range" id="volume-bar" min="0" max="1" step="0.1" value="1">
<button type="button" id="full-screen">Full-Screen</button>
</div>
</div>
<span id="currentTime">0</span>
<span id="duration">0</span>
JS:
// Video
var video = document.getElementById("video");
// Buttons
var playButton = document.getElementById("play-pause");
var muteButton = document.getElementById("mute");
var fullScreenButton = document.getElementById("full-screen");
// Sliders
var seekBar = document.getElementById("seek-bar");
var volumeBar = document.getElementById("volume-bar");
// Event listener for the play/pause button
playButton.addEventListener("click", function() {
if (video.paused == true) {
// Play the video
video.play();
// Update the button text to 'Pause'
playButton.innerHTML = "Pause";
} else {
// Pause the video
video.pause();
// Update the button text to 'Play'
playButton.innerHTML = "Play";
}
});
...
// Update the seek bar as the video plays
video.addEventListener("timeupdate", function() {
// Calculate the slider value
var value = (100 / video.duration) * video.currentTime;
// Update the slider value
seekBar.value = value;
});
...
The JS is mainly taken from here: http://blog.teamtreehouse.com/building-custom-controls-for-html5-videos
What I have tried:
video.addEventListener('loadedmetadata',function(){
console.log(video.duration); //returns NaN
});
Some jQuery
$(document).ready(function(){
$("#video").on("timeupdate", function(event){
onTrackedVideoFrame(this.currentTime, this.duration);
});
});
function onTrackedVideoFrame(currentTime, duration){
$("#current").text(currentTime);
$("#duration").text(duration);
}
Another attempt:
window.setInterval(function(t){
if (video.readyState > 0) {
var duration = $('#duration').get(0);
var vid_duration = Math.round(video.duration);
duration.firstChild.nodeValue = vid_duration;
clearInterval(t);
}
},500);
Also
<video preload="metadata">
None of these solutions seemed to have worked.
I've also looked at this question on SO. Which has a voted answer that is untested.
To get the duration of the video you have to wait for the metadata portion of the video source to load (this can vary significantly depending on how the video has been encoded - ideally a 2-pass encode that relocates the MOOV atom to the beginning has been used so that can be extracted quickly).
By listening to the loadedmetadata event on the video object your code will know when it is safe to query that.
In the code below you will see I register the event handler using inline javascript (or could be in the document.onload) which in turn calls the function to extract the duration when the value is available. Any code that needs to know the value should then be run.
I've also added preload="auto" just for personal preference as it helps pre-buffer content in some browsers/devices, that's not needed for the event to work in most scenarios.
<video preload="auto" id="video" width="640" height="365" poster="https://68.media.tumblr.com/tumblr_oj7dx3dAJL1vf3pu7_smart1.jpg">
<source src="https://www.tumblr.com/video_file/t:kuWOGAOEWjotDIJeZpO4yw/155341819278/tumblr_oj7dx3dAJL1vf3pu7/480" type="video/mp4">
</video>
<script>
var vid = document.getElementById("video"
vid.addEventListener('loadedmetadata', getDuration, false);
function getDuration() {
console.log(vid.duration)
}
</script>

Playing next video by calling a function that needs the event passed to it

The question of how can JavaScript call the next video has been asked as I have thoroughly researched this topic before posting. However, in the situation that I will describe, a video is running after an onclick event calls the function: function(e) and I need to be able to call the next video by calling the function(e) again once the code detects an ended event by using the addEventListener method.
I have posted all my code below. In addition I have added comments to illustrate what I “think” is happening. I am brand new to JavaScript and have recently retired, so I have had time to research the Internet to try and piece together what is taking place. Please feel free to clarify my commented code as I would appreciate being set straight on what I have wriiten.
I have also made an attempt to put the code on jsfiddle
https://jsfiddle.net/dan2004/tuouh36d/
but it only seems to function in Chrome.
My main question to everyone is in regard to the statement:
document.getElementById('videoPlayer').addEventListener('ended',handler,false);
If I call a function outside of the function, I can issue a message via an alert, but if I call the function that I am in (handler(e)) I cannot get the next video to run. Somehow I need to be able to call the handler(e) function and send it the next onclick event.
Thanks for any help.
var video_playlist, links, i, videotarget, filename, video, source;
// Gets the video object from <div id="player"> in the HTML
video_playlist = document.getElementById("player");
// "links" is an array which contains all the <a href> tags in the <div id="playlist">.
// This div is located within <div id="player"> and contains a clickable playlist.
links = video_playlist.getElementsByTagName('a');
// This "for loop" scrolls through the links array of <a href> attributes and
// assigns an "onclick = handler" event to each one.
for (i=0; i<links.length; i++) {
links[i].onclick = handler;
};
// e is an [object MouseEvent]
function handler(e) {
// The handler function receives the full path to the mp4 file when it is clicked on in the playlist.
// The "preventDefault" method stops the function from following that path.
// This is so the data in the path may be parsed and manipulated below
e.preventDefault();
// videotarget grabs the href attribute of the item clicked on
// in the "playlist". This is the full path to the video file.
videotarget = this.getAttribute("href");
// Through the use of substr, filename grabs that part of the href which
// does not include the extension.
filename = videotarget.substr(0, videotarget.lastIndexOf('.')) || videotarget;
// The variable "video" contains the video object. This is obtained by using document.querySelector().
// This document method uses the css id class, #player, and grabs the [object HTML VideoElement].
// The [object HTML VideoElement] resides in <div id="player">
video = document.querySelector("#player video");
//Removes the poster attribute in the video tag
video.removeAttribute("poster");
// The source variable is used to hold an array of all the source tags in the
// [object HTML VideoElement] from <div id="player">.
source = document.querySelectorAll("#player video source");
// Using the substring extracted from the user's click choice in <div id="playlist">
// the three file types for browsers to choose from are concatenated to the string.
// These thre source files are then stored under the video object located in <div id="player">.
source[0].src = filename + ".mp4";
source[1].src = filename + ".webm";
source[2].src = filename + ".ogv";
// The video object will load the appropriate source[].src file then play it
video.load();
video.play();
// When the video ends the following statement will call the function test()
// which will then broadcast the alert message "Video Ended"
document.getElementById('videoPlayer').addEventListener('ended',test,false);
// This statement will not call the handler function in order to play the next video selection.
// document.getElementById('videoPlayer').addEventListener('ended',handler,false);
}; // function handler(e)
function test(){
alert("Video Ended");
};
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Video Playlist Tutorial</title>
<style>
body {font-family:Arial, Helvetica, sans-serif;background:#fff}
.center {text-align:center;width:640px;margin:0 auto;}
#player {background:#000; padding:10px;width:640px;margin:0 auto;border-radius:10px;}
#player video {width:640px;}
#playlist {background:#333;list-style:none;padding:0;margin:0; width:640px;}
#playlist h1 {font: 24px Arial, Helvetica, sans-serif; color:#FFF; font-weight:bold;padding:5px 2px;margin:0;}
#playlist a {color:#eeeedd;background:#333;padding:10px 5px;display:block;text-decoration:none;border-bottom:1px solid #222;}
#playlist a:hover {text-decoration:none; background:#999;color:#000}
</style>
</head>
<body>
<div id="player"> <!-- Assign id to video tag for ended event and to call handler to play next video -->
<video id="videoPlayer" controls="controls" width="640" height="360" preload="auto" autoplay >
<source src="1.mp4" type="video/mp4" />
<source src="1.webm" type="video/webm" />
<source src="1.ogv" type="video/ogg" />
</video>
<div id="playlist">
<h1>Videos</h1>
Bear <br>
Buck Bunny
</div>
</div>
<script>
</script>
</body>
</html>
Seeing as your handler() function relies on this being the clicked element, you can't just call that function, you'd have to also set the this-value to the next anchor etc. and trigger the event in a way that makes it look like it was triggered by the actual element.
An easier way to do this, would be to just decouple the playing logic, get the next element, and play the video
var video_playlist = document.getElementById("player");
var links = video_playlist.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
(function(j) {
links[j].addEventListener('click', function(e) {
handler.apply(this, [e, j])
});
})(i);
};
function handler(e, index) {
e.preventDefault();
var videotarget = this.getAttribute("href");
play(videotarget, index).addEventListener('ended', function() {
index = (++index) >= links.length ? 0 : index;
play(links[index].getAttribute("href"), index);
});
};
function play(videotarget) {
var filename = videotarget.substr(0, videotarget.lastIndexOf('.')) || videotarget;
var video = document.querySelector("#player video");
var source = document.querySelectorAll("#player video source");
video.removeAttribute("poster");
source[0].src = filename + ".mp4";
source[1].src = filename + ".webm";
source[2].src = filename + ".ogv";
video.load();
video.play();
return video;
};
<div id="player">
<!-- Assign id to video tag for ended event and to call handler to play next video -->
<video id="videoPlayer" controls="controls" width="640" height="360" preload="auto" autoplay>
<source src="1.mp4" type="video/mp4" />
<source src="1.webm" type="video/webm" />
<source src="1.ogv" type="video/ogg" />
</video>
<div id="playlist">
<h1>Videos</h1>
Bear
Buck Bunny
</div>
</div>

Stepping through a video file with reveal.js

Problem and question
In a reveal.js presentation, I want to include a long video file. I want to have the playblack stop at certain positions, so that I have time to explain to the audience what they’re seeing. Then, I want to have the playback continue when I click. How can I do this?
Unsuccessful attempts so far
My attempts are as follows. I split the video file into parts 1.webm, 2.webm, 3.webm and so on, such that each part ends where I want to have a break. My idea then is to
Override the keydown event of Reveal.js so that it doesn’t go to the next slide, but instead executes my Javascript. How can I do something like this?
<div class="slides">
<section class="video-stepper">
<video>
<source data-src="1.webm" type="video/webm" />
</video>
</section>
</div>
<script>
$(function() {
// How can I do this?
Reveal.addEventListener('click', function(event) {
if ($(event.currentSlide).hasClass('video-stepper')) {
event.preventDefault();
// change 'src' of the video element and start the playback.
}
});
});
</script>
Use fragments and autoplay the video when it is shown:
<div class="slides">
<section class="video-stepper">
<video class="fragment current-visible video-step">
<source data-src="1.webm" type="video/webm" />
</video>
<video class="fragment current-visible video-step">
<source data-src="2.webm" type="video/webm" />
</video>
<video class="fragment current-visible video-step">
<source data-src="3.webm" type="video/webm" />
</video>
</section>
</div>
<script>
$(function() {
Reveal.addEventListener('fragmentshown', function(event) {
if ($(event.fragment).hasClass('video-step')) {
event.fragment.play();
}
});
});
</script>
And some CSS taken from the question Hide reveal.js fragments after their appearance, so that the fragments stack on top of each other:
.fragment.current-visible.visible:not(.current-fragment) {
display: none;
height:0px;
line-height: 0px;
font-size: 0px;
}
However, this comes with some fading in and out, which looks bad. How can I avoid the fading?
When entering the video slide, you can basically disable reveal.js by calling Reveal.disableEventListeners(), then bind your own logic to the keydown event until you’ve stepped through all videos, before enabling reveal.js again with Reveal.addEventListeners().
Some additional effort is required to avoid flickering when transitioning to the next video. You can add a new <video> element with the new video, place it on top of the current <video> with the help of CSS z-index, play the new video, then remove the old.
HTML
<section class="video-stepper">
<!-- Unlike the other <video> element, this one is not absolutely
positioned. We hide it with CSS, but use it to reserve space
on the slide and compute the optimal width and height. -->
<video class="placeholder stretch">
<source src="1.webm">
</video>
<video class="video-step" data-sources='["1.webm","2.webm","3.webm"]'></video>
</section>
CSS
.video-stepper {
position: relative;
}
video.video-step {
position: absolute;
top: 0;
left: 0;
}
video.video-step.front {
z-index: 10;
}
video.placeholder {
visibility: hidden;
}
Javascript
This is a bit lengthy, but works as desired.
Reveal.addEventListener('slidechanged', function(event) {
if ($(event.currentSlide).hasClass('video-stepper')) {
// When we enter a slide with a step-by-step video, we stop reveal.js
// from doing anything. Below, we define our own keystroke handler.
Reveal.removeEventListeners();
// Set the width and height of the video so that it fills the slide.
var stretcher = $(event.currentSlide).find('video.placeholder').get(0);
var video = $(event.currentSlide).find('video.video-step').get(0);
video.setAttribute('width', stretcher.getAttribute('width'));
video.setAttribute('height', stretcher.getAttribute('height'));
// Convert the data-sources attribute to an array of strings. We will
// iterate through the array with current_video_index.
var sources = JSON.parse(video.getAttribute('data-sources'));
var current_video_index = 0;
// Add a <source> element to the video and set the 'src' to
// the first video.
var source = document.createElement('source');
source.setAttribute('src', sources[0]);
video.appendChild(source);
document.addEventListener('keydown', function step_through_videos(event) {
if (event.which == 39) {
// right arrow key: show next video
// For the next video, create a new <video> element
// and place it on top of the old <video> element.
// Then load and play the new. This avoids flickering.
var new_video = $(video).clone().get(0);
var new_video_source = $(new_video).children('source').get(0);
new_video_source.src = sources[current_video_index];
new_video.load();
$(new_video).addClass('front video-step');
$(new_video).insertAfter(video);
new_video.play();
// Wait a little before removing the old video.
new Promise((resolve) => setTimeout(resolve, 500)).then(function() {
video.remove();
video = new_video;
$(video).removeClass('front');
});
current_video_index = current_video_index + 1;
event.preventDefault();
} else if (event.which == 37) {
// left arrow key: return the counter to previous video
current_video_index = current_video_index - 1;
event.preventDefault();
}
if (0 > current_video_index || current_video_index >= sources.length) {
// Reinstall reveal.js handlers.
document.removeEventListener('keydown', step_through_videos, true);
Reveal.addEventListeners();
console.log('Added reveal.js event listeners.');
}
}, true);
}
});

Categories