I am facing a problem to disable the backward and forward keys action for HTML video player. Currently, the default behavior is we can move forward and backward from keys
Here is the code snippet
<style>
audio::-webkit-media-controls-timeline, video::-webkit-media-controls-timeline {
display: none;
}
video::-webkit-media-controls-current-time-display {
display: none;
}
video::-webkit-media-controls-time-remaining-display{
display: none;
}
</style><script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<video id="home_explainer_placeholder" class="video_placeholder" controls controlsList="nodownload">
<source src="http://re10tive.com/wp-content/uploads/2020/07/Arsenal-football-player-Aubameyang-driving-his-£3-Million-LaFerrari-in-Central-London.mp4" type="video/mp4">
</video>
<script>
Is there any way to achieve this or any kind of solution highly appreciated
Adding listeners for seeking and timeupdate should help you disable the seeking all together, example that I used was originally found here: How to disable seeking with HTML5 video tag ?
Unfortunately, I haven't found any solution that would do it without that annoying lag in the video. Unless of course you have some custom player that would allow you to disable skipping.
var video = document.getElementById('home_explainer_placeholder');
var supposedCurrentTime = 0;
video.addEventListener('timeupdate', function() {
if (!video.seeking) {
supposedCurrentTime = video.currentTime;
}
});
// prevent user from seeking
video.addEventListener('seeking', function() {
// guard agains infinite recursion:
// user seeks, seeking is fired, currentTime is modified, seeking is fired, current time is modified, ....
var delta = video.currentTime - supposedCurrentTime;
if (Math.abs(delta) > 0.01) {
console.log("Seeking is disabled");
video.currentTime = supposedCurrentTime;
}
});
// delete the following event handler if rewind is not required
video.addEventListener('ended', function() {
// reset state in order to allow for rewind
supposedCurrentTime = 0;
});
audio::-webkit-media-controls-timeline,
video::-webkit-media-controls-timeline {
display: none;
}
video::-webkit-media-controls-current-time-display {
display: none;
}
video::-webkit-media-controls-time-remaining-display {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<video id="home_explainer_placeholder" class="video_placeholder" controls controlsList="nodownload">
<source src="http://re10tive.com/wp-content/uploads/2020/07/Arsenal-football-player-Aubameyang-driving-his-£3-Million-LaFerrari-in-Central-London.mp4" type="video/mp4">
</video>
Related
I'm trying to make background audio remember the play/pause state. I have achieved pausing it and storing the pause state in the cookie, so when I go between page one and two it stays paused (vs autoplaying initially on both pages), but when I click play after that and refresh the page, even though the cookie is changing to "false" in the inspector, the audio doesn't play and the div colour doesn't change to green.
Live demo https://timcliss.webflow.io/audio-test
Codepen
Here is the code
External Scripts
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie#2.2.1/src/js.cookie.min.js"></script>
HTML
<div class="div" id="toggle"> PLAY/PAUSE </div>
<audio id="player" src="https://storage.yandexcloud.net/timcliss/About.mp3" autoplay loop>
CSS
.div {
background-color:green;
height: 50px;
color: white;
text-align: center;
}
JS
var audio = $('#player')[0];
audio.volume = 0.5;
var isMuted = Cookies.get('playerMuted'); // Receive stored cookie
if(isMuted) // If muted, stop the player
{
$('#player')[0].pause();
$('#toggle').css({'background-color': 'red'});
}
else
{
$('#player')[0].play();
$('#toggle').css({'background-color': 'green'});
}
// This function will be called when you click the toggle div
function toggleMute()
{
if(isMuted) // If player is muted, then unmute it
{
$('#player')[0].play();
$('#toggle').css({'background-color': 'green'});
isMuted = false;
} else // Else mute it
{
$('#player')[0].pause();
$('#toggle').css({'background-color': 'red'});
isMuted = true;
}
Cookies.set('playerMuted', isMuted); // Save current state of player
}
$('#toggle').click(toggleMute);
In new browsers audio autoplay is not supported and user has to explicitly play audio that is why play part is not working. You can read more about it here
(https://developers.google.com/web/updates/2017/09/autoplay-policy-changes).
Solved, turns out I just needed to use
if(isMuted=='true')
and then isMuted = 'false'; or isMuted = 'true';
Here is the updated pen
Works like a clock in Chrome with autoplay enabled! :)
Safari is more strict with the autoplay, so I added a check and only run this code for Chrome, and run standard player for Safari
var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if(isSafari){
alert('Is safari');
//Do something
} else {
alert('Is other browser');
// Do something
}
If someone finds a workaround for autoplay in Safari, or if you need help with implementing it in Chrome please let me know!
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)
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>
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);
}
});
I find that in HTML5 video when users perform a seek action by clicking the controls, there is no way to get the time before seeking.
What I want to do is whenever users seek video to a certain point, system will know exactly at what point before users seek. For example, if a user is watching video untill 2:00 and then click the control at 4:00, we need to keep track of the time 2:00. I have tried seeking and seeked event but can't get the time 2:00. It always return me the time 4:00. Is there any solution?
use a click event to track the click that leads to the seeking event.
Example using video.js
var clickTime = 0;
this.controlBar.on('click', function(){
clickTime = player.currentTime();
});
this.on('seeking', function(){
seekTime = clickTime;
//do stuff
});
You should be able to track this thanks to timeupdate event, with a short delay :
var video = document.querySelector('video');
video.BP = 0;
video.addEventListener('timeupdate', function(e){
var that = this;
(function(){
setTimeout(function(){
that.BP=that.currentTime;
}, 500);
}).call(that);}
);
video.addEventListener('seeking', function(e){
log('boringPart = '+this.BP+
" gone to "+this.currentTime)
})
function log(txt){document.querySelector('p').innerHTML = txt;}
<video controls="true" autoplay="true" height="200" width="600"><source type="video/ogg" src="http://media.w3.org/2010/05/sintel/trailer.ogv"><source type="video/mp4" src="http://media.w3.org/2010/05/sintel/trailer.mp4"></video>
<p style="position:absolute; top: 0.5em;left: 3em; color: #FFF;background:#000"></p>